diff --git a/open-sse/translator/request/claude-to-openai.ts b/open-sse/translator/request/claude-to-openai.ts index dda41d3aef..cb682b16a7 100644 --- a/open-sse/translator/request/claude-to-openai.ts +++ b/open-sse/translator/request/claude-to-openai.ts @@ -39,7 +39,10 @@ function isClaudeServerWebSearchTool(tool: unknown): tool is JsonRecord { function toStringArray(value: unknown): string[] { if (!Array.isArray(value)) return []; - return value.map((entry) => String(entry || "").trim()).filter(Boolean); + return value + .filter((entry): entry is string => typeof entry === "string") + .map((entry) => entry.trim()) + .filter(Boolean); } function convertClaudeServerWebSearchTool(tool: JsonRecord): JsonRecord { @@ -132,15 +135,17 @@ export function claudeToOpenAIRequest(model, body, stream) { return convertClaudeServerWebSearchTool(tool); } - const name = typeof tool.name === "string" ? tool.name.trim() : ""; + if (!tool || typeof tool !== "object" || Array.isArray(tool)) return null; + const record = tool as JsonRecord; + const name = typeof record.name === "string" ? record.name.trim() : ""; if (!name) return null; // skip tools with empty/invalid name return { type: "function", function: { name, - description: typeof tool.description === "string" ? tool.description : "", // fix: never null (#276) - parameters: normalizeToolSchema(tool.input_schema), + description: typeof record.description === "string" ? record.description : "", // fix: never null (#276) + parameters: normalizeToolSchema(record.input_schema), }, }; }) diff --git a/tests/unit/translator-claude-to-openai.test.ts b/tests/unit/translator-claude-to-openai.test.ts index 5a9e084186..c14bdfccc3 100644 --- a/tests/unit/translator-claude-to-openai.test.ts +++ b/tests/unit/translator-claude-to-openai.test.ts @@ -63,8 +63,8 @@ test("Claude -> OpenAI maps Claude server WebSearch to native Responses web_sear { type: "web_search_20250305", name: "web_search", - allowed_domains: ["docs.anthropic.com", ""], - blocked_domains: ["spam.example"], + allowed_domains: ["docs.anthropic.com", "", 123, { domain: "bad.example" }], + blocked_domains: ["spam.example", false], max_uses: 8, user_location: { type: "approximate", country: "US" }, }, @@ -87,6 +87,28 @@ test("Claude -> OpenAI maps Claude server WebSearch to native Responses web_sear assert.deepEqual(result.tool_choice, { type: "web_search" }); }); +test("Claude -> OpenAI skips invalid tool payloads without crashing", () => { + const result = claudeToOpenAIRequest( + "gpt-4o", + { + messages: [{ role: "user", content: "hi" }], + tools: [null, "bad", 42, [], { name: "", input_schema: { type: "object" } }, { name: "ok" }], + }, + false + ); + + assert.deepEqual(result.tools, [ + { + type: "function", + function: { + name: "ok", + description: "", + parameters: { type: "object", properties: {} }, + }, + }, + ]); +}); + test("Claude -> OpenAI leaves ordinary web_search function tools as functions", () => { const result = claudeToOpenAIRequest( "gpt-4o",