diff --git a/scripts/core-boundaries/rules/source/required-rules.mjs b/scripts/core-boundaries/rules/source/required-rules.mjs index 434d377b4..a5a78d79d 100644 --- a/scripts/core-boundaries/rules/source/required-rules.mjs +++ b/scripts/core-boundaries/rules/source/required-rules.mjs @@ -2994,8 +2994,12 @@ export const requiredContentRules = [ message: 'missing delegation policy parsing owner', }, { - regex: /\bpub fn primary_model_supports_image_understanding\b/, - message: 'missing model image-support policy owner', + regex: /\bpub struct PrimaryModelFacts\b/, + message: 'missing typed primary model facts owner', + }, + { + regex: /\bpub fn multimodal_tool_output_supported\b/, + message: 'missing primary model multimodal tool-output policy owner', }, { regex: /\bmaterializes_provider_neutral_tool_custom_data\b/, diff --git a/src/crates/assembly/core/src/agentic/execution/execution_engine.rs b/src/crates/assembly/core/src/agentic/execution/execution_engine.rs index f14794b51..fdf6cd96a 100644 --- a/src/crates/assembly/core/src/agentic/execution/execution_engine.rs +++ b/src/crates/assembly/core/src/agentic/execution/execution_engine.rs @@ -51,6 +51,7 @@ use std::collections::{HashMap, HashSet}; use std::path::Path; use std::sync::Arc; use tokio_util::sync::CancellationToken; +use tool_runtime::context::PrimaryModelFacts; /// Execution engine configuration #[derive(Debug, Clone)] @@ -658,9 +659,9 @@ impl ExecutionEngine { async fn resolve_primary_model_context( model_id: &str, ai_client_model: &str, - ai_client_provider: &str, + ai_client_api_format: &str, unavailable_log_message: &str, - ) -> (String, bool) { + ) -> PrimaryModelFacts { let config_service = get_global_config_service().await.ok(); if let Some(service) = config_service { let ai_config: crate::service::config::types::AIConfig = @@ -680,7 +681,7 @@ impl ExecutionEngine { }) .or_else(|| { ai_config.models.iter().find(|m| { - m.model_name == ai_client_model && m.provider == ai_client_provider + m.model_name == ai_client_model && m.provider == ai_client_api_format }) }); @@ -691,10 +692,10 @@ impl ExecutionEngine { || matches!(m.category, ModelCategory::Multimodal) }); - (resolved_id, supports) + PrimaryModelFacts::new(resolved_id, ai_client_model, ai_client_api_format, supports) } else { warn!("{}", unavailable_log_message); - (model_id.to_string(), false) + PrimaryModelFacts::new(model_id, ai_client_model, ai_client_api_format, false) } } @@ -1116,7 +1117,7 @@ impl ExecutionEngine { round_number: usize, round_group_id: Option, execution_context_vars: &HashMap, - primary_supports_image_understanding: bool, + primary_model_facts: &PrimaryModelFacts, prepended_reminders: &[&str], messages: &[Message], reminder_text: &str, @@ -1139,7 +1140,7 @@ impl ExecutionEngine { .as_ref() .map(|workspace| workspace.root_path()), &context.dialog_turn_id, - primary_supports_image_understanding, + primary_model_facts.supports_image_inputs, prepended_reminders, ) .await?; @@ -1159,6 +1160,7 @@ impl ExecutionEngine { collapsed_tools: Vec::new(), unlocked_collapsed_tools: Vec::new(), model_name: ai_client.config.model.clone(), + primary_model_facts: primary_model_facts.clone(), agent_type, context_vars: execution_context_vars.clone(), delegation_policy: context.delegation_policy, @@ -1570,14 +1572,15 @@ impl ExecutionEngine { )) })?; - let (resolved_primary_model_id, primary_supports_image_understanding) = - Self::resolve_primary_model_context( - &model_id, - &ai_client.config.model, - &ai_client.config.format, - "Config service unavailable, assuming compression model is text-only for image input gating", - ) - .await; + let primary_model_facts = Self::resolve_primary_model_context( + &model_id, + &ai_client.config.model, + &ai_client.config.format, + "Config service unavailable, assuming compression model is text-only for image input gating", + ) + .await; + let resolved_primary_model_id = primary_model_facts.model_id.clone(); + let primary_supports_image_understanding = primary_model_facts.supports_image_inputs; let model_capability_profile = ModelCapabilityProfile::from_resolved_model( &resolved_primary_model_id, @@ -1613,10 +1616,7 @@ impl ExecutionEngine { &context.agent_type, context.workspace.as_ref(), context.workspace_services.as_ref(), - Some(&resolved_primary_model_id), - Some(&ai_client.config.model), - Some(&ai_client.config.format), - primary_supports_image_understanding, + Some(&primary_model_facts), &tool_manifest_context_vars, ); let tool_manifest = if enable_tools { @@ -2212,14 +2212,15 @@ impl ExecutionEngine { })?; // Primary model vision capability (tools + system prompt appendix; also used below for API message stripping). - let (resolved_primary_model_id, primary_supports_image_understanding) = - Self::resolve_primary_model_context( - &model_id, - &ai_client.config.model, - &ai_client.config.format, - "Config service unavailable, assuming primary model is text-only for image input gating", - ) - .await; + let primary_model_facts = Self::resolve_primary_model_context( + &model_id, + &ai_client.config.model, + &ai_client.config.format, + "Config service unavailable, assuming primary model is text-only for image input gating", + ) + .await; + let resolved_primary_model_id = primary_model_facts.model_id.clone(); + let primary_supports_image_understanding = primary_model_facts.supports_image_inputs; let model_context_window = ai_client.config.context_window as usize; let session_max_tokens = session.config.max_context_tokens; @@ -2277,10 +2278,7 @@ impl ExecutionEngine { &agent_type, context.workspace.as_ref(), context.workspace_services.as_ref(), - Some(&resolved_primary_model_id), - Some(&ai_client.config.model), - Some(&ai_client.config.format), - primary_supports_image_understanding, + Some(&primary_model_facts), &tool_manifest_context_vars, ); @@ -2334,6 +2332,18 @@ impl ExecutionEngine { } else { (vec![], None) }; + let final_tool_names = Self::finalize_tool_names(tool_definitions.as_deref()); + debug!( + "Primary model and tool manifest resolved: session_id={}, turn_id={}, resolved_primary_model_id={}, primary_model_api_format={}, primary_model_supports_image_inputs={}, final_tool_count={}, final_tool_names={:?}, collapsed_tool_names={:?}", + context.session_id, + context.dialog_turn_id, + primary_model_facts.model_id, + primary_model_facts.api_format, + primary_model_facts.supports_image_inputs, + final_tool_names.len(), + final_tool_names, + collapsed_tools, + ); // 4. Resolve the prompt scaffold used by model requests in this turn. // It is refreshed after successful context compression so the first @@ -2403,22 +2413,6 @@ impl ExecutionEngine { let compression_threshold = session.config.compression_threshold; let mut execution_context_vars = context.context.clone(); - execution_context_vars.insert( - "primary_model_id".to_string(), - resolved_primary_model_id.clone(), - ); - execution_context_vars.insert( - "primary_model_name".to_string(), - ai_client.config.model.clone(), - ); - execution_context_vars.insert( - "primary_model_provider".to_string(), - ai_client.config.format.clone(), - ); - execution_context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - primary_supports_image_understanding.to_string(), - ); execution_context_vars.insert("turn_index".to_string(), context.turn_index.to_string()); // If the primary model is text-only, do not send image payloads to the provider. @@ -2632,6 +2626,7 @@ impl ExecutionEngine { collapsed_tools: collapsed_tools.clone(), unlocked_collapsed_tools, model_name: ai_client.config.model.clone(), + primary_model_facts: primary_model_facts.clone(), agent_type: agent_type.clone(), context_vars: round_context_vars, delegation_policy: context.delegation_policy, @@ -3120,7 +3115,7 @@ impl ExecutionEngine { completed_rounds, finalize_round_group_id.clone(), &execution_context_vars, - primary_supports_image_understanding, + &primary_model_facts, &finalize_prepended_reminders, &messages, finalize_reminder, @@ -3150,7 +3145,7 @@ impl ExecutionEngine { completed_rounds, finalize_round_group_id.clone(), &execution_context_vars, - primary_supports_image_understanding, + &primary_model_facts, &finalize_prepended_reminders, &messages, finalize_reminder, diff --git a/src/crates/assembly/core/src/agentic/execution/round_executor.rs b/src/crates/assembly/core/src/agentic/execution/round_executor.rs index 5bef81b3e..6f24fc2c8 100644 --- a/src/crates/assembly/core/src/agentic/execution/round_executor.rs +++ b/src/crates/assembly/core/src/agentic/execution/round_executor.rs @@ -721,6 +721,7 @@ impl RoundExecutor { attempt_index: Some((attempt_index + 1) as u32), agent_type: context.agent_type.clone(), workspace: context.workspace.clone(), + primary_model_facts: context.primary_model_facts.clone(), context_vars: context.context_vars.clone(), subagent_parent_info, delegation_policy: context.delegation_policy, @@ -1345,6 +1346,9 @@ mod tests { collapsed_tools: Vec::new(), unlocked_collapsed_tools: Vec::new(), model_name: "model-1".to_string(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::new( + "model-1", "model-1", "openai", true, + ), agent_type: "agentic".to_string(), context_vars: HashMap::new(), delegation_policy: DelegationPolicy::top_level(), diff --git a/src/crates/assembly/core/src/agentic/execution/types.rs b/src/crates/assembly/core/src/agentic/execution/types.rs index aa051346e..627e48880 100644 --- a/src/crates/assembly/core/src/agentic/execution/types.rs +++ b/src/crates/assembly/core/src/agentic/execution/types.rs @@ -12,6 +12,7 @@ use serde_json::Value; use std::collections::HashMap; use std::sync::Arc; use tokio_util::sync::CancellationToken; +use tool_runtime::context::PrimaryModelFacts; /// Execution context #[derive(Clone)] @@ -51,6 +52,7 @@ pub struct RoundContext { pub collapsed_tools: Vec, pub unlocked_collapsed_tools: Vec, pub model_name: String, + pub primary_model_facts: PrimaryModelFacts, pub agent_type: String, pub context_vars: HashMap, pub(crate) delegation_policy: DelegationPolicy, diff --git a/src/crates/assembly/core/src/agentic/skill_agent_snapshot.rs b/src/crates/assembly/core/src/agentic/skill_agent_snapshot.rs index 713b3bc84..0742723e5 100644 --- a/src/crates/assembly/core/src/agentic/skill_agent_snapshot.rs +++ b/src/crates/assembly/core/src/agentic/skill_agent_snapshot.rs @@ -52,9 +52,6 @@ pub async fn resolve_skill_agent_snapshot( workspace, workspace_services, None, - None, - None, - true, context_vars, ); let manifest = resolve_tool_manifest( diff --git a/src/crates/assembly/core/src/agentic/tools/file_read_state_runtime.rs b/src/crates/assembly/core/src/agentic/tools/file_read_state_runtime.rs index 261900bd6..aa7394ada 100644 --- a/src/crates/assembly/core/src/agentic/tools/file_read_state_runtime.rs +++ b/src/crates/assembly/core/src/agentic/tools/file_read_state_runtime.rs @@ -268,6 +268,7 @@ mod tests { dialog_turn_id: Some("turn-1".to_string()), workspace: Some(WorkspaceBinding::new(None, root)), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/ask_user_question_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/ask_user_question_tool.rs index 0874c87e8..a9ae9b65f 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/ask_user_question_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/ask_user_question_tool.rs @@ -267,6 +267,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data, computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/code_review_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/code_review_tool.rs index 0018db7ee..ddac133c0 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/code_review_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/code_review_tool.rs @@ -713,6 +713,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/computer_use_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/computer_use_tool.rs index 6eb25e378..e4601a785 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/computer_use_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/computer_use_tool.rs @@ -448,14 +448,6 @@ The **primary model cannot consume images** in tool results — **do not** use * Ok(vec![ToolResult::ok(body, Some(hint.to_string()))]) } - fn primary_api_format(ctx: &ToolUseContext) -> String { - ctx.custom_data - .get("primary_model_provider") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_lowercase() - } - /// Screenshot tool results attach JPEGs via `tool_image_attachments`; only providers whose /// request converters emit multimodal tool output are supported (Anthropic + OpenAI-compatible). fn require_multimodal_tool_output_for_screenshot(ctx: &ToolUseContext) -> BitFunResult<()> { @@ -464,11 +456,7 @@ The **primary model cannot consume images** in tool results — **do not** use * "The primary model does not accept images; do not use ComputerUse action `screenshot` or other image-producing steps. Use `click_element`, `locate`, `move_to_text` (with `move_to_text_match_index` when listed), `mouse_move` with globals from tool JSON, `key_chord`, etc.".to_string(), )); } - let f = Self::primary_api_format(ctx); - if matches!( - f.as_str(), - "anthropic" | "openai" | "response" | "responses" - ) { + if ctx.primary_model_facts().multimodal_tool_output_supported() { return Ok(()); } Err(BitFunError::tool( diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/control_hub_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/control_hub_tool.rs index cef1f09c5..8386344cf 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/control_hub_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/control_hub_tool.rs @@ -2066,6 +2066,7 @@ mod control_hub_tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: std::collections::HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs index 7f2fc7682..1604d8916 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/cron_tool.rs @@ -1207,6 +1207,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/exec_command/command.rs b/src/crates/assembly/core/src/agentic/tools/implementations/exec_command/command.rs index e15209172..3bb548f60 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/exec_command/command.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/exec_command/command.rs @@ -1130,6 +1130,7 @@ mod tests { session_identity, )), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs index e05d80b48..099b3d985 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/file_write_tool.rs @@ -244,6 +244,7 @@ mod tests { dialog_turn_id: None, workspace: Some(WorkspaceBinding::new(None, root)), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/get_time_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/get_time_tool.rs index 2c371beaf..65c7f489c 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/get_time_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/get_time_tool.rs @@ -124,6 +124,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs index ead63e15e..23b02ae3e 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs @@ -559,6 +559,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/session_message_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/session_message_tool.rs index d28fde7a2..b5eef9e78 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/session_message_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/session_message_tool.rs @@ -626,6 +626,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/skill_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/skill_tool.rs index 7f29977a2..ecc28a8d6 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/skill_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/skill_tool.rs @@ -408,6 +408,7 @@ Use the remote project skill. dialog_turn_id: None, workspace: Some(workspace), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: Default::default(), computer_use_host: None, runtime_tool_restrictions: Default::default(), @@ -449,6 +450,7 @@ Use the remote project skill. dialog_turn_id: None, workspace: Some(workspace), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: Default::default(), computer_use_host: None, runtime_tool_restrictions: Default::default(), @@ -496,6 +498,7 @@ Use the remote project skill. dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: Default::default(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/task_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/task_tool.rs index b53e8ed8e..c6b9a70ac 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/task_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/task_tool.rs @@ -1738,6 +1738,7 @@ mod tests { dialog_turn_id: Some("turn-1".to_string()), workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::from([ ( "delegation_allow_subagent_spawn".to_string(), @@ -1877,6 +1878,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -1912,6 +1914,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/view_image_tool.rs b/src/crates/assembly/core/src/agentic/tools/implementations/view_image_tool.rs index 8a0ec7963..ded1fe8f5 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/view_image_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/view_image_tool.rs @@ -39,11 +39,7 @@ impl ViewImageTool { } fn primary_api_format(ctx: &ToolUseContext) -> String { - ctx.custom_data - .get("primary_model_provider") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_lowercase() + ctx.primary_model_facts().api_format.to_lowercase() } fn require_multimodal_tool_output(ctx: &ToolUseContext) -> BitFunResult<()> { @@ -70,10 +66,7 @@ impl ViewImageTool { fn supports_multimodal_tool_output(ctx: &ToolUseContext) -> bool { ctx.primary_model_supports_image_understanding() - && matches!( - Self::primary_api_format(ctx).as_str(), - "anthropic" | "openai" | "response" | "responses" - ) + && ctx.primary_model_facts().multimodal_tool_output_supported() } fn mime_type_for_image(bytes: &[u8]) -> BitFunResult<&'static str> { @@ -454,6 +447,7 @@ mod tests { use std::io::Cursor; use std::path::PathBuf; use std::sync::Arc; + use tool_runtime::context::PrimaryModelFacts; fn png_bytes(width: u32, height: u32) -> Vec { let image = ImageBuffer::from_pixel(width, height, Rgb([80u8, 120u8, 160u8])); @@ -520,12 +514,8 @@ mod tests { } fn context(provider: &str, supports_images: bool) -> ToolUseContext { - let mut custom_data = HashMap::new(); - custom_data.insert("primary_model_provider".to_string(), json!(provider)); - custom_data.insert( - "primary_model_supports_image_understanding".to_string(), - json!(supports_images), - ); + let primary_model_facts = + PrimaryModelFacts::new("primary-model", "vision-model", provider, supports_images); ToolUseContext { tool_call_id: None, agent_type: None, @@ -533,7 +523,8 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), - custom_data, + primary_model_facts, + custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), runtime_handles: bitfun_runtime_ports::ToolRuntimeHandles::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/implementations/web/mod.rs b/src/crates/assembly/core/src/agentic/tools/implementations/web/mod.rs index 1f1b6f5db..d2bfb05c5 100644 --- a/src/crates/assembly/core/src/agentic/tools/implementations/web/mod.rs +++ b/src/crates/assembly/core/src/agentic/tools/implementations/web/mod.rs @@ -29,6 +29,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: std::collections::HashMap::new(), computer_use_host: None, runtime_tool_restrictions: Default::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/manifest_resolver.rs b/src/crates/assembly/core/src/agentic/tools/manifest_resolver.rs index 218b59642..7d1a28f8e 100644 --- a/src/crates/assembly/core/src/agentic/tools/manifest_resolver.rs +++ b/src/crates/assembly/core/src/agentic/tools/manifest_resolver.rs @@ -44,6 +44,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/pipeline/state_manager.rs b/src/crates/assembly/core/src/agentic/tools/pipeline/state_manager.rs index 03a892592..1041ffeb7 100644 --- a/src/crates/assembly/core/src/agentic/tools/pipeline/state_manager.rs +++ b/src/crates/assembly/core/src/agentic/tools/pipeline/state_manager.rs @@ -299,6 +299,7 @@ mod tests { attempt_index: None, agent_type: "agentic".to_string(), workspace: None, + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), context_vars: HashMap::new(), subagent_parent_info: None, delegation_policy: bitfun_runtime_ports::DelegationPolicy::top_level(), diff --git a/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs b/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs index eb2bd7b08..34e2fcefa 100644 --- a/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs +++ b/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs @@ -1439,6 +1439,7 @@ mod tests { attempt_index: None, agent_type: "agent".to_string(), workspace: None, + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), context_vars: HashMap::new(), subagent_parent_info: None, delegation_policy: bitfun_runtime_ports::DelegationPolicy::top_level(), @@ -1807,13 +1808,6 @@ mod tests { task.context .context_vars .insert("turn_index".to_string(), "7".to_string()); - task.context - .context_vars - .insert("primary_model_provider".to_string(), "openai".to_string()); - task.context.context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "true".to_string(), - ); task.context .context_vars .insert("acp_transport".to_string(), "true".to_string()); @@ -1839,14 +1833,10 @@ mod tests { .is_tool_allowed("WebFetch")); assert!(!context.runtime_tool_restrictions.is_tool_allowed("Bash")); assert_eq!(context.custom_data["turn_index"], json!(7)); - assert_eq!( - context.custom_data["primary_model_provider"], - json!("openai") - ); - assert_eq!( - context.custom_data["primary_model_supports_image_understanding"], - json!(true) - ); + assert!(!context.custom_data.contains_key("primary_model_provider")); + assert!(!context + .custom_data + .contains_key("primary_model_supports_image_understanding")); assert_eq!(context.custom_data["acp_transport"], json!(true)); let facts = context.to_tool_context_facts(); diff --git a/src/crates/assembly/core/src/agentic/tools/pipeline/types.rs b/src/crates/assembly/core/src/agentic/tools/pipeline/types.rs index 1e9848e68..7c770f8ae 100644 --- a/src/crates/assembly/core/src/agentic/tools/pipeline/types.rs +++ b/src/crates/assembly/core/src/agentic/tools/pipeline/types.rs @@ -9,6 +9,7 @@ use crate::agentic::WorkspaceBinding; use bitfun_runtime_ports::DelegationPolicy; use std::collections::HashMap; use std::time::SystemTime; +pub use tool_runtime::context::PrimaryModelFacts; pub use tool_runtime::pipeline::SubagentBatchExecutionPolicy; /// Tool execution options @@ -64,6 +65,7 @@ pub struct ToolExecutionContext { pub attempt_index: Option, pub agent_type: String, pub workspace: Option, + pub primary_model_facts: PrimaryModelFacts, pub context_vars: HashMap, pub subagent_parent_info: Option, pub(crate) delegation_policy: DelegationPolicy, diff --git a/src/crates/assembly/core/src/agentic/tools/product_runtime/catalog.rs b/src/crates/assembly/core/src/agentic/tools/product_runtime/catalog.rs index 6390279bd..74146738a 100644 --- a/src/crates/assembly/core/src/agentic/tools/product_runtime/catalog.rs +++ b/src/crates/assembly/core/src/agentic/tools/product_runtime/catalog.rs @@ -232,6 +232,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -241,13 +242,8 @@ mod tests { fn multimodal_anthropic_tool_context(agent_type: Option<&str>) -> ToolUseContext { let mut context = tool_context(agent_type); - context.custom_data.insert( - "primary_model_supports_image_understanding".to_string(), - json!(true), - ); - context - .custom_data - .insert("primary_model_provider".to_string(), json!("anthropic")); + context.primary_model_facts = + tool_runtime::context::PrimaryModelFacts::new("model-1", "claude", "anthropic", true); context } diff --git a/src/crates/assembly/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs b/src/crates/assembly/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs index ca019c642..e3cd64978 100644 --- a/src/crates/assembly/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs +++ b/src/crates/assembly/core/src/agentic/tools/product_runtime/get_tool_spec_tool.rs @@ -140,6 +140,7 @@ mod tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: vec!["WebFetch".to_string()], + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/assembly/core/src/agentic/tools/tool_context_runtime.rs b/src/crates/assembly/core/src/agentic/tools/tool_context_runtime.rs index f2035c0eb..3014cb8ea 100644 --- a/src/crates/assembly/core/src/agentic/tools/tool_context_runtime.rs +++ b/src/crates/assembly/core/src/agentic/tools/tool_context_runtime.rs @@ -46,9 +46,8 @@ use std::future::Future; use std::path::{Path, PathBuf}; use tokio_util::sync::CancellationToken; use tool_runtime::context::{ - build_tool_runtime_custom_data, delegation_policy_from_custom_data, - primary_model_supports_image_understanding as runtime_primary_model_supports_image_understanding, - project_tool_context_facts, ToolRuntimeContextFactsInput, ToolRuntimeCustomDataInput, + build_tool_runtime_custom_data, delegation_policy_from_custom_data, project_tool_context_facts, + PrimaryModelFacts, ToolRuntimeContextFactsInput, ToolRuntimeCustomDataInput, }; /// Core-owned tool use context. @@ -60,6 +59,7 @@ pub struct ToolUseContext { pub dialog_turn_id: Option, pub workspace: Option, pub unlocked_collapsed_tools: Vec, + pub primary_model_facts: PrimaryModelFacts, /// Extended context data passed from execution layer to tools. pub custom_data: HashMap, /// Desktop automation (Computer use); only set in BitFun desktop. @@ -113,7 +113,11 @@ impl ToolUseContext { /// Whether the session primary model accepts image inputs (from tool-definition / pipeline context). /// Defaults to **true** when unset (e.g. API listings without model metadata). pub fn primary_model_supports_image_understanding(&self) -> bool { - runtime_primary_model_supports_image_understanding(&self.custom_data) + self.primary_model_facts.supports_image_inputs + } + + pub fn primary_model_facts(&self) -> &PrimaryModelFacts { + &self.primary_model_facts } pub fn cancellation_token(&self) -> Option<&CancellationToken> { @@ -135,6 +139,7 @@ impl ToolUseContext { dialog_turn_id: None, workspace, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -210,6 +215,7 @@ pub(crate) fn build_tool_use_context_for_execution_context( dialog_turn_id: Some(context.dialog_turn_id.clone()), workspace: context.workspace.clone(), unlocked_collapsed_tools: context.unlocked_collapsed_tools.clone(), + primary_model_facts: context.primary_model_facts.clone(), custom_data: build_tool_context_custom_data(context), computer_use_host, runtime_handles: ToolRuntimeHandles::new( @@ -224,35 +230,11 @@ pub(crate) fn build_tool_description_context( agent_type: &str, workspace: Option<&WorkspaceBinding>, workspace_services: Option<&WorkspaceServices>, - primary_model_id: Option<&str>, - primary_model_name: Option<&str>, - primary_model_provider: Option<&str>, - primary_supports_image_understanding: bool, + primary_model_facts: Option<&PrimaryModelFacts>, context_vars: &HashMap, ) -> ToolUseContext { let mut custom_data = HashMap::new(); - if let Some(primary_model_id) = primary_model_id { - custom_data.insert( - "primary_model_id".to_string(), - Value::String(primary_model_id.to_string()), - ); - } - if let Some(primary_model_name) = primary_model_name { - custom_data.insert( - "primary_model_name".to_string(), - Value::String(primary_model_name.to_string()), - ); - } - if let Some(primary_model_provider) = primary_model_provider { - custom_data.insert( - "primary_model_provider".to_string(), - Value::String(primary_model_provider.to_string()), - ); - } - custom_data.insert( - "primary_model_supports_image_understanding".to_string(), - Value::Bool(primary_supports_image_understanding), - ); + let primary_model_facts = primary_model_facts.cloned().unwrap_or_default(); for (key, value) in context_vars { custom_data.insert(key.clone(), Value::String(value.clone())); } @@ -264,6 +246,7 @@ pub(crate) fn build_tool_description_context( dialog_turn_id: None, workspace: workspace.cloned(), unlocked_collapsed_tools: Vec::new(), + primary_model_facts, custom_data, computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -667,6 +650,7 @@ mod context_facts_tests { use crate::service::remote_ssh::workspace_state::workspace_session_identity; use std::collections::{BTreeSet, HashMap}; use std::path::PathBuf; + use tool_runtime::context::PrimaryModelFacts; fn local_context(root: &str) -> ToolUseContext { ToolUseContext { @@ -676,6 +660,7 @@ mod context_facts_tests { dialog_turn_id: None, workspace: Some(WorkspaceBinding::new(None, PathBuf::from(root))), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -692,6 +677,7 @@ mod context_facts_tests { dialog_turn_id: Some("turn-1".to_string()), workspace: Some(WorkspaceBinding::new(None, PathBuf::from("/repo/project"))), unlocked_collapsed_tools: vec!["WebFetch".to_string()], + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions { @@ -736,6 +722,7 @@ mod context_facts_tests { dialog_turn_id: Some("turn-runtime".to_string()), workspace: Some(WorkspaceBinding::new(None, PathBuf::from("/repo/runtime"))), unlocked_collapsed_tools: vec!["WebFetch".to_string(), "Git".to_string()], + primary_model_facts: PrimaryModelFacts::default(), custom_data, computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions { @@ -797,6 +784,7 @@ mod context_facts_tests { session_identity, )), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -839,6 +827,7 @@ mod path_resolution_tests { use crate::service::remote_ssh::workspace_state::workspace_session_identity; use std::collections::HashMap; use std::path::PathBuf; + use tool_runtime::context::PrimaryModelFacts; fn local_context(root: &str) -> ToolUseContext { ToolUseContext { @@ -848,6 +837,7 @@ mod path_resolution_tests { dialog_turn_id: None, workspace: Some(WorkspaceBinding::new(None, PathBuf::from(root))), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -871,6 +861,7 @@ mod path_resolution_tests { session_identity, )), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -896,6 +887,7 @@ mod path_resolution_tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -1055,6 +1047,7 @@ mod call_runtime_tests { use std::collections::HashMap; use tokio::time::{sleep, Duration}; use tokio_util::sync::CancellationToken; + use tool_runtime::context::PrimaryModelFacts; struct MeasurementReadTool; @@ -1101,6 +1094,7 @@ mod call_runtime_tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -1137,6 +1131,7 @@ mod call_runtime_tests { dialog_turn_id: None, workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -1177,6 +1172,7 @@ mod call_runtime_tests { dialog_turn_id: Some("subagent-turn".to_string()), workspace: None, unlocked_collapsed_tools: Vec::new(), + primary_model_facts: PrimaryModelFacts::default(), custom_data, computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), @@ -1207,25 +1203,23 @@ mod call_runtime_tests { #[cfg(test)] mod context_builder_tests { use super::build_tool_description_context; - use serde_json::json; use std::collections::HashMap; + use tool_runtime::context::PrimaryModelFacts; #[test] fn tool_description_context_preserves_manifest_custom_data_shape() { - let mut context_vars = HashMap::new(); - context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "false".to_string(), - ); + let context_vars = HashMap::new(); let context = build_tool_description_context( "coding", None, None, - Some("model_1"), - Some("vision-model"), - Some("anthropic"), - true, + Some(&PrimaryModelFacts::new( + "model_1", + "vision-model", + "anthropic", + true, + )), &context_vars, ); @@ -1238,19 +1232,16 @@ mod context_builder_tests { assert!(context.cancellation_token().is_none()); assert!(context.workspace_services().is_none()); assert!(context.runtime_tool_restrictions.is_tool_allowed("Write")); - assert_eq!( - context.custom_data["primary_model_supports_image_understanding"], - json!("false") - ); - assert_eq!(context.custom_data["primary_model_id"], json!("model_1")); - assert_eq!( - context.custom_data["primary_model_name"], - json!("vision-model") - ); - assert_eq!( - context.custom_data["primary_model_provider"], - json!("anthropic") - ); + assert!(context.primary_model_supports_image_understanding()); + assert_eq!(context.primary_model_facts().model_id, "model_1"); + assert_eq!(context.primary_model_facts().model_name, "vision-model"); + assert_eq!(context.primary_model_facts().api_format, "anthropic"); + assert!(!context.custom_data.contains_key("primary_model_id")); + assert!(!context.custom_data.contains_key("primary_model_name")); + assert!(!context.custom_data.contains_key("primary_model_provider")); + assert!(!context + .custom_data + .contains_key("primary_model_supports_image_understanding")); } } @@ -1266,15 +1257,11 @@ mod task_context_tests { use serde_json::json; use std::collections::{BTreeSet, HashMap}; use tokio_util::sync::CancellationToken; + use tool_runtime::context::PrimaryModelFacts; fn task_with_context_vars() -> ToolTask { let mut context_vars = HashMap::new(); context_vars.insert("turn_index".to_string(), "7".to_string()); - context_vars.insert("primary_model_provider".to_string(), "openai".to_string()); - context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "true".to_string(), - ); context_vars.insert("acp_transport".to_string(), "true".to_string()); context_vars.insert( "deep_review_run_manifest".to_string(), @@ -1306,6 +1293,12 @@ mod task_context_tests { attempt_index: None, agent_type: "agent".to_string(), workspace: None, + primary_model_facts: PrimaryModelFacts::new( + "primary-model", + "vision-model", + "openai", + true, + ), context_vars, subagent_parent_info: Some(SubagentParentInfo { tool_call_id: "parent_tool".to_string(), @@ -1346,14 +1339,13 @@ mod task_context_tests { .is_tool_allowed("WebFetch")); assert!(!context.runtime_tool_restrictions.is_tool_allowed("Bash")); assert_eq!(context.custom_data["turn_index"], json!(7)); - assert_eq!( - context.custom_data["primary_model_provider"], - json!("openai") - ); - assert_eq!( - context.custom_data["primary_model_supports_image_understanding"], - json!(true) - ); + assert_eq!(context.primary_model_facts().model_id, "primary-model"); + assert_eq!(context.primary_model_facts().api_format, "openai"); + assert!(context.primary_model_facts().supports_image_inputs); + assert!(!context.custom_data.contains_key("primary_model_provider")); + assert!(!context + .custom_data + .contains_key("primary_model_supports_image_understanding")); assert_eq!(context.custom_data["acp_transport"], json!(true)); assert_eq!( context.custom_data["deep_review_run_manifest"], diff --git a/src/crates/assembly/core/src/agentic/tools/tool_result_storage.rs b/src/crates/assembly/core/src/agentic/tools/tool_result_storage.rs index dc51850a3..32846922d 100644 --- a/src/crates/assembly/core/src/agentic/tools/tool_result_storage.rs +++ b/src/crates/assembly/core/src/agentic/tools/tool_result_storage.rs @@ -357,6 +357,7 @@ mod tests { dialog_turn_id: Some("turn_1".to_string()), workspace: Some(WorkspaceBinding::new(None, root)), unlocked_collapsed_tools: Vec::new(), + primary_model_facts: tool_runtime::context::PrimaryModelFacts::default(), custom_data: HashMap::new(), computer_use_host: None, runtime_tool_restrictions: ToolRuntimeRestrictions::default(), diff --git a/src/crates/execution/tool-execution/src/context.rs b/src/crates/execution/tool-execution/src/context.rs index 4b24d7be6..dd6a4fba7 100644 --- a/src/crates/execution/tool-execution/src/context.rs +++ b/src/crates/execution/tool-execution/src/context.rs @@ -3,6 +3,50 @@ use bitfun_runtime_ports::DelegationPolicy; use serde_json::Value; use std::collections::HashMap; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PrimaryModelFacts { + pub model_id: String, + pub model_name: String, + pub api_format: String, + pub supports_image_inputs: bool, +} + +impl PrimaryModelFacts { + pub fn new( + model_id: impl Into, + model_name: impl Into, + api_format: impl Into, + supports_image_inputs: bool, + ) -> Self { + Self { + model_id: model_id.into(), + model_name: model_name.into(), + api_format: api_format.into(), + supports_image_inputs, + } + } + + pub fn multimodal_tool_output_supported(&self) -> bool { + matches!( + self.api_format.to_lowercase().as_str(), + "anthropic" | "openai" | "response" | "responses" + ) + } +} + +impl Default for PrimaryModelFacts { + fn default() -> Self { + Self { + model_id: String::new(), + model_name: String::new(), + api_format: String::new(), + // Preserve the historical behavior for listing/API contexts that do + // not carry model metadata. + supports_image_inputs: true, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct ToolRuntimeCustomDataInput<'a> { pub context_vars: &'a HashMap, @@ -37,12 +81,6 @@ pub fn build_tool_runtime_custom_data( ); insert_u64_context_var(input.context_vars, &mut map, "turn_index"); - insert_non_empty_string_context_var(input.context_vars, &mut map, "primary_model_provider"); - insert_bool_context_var( - input.context_vars, - &mut map, - "primary_model_supports_image_understanding", - ); insert_bool_context_var(input.context_vars, &mut map, "acp_transport"); insert_bool_context_var(input.context_vars, &mut map, input.remote_file_delivery_key); if let Some(extension_custom_data) = input.extension_custom_data { @@ -85,17 +123,6 @@ pub fn delegation_policy_from_custom_data( } } -/// Whether the session primary model accepts image inputs. -/// -/// Defaults to true when unset so API listings without model metadata keep the -/// historical behavior. -pub fn primary_model_supports_image_understanding(custom_data: &HashMap) -> bool { - custom_data - .get("primary_model_supports_image_understanding") - .and_then(Value::as_bool) - .unwrap_or(true) -} - fn insert_u64_context_var( context_vars: &HashMap, map: &mut HashMap, @@ -120,26 +147,12 @@ fn insert_bool_context_var( } } -fn insert_non_empty_string_context_var( - context_vars: &HashMap, - map: &mut HashMap, - key: &str, -) { - if let Some(value) = context_vars - .get(key) - .map(String::as_str) - .filter(|value| !value.is_empty()) - { - map.insert(key.to_string(), serde_json::json!(value)); - } -} - #[cfg(test)] mod tests { use super::{ build_tool_runtime_custom_data, delegation_policy_from_custom_data, - primary_model_supports_image_understanding, project_tool_context_facts, - ToolRuntimeContextFactsInput, ToolRuntimeCustomDataInput, + project_tool_context_facts, PrimaryModelFacts, ToolRuntimeContextFactsInput, + ToolRuntimeCustomDataInput, }; use bitfun_agent_tools::{ToolRuntimeRestrictions, ToolWorkspaceKind}; use bitfun_runtime_ports::DelegationPolicy; @@ -150,11 +163,6 @@ mod tests { fn materializes_provider_neutral_tool_custom_data() { let mut context_vars = HashMap::new(); context_vars.insert("turn_index".to_string(), "7".to_string()); - context_vars.insert("primary_model_provider".to_string(), "openai".to_string()); - context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "false".to_string(), - ); context_vars.insert("acp_transport".to_string(), "true".to_string()); context_vars.insert("remote_file_delivery".to_string(), "true".to_string()); let extension_custom_data = HashMap::from([("extension_key".to_string(), json!("kept"))]); @@ -169,11 +177,6 @@ mod tests { assert_eq!(custom_data["delegation_allow_subagent_spawn"], json!(false)); assert_eq!(custom_data["delegation_nesting_depth"], json!(1)); assert_eq!(custom_data["turn_index"], json!(7)); - assert_eq!(custom_data["primary_model_provider"], json!("openai")); - assert_eq!( - custom_data["primary_model_supports_image_understanding"], - json!(false) - ); assert_eq!(custom_data["acp_transport"], json!(true)); assert_eq!(custom_data["remote_file_delivery"], json!(true)); assert_eq!(custom_data["extension_key"], json!("kept")); @@ -183,11 +186,6 @@ mod tests { fn custom_data_ignores_invalid_or_empty_context_values() { let mut context_vars = HashMap::new(); context_vars.insert("turn_index".to_string(), "not-a-number".to_string()); - context_vars.insert("primary_model_provider".to_string(), "".to_string()); - context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "not-bool".to_string(), - ); context_vars.insert("acp_transport".to_string(), "not-bool".to_string()); context_vars.insert("remote_file_delivery".to_string(), "not-bool".to_string()); @@ -201,8 +199,6 @@ mod tests { assert_eq!(custom_data["delegation_allow_subagent_spawn"], json!(true)); assert_eq!(custom_data["delegation_nesting_depth"], json!(0)); assert!(!custom_data.contains_key("turn_index")); - assert!(!custom_data.contains_key("primary_model_provider")); - assert!(!custom_data.contains_key("primary_model_supports_image_understanding")); assert!(!custom_data.contains_key("acp_transport")); assert!(!custom_data.contains_key("remote_file_delivery")); } @@ -211,17 +207,9 @@ mod tests { fn extension_custom_data_cannot_override_runtime_owned_values() { let mut context_vars = HashMap::new(); context_vars.insert("turn_index".to_string(), "7".to_string()); - context_vars.insert( - "primary_model_supports_image_understanding".to_string(), - "true".to_string(), - ); let extension_custom_data = HashMap::from([ ("turn_index".to_string(), json!(99)), ("delegation_allow_subagent_spawn".to_string(), json!(false)), - ( - "primary_model_supports_image_understanding".to_string(), - json!(false), - ), ("extension_key".to_string(), json!("kept")), ]); @@ -234,10 +222,6 @@ mod tests { assert_eq!(custom_data["delegation_allow_subagent_spawn"], json!(true)); assert_eq!(custom_data["turn_index"], json!(7)); - assert_eq!( - custom_data["primary_model_supports_image_understanding"], - json!(true) - ); assert_eq!(custom_data["extension_key"], json!("kept")); } @@ -246,11 +230,6 @@ mod tests { let mut custom_data = HashMap::new(); custom_data.insert("delegation_allow_subagent_spawn".to_string(), json!(false)); custom_data.insert("delegation_nesting_depth".to_string(), json!(3)); - custom_data.insert( - "primary_model_supports_image_understanding".to_string(), - json!(false), - ); - assert_eq!( delegation_policy_from_custom_data(&custom_data), DelegationPolicy { @@ -258,8 +237,7 @@ mod tests { nesting_depth: 3 } ); - assert!(!primary_model_supports_image_understanding(&custom_data)); - assert!(primary_model_supports_image_understanding(&HashMap::new())); + assert!(PrimaryModelFacts::default().supports_image_inputs); } #[test]