fix(translator): strict Anthropic content block compliance (#2225)

Filter empty text blocks from thoughtSignature-only parts, preserve
tool_calls when functionResponse and functionCall coexist in the same
content, and skip empty regular text parts before they reach Claude.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Sahrul Ramadhan Hardiansyah
2026-07-03 10:53:31 +07:00
committed by decolua
parent 7afaecd617
commit ce6120ce7b
2 changed files with 22 additions and 7 deletions

View File

@@ -138,12 +138,14 @@ function convertContent(content) {
// Text with thoughtSignature = regular text after thinking
if (part.thoughtSignature && part.text !== undefined) {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
if (part.text) {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
}
continue;
}
// Regular text
if (part.text !== undefined) {
if (part.text !== undefined && part.text !== "") {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
}
@@ -180,8 +182,22 @@ function convertContent(content) {
}
}
// Content with only functionResponses return array of tool messages
// Content with functionResponses return array of tool result messages,
// plus an assistant message for any co-located tool calls / text.
if (toolResults.length > 0) {
if (toolCalls.length > 0 || textParts.length > 0 || reasoningContent) {
const assistantMsg = { role: ROLE.ASSISTANT };
if (textParts.length > 0) {
assistantMsg.content = collapseTextParts(textParts);
}
if (reasoningContent) {
assistantMsg.reasoning_content = reasoningContent;
}
if (toolCalls.length > 0) {
assistantMsg.tool_calls = toolCalls;
}
return [...toolResults, assistantMsg];
}
return toolResults;
}

View File

@@ -9,10 +9,9 @@ const AG2O = (req) =>
translateRequest(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, "m", { request: req }, true, null, null);
describe("Antigravity → OpenAI", () => {
// antigravity-to-openai.js:177-189 — content with BOTH functionResponse and functionCall/text
// returns toolResults early → drops the tool calls / text.
// KNOWN BUG
it.fails("functionResponse + functionCall in same content keeps both", () => {
// antigravity-to-openai.js — content with BOTH functionResponse and functionCall/text
// previously returned toolResults early → dropped tool calls / text (fixed in #2225)
it("functionResponse + functionCall in same content keeps both", () => {
const out = AG2O({
contents: [{
role: "model",