diff --git a/Dockerfile b/Dockerfile index e5c61740e8..3ab0adf7fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,8 @@ RUN mkdir -p /app/data COPY --from=builder /app/public ./public COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/.next/standalone ./ +# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime +COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs COPY --from=builder /app/scripts/healthcheck.mjs ./healthcheck.mjs diff --git a/open-sse/translator/helpers/claudeHelper.ts b/open-sse/translator/helpers/claudeHelper.ts index 0560352ecf..bf8c42d71e 100644 --- a/open-sse/translator/helpers/claudeHelper.ts +++ b/open-sse/translator/helpers/claudeHelper.ts @@ -127,14 +127,24 @@ export function prepareClaudeRequest(body, provider = null) { } // Pass 1.4: Filter out tool_use blocks with empty names (causes Claude 400 error) + // Apply to ALL roles (assistant tool_use + any user messages that may carry tool_use) + // Also filter tool_result blocks with missing tool_use_id for (const msg of filtered) { - if (msg.role === "assistant" && Array.isArray(msg.content)) { + if (Array.isArray(msg.content)) { msg.content = msg.content.filter( - (block) => block.type !== "tool_use" || (block.name && block.name.trim()) + (block) => block.type !== "tool_use" || (block.name && block.name?.trim()) + ); + msg.content = msg.content.filter( + (block) => block.type !== "tool_result" || block.tool_use_id ); } } + // Also filter top-level tool declarations with empty names + if (body.tools && Array.isArray(body.tools)) { + body.tools = body.tools.filter((tool) => tool.name && tool.name?.trim()); + } + // Pass 1.5: Fix tool_use/tool_result ordering // Each tool_use must have tool_result in the NEXT message (not same message with other content) filtered = fixToolUseOrdering(filtered); diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index 51b5331aeb..a5757440cf 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -175,6 +175,9 @@ export function openaiToClaudeRequest(model, body, stream) { }; }); + // Filter out tools with empty names (would cause Claude 400 error) + result.tools = result.tools.filter((tool) => tool.name && tool.name?.trim()); + // Add cache_control to last tool that doesn't have defer_loading // Tools with defer_loading=true cannot have cache_control (API rejects it) for (let i = result.tools.length - 1; i >= 0; i--) { @@ -227,6 +230,8 @@ function getContentBlocksFromMessage(msg, toolNameMap = new Map(), disableToolPr if (part.type === "text" && part.text) { blocks.push({ type: "text", text: part.text }); } else if (part.type === "tool_result") { + // Skip tool_result with no tool_use_id (would be useless and may cause errors) + if (!part.tool_use_id) continue; blocks.push({ type: "tool_result", tool_use_id: part.tool_use_id,