From 444390390001b9a3021c2a3be2339fa734394053 Mon Sep 17 00:00:00 2001 From: decolua Date: Mon, 8 Jun 2026 15:37:01 +0700 Subject: [PATCH] fix: add normalization for Claude passthrough bodies --- open-sse/handlers/chatCore.js | 3 ++ open-sse/translator/helpers/claudeHelper.js | 46 +++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js index f6d17432..65281cfa 100644 --- a/open-sse/handlers/chatCore.js +++ b/open-sse/handlers/chatCore.js @@ -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) { diff --git a/open-sse/translator/helpers/claudeHelper.js b/open-sse/translator/helpers/claudeHelper.js index 762f5b03..3bdd31db 100644 --- a/open-sse/translator/helpers/claudeHelper.js +++ b/open-sse/translator/helpers/claudeHelper.js @@ -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