@tangle-network/agent-app 0.43.46 → 0.43.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/index.d.ts +3 -2
- package/dist/assistant/index.js +3 -2
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-routes/index.d.ts +407 -160
- package/dist/chat-routes/index.js +512 -102
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +3 -2
- package/dist/chat-store/index.js +19 -1
- package/dist/chat-store/index.js.map +1 -1
- package/dist/chunk-23LUHHHN.js +282 -0
- package/dist/chunk-23LUHHHN.js.map +1 -0
- package/dist/{chunk-4AUQIAYU.js → chunk-5MG74GVQ.js} +2 -281
- package/dist/chunk-5MG74GVQ.js.map +1 -0
- package/dist/chunk-BQV42AFK.js +99 -0
- package/dist/chunk-BQV42AFK.js.map +1 -0
- package/dist/{chunk-6E2XJSCT.js → chunk-JGYOYY5D.js} +65 -2
- package/dist/chunk-JGYOYY5D.js.map +1 -0
- package/dist/{chunk-7VMUOD3G.js → chunk-PHIXZC6Y.js} +1 -1
- package/dist/chunk-PHIXZC6Y.js.map +1 -0
- package/dist/{file-index-Bn6sitKb.d.ts → file-index-b26ee-_R.d.ts} +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +38 -22
- package/dist/{parts-1_3y2JmR.d.ts → parts-IB-Kbb7z.d.ts} +146 -2
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/stale-turn-lock-C8Na1cFZ.d.ts +149 -0
- package/dist/stream/index.d.ts +5 -192
- package/dist/stream/index.js +14 -12
- package/dist/stream-normalizer-DWvtmY6F.d.ts +48 -0
- package/dist/teams/index.js +9 -9
- package/dist/teams/invitations-api.js +7 -7
- package/dist/turn-buffer-C9mEgoop.d.ts +146 -0
- package/dist/turn-stream/index.d.ts +551 -0
- package/dist/turn-stream/index.js +837 -0
- package/dist/turn-stream/index.js.map +1 -0
- package/dist/web-react/index.d.ts +4 -3
- package/dist/web-react/index.js +24 -2
- package/package.json +6 -1
- package/dist/chunk-4AUQIAYU.js.map +0 -1
- package/dist/chunk-6E2XJSCT.js.map +0 -1
- package/dist/chunk-7VMUOD3G.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/turn-stream/core.ts","../../src/turn-stream/do.ts","../../src/turn-stream/adapters.ts","../../src/turn-stream/memory.ts"],"sourcesContent":["/**\n * `/turn-stream` core — the pure, substrate-free half of the shared durable\n * turn replay/broadcast/lock channel (issue #221).\n *\n * Extracted from the reference consumer's hand-rolled Durable Object\n * (gtm-agent `SessionStreamDO` + `session-broadcast.ts`): the per-turn\n * segment store that backs reconnect replay over a live socket, the\n * single-flight chat-turn lock record and its release fences, and the wire\n * contract (channel keys, endpoint paths, request/response bodies) shared by\n * the DO transport shell (`./do`) and the worker-side adapters\n * (`./adapters`). Everything here is plain data + functions — no\n * `cloudflare:workers`, no storage, no sockets — so the semantics are\n * unit-testable in Node and the DO stays a thin shell.\n */\n\n// ── events ──────────────────────────────────────────────────────────────────\n\n/** One event on a turn-stream channel. `seq` is monotonic within a turn\n * segment and assigned by {@link appendSegmentEvent} on arrival at the DO. */\nexport interface TurnStreamEvent {\n type: string\n data?: unknown\n timestamp: number\n seq?: number\n}\n\n/** Terminal run markers: they close a turn segment and auto-release the\n * channel's chat-turn lock for the segment's execution. */\nexport function isTerminalRunEvent(type: string): boolean {\n return type === 'session.run.completed' || type === 'session.run.failed'\n}\n\n// ── channel keys ────────────────────────────────────────────────────────────\n//\n// One DO instance per channel key. Three families:\n// thread `${workspaceId}:${threadId}` — live turn fanout + segments +\n// the thread-scope lock.\n// workspace `${workspaceId}` — sidebar activity + thread.created\n// + the workspace-scope lock.\n// turn `turn:${turnId}` — durable turn-event rows + status\n// (the TurnEventStore contract; replay survives DO eviction).\n// scope `scope:${scopeId}` — running-turn index for a thread,\n// backing `TurnEventStore.listRunning`.\n\nexport type TurnLockScope = 'thread' | 'workspace'\n\nexport function threadChannelKey(workspaceId: string, threadId: string): string {\n return `${workspaceId}:${threadId}`\n}\n\nexport function workspaceChannelKey(workspaceId: string): string {\n return workspaceId\n}\n\n/** The channel a lock lives on: workspace-scope locks serialize every thread\n * in the workspace (one shared sandbox), thread-scope locks serialize one\n * thread (router lane). Same keying as the reference consumer, so a product\n * swapping its fork for this package contends on identical instances. */\nexport function turnLockChannelKey(workspaceId: string, threadId: string, scope: TurnLockScope): string {\n return scope === 'workspace' ? workspaceChannelKey(workspaceId) : threadChannelKey(workspaceId, threadId)\n}\n\nexport function turnStorageChannelKey(turnId: string): string {\n return `turn:${turnId}`\n}\n\nexport function scopeIndexChannelKey(scopeId: string): string {\n return `scope:${scopeId}`\n}\n\n// ── segment store (reconnect replay over a live socket) ─────────────────────\n\nexport interface TurnSegment {\n events: TurnStreamEvent[]\n maxSeq: number\n terminal: boolean\n}\n\nexport interface SegmentStore {\n segments: Map<string, TurnSegment>\n activeExecutionId: string | null\n}\n\n/** Per-turn replay window. Generous enough for normal turns; a turn that\n * exceeds it loses its earliest deltas from replay (a late resumer\n * self-heals via the final `result` event + loader revalidation). */\nexport const MAX_SEGMENT_EVENTS = 2000\n\n/** Recent `thread.created` markers kept for late-connecting sidebars. */\nexport const MAX_RECENT_CREATED = 50\n\n/** A responding marker older than this is treated as stale, so a dropped\n * `end` broadcast can't leave a permanently-stuck \"responding\" dot. */\nexport const ACTIVITY_TTL_MS = 15 * 60 * 1000\n\nexport function createSegmentStore(): SegmentStore {\n return { segments: new Map(), activeExecutionId: null }\n}\n\n/**\n * Append a per-turn event to its execution's segment, assigning a monotonic\n * `seq`. A `session.run.started` (or the first-seen event for an execution)\n * opens a fresh segment, makes it active, and drops prior turns' buffers so a\n * resumer only ever replays the current turn. A terminal run event marks the\n * segment terminal. Returns the seq-stamped event to broadcast.\n */\nexport function appendSegmentEvent(\n store: SegmentStore,\n executionId: string,\n incoming: TurnStreamEvent,\n maxEvents = MAX_SEGMENT_EVENTS,\n): TurnStreamEvent {\n let segment = store.segments.get(executionId)\n if (incoming.type === 'session.run.started' || !segment) {\n if (!segment) {\n segment = { events: [], maxSeq: 0, terminal: false }\n store.segments.set(executionId, segment)\n }\n store.activeExecutionId = executionId\n for (const id of store.segments.keys()) {\n if (id !== executionId) store.segments.delete(id)\n }\n }\n const seq = ++segment.maxSeq\n const stamped: TurnStreamEvent = { ...incoming, seq }\n segment.events.push(stamped)\n if (segment.events.length > maxEvents) {\n segment.events = segment.events.slice(-maxEvents)\n }\n if (isTerminalRunEvent(incoming.type)) {\n segment.terminal = true\n }\n return stamped\n}\n\n/**\n * Events of the active, non-terminal turn with `seq > afterSeq` — what a\n * (re)connecting client replays before going live. A terminal (finished) turn\n * replays nothing: the client falls back to the loader's persisted row.\n */\nexport function replayActiveSegment(store: SegmentStore, afterSeq: number): TurnStreamEvent[] {\n const segment = store.activeExecutionId ? store.segments.get(store.activeExecutionId) : undefined\n if (!segment || segment.terminal) return []\n return segment.events.filter((event) => (event.seq ?? 0) > afterSeq)\n}\n\n/**\n * Remove responding entries (threadId → startedAt) older than `ttlMs`, so a\n * dropped `end` broadcast can't leave a permanently-stuck dot. Mutates\n * `active` and returns the removed thread ids.\n */\nexport function pruneStaleThreads(active: Map<string, number>, now: number, ttlMs: number): string[] {\n const removed: string[] = []\n for (const [threadId, startedAt] of active) {\n if (now - startedAt > ttlMs) {\n active.delete(threadId)\n removed.push(threadId)\n }\n }\n return removed\n}\n\n// ── chat-turn lock record + fences ──────────────────────────────────────────\n\n/** Default lifetime of an unreleased lock. Long enough that a legitimately\n * slow sandbox turn never loses its guard mid-run; the way OUT of a wedge is\n * never the TTL but `reconcileStaleTurnLock` (in `/chat-routes`), which\n * probes the execution's actual state. */\nexport const TURN_LOCK_TTL_MS = 30 * 60 * 1000\n\n/** The stored single-flight lock. Field-compatible with the reference\n * consumer's `ChatTurnLock` so adoption is a swap, not a migration. */\nexport interface DurableTurnLock {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n startedAt: number\n expiresAt: number\n turnId?: string\n /** The turn released this lock, but a product-owned post-turn task (e.g.\n * file persistence reading the box) is still running, so the release is\n * parked on the lock until the task settles. Written only through the DO's\n * defer seam — the base package never sets it on its own. */\n releasePending?: boolean\n}\n\nexport interface TurnLockAcquireInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n turnId?: string\n}\n\nexport type TurnLockAcquireResult =\n | { acquired: true; lock: DurableTurnLock }\n | { acquired: false; active: DurableTurnLock }\n\nexport interface TurnLockReleaseInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n}\n\n/** Fenced out-of-band release (stop button, stale-lock reconciliation). The\n * fences make it refuse a SUCCESSOR lock: `interruptedAt` must not precede\n * the lock's own start, and when either side names a turn, both must name\n * the same one. */\nexport interface TurnLockInterruptedReleaseInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread. */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** `stored` is what the DO read from storage; expired locks are dead. */\nexport function activeTurnLock(stored: DurableTurnLock | undefined, now: number): DurableTurnLock | null {\n if (!stored) return null\n // Locks written before the scope field existed default to thread scope.\n const lock = stored.scope ? stored : { ...stored, scope: 'thread' as const }\n return lock.expiresAt > now ? lock : null\n}\n\nexport function createTurnLock(input: TurnLockAcquireInput, now: number, ttlMs = TURN_LOCK_TTL_MS): DurableTurnLock {\n return {\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n scope: input.scope,\n executionId: input.executionId,\n lockId: input.lockId,\n startedAt: now,\n expiresAt: now + ttlMs,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n }\n}\n\n/** A cooperative release must present the lock's own identity — both the\n * execution and the lockId minted at acquire — so a retry of a PREVIOUS turn\n * can never release the current one. `lockId` is optional only for the DO's\n * internal terminal-event auto-release, which knows the execution but not\n * the caller-held lockId. */\nexport function turnLockMatchesRelease(\n active: DurableTurnLock,\n input: { executionId: string; lockId?: string },\n): boolean {\n if (active.executionId !== input.executionId) return false\n if (input.lockId && active.lockId !== input.lockId) return false\n return true\n}\n\n/**\n * The interrupted/stale release fence. `interruptedAt` is the instant the\n * releasing evidence was observed (the stop click, the stale-lock probe) —\n * a lock STARTED after that instant is a successor the evidence says nothing\n * about, so it survives. When the lock recorded a client turnId, the release\n * must name the same turn; a lock without one refuses a turn-specific\n * release (it cannot prove it is that turn).\n */\nexport function interruptedReleaseApplies(\n active: DurableTurnLock,\n input: { threadId: string; interruptedAt: number; turnId?: string },\n): boolean {\n if (active.threadId !== input.threadId) return false\n if (active.startedAt > input.interruptedAt) return false\n if (active.turnId) {\n if (active.turnId !== input.turnId) return false\n } else if (input.turnId) {\n return false\n }\n return true\n}\n\n// ── wire contract (adapter ↔ DO endpoint paths) ─────────────────────────────\n//\n// Kept byte-identical to the reference consumer's DO where an endpoint\n// existed there, so a product deletes its fork by re-pointing a binding, not\n// by re-speaking a protocol.\n\nexport const TURN_STREAM_PATHS = {\n broadcast: '/broadcast',\n lockAcquire: '/chat-turn-lock/acquire',\n lockRelease: '/chat-turn-lock/release',\n lockReleaseInterrupted: '/chat-turn-lock/release-interrupted',\n turnEventsAppend: '/turn-events/append',\n turnEventsRead: '/turn-events/read',\n turnStatusSet: '/turn-status/set',\n turnStatusGet: '/turn-status/get',\n scopeStatusSet: '/turn-scope/set',\n scopeRunningList: '/turn-scope/running',\n} as const\n\n/** Storage keys inside a DO instance. Exported for subclass coexistence —\n * a product extending the DO must not collide with these. */\nexport const TURN_STREAM_STORAGE_KEYS = {\n lock: 'chatTurnLock',\n activeThreads: 'activeThreads',\n turnStatus: 'turnStatus',\n turnScope: 'turnScopeIndex',\n turnEventPrefix: 'turnEvent:',\n} as const\n\n/** Zero-padded seq so DO storage `list({ prefix })` returns rows in replay\n * order without a sort. 10 digits holds any realistic turn. */\nexport function turnEventStorageKey(seq: number): string {\n return `${TURN_STREAM_STORAGE_KEYS.turnEventPrefix}${String(seq).padStart(10, '0')}`\n}\n","/**\n * `TurnStreamDO` — the shared Durable Object transport shell over the pure\n * core (`./core`). One class serves every channel family; the instance NAME\n * decides which endpoints a given instance ever sees:\n *\n * - **thread channel** (`${workspaceId}:${threadId}`) — the live chat turn:\n * WebSocket fanout, per-turn segments with `sync`/`afterSeq` reconnect\n * replay, and the thread-scope lock.\n * - **workspace channel** (`${workspaceId}`) — coarse sidebar signals\n * (`thread.activity` responding set, durable across eviction;\n * `thread.created` recent list) and the workspace-scope lock.\n * - **turn storage** (`turn:${turnId}`) — the durable `TurnEventStore` rows +\n * status for one buffered turn (replay survives DO eviction — this is what\n * graduates the vertical's `turnStore` from no-op).\n * - **scope index** (`scope:${scopeId}`) — the running-turn index backing\n * `TurnEventStore.listRunning` reconnect discovery.\n *\n * The class is a PLAIN class over a structural {@link TurnStreamDOState} —\n * no `cloudflare:workers` import, so this package stays substrate-free and\n * the DO is unit-testable in Node. Cloudflare's `DurableObjectState`\n * satisfies the interface; a product binds it in wrangler by re-exporting:\n *\n * // worker entry\n * export { TurnStreamDO } from '@tangle-network/agent-app/turn-stream'\n *\n * Fan-out enumerates `state.getWebSockets()` (never an in-memory socket map)\n * and reads per-socket metadata from the serialized attachment, so it is\n * correct across WebSocket hibernation.\n *\n * Product extension (how the reference consumer keeps its Vault machinery\n * while deleting its fork): subclass and override\n * {@link TurnStreamDO.handleProductRequest} (extra POST endpoints),\n * {@link TurnStreamDO.shouldDeferLockRelease} /\n * {@link TurnStreamDO.completeDeferredLockRelease} (park a lock release\n * behind a product-owned post-turn task), and\n * {@link TurnStreamDO.productSyncEvents} (extra state replayed to a\n * late-connecting socket). Product storage keys must avoid\n * {@link TURN_STREAM_STORAGE_KEYS}.\n */\n\nimport {\n ACTIVITY_TTL_MS,\n MAX_RECENT_CREATED,\n MAX_SEGMENT_EVENTS,\n TURN_LOCK_TTL_MS,\n TURN_STREAM_PATHS,\n TURN_STREAM_STORAGE_KEYS,\n activeTurnLock,\n appendSegmentEvent,\n createSegmentStore,\n createTurnLock,\n interruptedReleaseApplies,\n isTerminalRunEvent,\n pruneStaleThreads,\n replayActiveSegment,\n turnEventStorageKey,\n turnLockMatchesRelease,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural Cloudflare surface ───────────────────────────────────────────\n\n/** The socket surface the DO touches. Cloudflare's hibernatable `WebSocket`\n * satisfies it. */\nexport interface TurnStreamSocket {\n send(data: string): void\n close(code?: number, reason?: string): void\n serializeAttachment(value: unknown): void\n deserializeAttachment(): unknown\n}\n\n/** The storage surface the DO touches (Cloudflare `DurableObjectStorage`\n * satisfies it structurally). `list` must return keys in ascending order —\n * the turn-event rows rely on it for replay order. */\nexport interface TurnStreamStorage {\n get<T = unknown>(key: string): Promise<T | undefined>\n put<T = unknown>(key: string, value: T): Promise<void>\n delete(key: string): Promise<boolean | void>\n list<T = unknown>(options: { prefix: string; start?: string }): Promise<Map<string, T>>\n}\n\n/** The `DurableObjectState` surface the DO uses. */\nexport interface TurnStreamDOState {\n storage: TurnStreamStorage\n acceptWebSocket(ws: TurnStreamSocket): void\n getWebSockets(): TurnStreamSocket[]\n}\n\ninterface SocketMeta {\n sessionId: string\n scope: 'thread' | 'workspace'\n /** Live fan-out is withheld until the client has run its initial `sync`\n * (see {@link TurnStreamDO.webSocketMessage}). This makes replay and live\n * delivery mutually exclusive per socket, so frames can neither interleave\n * out of order nor drop in the connect race. */\n synced: boolean\n}\n\n// ── request/response plumbing ───────────────────────────────────────────────\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\nfunction stringValue(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nasync function jsonBody(request: Request): Promise<Record<string, unknown> | null> {\n const body = await request.json().catch(() => null)\n return body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : null\n}\n\n/** Per-scope running-turn index entry (one record per buffered turn). */\ninterface ScopeTurnEntry {\n status: 'running' | 'complete' | 'error'\n updatedAt: number\n}\n\n/** Cap on remembered turns per scope; oldest terminal entries are pruned\n * first, so a busy thread's index stays bounded without ever dropping a\n * still-running turn. */\nconst MAX_SCOPE_TURNS = 100\n\n// ── the DO ──────────────────────────────────────────────────────────────────\n\nexport interface TurnStreamDOOptions {\n /** Override {@link TURN_LOCK_TTL_MS}. */\n lockTtlMs?: number\n /** Override {@link MAX_SEGMENT_EVENTS}. */\n maxSegmentEvents?: number\n /** Override {@link ACTIVITY_TTL_MS}. */\n activityTtlMs?: number\n}\n\nexport class TurnStreamDO {\n protected readonly state: TurnStreamDOState\n protected readonly env: unknown\n protected readonly options: TurnStreamDOOptions\n\n // Thread channel: per-turn segments; only the active one is replayed.\n private segments = createSegmentStore()\n // Workspace channel: recent thread.created markers (in-memory, best-effort)\n // + durable responding set (threadId → startedAt) that survives eviction.\n private recentCreated: TurnStreamEvent[] = []\n private activeThreads: Map<string, number> | null = null\n\n constructor(state: TurnStreamDOState, env?: unknown, options: TurnStreamDOOptions = {}) {\n this.state = state\n this.env = env\n this.options = options\n }\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n if (request.headers.get('Upgrade') === 'websocket') {\n return this.handleWebSocketUpgrade(request, url)\n }\n\n if (request.method === 'POST') {\n switch (url.pathname) {\n case TURN_STREAM_PATHS.broadcast:\n return this.handleBroadcast(request)\n case TURN_STREAM_PATHS.lockAcquire:\n return this.handleLockAcquire(request)\n case TURN_STREAM_PATHS.lockRelease:\n return this.handleLockRelease(request)\n case TURN_STREAM_PATHS.lockReleaseInterrupted:\n return this.handleLockReleaseInterrupted(request)\n case TURN_STREAM_PATHS.turnEventsAppend:\n return this.handleTurnEventsAppend(request)\n case TURN_STREAM_PATHS.turnEventsRead:\n return this.handleTurnEventsRead(request)\n case TURN_STREAM_PATHS.turnStatusSet:\n return this.handleTurnStatusSet(request)\n case TURN_STREAM_PATHS.turnStatusGet:\n return this.handleTurnStatusGet()\n case TURN_STREAM_PATHS.scopeStatusSet:\n return this.handleScopeStatusSet(request)\n case TURN_STREAM_PATHS.scopeRunningList:\n return this.handleScopeRunningList()\n }\n }\n\n const product = await this.handleProductRequest(request, url)\n if (product) return product\n return request.method === 'POST'\n ? jsonResponse({ error: `Unknown turn-stream endpoint ${url.pathname}` }, 404)\n : new Response('Method not allowed', { status: 405 })\n }\n\n // ── product extension seam ────────────────────────────────────────────────\n\n /** Called for any request no base endpoint claimed (before the 404), so a\n * subclass adds product endpoints without touching base routing. Return\n * `null` to decline. */\n protected async handleProductRequest(_request: Request, _url: URL): Promise<Response | null> {\n return null\n }\n\n /** Consulted before any lock release (cooperative, interrupted, or the\n * terminal-event auto-release). Return `true` while a product-owned\n * post-turn task for `executionId` must keep the scope serialized (e.g.\n * file persistence still reading the box) — the release is then parked as\n * `releasePending` on the lock and completed via\n * {@link completeDeferredLockRelease}. Base: never defer. */\n protected async shouldDeferLockRelease(_executionId: string): Promise<boolean> {\n return false\n }\n\n /** Complete a release parked by {@link shouldDeferLockRelease}. A subclass\n * calls this when its deferred condition settles. */\n protected async completeDeferredLockRelease(executionId: string): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active?.releasePending || active.executionId !== executionId) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Extra product state replayed to a socket during its `sync`, after the\n * base replay for its scope (e.g. an in-flight persistence status card).\n * Base: none. */\n protected async productSyncEvents(_scope: 'thread' | 'workspace', _meta: { sessionId: string }): Promise<TurnStreamEvent[]> {\n return []\n }\n\n // ── WebSocket channel ─────────────────────────────────────────────────────\n\n private handleWebSocketUpgrade(_request: Request, url: URL): Response {\n const sessionId = url.searchParams.get('sessionId')\n if (!sessionId) return new Response('Missing sessionId', { status: 400 })\n const scope = url.searchParams.get('scope') === 'workspace' ? 'workspace' : 'thread'\n\n const PairCtor = (globalThis as { WebSocketPair?: new () => Record<number, unknown> }).WebSocketPair\n if (!PairCtor) {\n return new Response('WebSocket upgrades require the Cloudflare runtime', { status: 501 })\n }\n const pair = new PairCtor()\n const client = pair[0]\n const server = pair[1] as TurnStreamSocket\n\n // Accept for hibernation and record routing metadata. No replay is pushed\n // here: sending before the client finishes the 101 handshake is racy\n // (frames can drop). The client issues a `sync` once open; replay happens\n // there.\n this.state.acceptWebSocket(server)\n server.serializeAttachment({ sessionId, scope, synced: false } satisfies SocketMeta)\n\n return new Response(null, { status: 101, webSocket: client } as ResponseInit)\n }\n\n /**\n * First (and only) client message after open: `{ type: 'sync', afterSeq }`.\n * Replays the current state for the socket's scope, then marks it `synced`\n * so live broadcasts start flowing. Because the DO is single-threaded, the\n * replay snapshot and the synced flip are atomic w.r.t. broadcasts — every\n * event reaches the socket exactly once, in order, via replay XOR live\n * fan-out.\n */\n async webSocketMessage(ws: TurnStreamSocket, message: string | ArrayBuffer): Promise<void> {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta || meta.synced) return\n let afterSeq = 0\n try {\n const parsed = JSON.parse(typeof message === 'string' ? message : '') as { type?: string; afterSeq?: number }\n if (parsed.type !== 'sync') return\n afterSeq = typeof parsed.afterSeq === 'number' ? parsed.afterSeq : 0\n } catch {\n return\n }\n\n if (meta.scope === 'workspace') {\n // Current responding state as synthetic `start` markers.\n const active = await this.loadActiveThreads()\n const removed = pruneStaleThreads(active, Date.now(), this.options.activityTtlMs ?? ACTIVITY_TTL_MS)\n if (removed.length > 0) await this.persistActiveThreads(active)\n for (const [threadId, startedAt] of active) {\n this.trySend(ws, {\n type: 'thread.activity',\n data: { threadId, phase: 'start', sessionId: meta.sessionId },\n timestamp: startedAt,\n })\n }\n // Recently-created threads for late joiners.\n for (const event of this.recentCreated) this.trySend(ws, event)\n } else {\n // The active, non-terminal turn segment from the cursor.\n for (const event of replayActiveSegment(this.segments, afterSeq)) {\n this.trySend(ws, event)\n }\n }\n for (const event of await this.productSyncEvents(meta.scope, { sessionId: meta.sessionId })) {\n this.trySend(ws, event)\n }\n\n ws.serializeAttachment({ ...meta, synced: true } satisfies SocketMeta)\n }\n\n webSocketClose(ws: TurnStreamSocket, code: number, reason: string): void {\n try {\n ws.close(code, reason)\n } catch {\n // Already closed.\n }\n }\n\n webSocketError(): void {\n // Socket dropped; getWebSockets() will no longer include it.\n }\n\n private trySend(ws: TurnStreamSocket, event: TurnStreamEvent): void {\n try {\n ws.send(JSON.stringify(event))\n } catch {\n // Client may not be ready yet, or is closing.\n }\n }\n\n // ── broadcast (live fanout + segments + activity) ─────────────────────────\n\n private async handleBroadcast(request: Request): Promise<Response> {\n const incoming = (await request.json()) as TurnStreamEvent\n const data = (incoming.data ?? {}) as Record<string, unknown>\n const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined\n let outgoing: TurnStreamEvent = incoming\n\n if (incoming.type === 'thread.activity') {\n const threadId = typeof data.threadId === 'string' ? data.threadId : undefined\n if (threadId) {\n const active = await this.loadActiveThreads()\n if (data.phase === 'end') active.delete(threadId)\n else active.set(threadId, Date.now())\n await this.persistActiveThreads(active)\n }\n } else if (incoming.type === 'thread.created') {\n this.recentCreated.push(incoming)\n if (this.recentCreated.length > MAX_RECENT_CREATED) {\n this.recentCreated = this.recentCreated.slice(-MAX_RECENT_CREATED)\n }\n } else if (typeof data.executionId === 'string') {\n outgoing = appendSegmentEvent(\n this.segments,\n data.executionId,\n incoming,\n this.options.maxSegmentEvents ?? MAX_SEGMENT_EVENTS,\n )\n // A finished turn frees the channel's single-flight lock without waiting\n // for the worker's cooperative release — unless a product task defers it.\n if (isTerminalRunEvent(incoming.type)) {\n if (await this.shouldDeferLockRelease(data.executionId)) {\n await this.deferLockRelease({ executionId: data.executionId })\n } else {\n await this.releaseActiveLock({ executionId: data.executionId })\n }\n }\n }\n\n // Fan out to live sockets for this session. Enumerate accepted sockets so\n // fan-out is correct after WebSocket hibernation (no in-memory socket map).\n // Only `synced` sockets receive live frames; an un-synced socket is still\n // mid-handshake and will pick this event up in its `sync` replay snapshot.\n const message = JSON.stringify(outgoing)\n for (const ws of this.state.getWebSockets()) {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta?.synced) continue\n if (!sessionId || meta.sessionId === sessionId) {\n try {\n ws.send(message)\n } catch {\n // Dead/closing socket — getWebSockets() will stop returning it.\n }\n }\n }\n\n return new Response('OK', { status: 200 })\n }\n\n private async loadActiveThreads(): Promise<Map<string, number>> {\n if (this.activeThreads === null) {\n const stored = await this.state.storage.get<Record<string, number>>(TURN_STREAM_STORAGE_KEYS.activeThreads)\n this.activeThreads = new Map(Object.entries(stored ?? {}))\n }\n return this.activeThreads\n }\n\n private async persistActiveThreads(map: Map<string, number>): Promise<void> {\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.activeThreads, Object.fromEntries(map))\n }\n\n // ── chat-turn lock ────────────────────────────────────────────────────────\n\n protected async loadActiveLock(now = Date.now()): Promise<DurableTurnLock | null> {\n const stored = await this.state.storage.get<DurableTurnLock>(TURN_STREAM_STORAGE_KEYS.lock)\n const lock = activeTurnLock(stored, now)\n if (stored && !lock) await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return lock\n }\n\n private async releaseActiveLock(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Park a release on the lock itself; {@link completeDeferredLockRelease}\n * finishes it once the product's deferred condition settles. */\n private async deferLockRelease(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n if (active.releasePending) return true\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, { ...active, releasePending: true })\n return true\n }\n\n private async handleLockAcquire(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const workspaceId = stringValue(body?.workspaceId)\n const threadId = stringValue(body?.threadId)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n const scope: TurnLockScope | null =\n body?.scope === 'workspace' ? 'workspace' : body?.scope === 'thread' ? 'thread' : null\n const turnId = stringValue(body?.turnId) ?? undefined\n if (!workspaceId || !threadId || !executionId || !lockId || !scope) {\n return jsonResponse({ error: 'Missing workspaceId, threadId, executionId, lockId, or scope' }, 400)\n }\n\n const active = await this.loadActiveLock()\n if (active) {\n return jsonResponse({ acquired: false, active } satisfies TurnLockAcquireResult, 409)\n }\n\n const lock = createTurnLock(\n { workspaceId, threadId, scope, executionId, lockId, ...(turnId ? { turnId } : {}) },\n Date.now(),\n this.options.lockTtlMs ?? TURN_LOCK_TTL_MS,\n )\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, lock)\n return jsonResponse({ acquired: true, lock } satisfies TurnLockAcquireResult)\n }\n\n private async handleLockRelease(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n if (!executionId || !lockId) {\n return jsonResponse({ error: 'Missing executionId or lockId' }, 400)\n }\n\n if (await this.shouldDeferLockRelease(executionId)) {\n const deferred = await this.deferLockRelease({ executionId, lockId })\n return jsonResponse({ released: false, deferred })\n }\n const released = await this.releaseActiveLock({ executionId, lockId })\n return jsonResponse({ released })\n }\n\n private async handleLockReleaseInterrupted(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const threadId = stringValue(body?.threadId)\n const interruptedAt =\n typeof body?.interruptedAt === 'number' && Number.isFinite(body.interruptedAt) ? body.interruptedAt : undefined\n if (!threadId || interruptedAt === undefined) {\n return jsonResponse({ error: 'Missing threadId or interruptedAt' }, 400)\n }\n const turnId = stringValue(body?.turnId) ?? undefined\n\n const active = await this.loadActiveLock()\n if (!active) return jsonResponse({ released: false })\n // A deferred product task holds the scope even against an out-of-band\n // release — the box is still being read.\n if (await this.shouldDeferLockRelease(active.executionId)) {\n return jsonResponse({ released: false })\n }\n if (!interruptedReleaseApplies(active, { threadId, interruptedAt, ...(turnId ? { turnId } : {}) })) {\n return jsonResponse({ released: false })\n }\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return jsonResponse({ released: true })\n }\n\n // ── durable turn-event storage (TurnEventStore backing) ───────────────────\n\n private async handleTurnEventsAppend(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const events = Array.isArray(body?.events) ? body.events : null\n if (!events) return jsonResponse({ error: 'Missing events' }, 400)\n for (const row of events) {\n const seq = (row as { seq?: unknown }).seq\n const event = (row as { event?: unknown }).event\n if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 1 || typeof event !== 'string') {\n return jsonResponse({ error: 'Invalid turn-event row' }, 400)\n }\n }\n for (const row of events as Array<{ seq: number; event: string }>) {\n await this.state.storage.put(turnEventStorageKey(row.seq), row.event)\n }\n return jsonResponse({ appended: events.length })\n }\n\n private async handleTurnEventsRead(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const fromSeq = typeof body?.fromSeq === 'number' && Number.isFinite(body.fromSeq) ? Math.trunc(body.fromSeq) : 0\n const rows = await this.state.storage.list<string>({\n prefix: TURN_STREAM_STORAGE_KEYS.turnEventPrefix,\n start: turnEventStorageKey(fromSeq + 1),\n })\n const events: Array<{ seq: number; event: string }> = []\n for (const [key, event] of rows) {\n const seq = Number(key.slice(TURN_STREAM_STORAGE_KEYS.turnEventPrefix.length))\n if (Number.isFinite(seq) && seq > fromSeq) events.push({ seq, event })\n }\n return jsonResponse({ events })\n }\n\n private async handleTurnStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const status = body?.status\n if (status !== 'running' && status !== 'complete' && status !== 'error') {\n return jsonResponse({ error: 'Invalid status' }, 400)\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnStatus, status)\n return jsonResponse({ ok: true })\n }\n\n private async handleTurnStatusGet(): Promise<Response> {\n const status = await this.state.storage.get<string>(TURN_STREAM_STORAGE_KEYS.turnStatus)\n return jsonResponse({ status: status ?? null })\n }\n\n // ── scope index (listRunning reconnect discovery) ─────────────────────────\n\n private async handleScopeStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const turnId = stringValue(body?.turnId)\n const status = body?.status\n if (!turnId || (status !== 'running' && status !== 'complete' && status !== 'error')) {\n return jsonResponse({ error: 'Invalid scope-status request' }, 400)\n }\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n index[turnId] = { status, updatedAt: Date.now() }\n const entries = Object.entries(index)\n if (entries.length > MAX_SCOPE_TURNS) {\n // Prune oldest terminal entries first; a running turn is never dropped.\n const removable = entries\n .filter(([, entry]) => entry.status !== 'running')\n .sort((a, b) => a[1].updatedAt - b[1].updatedAt)\n for (const [id] of removable.slice(0, entries.length - MAX_SCOPE_TURNS)) {\n delete index[id]\n }\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnScope, index)\n return jsonResponse({ ok: true })\n }\n\n private async handleScopeRunningList(): Promise<Response> {\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n const running = Object.entries(index)\n .filter(([, entry]) => entry.status === 'running')\n .sort((a, b) => b[1].updatedAt - a[1].updatedAt)\n .map(([turnId]) => turnId)\n return jsonResponse({ running })\n }\n}\n","/**\n * Worker-side adapters over {@link TurnStreamDO}: the concrete implementations\n * of the chat vertical's `turnStore` and `turnLock` seams, the WebSocket\n * upgrade forwarder, the best-effort broadcast helpers, and an in-process\n * memory harness for tests and keyless local dev.\n *\n * Everything takes the namespace STRUCTURALLY ({@link TurnStreamNamespaceLike}\n * — Cloudflare's `DurableObjectNamespace` satisfies it), so nothing here\n * imports Cloudflare types and the same adapters run against the memory\n * harness in vitest.\n *\n * Live fanout is deliberately NOT a side effect of the turn-event store: the\n * store is keyed by turnId/scopeId while viewer sockets live on the\n * `${workspaceId}:${threadId}` channel, and only the product's per-turn\n * context knows both. Products wire {@link broadcastTurnStreamEvent} (and the\n * workspace helpers) into `createChatTurnRoutes`' `onEvent` — the same\n * contract the reference consumer already runs.\n */\n\nimport { reconcileStaleTurnLock, type ReconcileStaleTurnLockOptions } from '../chat-routes/stale-turn-lock'\nimport type { TurnEventStore, TurnStatus } from '../stream/turn-buffer'\nimport {\n TURN_STREAM_PATHS,\n scopeIndexChannelKey,\n threadChannelKey,\n turnLockChannelKey,\n turnStorageChannelKey,\n workspaceChannelKey,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockReleaseInput,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural namespace ────────────────────────────────────────────────────\n\nexport interface TurnStreamStubLike {\n fetch(input: Request | string, init?: RequestInit): Promise<Response>\n}\n\n/** The surface of `DurableObjectNamespace` the adapters use. */\nexport interface TurnStreamNamespaceLike {\n idFromName(name: string): unknown\n get(id: unknown): TurnStreamStubLike\n}\n\nconst INTERNAL_ORIGIN = 'https://turn-stream.internal'\n\nasync function postJson<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<{ status: number; body: T }> {\n const stub = namespace.get(namespace.idFromName(channelKey))\n const response = await stub.fetch(`${INTERNAL_ORIGIN}${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n return { status: response.status, body: (await response.json()) as T }\n}\n\nasync function postJsonOk<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const result = await postJson<T>(namespace, channelKey, path, body)\n if (result.status !== 200) {\n throw new Error(`turn-stream ${path} failed with status ${result.status}`)\n }\n return result.body\n}\n\n// ── TurnEventStore adapter (the real turnStore) ─────────────────────────────\n\n/**\n * A {@link TurnEventStore} backed by {@link TurnStreamDO} storage — the\n * production implementation of `createChatTurnRoutes`' `turnStore` seam for\n * apps that don't run D1 for turn events (or want replay co-located with the\n * live channel). Each buffered turn lives on its own `turn:<turnId>` DO\n * instance; `listRunning` reconnect discovery rides a per-scope index\n * instance. Drops in wherever `createD1TurnEventStore(env.DB)` would.\n */\nexport function createDurableObjectTurnEventStore(namespace: TurnStreamNamespaceLike): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnEventsAppend, { events })\n },\n async read(turnId, fromSeq) {\n const body = await postJsonOk<{ events: Array<{ seq: number; event: string }> }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnEventsRead,\n { fromSeq },\n )\n return body.events\n },\n async setStatus(turnId, status, scopeId) {\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnStatusSet, { status })\n if (scopeId) {\n await postJsonOk(namespace, scopeIndexChannelKey(scopeId), TURN_STREAM_PATHS.scopeStatusSet, {\n turnId,\n status,\n })\n }\n },\n async getStatus(turnId) {\n const body = await postJsonOk<{ status: TurnStatus | null }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnStatusGet,\n {},\n )\n return body.status\n },\n async listRunning(scopeId) {\n const body = await postJsonOk<{ running: string[] }>(\n namespace,\n scopeIndexChannelKey(scopeId),\n TURN_STREAM_PATHS.scopeRunningList,\n {},\n )\n return body.running\n },\n }\n}\n\n// ── lock primitives (usable outside the route seam too) ─────────────────────\n\nexport interface AcquireDurableTurnLockInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n turnId?: string\n /** Supply to reclaim/retry with a stable id; default mints a UUID. */\n lockId?: string\n}\n\nexport async function acquireDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: AcquireDurableTurnLockInput,\n): Promise<TurnLockAcquireResult> {\n const lockId = input.lockId ?? crypto.randomUUID()\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n const result = await postJson<TurnLockAcquireResult>(namespace, key, TURN_STREAM_PATHS.lockAcquire, {\n ...input,\n lockId,\n })\n if (result.status !== 200 && result.status !== 409) {\n throw new Error(`turn-stream lock acquire failed with status ${result.status}`)\n }\n return result.body\n}\n\nexport async function releaseDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: TurnLockReleaseInput,\n): Promise<{ released: boolean; deferred?: boolean }> {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n return postJsonOk(namespace, key, TURN_STREAM_PATHS.lockRelease, input)\n}\n\nexport interface ReleaseInterruptedDurableTurnLockInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread (a stop button\n * doesn't know which lane the wedged turn ran on). */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** Fenced out-of-band release — the DO refuses a successor lock (started\n * after `interruptedAt`) and a turnId mismatch. Returns whether any scope\n * released. */\nexport async function releaseInterruptedDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: ReleaseInterruptedDurableTurnLockInput,\n): Promise<boolean> {\n const scopes: readonly TurnLockScope[] = input.scope ? [input.scope] : ['workspace', 'thread']\n for (const scope of scopes) {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, scope)\n const result = await postJsonOk<{ released: boolean }>(namespace, key, TURN_STREAM_PATHS.lockReleaseInterrupted, {\n threadId: input.threadId,\n interruptedAt: input.interruptedAt,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n })\n if (result.released) return true\n }\n return false\n}\n\n// ── stale-lock reconciliation against the DO ────────────────────────────────\n\nexport interface ReconcileStaleDurableTurnLockOptions\n extends Pick<\n ReconcileStaleTurnLockOptions,\n 'probeSandbox' | 'probeSession' | 'graceMs' | 'terminalGraceMs' | 'context' | 'log' | 'now'\n > {\n namespace: TurnStreamNamespaceLike\n workspaceId: string\n threadId: string\n /** The lock the acquire attempt was refused on. */\n active: DurableTurnLock\n}\n\n/**\n * `/chat-routes`' `reconcileStaleTurnLock` policy wired to the DO: the\n * product supplies the probes (which box, what its session says), the policy\n * decides, and a release lands as a FENCED interrupted release —\n * `interruptedAt` is the policy's `fence.observedAt`, so a successor lock\n * acquired while the probes were in flight survives by construction.\n */\nexport async function reconcileStaleDurableTurnLock(\n options: ReconcileStaleDurableTurnLockOptions,\n): Promise<{ released: boolean; diagnostics: Record<string, unknown> }> {\n const { namespace, workspaceId, threadId, active, ...policy } = options\n return reconcileStaleTurnLock({\n ...policy,\n lockStartedAt: active.startedAt,\n release: (fence) =>\n releaseInterruptedDurableTurnLock(namespace, {\n workspaceId,\n threadId: active.threadId,\n scope: active.scope,\n interruptedAt: fence.observedAt,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n }),\n })\n}\n\n// ── ChatTurnLock adapter (the turnLock seam) ────────────────────────────────\n\n/** The subset of `ChatTurnProduceArgs` the lock adapter reads — structural,\n * so this module needs no import from `/chat-routes/turn-routes` (which\n * would drag the `agent-runtime` peer into every `/turn-stream` consumer). */\nexport interface TurnLockSeamArgs<TContext> {\n identity: { tenantId: string; sessionId: string }\n executionId: string\n context: TContext\n body: { turnId?: string }\n}\n\n/** Verdict shape of the vertical's `turnLock.acquire`. */\nexport type TurnLockSeamResult =\n | { acquired: true; handle?: unknown }\n | { acquired: false; response: Response }\n\nexport interface CreateDurableTurnLockOptions<TContext> {\n namespace: TurnStreamNamespaceLike\n /** Which lane serializes this turn: `'workspace'` (shared sandbox — one\n * turn per workspace) or `'thread'` (router lane — one turn per thread). */\n scopeOf(args: TurnLockSeamArgs<TContext>): TurnLockScope\n /** Override the execution id the lock records (e.g. a follow-up reclaiming\n * the execution it already dispatched). Default: the turn's own. */\n lockExecutionIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /** Client turn id recorded on the lock (fences interrupted releases to the\n * right turn). Default: the request body's `turnId`. */\n clientTurnIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /**\n * Attempt stale-lock recovery after a refused acquire. Return whether the\n * held lock was released (the adapter then retries the acquire once).\n * Products wire {@link reconcileStaleDurableTurnLock} with their sandbox +\n * session probes here. Omit → a refused acquire is final.\n */\n reconcile?(\n args: TurnLockSeamArgs<TContext>,\n active: DurableTurnLock,\n ): Promise<{ released: boolean; diagnostics?: Record<string, unknown> }>\n /** Build the refusal `Response`. Default: a 409 with the shared body shape\n * (`code`, `message`, lock identity + age, reconcile diagnostics). */\n onRefused?(active: DurableTurnLock, diagnostics: Record<string, unknown> | undefined): Response\n}\n\nfunction defaultRefusalResponse(\n active: DurableTurnLock,\n diagnostics: Record<string, unknown> | undefined,\n): Response {\n const workspaceConflict = active.scope === 'workspace'\n return Response.json(\n {\n error: {\n code: workspaceConflict ? 'workspace_turn_in_flight' : 'chat_turn_in_flight',\n message: workspaceConflict\n ? 'A chat turn is already running for this workspace. Wait for it to finish before starting another.'\n : 'A chat turn is already running for this thread. Reconnect to the active response or wait for it to finish.',\n threadId: active.threadId,\n executionId: active.executionId,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n lockStartedAt: active.startedAt,\n lockAgeMs: Date.now() - active.startedAt,\n ...(diagnostics ? { diagnostics } : {}),\n },\n },\n { status: 409 },\n )\n}\n\n/**\n * The vertical's `turnLock` seam on the shared DO: dual-scope single-flight\n * acquire (with one reconcile-then-retry pass when the product supplies a\n * stale-lock reconciler) and cooperative release on settle. The returned\n * object satisfies `createChatTurnRoutes`' `ChatTurnLock<TContext>`\n * structurally.\n */\nexport function createDurableTurnLock<TContext>(options: CreateDurableTurnLockOptions<TContext>): {\n acquire(args: TurnLockSeamArgs<TContext>): Promise<TurnLockSeamResult>\n release(handle: unknown): Promise<void>\n} {\n return {\n async acquire(args) {\n const workspaceId = args.identity.tenantId\n const threadId = args.identity.sessionId\n const scope = options.scopeOf(args)\n const executionId = options.lockExecutionIdOf?.(args) ?? args.executionId\n const turnId = options.clientTurnIdOf ? options.clientTurnIdOf(args) : args.body.turnId\n const input: AcquireDurableTurnLockInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n ...(turnId ? { turnId } : {}),\n }\n\n let acquired = await acquireDurableTurnLock(options.namespace, input)\n let diagnostics: Record<string, unknown> | undefined\n if (!acquired.acquired && options.reconcile) {\n const reconciled = await options.reconcile(args, acquired.active)\n diagnostics = reconciled.diagnostics\n if (reconciled.released) {\n acquired = await acquireDurableTurnLock(options.namespace, input)\n }\n }\n if (!acquired.acquired) {\n return {\n acquired: false,\n response: (options.onRefused ?? defaultRefusalResponse)(acquired.active, diagnostics),\n }\n }\n const handle: TurnLockReleaseInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n lockId: acquired.lock.lockId,\n }\n return { acquired: true, handle }\n },\n async release(handle) {\n if (!handle) return\n await releaseDurableTurnLock(options.namespace, handle as TurnLockReleaseInput)\n },\n }\n}\n\n// ── broadcast helpers (products wire these into onEvent) ────────────────────\n\n/**\n * Fan a turn event out to the per-thread channel. `executionId` groups events\n * into a per-turn segment with a monotonic seq, so a reconnecting client\n * replays only the active turn and resumes from a cursor. Callers MUST await\n * (the DO assigns seq on arrival — emission order matters); failures are\n * swallowed (fanout is best-effort and never breaks chat delivery).\n */\nexport async function broadcastTurnStreamEvent(\n namespace: TurnStreamNamespaceLike,\n input: {\n workspaceId: string\n threadId: string\n executionId: string\n event: { type: string; data?: Record<string, unknown> }\n },\n): Promise<void> {\n try {\n await postJson(namespace, threadChannelKey(input.workspaceId, input.threadId), TURN_STREAM_PATHS.broadcast, {\n type: input.event.type,\n timestamp: Date.now(),\n data: {\n ...(input.event.data ?? {}),\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n sessionId: input.threadId,\n executionId: input.executionId,\n },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort.\n }\n}\n\n/** Coarse per-workspace marker that a thread's turn started / ended — drives\n * a sidebar \"agent responding\" indicator subscribed once per workspace. */\nexport async function broadcastWorkspaceActivity(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n threadId: string,\n phase: 'start' | 'end',\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.activity',\n timestamp: Date.now(),\n data: { threadId, phase, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the indicator is non-critical UI.\n }\n}\n\n/** Per-workspace marker that a new thread was created, so an already-open\n * history list prepends it without a reload. */\nexport async function broadcastThreadCreated(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n thread: { threadId: string; title: string },\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.created',\n timestamp: Date.now(),\n data: { threadId: thread.threadId, title: thread.title, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the loader reconciles on the next revalidation.\n }\n}\n\n// ── WebSocket upgrade forwarder (worker entry) ──────────────────────────────\n\nexport type TurnStreamUpgradeAuthorization = { ok: true } | { ok: false; response: Response }\n\nexport interface CreateTurnStreamUpgradeHandlerOptions {\n namespace: TurnStreamNamespaceLike\n /** The worker route serving the stream. Default `/api/session-stream`. */\n path?: string\n /** Viewer access check (session cookie → workspace membership). */\n authorize(\n request: Request,\n target: { workspaceId: string; threadId: string | null },\n ): Promise<TurnStreamUpgradeAuthorization>\n}\n\n/**\n * The worker-entry WebSocket forwarder. Call BEFORE the app router (a router\n * loader cannot return a 101); returns `null` for requests that are not a\n * WebSocket upgrade on the configured path.\n *\n * GET {path}?workspaceId=... → workspace channel (sidebar)\n * GET {path}?workspaceId=...&threadId=… → thread channel (turn resume)\n *\n * After the 101, the client sends `{type:'sync', afterSeq}` and receives the\n * replay-then-live stream (see {@link TurnStreamDO.webSocketMessage}).\n */\nexport function createTurnStreamUpgradeHandler(\n options: CreateTurnStreamUpgradeHandlerOptions,\n): (request: Request) => Promise<Response | null> {\n const path = options.path ?? '/api/session-stream'\n return async (request) => {\n const url = new URL(request.url)\n if (url.pathname !== path || request.headers.get('Upgrade') !== 'websocket') return null\n\n const workspaceId = url.searchParams.get('workspaceId')\n const threadId = url.searchParams.get('threadId')\n if (!workspaceId) return new Response('Missing workspaceId', { status: 400 })\n\n const auth = await options.authorize(request, { workspaceId, threadId })\n if (!auth.ok) return auth.response\n\n // threadId present → per-thread turn channel; absent → workspace channel.\n const key = threadId ? threadChannelKey(workspaceId, threadId) : workspaceChannelKey(workspaceId)\n const sessionId = threadId ?? workspaceId\n const stub = options.namespace.get(options.namespace.idFromName(key))\n\n const forwardUrl = new URL(request.url)\n forwardUrl.searchParams.set('sessionId', sessionId)\n forwardUrl.searchParams.set('scope', threadId ? 'thread' : 'workspace')\n return stub.fetch(new Request(forwardUrl, request))\n }\n}\n","/**\n * In-process turn-stream harness: a fake namespace that routes `idFromName`\n * to REAL {@link TurnStreamDO} instances over an in-memory storage/socket\n * state. The adapters and the DO run their production code paths — only the\n * Cloudflare runtime (isolation, hibernation, real sockets) is simulated.\n *\n * For vitest composition tests and keyless local dev (the same role\n * `createMemoryTurnEventStore` plays for the D1 store). Not for production:\n * state is per-process and evaporates on restart.\n */\n\nimport { TurnStreamDO, type TurnStreamDOOptions, type TurnStreamDOState, type TurnStreamSocket, type TurnStreamStorage } from './do'\nimport type { TurnStreamNamespaceLike, TurnStreamStubLike } from './adapters'\n\nfunction createMemoryStorage(): TurnStreamStorage {\n const map = new Map<string, unknown>()\n return {\n async get<T>(key: string) {\n return map.get(key) as T | undefined\n },\n async put(key, value) {\n map.set(key, value)\n },\n async delete(key) {\n return map.delete(key)\n },\n async list<T>({ prefix, start }: { prefix: string; start?: string }) {\n const keys = [...map.keys()]\n .filter((key) => key.startsWith(prefix) && (start === undefined || key >= start))\n .sort()\n return new Map(keys.map((key) => [key, map.get(key) as T]))\n },\n }\n}\n\n/** A test-side viewer socket: records frames sent by the DO and lets the\n * test drive the `sync` handshake. */\nexport interface MemoryTurnStreamSocket extends TurnStreamSocket {\n readonly frames: string[]\n readonly closed: boolean\n}\n\nexport interface MemoryTurnStreamChannel {\n /** Attach a viewer socket to this channel (bypasses the HTTP 101 — the\n * upgrade handshake is Cloudflare-runtime-only) and run its `sync`. */\n connect(input: { sessionId: string; scope: 'thread' | 'workspace'; afterSeq?: number }): Promise<MemoryTurnStreamSocket>\n readonly instance: TurnStreamDO\n}\n\nexport interface MemoryTurnStreamHarness {\n namespace: TurnStreamNamespaceLike\n /** The channel (creating its DO instance if needed) for a channel key —\n * e.g. `threadChannelKey(ws, thread)` — to connect test viewers. */\n channel(name: string): MemoryTurnStreamChannel\n}\n\ninterface MemoryInstance {\n instance: TurnStreamDO\n sockets: MemoryTurnStreamSocket[]\n}\n\n/**\n * Build the harness. `createInstance` lets a product test run its own\n * `TurnStreamDO` subclass through the same wiring.\n */\nexport function createMemoryTurnStreamHarness(\n createInstance: (state: TurnStreamDOState) => TurnStreamDO = (state) => new TurnStreamDO(state),\n _options: TurnStreamDOOptions = {},\n): MemoryTurnStreamHarness {\n const instances = new Map<string, MemoryInstance>()\n\n function ensure(name: string): MemoryInstance {\n let entry = instances.get(name)\n if (!entry) {\n const sockets: MemoryTurnStreamSocket[] = []\n const state: TurnStreamDOState = {\n storage: createMemoryStorage(),\n acceptWebSocket: (ws) => sockets.push(ws as MemoryTurnStreamSocket),\n getWebSockets: () => sockets.filter((ws) => !ws.closed),\n }\n entry = { instance: createInstance(state), sockets }\n instances.set(name, entry)\n }\n return entry\n }\n\n const namespace: TurnStreamNamespaceLike = {\n idFromName: (name) => name,\n get(id): TurnStreamStubLike {\n const entry = ensure(String(id))\n return {\n fetch: (input, init) =>\n entry.instance.fetch(typeof input === 'string' ? new Request(input, init) : input),\n }\n },\n }\n\n return {\n namespace,\n channel(name) {\n const entry = ensure(name)\n return {\n instance: entry.instance,\n async connect({ sessionId, scope, afterSeq = 0 }) {\n let attachment: unknown = null\n const frames: string[] = []\n let closed = false\n const socket: MemoryTurnStreamSocket = {\n get frames() {\n return frames\n },\n get closed() {\n return closed\n },\n send(data) {\n if (closed) throw new Error('socket closed')\n frames.push(data)\n },\n close() {\n closed = true\n },\n serializeAttachment(value) {\n attachment = value\n },\n deserializeAttachment() {\n return attachment\n },\n }\n socket.serializeAttachment({ sessionId, scope, synced: false })\n entry.sockets.push(socket)\n await entry.instance.webSocketMessage(socket, JSON.stringify({ type: 'sync', afterSeq }))\n return socket\n },\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,14 +5,15 @@ export { d as ComposerAnswerDelivery, I as INTERACTION_CANCEL_EVENT, e as INTERA
|
|
|
5
5
|
import { ChatPlan } from '../plans/index.js';
|
|
6
6
|
import { InteractionData } from '@tangle-network/agent-interface';
|
|
7
7
|
export { InteractionData, InteractionOutcome, InteractionRequest } from '@tangle-network/agent-interface';
|
|
8
|
-
import {
|
|
9
|
-
export {
|
|
8
|
+
import { K as FileMention, f as ChatMentionPart } from '../parts-IB-Kbb7z.js';
|
|
9
|
+
export { O as ChatAttachmentInput, C as ChatAttachmentKind, a as ChatAttachmentPart, e as ChatMentionKind, N as ChatTurnFilePartInput, M as ChatTurnPartInput, L as ChatTurnRequestPayload, R as DISPATCH_MAX_MEDIA_PARTS, T as DISPATCH_MAX_PARTS, U as DISPATCH_REQUEST_MAX_BYTES, V as DISPATCH_STRUCTURAL_RESERVE_BYTES, t as attachmentInputToPart, u as attachmentKindForMime, v as attachmentPartsFromMessageParts, $ as base64WireLen, a0 as buildMentionPromptBlock, a1 as chatTurnRequestInit, a2 as fileMentionsToParts, y as isChatAttachmentPart, a4 as mediaTypeForMentionPath, H as mentionInputToPart, a5 as mentionKindForPath, I as mentionPartsFromMessageParts } from '../parts-IB-Kbb7z.js';
|
|
10
10
|
import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
|
|
11
11
|
import { a as FlowTrace } from '../flow-types-Cb_AblZs.js';
|
|
12
12
|
export { S as SandboxTerminalConnection, a as SandboxTerminalConnectionResponse, U as UseSandboxTerminalConnectionOptions, b as UseSandboxTerminalConnectionResult, t as tabTerminalConnectionId, u as useSandboxTerminalConnection } from '../sandbox-terminal-BIIC__CP.js';
|
|
13
13
|
import { CatalogModel } from '../catalog/index.js';
|
|
14
14
|
import { Harness } from '../harness/index.js';
|
|
15
|
-
export { b as FileIndexReadyResponse, c as FileIndexResponse, d as FileIndexWarmingResponse } from '../file-index-
|
|
15
|
+
export { b as FileIndexReadyResponse, c as FileIndexResponse, d as FileIndexWarmingResponse } from '../file-index-b26ee-_R.js';
|
|
16
|
+
export { d as attachmentPartKey } from '../stream-normalizer-DWvtmY6F.js';
|
|
16
17
|
|
|
17
18
|
type DurablePlanDecision = 'approved' | 'rejected';
|
|
18
19
|
/** Stable authority receipt for the follow-up turn dispatched by a plan
|
package/dist/web-react/index.js
CHANGED
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
useSmoothText,
|
|
69
69
|
useThinkingSeconds,
|
|
70
70
|
waterfallLayout
|
|
71
|
-
} from "../chunk-
|
|
71
|
+
} from "../chunk-PHIXZC6Y.js";
|
|
72
72
|
import {
|
|
73
73
|
tabTerminalConnectionId,
|
|
74
74
|
useSandboxTerminalConnection
|
|
@@ -76,14 +76,26 @@ import {
|
|
|
76
76
|
import "../chunk-LCNY3DCM.js";
|
|
77
77
|
import "../chunk-2QI7XV2T.js";
|
|
78
78
|
import {
|
|
79
|
+
DISPATCH_MAX_MEDIA_PARTS,
|
|
80
|
+
DISPATCH_MAX_PARTS,
|
|
81
|
+
DISPATCH_REQUEST_MAX_BYTES,
|
|
82
|
+
DISPATCH_STRUCTURAL_RESERVE_BYTES,
|
|
83
|
+
attachmentInputToPart,
|
|
84
|
+
attachmentKindForMime,
|
|
85
|
+
attachmentPartsFromMessageParts,
|
|
86
|
+
base64WireLen,
|
|
79
87
|
buildMentionPromptBlock,
|
|
80
88
|
chatTurnRequestInit,
|
|
81
89
|
fileMentionsToParts,
|
|
90
|
+
isChatAttachmentPart,
|
|
82
91
|
mediaTypeForMentionPath,
|
|
83
92
|
mentionInputToPart,
|
|
84
93
|
mentionKindForPath,
|
|
85
94
|
mentionPartsFromMessageParts
|
|
86
|
-
} from "../chunk-
|
|
95
|
+
} from "../chunk-JGYOYY5D.js";
|
|
96
|
+
import {
|
|
97
|
+
attachmentPartKey
|
|
98
|
+
} from "../chunk-5MG74GVQ.js";
|
|
87
99
|
import {
|
|
88
100
|
INTERACTION_CANCEL_EVENT,
|
|
89
101
|
INTERACTION_EVENT,
|
|
@@ -120,6 +132,10 @@ export {
|
|
|
120
132
|
DEFAULT_EFFORT_LEVELS,
|
|
121
133
|
DEFAULT_MENTION_EMPTY_TEXT,
|
|
122
134
|
DEFAULT_MENTION_LIMIT,
|
|
135
|
+
DISPATCH_MAX_MEDIA_PARTS,
|
|
136
|
+
DISPATCH_MAX_PARTS,
|
|
137
|
+
DISPATCH_REQUEST_MAX_BYTES,
|
|
138
|
+
DISPATCH_STRUCTURAL_RESERVE_BYTES,
|
|
123
139
|
DurableChatCards,
|
|
124
140
|
DurablePlanCard,
|
|
125
141
|
DurablePlanClientError,
|
|
@@ -142,6 +158,11 @@ export {
|
|
|
142
158
|
RunDrillIn,
|
|
143
159
|
SeatPaywall,
|
|
144
160
|
activityTone,
|
|
161
|
+
attachmentInputToPart,
|
|
162
|
+
attachmentKindForMime,
|
|
163
|
+
attachmentPartKey,
|
|
164
|
+
attachmentPartsFromMessageParts,
|
|
165
|
+
base64WireLen,
|
|
145
166
|
buildAnswerData,
|
|
146
167
|
buildMentionPromptBlock,
|
|
147
168
|
canTransitionInteractionStatus,
|
|
@@ -175,6 +196,7 @@ export {
|
|
|
175
196
|
interactionSubmissionSignature,
|
|
176
197
|
interactionTerminalNotes,
|
|
177
198
|
interactionToPersistedPart,
|
|
199
|
+
isChatAttachmentPart,
|
|
178
200
|
isLateAnswerableStatus,
|
|
179
201
|
isRenderableInteractionKind,
|
|
180
202
|
isSafeInteractionFieldKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.48",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|
|
@@ -172,6 +172,11 @@
|
|
|
172
172
|
"import": "./dist/stream/index.js",
|
|
173
173
|
"default": "./dist/stream/index.js"
|
|
174
174
|
},
|
|
175
|
+
"./turn-stream": {
|
|
176
|
+
"types": "./dist/turn-stream/index.d.ts",
|
|
177
|
+
"import": "./dist/turn-stream/index.js",
|
|
178
|
+
"default": "./dist/turn-stream/index.js"
|
|
179
|
+
},
|
|
175
180
|
"./integrations": {
|
|
176
181
|
"types": "./dist/integrations/index.d.ts",
|
|
177
182
|
"import": "./dist/integrations/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stream/stream-normalizer.ts","../src/stream/turn-identity.ts","../src/stream/turn-buffer.ts"],"sourcesContent":["import {\n canTransitionInteractionStatus,\n persistedPartToInteraction,\n type ChatInteractionStatus,\n} from '../interactions/contract'\nimport {\n canTransitionPlanStatus,\n persistedPartToPlan,\n planPartKey,\n planToPersistedPart,\n type ChatPlanStatus,\n} from '../plans/index'\n\nexport type JsonRecord = Record<string, unknown>\n\nexport interface StreamEvent {\n type: string\n data?: JsonRecord\n}\n\nexport function asRecord(value: unknown): JsonRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as JsonRecord\n : undefined\n}\n\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveToolId(part: JsonRecord): string {\n return String(\n part.id ??\n part.callID ??\n part.callId ??\n part.toolUseId ??\n part.toolCallId ??\n part.tool ??\n part.name ??\n `tool-${Date.now()}`,\n )\n}\n\nexport function resolveToolName(part: JsonRecord): string {\n return String(part.tool ?? part.name ?? 'tool')\n}\n\nexport function normalizeTime(value: unknown): JsonRecord | undefined {\n const record = asRecord(value)\n if (!record) return undefined\n\n const start = Number(record.start ?? record.startedAt ?? record.started_at)\n const end = Number(record.end ?? record.completedAt ?? record.completed_at)\n if (!Number.isFinite(start) && !Number.isFinite(end)) return undefined\n\n return {\n start: Number.isFinite(start) ? start : undefined,\n end: Number.isFinite(end) ? end : undefined,\n }\n}\n\nexport function normalizeToolEvent(event: StreamEvent): StreamEvent {\n if (event.type === 'tool_call' || event.type === 'tool.call') {\n const data = event.data ?? {}\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n input: data.arguments ?? data.input,\n status: 'running',\n },\n },\n }\n }\n\n if (event.type === 'tool_result' || event.type === 'tool.result') {\n const data = event.data ?? {}\n const error = asString(data.error)\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n output: data.output,\n error,\n status: error ? 'error' : 'completed',\n },\n },\n }\n }\n\n return event\n}\n\nexport function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null {\n const type = String(rawPart.type ?? '')\n\n if (type === 'text') {\n const id = asString(rawPart.id) ?? asString(rawPart.partId)\n return {\n type: 'text',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n // id: per-segment identity from the harness; absent on legacy parts,\n // which collapse to a single keyed segment. Never invented here.\n ...(id ? { id } : {}),\n }\n }\n\n if (type === 'reasoning') {\n const id = asString(rawPart.id) ?? asString(rawPart.partId)\n return {\n type: 'reasoning',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n time: normalizeTime(rawPart.time),\n ...(id ? { id } : {}),\n }\n }\n\n if (type === 'file' || type === 'image') {\n const id = asString(rawPart.id) ?? asString(rawPart.partId)\n return {\n type,\n ...(id ? { id } : {}),\n ...(asString(rawPart.filename) ? { filename: asString(rawPart.filename) } : {}),\n ...(asString(rawPart.mediaType) ? { mediaType: asString(rawPart.mediaType) } : {}),\n ...(asString(rawPart.url) ? { url: asString(rawPart.url) } : {}),\n ...(asString(rawPart.path) ? { path: asString(rawPart.path) } : {}),\n ...(type === 'file' && asString(rawPart.content) ? { content: asString(rawPart.content) } : {}),\n }\n }\n\n if (type === 'step-start') {\n return { type: 'step-start' }\n }\n\n // The harness's per-step usage receipt. Dropping it here silently loses the\n // turn's token/cost accounting from the persisted transcript.\n if (type === 'step-finish') {\n const tokens = asRecord(rawPart.tokens)\n const cost = Number(rawPart.cost)\n return {\n type: 'step-finish',\n ...(asString(rawPart.reason) ? { reason: asString(rawPart.reason) } : {}),\n ...(tokens ? { tokens } : {}),\n ...(Number.isFinite(cost) ? { cost } : {}),\n }\n }\n\n if (type === 'subtask') {\n const id = asString(rawPart.id) ?? asString(rawPart.partId)\n return {\n type: 'subtask',\n prompt: asString(rawPart.prompt) ?? '',\n description: asString(rawPart.description) ?? '',\n agent: asString(rawPart.agent) ?? '',\n ...(id ? { id } : {}),\n }\n }\n\n if (type === 'interaction') {\n return persistedPartToInteraction(rawPart) ? rawPart : null\n }\n\n if (type === 'plan') {\n const plan = persistedPartToPlan(rawPart)\n return plan ? { ...rawPart, ...planToPersistedPart(plan) } : null\n }\n\n // System-authored notices pass through verbatim; `/chat-store` owns their\n // final typed validation before persistence.\n if (type === 'notice') {\n return rawPart\n }\n\n if (type === 'tool') {\n const state = asRecord(rawPart.state)\n const output = state?.output ?? rawPart.output\n const error = asString(state?.error ?? rawPart.error)\n const terminalError =\n state?.status === 'error' ||\n state?.status === 'failed' ||\n rawPart.status === 'error' ||\n rawPart.status === 'failed' ||\n Boolean(error)\n const status =\n state?.status === 'completed' || rawPart.status === 'completed'\n ? 'completed'\n : terminalError\n ? 'error'\n : output !== undefined\n ? 'completed'\n : 'running'\n\n return {\n type: 'tool',\n id: resolveToolId(rawPart),\n tool: resolveToolName(rawPart),\n callID:\n rawPart.callID != null || rawPart.callId != null\n ? String(rawPart.callID ?? rawPart.callId)\n : undefined,\n state: {\n status,\n input: state?.input ?? rawPart.input,\n output,\n error,\n metadata: asRecord(state?.metadata) ?? asRecord(rawPart.metadata),\n time: normalizeTime(state?.time ?? rawPart.time),\n },\n }\n }\n\n return null\n}\n\n/** Stream/transcript part key for a promoted (path-bearing) attachment,\n * keyed on its storage path — re-emitting the same path folds into the same\n * segment instead of duplicating it. */\nexport function attachmentPartKey(path: string): string {\n return `attachment:${path}`\n}\n\nexport function getPartKey(part: JsonRecord): string {\n const type = String(part.type ?? 'unknown')\n if (type === 'tool') {\n return `tool:${resolveToolId(part)}`\n }\n if (type === 'plan') return planPartKey(String(part.planId ?? ''))\n if ((type === 'file' || type === 'image') && asString(part.path)) {\n return attachmentPartKey(String(part.path))\n }\n\n // Keyed by the part's OWN type so distinct kinds never merge into each\n // other. Untyped parts fall back to the text lane (legacy shape).\n const lane = type && type !== 'unknown' ? type : 'text'\n return `${lane}:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n}\n\n/** Shallow overlay that skips `undefined` incoming values, so a later partial\n * update never erases a field an earlier one captured. */\nfunction overlayDefined(base: JsonRecord, patch: JsonRecord): JsonRecord {\n const out: JsonRecord = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n if (value !== undefined) out[key] = value\n }\n return out\n}\n\nexport function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord {\n const type = String(incoming.type ?? '')\n if (!existing) {\n if (type === 'text' && delta) {\n return { type: 'text', text: delta }\n }\n return incoming\n }\n\n if (type === 'text' && String(existing.type ?? '') === 'text') {\n const existingText = String(existing.text ?? '')\n const incomingText = String(incoming.text ?? '')\n return {\n ...existing,\n ...incoming,\n // An empty snapshot never erases accumulated text (matches reasoning).\n text: delta ? `${existingText}${delta}` : incomingText || existingText,\n }\n }\n\n if (type === 'reasoning' && String(existing.type ?? '') === 'reasoning') {\n const existingText = String(existing.text ?? '')\n const incomingText = String(incoming.text ?? '')\n return {\n ...existing,\n ...incoming,\n text: delta && incomingText === existingText ? `${existingText}${delta}` : incomingText || existingText,\n time: incoming.time ?? existing.time,\n }\n }\n\n if (type === 'tool' && String(existing.type ?? '') === 'tool') {\n const existingState = asRecord(existing.state) ?? {}\n const incomingState = asRecord(incoming.state) ?? {}\n // Overlay only DEFINED incoming fields: a normalized tool part always\n // carries `output`/`error` keys (undefined when not captured), so a plain\n // spread would clobber a completed tool's output with a later empty update.\n const mergedState = overlayDefined(existingState, incomingState)\n // A partial update with no captured status/output/error normalizes to\n // `running`; never let it downgrade a tool that already settled.\n const existingStatus = String(existingState.status ?? '')\n if (\n (existingStatus === 'completed' || existingStatus === 'error') &&\n String(incomingState.status ?? '') === 'running'\n ) {\n mergedState.status = existingStatus\n }\n return {\n ...overlayDefined(existing, incoming),\n state: mergedState,\n }\n }\n\n if (type === 'interaction' && String(existing.type ?? '') === 'interaction') {\n const merged = overlayDefined(existing, incoming)\n const existingStatus = existing.status as ChatInteractionStatus | undefined\n const incomingStatus = incoming.status as ChatInteractionStatus | undefined\n if (\n existingStatus &&\n incomingStatus &&\n existingStatus !== incomingStatus &&\n !canTransitionInteractionStatus(existingStatus, incomingStatus)\n ) {\n merged.status = existingStatus\n }\n if (incoming.answers === undefined && existing.answers !== undefined) {\n merged.answers = existing.answers\n }\n return merged\n }\n\n if (type === 'plan' && String(existing.type ?? '') === 'plan') {\n const existingRevision = Number(existing.revision)\n const incomingRevision = Number(incoming.revision)\n if (Number.isInteger(existingRevision) && Number.isInteger(incomingRevision)) {\n if (incomingRevision < existingRevision) return existing\n if (incomingRevision > existingRevision) return incoming\n }\n const merged = overlayDefined(existing, incoming)\n const existingStatus = existing.status as ChatPlanStatus | undefined\n const incomingStatus = incoming.status as ChatPlanStatus | undefined\n if (\n existingStatus &&\n incomingStatus &&\n existingStatus !== incomingStatus &&\n !canTransitionPlanStatus(existingStatus, incomingStatus)\n ) {\n merged.status = existingStatus\n }\n return merged\n }\n\n return incoming\n}\n\nexport const MISSING_TOOL_TERMINAL_ERROR = 'Tool did not report a terminal result before the assistant turn completed.'\nexport const MISSING_TOOL_TERMINAL_REASON = 'missing-tool-terminal'\n\n/** Closes a tool part left `running` when a stream ended abnormally: settles\n * it as a terminal `error` and stamps `state.metadata.terminalized` so the\n * synthetic settlement is distinguishable from a real tool failure. Parts\n * that already settled (and non-tool parts) pass through untouched. */\nexport function terminalizeDanglingToolPart(part: JsonRecord): JsonRecord {\n if (String(part.type ?? '') !== 'tool') return part\n\n const state = asRecord(part.state) ?? {}\n if (String(state.status ?? part.status ?? '') !== 'running') return part\n\n const metadata = asRecord(state.metadata) ?? {}\n return {\n ...part,\n state: {\n ...state,\n status: 'error',\n error: asString(state.error ?? part.error) ?? MISSING_TOOL_TERMINAL_ERROR,\n metadata: {\n ...metadata,\n terminalized: true,\n terminalReason: MISSING_TOOL_TERMINAL_REASON,\n },\n },\n }\n}\n\nexport function terminalizeDanglingToolParts(parts: JsonRecord[]): JsonRecord[] {\n return parts.map(terminalizeDanglingToolPart)\n}\n\n/** Settles still-pending interaction parts at persist time. The broker\n * guarantees a resolved question either answered (run unblocked, no cancel\n * event) or cancelled/timed out (cancel event already updated the part), so\n * the success path finalizes remaining pendings as `answered` and the\n * failure/terminalize paths as `expired`. */\nexport function finalizePendingInteractionParts(\n parts: JsonRecord[],\n outcome: Extract<ChatInteractionStatus, 'answered' | 'expired'>,\n): JsonRecord[] {\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'interaction') return part\n if (String(part.status ?? '') !== 'pending') return part\n return { ...part, status: outcome }\n })\n}\n\n/** Collapses text-part artifacts of unstable upstream segment identity: the\n * same text arriving under two keys (id-less delta stream, then an\n * id-bearing snapshot) folds into two segments, and interleaved empty\n * segments survive as blank parts. Consecutive identical text parts merge\n * into one; empty text parts drop when any non-empty text part exists. */\nexport function collapseRedundantTextParts(parts: JsonRecord[]): JsonRecord[] {\n const hasNonEmptyText = parts.some(\n (part) => String(part.type ?? '') === 'text' && String(part.text ?? '').trim().length > 0,\n )\n const collapsed: JsonRecord[] = []\n for (const part of parts) {\n if (String(part.type ?? '') !== 'text') {\n collapsed.push(part)\n continue\n }\n const text = String(part.text ?? '')\n if (hasNonEmptyText && text.trim().length === 0) continue\n const previous = collapsed[collapsed.length - 1]\n if (previous && String(previous.type ?? '') === 'text' && String(previous.text ?? '') === text) continue\n collapsed.push(part)\n }\n return collapsed\n}\n\nfunction assembleAssistantParts(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n const parts = partOrder\n .map((key) => partMap.get(key))\n .filter((part): part is JsonRecord => Boolean(part))\n\n const textParts = parts.filter((part) => String(part.type ?? '') === 'text')\n\n if (textParts.length === 0) {\n if (finalText.trim()) {\n parts.push({ type: 'text', text: finalText })\n }\n return parts\n }\n\n // Id-less text parts form a single logical stream — the final text is\n // authoritative for it.\n if (!textParts.some((part) => asString(part.id))) {\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'text') return part\n return {\n ...part,\n text: finalText || String(part.text ?? ''),\n }\n })\n }\n\n // Per-id text segments: invariant is concat(text parts) === persisted final\n // text, so segment boundaries survive without duplicating the answer into\n // every segment.\n const joined = textParts.map((part) => String(part.text ?? '')).join('')\n if (finalText === joined || finalText.trimEnd() === joined.trimEnd()) {\n return parts\n }\n\n if (finalText.startsWith(joined)) {\n // Final text extends the streamed segments (e.g. a failure diagnostic\n // appended after the stream) — persist the remainder as a trailing\n // id-less segment.\n return [...parts, { type: 'text', text: finalText.slice(joined.length) }]\n }\n\n // Final text replaced the streamed text outright. Keep non-text chronology;\n // collapse text to one authoritative segment at the last text position.\n const lastTextPart = textParts[textParts.length - 1]\n return parts\n .filter((part) => String(part.type ?? '') !== 'text' || part === lastTextPart)\n .map((part) => (part === lastTextPart ? { ...part, text: finalText } : part))\n}\n\nexport function finalizeAssistantParts(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n // A stream that ended abnormally can leave tool parts `running` — never\n // persist one; collapsing then removes the duplicate/blank text segments an\n // unstable upstream segment identity produced.\n return collapseRedundantTextParts(terminalizeDanglingToolParts(\n assembleAssistantParts(partOrder, partMap, finalText),\n ))\n}\n\nfunction partStatus(part: JsonRecord | undefined): string {\n const state = asRecord(part?.state)\n return String(state?.status ?? part?.status ?? '')\n}\n\n/** Finalizes, then folds each synthetic tool settlement back into `partMap`\n * and returns just those updates — the shape a streaming loop needs to emit\n * closing `message.part.updated` frames for tools the stream never settled. */\nexport function terminalizeDanglingAssistantToolUpdates(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n const finalizedParts = finalizeAssistantParts(partOrder, partMap, finalText)\n const updates: JsonRecord[] = []\n\n for (const part of finalizedParts) {\n if (String(part.type ?? '') !== 'tool') continue\n\n const key = getPartKey(part)\n const existing = partMap.get(key)\n if (partStatus(existing) !== 'running' || partStatus(part) === 'running') continue\n\n partMap.set(key, mergePersistedPart(existing, part))\n updates.push(part)\n }\n\n return updates\n}\n\nexport function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n","import type { JsonRecord } from './stream-normalizer'\n\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n}\n\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(existingMessages, userContent, turnId)\n if (reusableIndex >= 0) {\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n const latest = messages.at(-1)\n if (latest?.role === 'user' && latest.content === userContent) {\n return messages.length - 1\n }\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n","/**\n * Resumable chat turns — the router-path answer to \"streams resume on\n * disconnect\" (issue #27). A turn's loop events are teed into a store as they\n * stream; the turn keeps running under `ctx.waitUntil` when the client drops;\n * a reconnecting client replays the buffered tail by sequence number and\n * keeps following until the turn completes.\n *\n * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON\n * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON\n *\n * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation\n * ships here because that's what Cloudflare products have (KV is unsuitable:\n * eventually consistent cross-isolate). Per-token deltas would mean hundreds\n * of rows per turn, so consecutive text/reasoning deltas are coalesced within\n * a flush window before they are persisted — replay yields slightly chunkier\n * deltas with identical concatenation.\n */\n\nexport type TurnStatus = 'running' | 'complete' | 'error'\n\nexport interface BufferedTurnEvent {\n seq: number\n /** The serialized event line (JSON string, no trailing newline). */\n event: string\n}\n\nexport interface TurnEventStore {\n append(turnId: string, events: BufferedTurnEvent[]): Promise<void>\n read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>\n /** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets\n * {@link TurnEventStore.listRunning} rediscover this turn after a client reload\n * loses the turnId; stores that don't track scope ignore it. */\n setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>\n getStatus(turnId: string): Promise<TurnStatus | null>\n /** Running turnIds for a scope, newest first — so a reloaded client (clientRunId\n * lost) can find and resume the in-flight turn. Optional: a store records it\n * only if `setStatus` was given a `scopeId`. */\n listRunning?(scopeId: string): Promise<string[]>\n}\n\n// ── coalescing ────────────────────────────────────────────────────────────\n\ntype AnyRecord = Record<string, unknown>\n\nfunction deltaTypeOf(ev: unknown): 'text' | 'reasoning' | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object') return null\n const inner = (e.kind === 'event' ? (e.event as AnyRecord | undefined) : e) as AnyRecord | undefined\n if (!inner || typeof inner !== 'object') return null\n if ((inner.type === 'text' || inner.type === 'reasoning') && typeof inner.text === 'string') {\n return inner.type\n }\n return null\n}\n\n/** Merge consecutive text/reasoning deltas of the same type into one event.\n * Concatenation-preserving: replaying the coalesced stream produces the same\n * accumulated text as the original. */\nexport function coalesceDeltas(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const type = deltaTypeOf(ev)\n const prev = out[out.length - 1]\n if (type && prev && deltaTypeOf(prev) === type) {\n const read = (x: unknown): AnyRecord =>\n ((x as AnyRecord).kind === 'event' ? (x as AnyRecord).event : x) as AnyRecord\n const merged = JSON.parse(JSON.stringify(prev)) as AnyRecord\n read(merged).text = String(read(prev).text) + String(read(ev).text)\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\nfunction asPartUpdate(ev: unknown): { partId: unknown; delta: unknown } | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object' || e.type !== 'message.part.updated') return null\n const data = e.data as AnyRecord | undefined\n if (!data || typeof data !== 'object') return null\n const part = data.part as AnyRecord | undefined\n const partId = part?.id ?? data.partId ?? part?.partId ?? null\n return { partId, delta: data.delta }\n}\n\n/**\n * Coalesce consecutive `message.part.updated` deltas for the SAME part into one\n * event. agent-runtime products stream `ChatStreamEvent` NDJSON\n * (`{type:'message.part.updated', data:{part, delta}}`); pumped through the\n * buffer with the default tool-loop coalescer, every per-token delta persists as\n * its own row because that coalescer never recognizes the shape. Pass this as\n * {@link PumpBufferedTurnOptions.coalesce} instead.\n *\n * Concatenation-preserving for BOTH consumer styles: the merged event keeps the\n * LATEST event's `data.part` (already the cumulative accumulation) and sets\n * `data.delta` to the concatenation of the merged deltas, so a client that\n * appends `delta` and one that reads the cumulative `part` both reconstruct the\n * identical final text.\n */\nexport function coalesceChatStreamEvents(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const cur = asPartUpdate(ev)\n const prevEv = out[out.length - 1]\n const prev = prevEv ? asPartUpdate(prevEv) : null\n if (cur && prev && cur.partId != null && cur.partId === prev.partId) {\n // Base the merged row on the latest event (its `part` is the most complete\n // accumulation); carry forward the summed delta.\n const merged = JSON.parse(JSON.stringify(ev)) as AnyRecord\n ;(merged.data as AnyRecord).delta = String(prev.delta ?? '') + String(cur.delta ?? '')\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\n// ── buffering core (the tap) ────────────────────────────────────────────────\n\nexport interface BufferedTurnOptions {\n store: TurnEventStore\n turnId: string\n /** Deliver one serialized line to the live client. Throwing here (client\n * disconnected) does NOT stop buffering — events keep persisting. */\n write?: (line: string) => Promise<void> | void\n /** Flush buffered events to the store at most this often. Default 400ms. */\n flushIntervalMs?: number\n /** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning\n * deltas). agent-runtime products streaming `ChatStreamEvent` pass\n * {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a\n * row. Must be concatenation-preserving. */\n coalesce?: (events: unknown[]) => unknown[]\n /** Optional scope (thread/session id) recorded with the turn status, so\n * {@link TurnEventStore.listRunning} can find this turn after a reload. */\n scopeId?: string\n}\n\n/** A push-driven buffer for a turn whose producer the caller does NOT own. */\nexport interface BufferedTurnTap {\n /** Buffer one event: persist (coalesced, on the flush window) + best-effort\n * live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime\n * `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */\n onEvent(raw: unknown): Promise<void>\n /** Settle the turn: final flush + set status. Call after the producer resolves\n * ('complete') or rejects ('error'). 'error' flushes what was produced first. */\n done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>\n}\n\n/**\n * The buffering core. Sequence-numbers every event, delivers it to `write`\n * (best-effort — a disconnected client never stops buffering), and flushes to\n * the store in coalesced batches. Drives both transports:\n *\n * • {@link pumpBufferedTurn} — when you OWN an `AsyncIterable` producer.\n * • this tap (`onEvent`/`done`) — when the producer owns iteration and only\n * hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`\n * + the finished body). Durability stays here in the shell; the engine needs\n * no `TurnEventStore` seam.\n */\nexport function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap {\n const flushIntervalMs = opts.flushIntervalMs ?? 400\n const coalesce = opts.coalesce ?? coalesceDeltas\n const startedAt = Date.now()\n let seq = 0\n let clientGone = false\n let pending: unknown[] = []\n let lastFlush = Date.now()\n let started = false\n\n async function flush(): Promise<void> {\n if (pending.length === 0) return\n const batch = coalesce(pending)\n pending = []\n const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }))\n await opts.store.append(opts.turnId, rows)\n lastFlush = Date.now()\n }\n\n async function ensureStarted(): Promise<void> {\n if (started) return\n started = true\n await opts.store.setStatus(opts.turnId, 'running', opts.scopeId)\n }\n\n return {\n async onEvent(raw) {\n await ensureStarted()\n // Stamp ms-since-turn-start so any stored turn is replayable AND traceable\n // (see ../trace) from the same buffered rows.\n const ev = raw && typeof raw === 'object' ? { ...(raw as Record<string, unknown>), _t: Date.now() - startedAt } : raw\n pending.push(ev)\n if (!clientGone && opts.write) {\n try {\n // Live delivery carries a provisional ordering hint, not the persisted\n // seq (coalescing changes seq assignment); clients resume with the\n // seqs from replay, or 0 for \"everything\".\n await opts.write(JSON.stringify(ev))\n } catch {\n clientGone = true\n }\n }\n if (Date.now() - lastFlush >= flushIntervalMs) await flush()\n },\n async done(status = 'complete') {\n await ensureStarted()\n if (status === 'error') {\n await flush().catch(() => {})\n await opts.store.setStatus(opts.turnId, 'error', opts.scopeId).catch(() => {})\n return\n }\n await flush()\n await opts.store.setStatus(opts.turnId, 'complete', opts.scopeId)\n },\n }\n}\n\n// ── pump (producer side) ──────────────────────────────────────────────────\n\nexport interface PumpBufferedTurnOptions extends BufferedTurnOptions {\n source: AsyncIterable<unknown>\n}\n\n/**\n * Drive a turn to completion regardless of the live client, when you OWN the\n * producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.\n * Returns a promise that resolves when the turn finishes — hand it to\n * `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on\n * client-write failure; a source error marks the turn 'error' (after flushing\n * what was produced) and rethrows.\n */\nexport async function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void> {\n const tap = createBufferedTurnTap(opts)\n try {\n for await (const raw of opts.source) await tap.onEvent(raw)\n await tap.done('complete')\n } catch (err) {\n await tap.done('error')\n throw err\n }\n}\n\n// ── replay (consumer side) ────────────────────────────────────────────────\n\nexport interface ReplayTurnEventsOptions {\n store: TurnEventStore\n turnId: string\n /** Replay strictly after this sequence number (0 = from the beginning). */\n fromSeq?: number\n /** Poll cadence while the turn is still running. Default 500ms. */\n pollMs?: number\n /** Give up following a 'running' turn after this long. Default 120s. */\n timeoutMs?: number\n}\n\n/**\n * Yield buffered events after `fromSeq`, then keep polling while the turn is\n * still 'running' until it completes, errors, or times out. Terminates with a\n * final `{seq: -1, event: '{\"type\":\"turn_status\",...}'}` marker so clients\n * know why the replay ended.\n */\nexport async function* replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent> {\n const pollMs = opts.pollMs ?? 500\n const timeoutMs = opts.timeoutMs ?? 120_000\n let cursor = opts.fromSeq ?? 0\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n const batch = await opts.store.read(opts.turnId, cursor)\n for (const row of batch) {\n cursor = Math.max(cursor, row.seq)\n yield row\n }\n const status = await opts.store.getStatus(opts.turnId)\n if (status !== 'running') {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: status ?? 'unknown' }) }\n return\n }\n if (Date.now() >= deadline) {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: 'timeout' }) }\n return\n }\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n// ── D1 store ──────────────────────────────────────────────────────────────\n\n/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */\nexport interface D1LikeForTurns {\n prepare(sql: string): {\n bind(...values: unknown[]): {\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n first<T = Record<string, unknown>>(): Promise<T | null>\n }\n }\n}\n\n/** Schema for the D1 store — append to the product's migrations. */\nexport const TURN_EVENTS_MIGRATION_SQL = `\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n scopeId TEXT,\n updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n`\n\n/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —\n * run once to add the column (the CREATE above already includes it for new\n * deployments). SQLite ignores a duplicate-add error if already applied. */\nexport const TURN_STATUS_SCOPE_MIGRATION_SQL = `ALTER TABLE turn_status ADD COLUMN scopeId TEXT;`\n\nexport function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n // One multi-row insert per flush window keeps write volume bounded.\n const placeholders = events.map(() => '(?, ?, ?)').join(', ')\n const values = events.flatMap((e) => [turnId, e.seq, e.event])\n await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run()\n },\n async read(turnId, fromSeq) {\n const { results } = await db\n .prepare('SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC')\n .bind(turnId, fromSeq)\n .all<{ seq: number; event: string }>()\n return results\n },\n async setStatus(turnId, status, scopeId) {\n // COALESCE preserves a scopeId set on the initial 'running' write when a\n // later 'complete'/'error' write passes none.\n await db\n .prepare(\n 'INSERT INTO turn_status (turnId, status, scopeId, updatedAt) VALUES (?, ?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, scopeId = COALESCE(excluded.scopeId, turn_status.scopeId), updatedAt = excluded.updatedAt',\n )\n .bind(turnId, status, scopeId ?? null, new Date().toISOString())\n .run()\n },\n async getStatus(turnId) {\n const row = await db.prepare('SELECT status FROM turn_status WHERE turnId = ?').bind(turnId).first<{ status: TurnStatus }>()\n return row?.status ?? null\n },\n async listRunning(scopeId) {\n const { results } = await db\n .prepare(\"SELECT turnId FROM turn_status WHERE scopeId = ? AND status = 'running' ORDER BY updatedAt DESC\")\n .bind(scopeId)\n .all<{ turnId: string }>()\n return results.map((r) => r.turnId)\n },\n }\n}\n\n/** In-memory store for tests and keyless local dev. */\nexport function createMemoryTurnEventStore(): TurnEventStore {\n const events = new Map<string, BufferedTurnEvent[]>()\n const status = new Map<string, TurnStatus>()\n const scopes = new Map<string, string>()\n const order: string[] = []\n return {\n async append(turnId, rows) {\n const list = events.get(turnId) ?? []\n list.push(...rows)\n events.set(turnId, list)\n },\n async read(turnId, fromSeq) {\n return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq)\n },\n async setStatus(turnId, s, scopeId) {\n status.set(turnId, s)\n if (scopeId) scopes.set(turnId, scopeId)\n if (!order.includes(turnId)) order.push(turnId)\n },\n async getStatus(turnId) {\n return status.get(turnId) ?? null\n },\n async listRunning(scopeId) {\n // Newest first, mirroring the D1 store's `ORDER BY updatedAt DESC`.\n return [...order].reverse().filter((t) => status.get(t) === 'running' && scopes.get(t) === scopeId)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat-routes/wire.ts","../src/chat-store/parts.ts"],"sourcesContent":["/**\n * Wire contract between the chat client (composer + `streamChatTurn`) and the\n * assembled server vertical (`createChatTurnRoutes`). Import-free on purpose:\n * `/web-react` re-exports these types into browser bundles, so nothing here may\n * reach a Node builtin or an engine package.\n *\n * The part shape mirrors the sandbox SDK's `PromptInputPart` structurally\n * (text | image | file with filename/mediaType/url/path/content) — derived\n * here, not imported, so the client bundle never touches the SDK.\n */\n\nexport interface ChatTurnTextPartInput {\n type: 'text'\n text: string\n}\n\n/** A non-text prompt part the upload route hands back and the client echoes\n * on send. `url` carries an inline `data:` URI for small files; `path` is a\n * sandbox workspace reference for large ones (the >1 MiB gateway body cap\n * makes the two-step upload mandatory). */\nexport interface ChatTurnFilePartInput {\n type: 'image' | 'file'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport type ChatTurnPartInput = ChatTurnTextPartInput | ChatTurnFilePartInput\n\n/** POST body for the turn route. `content` may be empty when `parts` carry the\n * message (an image-only send). Product routing fields (workspaceId etc.) ride\n * alongside and are read by the product's `authorize` seam. */\nexport interface ChatTurnRequestPayload {\n threadId: string\n content?: string\n /** Non-text parts from the upload route, echoed back verbatim. */\n parts?: ChatTurnFilePartInput[]\n /** `@`-picked file mentions for this turn — path references into the\n * workspace sandbox, NOT uploads, so they travel in their own field rather\n * than as `parts` entries. A product whose `parts` field is already spoken\n * for (an attachment sentinel) can still send mentions, and mentions\n * persist as their own `ChatMentionPart`s so a retry rebuilds them. The\n * route validates this field with {@link parseFileMentions} and replaces it\n * on the payload with the validated, deduped list. */\n mentions?: FileMention[]\n model?: string\n effort?: 'auto' | 'low' | 'medium' | 'high'\n harness?: string\n /** Client-generated idempotency key for the logical turn (retry-safe). */\n turnId?: string\n [key: string]: unknown\n}\n\n/** `fetch` init for the turn route — the one place the client wire shape is\n * serialized, so composer glue and products never drift from the server's\n * parser. */\nexport function chatTurnRequestInit(payload: ChatTurnRequestPayload): RequestInit {\n return {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n}\n\n// ── inline-part byte budget ─────────────────────────────────────────────────\n//\n// The sandbox gateway caps request bodies at 1 MiB; a turn body whose inline\n// `data:` parts exceed it dies at the gateway with an opaque 413. Enforce the\n// budget at the route boundary instead, with headroom for the JSON envelope\n// (same fail-loud-at-the-choke-point style as /sandbox's provision-payload and\n// env-size gates).\n\nexport const INLINE_PARTS_MAX_BYTES = 950_000\n\nexport class ChatTurnInputError extends Error {\n constructor(message: string, readonly status = 400, readonly code = 'INVALID_CHAT_TURN') {\n super(message)\n this.name = 'ChatTurnInputError'\n }\n}\n\nfunction partByteSize(part: ChatTurnPartInput): number {\n let bytes = 0\n if (part.type === 'text') return part.text.length\n if (part.url) bytes += part.url.length\n if (part.content) bytes += part.content.length\n if (part.path) bytes += part.path.length\n return bytes\n}\n\nexport function promptPartsByteSize(parts: ChatTurnPartInput[]): number {\n return parts.reduce((total, part) => total + partByteSize(part), 0)\n}\n\n/** Throws `ChatTurnInputError` (413) when the parts' inline payload would blow\n * the gateway cap. Path-ref parts are tiny by construction and always pass. */\nexport function assertPromptPartsWithinCap(\n parts: ChatTurnPartInput[],\n maxBytes = INLINE_PARTS_MAX_BYTES,\n): void {\n const total = promptPartsByteSize(parts)\n if (total <= maxBytes) return\n const largest = [...parts].sort((a, b) => partByteSize(b) - partByteSize(a))[0]\n const largestName = largest && largest.type !== 'text' ? largest.filename ?? largest.path ?? largest.type : 'text'\n throw new ChatTurnInputError(\n `Inline prompt parts total ${total}B, over the ${maxBytes}B budget (largest: ${largestName}, ${largest ? partByteSize(largest) : 0}B). ` +\n 'Upload large files through the upload route so they travel as sandbox path references.',\n 413,\n 'PROMPT_PARTS_TOO_LARGE',\n )\n}\n\n// ── file mentions ────────────────────────────────────────────────────────\n//\n// A file mention (`@`-picked in the composer, sandbox-ui#184) is a path\n// reference into the workspace sandbox — no byte upload. These helpers turn\n// a resolved mention list into wire parts and the prompt pointer block that\n// tells the agent where to read them from.\n\n/** A file mention resolved from the composer's `@`-picker: the\n * workspace-relative path plus enough metadata to build a prompt part and\n * pointer text. `path` is the canonical identity — the mention pill's\n * `MentionItem.id` for the file kind (`/web-react`'s `useFileMentions`). */\nexport interface FileMention {\n path: string\n name: string\n size?: number\n}\n\nconst MENTION_IMAGE_MEDIA_TYPES: ReadonlyMap<string, string> = new Map([\n ['.png', 'image/png'],\n ['.jpg', 'image/jpeg'],\n ['.jpeg', 'image/jpeg'],\n ['.gif', 'image/gif'],\n ['.webp', 'image/webp'],\n ['.svg', 'image/svg+xml'],\n ['.bmp', 'image/bmp'],\n ['.heic', 'image/heic'],\n ['.heif', 'image/heif'],\n ['.avif', 'image/avif'],\n])\n\nfunction extensionOf(path: string): string {\n const base = path.split('/').filter(Boolean).pop() ?? path\n const dot = base.lastIndexOf('.')\n return dot > 0 ? base.slice(dot).toLowerCase() : ''\n}\n\n/** The `image/*` mime for a mention path by extension, or `undefined` for\n * anything not in the known image set (dispatched as `type: 'file'`). */\nexport function mediaTypeForMentionPath(path: string): string | undefined {\n return MENTION_IMAGE_MEDIA_TYPES.get(extensionOf(path))\n}\n\n/** The image/file split a mention is rendered and persisted under — the\n * composer pill's icon, the dispatched part's `type`, and\n * `ChatMentionPart.mentionKind` are all this one value. */\nexport type ChatMentionKind = 'image' | 'file'\n\n/** `image` when the path's extension is in the known image set (the same table\n * {@link mediaTypeForMentionPath} reads), `file` otherwise. Exported so a\n * client that needs only the discriminant — a pill icon, a persisted part's\n * `mentionKind` — never re-declares the extension table; two frozen copies of\n * one mime table is how one gains a format and the other doesn't. */\nexport function mentionKindForPath(path: string): ChatMentionKind {\n return mediaTypeForMentionPath(path) ? 'image' : 'file'\n}\n\nexport interface FileMentionsToPartsOptions {\n /** Resolve a mention's workspace-relative path to the absolute path the\n * dispatched part should carry (e.g. a host prefixing the in-box vault\n * root). Default: identity — the path travels unchanged. */\n resolvePath?: (path: string) => string\n}\n\n/** Maps resolved file mentions to path-only `ChatTurnFilePartInput`s —\n * `image` vs `file` by extension, and always a `path`, never a `url` (the\n * url/path XOR invariant: a mention is a sandbox path reference, never\n * inline bytes). */\nexport function fileMentionsToParts(\n mentions: readonly FileMention[],\n opts: FileMentionsToPartsOptions = {},\n): ChatTurnFilePartInput[] {\n const resolvePath = opts.resolvePath ?? ((path: string) => path)\n return mentions.map((mention) => {\n const mediaType = mediaTypeForMentionPath(mention.path)\n const part: ChatTurnFilePartInput = {\n type: mediaType ? 'image' : 'file',\n filename: mention.name,\n path: resolvePath(mention.path),\n }\n if (mediaType) part.mediaType = mediaType\n return part\n })\n}\n\n/** The agent-facing pointer block appended to the dispatched prompt — never\n * persisted in message `content`. Empty array → `''` so callers can append\n * unconditionally. This is the sole producer of that text: the current\n * turn's dispatch and any history projection built from the same mention\n * list both route through here, so the two can't drift apart. */\nexport function buildMentionPromptBlock(\n mentions: readonly Pick<FileMention, 'name' | 'path'>[],\n): string {\n if (mentions.length === 0) return ''\n const lines = mentions.map((m) => `- ${m.name} (${m.path})`)\n return `\\n\\nMentioned files — read them from these paths:\\n${lines.join('\\n')}`\n}\n\n// ── mention validation ───────────────────────────────────────────────────\n//\n// This package owns BOTH ends of the mention path contract — it emits paths\n// from `createSandboxFileIndexRoute` and consumes them in `fileMentionsToParts`\n// / `buildMentionPromptBlock` — so the validation belongs here rather than in\n// each app that wires the pair up. A mention names a file that already exists\n// in the sandbox, so validation is a pure path/charset/count check with no\n// I/O: existence is proven later, when the agent reads the path and the turn\n// fails loudly if it is gone.\n\n/** Hard cap on mentions per turn. Bounds the prompt pointer block, the\n * persisted parts, and whatever media budget a dispatch draws from them. */\nexport const MENTION_MAX_COUNT = 16\n\n/** Longest mention display name accepted — bounds the pointer-block text and\n * the transcript pill label. */\nconst MAX_MENTION_NAME_LENGTH = 256\n/** Longest mention path accepted. */\nconst MAX_MENTION_PATH_LENGTH = 1024\n\nexport type SandboxMentionPathCheck =\n | { succeeded: true }\n | { succeeded: false; error: string }\n\n/**\n * Validate a workspace-relative sandbox mention path. Rejects traversal (a\n * `..` path segment), absolute paths (leading `/`), backslashes, and null\n * bytes — the four ways a path picked in a client can escape the root the\n * index route scanned.\n *\n * Spaces and unicode are deliberately ALLOWED: in-box filenames are arbitrary,\n * and an ASCII-only charset would silently drop real files from a feature\n * whose whole job is naming them.\n */\nexport function validateSandboxMentionPath(path: unknown): SandboxMentionPathCheck {\n if (typeof path !== 'string' || path.length === 0) {\n return { succeeded: false, error: 'mention path must be a non-empty string' }\n }\n if (path.length > MAX_MENTION_PATH_LENGTH) {\n return { succeeded: false, error: `mention path must not exceed ${MAX_MENTION_PATH_LENGTH} characters` }\n }\n if (path.includes('\\0')) {\n return { succeeded: false, error: 'mention path must not contain null bytes' }\n }\n if (path.includes('\\\\')) {\n return { succeeded: false, error: 'mention path must not contain backslashes' }\n }\n if (path.startsWith('/')) {\n return { succeeded: false, error: 'mention path must be workspace-relative, not absolute' }\n }\n if (path.split('/').some((segment) => segment === '..')) {\n return { succeeded: false, error: 'mention path must not contain \"..\" segments' }\n }\n return { succeeded: true }\n}\n\nfunction parseFileMention(value: unknown, index: number): FileMention {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new ChatTurnInputError(`mentions[${index}] must be an object`)\n }\n const record = value as Record<string, unknown>\n\n const pathCheck = validateSandboxMentionPath(record.path)\n if (!pathCheck.succeeded) throw new ChatTurnInputError(`mentions[${index}]: ${pathCheck.error}`)\n\n const name = record.name\n if (typeof name !== 'string' || !name.trim()) {\n throw new ChatTurnInputError(`mentions[${index}].name must be a non-empty string`)\n }\n if (name.length > MAX_MENTION_NAME_LENGTH) {\n throw new ChatTurnInputError(`mentions[${index}].name must not exceed ${MAX_MENTION_NAME_LENGTH} characters`)\n }\n\n const size = record.size\n if (size !== undefined) {\n if (typeof size !== 'number' || !Number.isFinite(size)) {\n throw new ChatTurnInputError(`mentions[${index}].size must be a finite number`)\n }\n if (size < 0) {\n throw new ChatTurnInputError(`mentions[${index}].size must not be negative`)\n }\n }\n\n return { path: record.path as string, name, ...(typeof size === 'number' ? { size } : {}) }\n}\n\n/**\n * Validates the untyped `mentions` array off the wire, mirroring\n * {@link parseChatTurnParts}: the typed list, or `ChatTurnInputError` (400)\n * naming the offending entry. Never sanitizes-and-continues — a traversal path\n * is a rejected request, not a trimmed one.\n *\n * A path repeated within one turn is deduped to its first occurrence rather\n * than rejected: mentioning the same file twice is plausible user input, not\n * an attack.\n */\nexport function parseFileMentions(raw: unknown): FileMention[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('mentions must be an array')\n if (raw.length > MENTION_MAX_COUNT) {\n throw new ChatTurnInputError(`mentions must not exceed ${MENTION_MAX_COUNT} entries`)\n }\n\n const mentions: FileMention[] = []\n const seenPaths = new Set<string>()\n for (let index = 0; index < raw.length; index += 1) {\n const mention = parseFileMention(raw[index], index)\n if (seenPaths.has(mention.path)) continue\n seenPaths.add(mention.path)\n mentions.push(mention)\n }\n return mentions\n}\n\n/** Validates the untyped `parts` array off the wire. Returns the typed parts\n * or throws `ChatTurnInputError` (400) naming the offending entry. */\nexport function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[] {\n if (raw === undefined || raw === null) return []\n if (!Array.isArray(raw)) throw new ChatTurnInputError('parts must be an array')\n return raw.map((entry, index) => {\n const part = entry as Record<string, unknown> | null\n if (!part || typeof part !== 'object') {\n throw new ChatTurnInputError(`parts[${index}] must be an object`)\n }\n if (part.type !== 'image' && part.type !== 'file') {\n throw new ChatTurnInputError(`parts[${index}].type must be 'image' or 'file'`)\n }\n for (const key of ['filename', 'mediaType', 'url', 'path', 'content'] as const) {\n if (part[key] !== undefined && typeof part[key] !== 'string') {\n throw new ChatTurnInputError(`parts[${index}].${key} must be a string`)\n }\n }\n if (!part.url && !part.path && !part.content) {\n throw new ChatTurnInputError(`parts[${index}] needs a url, path, or content`)\n }\n return {\n type: part.type,\n ...(part.filename !== undefined ? { filename: part.filename as string } : {}),\n ...(part.mediaType !== undefined ? { mediaType: part.mediaType as string } : {}),\n ...(part.url !== undefined ? { url: part.url as string } : {}),\n ...(part.path !== undefined ? { path: part.path as string } : {}),\n ...(part.content !== undefined ? { content: part.content as string } : {}),\n }\n })\n}\n","/**\n * The stored shape of `message.parts` — one typed vocabulary for every part a\n * product persists into a chat transcript. NOT an ad-hoc union reverse-\n * engineered from product schemas; each member is matched field-for-field to\n * its canonical source:\n *\n * - `text` / `reasoning` / `tool`: the persisted projection `/stream`'s\n * `normalizePersistedPart` produces from the harness lane's\n * `message.part.updated` events (ADC sidecar\n * `apps/sidecar/src/events/session-events.ts:56` wraps the canonical part in\n * an `{id, sessionID, messageID}` envelope; the projection strips the\n * session/message ids and keeps the per-segment part id).\n * - `file` / `image` / `step-start` / `step-finish`: the sidecar's canonical\n * `MessagePartSchema` members (ADC\n * `apps/sidecar/src/schemas/agent-schemas.ts:50-154`); `step-finish` carries\n * the harness's per-step usage receipt — tokens\n * `{total, input, output, reasoning, cache{write, read}}` + `cost` — which is\n * also the shape the message-level token/cost columns mirror.\n * - `subtask`: `@tangle-network/agent-interface`'s `SubtaskPart` (a spawned\n * sub-agent task).\n * - `interaction` / `notice`: the persisted-part codecs in\n * `/web-react`'s chat-interactions contract (`interactionToPersistedPart`,\n * `noticePart`) — type-only imports, one source of truth for their statuses\n * and field shapes.\n * - `plan`: the durable-plan projection in `/plans`, derived from the sandbox\n * SDK's authoritative plan lifecycle.\n * - `mention`: an `@`-picked reference to a file that already lives in the\n * workspace sandbox (`FileMention` in `/chat-routes`'s wire contract, plus\n * the image/file discriminant). Neither transport lane produces it — the\n * turn route persists it from the request's `mentions` field — but it is a\n * part a product persists into a transcript, so it belongs in this\n * vocabulary rather than in a parallel one.\n *\n * `@tangle-network/agent-interface` exports the canonical wire `Part` union,\n * but its `PartBase` requires the `sessionID`/`messageID` stream envelope that\n * is deliberately NOT persisted, so the stored union is defined here as the\n * envelope-free projection (a type-level coverage check against the peer's\n * `Part['type']` lives in the tests). Contribute-down candidate: if\n * agent-interface grows envelope-free persisted-part types, re-export them\n * here and delete these definitions.\n *\n * Two transport lanes serialize into this SAME stored shape:\n * - harness lane: canonical `message.part.updated` parts, merged/normalized by\n * `/stream` (`mergePersistedPart`, `finalizeAssistantParts`);\n * - router/openai-compat lane: `text_delta`/`tool_call` stream events are\n * mapped INTO canonical part events first (`/runtime`'s `toLoopEvents` +\n * `/stream`'s `normalizeToolEvent`) and then persisted identically — the\n * store never sees a router-specific shape.\n */\n\nimport type { Part as HarnessWirePart } from '@tangle-network/agent-interface'\nimport type {\n ChatInteractionField,\n ChatInteractionStatus,\n InteractionAnswers,\n InteractionPersistedPart,\n NoticeKind,\n NoticePersistedPart,\n} from '../web-react/chat-interactions'\nimport { persistedPartToInteraction } from '../interactions/contract'\nimport {\n persistedPartToPlan,\n planToPersistedPart,\n type ChatPlanPersistedPart,\n} from '../plans/index'\n// `./wire` is import-free by construction, so this edge costs the store\n// nothing and keeps ONE image-extension table for the whole mention layer.\nimport { mentionKindForPath, type ChatMentionKind, type FileMention } from '../chat-routes/wire'\n\nexport type { ChatMentionKind }\n\n/** Start/end wall-clock millis, as normalized by `/stream`'s `normalizeTime`. */\nexport interface ChatPartTime {\n start?: number\n end?: number\n}\n\n/** `id` is the harness's per-segment identity; absent on legacy/router parts,\n * which collapse to a single logical text stream. Never invented client-side. */\nexport interface ChatTextPart {\n type: 'text'\n text: string\n id?: string\n}\n\nexport interface ChatReasoningPart {\n type: 'reasoning'\n text: string\n id?: string\n time?: ChatPartTime\n}\n\n/** Superset of the sidecar's status enum (`pending|running|completed|failed`)\n * and agent-interface's `ToolState` statuses; `error` is the persisted\n * terminal form `/stream`'s `normalizePersistedPart` settles on. */\nexport type ChatToolStatus = 'pending' | 'running' | 'completed' | 'error' | 'failed'\n\nexport interface ChatToolState {\n status: ChatToolStatus\n input?: unknown\n output?: unknown\n error?: string\n title?: string\n metadata?: Record<string, unknown>\n time?: ChatPartTime\n}\n\nexport interface ChatToolPart {\n type: 'tool'\n id: string\n tool: string\n callID?: string\n state: ChatToolState\n}\n\n/** Union of the sidecar's legacy (path-based) and AI-SDK (url-based) file\n * shapes; response-side every field besides `type` is optional. */\nexport interface ChatFilePart {\n type: 'file'\n id?: string\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n content?: string\n}\n\nexport interface ChatImagePart {\n type: 'image'\n filename?: string\n mediaType?: string\n url?: string\n path?: string\n}\n\nexport interface ChatSubtaskPart {\n type: 'subtask'\n prompt: string\n description: string\n agent: string\n id?: string\n}\n\n/** OpenCode step-boundary marker — no renderable text; preserved so mappers\n * never coerce it into a \"[object Object]\" text part. */\nexport interface ChatStepStartPart {\n type: 'step-start'\n}\n\n/** Per-step usage receipt as the harness reports it (sidecar\n * `StepFinishPartSchema`). The message-level token/cost columns are this\n * shape flattened. */\nexport interface ChatUsageTokens {\n total?: number\n input?: number\n output?: number\n reasoning?: number\n cache?: {\n write?: number\n read?: number\n }\n}\n\nexport interface ChatStepFinishPart {\n type: 'step-finish'\n reason?: string\n tokens?: ChatUsageTokens\n cost?: number\n}\n\n/** Persisted human-in-the-loop ask — byte-matches\n * `interactionToPersistedPart` in `/web-react`'s chat-interactions contract. */\nexport interface ChatInteractionPart {\n type: 'interaction'\n id: string\n kind: string\n title: string\n body?: string\n answerSpec: { fields: ChatInteractionField[] }\n status: ChatInteractionStatus\n answers?: InteractionAnswers\n cancelReason?: string\n}\n\nexport type ChatPlanPart = ChatPlanPersistedPart\n\n/** Persisted one-line transcript notice — byte-matches `noticePart` in\n * `/web-react`'s chat-interactions contract. */\nexport interface ChatNoticePart {\n type: 'notice'\n id: string\n noticeKind: NoticeKind\n text: string\n}\n\n/**\n * A file the user `@`-mentioned on this turn: a workspace-relative path into\n * the sandbox, never bytes. `type: 'mention'` is its own discriminant\n * precisely so it does NOT collide with the `file`/`image` attachment parts —\n * an attachment carries content the product uploaded, a mention points at\n * something the box already has, and a transcript renders them differently\n * (an inline pill, not an attachment card).\n *\n * `path` is the identity: mentioning one file twice in a turn folds to one\n * part. `turnId` is optional and set by products that rebuild a turn's\n * mentions on retry.\n */\nexport interface ChatMentionPart {\n type: 'mention'\n mentionKind: ChatMentionKind\n path: string\n name: string\n size?: number\n turnId?: string\n}\n\n// The \"byte-matches\" claims above, enforced at compile time: the interaction\n// contract's codec output types and the stored part types must stay mutually\n// assignable, so a codec field added on one side without the other fails here.\ntype MutuallyAssignable<A extends B, B> = A\ntype _CodecEmitsStorableInteractionPart = MutuallyAssignable<InteractionPersistedPart, ChatInteractionPart>\ntype _StoredInteractionPartFeedsCodec = MutuallyAssignable<ChatInteractionPart, InteractionPersistedPart>\ntype _CodecEmitsStorableNoticePart = MutuallyAssignable<NoticePersistedPart, ChatNoticePart>\ntype _StoredNoticePartFeedsCodec = MutuallyAssignable<ChatNoticePart, NoticePersistedPart>\ntype _CodecEmitsStorablePlanPart = MutuallyAssignable<ChatPlanPersistedPart, ChatPlanPart>\ntype _StoredPlanPartFeedsCodec = MutuallyAssignable<ChatPlanPart, ChatPlanPersistedPart>\n\nexport type ChatMessagePart =\n | ChatTextPart\n | ChatReasoningPart\n | ChatToolPart\n | ChatFilePart\n | ChatImagePart\n | ChatSubtaskPart\n | ChatStepStartPart\n | ChatStepFinishPart\n | ChatInteractionPart\n | ChatNoticePart\n | ChatPlanPart\n | ChatMentionPart\n\n/** Every canonical harness wire-part kind must be storable — compile-time\n * guarantee that a new agent-interface part kind cannot silently fall out of\n * the persisted vocabulary. */\nexport type StorableHarnessPartKind = HarnessWirePart['type'] & ChatMessagePart['type']\n\n/**\n * The typed projection at the `/stream` → `/chat-store` boundary. The stream\n * normalizers (`normalizePersistedPart`/`mergePersistedPart`/\n * `finalizeAssistantParts`) deliberately produce untyped `JsonRecord`s — they\n * normalize wire shapes and do not own the stored vocabulary. THIS module\n * owns it, so this is where rows gain the `ChatMessagePart` type: each entry\n * is validated against its kind's required fields and narrowed, junk is\n * dropped, and — enforced by the exhaustiveness check below — no storable\n * kind can silently fall out (the step-finish/interaction trap).\n */\nexport function toChatMessageParts(parts: Array<Record<string, unknown>>): ChatMessagePart[] {\n const out: ChatMessagePart[] = []\n for (const part of parts) {\n const typed = toChatMessagePart(part)\n if (typed) out.push(typed)\n }\n return out\n}\n\nconst str = (value: unknown): value is string => typeof value === 'string'\n\nfunction toChatMessagePart(part: Record<string, unknown>): ChatMessagePart | null {\n if (!part || typeof part !== 'object') return null\n const type = part.type as ChatMessagePart['type'] | undefined\n switch (type) {\n case 'text':\n case 'reasoning':\n return str(part.text) ? (part as unknown as ChatTextPart | ChatReasoningPart) : null\n case 'tool':\n return str(part.id) && str(part.tool) && part.state && typeof part.state === 'object'\n ? (part as unknown as ChatToolPart)\n : null\n case 'file':\n case 'image':\n return part as unknown as ChatFilePart | ChatImagePart\n case 'subtask':\n return str(part.prompt) && str(part.description) && str(part.agent)\n ? (part as unknown as ChatSubtaskPart)\n : null\n case 'step-start':\n return { type: 'step-start' }\n case 'step-finish':\n return part as unknown as ChatStepFinishPart\n case 'interaction':\n return persistedPartToInteraction(part) ? (part as unknown as ChatInteractionPart) : null\n case 'notice':\n return str(part.id) && str(part.noticeKind) && str(part.text)\n ? (part as unknown as ChatNoticePart)\n : null\n case 'plan': {\n const plan = persistedPartToPlan(part)\n return plan ? ({ ...part, ...planToPersistedPart(plan) } as ChatPlanPart) : null\n }\n case 'mention':\n return isChatMentionPart(part) ? part : null\n case undefined:\n return null\n default: {\n // Compile-time exhaustiveness: a new ChatMessagePart kind that is not\n // handled above makes `type` non-never here and this line fails.\n const _exhaustive: never = type\n void _exhaustive\n return null\n }\n }\n}\n\nexport function isChatToolPart(part: ChatMessagePart): part is ChatToolPart {\n return part.type === 'tool'\n}\n\nexport function isChatTextPart(part: ChatMessagePart): part is ChatTextPart {\n return part.type === 'text'\n}\n\nexport function isChatInteractionPart(part: ChatMessagePart): part is ChatInteractionPart {\n return part.type === 'interaction'\n}\n\nexport function isChatPlanPart(part: ChatMessagePart): part is ChatPlanPart {\n return part.type === 'plan'\n}\n\nexport function isChatStepFinishPart(part: ChatMessagePart): part is ChatStepFinishPart {\n return part.type === 'step-finish'\n}\n\n/** Widened to `unknown` — unlike its siblings this guard also runs over raw\n * untyped stored rows (a transcript renderer reads `message.parts` before the\n * typed projection), which is exactly what {@link mentionPartsFromMessageParts}\n * needs. `path` and `name` carry the pill; a row missing either is unrenderable.\n *\n * Mirrors the write contract exactly (`parseFileMention` in `/chat-routes`,\n * then {@link mentionInputToPart}): a blank `name` is rejected there and so is\n * rejected here, and `size` — optional, but typed `number` once present — is\n * type-checked so `'12'` or `null` cannot ride through the guard wearing a\n * type it does not have. Negative sizes are NOT re-rejected: the wire screens\n * them, `mentionInputToPart` trusts its input, and a read guard stricter than\n * what the writer can emit would drop rows it produced itself. */\nexport function isChatMentionPart(part: unknown): part is ChatMentionPart {\n if (!part || typeof part !== 'object') return false\n const record = part as Record<string, unknown>\n if (record.size !== undefined && (typeof record.size !== 'number' || !Number.isFinite(record.size))) {\n return false\n }\n return (\n record.type === 'mention' &&\n typeof record.path === 'string' &&\n record.path.length > 0 &&\n typeof record.name === 'string' &&\n record.name.trim().length > 0 &&\n (record.mentionKind === 'image' || record.mentionKind === 'file')\n )\n}\n\n/** Every mention part on one message, in stored order. The projection a\n * transcript renderer runs before deciding which mentions the message text\n * already shows inline (see `segmentMentionContent` in `/web-react`). */\nexport function mentionPartsFromMessageParts(\n parts: ReadonlyArray<Record<string, unknown>> | ReadonlyArray<ChatMessagePart> | null | undefined,\n): ChatMentionPart[] {\n if (!parts) return []\n return (parts as ReadonlyArray<unknown>).filter(isChatMentionPart)\n}\n\n/** A validated wire mention (`parseFileMentions` in `/chat-routes`) as the\n * part the turn route persists. An absent/non-finite `size` is DROPPED rather\n * than stored as `undefined`, so a stored row never carries a key that means\n * nothing. */\nexport function mentionInputToPart(input: FileMention): ChatMentionPart {\n const part: ChatMentionPart = {\n type: 'mention',\n mentionKind: mentionKindForPath(input.path),\n path: input.path,\n name: input.name,\n }\n if (typeof input.size === 'number' && Number.isFinite(input.size)) part.size = input.size\n return part\n}\n"],"mappings":"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","names":[]}
|