feat(binding-llm): SSE content decoder for text/event-stream - #2554
Open
jfallows wants to merge 4 commits into
Open
feat(binding-llm): SSE content decoder for text/event-stream#2554jfallows wants to merge 4 commits into
jfallows wants to merge 4 commits into
Conversation
Scaffold incubator/binding-llm.spec per AGENTS.md conventions and define LlmBeginEx, LlmDataEx, and the LlmFlushEx union, modelled on binding-mcp.spec's idl. LlmBeginEx carries dialect only; model routing is deferred. LlmDataEx has no fields: content flows through the DATA frame's own payload octets and INIT/FIN through its existing flags, so nothing survives in the extension once block identity moves to the FLUSH plane. LlmFlushEx is a 7-case union covering message start, block start/end, finish, usage, keepalive, and an opaque native/raw case for re-encoding events a same-dialect route doesn't recognize. Fixes #2476 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AuZoMsETwEczJx3cbkb8EJ
Scaffolds incubator/binding-llm and incubator/binding-llm.conf, modelled on binding-mcp's SERVER/CLIENT BindingContext structure. LlmBindingInfo is annotated @Incubating so type: llm config loading is gated behind ZILLA_INCUBATOR_ENABLED via FeatureFilter, matching the AmqpBindingInfo/ PgsqlBindingInfo/RisingwaveBindingInfo precedent. Fixes #2477 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142buJWS7C89AKr9uDJtSy4
…t-type Registers by content-type and hands back a per-stream LlmContentDecoder; stays in an internal, unexported package for now with no concrete implementation registered yet. Fixes #2478 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mw32oxEw24fLt5Ypakj6pH
Implements LlmContentDecoderSpi for text/event-stream, decoding blank-line- delimited SSE framing (data:/event:/id: fields, comment lines, CRLF/CR/LF line endings) into event DATA + FLUSH frames per the WHATWG SSE parsing algorithm. Framing decode only, independent of any dialect-specific (e.g. OpenAI, Anthropic) payload interpretation. The last-event-id persists across dispatches until overwritten by a subsequent id: field, matching SSE reconnection semantics, and is reported as the flush boundary's associated bytes. A trailing lone CR at the edge of the currently available bytes is deliberately left unconsumed (returned as no progress) since it may still turn out to be part of a CRLF pair once more bytes arrive - avoiding a premature, ambiguous line-terminator decision on a streamed input. Representational-slack note for the round-trip decision this issue flags: this decoder normalizes field ordering and does not preserve field-value whitespace beyond the single optional space after the colon that the SSE spec itself strips, so re-encoding from the decoded data/flush frames alone cannot byte-exactly reproduce arbitrary source framing - only semantic equality should be asserted against this decoder's output. Fixes #2479 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013kK9z4ywuPJe33jm6W1fik
This was referenced Sep 8, 2026
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.
Description
Implements
LlmContentDecoderSpifortext/event-stream, decoding blank-line-delimited SSE framing (data:/event:/id:fields, comment lines, CRLF/CR/LF line endings) into event DATA + FLUSH frames, per the WHATWG SSE parsing algorithm. Framing decode only, independent of any dialect-specific (OpenAI, Anthropic, etc.) payload interpretation — feeds both same-dialect (framing-only) and cross-dialect (framing + payload transform) routes.LlmSseContentDecoder(io.aklivity.zilla.runtime.binding.llm.internal.decode) — decodes one stream's SSE framing, reportingdata()for accumulateddata:field bytes (joined with\nacross multipledata:lines in one event) andflush(event, id, ...)on each blank-line dispatch. Comment lines (:...) and unrecognized fields (e.g.retry:) are ignored.LlmSseContentDecoderFactorySpi— registers the decoder for content-typetext/event-stream, wired viaMETA-INF/servicesand aprovidesclause added tomodule-info.java(theusesdeclaration already existed from feat(binding-llm): LlmContentDecoderSpi internal SPI, keyed by content-type #2553).LlmSseContentDecoderTest/LlmSseContentDecoderFactorySpiTest— 19 new unit tests covering multi-line data joining, event-name reset semantics, last-event-id persistence across dispatches (and NUL-byte rejection / buffer growth for it), comment/unrecognized-field handling, and the CR/CRLF line-ending edge cases including the ambiguous trailing-CR-at-buffer-edge case (deliberately left unconsumed since it may still turn out to be part of a CRLF pair once more bytes arrive).Representational-slack note (per the issue's open question): this decoder normalizes field ordering and does not preserve field-value whitespace beyond the single optional space after the colon that the SSE spec itself strips. Re-encoding from the decoded
data/flushframes alone cannot byte-exactly reproduce arbitrary source framing — a later round-trip test should assert semantic equality, notassertEquals(bytes).Verified
./mvnw clean install -pl incubator/binding-llm.conf,incubator/binding-llmpasses end-to-end: checkstyle, license headers, and all 27 unit tests pass with full jacoco coverage.This branch is built on top of
claude/issue-2478-76f5ry(#2553, theLlmContentDecoderSpiinternal SPI), so this diff includes #2553's commits until that merges todevelop— at which point this PR's diff will shrink to just this SSE decoder commit.Fixes #2479
🤖 Generated with Claude Code
https://claude.ai/code/session_013kK9z4ywuPJe33jm6W1fik
Generated by Claude Code