refactor(runtime): move chunk interpretation into the agent adapter - #54
Open
FreshlyBrewedCode wants to merge 2 commits into
Open
FreshlyBrewedCode wants to merge 2 commits into
FreshlyBrewedCode wants to merge 2 commits into
Conversation
FreshlyBrewedCode
added this pull request to stack #58
September 20, 2026 13:00
FreshlyBrewedCode
force-pushed
the
35-move-chunk-interpretation-into-adapter
branch
from
September 20, 2026 13:15
64e78cc to
2336bab
Compare
This was referenced Sep 22, 2026
ADR 0012 §2: AgentAdapter now yields AgentAdapterYield items (opaque chunk
plus optional AgentSignal). The normalized signal union has three members —
sessionId, structuredOutput, runError — each documented against the
AgentStepFinished field it populates.
The opencode adapter extracts signals via extractOpencodeSignal(), the only
place in src/runtime/ that string-matches vendor event names
(opencode.session-id, structured-output.complete). The runtime consumes
signals directly via a switch on _tag and never matches vendor strings.
The corpus replay adapter reuses extractOpencodeSignal() to supply signals
from recorded opencode traces. Existing corpus files replay without
modification. The slow fake adapter wraps raw chunks in { chunk } items.
The adapter contract remains plain async (AsyncIterable), authorable
without Effect (ADR 0012 §5).
- agent-step.test.ts: signal-based adapter yields are consumed correctly (sessionId, structuredOutput, runError signals; onChunk receives raw chunk; chunks without signals pass through) - opencode-adapter.test.ts: extractOpencodeSignal() handles all cases (vendor CUSTOM chunks, RUN_ERROR, non-matching chunks, malformed values) - adapter.test.ts: corpus replay yields AgentAdapterYield items with signals extracted from recorded opencode chunks
FreshlyBrewedCode
force-pushed
the
35-move-chunk-interpretation-into-adapter
branch
from
September 23, 2026 07:09
2336bab to
8bf6c49
Compare
This branch has not been deployed
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.
Part of #32 · Closes #35
runtime/agent-step.tsstring-matchedCUSTOM+opencode.session-idto populateAgentStepFinished.sessionId, and string-matchedstructured-output.complete— which isn't aprovider capability at all, but
@tanstack/ai-opencode's text adapter simulating structuredoutput by re-parsing its own final message (ADR 0001 §1). That left opencode's event vocabulary
leaking into the runtime, so a second adapter (claude-code, codex, a raw provider loop) would have
had to emit opencode's exact CUSTOM event names just to surface a session id or a structured
output. ADR 0012 §1/§2 draws the seam: the adapter interprets its own stream, the runtime consumes
a normalized signal.
What changed
AgentAdapter.streamnow yieldsAgentAdapterYield({ chunk, signal? }) instead of a bareunknownchunk.AgentSignalis a small closed union —sessionId,structuredOutput,runError— documented against the exactAgentStepFinishedfields each one populates.opencode-adapter.tsgainsextractOpencodeSignal, the only place undersrc/runtime/leftthat string-matches
opencode.session-idandstructured-output.complete; it also folds inRUN_ERRORdetection, whichagent-step.tsused to do itself.agent-step.tsdrops all vendor string-matching: it now just switches onyieldItem.signal._tagto populate
partial.sessionId/structuredOutput/runError, and keeps interpreting onlythe standard AG-UI
TEXT_MESSAGE_*/RUN_FINISHEDtypes forfinalTextaccumulation and usage.replay/adapter.ts's corpus replayer andcreateSlowFakeAdapterboth updated to the new yieldshape; the corpus replayer reuses
extractOpencodeSignalso recorded opencode traces keepproducing the same signals without re-deriving the logic.
runtime/agent-step.test.ts(signal-to-field wiring, chunk passthrough, no-signalcase),
runtime/opencode-adapter.test.ts(extractOpencodeSignalunit coverage including theguarded/negative cases), and a corpus round-trip assertion in
replay/adapter.test.tsthatchecks signal extraction end-to-end against a real recorded trace.
Notes for reviewers
RUN_ERRORis a standard AG-UI type, not opencode-specific, but its detection now lives insideextractOpencodeSignalalongside the two vendor-specific signals — every adapter owns detectingall three signal kinds for its own stream, per ADR 0012 §2, rather than the runtime special-casing
one AG-UI type generically.
extractOpencodeSignalfromopencode-adapter.tsrather thanduplicating the matching logic. That's a deliberate coupling: the recorded corpora are opencode
chunks, so replaying them faithfully means reusing the real interpretation, not reimplementing it.
A future non-opencode adapter is not obligated to imitate opencode chunk shapes at all — it would
supply signals through its own stream implementation.
resolveOutput's two-tier structured-output extraction (event value, thenfinalTextre-parse)in
runtime/run.tsis untouched — this ticket only changes where the tier-1 value comes from.opencode.session-id,structured-output.complete) still appear inagent-step.test.tsandopencode-adapter.test.tsas literal test fixtures for the extractionlogic — that's expected; no interpretation logic outside
opencode-adapter.tsmatches on them.Verification
extractOpencodeSignal), signal-to-field wiring inbuildAgentStepEffect, and a real-corpus round trip confirming the recorded session-id tracestill yields the expected signal and chunk count.
checkjob (format, lint, typecheck,bun test) passed on this PR; I did not additionallyrun it locally. There's no CI coverage of a live opencode run, so
AgentStepFinished.sessionId/.output/.errorparity for a live run is established by code inspection (the moved logic inextractOpencodeSignalis byte-for-byte equivalent to whatagent-step.tsused to do) ratherthan an end-to-end run.
Stack
33-parse-cli-with-effect-cli(issue Parse CLI arguments with effect/unstable/cli #33)34-domain-errors-as-tagged-errors(issue Represent domain failures as Schema.TaggedError #34)35-move-chunk-interpretation-into-adapter(issue Move chunk interpretation into the agent adapter #35) — this PR37-move-headless-permissions(issue Move headless permission setup out of the workspace allocator #37)36-agent-runtime-service(issue Add an Effect composition root and make the agent runtime a service #36)38-move-singletons-into-layers(issue Move the daemon's remaining singletons into layers #38)Stack created with GitHub Stacks CLI • Give Feedback 💬