feat(0.14.0): runDurableTurn + DurableChatTurnEngine#22
Merged
Conversation
…at layer
Two reusable primitives so every product chat handler routes durability
through one place instead of copy-pasting it four times.
runDurableTurn (src/durable/turn.ts) — a streaming, backend-agnostic,
checkpoint+replay durable turn. Generic over the event type; never
inspects events, only forwards them and reads finalText() after drain.
- Fresh run: producer runs, events forward live (streaming preserved),
final text checkpointed on drain.
- Replay: a completed turn re-emits cached text as one synthetic event;
the producer is never constructed — no LLM call, no double-billing.
- Mid-stream crash: a turn that died while streaming re-runs from the
top (the substrate checkpoints JSON at step granularity — there is no
partial-stream checkpoint; this is the honest durability ceiling).
- One step, lease claimed once via startOrResume; concurrent workers on
the same runId are rejected with DurableRunLeaseHeldError.
DurableChatTurnEngine (src/durable/chat-engine.ts) — the framework-neutral
chat-turn orchestrator. Owns what was duplicated across legal/gtm/creative/
tax: durable checkpointing, the NDJSON StreamEvent line protocol, the
session.run.started/completed/failed lifecycle envelope, ordered persist/
post-process hooks, trace flush. Everything product-specific is a hook:
produce / persistAssistantMessage / onTurnComplete / onEvent /
transformFinalText / traceFlush. Takes resolved values (identity tuple,
store, waitUntil) — never a Request or a Context — so React Router and
Hono products use it identically. Replay skips persist + post-process so
a retried turn never double-writes. Producer failure becomes an error +
session.run.failed pair; the stream always closes; hook errors are
swallowed + logged so a post-process failure never fails a streamed turn.
36 new tests (durable-turn 15, chat-engine 21), each run against the full
InMemory / FileSystem / D1-over-better-sqlite3 store matrix. Total suite:
213 tests, typecheck + biome clean.
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.
Summary
Two reusable primitives so every product chat handler routes durability through one place instead of copy-pasting it across legal/gtm/creative/tax.
runDurableTurn— streaming, backend-agnostic, checkpoint+replay durable turnRuntimeStreamEvent.DurableChatTurnEngine— framework-neutral chat-turn orchestratorOwns what was duplicated 4×: durable checkpointing, NDJSON line protocol,
session.run.*lifecycle envelope, ordered persist/post-process hooks, trace flush. Product-specific behavior is hooks:produce/persistAssistantMessage/onTurnComplete/onEvent/transformFinalText/traceFlush. Takes resolved values (identity, store,waitUntil) — never aRequest/Context— so React Router AND Hono products use it identically.error+session.run.failed; stream always closes.Test plan
pnpm test— 213/213 passpnpm typecheck+pnpm lintcleanFollow-up
Migrate all 4 product chat handlers onto
DurableChatTurnEngine— eachapi.chatcollapses to a thin route adapter (auth + parse +engine.runTurn). Per-productdurable-chat.tsdeleted. Supersedes legal#75, gtm#126, creative#102.