Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion codex-rs/app-server/tests/suite/v2/turn_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const TEST_ORIGINATOR: &str = "codex_vscode";
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
const TINY_PNG_BYTES: &[u8] = &[
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0,
Expand Down Expand Up @@ -3650,7 +3651,12 @@ async fn direct_input_to_multi_agent_v2_subagent_is_rejected() -> Result<()> {
|req: &wiremock::Request| body_contains(req, PARENT_PROMPT),
responses::sse(vec![
responses::ev_response_created("resp-parent-1"),
responses::ev_function_call(SPAWN_CALL_ID, "spawn_agent", &spawn_args),
responses::ev_function_call_with_namespace(
SPAWN_CALL_ID,
MULTI_AGENT_V2_NAMESPACE,
"spawn_agent",
&spawn_args,
),
responses::ev_completed("resp-parent-1"),
]),
)
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ Payload:
```
You may also see them addressed as to=/root/..., which indicates your identity is /root/...
"#;
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.spawn_agent` without a configured namespace or `to=functions.agents.spawn_agent` with `tool_namespace = "agents"`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.
const DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE: &str = "collaboration";
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.collaboration.spawn_agent`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.
Comment thread
jif-oai marked this conversation as resolved.

All agents share the same directory. In detail:
- All agents have access to the same container and filesystem as you.
Expand Down Expand Up @@ -1157,7 +1158,7 @@ impl MultiAgentV2Config {
DEFAULT_MULTI_AGENT_V2_SUBAGENT_USAGE_HINT_TEXT,
max_concurrent_threads_per_session,
)),
tool_namespace: None,
tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()),
hide_spawn_agent_metadata: true,
non_code_mode_only: true,
}
Expand Down
66 changes: 51 additions & 15 deletions codex-rs/core/src/tools/spec_plan_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ use crate::tools::router::ToolRouterParams;
use crate::tools::router::ToolSuggestCandidates;
use crate::tools::router::ToolSuggestPresentation;

const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";

#[derive(Default)]
struct ToolPlanInputs {
mcp_tools: Option<Vec<ToolInfo>>,
Expand Down Expand Up @@ -1151,19 +1153,48 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
});
})
.await;
v2.assert_visible_contains(&[
v2.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
v2.assert_visible_lacks(&[
"spawn_agent",
"send_message",
"followup_task",
"wait_agent",
"interrupt_agent",
"list_agents",
"send_input",
"resume_agent",
"assign_task",
"close_agent",
]);
v2.assert_visible_lacks(&["send_input", "resume_agent", "assign_task", "close_agent"]);
let spawn_agent_description = match v2.visible_spec("spawn_agent") {
ToolSpec::Function(tool) => tool.description.as_str(),
other => panic!("expected spawn_agent function spec, got {other:?}"),
for tool_name in [
"spawn_agent",
"send_message",
"followup_task",
"wait_agent",
"interrupt_agent",
"list_agents",
] {
assert!(
v2.namespace_function_names(MULTI_AGENT_V2_NAMESPACE)
.iter()
.any(|name| name == tool_name),
"expected {tool_name} in {MULTI_AGENT_V2_NAMESPACE} namespace"
);
}
let ToolSpec::Namespace(namespace) = v2.visible_spec(MULTI_AGENT_V2_NAMESPACE) else {
panic!("expected {MULTI_AGENT_V2_NAMESPACE} namespace");
};
let Some(ResponsesApiNamespaceTool::Function(spawn_agent)) =
namespace.tools.iter().find(|tool| {
matches!(
tool,
ResponsesApiNamespaceTool::Function(tool) if tool.name == "spawn_agent"
)
})
else {
panic!("expected spawn_agent in {MULTI_AGENT_V2_NAMESPACE} namespace");
};
let spawn_agent_description = spawn_agent.description.as_str();
assert!(!spawn_agent_description.contains("max_concurrent_threads_per_session"));
assert!(spawn_agent_description.contains(
"Note that passing `fork_turns=\"none\"` will not pass any surrounding context to the spawned subagent"
Expand All @@ -1183,9 +1214,11 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
});
})
.await;
direct_model_only.assert_visible_contains(&["spawn_agent", "send_message", "wait_agent"]);
direct_model_only.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
direct_model_only.assert_visible_lacks(&["spawn_agent", "send_message", "wait_agent"]);
assert_eq!(
direct_model_only.exposure("spawn_agent"),
direct_model_only
.exposure(&ToolName::namespaced(MULTI_AGENT_V2_NAMESPACE, "spawn_agent").to_string()),
ToolExposure::DirectModelOnly
);
}
Expand All @@ -1196,9 +1229,17 @@ async fn multi_agent_v2_message_schemas_are_encrypted() {
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true);
})
.await;
let ToolSpec::Namespace(namespace) = plan.visible_spec(MULTI_AGENT_V2_NAMESPACE) else {
panic!("expected {MULTI_AGENT_V2_NAMESPACE} namespace");
};
for tool_name in ["spawn_agent", "send_message", "followup_task"] {
let ToolSpec::Function(tool) = plan.visible_spec(tool_name) else {
panic!("expected {tool_name} function spec");
let Some(ResponsesApiNamespaceTool::Function(tool)) = namespace.tools.iter().find(|tool| {
matches!(
tool,
ResponsesApiNamespaceTool::Function(tool) if tool.name == tool_name
)
}) else {
panic!("expected {tool_name} in {MULTI_AGENT_V2_NAMESPACE} namespace");
};
let properties = tool
.parameters
Expand Down Expand Up @@ -1464,12 +1505,7 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() {
codex_code_mode::WAIT_TOOL_NAME,
"request_user_input",
// Multi-agent v2 tools.
"spawn_agent",
"send_message",
"followup_task",
"wait_agent",
"interrupt_agent",
"list_agents",
MULTI_AGENT_V2_NAMESPACE,
// Hosted Responses tools.
"web_search",
"image_generation",
Expand Down
17 changes: 14 additions & 3 deletions codex-rs/core/tests/suite/agent_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Result;
use codex_features::Feature;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_function_call_with_namespace;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::sse;
Expand All @@ -15,6 +15,7 @@ use std::time::Duration;
const FIRST_PROMPT: &str = "spawn the first worker";
const FIRST_TASK: &str = "first worker task";
const SECOND_TASK: &str = "second worker task";
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";

fn body_contains(request: &wiremock::Request, text: &str) -> bool {
serde_json::from_slice::<serde_json::Value>(&request.body)
Expand Down Expand Up @@ -47,7 +48,12 @@ async fn v2_nested_spawn_checks_shared_active_execution_capacity() -> Result<()>
|request: &wiremock::Request| body_contains(request, FIRST_PROMPT),
sse(vec![
ev_response_created("first-response"),
ev_function_call("first-call", "spawn_agent", &first_args),
ev_function_call_with_namespace(
"first-call",
MULTI_AGENT_V2_NAMESPACE,
"spawn_agent",
&first_args,
),
ev_completed("first-response"),
]),
)
Expand All @@ -63,7 +69,12 @@ async fn v2_nested_spawn_checks_shared_active_execution_capacity() -> Result<()>
},
sse(vec![
ev_response_created("first-worker-response"),
ev_function_call("second-call", "spawn_agent", &second_args),
ev_function_call_with_namespace(
"second-call",
MULTI_AGENT_V2_NAMESPACE,
"spawn_agent",
&second_args,
),
ev_completed("first-worker-response"),
]),
)
Expand Down
12 changes: 9 additions & 3 deletions codex-rs/core/tests/suite/model_runtime_selectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use tokio::time::sleep;
const CHILD_MODEL: &str = "test-multi-agent-child";
const ROOT_MODEL: &str = "test-multi-agent-root";
const ROOT_PROMPT: &str = "spawn a child";
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
const UNSUPPORTED_CODE_MODE_WARNING: &str = "does not advertise Code Mode support";

struct RemoteModelResponse {
Expand Down Expand Up @@ -335,7 +336,7 @@ async fn remote_multi_agent_selector_overrides_feature_flags() -> Result<()> {
.expect("test config should allow feature update");
})
.await?;
assert!(tool_names(&v2_body).contains(&"send_message".to_string()));
assert!(tool_names(&v2_body).contains(&MULTI_AGENT_V2_NAMESPACE.to_string()));

let mut disabled_model = remote_model("test-multi-agent-disabled");
disabled_model.multi_agent_version = Some(MultiAgentVersion::Disabled);
Expand All @@ -349,7 +350,12 @@ async fn remote_multi_agent_selector_overrides_feature_flags() -> Result<()> {
let disabled_tools = tool_names(&disabled_body);
assert!(disabled_tools.iter().all(|name| !matches!(
name.as_str(),
"multi_agent_v1" | "spawn_agent" | "send_message" | "wait_agent" | "list_agents"
"multi_agent_v1"
| MULTI_AGENT_V2_NAMESPACE
| "spawn_agent"
| "send_message"
| "wait_agent"
| "list_agents"
)));

Ok(())
Expand Down Expand Up @@ -432,7 +438,7 @@ async fn remote_multi_agent_selector_uses_model_selected_before_first_turn() ->
.expect("expected response request")
.body_json(),
)
.contains(&"send_message".to_string()),
.contains(&MULTI_AGENT_V2_NAMESPACE.to_string()),
),
(1, Some(MultiAgentVersion::V2), true)
);
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/core/tests/suite/pending_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use core_test_support::responses;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_function_call_with_namespace;
use core_test_support::responses::ev_message_item_added;
use core_test_support::responses::ev_output_text_delta;
use core_test_support::responses::ev_reasoning_item;
Expand Down Expand Up @@ -290,11 +291,13 @@ async fn steer_interrupts_wait_agent_and_is_sent_in_follow_up_request() {
const WAIT_CALL_ID: &str = "wait-call";
const INITIAL_PROMPT: &str = "wait for an agent";
const STEER_PROMPT: &str = "stop waiting and continue";
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";

let first_chunks = vec![
chunk(ev_response_created("resp-1")),
chunk(ev_function_call(
chunk(ev_function_call_with_namespace(
WAIT_CALL_ID,
MULTI_AGENT_V2_NAMESPACE,
"wait_agent",
r#"{"timeout_ms":10000}"#,
)),
Expand Down
11 changes: 9 additions & 2 deletions codex-rs/core/tests/suite/rollout_budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use core_test_support::responses::ResponsesRequest;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_completed_with_tokens;
use core_test_support::responses::ev_function_call;
use core_test_support::responses::ev_function_call_with_namespace;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::mount_sse_sequence;
Expand All @@ -25,6 +25,8 @@ use std::time::Duration;
use test_case::test_case;
use tokio::time::timeout;

const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";

fn rollout_budget() -> RolloutBudgetConfig {
RolloutBudgetConfig {
limit_tokens: 100,
Expand Down Expand Up @@ -130,7 +132,12 @@ async fn subagent_usage_draws_from_the_shared_budget() -> Result<()> {
|request: &wiremock::Request| wire_request_contains(request, ROOT_PROMPT),
sse(vec![
ev_response_created("root-1"),
ev_function_call(SPAWN_CALL_ID, "spawn_agent", &spawn_args),
ev_function_call_with_namespace(
SPAWN_CALL_ID,
MULTI_AGENT_V2_NAMESPACE,
"spawn_agent",
&spawn_args,
),
ev_completed_with_tokens("root-1", /*total_tokens*/ 10),
]),
)
Expand Down
Loading
Loading