mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Finalize model-specific tool plans in ToolRouter (#41195)
## Why Planning tools for a candidate model must not change the selected model or its Responses Lite tool inventory. Otherwise, preparing a fallback can overwrite metadata before the current model's request is sent. ## What changed - Build tool registries and visible specs from an explicit `ModelInfo`. - Store the finalized tool mode, code-mode names, namespace inventory, and terminal and child-management capabilities on `ToolRouter`. - Publish namespace inventory only after selecting the step's router, and use that router's mode when dispatching and recording tool calls. ## Testing Added coverage for candidate-model isolation, fallback compaction metadata, code-mode worker selection, normalized tool names, and router capabilities. GitOrigin-RevId: 56df2de8f42c7e93a4c4dcd117720e40a0cca3db
This commit is contained in:
1 parent
8aea62b2d8
commit
0182ff3480
13 files changed
+532
-127
No files matched your search
@@ -3398,6 +3398,8 @@ impl Session {
|
|||||||
let tool_router = turn::built_tools(
|
let tool_router = turn::built_tools(
|
||||||
self.as_ref(),
|
self.as_ref(),
|
||||||
turn_context.as_ref(),
|
turn_context.as_ref(),
|
||||||
|
// TODO(CDXENT-441): use the step scoped model
|
||||||
|
turn_context.model_info(),
|
||||||
&environments,
|
&environments,
|
||||||
&mcp,
|
&mcp,
|
||||||
&extension_data,
|
&extension_data,
|
||||||
@@ -3405,6 +3407,21 @@ impl Session {
|
|||||||
)
|
)
|
||||||
.or_cancel(cancellation_token)
|
.or_cancel(cancellation_token)
|
||||||
.await??;
|
.await??;
|
||||||
|
// Publish inventory after planning rather than during finalization, so constructing
|
||||||
|
// additional candidate plans cannot overwrite turn-wide metadata.
|
||||||
|
if turn_context
|
||||||
|
.config
|
||||||
|
.tool_registry
|
||||||
|
.turn_metadata_includes_tool_info
|
||||||
|
&& turn_context.model_info().use_responses_lite
|
||||||
|
{
|
||||||
|
turn_context.turn_metadata_state.set_tool_namespaces_info(
|
||||||
|
tool_router
|
||||||
|
.tool_namespaces_info()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(Arc::new(StepContext {
|
Ok(Arc::new(StepContext {
|
||||||
settings,
|
settings,
|
||||||
token_budget,
|
token_budget,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ use codex_protocol::models::ImageDetail;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::SandboxEnforcement;
|
use codex_protocol::models::SandboxEnforcement;
|
||||||
use codex_protocol::openai_models::ModelServiceTier;
|
use codex_protocol::openai_models::ModelServiceTier;
|
||||||
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_protocol::permissions::FileSystemAccessMode;
|
use codex_protocol::permissions::FileSystemAccessMode;
|
||||||
use codex_protocol::permissions::FileSystemPath;
|
use codex_protocol::permissions::FileSystemPath;
|
||||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||||
@@ -257,6 +258,10 @@ impl StepContext {
|
|||||||
tool_router: Arc::new(ToolRouter::from_parts(
|
tool_router: Arc::new(ToolRouter::from_parts(
|
||||||
ToolRegistry::empty_for_test(),
|
ToolRegistry::empty_for_test(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
ToolMode::Direct,
|
||||||
|
BTreeMap::new(),
|
||||||
|
/*tool_namespaces_info*/ None,
|
||||||
|
&[],
|
||||||
)),
|
)),
|
||||||
loaded_agents_md: None,
|
loaded_agents_md: None,
|
||||||
})
|
})
|
||||||
@@ -877,6 +882,7 @@ pub(crate) fn tool_registry_for_test_step(
|
|||||||
) -> (ToolRegistry, Vec<ToolSpec>) {
|
) -> (ToolRegistry, Vec<ToolSpec>) {
|
||||||
let mut registry = crate::tools::spec_plan::build_core_tool_registry(
|
let mut registry = crate::tools::spec_plan::build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
step_context.mcp.as_ref(),
|
step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -884,6 +890,7 @@ pub(crate) fn tool_registry_for_test_step(
|
|||||||
);
|
);
|
||||||
let hosted_specs = crate::tools::spec_plan::append_source_tools(
|
let hosted_specs = crate::tools::spec_plan::append_source_tools(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -897,6 +904,7 @@ fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> T
|
|||||||
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
||||||
let router = Arc::new(ToolRouter::from_registry(
|
let router = Arc::new(ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
@@ -11612,6 +11620,7 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
|||||||
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
||||||
let router = ToolRouter::from_registry(
|
let router = ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
|
|||||||
@@ -1509,13 +1509,14 @@ pub(crate) async fn prepare_tool_recommendations(
|
|||||||
skip_all,
|
skip_all,
|
||||||
fields(
|
fields(
|
||||||
turn_id = %turn_context.sub_id,
|
turn_id = %turn_context.sub_id,
|
||||||
model = %turn_context.model_info().slug,
|
model = %model_info.slug,
|
||||||
apps_enabled = turn_context.apps_enabled()
|
apps_enabled = turn_context.apps_enabled()
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub(crate) async fn built_tools(
|
pub(crate) async fn built_tools(
|
||||||
sess: &Session,
|
sess: &Session,
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &codex_protocol::openai_models::ModelInfo,
|
||||||
environments: &TurnEnvironmentSnapshot,
|
environments: &TurnEnvironmentSnapshot,
|
||||||
mcp: &Arc<codex_mcp::McpBinding>,
|
mcp: &Arc<codex_mcp::McpBinding>,
|
||||||
step_store: &ExtensionData,
|
step_store: &ExtensionData,
|
||||||
@@ -1584,6 +1585,7 @@ pub(crate) async fn built_tools(
|
|||||||
Ok(Arc::new(build_tool_router(
|
Ok(Arc::new(build_tool_router(
|
||||||
sess,
|
sess,
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
environments,
|
environments,
|
||||||
mcp,
|
mcp,
|
||||||
apps_enabled,
|
apps_enabled,
|
||||||
|
|||||||
@@ -1046,14 +1046,14 @@ impl Session {
|
|||||||
|
|
||||||
if !tc.code_mode_available
|
if !tc.code_mode_available
|
||||||
&& matches!(
|
&& matches!(
|
||||||
crate::tools::requested_tool_mode(tc),
|
crate::tools::requested_tool_mode(tc, tc.model_info()),
|
||||||
codex_protocol::openai_models::ToolMode::CodeMode
|
codex_protocol::openai_models::ToolMode::CodeMode
|
||||||
| codex_protocol::openai_models::ToolMode::CodeModeOnly
|
| codex_protocol::openai_models::ToolMode::CodeModeOnly
|
||||||
)
|
)
|
||||||
&& let Some(message) = self
|
&& let Some(message) = self
|
||||||
.services
|
.services
|
||||||
.code_mode_service
|
.code_mode_service
|
||||||
.take_unavailable_warning(crate::tools::effective_tool_mode(tc))
|
.take_unavailable_warning(crate::tools::effective_tool_mode(tc, tc.model_info()))
|
||||||
{
|
{
|
||||||
self.send_event(tc, EventMsg::Warning(WarningEvent { message }))
|
self.send_event(tc, EventMsg::Warning(WarningEvent { message }))
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ async fn handle_output_item_done_returns_contributed_last_agent_message() {
|
|||||||
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
let (registry, hosted_specs) = tool_registry_for_test_step(step_context.as_ref());
|
||||||
let router = Arc::new(ToolRouter::from_registry(
|
let router = Arc::new(ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ use crate::tools::ExecutedToolCallRecorder;
|
|||||||
use crate::tools::context::FunctionToolOutput;
|
use crate::tools::context::FunctionToolOutput;
|
||||||
use crate::tools::context::SharedTurnDiffTracker;
|
use crate::tools::context::SharedTurnDiffTracker;
|
||||||
use crate::tools::context::ToolPayload;
|
use crate::tools::context::ToolPayload;
|
||||||
use crate::tools::effective_tool_mode;
|
|
||||||
use crate::tools::parallel::ToolCallRuntime;
|
use crate::tools::parallel::ToolCallRuntime;
|
||||||
use crate::tools::router::ToolCall;
|
use crate::tools::router::ToolCall;
|
||||||
use crate::tools::router::ToolCallSource;
|
use crate::tools::router::ToolCallSource;
|
||||||
@@ -205,8 +204,7 @@ impl CodeModeService {
|
|||||||
tracker: SharedTurnDiffTracker,
|
tracker: SharedTurnDiffTracker,
|
||||||
) -> Option<CodeModeDispatchWorker> {
|
) -> Option<CodeModeDispatchWorker> {
|
||||||
let turn = &step_context.turn;
|
let turn = &step_context.turn;
|
||||||
let tool_mode = effective_tool_mode(turn);
|
if !step_context.tool_router.requires_code_mode_worker() {
|
||||||
if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,14 +440,52 @@ fn build_freeform_tool_payload(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::build_nested_tool_payload;
|
use super::build_nested_tool_payload;
|
||||||
use super::truncate_code_mode_result;
|
use super::truncate_code_mode_result;
|
||||||
|
use crate::session::step_context::StepContext;
|
||||||
|
use crate::session::tests::make_session_and_context;
|
||||||
use crate::tools::context::ToolPayload;
|
use crate::tools::context::ToolPayload;
|
||||||
|
use crate::tools::registry::ToolRegistry;
|
||||||
|
use crate::tools::router::ToolRouter;
|
||||||
|
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||||
use codex_code_mode::CodeModeToolKind;
|
use codex_code_mode::CodeModeToolKind;
|
||||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||||
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_tools::ToolName;
|
use codex_tools::ToolName;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn turn_worker_uses_step_router_mode_instead_of_admitted_turn() {
|
||||||
|
let (session, turn) = make_session_and_context().await;
|
||||||
|
assert_eq!(
|
||||||
|
crate::tools::effective_tool_mode(&turn, turn.model_info()),
|
||||||
|
ToolMode::Direct
|
||||||
|
);
|
||||||
|
let session = Arc::new(session);
|
||||||
|
let step_context = StepContext::for_test(Arc::new(turn));
|
||||||
|
let router = Arc::new(ToolRouter::from_parts(
|
||||||
|
ToolRegistry::empty_for_test(),
|
||||||
|
Vec::new(),
|
||||||
|
ToolMode::CodeModeOnly,
|
||||||
|
BTreeMap::new(),
|
||||||
|
/*tool_namespaces_info*/ None,
|
||||||
|
&[],
|
||||||
|
));
|
||||||
|
let step_context = step_context.with_tool_router_for_test(router);
|
||||||
|
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||||
|
|
||||||
|
let worker =
|
||||||
|
session
|
||||||
|
.services
|
||||||
|
.code_mode_service
|
||||||
|
.start_turn_worker(&session, step_context, tracker);
|
||||||
|
|
||||||
|
assert!(worker.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_nested_tool_payload_uses_function_kind() {
|
fn build_nested_tool_payload_uses_function_kind() {
|
||||||
let payload = build_nested_tool_payload(
|
let payload = build_nested_tool_payload(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use crate::session::turn_context::TurnContext;
|
|||||||
pub(crate) use approvals::ApprovalContext;
|
pub(crate) use approvals::ApprovalContext;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::exec_output::ExecToolCallOutput;
|
use codex_protocol::exec_output::ExecToolCallOutput;
|
||||||
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
use codex_protocol::openai_models::ToolMode;
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_tools::ToolName;
|
use codex_tools::ToolName;
|
||||||
use codex_utils_output_truncation::TruncationPolicy;
|
use codex_utils_output_truncation::TruncationPolicy;
|
||||||
@@ -64,8 +65,8 @@ pub(crate) fn tool_user_shell_type(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn requested_tool_mode(turn_context: &TurnContext) -> ToolMode {
|
pub(crate) fn requested_tool_mode(turn_context: &TurnContext, model_info: &ModelInfo) -> ToolMode {
|
||||||
turn_context.model_info().tool_mode.unwrap_or_else(|| {
|
model_info.tool_mode.unwrap_or_else(|| {
|
||||||
if turn_context.config.features.enabled(Feature::CodeModeOnly) {
|
if turn_context.config.features.enabled(Feature::CodeModeOnly) {
|
||||||
ToolMode::CodeModeOnly
|
ToolMode::CodeModeOnly
|
||||||
} else if turn_context.config.features.enabled(Feature::CodeMode) {
|
} else if turn_context.config.features.enabled(Feature::CodeMode) {
|
||||||
@@ -76,8 +77,8 @@ pub(crate) fn requested_tool_mode(turn_context: &TurnContext) -> ToolMode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn effective_tool_mode(turn_context: &TurnContext) -> ToolMode {
|
pub(crate) fn effective_tool_mode(turn_context: &TurnContext, model_info: &ModelInfo) -> ToolMode {
|
||||||
let requested_tool_mode = requested_tool_mode(turn_context);
|
let requested_tool_mode = requested_tool_mode(turn_context, model_info);
|
||||||
if !turn_context.code_mode_available
|
if !turn_context.code_mode_available
|
||||||
&& requested_tool_mode == ToolMode::CodeMode
|
&& requested_tool_mode == ToolMode::CodeMode
|
||||||
&& !turn_context.config.code_mode.disable_in_process_fallback
|
&& !turn_context.config.code_mode.disable_in_process_fallback
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ impl ToolCallRuntime {
|
|||||||
executed_tool_calls.record_tool_call(
|
executed_tool_calls.record_tool_call(
|
||||||
&call,
|
&call,
|
||||||
&source,
|
&source,
|
||||||
super::effective_tool_mode(&self.step_context.turn),
|
self.step_context.tool_router.tool_mode(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let router = &self.step_context.tool_router;
|
let router = &self.step_context.tool_router;
|
||||||
@@ -349,6 +349,7 @@ impl Drop for ToolCallTimingGuard {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::session::step_context::StepContext;
|
use crate::session::step_context::StepContext;
|
||||||
@@ -362,6 +363,7 @@ mod tests {
|
|||||||
use codex_extension_api::ToolCallOutcome;
|
use codex_extension_api::ToolCallOutcome;
|
||||||
use codex_protocol::models::FunctionCallOutputBody;
|
use codex_protocol::models::FunctionCallOutputBody;
|
||||||
use codex_protocol::models::FunctionCallOutputPayload;
|
use codex_protocol::models::FunctionCallOutputPayload;
|
||||||
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
@@ -425,6 +427,10 @@ mod tests {
|
|||||||
let router = Arc::new(ToolRouter::from_parts(
|
let router = Arc::new(ToolRouter::from_parts(
|
||||||
ToolRegistry::from_tools([handler]),
|
ToolRegistry::from_tools([handler]),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
ToolMode::Direct,
|
||||||
|
BTreeMap::new(),
|
||||||
|
/*tool_namespaces_info*/ None,
|
||||||
|
&[],
|
||||||
));
|
));
|
||||||
let step_context = step_context.with_tool_router_for_test(router);
|
let step_context = step_context.with_tool_router_for_test(router);
|
||||||
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||||
@@ -610,6 +616,10 @@ mod tests {
|
|||||||
let router = Arc::new(ToolRouter::from_parts(
|
let router = Arc::new(ToolRouter::from_parts(
|
||||||
ToolRegistry::from_tools([handler]),
|
ToolRegistry::from_tools([handler]),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
|
ToolMode::Direct,
|
||||||
|
BTreeMap::new(),
|
||||||
|
/*tool_namespaces_info*/ None,
|
||||||
|
&[],
|
||||||
));
|
));
|
||||||
let step_context = step_context.with_tool_router_for_test(router);
|
let step_context = step_context.with_tool_router_for_test(router);
|
||||||
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::function_tool::FunctionCallError;
|
use crate::function_tool::FunctionCallError;
|
||||||
|
use crate::responses_metadata::TurnToolNamespacesInfo;
|
||||||
use crate::session::session::Session;
|
use crate::session::session::Session;
|
||||||
use crate::session::step_context::StepContext;
|
use crate::session::step_context::StepContext;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -16,7 +17,11 @@ use crate::tools::registry::ToolRegistry;
|
|||||||
use crate::tools::spec_plan::finalize_tool_router;
|
use crate::tools::spec_plan::finalize_tool_router;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use codex_protocol::models::SearchToolCallParams;
|
use codex_protocol::models::SearchToolCallParams;
|
||||||
|
#[cfg(test)]
|
||||||
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_tools::DiscoverableTool;
|
use codex_tools::DiscoverableTool;
|
||||||
|
use codex_tools::ResponsesApiNamespaceTool;
|
||||||
use codex_tools::ToolName;
|
use codex_tools::ToolName;
|
||||||
use codex_tools::ToolSpec;
|
use codex_tools::ToolSpec;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@@ -65,9 +70,14 @@ pub(crate) fn tool_log_payload<'a>(
|
|||||||
payload.log_payload()
|
payload.log_payload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One finalized tool plan: its advertised surfaces and matching executable runtimes.
|
||||||
pub struct ToolRouter {
|
pub struct ToolRouter {
|
||||||
registry: ToolRegistry,
|
registry: ToolRegistry,
|
||||||
model_visible_specs: Arc<[ToolSpec]>,
|
model_visible_specs: Arc<[ToolSpec]>,
|
||||||
|
tool_mode: ToolMode,
|
||||||
|
code_mode_tool_names: BTreeMap<String, ToolName>,
|
||||||
|
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
|
||||||
|
can_manage_children: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -86,12 +96,14 @@ impl ToolRouter {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn from_registry(
|
pub(crate) fn from_registry(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
registry: ToolRegistry,
|
registry: ToolRegistry,
|
||||||
hosted_specs: Vec<ToolSpec>,
|
hosted_specs: Vec<ToolSpec>,
|
||||||
tool_search_handler_cache: &ToolSearchHandlerCache,
|
tool_search_handler_cache: &ToolSearchHandlerCache,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
finalize_tool_router(
|
finalize_tool_router(
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
tool_search_handler_cache,
|
tool_search_handler_cache,
|
||||||
@@ -99,17 +111,103 @@ impl ToolRouter {
|
|||||||
.expect("test tool registry should not contain duplicate tools")
|
.expect("test tool registry should not contain duplicate tools")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_parts(registry: ToolRegistry, model_visible_specs: Vec<ToolSpec>) -> Self {
|
pub(crate) fn from_parts(
|
||||||
Self {
|
registry: ToolRegistry,
|
||||||
|
model_visible_specs: Vec<ToolSpec>,
|
||||||
|
tool_mode: ToolMode,
|
||||||
|
code_mode_tool_names: BTreeMap<String, ToolName>,
|
||||||
|
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
|
||||||
|
child_management_tools: &[ToolName],
|
||||||
|
) -> Self {
|
||||||
|
let mut router = Self {
|
||||||
registry,
|
registry,
|
||||||
model_visible_specs: model_visible_specs.into(),
|
model_visible_specs: model_visible_specs.into(),
|
||||||
}
|
tool_mode,
|
||||||
|
code_mode_tool_names,
|
||||||
|
tool_namespaces_info,
|
||||||
|
can_manage_children: false,
|
||||||
|
};
|
||||||
|
router.can_manage_children = !child_management_tools.is_empty()
|
||||||
|
&& child_management_tools
|
||||||
|
.iter()
|
||||||
|
.all(|name| router.exposes_tool(name));
|
||||||
|
router
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn model_visible_specs(&self) -> Arc<[ToolSpec]> {
|
pub(crate) fn model_visible_specs(&self) -> Arc<[ToolSpec]> {
|
||||||
Arc::clone(&self.model_visible_specs)
|
Arc::clone(&self.model_visible_specs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tool_mode(&self) -> ToolMode {
|
||||||
|
self.tool_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Code Mode still needs its dispatcher when the nested tool set is empty.
|
||||||
|
pub(crate) fn requires_code_mode_worker(&self) -> bool {
|
||||||
|
matches!(self.tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The normalized nested identities chosen after exclusions and collisions.
|
||||||
|
// Consumed by the follow-up cell-origin migration.
|
||||||
|
#[cfg_attr(not(test), allow(dead_code))]
|
||||||
|
pub(crate) fn code_mode_tool_names(&self) -> &BTreeMap<String, ToolName> {
|
||||||
|
&self.code_mode_tool_names
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional request inventory for this exact plan, without publishing it to turn state.
|
||||||
|
pub(crate) fn tool_namespaces_info(&self) -> Option<&TurnToolNamespacesInfo> {
|
||||||
|
self.tool_namespaces_info.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the model can both start and interact with a terminal process.
|
||||||
|
// Consumed by the follow-up live tool-plan selection.
|
||||||
|
#[cfg_attr(not(test), allow(dead_code))]
|
||||||
|
pub(crate) fn has_terminal_controls(&self) -> bool {
|
||||||
|
self.exposes_tool(&ToolName::plain("exec_command"))
|
||||||
|
&& self.exposes_tool(&ToolName::plain("write_stdin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the configured collaboration backend's child-management tools remain exposed.
|
||||||
|
// Consumed by the follow-up live tool-plan selection.
|
||||||
|
#[cfg_attr(not(test), allow(dead_code))]
|
||||||
|
pub(crate) fn can_manage_children(&self) -> bool {
|
||||||
|
self.can_manage_children
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answers if the tool plan lets the model invoke the tool directly, through code mode, or deferred tool search.
|
||||||
|
fn exposes_tool(&self, name: &ToolName) -> bool {
|
||||||
|
let name = name.clone().with_default_namespace();
|
||||||
|
if self
|
||||||
|
.code_mode_tool_names
|
||||||
|
.values()
|
||||||
|
.any(|nested| nested.clone().with_default_namespace() == name)
|
||||||
|
|| self.model_visible_specs.iter().any(|spec| match spec {
|
||||||
|
ToolSpec::Function(_) | ToolSpec::Freeform(_) => {
|
||||||
|
name.is_default_namespace() && spec.name() == name.name
|
||||||
|
}
|
||||||
|
ToolSpec::Namespace(namespace) => {
|
||||||
|
name.namespace.as_deref() == Some(namespace.name.as_str())
|
||||||
|
&& namespace.tools.iter().any(|tool| match tool {
|
||||||
|
ResponsesApiNamespaceTool::Function(tool) => tool.name == name.name,
|
||||||
|
ResponsesApiNamespaceTool::Custom(tool) => tool.name == name.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ToolSpec::ToolSearch { .. } | ToolSpec::WebSearch { .. } => false,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.model_visible_specs
|
||||||
|
.iter()
|
||||||
|
.any(|spec| matches!(spec, ToolSpec::ToolSearch { .. }))
|
||||||
|
&& self.registry.entries().any(|tool| {
|
||||||
|
tool.exposure.is_deferred()
|
||||||
|
&& tool.runtime.tool_name().with_default_namespace() == name
|
||||||
|
&& (tool.runtime.immutable_spec().is_some()
|
||||||
|
|| tool.runtime.search_info().is_some())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn deferred_tool_namespaces(&self) -> BTreeMap<String, String> {
|
pub(crate) fn deferred_tool_namespaces(&self) -> BTreeMap<String, String> {
|
||||||
self.registry.deferred_tool_namespaces()
|
self.registry.deferred_tool_namespaces()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ fn test_tool_router(
|
|||||||
) -> ToolRouter {
|
) -> ToolRouter {
|
||||||
let mut registry = build_core_tool_registry(
|
let mut registry = build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
step_context.mcp.as_ref(),
|
step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -147,6 +148,7 @@ fn test_tool_router(
|
|||||||
);
|
);
|
||||||
let hosted_specs = append_source_tools(
|
let hosted_specs = append_source_tools(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
mcp_tools,
|
mcp_tools,
|
||||||
extension_tool_executors,
|
extension_tool_executors,
|
||||||
@@ -154,6 +156,7 @@ fn test_tool_router(
|
|||||||
);
|
);
|
||||||
ToolRouter::from_registry(
|
ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ use crate::tools::handlers::multi_agents::WaitAgentHandler;
|
|||||||
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
|
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
|
||||||
use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
|
use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
|
||||||
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
|
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
|
||||||
|
use crate::tools::handlers::multi_agents_spec::MULTI_AGENT_V1_NAMESPACE;
|
||||||
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
|
||||||
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
|
||||||
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
|
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
|
||||||
@@ -72,6 +73,7 @@ use codex_protocol::error::Result as CodexResult;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
use codex_protocol::openai_models::InputModality;
|
use codex_protocol::openai_models::InputModality;
|
||||||
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
use codex_protocol::openai_models::ToolMode;
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_protocol::protocol::MultiAgentVersion;
|
use codex_protocol::protocol::MultiAgentVersion;
|
||||||
use codex_tools::ResponsesApiNamespace;
|
use codex_tools::ResponsesApiNamespace;
|
||||||
@@ -105,6 +107,7 @@ const IMAGEGEN_TOOL_NAME: &str = "imagegen";
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct CoreToolPlanContext<'a> {
|
struct CoreToolPlanContext<'a> {
|
||||||
turn_context: &'a TurnContext,
|
turn_context: &'a TurnContext,
|
||||||
|
model_info: &'a ModelInfo,
|
||||||
environments: &'a TurnEnvironmentSnapshot,
|
environments: &'a TurnEnvironmentSnapshot,
|
||||||
mcp: &'a codex_mcp::McpBinding,
|
mcp: &'a codex_mcp::McpBinding,
|
||||||
tool_suggest_candidates: Option<&'a crate::tools::router::ToolSuggestCandidates>,
|
tool_suggest_candidates: Option<&'a crate::tools::router::ToolSuggestCandidates>,
|
||||||
@@ -114,9 +117,11 @@ struct CoreToolPlanContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn build_tool_router(
|
pub(crate) fn build_tool_router(
|
||||||
session: &Session,
|
session: &Session,
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
environments: &TurnEnvironmentSnapshot,
|
environments: &TurnEnvironmentSnapshot,
|
||||||
mcp: &Arc<codex_mcp::McpBinding>,
|
mcp: &Arc<codex_mcp::McpBinding>,
|
||||||
apps_enabled: bool,
|
apps_enabled: bool,
|
||||||
@@ -131,6 +136,7 @@ pub(crate) fn build_tool_router(
|
|||||||
.get::<crate::WaitForEnvironmentToolConfig>();
|
.get::<crate::WaitForEnvironmentToolConfig>();
|
||||||
let context = CoreToolPlanContext {
|
let context = CoreToolPlanContext {
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
environments,
|
environments,
|
||||||
mcp,
|
mcp,
|
||||||
tool_suggest_candidates,
|
tool_suggest_candidates,
|
||||||
@@ -149,6 +155,7 @@ pub(crate) fn build_tool_router(
|
|||||||
{
|
{
|
||||||
append_extension_tool_executors(
|
append_extension_tool_executors(
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
history_tools.0.iter().cloned(),
|
history_tools.0.iter().cloned(),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
);
|
);
|
||||||
@@ -160,21 +167,33 @@ pub(crate) fn build_tool_router(
|
|||||||
&turn_context.config,
|
&turn_context.config,
|
||||||
apps_enabled,
|
apps_enabled,
|
||||||
&mcp.config().mcp_server_catalog,
|
&mcp.config().mcp_server_catalog,
|
||||||
search_tool_enabled(turn_context),
|
search_tool_enabled(turn_context, model_info),
|
||||||
|
&mut registry,
|
||||||
|
);
|
||||||
|
apply_mcp_tool_exposure_policy(
|
||||||
|
turn_context,
|
||||||
|
model_info,
|
||||||
|
mcp,
|
||||||
|
®istered_mcp_tools,
|
||||||
&mut registry,
|
&mut registry,
|
||||||
);
|
);
|
||||||
apply_mcp_tool_exposure_policy(turn_context, mcp, ®istered_mcp_tools, &mut registry);
|
|
||||||
let standalone_web_search_tool = append_extension_tool_executors(
|
let standalone_web_search_tool = append_extension_tool_executors(
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
extension_tool_executors(session, step_store),
|
extension_tool_executors(session, step_store),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
);
|
);
|
||||||
append_dynamic_tool_runtimes(&turn_context.dynamic_tools, &mut registry);
|
append_dynamic_tool_runtimes(&turn_context.dynamic_tools, &mut registry);
|
||||||
hosted_model_tool_specs(turn_context, standalone_web_search_tool.as_slice())
|
hosted_model_tool_specs(
|
||||||
|
turn_context,
|
||||||
|
model_info,
|
||||||
|
standalone_web_search_tool.as_slice(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
finalize_tool_router(
|
finalize_tool_router(
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&session.services.tool_search_handler_cache,
|
&session.services.tool_search_handler_cache,
|
||||||
@@ -183,6 +202,7 @@ pub(crate) fn build_tool_router(
|
|||||||
|
|
||||||
fn apply_mcp_tool_exposure_policy(
|
fn apply_mcp_tool_exposure_policy(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
mcp: &codex_mcp::McpBinding,
|
mcp: &codex_mcp::McpBinding,
|
||||||
registered_mcp_tools: &HashSet<ToolName>,
|
registered_mcp_tools: &HashSet<ToolName>,
|
||||||
registry: &mut ToolRegistry,
|
registry: &mut ToolRegistry,
|
||||||
@@ -228,9 +248,9 @@ fn apply_mcp_tool_exposure_policy(
|
|||||||
exposures = exposures.difference(ToolExposures::DEFERRED | ToolExposures::CODE_MODE);
|
exposures = exposures.difference(ToolExposures::DEFERRED | ToolExposures::CODE_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
exposures = if search_tool_enabled(turn_context)
|
exposures = if search_tool_enabled(turn_context, model_info)
|
||||||
&& exposures.contains(ToolExposures::DEFERRED)
|
&& exposures.contains(ToolExposures::DEFERRED)
|
||||||
&& (effective_tool_mode(turn_context) != ToolMode::CodeModeOnly
|
&& (effective_tool_mode(turn_context, model_info) != ToolMode::CodeModeOnly
|
||||||
|| exposures.contains(ToolExposures::CODE_MODE))
|
|| exposures.contains(ToolExposures::CODE_MODE))
|
||||||
{
|
{
|
||||||
exposures.difference(ToolExposures::DIRECT)
|
exposures.difference(ToolExposures::DIRECT)
|
||||||
@@ -258,6 +278,7 @@ fn apply_mcp_tool_exposure_policy(
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
pub(crate) fn build_core_tool_registry(
|
pub(crate) fn build_core_tool_registry(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
environments: &TurnEnvironmentSnapshot,
|
environments: &TurnEnvironmentSnapshot,
|
||||||
mcp: &codex_mcp::McpBinding,
|
mcp: &codex_mcp::McpBinding,
|
||||||
tool_suggest_candidates: Option<&crate::tools::router::ToolSuggestCandidates>,
|
tool_suggest_candidates: Option<&crate::tools::router::ToolSuggestCandidates>,
|
||||||
@@ -267,6 +288,7 @@ pub(crate) fn build_core_tool_registry(
|
|||||||
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
||||||
let context = CoreToolPlanContext {
|
let context = CoreToolPlanContext {
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
environments,
|
environments,
|
||||||
mcp,
|
mcp,
|
||||||
tool_suggest_candidates,
|
tool_suggest_candidates,
|
||||||
@@ -283,6 +305,7 @@ pub(crate) fn build_core_tool_registry(
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
pub(crate) fn append_source_tools(
|
pub(crate) fn append_source_tools(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
registry: &mut ToolRegistry,
|
registry: &mut ToolRegistry,
|
||||||
mcp_tools: Vec<RegisteredTool>,
|
mcp_tools: Vec<RegisteredTool>,
|
||||||
extension_tool_executors: impl IntoIterator<
|
extension_tool_executors: impl IntoIterator<
|
||||||
@@ -297,10 +320,18 @@ pub(crate) fn append_source_tools(
|
|||||||
for tool in mcp_tools {
|
for tool in mcp_tools {
|
||||||
registry.register_external_with_exposure(tool.runtime, tool.exposure);
|
registry.register_external_with_exposure(tool.runtime, tool.exposure);
|
||||||
}
|
}
|
||||||
let standalone_web_search_tool =
|
let standalone_web_search_tool = append_extension_tool_executors(
|
||||||
append_extension_tool_executors(turn_context, extension_tool_executors, registry);
|
turn_context,
|
||||||
|
model_info,
|
||||||
|
extension_tool_executors,
|
||||||
|
registry,
|
||||||
|
);
|
||||||
append_dynamic_tool_runtimes(dynamic_tools, registry);
|
append_dynamic_tool_runtimes(dynamic_tools, registry);
|
||||||
hosted_model_tool_specs(turn_context, standalone_web_search_tool.as_slice())
|
hosted_model_tool_specs(
|
||||||
|
turn_context,
|
||||||
|
model_info,
|
||||||
|
standalone_web_search_tool.as_slice(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
@@ -325,15 +356,14 @@ pub(crate) fn extension_tool_executors<'a>(
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
pub(crate) fn finalize_tool_router(
|
pub(crate) fn finalize_tool_router(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
mut registry: ToolRegistry,
|
mut registry: ToolRegistry,
|
||||||
hosted_specs: Vec<ToolSpec>,
|
hosted_specs: Vec<ToolSpec>,
|
||||||
tool_search_handler_cache: &ToolSearchHandlerCache,
|
tool_search_handler_cache: &ToolSearchHandlerCache,
|
||||||
) -> CodexResult<ToolRouter> {
|
) -> CodexResult<ToolRouter> {
|
||||||
apply_direct_model_only_namespace_overrides(turn_context, &mut registry);
|
apply_direct_model_only_namespace_overrides(turn_context, &mut registry);
|
||||||
let code_mode_enabled = matches!(
|
let tool_mode = effective_tool_mode(turn_context, model_info);
|
||||||
effective_tool_mode(turn_context),
|
let code_mode_enabled = matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly);
|
||||||
ToolMode::CodeMode | ToolMode::CodeModeOnly
|
|
||||||
);
|
|
||||||
if code_mode_enabled {
|
if code_mode_enabled {
|
||||||
for tool_name in [
|
for tool_name in [
|
||||||
ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME),
|
ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME),
|
||||||
@@ -345,7 +375,7 @@ pub(crate) fn finalize_tool_router(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tool_search_name = ToolName::plain(TOOL_SEARCH_TOOL_NAME);
|
let tool_search_name = ToolName::plain(TOOL_SEARCH_TOOL_NAME);
|
||||||
if search_tool_enabled(turn_context)
|
if search_tool_enabled(turn_context, model_info)
|
||||||
&& registry.entries().any(|tool| {
|
&& registry.entries().any(|tool| {
|
||||||
tool.runtime.tool_name() != tool_search_name
|
tool.runtime.tool_name() != tool_search_name
|
||||||
&& tool.exposure.is_deferred()
|
&& tool.exposure.is_deferred()
|
||||||
@@ -381,12 +411,13 @@ pub(crate) fn finalize_tool_router(
|
|||||||
append_tool_search_executor(turn_context, &mut registry, tool_search_handler_cache);
|
append_tool_search_executor(turn_context, &mut registry, tool_search_handler_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
let code_mode_tool_names = register_code_mode_executors(turn_context, &mut registry);
|
let code_mode_tool_names =
|
||||||
|
register_code_mode_executors(turn_context, model_info, &mut registry);
|
||||||
let include_tool_namespaces_info = turn_context
|
let include_tool_namespaces_info = turn_context
|
||||||
.config
|
.config
|
||||||
.tool_registry
|
.tool_registry
|
||||||
.turn_metadata_includes_tool_info
|
.turn_metadata_includes_tool_info
|
||||||
&& turn_context.model_info().use_responses_lite;
|
&& model_info.use_responses_lite;
|
||||||
|
|
||||||
if turn_context.config.tool_registry.error_on_tool_collisions {
|
if turn_context.config.tool_registry.error_on_tool_collisions {
|
||||||
if let Some(tool_name) = registry.first_collision() {
|
if let Some(tool_name) = registry.first_collision() {
|
||||||
@@ -444,19 +475,28 @@ pub(crate) fn finalize_tool_router(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let model_visible_specs =
|
let model_visible_specs = build_model_visible_specs(
|
||||||
build_model_visible_specs(turn_context, ®istry, &code_mode_tool_names, hosted_specs);
|
turn_context,
|
||||||
if include_tool_namespaces_info {
|
model_info,
|
||||||
turn_context
|
®istry,
|
||||||
.turn_metadata_state
|
&code_mode_tool_names,
|
||||||
.set_tool_namespaces_info(collect_tool_namespaces_info(
|
hosted_specs,
|
||||||
®istry,
|
);
|
||||||
&code_mode_tool_names,
|
let tool_namespaces_info = include_tool_namespaces_info
|
||||||
&model_visible_specs,
|
.then(|| {
|
||||||
));
|
collect_tool_namespaces_info(®istry, &code_mode_tool_names, &model_visible_specs)
|
||||||
}
|
})
|
||||||
|
.filter(|info| !info.is_empty());
|
||||||
|
let child_management_tools = required_child_management_tool_names(turn_context, model_info);
|
||||||
|
|
||||||
Ok(ToolRouter::from_parts(registry, model_visible_specs))
|
Ok(ToolRouter::from_parts(
|
||||||
|
registry,
|
||||||
|
model_visible_specs,
|
||||||
|
tool_mode,
|
||||||
|
code_mode_tool_names,
|
||||||
|
tool_namespaces_info,
|
||||||
|
&child_management_tools,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_direct_model_only_namespace_overrides(
|
fn apply_direct_model_only_namespace_overrides(
|
||||||
@@ -495,6 +535,7 @@ fn apply_direct_model_only_namespace_overrides(
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
fn build_model_visible_specs(
|
fn build_model_visible_specs(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
registry: &ToolRegistry,
|
registry: &ToolRegistry,
|
||||||
code_mode_tool_names: &BTreeMap<String, ToolName>,
|
code_mode_tool_names: &BTreeMap<String, ToolName>,
|
||||||
hosted_specs: Vec<ToolSpec>,
|
hosted_specs: Vec<ToolSpec>,
|
||||||
@@ -507,13 +548,14 @@ fn build_model_visible_specs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tool_name = tool.runtime.tool_name();
|
let tool_name = tool.runtime.tool_name();
|
||||||
if is_hidden_by_code_mode_only(turn_context, &tool_name, exposure) {
|
if is_hidden_by_code_mode_only(turn_context, model_info, &tool_name, exposure) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let spec = tool.runtime.spec();
|
let spec = tool.runtime.spec();
|
||||||
specs.push(spec_for_model_request(
|
specs.push(spec_for_model_request(
|
||||||
turn_context,
|
turn_context,
|
||||||
|
model_info,
|
||||||
exposure,
|
exposure,
|
||||||
&tool_name,
|
&tool_name,
|
||||||
code_mode_tool_names,
|
code_mode_tool_names,
|
||||||
@@ -532,12 +574,13 @@ fn build_model_visible_specs(
|
|||||||
|
|
||||||
fn spec_for_model_request(
|
fn spec_for_model_request(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
exposure: ToolExposure,
|
exposure: ToolExposure,
|
||||||
tool_name: &ToolName,
|
tool_name: &ToolName,
|
||||||
code_mode_tool_names: &BTreeMap<String, ToolName>,
|
code_mode_tool_names: &BTreeMap<String, ToolName>,
|
||||||
spec: ToolSpec,
|
spec: ToolSpec,
|
||||||
) -> ToolSpec {
|
) -> ToolSpec {
|
||||||
let tool_mode = effective_tool_mode(turn_context);
|
let tool_mode = effective_tool_mode(turn_context, model_info);
|
||||||
if matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly)
|
if matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly)
|
||||||
&& exposure.is_available_in_code_mode()
|
&& exposure.is_available_in_code_mode()
|
||||||
&& !is_excluded_from_code_mode(turn_context, tool_name)
|
&& !is_excluded_from_code_mode(turn_context, tool_name)
|
||||||
@@ -557,17 +600,18 @@ fn spec_for_model_request(
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
fn hosted_model_tool_specs(
|
fn hosted_model_tool_specs(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
registered_extension_tool_names: &[ToolName],
|
registered_extension_tool_names: &[ToolName],
|
||||||
) -> Vec<ToolSpec> {
|
) -> Vec<ToolSpec> {
|
||||||
// Responses Lite accepts schemas for client-executed tools, not hosted Responses tools.
|
// Responses Lite accepts schemas for client-executed tools, not hosted Responses tools.
|
||||||
if turn_context.model_info().use_responses_lite
|
if model_info.use_responses_lite
|
||||||
|| crate::guardian::is_basic_session_source(&turn_context.session_source)
|
|| crate::guardian::is_basic_session_source(&turn_context.session_source)
|
||||||
{
|
{
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut specs = Vec::new();
|
let mut specs = Vec::new();
|
||||||
let standalone_web_search_available = standalone_web_search_enabled(turn_context)
|
let standalone_web_search_available = standalone_web_search_enabled(turn_context, model_info)
|
||||||
&& registered_extension_tool_names.contains(&ToolName::namespaced("web", "run"));
|
&& registered_extension_tool_names.contains(&ToolName::namespaced("web", "run"));
|
||||||
// `Some(Cached/Live/Disabled)` are the options for mode when standalone search is unavailable
|
// `Some(Cached/Live/Disabled)` are the options for mode when standalone search is unavailable
|
||||||
// and the provider supports hosted search. `None` prevents emitting a hosted search tool.
|
// and the provider supports hosted search. `None` prevents emitting a hosted search tool.
|
||||||
@@ -580,15 +624,15 @@ fn hosted_model_tool_specs(
|
|||||||
if let Some(hosted_web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
if let Some(hosted_web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||||
web_search_mode,
|
web_search_mode,
|
||||||
web_search_config,
|
web_search_config,
|
||||||
web_search_tool_type: turn_context.model_info().web_search_tool_type,
|
web_search_tool_type: model_info.web_search_tool_type,
|
||||||
}) {
|
}) {
|
||||||
specs.push(hosted_web_search_tool);
|
specs.push(hosted_web_search_tool);
|
||||||
}
|
}
|
||||||
specs
|
specs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn search_tool_enabled(turn_context: &TurnContext) -> bool {
|
pub(crate) fn search_tool_enabled(turn_context: &TurnContext, model_info: &ModelInfo) -> bool {
|
||||||
turn_context.model_info().supports_search_tool && namespace_tools_enabled(turn_context)
|
model_info.supports_search_tool && namespace_tools_enabled(turn_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tool_suggest_enabled(turn_context: &TurnContext) -> bool {
|
pub(crate) fn tool_suggest_enabled(turn_context: &TurnContext) -> bool {
|
||||||
@@ -606,7 +650,7 @@ fn multi_agent_v2_enabled(turn_context: &TurnContext) -> bool {
|
|||||||
turn_context.multi_agent_version == MultiAgentVersion::V2
|
turn_context.multi_agent_version == MultiAgentVersion::V2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
|
fn collab_tools_enabled(turn_context: &TurnContext, model_info: &ModelInfo) -> bool {
|
||||||
match turn_context.multi_agent_version {
|
match turn_context.multi_agent_version {
|
||||||
MultiAgentVersion::Disabled => false,
|
MultiAgentVersion::Disabled => false,
|
||||||
MultiAgentVersion::V1 => !exceeds_thread_spawn_depth_limit(
|
MultiAgentVersion::V1 => !exceeds_thread_spawn_depth_limit(
|
||||||
@@ -615,12 +659,49 @@ fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
|
|||||||
),
|
),
|
||||||
MultiAgentVersion::V2 => {
|
MultiAgentVersion::V2 => {
|
||||||
turn_context.session_source.get_agent_path().is_none()
|
turn_context.session_source.get_agent_path().is_none()
|
||||||
|| turn_context.model_info().multi_agent_version == Some(MultiAgentVersion::V2)
|
|| model_info.multi_agent_version == Some(MultiAgentVersion::V2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_generation_available(turn_context: &TurnContext) -> bool {
|
fn required_child_management_tool_names(
|
||||||
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
|
) -> Vec<ToolName> {
|
||||||
|
if !collab_tools_enabled(turn_context, model_info) {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (namespace, names): (_, &[&str]) = match turn_context.multi_agent_version {
|
||||||
|
MultiAgentVersion::Disabled => return Vec::new(),
|
||||||
|
MultiAgentVersion::V1 => (
|
||||||
|
Some(MULTI_AGENT_V1_NAMESPACE),
|
||||||
|
&["send_input", "wait_agent", "resume_agent", "close_agent"],
|
||||||
|
),
|
||||||
|
MultiAgentVersion::V2 => (
|
||||||
|
namespace_tools_enabled(turn_context)
|
||||||
|
.then_some(turn_context.config.multi_agent_v2.tool_namespace.as_deref())
|
||||||
|
.flatten(),
|
||||||
|
&[
|
||||||
|
"send_message",
|
||||||
|
"followup_task",
|
||||||
|
"interrupt_agent",
|
||||||
|
"list_agents",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let mut tools = names
|
||||||
|
.iter()
|
||||||
|
.map(|name| ToolName::new(namespace.map(str::to_owned), (*name).to_owned()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if multi_agent_v2_enabled(turn_context) && turn_context.config.multi_agent_v2.wait_agent_enabled
|
||||||
|
{
|
||||||
|
tools.push(ToolName::new(namespace.map(str::to_owned), "wait_agent"));
|
||||||
|
}
|
||||||
|
tools
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_generation_available(turn_context: &TurnContext, model_info: &ModelInfo) -> bool {
|
||||||
if !turn_context
|
if !turn_context
|
||||||
.config
|
.config
|
||||||
.features
|
.features
|
||||||
@@ -645,11 +726,7 @@ fn image_generation_available(turn_context: &TurnContext) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !turn_context
|
if !model_info.input_modalities.contains(&InputModality::Image) {
|
||||||
.model_info()
|
|
||||||
.input_modalities
|
|
||||||
.contains(&InputModality::Image)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,10 +770,11 @@ fn agent_type_description(
|
|||||||
|
|
||||||
fn is_hidden_by_code_mode_only(
|
fn is_hidden_by_code_mode_only(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
tool_name: &ToolName,
|
tool_name: &ToolName,
|
||||||
exposure: ToolExposure,
|
exposure: ToolExposure,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let tool_mode = effective_tool_mode(turn_context);
|
let tool_mode = effective_tool_mode(turn_context, model_info);
|
||||||
tool_mode == ToolMode::CodeModeOnly
|
tool_mode == ToolMode::CodeModeOnly
|
||||||
&& exposure.is_available_in_code_mode()
|
&& exposure.is_available_in_code_mode()
|
||||||
&& codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name(
|
&& codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name(
|
||||||
@@ -717,9 +795,10 @@ fn is_excluded_from_code_mode(turn_context: &TurnContext, tool_name: &ToolName)
|
|||||||
|
|
||||||
fn register_code_mode_executors(
|
fn register_code_mode_executors(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
registry: &mut ToolRegistry,
|
registry: &mut ToolRegistry,
|
||||||
) -> BTreeMap<String, ToolName> {
|
) -> BTreeMap<String, ToolName> {
|
||||||
let tool_mode = effective_tool_mode(turn_context);
|
let tool_mode = effective_tool_mode(turn_context, model_info);
|
||||||
if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) {
|
if !matches!(tool_mode, ToolMode::CodeMode | ToolMode::CodeModeOnly) {
|
||||||
return BTreeMap::new();
|
return BTreeMap::new();
|
||||||
}
|
}
|
||||||
@@ -729,7 +808,7 @@ fn register_code_mode_executors(
|
|||||||
let mut exec_prompt_tool_specs = Vec::new();
|
let mut exec_prompt_tool_specs = Vec::new();
|
||||||
let mut deferred_exec_prompt_tool_specs = Vec::new();
|
let mut deferred_exec_prompt_tool_specs = Vec::new();
|
||||||
let mut included_deferred_mcp_output_schema = false;
|
let mut included_deferred_mcp_output_schema = false;
|
||||||
let deferred_tools_guidance_enabled = search_tool_enabled(turn_context);
|
let deferred_tools_guidance_enabled = search_tool_enabled(turn_context, model_info);
|
||||||
for tool in registry.entries() {
|
for tool in registry.entries() {
|
||||||
let exposure = tool.exposure;
|
let exposure = tool.exposure;
|
||||||
if !exposure.is_available_in_code_mode() {
|
if !exposure.is_available_in_code_mode() {
|
||||||
@@ -807,10 +886,7 @@ fn register_code_mode_executors(
|
|||||||
&namespace_descriptions,
|
&namespace_descriptions,
|
||||||
turn_context.config.code_mode.default_exec_yield_time_ms,
|
turn_context.config.code_mode.default_exec_yield_time_ms,
|
||||||
tool_mode == ToolMode::CodeModeOnly,
|
tool_mode == ToolMode::CodeModeOnly,
|
||||||
if unified_image_budget_enabled(
|
if unified_image_budget_enabled(&turn_context.config.features, model_info) {
|
||||||
&turn_context.config.features,
|
|
||||||
turn_context.model_info(),
|
|
||||||
) {
|
|
||||||
codex_code_mode::ImageDetailVisibility::Hidden
|
codex_code_mode::ImageDetailVisibility::Hidden
|
||||||
} else {
|
} else {
|
||||||
codex_code_mode::ImageDetailVisibility::Visible
|
codex_code_mode::ImageDetailVisibility::Visible
|
||||||
@@ -922,10 +998,7 @@ fn add_core_tool_sources(context: &CoreToolPlanContext<'_>, registry: &mut ToolR
|
|||||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||||
if turn_context.config.features.enabled(Feature::ShellTool)
|
if turn_context.config.features.enabled(Feature::ShellTool)
|
||||||
&& turn_context.config.features.enabled(Feature::UnifiedExec)
|
&& turn_context.config.features.enabled(Feature::UnifiedExec)
|
||||||
&& !matches!(
|
&& !matches!(context.model_info.shell_type, ConfigShellToolType::Disabled)
|
||||||
turn_context.model_info().shell_type,
|
|
||||||
ConfigShellToolType::Disabled
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
registry.add(ExecCommandHandler::new(ExecCommandHandlerOptions {
|
registry.add(ExecCommandHandler::new(ExecCommandHandlerOptions {
|
||||||
allow_login_shell: any_environment_allows_login_shell(context.environments),
|
allow_login_shell: any_environment_allows_login_shell(context.environments),
|
||||||
@@ -941,11 +1014,11 @@ fn add_core_tool_sources(context: &CoreToolPlanContext<'_>, registry: &mut ToolR
|
|||||||
if turn_context.config.features.enabled(Feature::ViewImage) {
|
if turn_context.config.features.enabled(Feature::ViewImage) {
|
||||||
registry.add(ViewImageHandler::new(ViewImageToolOptions {
|
registry.add(ViewImageHandler::new(ViewImageToolOptions {
|
||||||
can_request_original_image_detail: can_request_original_image_detail(
|
can_request_original_image_detail: can_request_original_image_detail(
|
||||||
turn_context.model_info(),
|
context.model_info,
|
||||||
),
|
),
|
||||||
unified_image_budget: unified_image_budget_enabled(
|
unified_image_budget: unified_image_budget_enabled(
|
||||||
&turn_context.config.features,
|
&turn_context.config.features,
|
||||||
turn_context.model_info(),
|
context.model_info,
|
||||||
),
|
),
|
||||||
include_environment_id,
|
include_environment_id,
|
||||||
}));
|
}));
|
||||||
@@ -960,10 +1033,10 @@ fn add_core_tool_sources(context: &CoreToolPlanContext<'_>, registry: &mut ToolR
|
|||||||
add_collaboration_tools(context, registry);
|
add_collaboration_tools(context, registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn standalone_web_search_enabled(turn_context: &TurnContext) -> bool {
|
fn standalone_web_search_enabled(turn_context: &TurnContext, model_info: &ModelInfo) -> bool {
|
||||||
namespace_tools_enabled(turn_context)
|
namespace_tools_enabled(turn_context)
|
||||||
&& turn_context.provider.capabilities().web_search
|
&& turn_context.provider.capabilities().web_search
|
||||||
&& (turn_context.model_info().use_responses_lite
|
&& (model_info.use_responses_lite
|
||||||
|| turn_context
|
|| turn_context
|
||||||
.config
|
.config
|
||||||
.features
|
.features
|
||||||
@@ -989,10 +1062,7 @@ fn add_shell_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistr
|
|||||||
if !environment_mode.has_environment()
|
if !environment_mode.has_environment()
|
||||||
|| !features.enabled(Feature::ShellTool)
|
|| !features.enabled(Feature::ShellTool)
|
||||||
|| !features.enabled(Feature::UnifiedExec)
|
|| !features.enabled(Feature::UnifiedExec)
|
||||||
|| matches!(
|
|| matches!(context.model_info.shell_type, ConfigShellToolType::Disabled)
|
||||||
turn_context.model_info().shell_type,
|
|
||||||
ConfigShellToolType::Disabled
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1065,8 +1135,8 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !turn_context.session_source.is_non_root_agent()
|
if !turn_context.session_source.is_non_root_agent()
|
||||||
&& turn_context
|
&& context
|
||||||
.model_info()
|
.model_info
|
||||||
.experimental_supported_tools
|
.experimental_supported_tools
|
||||||
.iter()
|
.iter()
|
||||||
.any(|tool| tool == "send_user_message_async")
|
.any(|tool| tool == "send_user_message_async")
|
||||||
@@ -1111,15 +1181,13 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if environment_mode.has_environment()
|
if environment_mode.has_environment() && context.model_info.apply_patch_tool_type.is_some() {
|
||||||
&& turn_context.model_info().apply_patch_tool_type.is_some()
|
|
||||||
{
|
|
||||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||||
registry.add(ApplyPatchHandler::new(include_environment_id));
|
registry.add(ApplyPatchHandler::new(include_environment_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if turn_context
|
if context
|
||||||
.model_info()
|
.model_info
|
||||||
.experimental_supported_tools
|
.experimental_supported_tools
|
||||||
.iter()
|
.iter()
|
||||||
.any(|tool| tool == "test_sync_tool")
|
.any(|tool| tool == "test_sync_tool")
|
||||||
@@ -1131,11 +1199,11 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
|||||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||||
registry.add(ViewImageHandler::new(ViewImageToolOptions {
|
registry.add(ViewImageHandler::new(ViewImageToolOptions {
|
||||||
can_request_original_image_detail: can_request_original_image_detail(
|
can_request_original_image_detail: can_request_original_image_detail(
|
||||||
turn_context.model_info(),
|
context.model_info,
|
||||||
),
|
),
|
||||||
unified_image_budget: unified_image_budget_enabled(
|
unified_image_budget: unified_image_budget_enabled(
|
||||||
&turn_context.config.features,
|
&turn_context.config.features,
|
||||||
turn_context.model_info(),
|
context.model_info,
|
||||||
),
|
),
|
||||||
include_environment_id,
|
include_environment_id,
|
||||||
}));
|
}));
|
||||||
@@ -1145,7 +1213,7 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
|||||||
#[instrument(level = "trace", skip_all)]
|
#[instrument(level = "trace", skip_all)]
|
||||||
fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistry) {
|
fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistry) {
|
||||||
let turn_context = context.turn_context;
|
let turn_context = context.turn_context;
|
||||||
if collab_tools_enabled(turn_context) {
|
if collab_tools_enabled(turn_context, context.model_info) {
|
||||||
if multi_agent_v2_enabled(turn_context) {
|
if multi_agent_v2_enabled(turn_context) {
|
||||||
let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only {
|
let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only {
|
||||||
ToolExposure::DirectModelOnly
|
ToolExposure::DirectModelOnly
|
||||||
@@ -1205,7 +1273,7 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, registry: &mut Too
|
|||||||
} else {
|
} else {
|
||||||
let agent_type_description =
|
let agent_type_description =
|
||||||
agent_type_description(turn_context, context.default_agent_type_description);
|
agent_type_description(turn_context, context.default_agent_type_description);
|
||||||
let exposure = if search_tool_enabled(turn_context) {
|
let exposure = if search_tool_enabled(turn_context, context.model_info) {
|
||||||
ToolExposure::Deferred
|
ToolExposure::Deferred
|
||||||
} else {
|
} else {
|
||||||
ToolExposure::Direct
|
ToolExposure::Direct
|
||||||
@@ -1285,10 +1353,11 @@ fn append_tool_search_executor(
|
|||||||
|
|
||||||
fn append_extension_tool_executors(
|
fn append_extension_tool_executors(
|
||||||
turn_context: &TurnContext,
|
turn_context: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
executors: impl IntoIterator<Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>>,
|
executors: impl IntoIterator<Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>>,
|
||||||
registry: &mut ToolRegistry,
|
registry: &mut ToolRegistry,
|
||||||
) -> Option<ToolName> {
|
) -> Option<ToolName> {
|
||||||
let standalone_web_search_enabled = standalone_web_search_enabled(turn_context);
|
let standalone_web_search_enabled = standalone_web_search_enabled(turn_context, model_info);
|
||||||
let web_search_mode_on = turn_context.config.web_search_mode.value() != WebSearchMode::Disabled;
|
let web_search_mode_on = turn_context.config.web_search_mode.value() != WebSearchMode::Disabled;
|
||||||
let mut standalone_web_search_tool = None;
|
let mut standalone_web_search_tool = None;
|
||||||
|
|
||||||
@@ -1299,7 +1368,7 @@ fn append_extension_tool_executors(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if tool_name == ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME)
|
if tool_name == ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME)
|
||||||
&& !image_generation_available(turn_context)
|
&& !image_generation_available(turn_context, model_info)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use codex_protocol::error::CodexErrorDetails;
|
|||||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||||
use codex_protocol::openai_models::ConfigShellToolType;
|
use codex_protocol::openai_models::ConfigShellToolType;
|
||||||
use codex_protocol::openai_models::InputModality;
|
use codex_protocol::openai_models::InputModality;
|
||||||
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
use codex_protocol::openai_models::ToolMode;
|
use codex_protocol::openai_models::ToolMode;
|
||||||
use codex_protocol::openai_models::WebSearchToolType;
|
use codex_protocol::openai_models::WebSearchToolType;
|
||||||
use codex_protocol::protocol::EnvironmentConfigState;
|
use codex_protocol::protocol::EnvironmentConfigState;
|
||||||
@@ -46,6 +47,7 @@ use crate::config::CurrentTimeReminderConfig;
|
|||||||
use crate::environment_selection::TurnEnvironmentState;
|
use crate::environment_selection::TurnEnvironmentState;
|
||||||
use crate::responses_metadata::CodexResponsesRequestKind;
|
use crate::responses_metadata::CodexResponsesRequestKind;
|
||||||
use crate::responses_metadata::TurnToolFunctionInfo;
|
use crate::responses_metadata::TurnToolFunctionInfo;
|
||||||
|
use crate::responses_metadata::TurnToolNamespacesInfo;
|
||||||
use crate::responses_metadata::TurnToolSource;
|
use crate::responses_metadata::TurnToolSource;
|
||||||
use crate::session::step_context::StepContext;
|
use crate::session::step_context::StepContext;
|
||||||
use crate::session::tests::make_session_and_context;
|
use crate::session::tests::make_session_and_context;
|
||||||
@@ -74,12 +76,19 @@ struct ToolPlanInputs {
|
|||||||
dynamic_tools: Vec<DynamicToolSpec>,
|
dynamic_tools: Vec<DynamicToolSpec>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
struct ToolPlanProbe {
|
struct ToolPlanProbe {
|
||||||
visible_specs: Vec<ToolSpec>,
|
visible_specs: Vec<ToolSpec>,
|
||||||
visible_names: Vec<String>,
|
visible_names: Vec<String>,
|
||||||
namespace_functions: BTreeMap<String, Vec<String>>,
|
namespace_functions: BTreeMap<String, Vec<String>>,
|
||||||
registered_names: Vec<String>,
|
registered_names: Vec<String>,
|
||||||
exposures: BTreeMap<String, ToolExposure>,
|
exposures: BTreeMap<String, ToolExposure>,
|
||||||
|
tool_namespaces_info: Option<TurnToolNamespacesInfo>,
|
||||||
|
code_mode_tool_names: BTreeMap<String, ToolName>,
|
||||||
|
tool_mode: ToolMode,
|
||||||
|
requires_code_mode_worker: bool,
|
||||||
|
has_terminal_controls: bool,
|
||||||
|
can_manage_children: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolPlanProbe {
|
impl ToolPlanProbe {
|
||||||
@@ -129,6 +138,12 @@ impl ToolPlanProbe {
|
|||||||
namespace_functions,
|
namespace_functions,
|
||||||
registered_names,
|
registered_names,
|
||||||
exposures,
|
exposures,
|
||||||
|
tool_namespaces_info: router.tool_namespaces_info().cloned(),
|
||||||
|
code_mode_tool_names: router.code_mode_tool_names().clone(),
|
||||||
|
tool_mode: router.tool_mode(),
|
||||||
|
requires_code_mode_worker: router.requires_code_mode_worker(),
|
||||||
|
has_terminal_controls: router.has_terminal_controls(),
|
||||||
|
can_manage_children: router.can_manage_children(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,29 +219,38 @@ async fn probe_with(
|
|||||||
) -> ToolPlanProbe {
|
) -> ToolPlanProbe {
|
||||||
let (_session, mut turn) = make_session_and_context().await;
|
let (_session, mut turn) = make_session_and_context().await;
|
||||||
configure_turn(&mut turn);
|
configure_turn(&mut turn);
|
||||||
let turn = Arc::new(turn);
|
ToolPlanProbe::from_router(plan_with_model(&turn, turn.model_info(), inputs))
|
||||||
let step_context = StepContext::for_test(Arc::clone(&turn));
|
}
|
||||||
|
|
||||||
|
fn plan_with_model(
|
||||||
|
turn: &TurnContext,
|
||||||
|
model_info: &ModelInfo,
|
||||||
|
inputs: ToolPlanInputs,
|
||||||
|
) -> ToolRouter {
|
||||||
|
let mcp = codex_mcp::McpBinding::empty(mcp_config_for_test(&turn.config));
|
||||||
let mut registry = build_core_tool_registry(
|
let mut registry = build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
turn,
|
||||||
&step_context.environments,
|
model_info,
|
||||||
step_context.mcp.as_ref(),
|
&turn.environments,
|
||||||
|
&mcp,
|
||||||
inputs.tool_suggest_candidates.as_ref(),
|
inputs.tool_suggest_candidates.as_ref(),
|
||||||
inputs.wait_for_environment_tool_config.as_ref(),
|
inputs.wait_for_environment_tool_config.as_ref(),
|
||||||
);
|
);
|
||||||
let hosted_specs = append_source_tools(
|
let hosted_specs = append_source_tools(
|
||||||
step_context.turn.as_ref(),
|
turn,
|
||||||
|
model_info,
|
||||||
&mut registry,
|
&mut registry,
|
||||||
inputs.tool_runtimes,
|
inputs.tool_runtimes,
|
||||||
inputs.extension_tool_executors,
|
inputs.extension_tool_executors,
|
||||||
&inputs.dynamic_tools,
|
&inputs.dynamic_tools,
|
||||||
);
|
);
|
||||||
let router = ToolRouter::from_registry(
|
ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
turn,
|
||||||
|
model_info,
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
);
|
)
|
||||||
ToolPlanProbe::from_router(router)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn probe(configure_turn: impl FnOnce(&mut TurnContext)) -> ToolPlanProbe {
|
async fn probe(configure_turn: impl FnOnce(&mut TurnContext)) -> ToolPlanProbe {
|
||||||
@@ -497,6 +521,7 @@ async fn internal_guardian_sessions_exclude_optional_core_tools() {
|
|||||||
let router = super::build_tool_router(
|
let router = super::build_tool_router(
|
||||||
&session,
|
&session,
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
&step_context.mcp,
|
&step_context.mcp,
|
||||||
/*apps_enabled*/ false,
|
/*apps_enabled*/ false,
|
||||||
@@ -548,6 +573,7 @@ async fn internal_guardian_sessions_respect_managed_shell_restrictions() {
|
|||||||
let router = super::build_tool_router(
|
let router = super::build_tool_router(
|
||||||
&session,
|
&session,
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
&step_context.mcp,
|
&step_context.mcp,
|
||||||
/*apps_enabled*/ false,
|
/*apps_enabled*/ false,
|
||||||
@@ -583,6 +609,7 @@ async fn internal_guardian_sessions_preserve_code_mode() {
|
|||||||
let router = super::build_tool_router(
|
let router = super::build_tool_router(
|
||||||
&session,
|
&session,
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
&step_context.mcp,
|
&step_context.mcp,
|
||||||
/*apps_enabled*/ false,
|
/*apps_enabled*/ false,
|
||||||
@@ -652,6 +679,7 @@ async fn internal_guardian_sessions_require_managed_secondary_environments() {
|
|||||||
let router = super::build_tool_router(
|
let router = super::build_tool_router(
|
||||||
&session,
|
&session,
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
&step_context.mcp,
|
&step_context.mcp,
|
||||||
/*apps_enabled*/ false,
|
/*apps_enabled*/ false,
|
||||||
@@ -867,6 +895,7 @@ async fn shell_family_registers_only_unified_exec_tools() {
|
|||||||
|
|
||||||
plan.assert_visible_contains(&["exec_command", "write_stdin"]);
|
plan.assert_visible_contains(&["exec_command", "write_stdin"]);
|
||||||
plan.assert_registered_contains(&["exec_command", "write_stdin"]);
|
plan.assert_registered_contains(&["exec_command", "write_stdin"]);
|
||||||
|
assert!(plan.has_terminal_controls);
|
||||||
assert!(has_parameter(plan.visible_spec("exec_command"), "shell"));
|
assert!(has_parameter(plan.visible_spec("exec_command"), "shell"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -907,6 +936,7 @@ async fn login_shell_parameter_follows_selected_environment() {
|
|||||||
has_parameter(plan.visible_spec("exec_command"), "login"),
|
has_parameter(plan.visible_spec("exec_command"), "login"),
|
||||||
allow_login_shell
|
allow_login_shell
|
||||||
);
|
);
|
||||||
|
assert!(plan.has_terminal_controls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -959,6 +989,7 @@ async fn disabling_shell_tools_disables_command_tools_for_all_environments() {
|
|||||||
.await;
|
.await;
|
||||||
remote_environment.assert_visible_lacks(&["exec_command", "write_stdin"]);
|
remote_environment.assert_visible_lacks(&["exec_command", "write_stdin"]);
|
||||||
remote_environment.assert_registered_lacks(&["exec_command", "write_stdin"]);
|
remote_environment.assert_registered_lacks(&["exec_command", "write_stdin"]);
|
||||||
|
assert!(!remote_environment.has_terminal_controls);
|
||||||
|
|
||||||
let multiple_local_environments = probe(|turn| {
|
let multiple_local_environments = probe(|turn| {
|
||||||
set_feature(turn, Feature::ShellTool, /*enabled*/ false);
|
set_feature(turn, Feature::ShellTool, /*enabled*/ false);
|
||||||
@@ -1159,6 +1190,7 @@ async fn environment_count_controls_environment_backed_tools() {
|
|||||||
"view_image",
|
"view_image",
|
||||||
"request_permissions",
|
"request_permissions",
|
||||||
]);
|
]);
|
||||||
|
assert!(!no_environment.has_terminal_controls);
|
||||||
|
|
||||||
let multiple_environments = probe(|turn| {
|
let multiple_environments = probe(|turn| {
|
||||||
duplicate_primary_environment(turn);
|
duplicate_primary_environment(turn);
|
||||||
@@ -1176,6 +1208,7 @@ async fn environment_count_controls_environment_backed_tools() {
|
|||||||
"view_image",
|
"view_image",
|
||||||
"request_permissions",
|
"request_permissions",
|
||||||
]);
|
]);
|
||||||
|
assert!(multiple_environments.has_terminal_controls);
|
||||||
assert!(has_parameter(
|
assert!(has_parameter(
|
||||||
multiple_environments.visible_spec("exec_command"),
|
multiple_environments.visible_spec("exec_command"),
|
||||||
"environment_id"
|
"environment_id"
|
||||||
@@ -1206,14 +1239,16 @@ async fn environment_tools_follow_the_step_context() {
|
|||||||
|
|
||||||
let plan = ToolPlanProbe::from_router(ToolRouter::from_registry(
|
let plan = ToolPlanProbe::from_router(ToolRouter::from_registry(
|
||||||
turn.as_ref(),
|
turn.as_ref(),
|
||||||
|
turn.model_info(),
|
||||||
build_core_tool_registry(
|
build_core_tool_registry(
|
||||||
turn.as_ref(),
|
turn.as_ref(),
|
||||||
|
turn.model_info(),
|
||||||
&environments,
|
&environments,
|
||||||
mcp.as_ref(),
|
mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
/*wait_for_environment_tool_config*/ None,
|
/*wait_for_environment_tool_config*/ None,
|
||||||
),
|
),
|
||||||
super::hosted_model_tool_specs(turn.as_ref(), &[]),
|
super::hosted_model_tool_specs(turn.as_ref(), turn.model_info(), &[]),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -1424,9 +1459,8 @@ async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn tool_namespaces_info_is_opt_in_and_tracks_mcp_exposure() {
|
async fn tool_namespaces_info_is_opt_in_and_tracks_mcp_exposure() {
|
||||||
for enabled in [false, true] {
|
for (enabled, use_responses_lite) in [(false, true), (true, false), (true, true)] {
|
||||||
let mut metadata_state = None;
|
let plan = probe_with(
|
||||||
probe_with(
|
|
||||||
|turn| {
|
|turn| {
|
||||||
update_config(turn, |config| {
|
update_config(turn, |config| {
|
||||||
config.tool_registry.turn_metadata_includes_tool_info = enabled;
|
config.tool_registry.turn_metadata_includes_tool_info = enabled;
|
||||||
@@ -1434,9 +1468,8 @@ async fn tool_namespaces_info_is_opt_in_and_tracks_mcp_exposure() {
|
|||||||
set_feature(turn, Feature::CodeMode, /*enabled*/ true);
|
set_feature(turn, Feature::CodeMode, /*enabled*/ true);
|
||||||
update_turn_settings_for_test(turn, |settings| {
|
update_turn_settings_for_test(turn, |settings| {
|
||||||
Arc::make_mut(&mut settings.model_info).supports_search_tool = true;
|
Arc::make_mut(&mut settings.model_info).supports_search_tool = true;
|
||||||
Arc::make_mut(&mut settings.model_info).use_responses_lite = true;
|
Arc::make_mut(&mut settings.model_info).use_responses_lite = use_responses_lite;
|
||||||
});
|
});
|
||||||
metadata_state = Some(Arc::clone(&turn.turn_metadata_state));
|
|
||||||
},
|
},
|
||||||
ToolPlanInputs {
|
ToolPlanInputs {
|
||||||
tool_runtimes: vec![
|
tool_runtimes: vec![
|
||||||
@@ -1453,18 +1486,14 @@ async fn tool_namespaces_info_is_opt_in_and_tracks_mcp_exposure() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let metadata = metadata_state
|
let Some(namespaces) = plan.tool_namespaces_info else {
|
||||||
.expect("tool planning should capture the turn metadata")
|
assert!(
|
||||||
.to_responses_metadata(
|
!enabled || !use_responses_lite,
|
||||||
"installation".to_string(),
|
"opted-in Responses Lite planning should return namespaces"
|
||||||
"window".to_string(),
|
|
||||||
CodexResponsesRequestKind::Turn,
|
|
||||||
);
|
);
|
||||||
let Some(namespaces) = metadata.tool_namespaces_info else {
|
|
||||||
assert!(!enabled, "opted-in tool planning should publish namespaces");
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
assert!(enabled, "tool namespaces require opt-in");
|
assert!(enabled && use_responses_lite);
|
||||||
|
|
||||||
let namespace = namespaces
|
let namespace = namespaces
|
||||||
.get("mcp__registry")
|
.get("mcp__registry")
|
||||||
@@ -1497,6 +1526,80 @@ async fn tool_namespaces_info_is_opt_in_and_tracks_mcp_exposure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn candidate_model_plan_leaves_selected_model_and_inventory_unchanged() {
|
||||||
|
let (_session, mut turn) = make_session_and_context().await;
|
||||||
|
set_features(&mut turn, &[Feature::ShellTool, Feature::UnifiedExec]);
|
||||||
|
update_config(&mut turn, |config| {
|
||||||
|
config.tool_registry.turn_metadata_includes_tool_info = true;
|
||||||
|
});
|
||||||
|
update_turn_settings_for_test(&mut turn, |settings| {
|
||||||
|
let model = Arc::make_mut(&mut settings.model_info);
|
||||||
|
model.tool_mode = Some(ToolMode::Direct);
|
||||||
|
model.use_responses_lite = true;
|
||||||
|
model.shell_type = ConfigShellToolType::Disabled;
|
||||||
|
model.apply_patch_tool_type = None;
|
||||||
|
});
|
||||||
|
let selected_model = Arc::clone(turn.model_info());
|
||||||
|
let selected = ToolPlanProbe::from_router(plan_with_model(
|
||||||
|
&turn,
|
||||||
|
turn.model_info(),
|
||||||
|
ToolPlanInputs::default(),
|
||||||
|
));
|
||||||
|
let selected_inventory = selected
|
||||||
|
.tool_namespaces_info
|
||||||
|
.clone()
|
||||||
|
.expect("selected plan inventory");
|
||||||
|
turn.turn_metadata_state
|
||||||
|
.set_tool_namespaces_info(selected_inventory.clone());
|
||||||
|
|
||||||
|
let mut candidate_model = selected_model.as_ref().clone();
|
||||||
|
candidate_model.tool_mode = Some(ToolMode::CodeModeOnly);
|
||||||
|
candidate_model.shell_type = ConfigShellToolType::UnifiedExec;
|
||||||
|
candidate_model.apply_patch_tool_type = Some(ApplyPatchToolType::Freeform);
|
||||||
|
let candidate = ToolPlanProbe::from_router(plan_with_model(
|
||||||
|
&turn,
|
||||||
|
&candidate_model,
|
||||||
|
ToolPlanInputs::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
candidate.assert_visible_contains(&["exec", "wait"]);
|
||||||
|
candidate.assert_visible_lacks(&["exec_command", "write_stdin", "apply_patch"]);
|
||||||
|
candidate.assert_registered_contains(&["exec_command", "write_stdin", "apply_patch"]);
|
||||||
|
assert_eq!(candidate.tool_mode, ToolMode::CodeModeOnly);
|
||||||
|
assert!(candidate.requires_code_mode_worker);
|
||||||
|
assert!(candidate.has_terminal_controls);
|
||||||
|
assert_eq!(
|
||||||
|
candidate
|
||||||
|
.tool_namespaces_info
|
||||||
|
.as_ref()
|
||||||
|
.expect("candidate inventory")["functions"]
|
||||||
|
.functions["apply_patch"],
|
||||||
|
TurnToolFunctionInfo {
|
||||||
|
name: "apply_patch".to_string(),
|
||||||
|
direct: false,
|
||||||
|
code_mode_name: Some("apply_patch".to_string()),
|
||||||
|
deferred: false,
|
||||||
|
source: TurnToolSource::Harness,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let metadata = turn.turn_metadata_state.to_responses_metadata(
|
||||||
|
"installation".to_string(),
|
||||||
|
"window".to_string(),
|
||||||
|
CodexResponsesRequestKind::Turn,
|
||||||
|
);
|
||||||
|
assert_eq!(metadata.tool_namespaces_info, Some(selected_inventory));
|
||||||
|
assert_eq!(turn.model_info(), &selected_model);
|
||||||
|
assert_eq!(
|
||||||
|
ToolPlanProbe::from_router(plan_with_model(
|
||||||
|
&turn,
|
||||||
|
turn.model_info(),
|
||||||
|
ToolPlanInputs::default(),
|
||||||
|
)),
|
||||||
|
selected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn strict_namespace_ownership_requires_tool_namespace_inventory_opt_in() {
|
async fn strict_namespace_ownership_requires_tool_namespace_inventory_opt_in() {
|
||||||
for (enabled, second_exposure) in [
|
for (enabled, second_exposure) in [
|
||||||
@@ -1515,6 +1618,7 @@ async fn strict_namespace_ownership_requires_tool_namespace_inventory_opt_in() {
|
|||||||
let step_context = StepContext::for_test(Arc::new(turn));
|
let step_context = StepContext::for_test(Arc::new(turn));
|
||||||
let mut registry = build_core_tool_registry(
|
let mut registry = build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
step_context.mcp.as_ref(),
|
step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -1536,6 +1640,7 @@ async fn strict_namespace_ownership_requires_tool_namespace_inventory_opt_in() {
|
|||||||
.collect();
|
.collect();
|
||||||
let hosted_specs = append_source_tools(
|
let hosted_specs = append_source_tools(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
runtimes,
|
runtimes,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -1543,6 +1648,7 @@ async fn strict_namespace_ownership_requires_tool_namespace_inventory_opt_in() {
|
|||||||
);
|
);
|
||||||
let result = super::finalize_tool_router(
|
let result = super::finalize_tool_router(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
@@ -1772,6 +1878,7 @@ async fn strict_tool_collisions_reject_external_and_synthetic_duplicates() {
|
|||||||
let step_context = StepContext::for_test(Arc::clone(&turn));
|
let step_context = StepContext::for_test(Arc::clone(&turn));
|
||||||
let mut registry = build_core_tool_registry(
|
let mut registry = build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
step_context.mcp.as_ref(),
|
step_context.mcp.as_ref(),
|
||||||
inputs.tool_suggest_candidates.as_ref(),
|
inputs.tool_suggest_candidates.as_ref(),
|
||||||
@@ -1779,6 +1886,7 @@ async fn strict_tool_collisions_reject_external_and_synthetic_duplicates() {
|
|||||||
);
|
);
|
||||||
let hosted_specs = append_source_tools(
|
let hosted_specs = append_source_tools(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&mut registry,
|
&mut registry,
|
||||||
inputs.tool_runtimes,
|
inputs.tool_runtimes,
|
||||||
inputs.extension_tool_executors,
|
inputs.extension_tool_executors,
|
||||||
@@ -1787,6 +1895,7 @@ async fn strict_tool_collisions_reject_external_and_synthetic_duplicates() {
|
|||||||
|
|
||||||
let error = super::finalize_tool_router(
|
let error = super::finalize_tool_router(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
hosted_specs,
|
hosted_specs,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
@@ -1915,7 +2024,6 @@ async fn code_mode_uses_the_first_normalized_tool_identity() {
|
|||||||
(true, ToolExposure::Direct, true),
|
(true, ToolExposure::Direct, true),
|
||||||
(true, ToolExposure::Deferred, false),
|
(true, ToolExposure::Deferred, false),
|
||||||
] {
|
] {
|
||||||
let mut metadata_state = None;
|
|
||||||
let plan = probe_with(
|
let plan = probe_with(
|
||||||
|turn| {
|
|turn| {
|
||||||
update_config(turn, |config| {
|
update_config(turn, |config| {
|
||||||
@@ -1929,7 +2037,6 @@ async fn code_mode_uses_the_first_normalized_tool_identity() {
|
|||||||
Arc::make_mut(&mut settings.model_info).supports_search_tool = true;
|
Arc::make_mut(&mut settings.model_info).supports_search_tool = true;
|
||||||
Arc::make_mut(&mut settings.model_info).use_responses_lite = true;
|
Arc::make_mut(&mut settings.model_info).use_responses_lite = true;
|
||||||
});
|
});
|
||||||
metadata_state = Some(Arc::clone(&turn.turn_metadata_state));
|
|
||||||
},
|
},
|
||||||
ToolPlanInputs {
|
ToolPlanInputs {
|
||||||
tool_runtimes: vec![mcp_runtime(
|
tool_runtimes: vec![mcp_runtime(
|
||||||
@@ -1961,17 +2068,14 @@ async fn code_mode_uses_the_first_normalized_tool_identity() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let metadata = metadata_state
|
let tool_namespaces = plan
|
||||||
.expect("tool planning should capture the turn metadata")
|
|
||||||
.to_responses_metadata(
|
|
||||||
"installation".to_string(),
|
|
||||||
"window".to_string(),
|
|
||||||
CodexResponsesRequestKind::Turn,
|
|
||||||
);
|
|
||||||
let tool_namespaces = metadata
|
|
||||||
.tool_namespaces_info
|
.tool_namespaces_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("opted-in Responses Lite should receive the supported tools");
|
.expect("opted-in Responses Lite plan should contain the supported tools");
|
||||||
|
assert_eq!(
|
||||||
|
plan.code_mode_tool_names.get("normalized_alias__lookup"),
|
||||||
|
Some(&winner_name),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tool_namespaces["normalized-alias"].functions["lookup"]
|
tool_namespaces["normalized-alias"].functions["lookup"]
|
||||||
.code_mode_name
|
.code_mode_name
|
||||||
@@ -2040,6 +2144,7 @@ async fn tool_search_cache_rebuilds_when_deferred_sources_change() {
|
|||||||
let first_step_context = StepContext::for_test(Arc::clone(&first_turn));
|
let first_step_context = StepContext::for_test(Arc::clone(&first_turn));
|
||||||
let mut first_registry = build_core_tool_registry(
|
let mut first_registry = build_core_tool_registry(
|
||||||
first_step_context.turn.as_ref(),
|
first_step_context.turn.as_ref(),
|
||||||
|
first_step_context.turn.model_info(),
|
||||||
&first_step_context.environments,
|
&first_step_context.environments,
|
||||||
first_step_context.mcp.as_ref(),
|
first_step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -2049,8 +2154,13 @@ async fn tool_search_cache_rebuilds_when_deferred_sources_change() {
|
|||||||
first_registry.register_external_with_exposure(first_tool.runtime, first_tool.exposure);
|
first_registry.register_external_with_exposure(first_tool.runtime, first_tool.exposure);
|
||||||
let first_router = ToolRouter::from_registry(
|
let first_router = ToolRouter::from_registry(
|
||||||
first_step_context.turn.as_ref(),
|
first_step_context.turn.as_ref(),
|
||||||
|
first_step_context.turn.model_info(),
|
||||||
first_registry,
|
first_registry,
|
||||||
super::hosted_model_tool_specs(first_step_context.turn.as_ref(), &[]),
|
super::hosted_model_tool_specs(
|
||||||
|
first_step_context.turn.as_ref(),
|
||||||
|
first_step_context.turn.model_info(),
|
||||||
|
&[],
|
||||||
|
),
|
||||||
&cache,
|
&cache,
|
||||||
);
|
);
|
||||||
let first_plan = ToolPlanProbe::from_router(first_router);
|
let first_plan = ToolPlanProbe::from_router(first_router);
|
||||||
@@ -2063,6 +2173,7 @@ async fn tool_search_cache_rebuilds_when_deferred_sources_change() {
|
|||||||
let second_step_context = StepContext::for_test(Arc::clone(&second_turn));
|
let second_step_context = StepContext::for_test(Arc::clone(&second_turn));
|
||||||
let mut second_registry = build_core_tool_registry(
|
let mut second_registry = build_core_tool_registry(
|
||||||
second_step_context.turn.as_ref(),
|
second_step_context.turn.as_ref(),
|
||||||
|
second_step_context.turn.model_info(),
|
||||||
&second_step_context.environments,
|
&second_step_context.environments,
|
||||||
second_step_context.mcp.as_ref(),
|
second_step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -2072,8 +2183,13 @@ async fn tool_search_cache_rebuilds_when_deferred_sources_change() {
|
|||||||
second_registry.register_external_with_exposure(second_tool.runtime, second_tool.exposure);
|
second_registry.register_external_with_exposure(second_tool.runtime, second_tool.exposure);
|
||||||
let second_router = ToolRouter::from_registry(
|
let second_router = ToolRouter::from_registry(
|
||||||
second_step_context.turn.as_ref(),
|
second_step_context.turn.as_ref(),
|
||||||
|
second_step_context.turn.model_info(),
|
||||||
second_registry,
|
second_registry,
|
||||||
super::hosted_model_tool_specs(second_step_context.turn.as_ref(), &[]),
|
super::hosted_model_tool_specs(
|
||||||
|
second_step_context.turn.as_ref(),
|
||||||
|
second_step_context.turn.model_info(),
|
||||||
|
&[],
|
||||||
|
),
|
||||||
&cache,
|
&cache,
|
||||||
);
|
);
|
||||||
let second_plan = ToolPlanProbe::from_router(second_router);
|
let second_plan = ToolPlanProbe::from_router(second_router);
|
||||||
@@ -2117,6 +2233,7 @@ async fn tool_search_cache_rebuilds_when_deferred_world_state_changes() {
|
|||||||
let step_context = StepContext::for_test(Arc::clone(&turn));
|
let step_context = StepContext::for_test(Arc::clone(&turn));
|
||||||
let mut registry = build_core_tool_registry(
|
let mut registry = build_core_tool_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
&step_context.environments,
|
&step_context.environments,
|
||||||
step_context.mcp.as_ref(),
|
step_context.mcp.as_ref(),
|
||||||
/*tool_suggest_candidates*/ None,
|
/*tool_suggest_candidates*/ None,
|
||||||
@@ -2131,8 +2248,13 @@ async fn tool_search_cache_rebuilds_when_deferred_world_state_changes() {
|
|||||||
registry.register_external_with_exposure(tool.runtime, tool.exposure);
|
registry.register_external_with_exposure(tool.runtime, tool.exposure);
|
||||||
let router = ToolRouter::from_registry(
|
let router = ToolRouter::from_registry(
|
||||||
step_context.turn.as_ref(),
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
registry,
|
registry,
|
||||||
super::hosted_model_tool_specs(step_context.turn.as_ref(), &[]),
|
super::hosted_model_tool_specs(
|
||||||
|
step_context.turn.as_ref(),
|
||||||
|
step_context.turn.model_info(),
|
||||||
|
&[],
|
||||||
|
),
|
||||||
&cache,
|
&cache,
|
||||||
);
|
);
|
||||||
let plan = ToolPlanProbe::from_router(router);
|
let plan = ToolPlanProbe::from_router(router);
|
||||||
@@ -2303,6 +2425,10 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() {
|
|||||||
codex_code_mode::PUBLIC_TOOL_NAME,
|
codex_code_mode::PUBLIC_TOOL_NAME,
|
||||||
codex_code_mode::WAIT_TOOL_NAME,
|
codex_code_mode::WAIT_TOOL_NAME,
|
||||||
]);
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
(plain.tool_mode, plain.requires_code_mode_worker),
|
||||||
|
(ToolMode::Direct, false)
|
||||||
|
);
|
||||||
|
|
||||||
let code_mode_only = probe_with(
|
let code_mode_only = probe_with(
|
||||||
|turn| {
|
|turn| {
|
||||||
@@ -2322,6 +2448,13 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() {
|
|||||||
codex_code_mode::PUBLIC_TOOL_NAME,
|
codex_code_mode::PUBLIC_TOOL_NAME,
|
||||||
codex_code_mode::WAIT_TOOL_NAME,
|
codex_code_mode::WAIT_TOOL_NAME,
|
||||||
]);
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
code_mode_only.tool_mode,
|
||||||
|
code_mode_only.requires_code_mode_worker
|
||||||
|
),
|
||||||
|
(ToolMode::CodeModeOnly, true),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
code_mode_only.namespace_function_names("codex_app"),
|
code_mode_only.namespace_function_names("codex_app"),
|
||||||
Vec::<String>::new().as_slice()
|
Vec::<String>::new().as_slice()
|
||||||
@@ -2484,6 +2617,7 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
v1.assert_visible_contains(&[MULTI_AGENT_V1_NAMESPACE]);
|
v1.assert_visible_contains(&[MULTI_AGENT_V1_NAMESPACE]);
|
||||||
|
assert!(v1.can_manage_children);
|
||||||
v1.assert_visible_lacks(&[
|
v1.assert_visible_lacks(&[
|
||||||
"spawn_agent",
|
"spawn_agent",
|
||||||
"send_input",
|
"send_input",
|
||||||
@@ -2540,6 +2674,7 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
v2.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
|
v2.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
|
||||||
|
assert!(v2.can_manage_children);
|
||||||
v2.assert_visible_lacks(&[
|
v2.assert_visible_lacks(&[
|
||||||
"spawn_agent",
|
"spawn_agent",
|
||||||
"send_message",
|
"send_message",
|
||||||
@@ -2674,6 +2809,7 @@ async fn multi_agent_v2_can_disable_wait_agent() {
|
|||||||
);
|
);
|
||||||
plan.assert_visible_lacks(&["clock"]);
|
plan.assert_visible_lacks(&["clock"]);
|
||||||
plan.assert_registered_lacks(&["collaboration.wait_agent", "clock.sleep"]);
|
plan.assert_registered_lacks(&["collaboration.wait_agent", "clock.sleep"]);
|
||||||
|
assert!(plan.can_manage_children);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -2856,6 +2992,7 @@ async fn multi_agent_v2_bedrock_workers_only_delegate_when_model_supports_v2() {
|
|||||||
|
|
||||||
let spawn_agent_name = ToolName::namespaced("agents", "spawn_agent").to_string();
|
let spawn_agent_name = ToolName::namespaced("agents", "spawn_agent").to_string();
|
||||||
let followup_task_name = ToolName::namespaced("agents", "followup_task").to_string();
|
let followup_task_name = ToolName::namespaced("agents", "followup_task").to_string();
|
||||||
|
assert_eq!(plan.can_manage_children, supports_delegation);
|
||||||
if supports_delegation {
|
if supports_delegation {
|
||||||
plan.assert_visible_contains(&["agents"]);
|
plan.assert_visible_contains(&["agents"]);
|
||||||
plan.assert_registered_contains(&[&spawn_agent_name, &followup_task_name]);
|
plan.assert_registered_contains(&[&spawn_agent_name, &followup_task_name]);
|
||||||
|
|||||||
@@ -2666,9 +2666,11 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
|||||||
let mut previous_model_info =
|
let mut previous_model_info =
|
||||||
model_info_with_context_window("gpt-5.4", /*context_window*/ 273_000);
|
model_info_with_context_window("gpt-5.4", /*context_window*/ 273_000);
|
||||||
previous_model_info.slug = previous_model_family.to_string();
|
previous_model_info.slug = previous_model_family.to_string();
|
||||||
|
previous_model_info.use_responses_lite = true;
|
||||||
let mut next_model_info =
|
let mut next_model_info =
|
||||||
model_info_with_context_window("gpt-5.4", /*context_window*/ 125_000);
|
model_info_with_context_window("gpt-5.4", /*context_window*/ 125_000);
|
||||||
next_model_info.slug = next_model.to_string();
|
next_model_info.slug = next_model.to_string();
|
||||||
|
next_model_info.use_responses_lite = false;
|
||||||
|
|
||||||
let models_mock = mount_models_once(
|
let models_mock = mount_models_once(
|
||||||
&server,
|
&server,
|
||||||
@@ -2710,6 +2712,7 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
|||||||
.with_model(retired_model)
|
.with_model(retired_model)
|
||||||
.with_config(move |config| {
|
.with_config(move |config| {
|
||||||
config.model_provider = model_provider;
|
config.model_provider = model_provider;
|
||||||
|
config.tool_registry.turn_metadata_includes_tool_info = true;
|
||||||
set_test_compact_prompt(config);
|
set_test_compact_prompt(config);
|
||||||
let _ = config.features.enable(Feature::RemoteCompactionV2);
|
let _ = config.features.enable(Feature::RemoteCompactionV2);
|
||||||
});
|
});
|
||||||
@@ -2755,6 +2758,25 @@ async fn pre_sampling_compact_falls_back_after_previous_model_invalid_request_on
|
|||||||
);
|
);
|
||||||
assert_eq!(requests[2].body_json()["model"].as_str(), Some(next_model));
|
assert_eq!(requests[2].body_json()["model"].as_str(), Some(next_model));
|
||||||
assert_eq!(requests[3].body_json()["model"].as_str(), Some(next_model));
|
assert_eq!(requests[3].body_json()["model"].as_str(), Some(next_model));
|
||||||
|
|
||||||
|
// Preparing the non-Lite fallback must not clear the previous model's inventory
|
||||||
|
// before its compaction attempt is sent.
|
||||||
|
let [first_metadata, compact_metadata] = [&requests[0], &requests[1]].map(|request| {
|
||||||
|
serde_json::from_str::<Value>(
|
||||||
|
request.body_json()["client_metadata"]["x-codex-turn-metadata"]
|
||||||
|
.as_str()
|
||||||
|
.expect("request should include turn metadata"),
|
||||||
|
)
|
||||||
|
.expect("turn metadata should be valid JSON")
|
||||||
|
});
|
||||||
|
let inventory = first_metadata["tool_namespaces_info"]
|
||||||
|
.as_object()
|
||||||
|
.expect("Responses Lite request should include tool inventory");
|
||||||
|
assert!(!inventory.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
compact_metadata["tool_namespaces_info"],
|
||||||
|
first_metadata["tool_namespaces_info"],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
|||||||
Reference in new issue
Block a user