fix: add normalization for Claude passthrough bodies

This commit is contained in:
decolua
2026-06-08 15:37:01 +07:00
parent f8b73faf5d
commit 4443903900
2 changed files with 49 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
import { detectFormat, getTargetFormat } from "../services/provider.js";
import { translateRequest } from "../translator/index.js";
import { FORMATS } from "../translator/formats.js";
import { normalizeClaudePassthrough } from "../translator/helpers/claudeHelper.js";
import { COLORS } from "../utils/stream.js";
import { createStreamController } from "../utils/streamHandler.js";
import { refreshWithRetry } from "../services/tokenRefresh.js";
@@ -91,6 +92,8 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
if (passthrough) {
log?.debug?.("PASSTHROUGH", `${clientTool}${provider} | native lossless`);
translatedBody = { ...body, model: upstreamModel };
// Normalize newer Cowork/CC beta shapes (adaptive thinking, mid-conversation system) the API rejects
if (clientTool === "claude") normalizeClaudePassthrough(translatedBody, upstreamModel);
} else {
translatedBody = translateRequest(sourceFormat, targetFormat, upstreamModel, body, stream, credentials, provider, reqLogger, stripList, connectionId, clientTool);
if (!translatedBody) {

View File

@@ -76,6 +76,52 @@ export function fixToolUseOrdering(messages) {
return merged;
}
// Models that reject thinking.type "adaptive" (only Sonnet/Opus support it)
const ADAPTIVE_THINKING_UNSUPPORTED = /haiku/i;
// Normalize a native Claude passthrough body to match Anthropic Messages API spec.
// Newer Cowork/Claude Code clients emit beta-only shapes that OAuth endpoints reject:
// 1. thinking.type "adaptive" → unsupported on Haiku
// 2. role "system" messages (mid-conversation-system beta) → only top-level system is allowed
export function normalizeClaudePassthrough(body, model = "") {
if (!body || typeof body !== "object") return body;
// 1. Downgrade adaptive thinking for models that don't support it
if (body.thinking?.type === "adaptive" && ADAPTIVE_THINKING_UNSUPPORTED.test(model)) {
body.thinking = { type: "enabled", budget_tokens: 10000 };
}
// 2. Hoist mid-conversation system messages into the top-level system field
if (Array.isArray(body.messages)) {
const systemBlocks = [];
const messages = [];
for (const msg of body.messages) {
if (msg.role === "system") {
const text = typeof msg.content === "string"
? msg.content
: Array.isArray(msg.content)
? msg.content.map(b => (typeof b === "string" ? b : b?.text || "")).join("\n")
: "";
if (text.trim()) systemBlocks.push({ type: "text", text });
continue;
}
messages.push(msg);
}
if (systemBlocks.length > 0) {
const existing = Array.isArray(body.system)
? body.system
: typeof body.system === "string" && body.system.trim()
? [{ type: "text", text: body.system }]
: [];
body.system = [...existing, ...systemBlocks];
body.messages = messages;
}
}
return body;
}
const CLAUDE_FORMAT_PROVIDERS_WITHOUT_OUTPUT_CONFIG = new Set(["minimax", "minimax-cn"]);
// Prepare request for Claude format endpoints