Skip to content

feat(ai): add opt-in raw provider events to LLM streams - #59382

Merged
rekram1-node merged 2 commits into
devfrom
include-raw-events
Oct 26, 2026
Merged

rekram1-node merged 2 commits into
devfrom
include-raw-events

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Adds an opt-in way to see every provider frame in an LLM stream.

Shape

  • New request option: includeRaw?: boolean on LLMRequest. It works through LLM.request, LLM.stream and the Promise client (ai.llm.stream({ ..., includeRaw: true })) with no extra plumbing.
  • New LLMEvent variant: { type: "raw", data: unknown }, with LLMEvent.raw(...) and LLMEvent.is.raw.
  • When includeRaw is set, each decoded provider frame produces one raw event, in order, right before the events parsed from that frame. Frames the protocol ignores still show up, so callers can translate unknown events with a plain stream map.
  • data is the parsed frame: JSON text frames are parsed (non-JSON sentinels like [DONE] stay strings), and already-decoded frames (Bedrock event-stream records, Gemini's parsed chunks) pass through as the protocol sees them. Transport-internal markers (Gemini's continuation boundary) are skipped.

Where

Implemented once in the shared route stream in packages/ai/src/route/client.ts, so every chat protocol gets it with no protocol changes. The terminal-event guard ignores raw events, so a frame that trails finish (for example an Anthropic ping) does not fail the stream. LLMResponse.reduce skips raw events, so generate returns the same response with or without the option.

Prior art

The AI SDK's includeRawChunks option and its { type: 'raw', rawValue } stream part.

Scope

  • Opt-in and off by default. Core does not set it, so nothing changes at runtime for opencode sessions.
  • No public Protocol or Server schema exposes LLMEvent, so the client does not need regenerating.
  • Follow-up: media streams (images, speech, transcription) are not covered here.

Tests

  • packages/ai/test/raw-events.test.ts: raw events come in order before each frame's own events, include a frame the protocol ignores and a frame after finish, and do not appear without the option; generate gives the same result with the option on.
  • packages/ai suite passes, recorded tests replay unchanged; bun run check passes.

Added by the opencode bot (AI-generated).

Public API: adds includeRaw to LLMRequest and a raw LLMEvent variant to @opencode/ai (backwards compatible)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No blocking issues; the feature works as described. Using a scratch OpenAI Chat stream with includeRaw: true, I checked that each chunk's raw event comes right before that chunk's own events, that the usage-only chunk and the [DONE] sentinel (as a string) both show up, and that generate is unaffected. The packages/ai suite and tsc type check pass.

Public API: adds includeRaw?: boolean to LLMRequest and the { type: "raw", data: unknown } variant to LLMEvent, with LLMEvent.raw and LLMEvent.is.raw. Both are additions and backwards compatible. Code that switches over every LLMEvent type with an exhaustive check will need a raw case to compile.

One small point inline: Bedrock raw frames include the transport's internal rawBody field.

Comment thread packages/ai/src/route/client.ts Outdated
state = next
return output
return request.includeRaw && isProviderFrame(frame)
? [LLMEvent.raw({ data: rawFrame(frame) }), ...output]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For Bedrock, frame is the event-stream record built in bedrock-event-stream.ts, so data will include the internal rawBody string next to the event, which repeats the payload. Since data is public, it may be worth stripping that field here (or letting the transport supply the raw value) before callers start depending on it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 8453f6e. Framing definitions can now say what the raw value of a frame is (an optional raw(frame) next to body(frame)). Bedrock uses it to drop rawBody, so raw data is { <eventType>: payload } with padding removed. Exceptions keep { exception: { type, details } }. A Bedrock test covers it.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The earlier Bedrock point is fixed: the framing's new raw hook strips rawBody, and the new test checks that raw data contains only the event payload. The packages/ai suite and tsc type check pass. Nothing new to raise.

Public API is the same as before: adds includeRaw?: boolean to LLMRequest and the raw variant to LLMEvent (LLMEvent.raw, LLMEvent.is.raw). Both are backwards compatible. The Framing.Definition.raw and TransportExecution.raw hooks are new and optional.

@rekram1-node
rekram1-node merged commit 9370121 into dev Oct 26, 2026
19 checks passed
@rekram1-node
rekram1-node deleted the include-raw-events branch October 26, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant