fix(translator): don't drop image-only user messages in prepareClaudeRequest
hasValidContent() only treated text/tool_use/tool_result blocks as valid content, so a user message containing only an image block was filtered out as empty. When it was the only non-system message, this left an empty messages array and Anthropic rejected the request.
This commit is contained in:
parent
646b3b9ba3
commit
a7941ddab4
2 changed files with 24 additions and 1 deletions
|
|
@ -16,7 +16,9 @@ export function hasValidContent(msg) {
|
||||||
return msg.content.some(block =>
|
return msg.content.some(block =>
|
||||||
(block.type === CLAUDE_BLOCK.TEXT && block.text?.trim()) ||
|
(block.type === CLAUDE_BLOCK.TEXT && block.text?.trim()) ||
|
||||||
block.type === CLAUDE_BLOCK.TOOL_USE ||
|
block.type === CLAUDE_BLOCK.TOOL_USE ||
|
||||||
block.type === CLAUDE_BLOCK.TOOL_RESULT
|
block.type === CLAUDE_BLOCK.TOOL_RESULT ||
|
||||||
|
block.type === CLAUDE_BLOCK.IMAGE ||
|
||||||
|
block.type === CLAUDE_BLOCK.DOCUMENT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,27 @@ describe("OpenAI → Claude context mapping", () => {
|
||||||
expect(JSON.stringify(out), "remote image dropped").toContain("pic.png");
|
expect(JSON.stringify(out), "remote image dropped").toContain("pic.png");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// claude.js hasValidContent() — a user message whose content is only an
|
||||||
|
// image (no text block) was filtered out by prepareClaudeRequest's
|
||||||
|
// "drop empty messages" pass, so an image-only turn (e.g. a vision
|
||||||
|
// describe request with no accompanying prompt text in the user message)
|
||||||
|
// produced an empty `messages` array and Anthropic rejected the request
|
||||||
|
// with "messages: at least one message is required".
|
||||||
|
it("user message with only an image is not dropped as empty", () => {
|
||||||
|
const out = T({
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: "Describe the image." },
|
||||||
|
{ role: "user", content: [
|
||||||
|
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAA" } },
|
||||||
|
] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(out.messages.length, "image-only user message was dropped").toBeGreaterThan(0);
|
||||||
|
expect(out.messages[0].content).toEqual(
|
||||||
|
expect.arrayContaining([expect.objectContaining({ type: "image" })])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// prepareClaudeRequest reconciles max_tokens vs thinking.budget_tokens.
|
// prepareClaudeRequest reconciles max_tokens vs thinking.budget_tokens.
|
||||||
// applyThinking runs after adjustMaxTokens caps max_tokens, so a claude-budget
|
// applyThinking runs after adjustMaxTokens caps max_tokens, so a claude-budget
|
||||||
// model at "max" effort (budget 128000) can exceed the clamped max_tokens and
|
// model at "max" effort (budget 128000) can exceed the clamped max_tokens and
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue