Repository navigation
test: make the Cohere streaming drift leg compare real event frames - #528
Merged
Merged
Conversation
The streaming /v2/chat shape leg parsed both bodies with parseDataOnlySSE, which drops every frame that has an `event:` line. Cohere v2 streams are all `event:` + `data:` frames, so both chunk lists were empty and the comparison never ran. Parse the frames with a strict Cohere parser (event name must equal data.type, trailing `data: [DONE]` dropped, any other frame throws), require non-empty streams that open with message-start and close with message-end, and compare every event type with compareSSESequences. A live stream that ends in the no-generation terminal (finish_reason ERROR, empty usage) is a skip that cites the upstream code and id, the same way the 422 envelope already is. isCohereNoGenerationStreamEnd now takes the allowed error types, like cohereNoGenerationError.
commit: |
parseCohereSSE threw on legal SSE a server or proxy can send: a single trailing newline, no space after the field colon, `:` keepalive comments, and id/retry fields. That would raise a false drift alert. Parse line by line per the WHATWG event-stream rules: any line ending, blank line dispatches, comments and id/retry/unknown fields ignored, optional space after the colon, multi-line data joined, final event dispatched without a trailing blank line. Malformed data JSON, a non-object payload, an event/type mismatch and events after [DONE] still throw.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The streaming
/v2/chatshape leg incohere.drift.tsnever compared anything. It parsed both bodies withparseDataOnlySSE, which keeps only frames that start withdata:. Every Cohere v2 frame isevent: <type>+data: <json>, so both lists came out empty, theif (realChunks.length > 0 && mockChunks.length > 0)guard was false, and the test always passed. (A non-200 mock reply was also let through without a comparison.)Fix (test-only; no
src/behavior change)parseCohereSSE: strict parser. Each frame must be exactlyevent:+data:withdata.typeequal to the event name. A trailingdata: [DONE]is dropped. Any other frame throws instead of being dropped.message-startand closing withmessage-end. It compares every event type withcompareSSESequencesagainst SDK shapes for message-start / content-start / content-delta / content-end / message-end (the same pattern the Anthropic stream leg uses).message-endis the no-generation terminal (finish_reason: "ERROR", empty usage) is now a skip that gives the upstream code and id (NO_VALID_RESPONSE_GENERATED id=...), the same as test(drift): accept Cohere upstream no-generation outcomes on the live leg #525.isCohereNoGenerationStreamEndnow takes the allowed error types, likecohereNoGenerationError. K5 passesCOHERE_K5_NO_GENERATION, so K5 behavior is unchanged.allowedparam.Local RED (current main, live Cohere)
Temporary probe in the leg:
Mutant in
src/cohere.ts(text streammessage-end:finish_reasonrenamed tofinishReason). The test still passes:Local GREEN (this branch, live Cohere)
Two consecutive live runs, probe removed:
The same
finishReasonmutant now fails:A second mutant (drop the
content-endevent) also fails:Both mutants reverted.
Non-critical differences now visible (not fixed here)
These come from the comparison, but the existing triangulation rules do not rate them critical: live
usage.cached_tokens, livecontent-startcarriestext: "", and aimock addscontent.typeon deltas plusbilled_units.search_units/classifications. Outside the shape comparison: live ends withdata: [DONE]and aimock's Cohere stream does not.Gates
pnpm typecheck,pnpm lint,pnpm format:check,pnpm build,pnpm test(377 files, 14080 passed) all pass.Follow-up: parse by the SSE spec (review finding)
parseCohereSSEthrew on legal SSE: a single trailing\n,event:Xwith no space,:keepalive comments, andid:/retry:lines. It now follows the WHATWG event-stream rules: any line ending (\n,\r\n,\r), the space after the colon is optional, comments andid/retry/unknown fields are ignored, multi-linedatais joined, and a final event is dispatched without a trailing blank line. These still throw: data that is not JSON, data that is not an object, an event name that does not matchdata.type(an event withoutevent:is namedmessageby the spec), and any event after[DONE].RED: the new offline tests run against the previous parser:
GREEN: all 4 parser tests pass. Live streaming leg passed 2 consecutive times. The
finishReasonmutant still fails onSSE:message-end.delta.finish_reason. Gates pass again (377 files, 14080 tests).