Skip to content

fix: treat response.incomplete as a terminal partial turn - #26

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-9472-incomplete-lenient
Sep 28, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-9472-incomplete-lenient

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Truncated turns are turns, not protocol violations.

Streaming parse no longer throws ProtocolMismatchError on response.incomplete: the envelope is schema-validated, then ignored. Deltas already delivered stay, isStreamTerminal still ends the stream, and no usage is reported. Non-streaming parse decodes an incomplete body's output the same way and withholds its usage. This restores the 0.1.0-era lenient contract that the Codex adapter and downstream hosts depend on (partial text, terminal, no usage); the strict throw from #23 broke backends that truncate routinely on max_output_tokens.

Fixes CL-9472

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review · Comment

A response.incomplete stream event or an incomplete JSON body ends the turn with the partial content already decoded, and reports no usage.

Findings

  • src/protocol/iterator.ts:481 / src/protocol/iterator.ts:746 — usage is dropped on the incomplete path. The tokens in that envelope are what the vendor actually bills for the truncated turn (input and the output emitted before the cap), so withholding them undercounts any accounting built on inference.usage. Could we emit usage whenever the envelope carries it, the same way response.completed does at line 443, and keep the "partial" concern to the content rather than the counts? If the intent is that truncated usage is untrustworthy for a specific vendor, that reads like a quirk switch rather than a blanket drop.

Notes

  • Neither inference.done nor the turn carries a stop reason, so a consumer cannot tell a truncated turn from a complete one. That is a protocol limit, not something this branch can fix, but worth a line in the comment so the next reader knows the loss is intentional.
  • bun run check exits 0 (49 unit tests pass); bun test e2e passes 5 with 2 skipped.

The usage in a truncated response is what the backend bills for the
partial turn, so both the streaming response.incomplete event and the
non-streaming status "incomplete" body now emit it like a completed one
instead of withholding it.
@TheGreatAxios
TheGreatAxios merged commit 96a9328 into main Sep 28, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-9472-incomplete-lenient branch September 28, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant