Conversation
3d0cb35 to
a046a22
Compare
…port replies Providers on a binary or proprietary transport — kiro EventStream, cursor protobuf, commandcode NDJSON — decode their upstream reply to OpenAI Chat Completions inside their own executor. translateNonStreamingResponse has no branch for those target formats, so it fell through and returned the raw chat.completion body regardless of what the client asked for: a stream:false request to /v1/messages handed a Claude client `object:"chat.completion"`, and LiteLLM's anthropic/ prefix died on KeyError: 'content'. The streaming path was already correct. Convert on the way out when the body carries a choices array and the client speaks Claude or Responses. Fixes decolua#3199
a046a22 to
02d402c
Compare
|
will this fix be incorporated into next version? |
|
I wrote the patch, so I cannot answer for the release schedule — that is @decolua's call. What I can give you is the current state and a workaround. State. Open since 12 Aug, I re-checked it just now rather than assuming it had kept: merged the current Workaround until it lands. Only the non-streaming path is affected; streaming through the same route already returns the right shape. So on a binary-transport provider ( |
|
Superseded by #3878, which carries this commit unchanged, rebased onto current |
Fixes #3199
Problem
returns
{"id":"chatcmpl-…","object":"chat.completion","choices":[…]}instead of an Anthropic message. Streaming against the same route is correct.Providers on a binary or proprietary transport decode their upstream reply to OpenAI Chat Completions inside their own executor — the kiro executor emits
chat.completion.chunkSSE, whichhandleNonStreamingResponsecollapses viaparseSSEToOpenAIResponse(). By the timetranslateNonStreamingResponse()runs,targetFormatiskiroand the body is already OpenAI-shaped.translateNonStreamingResponse()branches onopenai,gemini/vertex/antigravity,claudeandollama.kiro(andcursor,commandcode) match none of them, so control reached the trailingreturn responseBodyand the OpenAI shape went straight to a Claude client. The existingtargetFormat === OPENAI && sourceFormat === CLAUDEbranch was the right conversion — it just was not reachable for these targets.Downstream impact: LiteLLM's
anthropic/prefix expects an Anthropic body from/v1/messagesand fails withKeyError: 'content'.Fix
The "body is OpenAI Chat Completions — what does the client speak?" dispatch existed once already, keyed on
targetFormat === OPENAI. Rather than write it a second time, it is extracted intofromOpenAICompletion()and called from both places: the existingtargetFormat === OPENAIbranch, and a new trailing guard for bodies that carry achoicesarray under a target format with no branch of its own.Net result is one list of client formats instead of two. The Responses arm covers the identical gap for a Responses client on the same targets. An OpenAI client is unaffected, and a body without
choicesis returned untouched.Scope note — the deeper seam, deliberately not taken
The codebase already has the right long-term mechanism for this:
result.responseFormat, honored inchatCore.js("Most executors return their registry format. Cursor AgentService is an exception: it is decoded by the executor into OpenAI-compatible output."). Today onlyexecutors/cursor.jssets it.executors/kiro.jsandexecutors/commandcode.jsboth qualify — kiro'sexecute()always runstransformEventStreamToSSE, and commandcode's always runswrapNdjsonAsOpenAISse(), so both unconditionally return OpenAI Chat Completions. DeclaringresponseFormat: FORMATS.OPENAIon those two executors would fix this issue with no new code at all, since the existingtargetFormat === OPENAIbranch would then handle them.I did not do that here because
providerResponseFormatalso feedshandleStreamingResponse, so it would reroute kiro's streaming path off the directkiro:clauderoute ontoopenai:claudeand maketranslator/response/kiro-to-claude.jsdead. That should be behavior-neutral, but it is a much larger change on a heavily used provider and belongs in its own PR. Flagging it so the direction is on record.Tests
New:
tests/unit/nonstream-binary-transport-source-format-3199.test.js— a kiro-decoded body converts to an Anthropic message (text,stop_reason,usage), tool calls becometool_useblocks, a Responses client getsobject:"response", an OpenAI client's body is returned by identity, and a body with nochoicesis untouched.Full suite (
npx vitest run unit translator) againstmasteras the baseline: 1670 → 1675 passed, 91 failed on both sides — no pass→fail, the 5 new passes are this PR's tests.