mirror of
https://github.com/openai/codex.git
synced 2026-04-09 17:11:44 +03:00
Compare commits
4 Commits
default/we
...
codex/code
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5405e1b949 | ||
|
|
b291e8d1ef | ||
|
|
546fa338d6 | ||
|
|
f6d6f0141c |
@@ -49,6 +49,14 @@ pub enum CodeModeToolKind {
|
||||
Freeform,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolOrigin {
|
||||
Native,
|
||||
Mcp,
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ToolDefinition {
|
||||
pub name: String,
|
||||
@@ -56,6 +64,7 @@ pub struct ToolDefinition {
|
||||
pub kind: CodeModeToolKind,
|
||||
pub input_schema: Option<JsonValue>,
|
||||
pub output_schema: Option<JsonValue>,
|
||||
pub origin: ToolOrigin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -169,7 +178,7 @@ pub fn is_code_mode_nested_tool(tool_name: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn build_exec_tool_description(
|
||||
enabled_tools: &[(String, String)],
|
||||
enabled_tools: &[ToolDefinition],
|
||||
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
||||
code_mode_only: bool,
|
||||
) -> String {
|
||||
@@ -185,8 +194,20 @@ pub fn build_exec_tool_description(
|
||||
if !enabled_tools.is_empty() {
|
||||
let mut current_namespace: Option<&str> = None;
|
||||
let mut nested_tool_sections = Vec::with_capacity(enabled_tools.len());
|
||||
let include_shared_mcp_types = enabled_tools
|
||||
.iter()
|
||||
.any(|tool| tool.origin == ToolOrigin::Mcp);
|
||||
|
||||
for (name, nested_description) in enabled_tools {
|
||||
if include_shared_mcp_types {
|
||||
sections.push(format!(
|
||||
"Shared MCP Types:\n```ts\n{}\n```",
|
||||
MCP_TYPESCRIPT_PREAMBLE
|
||||
));
|
||||
}
|
||||
|
||||
for tool in enabled_tools {
|
||||
let name = tool.name.as_str();
|
||||
let nested_description = render_code_mode_sample_for_definition(tool);
|
||||
let next_namespace = namespace_descriptions
|
||||
.get(name)
|
||||
.map(|namespace_description| namespace_description.name.as_str());
|
||||
@@ -206,10 +227,11 @@ pub fn build_exec_tool_description(
|
||||
let global_name = normalize_code_mode_identifier(name);
|
||||
let nested_description = nested_description.trim();
|
||||
if nested_description.is_empty() {
|
||||
nested_tool_sections.push(format!("### `{global_name}` (`{name}`)"));
|
||||
nested_tool_sections.push(render_tool_heading(&global_name, name));
|
||||
} else {
|
||||
nested_tool_sections.push(format!(
|
||||
"### `{global_name}` (`{name}`)\n{nested_description}"
|
||||
"{}\n{nested_description}",
|
||||
render_tool_heading(&global_name, name)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -251,7 +273,7 @@ pub fn normalize_code_mode_identifier(tool_key: &str) -> String {
|
||||
|
||||
pub fn augment_tool_definition(mut definition: ToolDefinition) -> ToolDefinition {
|
||||
if definition.name != PUBLIC_TOOL_NAME {
|
||||
definition.description = append_code_mode_sample_for_definition(&definition);
|
||||
definition.description = render_code_mode_sample_for_definition(&definition);
|
||||
}
|
||||
definition
|
||||
}
|
||||
@@ -273,7 +295,7 @@ pub struct EnabledToolMetadata {
|
||||
pub kind: CodeModeToolKind,
|
||||
}
|
||||
|
||||
pub fn append_code_mode_sample(
|
||||
pub fn render_code_mode_sample(
|
||||
description: &str,
|
||||
tool_name: &str,
|
||||
input_name: &str,
|
||||
@@ -287,7 +309,7 @@ pub fn append_code_mode_sample(
|
||||
format!("{description}\n\nexec tool declaration:\n```ts\n{declaration}\n```")
|
||||
}
|
||||
|
||||
fn append_code_mode_sample_for_definition(definition: &ToolDefinition) -> String {
|
||||
fn render_code_mode_sample_for_definition(definition: &ToolDefinition) -> String {
|
||||
let input_name = match definition.kind {
|
||||
CodeModeToolKind::Function => "args",
|
||||
CodeModeToolKind::Freeform => "input",
|
||||
@@ -300,12 +322,34 @@ fn append_code_mode_sample_for_definition(definition: &ToolDefinition) -> String
|
||||
.unwrap_or_else(|| "unknown".to_string()),
|
||||
CodeModeToolKind::Freeform => "string".to_string(),
|
||||
};
|
||||
if definition.origin == ToolOrigin::Mcp {
|
||||
let structured_content_type = definition
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.and_then(extract_mcp_structured_content_schema)
|
||||
.map(render_json_schema_to_typescript)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let output_type = if structured_content_type == "unknown" {
|
||||
"mcp_result".to_string()
|
||||
} else {
|
||||
format!("mcp_result<{structured_content_type}>")
|
||||
};
|
||||
|
||||
return render_code_mode_sample(
|
||||
&definition.description,
|
||||
&definition.name,
|
||||
input_name,
|
||||
input_type,
|
||||
output_type,
|
||||
);
|
||||
}
|
||||
|
||||
let output_type = definition
|
||||
.output_schema
|
||||
.as_ref()
|
||||
.map(render_json_schema_to_typescript)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
append_code_mode_sample(
|
||||
render_code_mode_sample(
|
||||
&definition.description,
|
||||
&definition.name,
|
||||
input_name,
|
||||
@@ -324,10 +368,65 @@ fn render_code_mode_tool_declaration(
|
||||
format!("{tool_name}({input_name}: {input_type}): Promise<{output_type}>;")
|
||||
}
|
||||
|
||||
fn render_tool_heading(global_name: &str, raw_name: &str) -> String {
|
||||
if global_name == raw_name {
|
||||
format!("### `{global_name}`")
|
||||
} else {
|
||||
format!("### `{global_name}` (`{raw_name}`)")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_json_schema_to_typescript(schema: &JsonValue) -> String {
|
||||
render_json_schema_to_typescript_inner(schema)
|
||||
}
|
||||
|
||||
fn extract_mcp_structured_content_schema(output_schema: &JsonValue) -> Option<&JsonValue> {
|
||||
let properties = output_schema.get("properties")?.as_object()?;
|
||||
Some(
|
||||
properties
|
||||
.get("structuredContent")
|
||||
.unwrap_or(&JsonValue::Bool(true)),
|
||||
)
|
||||
}
|
||||
|
||||
const MCP_TYPESCRIPT_PREAMBLE: &str = r#"type mcp_annotations = {
|
||||
audience?: Array<"user" | "assistant">;
|
||||
priority?: number;
|
||||
lastModified?: string;
|
||||
};
|
||||
type mcp_resource =
|
||||
| {
|
||||
uri: string;
|
||||
mimeType?: string;
|
||||
text: string;
|
||||
annotations?: mcp_annotations;
|
||||
}
|
||||
| {
|
||||
uri: string;
|
||||
mimeType?: string;
|
||||
blob: string;
|
||||
annotations?: mcp_annotations;
|
||||
};
|
||||
type mcp_output =
|
||||
| { type: "text"; text: string; annotations?: mcp_annotations }
|
||||
| { type: "image"; data: string; mimeType: string; annotations?: mcp_annotations }
|
||||
| { type: "audio"; data: string; mimeType: string; annotations?: mcp_annotations }
|
||||
| {
|
||||
type: "resource_link";
|
||||
uri: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
mimeType?: string;
|
||||
annotations?: mcp_annotations;
|
||||
}
|
||||
| { type: "resource"; resource: mcp_resource };
|
||||
type mcp_result<TStructured = unknown> = {
|
||||
_meta?: unknown;
|
||||
content: Array<mcp_output>;
|
||||
isError?: boolean;
|
||||
structuredContent?: TStructured;
|
||||
};"#;
|
||||
|
||||
fn render_json_schema_to_typescript_inner(schema: &JsonValue) -> String {
|
||||
match schema {
|
||||
JsonValue::Bool(true) => "unknown".to_string(),
|
||||
@@ -554,6 +653,7 @@ mod tests {
|
||||
use super::ParsedExecSource;
|
||||
use super::ToolDefinition;
|
||||
use super::ToolNamespaceDescription;
|
||||
use super::ToolOrigin;
|
||||
use super::augment_tool_definition;
|
||||
use super::build_exec_tool_description;
|
||||
use super::normalize_code_mode_identifier;
|
||||
@@ -615,6 +715,7 @@ mod tests {
|
||||
"properties": { "ok": { "type": "boolean" } },
|
||||
"required": ["ok"]
|
||||
})),
|
||||
origin: ToolOrigin::Native,
|
||||
};
|
||||
|
||||
let description = augment_tool_definition(definition).description;
|
||||
@@ -659,6 +760,7 @@ mod tests {
|
||||
},
|
||||
"required": ["forecast"]
|
||||
})),
|
||||
origin: ToolOrigin::Native,
|
||||
};
|
||||
|
||||
let description = augment_tool_definition(definition).description;
|
||||
@@ -676,11 +778,21 @@ mod tests {
|
||||
#[test]
|
||||
fn code_mode_only_description_includes_nested_tools() {
|
||||
let description = build_exec_tool_description(
|
||||
&[("foo".to_string(), "bar".to_string())],
|
||||
&[ToolDefinition {
|
||||
name: "foo".to_string(),
|
||||
description: "bar".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
}],
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
assert!(description.contains("### `foo` (`foo`)"));
|
||||
assert!(description.contains(
|
||||
"### `foo`
|
||||
bar"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -711,8 +823,38 @@ mod tests {
|
||||
]);
|
||||
let description = build_exec_tool_description(
|
||||
&[
|
||||
("mcp__sample__alpha".to_string(), "First tool".to_string()),
|
||||
("mcp__sample__beta".to_string(), "Second tool".to_string()),
|
||||
ToolDefinition {
|
||||
name: "mcp__sample__alpha".to_string(),
|
||||
description: "First tool".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
origin: ToolOrigin::Mcp,
|
||||
},
|
||||
ToolDefinition {
|
||||
name: "mcp__sample__beta".to_string(),
|
||||
description: "Second tool".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
origin: ToolOrigin::Mcp,
|
||||
},
|
||||
],
|
||||
&namespace_descriptions,
|
||||
/*code_mode_only*/ true,
|
||||
@@ -722,11 +864,21 @@ mod tests {
|
||||
r#"## mcp__sample
|
||||
Shared namespace guidance.
|
||||
|
||||
### `mcp__sample__alpha` (`mcp__sample__alpha`)
|
||||
### `mcp__sample__alpha`
|
||||
First tool
|
||||
|
||||
### `mcp__sample__beta` (`mcp__sample__beta`)
|
||||
Second tool"#
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__alpha(args: {}): Promise<mcp_result>; };
|
||||
```
|
||||
|
||||
### `mcp__sample__beta`
|
||||
Second tool
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__beta(args: {}): Promise<mcp_result>; };
|
||||
```"#
|
||||
));
|
||||
}
|
||||
|
||||
@@ -740,12 +892,130 @@ Second tool"#
|
||||
},
|
||||
)]);
|
||||
let description = build_exec_tool_description(
|
||||
&[("mcp__sample__alpha".to_string(), "First tool".to_string())],
|
||||
&[ToolDefinition {
|
||||
name: "mcp__sample__alpha".to_string(),
|
||||
description: "First tool".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
origin: ToolOrigin::Mcp,
|
||||
}],
|
||||
&namespace_descriptions,
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
|
||||
assert!(!description.contains("## mcp__sample"));
|
||||
assert!(description.contains("### `mcp__sample__alpha` (`mcp__sample__alpha`)"));
|
||||
assert!(description.contains("### `mcp__sample__alpha`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_only_description_renders_shared_mcp_types_once() {
|
||||
let first_tool = augment_tool_definition(ToolDefinition {
|
||||
name: "mcp__sample__alpha".to_string(),
|
||||
description: "First tool".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"structuredContent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"echo": { "type": "string" }
|
||||
},
|
||||
"required": ["echo"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"isError": { "type": "boolean" },
|
||||
"_meta": { "type": "object" }
|
||||
},
|
||||
"required": ["content"],
|
||||
"additionalProperties": false
|
||||
})),
|
||||
origin: ToolOrigin::Mcp,
|
||||
});
|
||||
let second_tool = augment_tool_definition(ToolDefinition {
|
||||
name: "mcp__sample__beta".to_string(),
|
||||
description: "Second tool".to_string(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
})),
|
||||
output_schema: Some(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"structuredContent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": { "type": "integer" }
|
||||
},
|
||||
"required": ["count"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"isError": { "type": "boolean" },
|
||||
"_meta": { "type": "object" }
|
||||
},
|
||||
"required": ["content"],
|
||||
"additionalProperties": false
|
||||
})),
|
||||
origin: ToolOrigin::Mcp,
|
||||
});
|
||||
|
||||
let description = build_exec_tool_description(
|
||||
&[
|
||||
ToolDefinition {
|
||||
name: first_tool.name,
|
||||
description: "First tool".to_string(),
|
||||
kind: first_tool.kind,
|
||||
input_schema: first_tool.input_schema,
|
||||
output_schema: first_tool.output_schema,
|
||||
origin: first_tool.origin,
|
||||
},
|
||||
ToolDefinition {
|
||||
name: second_tool.name,
|
||||
description: "Second tool".to_string(),
|
||||
kind: second_tool.kind,
|
||||
input_schema: second_tool.input_schema,
|
||||
output_schema: second_tool.output_schema,
|
||||
origin: second_tool.origin,
|
||||
},
|
||||
],
|
||||
&BTreeMap::new(),
|
||||
/*code_mode_only*/ true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
description
|
||||
.matches("type mcp_result<TStructured = unknown>")
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(description.matches("Shared MCP Types:").count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ pub use description::CODE_MODE_PRAGMA_PREFIX;
|
||||
pub use description::CodeModeToolKind;
|
||||
pub use description::ToolDefinition;
|
||||
pub use description::ToolNamespaceDescription;
|
||||
pub use description::append_code_mode_sample;
|
||||
pub use description::ToolOrigin;
|
||||
pub use description::augment_tool_definition;
|
||||
pub use description::build_exec_tool_description;
|
||||
pub use description::build_wait_tool_description;
|
||||
pub use description::is_code_mode_nested_tool;
|
||||
pub use description::normalize_code_mode_identifier;
|
||||
pub use description::parse_exec_source;
|
||||
pub use description::render_code_mode_sample;
|
||||
pub use description::render_json_schema_to_typescript;
|
||||
pub use response::FunctionCallOutputContentItem;
|
||||
pub use response::ImageDetail;
|
||||
|
||||
@@ -2280,7 +2280,14 @@ text(JSON.stringify(tool));
|
||||
parsed,
|
||||
serde_json::json!({
|
||||
"name": "mcp__rmcp__echo",
|
||||
"description": "Echo back the provided message and include environment data.\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };\n```",
|
||||
"description": concat!(
|
||||
"Echo back the provided message and include environment data.\n\n",
|
||||
"exec tool declaration:\n",
|
||||
"```ts\n",
|
||||
"declare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): ",
|
||||
"Promise<mcp_result<{ echo: string; env: string | null; }>>; };\n",
|
||||
"```",
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -45,11 +45,29 @@ impl TestToolServer {
|
||||
}))
|
||||
.expect("echo tool schema should deserialize");
|
||||
|
||||
Tool::new(
|
||||
let mut tool = Tool::new(
|
||||
Cow::Borrowed("echo"),
|
||||
Cow::Borrowed("Echo back the provided message and include environment data."),
|
||||
Arc::new(schema),
|
||||
)
|
||||
);
|
||||
#[expect(clippy::expect_used)]
|
||||
let output_schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"echo": { "type": "string" },
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["echo", "env"],
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.expect("echo tool output schema should deserialize");
|
||||
tool.output_schema = Some(Arc::new(output_schema));
|
||||
tool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,23 @@ impl TestToolServer {
|
||||
Cow::Borrowed(description),
|
||||
Arc::new(schema),
|
||||
);
|
||||
#[expect(clippy::expect_used)]
|
||||
let output_schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"echo": { "type": "string" },
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["echo", "env"],
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.expect("echo tool output schema should deserialize");
|
||||
tool.output_schema = Some(Arc::new(output_schema));
|
||||
tool.annotations = Some(ToolAnnotations::new().read_only(true));
|
||||
tool
|
||||
}
|
||||
|
||||
@@ -90,6 +90,23 @@ impl TestToolServer {
|
||||
Cow::Borrowed("Echo back the provided message and include environment data."),
|
||||
Arc::new(schema),
|
||||
);
|
||||
#[expect(clippy::expect_used)]
|
||||
let output_schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"echo": { "type": "string" },
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["echo", "env"],
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.expect("echo tool output schema should deserialize");
|
||||
tool.output_schema = Some(Arc::new(output_schema));
|
||||
tool.annotations = Some(ToolAnnotations::new().read_only(true));
|
||||
tool
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -60,6 +61,7 @@ pub fn create_spawn_agents_on_csv_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["csv_path".to_string(), "instruction".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +101,7 @@ pub fn create_report_agent_job_result_tool() -> ToolSpec {
|
||||
"result".to_string(),
|
||||
]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ fn spawn_agents_on_csv_tool_requires_csv_and_instruction() {
|
||||
),
|
||||
]), Some(vec!["csv_path".to_string(), "instruction".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -114,6 +115,7 @@ fn report_agent_job_result_tool_requires_result_payload() {
|
||||
"result".to_string(),
|
||||
]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use serde_json::Value;
|
||||
@@ -44,6 +45,7 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, /*required*/ None, Some(false.into())),
|
||||
output_schema: Some(spawn_agent_output_schema_v1()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,6 +87,7 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
output_schema: Some(spawn_agent_output_schema_v2(
|
||||
options.hide_agent_type_model_reasoning,
|
||||
)),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -119,6 +122,7 @@ pub fn create_send_input_tool_v1() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string()]), Some(false.into())),
|
||||
output_schema: Some(send_input_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,6 +150,7 @@ pub fn create_send_message_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string(), "message".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -180,6 +185,7 @@ pub fn create_followup_task_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string(), "message".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -198,6 +204,7 @@ pub fn create_resume_agent_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["id".to_string()]), Some(false.into())),
|
||||
output_schema: Some(resume_agent_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,6 +217,7 @@ pub fn create_wait_agent_tool_v1(options: WaitAgentTimeoutOptions) -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: wait_agent_tool_parameters_v1(options),
|
||||
output_schema: Some(wait_output_schema_v1()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -222,6 +230,7 @@ pub fn create_wait_agent_tool_v2(options: WaitAgentTimeoutOptions) -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: wait_agent_tool_parameters_v2(options),
|
||||
output_schema: Some(wait_output_schema_v2()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -243,6 +252,7 @@ pub fn create_list_agents_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, /*required*/ None, Some(false.into())),
|
||||
output_schema: Some(list_agents_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -259,6 +269,7 @@ pub fn create_close_agent_tool_v1() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string()]), Some(false.into())),
|
||||
output_schema: Some(close_agent_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,6 +288,7 @@ pub fn create_close_agent_tool_v2() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string()]), Some(false.into())),
|
||||
output_schema: Some(close_agent_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -118,6 +119,7 @@ pub fn create_apply_patch_json_tool() -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ fn create_apply_patch_json_tool_matches_expected_spec() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use codex_code_mode::CodeModeToolKind;
|
||||
use codex_code_mode::ToolDefinition as CodeModeToolDefinition;
|
||||
use codex_code_mode::ToolOrigin as CodeModeToolOrigin;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Augment tool descriptions with code-mode-specific exec samples.
|
||||
@@ -49,6 +51,19 @@ pub fn collect_code_mode_tool_definitions<'a>(
|
||||
tool_definitions
|
||||
}
|
||||
|
||||
pub fn collect_code_mode_exec_prompt_tool_definitions<'a>(
|
||||
specs: impl IntoIterator<Item = &'a ToolSpec>,
|
||||
) -> Vec<CodeModeToolDefinition> {
|
||||
let mut tool_definitions = specs
|
||||
.into_iter()
|
||||
.filter_map(code_mode_tool_definition_for_spec)
|
||||
.filter(|definition| codex_code_mode::is_code_mode_nested_tool(&definition.name))
|
||||
.collect::<Vec<_>>();
|
||||
tool_definitions.sort_by(|left, right| left.name.cmp(&right.name));
|
||||
tool_definitions.dedup_by(|left, right| left.name == right.name);
|
||||
tool_definitions
|
||||
}
|
||||
|
||||
pub fn create_wait_tool() -> ToolSpec {
|
||||
let properties = BTreeMap::from([
|
||||
(
|
||||
@@ -90,12 +105,13 @@ pub fn create_wait_tool() -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
defer_loading: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_code_mode_tool(
|
||||
enabled_tools: &[(String, String)],
|
||||
enabled_tools: &[CodeModeToolDefinition],
|
||||
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||
code_mode_only_enabled: bool,
|
||||
) -> ToolSpec {
|
||||
@@ -132,6 +148,11 @@ fn code_mode_tool_definition_for_spec(spec: &ToolSpec) -> Option<CodeModeToolDef
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: serde_json::to_value(&tool.parameters).ok(),
|
||||
output_schema: tool.output_schema.clone(),
|
||||
origin: match tool.origin {
|
||||
ToolOrigin::Native => CodeModeToolOrigin::Native,
|
||||
ToolOrigin::Mcp => CodeModeToolOrigin::Mcp,
|
||||
ToolOrigin::Dynamic => CodeModeToolOrigin::Dynamic,
|
||||
},
|
||||
}),
|
||||
ToolSpec::Freeform(tool) => Some(CodeModeToolDefinition {
|
||||
name: tool.name.clone(),
|
||||
@@ -139,6 +160,7 @@ fn code_mode_tool_definition_for_spec(spec: &ToolSpec) -> Option<CodeModeToolDef
|
||||
kind: CodeModeToolKind::Freeform,
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
origin: CodeModeToolOrigin::Native,
|
||||
}),
|
||||
ToolSpec::LocalShell {}
|
||||
| ToolSpec::ImageGeneration { .. }
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -35,6 +36,7 @@ fn augment_tool_spec_for_code_mode_augments_function_tools() {
|
||||
},
|
||||
"required": ["ok"],
|
||||
})),
|
||||
origin: ToolOrigin::Native,
|
||||
})),
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
@@ -62,6 +64,7 @@ declare const tools: { lookup_order(args: { order_id: string; }): Promise<{ ok:
|
||||
},
|
||||
"required": ["ok"],
|
||||
})),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -116,6 +119,7 @@ declare const tools: { apply_patch(input: string): Promise<unknown>; };
|
||||
kind: codex_code_mode::CodeModeToolKind::Freeform,
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
origin: codex_code_mode::ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -176,13 +180,21 @@ fn create_wait_tool_matches_expected_spec() {
|
||||
),
|
||||
]), Some(vec!["cell_id".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_code_mode_tool_matches_expected_spec() {
|
||||
let enabled_tools = vec![("update_plan".to_string(), "Update the plan".to_string())];
|
||||
let enabled_tools = vec![codex_code_mode::ToolDefinition {
|
||||
name: "update_plan".to_string(),
|
||||
description: "Update the plan".to_string(),
|
||||
kind: codex_code_mode::CodeModeToolKind::Function,
|
||||
input_schema: None,
|
||||
output_schema: None,
|
||||
origin: codex_code_mode::ToolOrigin::Native,
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
create_code_mode_tool(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use crate::parse_tool_input_schema;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
|
||||
@@ -14,6 +15,7 @@ pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> Result<ToolDefinition, serd
|
||||
description: description.clone(),
|
||||
input_schema: parse_tool_input_schema(input_schema)?,
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Dynamic,
|
||||
defer_loading: *defer_loading,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::parse_dynamic_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -34,6 +35,7 @@ fn parse_dynamic_tool_sanitizes_input_schema() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Dynamic,
|
||||
defer_loading: false,
|
||||
}
|
||||
);
|
||||
@@ -62,6 +64,7 @@ fn parse_dynamic_tool_preserves_defer_loading() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Dynamic,
|
||||
defer_loading: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -47,6 +48,7 @@ pub fn create_js_repl_reset_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(BTreeMap::new(), /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ fn js_repl_reset_tool_matches_expected_spec() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ pub use apply_patch_tool::ApplyPatchToolArgs;
|
||||
pub use apply_patch_tool::create_apply_patch_freeform_tool;
|
||||
pub use apply_patch_tool::create_apply_patch_json_tool;
|
||||
pub use code_mode::augment_tool_spec_for_code_mode;
|
||||
pub use code_mode::collect_code_mode_exec_prompt_tool_definitions;
|
||||
pub use code_mode::collect_code_mode_tool_definitions;
|
||||
pub use code_mode::create_code_mode_tool;
|
||||
pub use code_mode::create_wait_tool;
|
||||
@@ -94,6 +95,7 @@ pub use tool_config::ToolsConfigParams;
|
||||
pub use tool_config::UnifiedExecShellMode;
|
||||
pub use tool_config::ZshForkConfig;
|
||||
pub use tool_definition::ToolDefinition;
|
||||
pub use tool_definition::ToolOrigin;
|
||||
pub use tool_discovery::DiscoverablePluginInfo;
|
||||
pub use tool_discovery::DiscoverableTool;
|
||||
pub use tool_discovery::DiscoverableToolAction;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
@@ -86,6 +87,7 @@ pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: Some(unified_exec_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,6 +132,7 @@ pub fn create_write_stdin_tool() -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: Some(unified_exec_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -193,6 +196,7 @@ Examples of valid command strings:
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -263,6 +267,7 @@ Examples of valid command strings:
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,6 +293,7 @@ pub fn create_request_permissions_tool(description: String) -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ Examples of valid command strings:
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -170,6 +171,7 @@ fn exec_command_tool_matches_expected_spec() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(unified_exec_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -220,6 +222,7 @@ fn write_stdin_tool_matches_expected_spec() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(unified_exec_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -291,6 +294,7 @@ Examples of valid command strings:
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -323,6 +327,7 @@ fn request_permissions_tool_includes_full_permission_schema() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -397,6 +402,7 @@ Examples of valid command strings:
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -28,6 +29,7 @@ pub fn create_list_mcp_resources_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,6 +58,7 @@ pub fn create_list_mcp_resource_templates_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,6 +93,7 @@ pub fn create_read_mcp_resource_tool() -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ fn list_mcp_resources_tool_matches_expected_spec() {
|
||||
),
|
||||
]), /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -59,6 +60,7 @@ fn list_mcp_resource_templates_tool_matches_expected_spec() {
|
||||
),
|
||||
]), /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -91,6 +93,7 @@ fn read_mcp_resource_tool_matches_expected_spec() {
|
||||
),
|
||||
]), Some(vec!["server".to_string(), "uri".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use crate::parse_tool_input_schema;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
@@ -32,6 +33,7 @@ pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(
|
||||
structured_content_schema,
|
||||
)),
|
||||
origin: ToolOrigin::Mcp,
|
||||
defer_loading: false,
|
||||
})
|
||||
}
|
||||
@@ -42,13 +44,17 @@ pub fn mcp_call_tool_result_output_schema(structured_content_schema: JsonValue)
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"structuredContent": structured_content_schema,
|
||||
"isError": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"_meta": {}
|
||||
"_meta": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": ["content"],
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::mcp_call_tool_result_output_schema;
|
||||
use super::parse_mcp_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -40,6 +41,7 @@ fn parse_mcp_tool_inserts_empty_properties() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
origin: ToolOrigin::Mcp,
|
||||
defer_loading: false,
|
||||
}
|
||||
);
|
||||
@@ -87,6 +89,7 @@ fn parse_mcp_tool_preserves_top_level_output_schema() {
|
||||
},
|
||||
"required": ["result"]
|
||||
}))),
|
||||
origin: ToolOrigin::Mcp,
|
||||
defer_loading: false,
|
||||
}
|
||||
);
|
||||
@@ -120,6 +123,7 @@ fn parse_mcp_tool_preserves_output_schema_without_inferred_type() {
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"enum": ["ok", "error"]
|
||||
}))),
|
||||
origin: ToolOrigin::Mcp,
|
||||
defer_loading: false,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -45,5 +46,6 @@ At most one step can be in_progress at a time.
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES;
|
||||
@@ -80,6 +81,7 @@ pub fn create_request_user_input_tool(description: String) -> ToolSpec {
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ fn request_user_input_tool_includes_questions_schema() {
|
||||
),
|
||||
)]), Some(vec!["questions".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use crate::parse_dynamic_tool;
|
||||
use crate::parse_mcp_tool;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
@@ -34,6 +35,8 @@ pub struct ResponsesApiTool {
|
||||
pub parameters: JsonSchema,
|
||||
#[serde(skip)]
|
||||
pub output_schema: Option<Value>,
|
||||
#[serde(skip)]
|
||||
pub origin: ToolOrigin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
@@ -95,6 +98,7 @@ pub fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) ->
|
||||
defer_loading: tool_definition.defer_loading.then_some(true),
|
||||
parameters: tool_definition.input_schema,
|
||||
output_schema: tool_definition.output_schema,
|
||||
origin: tool_definition.origin,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::mcp_tool_to_deferred_responses_api_tool;
|
||||
use super::tool_definition_to_responses_api_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolOrigin;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -27,6 +28,7 @@ fn tool_definition_to_responses_api_tool_omits_false_defer_loading() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(json!({"type": "object"})),
|
||||
origin: ToolOrigin::Native,
|
||||
defer_loading: false,
|
||||
}),
|
||||
ResponsesApiTool {
|
||||
@@ -43,6 +45,7 @@ fn tool_definition_to_responses_api_tool_omits_false_defer_loading() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(json!({"type": "object"})),
|
||||
origin: ToolOrigin::Native,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -79,6 +82,7 @@ fn dynamic_tool_to_responses_api_tool_preserves_defer_loading() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Dynamic,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -120,6 +124,7 @@ fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading() {
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]), Some(vec!["order_id".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Mcp,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -140,6 +145,7 @@ fn tool_search_output_namespace_serializes_with_deferred_child_tools() {
|
||||
/*additional_properties*/ None,
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Mcp,
|
||||
})],
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
use crate::JsonSchema;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// Where a tool definition originated before it was normalized into the shared
|
||||
/// `ToolDefinition` shape.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ToolOrigin {
|
||||
/// A built-in Codex tool defined directly in Rust, such as `exec_command`,
|
||||
/// `view_image`, `update_plan`, or the agent/collaboration tools.
|
||||
#[default]
|
||||
Native,
|
||||
/// A tool discovered from an MCP server and converted through the MCP tool
|
||||
/// pipeline. These tools may advertise an MCP `outputSchema`, which code
|
||||
/// mode uses to render shared `mcp_result<T>` aliases.
|
||||
Mcp,
|
||||
/// A runtime-provided non-MCP tool definition, such as a `DynamicToolSpec`
|
||||
/// supplied externally rather than compiled into the binary.
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
/// Tool metadata and schemas that downstream crates can adapt into higher-level
|
||||
/// tool specs.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -9,6 +26,7 @@ pub struct ToolDefinition {
|
||||
pub description: String,
|
||||
pub input_schema: JsonSchema,
|
||||
pub output_schema: Option<JsonValue>,
|
||||
pub origin: ToolOrigin,
|
||||
pub defer_loading: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::ToolDefinition;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolOrigin;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -15,6 +16,7 @@ fn tool_definition() -> ToolDefinition {
|
||||
output_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
})),
|
||||
origin: ToolOrigin::Native,
|
||||
defer_loading: false,
|
||||
}
|
||||
}
|
||||
@@ -36,6 +38,7 @@ fn into_deferred_drops_output_schema_and_sets_defer_loading() {
|
||||
tool_definition().into_deferred(),
|
||||
ToolDefinition {
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
defer_loading: true,
|
||||
..tool_definition()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::JsonSchema;
|
||||
use crate::ResponsesApiNamespace;
|
||||
use crate::ResponsesApiNamespaceTool;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSearchOutputTool;
|
||||
use crate::ToolSpec;
|
||||
use crate::mcp_tool_to_deferred_responses_api_tool;
|
||||
@@ -322,6 +323,7 @@ pub fn create_tool_suggest_tool(discoverable_tools: &[ToolSuggestEntry]) -> Tool
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ fn create_tool_suggest_tool_uses_plugin_summary_fallback() {
|
||||
"suggest_reason".to_string(),
|
||||
]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -185,6 +186,7 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
}),
|
||||
ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "_list_events".to_string(),
|
||||
@@ -197,6 +199,7 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -214,6 +217,7 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})],
|
||||
}),
|
||||
],
|
||||
@@ -249,6 +253,7 @@ fn collect_tool_search_output_tools_falls_back_to_connector_name_description() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})],
|
||||
})],
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::ToolSpec;
|
||||
use crate::ToolsConfig;
|
||||
use crate::ViewImageToolOptions;
|
||||
use crate::WebSearchToolOptions;
|
||||
use crate::collect_code_mode_tool_definitions;
|
||||
use crate::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use crate::collect_tool_search_app_infos;
|
||||
use crate::collect_tool_suggest_entries;
|
||||
use crate::create_apply_patch_freeform_tool;
|
||||
@@ -93,17 +93,14 @@ pub fn build_tool_registry_plan(
|
||||
..params
|
||||
},
|
||||
);
|
||||
let mut enabled_tools = collect_code_mode_tool_definitions(
|
||||
let mut enabled_tools = collect_code_mode_exec_prompt_tool_definitions(
|
||||
nested_plan
|
||||
.specs
|
||||
.iter()
|
||||
.map(|configured_tool| &configured_tool.spec),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|tool| (tool.name, tool.description))
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tools.sort_by(|(left_name, _), (right_name, _)| {
|
||||
compare_code_mode_tool_names(left_name, right_name, &namespace_descriptions)
|
||||
);
|
||||
enabled_tools.sort_by(|left, right| {
|
||||
compare_code_mode_tool_names(&left.name, &right.name, &namespace_descriptions)
|
||||
});
|
||||
plan.push_spec(
|
||||
create_code_mode_tool(
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::ResponsesApiWebSearchFilters;
|
||||
use crate::ResponsesApiWebSearchUserLocation;
|
||||
use crate::ToolHandlerSpec;
|
||||
use crate::ToolNamespace;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolRegistryPlanAppTool;
|
||||
use crate::ToolsConfigParams;
|
||||
use crate::WaitAgentTimeoutOptions;
|
||||
@@ -1145,6 +1146,7 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
strict: false,
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
defer_loading: None,
|
||||
origin: ToolOrigin::Mcp,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1588,7 +1590,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };
|
||||
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<mcp_result>; };
|
||||
```"#
|
||||
);
|
||||
}
|
||||
@@ -1674,7 +1676,7 @@ fn code_mode_preserves_nullable_and_literal_mcp_input_shapes() {
|
||||
assert!(description.contains(
|
||||
r#"exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__fn(args: { open?: Array<{ lineno?: number | null; ref_id: string; }> | null; response_length?: "short" | "medium" | "long"; tagged_list?: Array<{ kind: "tagged"; scope: "one" | "two"; variant: "alpha" | "beta"; }> | null; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };
|
||||
declare const tools: { mcp__sample__fn(args: { open?: Array<{ lineno?: number | null; ref_id: string; }> | null; response_length?: "short" | "medium" | "long"; tagged_list?: Array<{ kind: "tagged"; scope: "one" | "two"; variant: "alpha" | "beta"; }> | null; }): Promise<mcp_result>; };
|
||||
```"#
|
||||
));
|
||||
}
|
||||
@@ -1902,6 +1904,88 @@ fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> r
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_tool_with_output_schema(
|
||||
name: &str,
|
||||
description: &str,
|
||||
input_schema: serde_json::Value,
|
||||
output_schema: serde_json::Value,
|
||||
) -> rmcp::model::Tool {
|
||||
let mut tool = mcp_tool(name, description, input_schema);
|
||||
tool.output_schema = Some(std::sync::Arc::new(rmcp::model::object(output_schema)));
|
||||
tool
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_mode_augments_mcp_tool_descriptions_with_structured_output_sample() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::CodeModeOnly);
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"mcp__sample__echo".to_string(),
|
||||
mcp_tool_with_output_schema(
|
||||
"echo",
|
||||
"Echo text",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": false
|
||||
}),
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"echo": {"type": "string"},
|
||||
"env": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "null"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["echo", "env"],
|
||||
"additionalProperties": false
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) =
|
||||
&find_tool(&tools, "mcp__sample__echo").spec
|
||||
else {
|
||||
panic!("expected function tool");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
description,
|
||||
r#"Echo text
|
||||
|
||||
exec tool declaration:
|
||||
```ts
|
||||
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<mcp_result<{ echo: string; env: string | null; }>>; };
|
||||
```"#
|
||||
);
|
||||
}
|
||||
|
||||
fn discoverable_connector(id: &str, name: &str, description: &str) -> DiscoverableTool {
|
||||
let slug = name.replace(' ', "-").to_lowercase();
|
||||
DiscoverableTool::Connector(Box::new(AppInfo {
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::create_tools_json_for_responses_api;
|
||||
use codex_protocol::config_types::WebSearchContextSize;
|
||||
use codex_protocol::config_types::WebSearchFilters as ConfigWebSearchFilters;
|
||||
@@ -30,6 +31,7 @@ fn tool_spec_name_covers_all_variants() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
.name(),
|
||||
"lookup_order"
|
||||
@@ -96,6 +98,7 @@ fn configured_tool_spec_name_delegates_to_tool_spec() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
)
|
||||
@@ -146,6 +149,7 @@ fn create_tools_json_for_responses_api_includes_top_level_name() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})])
|
||||
.expect("serialize tools"),
|
||||
vec![json!({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -36,6 +37,7 @@ pub fn create_list_dir_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["dir_path".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,6 +93,7 @@ pub fn create_test_sync_tool() -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ fn list_dir_tool_matches_expected_spec() {
|
||||
),
|
||||
]), Some(vec!["dir_path".to_string()]), Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -103,6 +104,7 @@ fn test_sync_tool_matches_expected_spec() {
|
||||
),
|
||||
]), /*required*/ None, Some(false.into())),
|
||||
output_schema: None,
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolOrigin;
|
||||
use crate::ToolSpec;
|
||||
use codex_protocol::models::VIEW_IMAGE_TOOL_NAME;
|
||||
use serde_json::Value;
|
||||
@@ -33,6 +34,7 @@ pub fn create_view_image_tool(options: ViewImageToolOptions) -> ToolSpec {
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["path".to_string()]), Some(false.into())),
|
||||
output_schema: Some(view_image_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
JsonSchema::string(Some("Local filesystem path to an image file".to_string()),),
|
||||
)]), Some(vec!["path".to_string()]), Some(false.into())),
|
||||
output_schema: Some(view_image_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -49,6 +50,7 @@ fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
),
|
||||
]), Some(vec!["path".to_string()]), Some(false.into())),
|
||||
output_schema: Some(view_image_output_schema()),
|
||||
origin: ToolOrigin::Native,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user