Skip to content

Move chunk interpretation into the agent adapter #35

Description

@FreshlyBrewedCode

Parent

#32

What to build

The AgentAdapter interprets its own chunk stream, so opencode's event vocabulary stops leaking
into the runtime. Today runtime/agent-step.ts string-matches CUSTOM + opencode.session-id to
populate AgentStepFinished.sessionId, and string-matches structured-output.complete — which is
not a provider capability at all, but @tanstack/ai-opencode's text adapter simulating
structured output by injecting JSON Schema into the prompt and re-parsing its own final message
(ADR 0001 §1).

After this ticket, the adapter yields the opaque chunk and a normalized signal drawn from a
small closed union Factory owns — session identifier, structured-output value, run error. The
runtime records those signals and never again matches a vendor event name. The union stays tiny by
rule: a member exists only when Factory has a field for it (AgentStepFinished.sessionId,
.output, .error), not as a general capability model for agents.

Chunks themselves do not change shape. ADR 0003 §2 stands: AgentChunk carries AG-UI verbatim, the
SPA keeps folding it with StreamProcessor from @tanstack/ai/client, and no stored event needs
migrating.

The observable payoff is that a second adapter — claude-code, codex, a raw provider loop — becomes
a normal amount of work instead of requiring it to emit opencode's exact CUSTOM event names. The
corpus replay adapter is the proof: it should be able to supply signals without replaying opencode
chunk shapes.

ADR 0012 was written ahead of this ticket — see docs/adr/0012-agent-runtime-seam.md §1, §2
and §5 for the boundary this implements, including why the adapter contract stays plain async
(AsyncIterable, not Stream.Stream).

Acceptance criteria

  • AgentAdapter yields opaque chunks plus a normalized signal union; the union's members are documented against the event fields they populate
  • No vendor event-name string (opencode.session-id, structured-output.complete) appears anywhere under src/runtime/ outside opencode-adapter.ts
  • AgentStepFinished.sessionId, .output and .error are populated exactly as before for a live opencode run
  • The runtime's two-tier structured-output resolution (event value, then finalText re-parse) still works — this ticket changes where the value comes from, not the fallback
  • Existing corpus traces still replay without modification, and the replay adapter supplies signals without imitating opencode chunk shapes
  • The adapter contract remains plain async (AsyncIterable), authorable without Effect
  • bun run check passes

Blocked by

None (can start immediately).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    taskA single self-contained piece of work that ships as one PR

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions