mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Fix MCP tool calling (#14491)
Properly escape mcp tool names and make tools only available via imports.
This commit is contained in:
committed by
GitHub
Unverified
parent
a5a4899d0c
commit
dadffd27d4
@@ -1,43 +1,8 @@
|
||||
const __codexEnabledTools = __CODE_MODE_ENABLED_TOOLS_PLACEHOLDER__;
|
||||
const __codexEnabledToolNames = __codexEnabledTools.map((tool) => tool.tool_name);
|
||||
const __codexContentItems = Array.isArray(globalThis.__codexContentItems)
|
||||
? globalThis.__codexContentItems
|
||||
: [];
|
||||
|
||||
function __codexCloneContentItem(item) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
throw new TypeError('content item must be an object');
|
||||
}
|
||||
switch (item.type) {
|
||||
case 'input_text':
|
||||
if (typeof item.text !== 'string') {
|
||||
throw new TypeError('content item "input_text" requires a string text field');
|
||||
}
|
||||
return { type: 'input_text', text: item.text };
|
||||
case 'input_image':
|
||||
if (typeof item.image_url !== 'string') {
|
||||
throw new TypeError('content item "input_image" requires a string image_url field');
|
||||
}
|
||||
return { type: 'input_image', image_url: item.image_url };
|
||||
default:
|
||||
throw new TypeError(`unsupported content item type "${item.type}"`);
|
||||
}
|
||||
}
|
||||
|
||||
function __codexNormalizeRawContentItems(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.flatMap((entry) => __codexNormalizeRawContentItems(entry));
|
||||
}
|
||||
return [__codexCloneContentItem(value)];
|
||||
}
|
||||
|
||||
function __codexNormalizeContentItems(value) {
|
||||
if (typeof value === 'string') {
|
||||
return [{ type: 'input_text', text: value }];
|
||||
}
|
||||
return __codexNormalizeRawContentItems(value);
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, '__codexContentItems', {
|
||||
value: __codexContentItems,
|
||||
configurable: true,
|
||||
@@ -45,33 +10,54 @@ Object.defineProperty(globalThis, '__codexContentItems', {
|
||||
writable: false,
|
||||
});
|
||||
|
||||
globalThis.codex = {
|
||||
enabledTools: Object.freeze(__codexEnabledToolNames.slice()),
|
||||
};
|
||||
|
||||
globalThis.add_content = (value) => {
|
||||
const contentItems = __codexNormalizeContentItems(value);
|
||||
__codexContentItems.push(...contentItems);
|
||||
return contentItems;
|
||||
};
|
||||
|
||||
globalThis.console = Object.freeze({
|
||||
log() {},
|
||||
info() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
debug() {},
|
||||
});
|
||||
|
||||
for (const name of __codexEnabledToolNames) {
|
||||
if (!(name in globalThis)) {
|
||||
Object.defineProperty(globalThis, name, {
|
||||
value: async (args) => __codex_tool_call(name, args),
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
});
|
||||
(() => {
|
||||
function cloneContentItem(item) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
throw new TypeError('content item must be an object');
|
||||
}
|
||||
switch (item.type) {
|
||||
case 'input_text':
|
||||
if (typeof item.text !== 'string') {
|
||||
throw new TypeError('content item "input_text" requires a string text field');
|
||||
}
|
||||
return { type: 'input_text', text: item.text };
|
||||
case 'input_image':
|
||||
if (typeof item.image_url !== 'string') {
|
||||
throw new TypeError('content item "input_image" requires a string image_url field');
|
||||
}
|
||||
return { type: 'input_image', image_url: item.image_url };
|
||||
default:
|
||||
throw new TypeError(`unsupported content item type "${item.type}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRawContentItems(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.flatMap((entry) => normalizeRawContentItems(entry));
|
||||
}
|
||||
return [cloneContentItem(value)];
|
||||
}
|
||||
|
||||
function normalizeContentItems(value) {
|
||||
if (typeof value === 'string') {
|
||||
return [{ type: 'input_text', text: value }];
|
||||
}
|
||||
return normalizeRawContentItems(value);
|
||||
}
|
||||
|
||||
globalThis.add_content = (value) => {
|
||||
const contentItems = normalizeContentItems(value);
|
||||
__codexContentItems.push(...contentItems);
|
||||
return contentItems;
|
||||
};
|
||||
|
||||
globalThis.console = Object.freeze({
|
||||
log() {},
|
||||
info() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
debug() {},
|
||||
});
|
||||
})();
|
||||
|
||||
__CODE_MODE_USER_CODE_PLACEHOLDER__
|
||||
|
||||
@@ -16,4 +16,3 @@
|
||||
- `set_max_output_tokens_per_exec_call(value)`: sets the token budget for direct `exec` results. By default the result is truncated to 10000 tokens.
|
||||
- `set_yield_time(value)`: asks `exec` to yield early after that many milliseconds if the script is still running.
|
||||
- `yield_control()`: yields the accumulated output to the model immediately while the script keeps running.
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::codex::TurnContext;
|
||||
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::code_mode_description::normalize_code_mode_identifier;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
@@ -233,10 +234,11 @@ fn enabled_tool_from_spec(spec: ToolSpec) -> Option<protocol::EnabledTool> {
|
||||
};
|
||||
|
||||
Some(protocol::EnabledTool {
|
||||
global_name: normalize_code_mode_identifier(&tool_name),
|
||||
tool_name,
|
||||
module_path: reference.module_path,
|
||||
namespace: reference.namespace,
|
||||
name: reference.tool_key,
|
||||
name: normalize_code_mode_identifier(&reference.tool_key),
|
||||
description,
|
||||
kind,
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(super) enum CodeModeToolKind {
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub(super) struct EnabledTool {
|
||||
pub(super) tool_name: String,
|
||||
pub(super) global_name: String,
|
||||
#[serde(rename = "module")]
|
||||
pub(super) module_path: String,
|
||||
pub(super) namespace: Vec<String>,
|
||||
|
||||
@@ -134,8 +134,8 @@ function codeModeWorkerMain() {
|
||||
function createToolsNamespace(callTool, enabledTools) {
|
||||
const tools = Object.create(null);
|
||||
|
||||
for (const { tool_name } of enabledTools) {
|
||||
Object.defineProperty(tools, tool_name, {
|
||||
for (const { tool_name, global_name } of enabledTools) {
|
||||
Object.defineProperty(tools, global_name, {
|
||||
value: async (args) => callTool(tool_name, args),
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
@@ -163,9 +163,9 @@ function codeModeWorkerMain() {
|
||||
const allTools = createAllToolsMetadata(enabledTools);
|
||||
const exportNames = ['ALL_TOOLS'];
|
||||
|
||||
for (const { tool_name } of enabledTools) {
|
||||
if (tool_name !== 'ALL_TOOLS') {
|
||||
exportNames.push(tool_name);
|
||||
for (const { global_name } of enabledTools) {
|
||||
if (global_name !== 'ALL_TOOLS') {
|
||||
exportNames.push(global_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +382,24 @@ function codeModeWorkerMain() {
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveDynamicModule(specifier, resolveModule) {
|
||||
const module = resolveModule(specifier);
|
||||
|
||||
if (module.status === 'unlinked') {
|
||||
await module.link(resolveModule);
|
||||
}
|
||||
|
||||
if (module.status === 'linked' || module.status === 'evaluating') {
|
||||
await module.evaluate();
|
||||
}
|
||||
|
||||
if (module.status === 'errored') {
|
||||
throw module.error;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
async function runModule(context, start, state, callTool) {
|
||||
const resolveModule = createModuleResolver(
|
||||
context,
|
||||
@@ -392,7 +410,8 @@ function codeModeWorkerMain() {
|
||||
const mainModule = new SourceTextModule(start.source, {
|
||||
context,
|
||||
identifier: 'exec_main.mjs',
|
||||
importModuleDynamically: async (specifier) => resolveModule(specifier),
|
||||
importModuleDynamically: async (specifier) =>
|
||||
resolveDynamicModule(specifier, resolveModule),
|
||||
});
|
||||
|
||||
await mainModule.link(resolveModule);
|
||||
@@ -408,7 +427,6 @@ function codeModeWorkerMain() {
|
||||
const callTool = createToolCaller();
|
||||
const context = vm.createContext({
|
||||
__codexContentItems: createContentItems(),
|
||||
__codex_tool_call: callTool,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user