mirror of
https://github.com/openai/codex.git
synced 2026-05-03 02:46:39 +00:00
Add ALL_TOOLS export to code mode (#14294)
So code mode can search for tools.
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::exec_env::create_env;
|
||||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::code_mode_description::augment_tool_spec_for_code_mode;
|
||||
use crate::tools::code_mode_description::code_mode_tool_reference;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
@@ -51,8 +52,11 @@ enum CodeModeToolKind {
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
struct EnabledTool {
|
||||
tool_name: String,
|
||||
#[serde(rename = "module")]
|
||||
module_path: String,
|
||||
namespace: Vec<String>,
|
||||
name: String,
|
||||
description: String,
|
||||
kind: CodeModeToolKind,
|
||||
}
|
||||
|
||||
@@ -107,7 +111,7 @@ pub(crate) fn instructions(config: &Config) -> Option<String> {
|
||||
section.push_str(&format!(
|
||||
"- `{PUBLIC_TOOL_NAME}` uses the same Node runtime resolution as `js_repl`. If needed, point `js_repl_node_path` at the Node binary you want Codex to use.\n",
|
||||
));
|
||||
section.push_str("- Import nested tools from `tools.js`, for example `import { exec_command } from \"tools.js\"` or `import { tools } from \"tools.js\"`. Namespaced tools are also available from `tools/<namespace...>.js`; MCP tools use `tools/mcp/<server>.js`, for example `import { append_notebook_logs_chart } from \"tools/mcp/ologs.js\"`. `tools[name]` and identifier wrappers like `await exec_command(args)` remain available for compatibility. Nested tool calls resolve to their code-mode result values.\n");
|
||||
section.push_str("- Import nested tools from `tools.js`, for example `import { exec_command } from \"tools.js\"` or `import { ALL_TOOLS } from \"tools.js\"` to inspect the available `{ module, name, description }` entries. Namespaced tools are also available from `tools/<namespace...>.js`; MCP tools use `tools/mcp/<server>.js`, for example `import { append_notebook_logs_chart } from \"tools/mcp/ologs.js\"`. Nested tool calls resolve to their code-mode result values.\n");
|
||||
section.push_str(&format!(
|
||||
"- Import `{{ output_text, output_image, set_max_output_tokens_per_exec_call, store, load }}` from `@openai/code_mode` (or `\"openai/code_mode\"`). `output_text(value)` surfaces text back to the model and stringifies non-string objects with `JSON.stringify(...)` when possible. `output_image(imageUrl)` appends an `input_image` content item for `http(s)` or `data:` URLs. `store(key, value)` persists JSON-serializable values across `{PUBLIC_TOOL_NAME}` calls in the current session, and `load(key)` returns a cloned stored value or `undefined`. `set_max_output_tokens_per_exec_call(value)` sets the token budget used to truncate the final Rust-side result of the current `{PUBLIC_TOOL_NAME}` execution; the default is `10000`. This guards the overall `{PUBLIC_TOOL_NAME}` output, not individual nested tool invocations. The returned content starts with a separate `Script completed` or `Script failed` text item that includes wall time. When truncation happens, the final text may include `Total output lines:` and the usual `…N tokens truncated…` marker.\n",
|
||||
));
|
||||
@@ -348,27 +352,43 @@ fn truncate_code_mode_result(
|
||||
|
||||
async fn build_enabled_tools(exec: &ExecContext) -> Vec<EnabledTool> {
|
||||
let router = build_nested_router(exec).await;
|
||||
let mut out = Vec::new();
|
||||
for spec in router.specs() {
|
||||
let tool_name = spec.name().to_string();
|
||||
if tool_name == PUBLIC_TOOL_NAME {
|
||||
continue;
|
||||
}
|
||||
|
||||
let reference = code_mode_tool_reference(&tool_name);
|
||||
|
||||
out.push(EnabledTool {
|
||||
tool_name,
|
||||
namespace: reference.namespace,
|
||||
name: reference.tool_key,
|
||||
kind: tool_kind_for_spec(&spec),
|
||||
});
|
||||
}
|
||||
let mut out = router
|
||||
.specs()
|
||||
.into_iter()
|
||||
.map(|spec| augment_tool_spec_for_code_mode(spec, true))
|
||||
.filter_map(enabled_tool_from_spec)
|
||||
.collect::<Vec<_>>();
|
||||
out.sort_by(|left, right| left.tool_name.cmp(&right.tool_name));
|
||||
out.dedup_by(|left, right| left.tool_name == right.tool_name);
|
||||
out
|
||||
}
|
||||
|
||||
fn enabled_tool_from_spec(spec: ToolSpec) -> Option<EnabledTool> {
|
||||
let tool_name = spec.name().to_string();
|
||||
if tool_name == PUBLIC_TOOL_NAME {
|
||||
return None;
|
||||
}
|
||||
|
||||
let reference = code_mode_tool_reference(&tool_name);
|
||||
|
||||
let (description, kind) = match spec {
|
||||
ToolSpec::Function(tool) => (tool.description, CodeModeToolKind::Function),
|
||||
ToolSpec::Freeform(tool) => (tool.description, CodeModeToolKind::Freeform),
|
||||
ToolSpec::LocalShell {} | ToolSpec::ImageGeneration { .. } | ToolSpec::WebSearch { .. } => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(EnabledTool {
|
||||
tool_name,
|
||||
module_path: reference.module_path,
|
||||
namespace: reference.namespace,
|
||||
name: reference.tool_key,
|
||||
description,
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
let nested_tools_config = exec.turn.tools_config.for_code_mode_nested_tools();
|
||||
let mcp_tools = exec
|
||||
|
||||
@@ -55,13 +55,6 @@ globalThis.add_content = (value) => {
|
||||
return contentItems;
|
||||
};
|
||||
|
||||
globalThis.tools = new Proxy(Object.create(null), {
|
||||
get(_target, prop) {
|
||||
const name = String(prop);
|
||||
return async (args) => __codex_tool_call(name, args);
|
||||
},
|
||||
});
|
||||
|
||||
globalThis.console = Object.freeze({
|
||||
log() {},
|
||||
info() {},
|
||||
@@ -71,7 +64,7 @@ globalThis.console = Object.freeze({
|
||||
});
|
||||
|
||||
for (const name of __codexEnabledToolNames) {
|
||||
if (/^[A-Za-z_$][0-9A-Za-z_$]*$/.test(name) && !(name in globalThis)) {
|
||||
if (!(name in globalThis)) {
|
||||
Object.defineProperty(globalThis, name, {
|
||||
value: async (args) => __codex_tool_call(name, args),
|
||||
configurable: true,
|
||||
|
||||
@@ -75,11 +75,9 @@ fn append_code_mode_sample(
|
||||
output_type: String,
|
||||
) -> String {
|
||||
let reference = code_mode_tool_reference(tool_name);
|
||||
let local_name = code_mode_local_name(&reference.tool_key);
|
||||
|
||||
format!(
|
||||
"{description}\n\nCode mode declaration:\n```ts\nimport {{ tools }} from \"{}\";\ndeclare function {local_name}({input_name}: {input_type}): Promise<{output_type}>;\n```",
|
||||
reference.module_path
|
||||
"{description}\n\nCode mode declaration:\n```ts\nimport {{ {} }} from \"{}\";\ndeclare function {}({input_name}: {input_type}): Promise<{output_type}>;\n```",
|
||||
reference.tool_key, reference.module_path, reference.tool_key
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,22 +98,6 @@ fn code_mode_local_name(tool_key: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
if identifier.is_empty() {
|
||||
return "tool_call".to_string();
|
||||
}
|
||||
|
||||
if identifier == "tools" {
|
||||
identifier.push_str("_tool");
|
||||
}
|
||||
|
||||
if identifier
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|ch| ch.is_ascii_digit())
|
||||
{
|
||||
identifier.insert(0, '_');
|
||||
}
|
||||
|
||||
identifier
|
||||
}
|
||||
|
||||
|
||||
@@ -108,10 +108,6 @@ function formatErrorText(error) {
|
||||
return String(error && error.stack ? error.stack : error);
|
||||
}
|
||||
|
||||
function isValidIdentifier(name) {
|
||||
return /^[A-Za-z_$][0-9A-Za-z_$]*$/.test(name);
|
||||
}
|
||||
|
||||
function cloneJsonValue(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
@@ -139,12 +135,25 @@ function createToolsNamespace(callTool, enabledTools) {
|
||||
return Object.freeze(tools);
|
||||
}
|
||||
|
||||
function createAllToolsMetadata(enabledTools) {
|
||||
return Object.freeze(
|
||||
enabledTools.map(({ module: modulePath, name, description }) =>
|
||||
Object.freeze({
|
||||
module: modulePath,
|
||||
name,
|
||||
description,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function createToolsModule(context, callTool, enabledTools) {
|
||||
const tools = createToolsNamespace(callTool, enabledTools);
|
||||
const exportNames = ['tools'];
|
||||
const allTools = createAllToolsMetadata(enabledTools);
|
||||
const exportNames = ['ALL_TOOLS'];
|
||||
|
||||
for (const { tool_name } of enabledTools) {
|
||||
if (tool_name !== 'tools' && isValidIdentifier(tool_name)) {
|
||||
if (tool_name !== 'ALL_TOOLS') {
|
||||
exportNames.push(tool_name);
|
||||
}
|
||||
}
|
||||
@@ -154,9 +163,9 @@ function createToolsModule(context, callTool, enabledTools) {
|
||||
return new SyntheticModule(
|
||||
uniqueExportNames,
|
||||
function initToolsModule() {
|
||||
this.setExport('tools', tools);
|
||||
this.setExport('ALL_TOOLS', allTools);
|
||||
for (const exportName of uniqueExportNames) {
|
||||
if (exportName !== 'tools') {
|
||||
if (exportName !== 'ALL_TOOLS') {
|
||||
this.setExport(exportName, tools[exportName]);
|
||||
}
|
||||
}
|
||||
@@ -283,10 +292,10 @@ function createNamespacedToolsNamespace(callTool, enabledTools, namespace) {
|
||||
|
||||
function createNamespacedToolsModule(context, callTool, enabledTools, namespace) {
|
||||
const tools = createNamespacedToolsNamespace(callTool, enabledTools, namespace);
|
||||
const exportNames = ['tools'];
|
||||
const exportNames = [];
|
||||
|
||||
for (const exportName of Object.keys(tools)) {
|
||||
if (exportName !== 'tools' && isValidIdentifier(exportName)) {
|
||||
if (exportName !== 'ALL_TOOLS') {
|
||||
exportNames.push(exportName);
|
||||
}
|
||||
}
|
||||
@@ -296,11 +305,8 @@ function createNamespacedToolsModule(context, callTool, enabledTools, namespace)
|
||||
return new SyntheticModule(
|
||||
uniqueExportNames,
|
||||
function initNamespacedToolsModule() {
|
||||
this.setExport('tools', tools);
|
||||
for (const exportName of uniqueExportNames) {
|
||||
if (exportName !== 'tools') {
|
||||
this.setExport(exportName, tools[exportName]);
|
||||
}
|
||||
this.setExport(exportName, tools[exportName]);
|
||||
}
|
||||
},
|
||||
{ context }
|
||||
|
||||
@@ -1622,7 +1622,7 @@ source: /[\s\S]+/
|
||||
enabled_tool_names.join(", ")
|
||||
};
|
||||
let description = format!(
|
||||
"Runs JavaScript in a Node-backed `node:vm` context. This is a freeform tool: send raw JavaScript source text (no JSON/quotes/markdown fences). Direct tool calls remain available while `{PUBLIC_TOOL_NAME}` is enabled. Inside JavaScript, import nested tools from `tools.js`, for example `import {{ exec_command }} from \"tools.js\"` or `import {{ tools }} from \"tools.js\"`. Namespaced tools are also available from `tools/<namespace...>.js`; MCP tools use `tools/mcp/<server>.js`, for example `import {{ append_notebook_logs_chart }} from \"tools/mcp/ologs.js\"`. `tools[name]` and identifier wrappers like `await shell(args)` remain available for compatibility when the tool name is a valid JS identifier. Nested tool calls resolve to their code-mode result values. Import `{{ output_text, output_image, set_max_output_tokens_per_exec_call, store, load }}` from `\"@openai/code_mode\"` (or `\"openai/code_mode\"`); `output_text(value)` surfaces text back to the model and stringifies non-string objects when possible, `output_image(imageUrl)` appends an `input_image` content item for `http(s)` or `data:` URLs, `store(key, value)` persists JSON-serializable values across `{PUBLIC_TOOL_NAME}` calls in the current session, `load(key)` returns a cloned stored value or `undefined`, and `set_max_output_tokens_per_exec_call(value)` sets the token budget used to truncate the final Rust-side result of the current `{PUBLIC_TOOL_NAME}` execution. The default is `10000`. This guards the overall `{PUBLIC_TOOL_NAME}` output, not individual nested tool invocations. The returned content starts with a separate `Script completed` or `Script failed` text item that includes wall time. When truncation happens, the final text may include `Total output lines:` and the usual `…N tokens truncated…` marker. Function tools require JSON object arguments. Freeform tools require raw strings. `add_content(value)` remains available for compatibility with a content item, content-item array, or string. Structured nested-tool results should be converted to text first, for example with `JSON.stringify(...)`. Only content passed to `output_text(...)`, `output_image(...)`, or `add_content(value)` is surfaced back to the model. Enabled nested tools: {enabled_list}."
|
||||
"Runs JavaScript in a Node-backed `node:vm` context. This is a freeform tool: send raw JavaScript source text (no JSON/quotes/markdown fences). Direct tool calls remain available while `{PUBLIC_TOOL_NAME}` is enabled. Inside JavaScript, import nested tools from `tools.js`, for example `import {{ exec_command }} from \"tools.js\"` or `import {{ ALL_TOOLS }} from \"tools.js\"` to inspect the available `{{ module, name, description }}` entries. Namespaced tools are also available from `tools/<namespace...>.js`; MCP tools use `tools/mcp/<server>.js`, for example `import {{ append_notebook_logs_chart }} from \"tools/mcp/ologs.js\"`. Nested tool calls resolve to their code-mode result values. Import `{{ output_text, output_image, set_max_output_tokens_per_exec_call, store, load }}` from `\"@openai/code_mode\"` (or `\"openai/code_mode\"`); `output_text(value)` surfaces text back to the model and stringifies non-string objects when possible, `output_image(imageUrl)` appends an `input_image` content item for `http(s)` or `data:` URLs, `store(key, value)` persists JSON-serializable values across `{PUBLIC_TOOL_NAME}` calls in the current session, `load(key)` returns a cloned stored value or `undefined`, and `set_max_output_tokens_per_exec_call(value)` sets the token budget used to truncate the final Rust-side result of the current `{PUBLIC_TOOL_NAME}` execution. The default is `10000`. This guards the overall `{PUBLIC_TOOL_NAME}` output, not individual nested tool invocations. The returned content starts with a separate `Script completed` or `Script failed` text item that includes wall time. When truncation happens, the final text may include `Total output lines:` and the usual `…N tokens truncated…` marker. Function tools require JSON object arguments. Freeform tools require raw strings. `add_content(value)` remains available for compatibility with a content item, content-item array, or string. Structured nested-tool results should be converted to text first, for example with `JSON.stringify(...)`. Only content passed to `output_text(...)`, `output_image(...)`, or `add_content(value)` is surfaced back to the model. Enabled nested tools: {enabled_list}."
|
||||
);
|
||||
|
||||
ToolSpec::Freeform(FreeformTool {
|
||||
@@ -1636,6 +1636,10 @@ source: /[\s\S]+/
|
||||
})
|
||||
}
|
||||
|
||||
fn is_code_mode_nested_tool(spec: &ToolSpec) -> bool {
|
||||
spec.name() != PUBLIC_TOOL_NAME && matches!(spec, ToolSpec::Function(_) | ToolSpec::Freeform(_))
|
||||
}
|
||||
|
||||
fn create_list_mcp_resources_tool() -> ToolSpec {
|
||||
let properties = BTreeMap::from([
|
||||
(
|
||||
@@ -2041,8 +2045,9 @@ pub(crate) fn build_specs(
|
||||
.build();
|
||||
let mut enabled_tool_names = nested_specs
|
||||
.into_iter()
|
||||
.map(|spec| spec.spec.name().to_string())
|
||||
.filter(|name| name != PUBLIC_TOOL_NAME)
|
||||
.map(|spec| spec.spec)
|
||||
.filter(is_code_mode_nested_tool)
|
||||
.map(|spec| spec.name().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
enabled_tool_names.sort();
|
||||
enabled_tool_names.dedup();
|
||||
@@ -4379,7 +4384,7 @@ Examples of valid command strings:
|
||||
|
||||
assert_eq!(
|
||||
description,
|
||||
"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nCode mode declaration:\n```ts\nimport { tools } from \"tools.js\";\ndeclare function view_image(args: {\n path: string;\n}): Promise<unknown>;\n```"
|
||||
"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nCode mode declaration:\n```ts\nimport { view_image } from \"tools.js\";\ndeclare function view_image(args: {\n path: string;\n}): Promise<unknown>;\n```"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4428,7 +4433,7 @@ Examples of valid command strings:
|
||||
|
||||
assert_eq!(
|
||||
description,
|
||||
"Echo text\n\nCode mode declaration:\n```ts\nimport { tools } from \"tools/mcp/sample.js\";\ndeclare function echo(args: {\n message: string;\n}): Promise<{\n _meta?: unknown;\n content: Array<unknown>;\n isError?: boolean;\n structuredContent?: unknown;\n}>;\n```"
|
||||
"Echo text\n\nCode mode declaration:\n```ts\nimport { echo } from \"tools/mcp/sample.js\";\ndeclare function echo(args: {\n message: string;\n}): Promise<{\n _meta?: unknown;\n content: Array<unknown>;\n isError?: boolean;\n structuredContent?: unknown;\n}>;\n```"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user