fix(claude): normalize trailing assistant turn

This commit is contained in:
Zac Gaetano 2026-08-24 22:49:26 -04:00
parent 0f6a449dbc
commit e99e91aa09

View file

@ -96,6 +96,40 @@ export function openaiToClaudeRequest(model, body, stream) {
flushCurrentMessage(); flushCurrentMessage();
// GUARD: some Claude auth channels (OAuth/Claude Code) reject requests
// that end on an assistant turn ("assistant message prefill" 400).
// Agentic loops (e.g. Hermes) sometimes resend their own last output as
// the new final message to request a continuation, with no new user
// turn in between. Normalize by appending a synthetic turn so the
// request always ends on `user`, regardless of auth channel or model.
// If the trailing assistant message has unresolved tool_use blocks,
// Anthropic separately requires a matching tool_result for each one
// (not just any user turn), so synthesize those instead of plain text.
{
const lastMsg = result.messages[result.messages.length - 1];
if (lastMsg && lastMsg.role === ROLE.ASSISTANT) {
const unresolvedToolUseIds = Array.isArray(lastMsg.content)
? lastMsg.content.filter(b => b.type === CLAUDE_BLOCK.TOOL_USE).map(b => b.id)
: [];
if (unresolvedToolUseIds.length > 0) {
result.messages.push({
role: ROLE.USER,
content: unresolvedToolUseIds.map(id => ({
type: CLAUDE_BLOCK.TOOL_RESULT,
tool_use_id: id,
content: "Continuing."
}))
});
} else {
result.messages.push({
role: ROLE.USER,
content: [{ type: CLAUDE_BLOCK.TEXT, text: "Continue." }]
});
}
}
}
// Add cache_control to last assistant message // Add cache_control to last assistant message
for (let i = result.messages.length - 1; i >= 0; i--) { for (let i = result.messages.length - 1; i >= 0; i--) {
const message = result.messages[i]; const message = result.messages[i];