diff --git a/CHANGELOG.md b/CHANGELOG.md index cb453bd3b..7a319f83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). from CLI, TUI, and ACP. Instruction re-discovery is skipped in a `--safe-mode` session so `/cd` cannot silently re-load project instructions and defeat the flag. Conversation history, active goals, and skill state are preserved across the switch (#6032). +- **zeph-tools/zeph-llm**: foundational type plumbing for multimodal MCP `ContentBlock::Image` + passthrough (spec-072, #6229 P0 of 4). Added `ToolOutput.media: Vec` + (empty by default, `#[derive(Default)]` on `ToolOutput`), a new `zeph-tools → zeph-llm` + dependency edge, and a redacting `impl Debug for ImageData` (`[image: {mime}, {n} bytes]`, + never the raw bytes), hardening a latent/reachable-by-construction Debug-derive leak on + `ImageData` as defense-in-depth for the existing user-upload image path (no production call + site currently Debug-formats `ImageData`/`Message`; debug-dump output uses `Serialize`, not + `Debug`). Purely additive — `media` is never populated yet, so this PR changes no runtime + behavior. Decode/validate/attach logic, the ephemeral persistence strip, vision-tier routing, + and the config/CLI/TUI surface are deferred to P1–P3 follow-up issues. ### Changed diff --git a/Cargo.lock b/Cargo.lock index 95bc1c5fb..76369bc89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11403,6 +11403,7 @@ dependencies = [ "zeph-common", "zeph-config", "zeph-db", + "zeph-llm", "zeph-sanitizer", ] diff --git a/crates/zeph-acp/src/fs.rs b/crates/zeph-acp/src/fs.rs index cbb18062a..59905ead8 100644 --- a/crates/zeph-acp/src/fs.rs +++ b/crates/zeph-acp/src/fs.rs @@ -366,6 +366,7 @@ impl zeph_tools::ToolExecutor for AcpFileExecutor { locations: Some(vec![resolved_str]), raw_response, claim_source: Some(zeph_tools::ClaimSource::FileSystem), + ..Default::default() })) } "write_file" if self.can_write => { @@ -491,6 +492,7 @@ impl AcpFileExecutor { locations: Some(vec![params.path]), raw_response: None, claim_source: Some(zeph_tools::ClaimSource::FileSystem), + ..Default::default() })) } @@ -562,6 +564,7 @@ impl AcpFileExecutor { locations: Some(vec![params.path]), raw_response: None, claim_source: Some(zeph_tools::ClaimSource::FileSystem), + ..Default::default() })) } @@ -632,6 +635,7 @@ impl AcpFileExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::FileSystem), + ..Default::default() })) } } diff --git a/crates/zeph-acp/src/terminal.rs b/crates/zeph-acp/src/terminal.rs index 75774b011..88f14a2fc 100644 --- a/crates/zeph-acp/src/terminal.rs +++ b/crates/zeph-acp/src/terminal.rs @@ -287,6 +287,7 @@ impl AcpShellExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::Shell), + ..Default::default() })) } @@ -449,6 +450,7 @@ impl zeph_tools::ToolExecutor for AcpShellExecutor { locations: None, raw_response, claim_source: Some(zeph_tools::ClaimSource::Shell), + ..Default::default() })) } diff --git a/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs b/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs index df91a975a..df7da5da4 100644 --- a/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs +++ b/crates/zeph-bench/src/loaders/tau2_bench/envs/airline.rs @@ -238,6 +238,7 @@ impl ToolExecutor for AirlineEnv { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs b/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs index bf1c3a21a..671ac54e3 100644 --- a/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs +++ b/crates/zeph-bench/src/loaders/tau2_bench/envs/retail.rs @@ -263,6 +263,7 @@ impl ToolExecutor for RetailEnv { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-core/src/agent/speculative/mod.rs b/crates/zeph-core/src/agent/speculative/mod.rs index c428b537e..0389733e0 100644 --- a/crates/zeph-core/src/agent/speculative/mod.rs +++ b/crates/zeph-core/src/agent/speculative/mod.rs @@ -329,6 +329,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-core/src/agent/tests/agent_tests/common.rs b/crates/zeph-core/src/agent/tests/agent_tests/common.rs index f6198b2cd..496939340 100644 --- a/crates/zeph-core/src/agent/tests/agent_tests/common.rs +++ b/crates/zeph-core/src/agent/tests/agent_tests/common.rs @@ -293,6 +293,7 @@ impl MockToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; Self::new(vec![Ok(Some(output))]) } diff --git a/crates/zeph-core/src/agent/tests/agent_tests/lifecycle_tests.rs b/crates/zeph-core/src/agent/tests/agent_tests/lifecycle_tests.rs index 7539dc74a..007afc63c 100644 --- a/crates/zeph-core/src/agent/tests/agent_tests/lifecycle_tests.rs +++ b/crates/zeph-core/src/agent/tests/agent_tests/lifecycle_tests.rs @@ -301,6 +301,7 @@ async fn agent_handles_tool_execution_success() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]); let agent_channel = MockChannel::new(vec!["execute tool".to_string()]); @@ -604,6 +605,7 @@ async fn agent_handles_tool_output_with_error_marker() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), Ok(None), ]); @@ -630,6 +632,7 @@ async fn agent_handles_empty_tool_output() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]); let mut agent = Agent::new(provider, channel, registry, None, 5, executor); @@ -759,6 +762,7 @@ async fn agent_processes_multi_turn_tool_execution() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]); let mut agent = Agent::new( @@ -799,6 +803,7 @@ async fn agent_respects_max_shell_iterations() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))); } let executor = MockToolExecutor::new(outputs); diff --git a/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs b/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs index d55f08330..4ab235515 100644 --- a/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs +++ b/crates/zeph-core/src/agent/tests/confirmation_propagation_tests.rs @@ -44,6 +44,7 @@ impl DagAwareToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() } } } diff --git a/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs b/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs index 0a45ead99..a45b73e36 100644 --- a/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs +++ b/crates/zeph-core/src/agent/tests/inline_tool_loop_tests.rs @@ -37,6 +37,7 @@ impl CallableToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]) } @@ -174,6 +175,7 @@ async fn multiple_tool_iterations_before_text() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), Ok(Some(ToolOutput { tool_name: "test_tool".into(), @@ -186,6 +188,7 @@ async fn multiple_tool_iterations_before_text() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), ]); @@ -249,6 +252,7 @@ async fn network_deny_wrapped_executor_blocks_fetch_before_reaching_inner() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -356,6 +360,7 @@ async fn elicitation_event_during_tool_execution_is_handled() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs b/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs index 30ee14630..3c4858c8f 100644 --- a/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs +++ b/crates/zeph-core/src/agent/tests/shutdown_summary_tests.rs @@ -356,6 +356,7 @@ async fn filter_stats_metrics_increment_on_normal_native_tool_path() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -432,6 +433,7 @@ impl zeph_tools::executor::ToolExecutor for TwoToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } else { Ok(Some(zeph_tools::executor::ToolOutput { @@ -453,6 +455,7 @@ impl zeph_tools::executor::ToolExecutor for TwoToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } diff --git a/crates/zeph-core/src/agent/tool_execution/tests/boundary_and_classifier_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/boundary_and_classifier_tests.rs index 3522c38d4..9453b75d6 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/boundary_and_classifier_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/boundary_and_classifier_tests.rs @@ -773,6 +773,7 @@ impl ToolExecutor for FixedOutputExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } @@ -817,6 +818,7 @@ impl ToolExecutor for FirstFailsExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } diff --git a/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs index e19bbf328..ab87633b0 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/native_tests.rs @@ -687,6 +687,7 @@ impl ToolExecutor for AlwaysOkSpecExec { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs index 0dbbe9240..8fd7a44d5 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/parallel_and_handle_tests.rs @@ -45,6 +45,7 @@ impl zeph_tools::executor::ToolExecutor for DelayExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } @@ -90,6 +91,7 @@ impl zeph_tools::executor::ToolExecutor for FailingNthExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } @@ -420,6 +422,7 @@ async fn handle_tool_result_with_output_returns_true() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; let result = agent .handle_tool_result("response", Ok(Some(output))) @@ -451,6 +454,7 @@ async fn handle_tool_result_empty_output_returns_false() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; let result = agent .handle_tool_result("response", Ok(Some(output))) @@ -482,6 +486,7 @@ async fn handle_tool_result_error_prefix_triggers_anomaly_error() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; // reflection_used = true so reflection path is skipped agent.services.learning_engine.mark_reflection_used(); @@ -518,6 +523,7 @@ async fn handle_tool_result_stderr_prefix_triggers_anomaly_error() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; agent.services.learning_engine.mark_reflection_used(); let result = agent diff --git a/crates/zeph-core/src/agent/tool_execution/tests/retry_and_skill_env_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/retry_and_skill_env_tests.rs index d7e90af5c..f35ae3b6b 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/retry_and_skill_env_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/retry_and_skill_env_tests.rs @@ -176,6 +176,7 @@ async fn handle_tool_result_sends_output_when_streamed_true() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; agent .handle_tool_result("response", Ok(Some(output))) @@ -212,6 +213,7 @@ async fn handle_tool_result_fenced_emits_tool_start_then_output_via_loopback() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; agent .handle_tool_result("response", Ok(Some(output))) @@ -291,6 +293,7 @@ async fn handle_tool_result_locations_propagated_to_loopback_event() { locations: Some(vec!["/src/main.rs".to_owned()]), raw_response: None, claim_source: None, + ..Default::default() }; agent .handle_tool_result("response", Ok(Some(output))) @@ -344,6 +347,7 @@ async fn handle_tool_result_display_is_raw_body_not_markdown_wrapped() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; agent .handle_tool_result("response", Ok(Some(output))) diff --git a/crates/zeph-core/src/agent/tool_execution/tests/sanitize_and_native_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/sanitize_and_native_tests.rs index 6f8b5497c..da7dd762a 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/sanitize_and_native_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/sanitize_and_native_tests.rs @@ -1048,6 +1048,7 @@ impl ToolExecutor for TransientThenOkExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } diff --git a/crates/zeph-core/src/agent/tool_execution/tests/tafc_and_record_outcomes_tests.rs b/crates/zeph-core/src/agent/tool_execution/tests/tafc_and_record_outcomes_tests.rs index 812e9315c..0476f5d80 100644 --- a/crates/zeph-core/src/agent/tool_execution/tests/tafc_and_record_outcomes_tests.rs +++ b/crates/zeph-core/src/agent/tool_execution/tests/tafc_and_record_outcomes_tests.rs @@ -161,6 +161,7 @@ impl ToolExecutor for FixedOutputExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } @@ -209,6 +210,7 @@ impl ToolExecutor for FirstSuccessExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } @@ -250,6 +252,7 @@ impl ToolExecutor for DispatchingExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), "tool-retryable" if idx == 1 => Err(ToolError::Execution(std::io::Error::new( std::io::ErrorKind::TimedOut, @@ -266,6 +269,7 @@ impl ToolExecutor for DispatchingExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), _ => Err(ToolError::Execution(std::io::Error::new( std::io::ErrorKind::TimedOut, @@ -546,6 +550,7 @@ async fn skipped_output_processing_does_not_reset_cross_iteration_utility_window locations: None, raw_response: None, claim_source: None, + ..Default::default() }; // Two LLM iterations, each: note_action records a non-ToolCall (Retrieve) @@ -2203,6 +2208,7 @@ impl ToolExecutor for RecordingExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } } diff --git a/crates/zeph-core/src/agent/tool_execution/tier_loop.rs b/crates/zeph-core/src/agent/tool_execution/tier_loop.rs index 15dde3e19..d2605e88c 100644 --- a/crates/zeph-core/src/agent/tool_execution/tier_loop.rs +++ b/crates/zeph-core/src/agent/tool_execution/tier_loop.rs @@ -215,6 +215,7 @@ impl Agent { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) }) } else { @@ -2877,6 +2878,7 @@ fn skipped_output( locations: None, raw_response: None, claim_source: None, + ..Default::default() } } @@ -3955,6 +3957,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]) .with_definitions(vec![test_tool_def()]); let mut agent = Agent::new(provider, channel, registry, None, 5, executor); @@ -4096,6 +4099,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]) .with_definitions(vec![test_tool_def()]); let mut agent = Agent::new(provider, channel, registry, None, 5, executor); @@ -4292,6 +4296,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() }))]) .with_definitions(vec![test_tool_def()]) // Consumes >1s of wall time on the first (only) dispatched retry, so the second @@ -4912,6 +4917,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), ]); let mut agent = Agent::new(provider, channel, registry, None, 5, executor); @@ -5017,6 +5023,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), ]); let mut agent = Agent::new(provider, channel, registry, None, 5, executor); diff --git a/crates/zeph-core/src/agent/tool_orchestrator.rs b/crates/zeph-core/src/agent/tool_orchestrator.rs index 2c067d292..cde2eb202 100644 --- a/crates/zeph-core/src/agent/tool_orchestrator.rs +++ b/crates/zeph-core/src/agent/tool_orchestrator.rs @@ -635,6 +635,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; o.result_cache.put(CacheKey::new("read", 1), output); o.result_cache.get(&CacheKey::new("read", 1)); // hit diff --git a/crates/zeph-core/src/memory_tools.rs b/crates/zeph-core/src/memory_tools.rs index 660c2db1b..c4379d3f4 100644 --- a/crates/zeph-core/src/memory_tools.rs +++ b/crates/zeph-core/src/memory_tools.rs @@ -184,6 +184,7 @@ impl ToolExecutor for MemoryToolExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::Memory), + ..Default::default() })) } "memory_save" => { @@ -245,6 +246,7 @@ impl ToolExecutor for MemoryToolExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::Memory), + ..Default::default() })) } _ => Ok(None), diff --git a/crates/zeph-core/src/overflow_tools.rs b/crates/zeph-core/src/overflow_tools.rs index b1020a49f..a769e08bc 100644 --- a/crates/zeph-core/src/overflow_tools.rs +++ b/crates/zeph-core/src/overflow_tools.rs @@ -92,6 +92,7 @@ impl ToolExecutor for OverflowToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } Ok(None) => Err(ToolError::Execution(std::io::Error::other( diff --git a/crates/zeph-core/src/skill_invoker.rs b/crates/zeph-core/src/skill_invoker.rs index 01faeb8d3..6a2dbc83b 100644 --- a/crates/zeph-core/src/skill_invoker.rs +++ b/crates/zeph-core/src/skill_invoker.rs @@ -155,6 +155,7 @@ fn make_output(summary: String) -> ToolOutput { locations: None, raw_response: None, claim_source: None, + ..Default::default() } } diff --git a/crates/zeph-core/src/skill_loader.rs b/crates/zeph-core/src/skill_loader.rs index ef8d37774..eb7e58f5b 100644 --- a/crates/zeph-core/src/skill_loader.rs +++ b/crates/zeph-core/src/skill_loader.rs @@ -125,6 +125,7 @@ impl ToolExecutor for SkillLoaderExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-core/src/testing.rs b/crates/zeph-core/src/testing.rs index 0c95923bf..1a027789a 100644 --- a/crates/zeph-core/src/testing.rs +++ b/crates/zeph-core/src/testing.rs @@ -175,6 +175,7 @@ impl MockToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; Self::new(vec![Ok(Some(output))]) } diff --git a/crates/zeph-core/tests/turn_lifecycle.rs b/crates/zeph-core/tests/turn_lifecycle.rs index 467e4291c..53c2a1e0a 100644 --- a/crates/zeph-core/tests/turn_lifecycle.rs +++ b/crates/zeph-core/tests/turn_lifecycle.rs @@ -121,6 +121,7 @@ impl SingleToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; Self { output: Arc::new(Mutex::new(Some(output))), diff --git a/crates/zeph-index/src/mcp_server.rs b/crates/zeph-index/src/mcp_server.rs index de920e152..b59334e77 100644 --- a/crates/zeph-index/src/mcp_server.rs +++ b/crates/zeph-index/src/mcp_server.rs @@ -461,6 +461,7 @@ impl ToolExecutor for IndexMcpServer { locations: None, raw_response: Some(result), claim_source: Some(ClaimSource::CodeSearch), + ..Default::default() })) } diff --git a/crates/zeph-llm/src/lib.rs b/crates/zeph-llm/src/lib.rs index c53181b58..725c0e155 100644 --- a/crates/zeph-llm/src/lib.rs +++ b/crates/zeph-llm/src/lib.rs @@ -116,7 +116,7 @@ pub use error::LlmError; pub use extractor::Extractor; pub use masking::{MaskedProvider, OutboundMasker, mask_messages}; pub use openai::{CompletionTokensParam, OpenAiConfig}; -pub use provider::{ChatExtras, ChatStream, LlmProvider, StreamChunk, ThinkingBlock}; +pub use provider::{ChatExtras, ChatStream, ImageData, LlmProvider, StreamChunk, ThinkingBlock}; pub use provider_dyn::LlmProviderDyn; pub use router::aware::RouterAware; pub use router::coe::{CoeConfig, CoeMetrics, CoeRouter}; diff --git a/crates/zeph-llm/src/provider.rs b/crates/zeph-llm/src/provider.rs index d7fea965f..9cf750de3 100644 --- a/crates/zeph-llm/src/provider.rs +++ b/crates/zeph-llm/src/provider.rs @@ -340,7 +340,7 @@ impl MessagePart { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] /// Raw image payload for vision-capable providers. /// /// The `data` field is serialized as a Base64 string. `mime_type` must be a valid @@ -351,6 +351,16 @@ pub struct ImageData { pub mime_type: String, } +impl std::fmt::Debug for ImageData { + /// Redacts the raw image bytes — only the MIME type and byte count are printed. + /// + /// `data` can carry arbitrary externally-sourced bytes (MCP tool results, user uploads); + /// a derived `Debug` would dump the full payload into logs/panics. + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[image: {}, {} bytes]", self.mime_type, self.data.len()) + } +} + mod serde_bytes_base64 { use base64::{Engine, engine::general_purpose::STANDARD}; use serde::{Deserialize, Deserializer, Serializer}; @@ -1044,6 +1054,17 @@ mod tests { } } + #[test] + fn test_image_data_debug_redacts_bytes() { + let img = ImageData { + data: vec![0xAB, 0xCD, 0xEF], + mime_type: "image/png".to_owned(), + }; + let debug = format!("{img:?}"); + assert_eq!(debug, "[image: image/png, 3 bytes]"); + assert!(!debug.contains("171") && !debug.contains("205") && !debug.contains("239")); + } + #[test] fn context_window_default_returns_none() { let provider = StubProvider { diff --git a/crates/zeph-mcp/src/executor.rs b/crates/zeph-mcp/src/executor.rs index 11466d1ec..b17105eaf 100644 --- a/crates/zeph-mcp/src/executor.rs +++ b/crates/zeph-mcp/src/executor.rs @@ -133,6 +133,7 @@ impl ToolExecutor for McpToolExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::Mcp), + ..Default::default() })) } @@ -201,6 +202,7 @@ impl ToolExecutor for McpToolExecutor { locations: None, raw_response: None, claim_source: Some(zeph_tools::ClaimSource::Mcp), + ..Default::default() })) } diff --git a/crates/zeph-mcp/src/testing.rs b/crates/zeph-mcp/src/testing.rs index 4618bb0e2..772539394 100644 --- a/crates/zeph-mcp/src/testing.rs +++ b/crates/zeph-mcp/src/testing.rs @@ -180,6 +180,7 @@ impl ToolExecutor for MockMcpServer { locations: None, raw_response: None, claim_source: None, + ..Default::default() })), Some(Err(msg)) => Err(ToolError::Blocked { command: msg }), None => Err(ToolError::Blocked { diff --git a/crates/zeph-subagent/src/agent_loop.rs b/crates/zeph-subagent/src/agent_loop.rs index b5e8ed66d..dc59e19a0 100644 --- a/crates/zeph-subagent/src/agent_loop.rs +++ b/crates/zeph-subagent/src/agent_loop.rs @@ -990,6 +990,7 @@ mod handle_tool_step_granted_secrets_tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })))) } diff --git a/crates/zeph-subagent/src/filter.rs b/crates/zeph-subagent/src/filter.rs index ad4476270..efd1adc15 100644 --- a/crates/zeph-subagent/src/filter.rs +++ b/crates/zeph-subagent/src/filter.rs @@ -644,6 +644,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })); Box::pin(std::future::ready(result)) } @@ -746,6 +747,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })); Box::pin(std::future::ready(result)) } diff --git a/crates/zeph-subagent/src/manager/spawn.rs b/crates/zeph-subagent/src/manager/spawn.rs index 61c3e64aa..5f326f029 100644 --- a/crates/zeph-subagent/src/manager/spawn.rs +++ b/crates/zeph-subagent/src/manager/spawn.rs @@ -1333,6 +1333,7 @@ mod build_filtered_executor_tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })); Box::pin(std::future::ready(result)) } diff --git a/crates/zeph-subagent/src/manager/tests.rs b/crates/zeph-subagent/src/manager/tests.rs index 4169a7b4c..905b6737a 100644 --- a/crates/zeph-subagent/src/manager/tests.rs +++ b/crates/zeph-subagent/src/manager/tests.rs @@ -710,6 +710,7 @@ async fn tool_call_loop_two_turns() { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } else { Ok(None) @@ -2326,6 +2327,7 @@ async fn run_agent_loop_executes_native_tool_call() { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; Box::pin(std::future::ready(Ok(Some(output)))) } diff --git a/crates/zeph-tools/Cargo.toml b/crates/zeph-tools/Cargo.toml index 9d5776ada..557f6f913 100644 --- a/crates/zeph-tools/Cargo.toml +++ b/crates/zeph-tools/Cargo.toml @@ -44,6 +44,7 @@ uuid = { workspace = true, features = ["v4", "serde"] } zeph-common = { workspace = true, features = ["treesitter"] } zeph-config.workspace = true zeph-db.workspace = true +zeph-llm.workspace = true zeph-sanitizer.workspace = true # See https://github.com/bug-ops/zeph (workspace dependencies only contain versions) diff --git a/crates/zeph-tools/src/adversarial_gate.rs b/crates/zeph-tools/src/adversarial_gate.rs index e4beab9ea..a994a4e53 100644 --- a/crates/zeph-tools/src/adversarial_gate.rs +++ b/crates/zeph-tools/src/adversarial_gate.rs @@ -355,6 +355,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -872,6 +873,7 @@ mod tests { locations: None, raw_response: None, claim_source: Some(crate::executor::ClaimSource::Shell), + ..Default::default() })) } diff --git a/crates/zeph-tools/src/cache.rs b/crates/zeph-tools/src/cache.rs index 84fc948cb..4e406cb21 100644 --- a/crates/zeph-tools/src/cache.rs +++ b/crates/zeph-tools/src/cache.rs @@ -226,6 +226,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() } } diff --git a/crates/zeph-tools/src/composite.rs b/crates/zeph-tools/src/composite.rs index e437e2e4c..65fc8fefc 100644 --- a/crates/zeph-tools/src/composite.rs +++ b/crates/zeph-tools/src/composite.rs @@ -177,6 +177,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -220,6 +221,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -311,6 +313,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } async fn execute_tool_call_confirmed( @@ -329,6 +332,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -419,6 +423,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } else { Ok(None) @@ -450,6 +455,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } else { Ok(None) diff --git a/crates/zeph-tools/src/compression/decorator.rs b/crates/zeph-tools/src/compression/decorator.rs index 521e4a7f8..822527240 100644 --- a/crates/zeph-tools/src/compression/decorator.rs +++ b/crates/zeph-tools/src/compression/decorator.rs @@ -211,6 +211,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() } } diff --git a/crates/zeph-tools/src/cwd.rs b/crates/zeph-tools/src/cwd.rs index a0a66981e..caf4ea9c3 100644 --- a/crates/zeph-tools/src/cwd.rs +++ b/crates/zeph-tools/src/cwd.rs @@ -121,6 +121,7 @@ impl ToolExecutor for SetCwdExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } diff --git a/crates/zeph-tools/src/diagnostics.rs b/crates/zeph-tools/src/diagnostics.rs index 35630b555..2ceb0fa3d 100644 --- a/crates/zeph-tools/src/diagnostics.rs +++ b/crates/zeph-tools/src/diagnostics.rs @@ -194,6 +194,7 @@ impl ToolExecutor for DiagnosticsExecutor { locations: None, raw_response: None, claim_source: Some(crate::executor::ClaimSource::Diagnostics), + ..Default::default() })) } diff --git a/crates/zeph-tools/src/executor.rs b/crates/zeph-tools/src/executor.rs index fcf46f55e..ae98b1f24 100644 --- a/crates/zeph-tools/src/executor.rs +++ b/crates/zeph-tools/src/executor.rs @@ -253,17 +253,12 @@ pub enum ClaimSource { /// tool_name: ToolName::new("shell"), /// summary: "hello\n".to_owned(), /// blocks_executed: 1, -/// filter_stats: None, -/// diff: None, -/// streamed: false, -/// terminal_id: None, -/// locations: None, -/// raw_response: None, /// claim_source: Some(ClaimSource::Shell), +/// ..Default::default() /// }; /// assert_eq!(output.to_string(), "hello\n"); /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ToolOutput { /// Name of the tool that produced this output (e.g. `"shell"`, `"web-scrape"`). pub tool_name: ToolName, @@ -286,6 +281,9 @@ pub struct ToolOutput { /// Provenance of this tool result. Set by the executor at construction time. /// `None` in pass-through wrappers, mocks, and tests. pub claim_source: Option, + /// Validated image data carried across the tool boundary (e.g. MCP `ContentBlock::Image` + /// passthrough, spec-072). Empty for executors that don't produce media. + pub media: Vec, } impl fmt::Display for ToolOutput { @@ -641,13 +639,7 @@ pub fn deserialize_params( /// tool_name: "echo".into(), /// summary: text, /// blocks_executed: 1, -/// filter_stats: None, -/// diff: None, -/// streamed: false, -/// terminal_id: None, -/// locations: None, -/// raw_response: None, -/// claim_source: None, +/// ..Default::default() /// })) /// } /// @@ -1130,10 +1122,16 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() }; assert_eq!(output.to_string(), "$ echo hello\nhello"); } + #[test] + fn test_tool_output_default_media_empty() { + assert!(ToolOutput::default().media.is_empty()); + } + #[test] fn tool_error_blocked_display() { let err = ToolError::Blocked { @@ -1490,6 +1488,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -1512,6 +1511,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-tools/src/file.rs b/crates/zeph-tools/src/file.rs index 119c65a45..21b79b2df 100644 --- a/crates/zeph-tools/src/file.rs +++ b/crates/zeph-tools/src/file.rs @@ -288,6 +288,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -315,6 +316,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -347,6 +349,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -393,6 +396,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -463,6 +467,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -524,6 +529,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -545,6 +551,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -585,6 +592,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -607,6 +615,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } @@ -641,6 +650,7 @@ impl FileExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::FileSystem), + ..Default::default() })) } } diff --git a/crates/zeph-tools/src/moderation.rs b/crates/zeph-tools/src/moderation.rs index 0fa2e228d..29cb88d72 100644 --- a/crates/zeph-tools/src/moderation.rs +++ b/crates/zeph-tools/src/moderation.rs @@ -223,6 +223,7 @@ impl ToolExecutor for Moderation locations: None, raw_response: None, claim_source: Some(ClaimSource::Moderation), + ..Default::default() })) } "telegram_delete_all_reactions" => { @@ -251,6 +252,7 @@ impl ToolExecutor for Moderation locations: None, raw_response: None, claim_source: Some(ClaimSource::Moderation), + ..Default::default() })) } _ => Ok(None), diff --git a/crates/zeph-tools/src/policy_gate.rs b/crates/zeph-tools/src/policy_gate.rs index d6af9a8a1..66ee4c481 100644 --- a/crates/zeph-tools/src/policy_gate.rs +++ b/crates/zeph-tools/src/policy_gate.rs @@ -444,6 +444,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-tools/src/scope.rs b/crates/zeph-tools/src/scope.rs index 6a5066091..d672dd29e 100644 --- a/crates/zeph-tools/src/scope.rs +++ b/crates/zeph-tools/src/scope.rs @@ -767,6 +767,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-tools/src/scrape.rs b/crates/zeph-tools/src/scrape.rs index 55fd6f48f..851857c0f 100644 --- a/crates/zeph-tools/src/scrape.rs +++ b/crates/zeph-tools/src/scrape.rs @@ -323,6 +323,7 @@ impl ToolExecutor for WebScrapeExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::WebScrape), + ..Default::default() })) } @@ -444,6 +445,7 @@ impl WebScrapeExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::WebScrape), + ..Default::default() })) } Err(e) => { diff --git a/crates/zeph-tools/src/search_code.rs b/crates/zeph-tools/src/search_code.rs index 13fa63ea6..c3cf52db7 100644 --- a/crates/zeph-tools/src/search_code.rs +++ b/crates/zeph-tools/src/search_code.rs @@ -591,6 +591,7 @@ fn build_search_code_output( locations: Some(locations), raw_response: Some(raw_response), claim_source: Some(ClaimSource::CodeSearch), + ..Default::default() } } diff --git a/crates/zeph-tools/src/shadow_probe.rs b/crates/zeph-tools/src/shadow_probe.rs index 6488e5f4a..72e465194 100644 --- a/crates/zeph-tools/src/shadow_probe.rs +++ b/crates/zeph-tools/src/shadow_probe.rs @@ -541,6 +541,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/crates/zeph-tools/src/shell/mod.rs b/crates/zeph-tools/src/shell/mod.rs index c9335551e..86fe3d696 100644 --- a/crates/zeph-tools/src/shell/mod.rs +++ b/crates/zeph-tools/src/shell/mod.rs @@ -741,6 +741,7 @@ impl ShellExecutor { locations: None, raw_response, claim_source: Some(ClaimSource::Shell), + ..Default::default() })) } @@ -978,6 +979,7 @@ impl ShellExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::Shell), + ..Default::default() })) } @@ -1802,6 +1804,7 @@ impl ToolExecutor for ShellExecutor { locations: None, raw_response: None, claim_source: Some(ClaimSource::Shell), + ..Default::default() })); } diff --git a/crates/zeph-tools/src/tool_filter.rs b/crates/zeph-tools/src/tool_filter.rs index c05cf3fcd..b1e8f9338 100644 --- a/crates/zeph-tools/src/tool_filter.rs +++ b/crates/zeph-tools/src/tool_filter.rs @@ -143,6 +143,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -216,6 +217,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } async fn execute_tool_call_confirmed( @@ -233,6 +235,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } fn is_tool_retryable(&self, _tool_id: &str) -> bool { diff --git a/crates/zeph-tools/src/trust_gate.rs b/crates/zeph-tools/src/trust_gate.rs index b73bf890b..45448c25d 100644 --- a/crates/zeph-tools/src/trust_gate.rs +++ b/crates/zeph-tools/src/trust_gate.rs @@ -360,6 +360,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } diff --git a/src/acp.rs b/src/acp.rs index 8b06a88ec..b57801b7f 100644 --- a/src/acp.rs +++ b/src/acp.rs @@ -3196,6 +3196,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -3643,6 +3644,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/src/agent_setup.rs b/src/agent_setup.rs index e009e0897..a96ced26b 100644 --- a/src/agent_setup.rs +++ b/src/agent_setup.rs @@ -3649,6 +3649,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/src/daemon.rs b/src/daemon.rs index 554159abe..e5e12d701 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1688,6 +1688,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -2368,6 +2369,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/src/runner.rs b/src/runner.rs index eccb21200..be64812c5 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -5221,6 +5221,7 @@ mod tests { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/src/scheduler_executor.rs b/src/scheduler_executor.rs index 4916957c6..509ba9546 100644 --- a/src/scheduler_executor.rs +++ b/src/scheduler_executor.rs @@ -434,6 +434,7 @@ fn make_output(tool_name: &str, summary: &str) -> ToolOutput { locations: None, raw_response: None, claim_source: None, + ..Default::default() } } diff --git a/tests/integration.rs b/tests/integration.rs index 677f01604..d58a7c134 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -198,6 +198,7 @@ impl ToolExecutor for OutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -213,6 +214,7 @@ impl ToolExecutor for OutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -233,6 +235,7 @@ impl ToolExecutor for EmptyOutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -248,6 +251,7 @@ impl ToolExecutor for EmptyOutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -268,6 +272,7 @@ impl ToolExecutor for ErrorOutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -283,6 +288,7 @@ impl ToolExecutor for ErrorOutputToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -326,6 +332,7 @@ impl ToolExecutor for ConfirmToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -350,6 +357,7 @@ impl ToolExecutor for ConfirmToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -421,6 +429,7 @@ impl ToolExecutor for ExitCodeToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -436,6 +445,7 @@ impl ToolExecutor for ExitCodeToolExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); @@ -2599,6 +2609,7 @@ mod self_learning { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } @@ -2617,6 +2628,7 @@ mod self_learning { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!(); diff --git a/tests/performance_agent_integration.rs b/tests/performance_agent_integration.rs index 81124c595..5a62f1f36 100644 --- a/tests/performance_agent_integration.rs +++ b/tests/performance_agent_integration.rs @@ -143,6 +143,7 @@ impl ToolExecutor for InstrumentedMockExecutor { locations: None, raw_response: None, claim_source: None, + ..Default::default() })) } zeph_tools::tool_executor_no_inner_defaults!();