Compare commits

...

4 Commits

Author SHA1 Message Date
Vivian Fang
5405e1b949 Inline MCP code mode preamble constant 2026-04-09 03:50:10 -07:00
Vivian Fang
b291e8d1ef Use raw string for MCP code mode preamble 2026-04-09 03:46:22 -07:00
Vivian Fang
546fa338d6 Thread tool origin into code mode rendering 2026-04-09 03:27:45 -07:00
Vivian Fang
f6d6f0141c Refine code mode MCP result typing 2026-04-09 02:55:52 -07:00
40 changed files with 595 additions and 35 deletions

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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",
"```",
),
})
);

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}

View File

@@ -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,
})
}

View File

@@ -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,
})
}

View File

@@ -41,6 +41,7 @@ fn create_apply_patch_json_tool_matches_expected_spec() {
Some(false.into())
),
output_schema: None,
origin: ToolOrigin::Native,
})
);
}

View File

@@ -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 { .. }

View File

@@ -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(

View File

@@ -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,
})
}

View File

@@ -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,
}
);

View File

@@ -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,
})
}

View File

@@ -36,6 +36,7 @@ fn js_repl_reset_tool_matches_expected_spec() {
Some(false.into())
),
output_schema: None,
origin: ToolOrigin::Native,
})
);
}

View File

@@ -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;

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}

View File

@@ -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

View File

@@ -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,
}
);

View File

@@ -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,
})
}

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}

View File

@@ -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,
}
}

View File

@@ -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,
})],
});

View File

@@ -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,
}

View File

@@ -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()
}

View File

@@ -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,
})
}

View File

@@ -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,
})],
})],
);

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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!({

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}

View File

@@ -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,
})
}

View File

@@ -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,
})
);
}