icebear0828 Claude Opus 4.6 commited on
Commit
2df0167
·
1 Parent(s): e336506

fix: normalize tool schemas missing `properties` field

Browse files

MCP tools with no parameters send `{"type":"object"}` without a
`properties` key. The Codex/OpenAI backend strictly requires it,
returning 400. Add a shared `normalizeSchema()` helper that injects
`properties: {}` when missing, applied consistently across all four
format converters (OpenAI tools, OpenAI functions, Anthropic, Gemini).

Thanks to @lookvincent for identifying this issue (PR #22).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. CHANGELOG.md +1 -0
  2. src/translation/tool-format.ts +16 -4
CHANGELOG.md CHANGED
@@ -33,6 +33,7 @@
33
 
34
  ### Fixed
35
 
 
36
  - 额度窗口刷新后 Dashboard 仍显示累计 Token:本地计数器从未按窗口重置,现在 `refreshStatus()` 每次 acquire/getAccounts 时检查 `window_reset_at`,过期自动归零窗口计数器
37
  - 空响应重试循环中账号双重释放:外层 catch 使用原始 `entryId` 而非当前活跃账号,导致换号重试失败时 double-release(`proxy-handler.ts`)
38
  - `apply-update.ts` 模型比较不再误报删除:静态提取只含 2 个硬编码模型,与 YAML 的 24 个比较会产生 22 个假删除,现在只报新增
 
33
 
34
  ### Fixed
35
 
36
+ - 工具 schema 缺少 `properties` 字段导致 400 错误:MCP 工具发送 `{"type":"object"}` 无 `properties` 时,Codex 后端拒绝请求;现在所有格式转换器(OpenAI/Anthropic/Gemini)统一注入 `properties: {}`(感谢 @lookvincent 发现此问题,PR #22)
37
  - 额度窗口刷新后 Dashboard 仍显示累计 Token:本地计数器从未按窗口重置,现在 `refreshStatus()` 每次 acquire/getAccounts 时检查 `window_reset_at`,过期自动归零窗口计数器
38
  - 空响应重试循环中账号双重释放:外层 catch 使用原始 `entryId` 而非当前活跃账号,导致换号重试失败时 double-release(`proxy-handler.ts`)
39
  - `apply-update.ts` 模型比较不再误报删除:静态提取只含 2 个硬编码模型,与 YAML 的 24 个比较会产生 22 个假删除,现在只报新增
src/translation/tool-format.ts CHANGED
@@ -9,6 +9,18 @@ import type { ChatCompletionRequest } from "../types/openai.js";
9
  import type { AnthropicMessagesRequest } from "../types/anthropic.js";
10
  import type { GeminiGenerateContentRequest } from "../types/gemini.js";
11
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // ── Codex Responses API tool format ─────────────────────────────
13
 
14
  export interface CodexToolDefinition {
@@ -30,7 +42,7 @@ export function openAIToolsToCodex(
30
  name: t.function.name,
31
  };
32
  if (t.function.description) def.description = t.function.description;
33
- if (t.function.parameters) def.parameters = t.function.parameters;
34
  return def;
35
  });
36
  }
@@ -59,7 +71,7 @@ export function openAIFunctionsToCodex(
59
  name: f.name,
60
  };
61
  if (f.description) def.description = f.description;
62
- if (f.parameters) def.parameters = f.parameters;
63
  return def;
64
  });
65
  }
@@ -75,7 +87,7 @@ export function anthropicToolsToCodex(
75
  name: t.name,
76
  };
77
  if (t.description) def.description = t.description;
78
- if (t.input_schema) def.parameters = t.input_schema;
79
  return def;
80
  });
81
  }
@@ -110,7 +122,7 @@ export function geminiToolsToCodex(
110
  name: fd.name,
111
  };
112
  if (fd.description) def.description = fd.description;
113
- if (fd.parameters) def.parameters = fd.parameters;
114
  defs.push(def);
115
  }
116
  }
 
9
  import type { AnthropicMessagesRequest } from "../types/anthropic.js";
10
  import type { GeminiGenerateContentRequest } from "../types/gemini.js";
11
 
12
+ // ── Helpers ─────────────────────────────────────────────────────
13
+
14
+ /** OpenAI requires `properties` when schema `type` is `"object"`. */
15
+ function normalizeSchema(
16
+ schema: Record<string, unknown>,
17
+ ): Record<string, unknown> {
18
+ if (schema.type === "object" && !("properties" in schema)) {
19
+ return { ...schema, properties: {} };
20
+ }
21
+ return schema;
22
+ }
23
+
24
  // ── Codex Responses API tool format ─────────────────────────────
25
 
26
  export interface CodexToolDefinition {
 
42
  name: t.function.name,
43
  };
44
  if (t.function.description) def.description = t.function.description;
45
+ if (t.function.parameters) def.parameters = normalizeSchema(t.function.parameters);
46
  return def;
47
  });
48
  }
 
71
  name: f.name,
72
  };
73
  if (f.description) def.description = f.description;
74
+ if (f.parameters) def.parameters = normalizeSchema(f.parameters);
75
  return def;
76
  });
77
  }
 
87
  name: t.name,
88
  };
89
  if (t.description) def.description = t.description;
90
+ if (t.input_schema) def.parameters = normalizeSchema(t.input_schema);
91
  return def;
92
  });
93
  }
 
122
  name: fd.name,
123
  };
124
  if (fd.description) def.description = fd.description;
125
+ if (fd.parameters) def.parameters = normalizeSchema(fd.parameters);
126
  defs.push(def);
127
  }
128
  }