mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
feat: spawn v2 make task name as mandatory (#15986)
This commit is contained in:
@@ -321,6 +321,40 @@ async fn spawn_agent_returns_agent_id_without_task_name() {
|
||||
assert_eq!(success, Some(true));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multi_agent_v2_spawn_requires_task_name() {
|
||||
let (mut session, mut turn) = make_session_and_context().await;
|
||||
let manager = thread_manager();
|
||||
let root = manager
|
||||
.start_thread((*turn.config).clone())
|
||||
.await
|
||||
.expect("root thread should start");
|
||||
session.services.agent_control = manager.agent_control();
|
||||
session.conversation_id = root.thread_id;
|
||||
let mut config = (*turn.config).clone();
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
let invocation = invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
"spawn_agent",
|
||||
function_payload(json!({
|
||||
"message": "inspect this repo"
|
||||
})),
|
||||
);
|
||||
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
|
||||
panic!("missing task_name should be rejected");
|
||||
};
|
||||
let FunctionCallError::RespondToModel(message) = err else {
|
||||
panic!("missing task_name should surface as a model-facing error");
|
||||
};
|
||||
assert!(message.contains("missing field `task_name`"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_errors_when_manager_dropped() {
|
||||
let (session, turn) = make_session_and_context().await;
|
||||
|
||||
@@ -83,7 +83,7 @@ impl ToolHandler for Handler {
|
||||
&turn.session_source,
|
||||
child_depth,
|
||||
role_name,
|
||||
args.task_name.clone(),
|
||||
Some(args.task_name.clone()),
|
||||
)?),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
|
||||
@@ -132,7 +132,6 @@ impl ToolHandler for Handler {
|
||||
.and_then(|snapshot| snapshot.reasoning_effort)
|
||||
.unwrap_or(args.reasoning_effort.unwrap_or_default());
|
||||
let nickname = new_agent_nickname.clone();
|
||||
let task_name = new_agent_path.clone();
|
||||
session
|
||||
.send_event(
|
||||
&turn,
|
||||
@@ -150,16 +149,21 @@ impl ToolHandler for Handler {
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
let new_thread_id = result?.thread_id;
|
||||
let _ = result?;
|
||||
let role_tag = role_name.unwrap_or(DEFAULT_ROLE_NAME);
|
||||
turn.session_telemetry.counter(
|
||||
"codex.multi_agent.spawn",
|
||||
/*inc*/ 1,
|
||||
&[("role", role_tag)],
|
||||
);
|
||||
let task_name = new_agent_path.ok_or_else(|| {
|
||||
FunctionCallError::RespondToModel(
|
||||
"spawned agent is missing a canonical task name".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(SpawnAgentResult {
|
||||
agent_id: task_name.is_none().then(|| new_thread_id.to_string()),
|
||||
agent_id: None,
|
||||
task_name,
|
||||
nickname,
|
||||
})
|
||||
@@ -170,7 +174,7 @@ impl ToolHandler for Handler {
|
||||
struct SpawnAgentArgs {
|
||||
message: Option<String>,
|
||||
items: Option<Vec<UserInput>>,
|
||||
task_name: Option<String>,
|
||||
task_name: String,
|
||||
agent_type: Option<String>,
|
||||
model: Option<String>,
|
||||
reasoning_effort: Option<ReasoningEffort>,
|
||||
@@ -181,7 +185,7 @@ struct SpawnAgentArgs {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct SpawnAgentResult {
|
||||
agent_id: Option<String>,
|
||||
task_name: Option<String>,
|
||||
task_name: String,
|
||||
nickname: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -157,10 +157,10 @@ fn spawn_agent_output_schema_v2() -> JsonValue {
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Thread identifier for the spawned agent when no task name was assigned."
|
||||
"description": "Legacy thread identifier for the spawned agent."
|
||||
},
|
||||
"task_name": {
|
||||
"type": ["string", "null"],
|
||||
"type": "string",
|
||||
"description": "Canonical task name for the spawned agent."
|
||||
},
|
||||
"nickname": {
|
||||
@@ -1215,13 +1215,13 @@ fn create_spawn_agent_tool_v1(config: &ToolsConfig) -> ToolSpec {
|
||||
|
||||
fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
|
||||
let available_models_description = spawn_agent_models_description(&config.available_models);
|
||||
let return_value_description = "Returns the canonical task name when the spawned agent was named, otherwise the agent id, plus the user-facing nickname when available.";
|
||||
let return_value_description = "Returns the canonical task name for the spawned agent, plus the user-facing nickname when available.";
|
||||
let mut properties = spawn_agent_common_properties(config);
|
||||
properties.insert(
|
||||
"task_name".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
"Optional task name for the new agent. Use lowercase letters, digits, and underscores."
|
||||
"Task name for the new agent. Use lowercase letters, digits, and underscores."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
@@ -1237,7 +1237,7 @@ fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::Object {
|
||||
properties,
|
||||
required: None,
|
||||
required: Some(vec!["task_name".to_string()]),
|
||||
additional_properties: Some(false.into()),
|
||||
},
|
||||
output_schema: Some(spawn_agent_output_schema_v2()),
|
||||
|
||||
@@ -454,7 +454,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
panic!("spawn_agent should use object params");
|
||||
};
|
||||
assert!(properties.contains_key("task_name"));
|
||||
assert_eq!(required.as_ref(), None);
|
||||
assert_eq!(required.as_ref(), Some(&vec!["task_name".to_string()]));
|
||||
let output_schema = output_schema
|
||||
.as_ref()
|
||||
.expect("spawn_agent should define output schema");
|
||||
|
||||
Reference in New Issue
Block a user