diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 5198a21a7d..873bb3fd82 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1986,12 +1986,13 @@ export async function handleChatCore({ // Use credentials.connectionId as a fallback so that requests without an // explicit session-level connectionId still register in the pendingRequests map. const pendingConnId = connectionId || credentials?.connectionId || null; - const pendingRequestId = trackPendingRequest(model, provider, pendingConnId, true, { - clientEndpoint: clientRawRequest?.endpoint || "/v1/chat/completions", - clientRequest: clientRawRequest?.body ?? body, - providerRequest: initialProviderRequest, - stage: "registered", - }) || generateRequestId(); + const pendingRequestId = + trackPendingRequest(model, provider, pendingConnId, true, { + clientEndpoint: clientRawRequest?.endpoint || "/v1/chat/completions", + clientRequest: clientRawRequest?.body ?? body, + providerRequest: initialProviderRequest, + stage: "registered", + }) || generateRequestId(); // Initialize rate limit settings from persisted DB (once, lazy) await initializeRateLimits(); @@ -2769,7 +2770,10 @@ export async function handleChatCore({ comboTargetLimits, }); contextLimit = resolved.limit; - log?.info?.("CONTEXT", `Combo context limit: ${resolved.limit} (source=${resolved.source})`); + log?.info?.( + "CONTEXT", + `Combo context limit: ${resolved.limit} (source=${resolved.source})` + ); } catch (err) { log?.warn?.("CONTEXT", "Failed to resolve combo limits for compression: " + err); } @@ -3104,6 +3108,12 @@ export async function handleChatCore({ translatedBody.messages, DEFAULT_THINKING_CLAUDE_SIGNATURE ) as typeof translatedBody.messages; + + // Anthropic API rejects requests with both temperature and top_p. + // VS Code Claude extension and similar clients send both; strip top_p. + if (translatedBody.temperature !== undefined && translatedBody.top_p !== undefined) { + delete translatedBody.top_p; + } } // Fix #2468: always extract role:"system" → top-level system. @@ -5442,17 +5452,14 @@ export async function handleChatCore({ } const responseHeaders: Record = { - ...buildStreamingResponseHeaders( - providerResponse.headers, - { - provider, - model, - cacheHit: false, - latencyMs: 0, - usage: null, - costUsd: 0, - } - ), + ...buildStreamingResponseHeaders(providerResponse.headers, { + provider, + model, + cacheHit: false, + latencyMs: 0, + usage: null, + costUsd: 0, + }), "x-omniroute-request-id": pendingRequestId, }; @@ -5560,7 +5567,9 @@ export async function handleChatCore({ }); } catch (e) { // Best-effort — don't break the stream completion path if this fails - try { console.warn("finalizeMostRecentPendingRequest failed:", e && (e.message || e)); } catch {} + try { + console.warn("finalizeMostRecentPendingRequest failed:", e && (e.message || e)); + } catch {} } if (apiKeyInfo?.id && streamUsage) { diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index 2671d7a5e8..7db6062331 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -212,7 +212,7 @@ export function openaiToClaudeRequest(model, body, stream) { if (body.temperature !== undefined) { result.temperature = body.temperature; } - if (body.top_p !== undefined) { + if (body.temperature === undefined && body.top_p !== undefined) { result.top_p = body.top_p; } if (body.stop !== undefined) { diff --git a/tests/unit/translator-openai-to-claude.test.ts b/tests/unit/translator-openai-to-claude.test.ts index 1bf5ca456a..cd819c574f 100644 --- a/tests/unit/translator-openai-to-claude.test.ts +++ b/tests/unit/translator-openai-to-claude.test.ts @@ -84,7 +84,8 @@ test("OpenAI -> Claude maps system messages, parameters and assistant cache mark assert.equal(result.stream, true); assert.equal(result.max_tokens, 33); assert.equal(result.temperature, 0.25); - assert.equal(result.top_p, 0.8); + // top_p is stripped when temperature is also present (Anthropic rejects both). + assert.equal(result.top_p, undefined); assert.deepEqual(result.stop_sequences, ["DONE"]); assert.equal(result.system[0].text, "Rule A\nRule B\nRule C"); assert.equal(result.messages[0].role, "user"); @@ -94,6 +95,21 @@ test("OpenAI -> Claude maps system messages, parameters and assistant cache mark assert.deepEqual(result.messages[1].content[0].cache_control, { type: "ephemeral" }); }); +test("OpenAI -> Claude strips top_p when temperature is also present", () => { + const result = openaiToClaudeRequest( + "claude-4-sonnet", + { + messages: [{ role: "user", content: "Hello" }], + temperature: 0.25, + top_p: 0.8, + }, + false + ); + + assert.equal(result.temperature, 0.25); + assert.equal(result.top_p, undefined); +}); + test("OpenAI -> Claude converts multimodal content, tool declarations, tool calls and tool results", () => { const result = openaiToClaudeRequest( "claude-4-sonnet",