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:
@@ -16,7 +16,9 @@ export function hasValidContent(msg) {
|
||||
return msg.content.some(block =>
|
||||
(block.type === CLAUDE_BLOCK.TEXT && block.text?.trim()) ||
|
||||
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;
|
||||
|
||||
@@ -67,6 +67,27 @@ describe("OpenAI → Claude context mapping", () => {
|
||||
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.
|
||||
// applyThinking runs after adjustMaxTokens caps max_tokens, so a claude-budget
|
||||
// model at "max" effort (budget 128000) can exceed the clamped max_tokens and
|
||||
|
||||
Reference in New Issue
Block a user