mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
codex-tools: extract collaboration tool specs (#16141)
## Why The recent `codex-tools` migration steps have moved shared tool models and low-coupling spec helpers out of `codex-core`, but `core/src/tools/spec.rs` still owned a large block of pure collaboration-tool spec construction. Those builders do not need session state or runtime behavior; they only need a small amount of core-owned configuration injected at the seam. Moving that cohesive slice into `codex-tools` makes the crate boundary more honest and removes a substantial amount of passive tool-spec logic from `codex-core` without trying to move the runtime-coupled multi-agent handlers at the same time. ## What changed - added `agent_tool.rs`, `request_user_input_tool.rs`, and `agent_job_tool.rs` to `codex-tools`, with sibling `*_tests.rs` coverage and an exports-only `lib.rs` - moved the pure `ToolSpec` builders for: - collaboration tools such as `spawn_agent`, `send_input`, `send_message`, `assign_task`, `resume_agent`, `wait_agent`, `list_agents`, and `close_agent` - `request_user_input` - agent-job specs `spawn_agents_on_csv` and `report_agent_job_result` - rewired `core/src/tools/spec.rs` to call the extracted builders while still supplying the core-owned inputs, such as spawn-agent role descriptions and wait timeout bounds - updated the `core/src/tools/spec.rs` seam tests to build expected collaboration specs through `codex-tools` - updated `codex-rs/tools/README.md` so the crate documentation reflects the broader collaboration-tool boundary ## Test plan - `CARGO_TARGET_DIR=/tmp/codex-tools-collab-specs cargo test -p codex-tools` - `CARGO_TARGET_DIR=/tmp/codex-core-collab-specs cargo test -p codex-core --lib tools::spec::` - `just fix -p codex-tools -p codex-core` - `just argument-comment-lint` ## References - #15923 - #15928 - #15944 - #15953 - #16031 - #16047 - #16129 - #16132 - #16138
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
use super::*;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
fn model_preset(id: &str, show_in_picker: bool) -> ModelPreset {
|
||||
ModelPreset {
|
||||
id: id.to_string(),
|
||||
model: format!("{id}-model"),
|
||||
display_name: format!("{id} display"),
|
||||
description: format!("{id} description"),
|
||||
default_reasoning_effort: ReasoningEffort::Medium,
|
||||
supported_reasoning_efforts: vec![ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Medium,
|
||||
description: "Balanced".to_string(),
|
||||
}],
|
||||
supports_personality: false,
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
show_in_picker,
|
||||
availability_nux: None,
|
||||
supported_in_api: true,
|
||||
input_modalities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
|
||||
let tool = create_spawn_agent_tool_v2(SpawnAgentToolOptions {
|
||||
available_models: &[
|
||||
model_preset("visible", /*show_in_picker*/ true),
|
||||
model_preset("hidden", /*show_in_picker*/ false),
|
||||
],
|
||||
agent_type_description: "role help".to_string(),
|
||||
});
|
||||
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description,
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = tool
|
||||
else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
else {
|
||||
panic!("spawn_agent should use object params");
|
||||
};
|
||||
assert!(description.contains("visible display (`visible-model`)"));
|
||||
assert!(!description.contains("hidden display (`hidden-model`)"));
|
||||
assert!(properties.contains_key("task_name"));
|
||||
assert_eq!(
|
||||
properties.get("agent_type"),
|
||||
Some(&JsonSchema::String {
|
||||
description: Some("role help".to_string()),
|
||||
})
|
||||
);
|
||||
assert_eq!(required, Some(vec!["task_name".to_string()]));
|
||||
assert_eq!(
|
||||
output_schema.expect("spawn_agent output schema")["required"],
|
||||
json!(["agent_id", "task_name", "nickname"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_message_tool_requires_items_and_uses_submission_output() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = create_send_message_tool()
|
||||
else {
|
||||
panic!("send_message should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object {
|
||||
properties,
|
||||
required,
|
||||
..
|
||||
} = parameters
|
||||
else {
|
||||
panic!("send_message should use object params");
|
||||
};
|
||||
assert!(properties.contains_key("target"));
|
||||
assert!(properties.contains_key("items"));
|
||||
assert!(!properties.contains_key("message"));
|
||||
assert_eq!(
|
||||
required,
|
||||
Some(vec!["target".to_string(), "items".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
output_schema.expect("send_message output schema")["required"],
|
||||
json!(["submission_id"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_agent_tool_v2_uses_task_targets_and_summary_output() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = create_wait_agent_tool_v2(WaitAgentTimeoutOptions {
|
||||
default_timeout_ms: 30_000,
|
||||
min_timeout_ms: 10_000,
|
||||
max_timeout_ms: 3_600_000,
|
||||
})
|
||||
else {
|
||||
panic!("wait_agent should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object { properties, .. } = parameters else {
|
||||
panic!("wait_agent should use object params");
|
||||
};
|
||||
let Some(JsonSchema::Array {
|
||||
description: Some(description),
|
||||
..
|
||||
}) = properties.get("targets")
|
||||
else {
|
||||
panic!("wait_agent should define targets array");
|
||||
};
|
||||
assert!(description.contains("canonical task names"));
|
||||
assert_eq!(
|
||||
output_schema.expect("wait output schema")["properties"]["message"]["description"],
|
||||
json!("Brief wait summary without the agent's final content.")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_agents_tool_includes_path_prefix_and_agent_fields() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
}) = create_list_agents_tool()
|
||||
else {
|
||||
panic!("list_agents should be a function tool");
|
||||
};
|
||||
let JsonSchema::Object { properties, .. } = parameters else {
|
||||
panic!("list_agents should use object params");
|
||||
};
|
||||
assert!(properties.contains_key("path_prefix"));
|
||||
assert_eq!(
|
||||
output_schema.expect("list_agents output schema")["properties"]["agents"]["items"]["required"],
|
||||
json!(["agent_name", "agent_status", "last_task_message"])
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user