mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Namespace multi-agent v2 tools under collaboration (#29067)
## Summary Multi-agent v2 tools now use the fixed `collaboration` namespace when namespace tools are available. This keeps the model-visible hint and the actual tool surface aligned around `functions.collaboration.*`, without exposing an unshipped namespace knob to users. The PR also removes the old `features.multi_agent_v2.tool_namespace` config/schema surface, updates the MAv2 test fixtures for namespaced calls, and fixes stale `TurnContext.features` references that were breaking `codex-core` builds. ## Changes - Expose MAv2 tools under `collaboration` instead of relying on a configurable namespace. - Remove `tool_namespace` from MAv2 TOML config, resolved config, validation, schema, and tests. - Update tool-planning and integration fixtures to assert or emit namespaced MAv2 tool calls. - Read feature state through `TurnContext.config.features` in the multi-agent mode context paths. ## Testing - `just write-config-schema` - `just test -p codex-features`
This commit is contained in:
@@ -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,
|
||||
@@ -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"),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
All agents share the same directory. In detail:
|
||||
- All agents have access to the same container and filesystem as you.
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>>,
|
||||
@@ -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"
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
@@ -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"),
|
||||
]),
|
||||
)
|
||||
@@ -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"),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
@@ -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);
|
||||
@@ -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(())
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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}"#,
|
||||
)),
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -17,7 +17,6 @@ use core_test_support::hooks::trust_discovered_hooks;
|
||||
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_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::ev_tool_search_call;
|
||||
@@ -48,6 +47,7 @@ use wiremock::MockServer;
|
||||
|
||||
const SPAWN_CALL_ID: &str = "spawn-call-1";
|
||||
const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1";
|
||||
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
|
||||
const TURN_0_FORK_PROMPT: &str = "seed fork context";
|
||||
const TURN_1_PROMPT: &str = "spawn a child and continue";
|
||||
const TURN_2_NO_WAIT_PROMPT: &str = "follow up without wait";
|
||||
@@ -63,6 +63,23 @@ const SUBAGENT_STOP_CONTINUATION: &str = "continue only the child";
|
||||
const INTERNAL_SUBAGENT_PROMPT: &str = "internal subagent: review";
|
||||
|
||||
fn body_contains(req: &wiremock::Request, text: &str) -> bool {
|
||||
decoded_body(req)
|
||||
.and_then(|body| String::from_utf8(body).ok())
|
||||
.is_some_and(|body| body.contains(text))
|
||||
}
|
||||
|
||||
fn request_has_input_type(req: &wiremock::Request, ty: &str) -> bool {
|
||||
decoded_body(req)
|
||||
.and_then(|body| serde_json::from_slice::<Value>(&body).ok())
|
||||
.and_then(|body| body.get("input").and_then(Value::as_array).cloned())
|
||||
.is_some_and(|items| {
|
||||
items
|
||||
.iter()
|
||||
.any(|item| item.get("type").and_then(Value::as_str) == Some(ty))
|
||||
})
|
||||
}
|
||||
|
||||
fn decoded_body(req: &wiremock::Request) -> Option<Vec<u8>> {
|
||||
let is_zstd = req
|
||||
.headers
|
||||
.get("content-encoding")
|
||||
@@ -72,14 +89,11 @@ fn body_contains(req: &wiremock::Request, text: &str) -> bool {
|
||||
.split(',')
|
||||
.any(|entry| entry.trim().eq_ignore_ascii_case("zstd"))
|
||||
});
|
||||
let bytes = if is_zstd {
|
||||
if is_zstd {
|
||||
zstd::stream::decode_all(std::io::Cursor::new(&req.body)).ok()
|
||||
} else {
|
||||
Some(req.body.clone())
|
||||
};
|
||||
bytes
|
||||
.and_then(|body| String::from_utf8(body).ok())
|
||||
.is_some_and(|body| body.contains(text))
|
||||
}
|
||||
}
|
||||
|
||||
fn has_subagent_notification(req: &ResponsesRequest) -> bool {
|
||||
@@ -1040,14 +1054,19 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
||||
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
|
||||
sse(vec![
|
||||
ev_response_created("resp-parent-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("resp-parent-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let child_request_log = mount_sse_once_match(
|
||||
&server,
|
||||
|req: &wiremock::Request| body_contains(req, "\"type\":\"agent_message\""),
|
||||
|req: &wiremock::Request| request_has_input_type(req, "agent_message"),
|
||||
sse(vec![
|
||||
ev_response_created("resp-child-1"),
|
||||
ev_completed("resp-child-1"),
|
||||
@@ -1057,7 +1076,7 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
||||
mount_sse_once_match(
|
||||
&server,
|
||||
|req: &wiremock::Request| {
|
||||
body_contains(req, SPAWN_CALL_ID) && !body_contains(req, "\"type\":\"agent_message\"")
|
||||
body_contains(req, SPAWN_CALL_ID) && !request_has_input_type(req, "agent_message")
|
||||
},
|
||||
sse(vec![
|
||||
ev_response_created("resp-parent-2"),
|
||||
@@ -1129,7 +1148,12 @@ async fn plaintext_multi_agent_v2_completion_sends_agent_message(
|
||||
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
|
||||
sse(vec![
|
||||
ev_response_created("resp-parent-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("resp-parent-1"),
|
||||
]),
|
||||
)
|
||||
@@ -1144,7 +1168,7 @@ async fn plaintext_multi_agent_v2_completion_sends_agent_message(
|
||||
};
|
||||
let child_request = mount_response_once_match(
|
||||
&server,
|
||||
|req: &wiremock::Request| body_contains(req, "\"type\":\"agent_message\""),
|
||||
|req: &wiremock::Request| request_has_input_type(req, "agent_message"),
|
||||
sse_response(sse(child_events)).set_delay(Duration::from_secs(1)),
|
||||
)
|
||||
.await;
|
||||
@@ -1183,7 +1207,12 @@ async fn plaintext_multi_agent_v2_completion_sends_agent_message(
|
||||
},
|
||||
sse(vec![
|
||||
ev_response_created("resp-parent-3"),
|
||||
ev_function_call("wait-agent-call", "wait_agent", "{}"),
|
||||
ev_function_call_with_namespace(
|
||||
"wait-agent-call",
|
||||
MULTI_AGENT_V2_NAMESPACE,
|
||||
"wait_agent",
|
||||
"{}",
|
||||
),
|
||||
ev_completed("resp-parent-3"),
|
||||
]),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user