mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 04:42:10 +03:00
fix: gate Claude WebSearch native mapping by target format
Only translate Claude Code web_search_YYYYMMDD server tools to native Responses web_search when the final target is OpenAI Responses. Keep the Chat Completions target on function-tool shape and cover the full translateRequest path.
This commit is contained in:
@@ -183,12 +183,15 @@ export function translateRequest(
|
||||
const toOpenAI = getRequestTranslator(sourceFormat, FORMATS.OPENAI);
|
||||
if (toOpenAI) {
|
||||
// Forward Copilot UA marker to source→openai translators only.
|
||||
const step1Credentials = options?.copilotClient
|
||||
? {
|
||||
...(credentials && typeof credentials === "object" ? credentials : {}),
|
||||
_copilotClient: true,
|
||||
}
|
||||
: credentials;
|
||||
const hasTargetHint = targetFormat != null;
|
||||
const step1Credentials =
|
||||
options?.copilotClient || hasTargetHint
|
||||
? {
|
||||
...(credentials && typeof credentials === "object" ? credentials : {}),
|
||||
...(options?.copilotClient ? { _copilotClient: true } : {}),
|
||||
...(hasTargetHint ? { _targetFormat: targetFormat } : {}),
|
||||
}
|
||||
: credentials;
|
||||
result = toOpenAI(model, result, stream, step1Credentials);
|
||||
// Log OpenAI intermediate format
|
||||
reqLogger?.logOpenAIRequest?.(result);
|
||||
|
||||
@@ -65,8 +65,17 @@ function hasClaudeServerWebSearchTool(tools: unknown): boolean {
|
||||
return Array.isArray(tools) && tools.some((tool) => isClaudeServerWebSearchTool(tool));
|
||||
}
|
||||
|
||||
function shouldUseNativeResponsesWebSearch(credentials: unknown): boolean {
|
||||
return (
|
||||
credentials !== null &&
|
||||
typeof credentials === "object" &&
|
||||
!Array.isArray(credentials) &&
|
||||
(credentials as JsonRecord)._targetFormat === FORMATS.OPENAI_RESPONSES
|
||||
);
|
||||
}
|
||||
|
||||
// Convert Claude request to OpenAI format
|
||||
export function claudeToOpenAIRequest(model, body, stream) {
|
||||
export function claudeToOpenAIRequest(model, body, stream, credentials: unknown = null) {
|
||||
const result: {
|
||||
model: string;
|
||||
messages: JsonRecord[];
|
||||
@@ -127,11 +136,13 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
// Fix missing tool responses - OpenAI requires every tool_call to have a response
|
||||
fixMissingToolResponses(result.messages);
|
||||
|
||||
const useNativeResponsesWebSearch = shouldUseNativeResponsesWebSearch(credentials);
|
||||
|
||||
// Tools
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
const normalizedTools = body.tools
|
||||
.map((tool) => {
|
||||
if (isClaudeServerWebSearchTool(tool)) {
|
||||
if (useNativeResponsesWebSearch && isClaudeServerWebSearchTool(tool)) {
|
||||
return convertClaudeServerWebSearchTool(tool);
|
||||
}
|
||||
|
||||
@@ -160,7 +171,7 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
if (body.tool_choice) {
|
||||
result.tool_choice = convertToolChoice(
|
||||
body.tool_choice,
|
||||
hasClaudeServerWebSearchTool(body.tools)
|
||||
useNativeResponsesWebSearch && hasClaudeServerWebSearchTool(body.tools)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
|
||||
|
||||
const { claudeToOpenAIRequest } =
|
||||
await import("../../open-sse/translator/request/claude-to-openai.ts");
|
||||
const { translateRequest } = await import("../../open-sse/translator/index.ts");
|
||||
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
|
||||
|
||||
test("Claude -> OpenAI maps system blocks, parameters, tool declarations and tool choice", () => {
|
||||
const result = claudeToOpenAIRequest(
|
||||
@@ -71,7 +73,8 @@ test("Claude -> OpenAI maps Claude server WebSearch to native Responses web_sear
|
||||
],
|
||||
tool_choice: { type: "tool", name: "web_search" },
|
||||
},
|
||||
true
|
||||
true,
|
||||
{ _targetFormat: FORMATS.OPENAI_RESPONSES }
|
||||
);
|
||||
|
||||
assert.deepEqual(result.tools, [
|
||||
@@ -87,6 +90,46 @@ test("Claude -> OpenAI maps Claude server WebSearch to native Responses web_sear
|
||||
assert.deepEqual(result.tool_choice, { type: "web_search" });
|
||||
});
|
||||
|
||||
test("translateRequest maps Claude server WebSearch natively only for Responses targets", () => {
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "Search docs" }],
|
||||
tools: [{ type: "web_search_20250305", name: "web_search" }],
|
||||
tool_choice: { type: "tool", name: "web_search" },
|
||||
};
|
||||
|
||||
const responses = translateRequest(
|
||||
FORMATS.CLAUDE,
|
||||
FORMATS.OPENAI_RESPONSES,
|
||||
"gpt-5.5",
|
||||
structuredClone(body),
|
||||
true
|
||||
);
|
||||
assert.deepEqual(responses.tools, [{ type: "web_search" }]);
|
||||
assert.deepEqual(responses.tool_choice, { type: "web_search" });
|
||||
|
||||
const chat = translateRequest(
|
||||
FORMATS.CLAUDE,
|
||||
FORMATS.OPENAI,
|
||||
"gpt-4o",
|
||||
structuredClone(body),
|
||||
true
|
||||
);
|
||||
assert.deepEqual(chat.tools, [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "web_search",
|
||||
description: "",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(chat.tool_choice, {
|
||||
type: "function",
|
||||
function: { name: "web_search" },
|
||||
});
|
||||
});
|
||||
|
||||
test("Claude -> OpenAI skips invalid tool payloads without crashing", () => {
|
||||
const result = claudeToOpenAIRequest(
|
||||
"gpt-4o",
|
||||
|
||||
Reference in New Issue
Block a user