gemi 0.59.0 → 0.61.0
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/ai/Agent.d.ts +533 -25
- package/dist/ai/Agent.d.ts.map +1 -1
- package/dist/ai/Agent.test-d.d.ts +2 -0
- package/dist/ai/Agent.test-d.d.ts.map +1 -0
- package/dist/ai/AgentController.d.ts +275 -6
- package/dist/ai/AgentController.d.ts.map +1 -1
- package/dist/ai/AgentProvider.d.ts +255 -0
- package/dist/ai/AgentProvider.d.ts.map +1 -0
- package/dist/ai/Schema.d.ts +125 -0
- package/dist/ai/Schema.d.ts.map +1 -0
- package/dist/ai/Schema.test-d.d.ts +2 -0
- package/dist/ai/Schema.test-d.d.ts.map +1 -0
- package/dist/ai/client/index.d.ts +24 -0
- package/dist/ai/client/index.d.ts.map +1 -0
- package/dist/ai/client/index.js +1053 -0
- package/dist/ai/client/index.js.map +1 -0
- package/dist/ai/client/reducer.d.ts +127 -0
- package/dist/ai/client/reducer.d.ts.map +1 -0
- package/dist/ai/client/reducer.test-d.d.ts +2 -0
- package/dist/ai/client/reducer.test-d.d.ts.map +1 -0
- package/dist/ai/client/sse.d.ts +54 -0
- package/dist/ai/client/sse.d.ts.map +1 -0
- package/dist/ai/example.d.ts +129 -0
- package/dist/ai/example.d.ts.map +1 -0
- package/dist/ai/index.d.ts +35 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +20 -0
- package/dist/ai/index.js.map +23 -0
- package/dist/ai/live/harness.d.ts +137 -0
- package/dist/ai/live/harness.d.ts.map +1 -0
- package/dist/ai/providers/call.d.ts +42 -0
- package/dist/ai/providers/call.d.ts.map +1 -0
- package/dist/ai/providers/capabilities.d.ts +50 -0
- package/dist/ai/providers/capabilities.d.ts.map +1 -0
- package/dist/ai/providers/errors.d.ts +37 -0
- package/dist/ai/providers/errors.d.ts.map +1 -0
- package/dist/ai/providers/fakeProvider.d.ts +35 -0
- package/dist/ai/providers/fakeProvider.d.ts.map +1 -0
- package/dist/ai/providers/http.d.ts +46 -0
- package/dist/ai/providers/http.d.ts.map +1 -0
- package/dist/ai/providers/request.d.ts +69 -0
- package/dist/ai/providers/request.d.ts.map +1 -0
- package/dist/ai/providers/stream.d.ts +42 -0
- package/dist/ai/providers/stream.d.ts.map +1 -0
- package/dist/ai/signing.d.ts +195 -0
- package/dist/ai/signing.d.ts.map +1 -0
- package/dist/ai/store/LiveRuns.d.ts +142 -0
- package/dist/ai/store/LiveRuns.d.ts.map +1 -0
- package/dist/ai/store/MemoryAgentStore.d.ts +61 -0
- package/dist/ai/store/MemoryAgentStore.d.ts.map +1 -0
- package/dist/ai/store/index.d.ts +4 -0
- package/dist/ai/store/index.d.ts.map +1 -0
- package/dist/ai/store/sse.d.ts +58 -0
- package/dist/ai/store/sse.d.ts.map +1 -0
- package/dist/ai/store/stubAgentRun.d.ts +56 -0
- package/dist/ai/store/stubAgentRun.d.ts.map +1 -0
- package/dist/ai/types.d.ts +443 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/useChat.d.ts +253 -7
- package/dist/ai/useChat.d.ts.map +1 -1
- package/dist/bin/gemi.js +500 -16
- package/dist/bin/gemi.js.map +10 -5
- package/dist/chunk-1aqzcgfr.js +5 -0
- package/dist/chunk-1aqzcgfr.js.map +10 -0
- package/dist/{chunk-get4mkx8.js → chunk-1b7e9rj7.js} +2 -2
- package/dist/{chunk-get4mkx8.js.map → chunk-1b7e9rj7.js.map} +1 -1
- package/dist/{chunk-gfma8e03.js → chunk-3aemqfdr.js} +2 -2
- package/dist/{chunk-gfma8e03.js.map → chunk-3aemqfdr.js.map} +1 -1
- package/dist/{chunk-j06g4sqc.js → chunk-528n3vgy.js} +2 -2
- package/dist/{chunk-j06g4sqc.js.map → chunk-528n3vgy.js.map} +1 -1
- package/dist/{chunk-2khdxyjb.js → chunk-57a0nqfj.js} +1 -1
- package/dist/chunk-57a0nqfj.js.map +10 -0
- package/dist/{chunk-c40n5r4v.js → chunk-5mhcwnyd.js} +2 -2
- package/dist/{chunk-c40n5r4v.js.map → chunk-5mhcwnyd.js.map} +1 -1
- package/dist/{chunk-gwchvzdp.js → chunk-71pk1mxx.js} +2 -2
- package/dist/{chunk-gwchvzdp.js.map → chunk-71pk1mxx.js.map} +1 -1
- package/dist/{chunk-spbgpndn.js → chunk-7t1hjs9f.js} +2 -2
- package/dist/{chunk-spbgpndn.js.map → chunk-7t1hjs9f.js.map} +1 -1
- package/dist/{chunk-9gsdcjt7.js → chunk-7xvaace2.js} +3 -3
- package/dist/{chunk-9gsdcjt7.js.map → chunk-7xvaace2.js.map} +1 -1
- package/dist/{chunk-fxy42w6n.js → chunk-8ag0da2s.js} +2 -2
- package/dist/{chunk-fxy42w6n.js.map → chunk-8ag0da2s.js.map} +1 -1
- package/dist/chunk-8r8epsef.js +5 -0
- package/dist/chunk-8r8epsef.js.map +11 -0
- package/dist/chunk-91cj3nxk.js +6 -0
- package/dist/{chunk-txhcx69q.js.map → chunk-91cj3nxk.js.map} +2 -2
- package/dist/{chunk-0fm6jh9b.js → chunk-9nmvm20t.js} +2 -2
- package/dist/{chunk-0fm6jh9b.js.map → chunk-9nmvm20t.js.map} +1 -1
- package/dist/{chunk-98a3k7bp.js → chunk-9xpa7dpy.js} +2 -2
- package/dist/{chunk-98a3k7bp.js.map → chunk-9xpa7dpy.js.map} +1 -1
- package/dist/{chunk-qva4841r.js → chunk-a1exbqcq.js} +3 -3
- package/dist/{chunk-qva4841r.js.map → chunk-a1exbqcq.js.map} +1 -1
- package/dist/{chunk-cw9y6k15.js → chunk-bb19bwg6.js} +2 -2
- package/dist/{chunk-cw9y6k15.js.map → chunk-bb19bwg6.js.map} +1 -1
- package/dist/{chunk-3gvjn3q4.js → chunk-cf7bvd12.js} +1 -1
- package/dist/{chunk-rkbv3df7.js → chunk-djp2xeqe.js} +2 -2
- package/dist/{chunk-rkbv3df7.js.map → chunk-djp2xeqe.js.map} +1 -1
- package/dist/chunk-ds44bqr9.js +4 -0
- package/dist/{chunk-4mcyyh1v.js.map → chunk-ds44bqr9.js.map} +4 -9
- package/dist/{chunk-wzvs3sym.js → chunk-exndjhza.js} +3 -3
- package/dist/{chunk-wzvs3sym.js.map → chunk-exndjhza.js.map} +1 -1
- package/dist/{chunk-f6dd4gd8.js → chunk-f233yzxf.js} +2 -2
- package/dist/{chunk-f6dd4gd8.js.map → chunk-f233yzxf.js.map} +1 -1
- package/dist/chunk-fz5g2z6h.js +4 -0
- package/dist/{chunk-fbvvqf9b.js.map → chunk-fz5g2z6h.js.map} +2 -2
- package/dist/{chunk-vj9538yn.js → chunk-gcszdwcb.js} +2 -2
- package/dist/{chunk-vj9538yn.js.map → chunk-gcszdwcb.js.map} +1 -1
- package/dist/{chunk-pkjq9833.js → chunk-htesx7ym.js} +4 -4
- package/dist/{chunk-pkjq9833.js.map → chunk-htesx7ym.js.map} +1 -1
- package/dist/chunk-hyxmmj9b.js +5 -0
- package/dist/{chunk-n412aa9s.js.map → chunk-hyxmmj9b.js.map} +2 -2
- package/dist/{chunk-stq96kya.js → chunk-k2sjvt0c.js} +2 -2
- package/dist/{chunk-stq96kya.js.map → chunk-k2sjvt0c.js.map} +1 -1
- package/dist/{chunk-zhbrkpb3.js → chunk-k75phgj4.js} +4 -4
- package/dist/{chunk-zhbrkpb3.js.map → chunk-k75phgj4.js.map} +1 -1
- package/dist/{chunk-y64j80v9.js → chunk-m0tp7zjp.js} +2 -2
- package/dist/{chunk-y64j80v9.js.map → chunk-m0tp7zjp.js.map} +1 -1
- package/dist/{chunk-06j6rsew.js → chunk-m45j7p1y.js} +2 -2
- package/dist/{chunk-06j6rsew.js.map → chunk-m45j7p1y.js.map} +1 -1
- package/dist/chunk-mca9wsvs.js +5 -0
- package/dist/{chunk-z2tcxwyr.js.map → chunk-mca9wsvs.js.map} +3 -4
- package/dist/{chunk-tey1xayb.js → chunk-ms13evzp.js} +2 -2
- package/dist/{chunk-tey1xayb.js.map → chunk-ms13evzp.js.map} +1 -1
- package/dist/{chunk-bn1v4sfs.js → chunk-r962ae93.js} +2 -2
- package/dist/{chunk-bn1v4sfs.js.map → chunk-r962ae93.js.map} +1 -1
- package/dist/{chunk-23h0dmx2.js → chunk-s41ees18.js} +2 -2
- package/dist/{chunk-23h0dmx2.js.map → chunk-s41ees18.js.map} +1 -1
- package/dist/chunk-snb68dgr.js +4 -0
- package/dist/{chunk-hwhw98hc.js.map → chunk-snb68dgr.js.map} +1 -1
- package/dist/chunk-sz051605.js +5 -0
- package/dist/chunk-sz051605.js.map +14 -0
- package/dist/{chunk-2cwcfwg3.js → chunk-tr3cbx8k.js} +2 -2
- package/dist/{chunk-2cwcfwg3.js.map → chunk-tr3cbx8k.js.map} +2 -2
- package/dist/{chunk-dgasxgsm.js → chunk-vqcswg7h.js} +2 -2
- package/dist/{chunk-dgasxgsm.js.map → chunk-vqcswg7h.js.map} +2 -2
- package/dist/{chunk-zqsfanvk.js → chunk-wpb1xpdp.js} +2 -2
- package/dist/{chunk-zqsfanvk.js.map → chunk-wpb1xpdp.js.map} +1 -1
- package/dist/{chunk-z1e55w67.js → chunk-ybqss0jy.js} +2 -2
- package/dist/{chunk-z1e55w67.js.map → chunk-ybqss0jy.js.map} +1 -1
- package/dist/{chunk-cejf873g.js → chunk-yk5wqmyh.js} +2 -2
- package/dist/{chunk-cejf873g.js.map → chunk-yk5wqmyh.js.map} +1 -1
- package/dist/{chunk-8kj3zrm9.js → chunk-ywntv8yw.js} +4 -4
- package/dist/{chunk-8kj3zrm9.js.map → chunk-ywntv8yw.js.map} +1 -1
- package/dist/chunks/{ThemeProvider-ByU4BQdL.js → ThemeProvider-BZ2SsSZ3.js} +60 -39
- package/dist/chunks/ThemeProvider-BZ2SsSZ3.js.map +1 -0
- package/dist/chunks/useParams-BN3XXfmG.js +20 -0
- package/dist/chunks/useParams-BN3XXfmG.js.map +1 -0
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/useDictionary.d.ts.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +3 -3
- package/dist/database/index.js +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/http/ApiRouter.d.ts +30 -2
- package/dist/http/ApiRouter.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/i18n/defineDictionary.d.ts +7 -4
- package/dist/i18n/defineDictionary.d.ts.map +1 -1
- package/dist/i18n/dictionaryRegistry.d.ts +38 -14
- package/dist/i18n/dictionaryRegistry.d.ts.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +2 -2
- package/dist/kernel/index.js +2 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +2 -2
- package/dist/server/index.js +1 -1
- package/dist/services/index.js +2 -2
- package/dist/services/index.js.map +2 -2
- package/dist/testing/index.js +2 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +5 -2
- package/skills/gemi-react-best-practices/SKILL.md +231 -0
- package/skills/gemi-react-best-practices/rules/_sections.md +56 -0
- package/skills/gemi-react-best-practices/rules/_template.md +28 -0
- package/skills/gemi-react-best-practices/rules/bundle-deep-imports.md +48 -0
- package/skills/gemi-react-best-practices/rules/bundle-mount-gate-heavy-panels.md +63 -0
- package/skills/gemi-react-best-practices/rules/client-form-vs-mutation-hooks.md +57 -0
- package/skills/gemi-react-best-practices/rules/client-loading-error-exports.md +54 -0
- package/skills/gemi-react-best-practices/rules/client-no-effect-data-flow.md +68 -0
- package/skills/gemi-react-best-practices/rules/client-typed-links.md +51 -0
- package/skills/gemi-react-best-practices/rules/controller-authorize-every-tenant-read.md +65 -0
- package/skills/gemi-react-best-practices/rules/controller-parse-request-at-the-boundary.md +54 -0
- package/skills/gemi-react-best-practices/rules/controller-redirect-facade-throws.md +68 -0
- package/skills/gemi-react-best-practices/rules/controller-request-schema.md +61 -0
- package/skills/gemi-react-best-practices/rules/controller-throw-framework-errors.md +57 -0
- package/skills/gemi-react-best-practices/rules/i18n-define-dictionary-inline.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-analytics-connection.md +53 -0
- package/skills/gemi-react-best-practices/rules/orm-include-not-n-plus-one.md +56 -0
- package/skills/gemi-react-best-practices/rules/orm-paginate-helper.md +69 -0
- package/skills/gemi-react-best-practices/rules/orm-plain-rows-by-default.md +55 -0
- package/skills/gemi-react-best-practices/rules/orm-select-narrow.md +58 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-no-io.md +54 -0
- package/skills/gemi-react-best-practices/rules/orm-transaction-sequential.md +64 -0
- package/skills/gemi-react-best-practices/rules/payload-dont-overprefetch.md +54 -0
- package/skills/gemi-react-best-practices/rules/payload-instant-vs-prefetch.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-minimal-view-props.md +51 -0
- package/skills/gemi-react-best-practices/rules/payload-parallel-controller-work.md +56 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-late-queries.md +58 -0
- package/skills/gemi-react-best-practices/rules/payload-prefetch-mirrors-usequery.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-debounce-search-variant.md +52 -0
- package/skills/gemi-react-best-practices/rules/query-keep-previous-data.md +40 -0
- package/skills/gemi-react-best-practices/rules/query-lazy-vs-mount-gate.md +54 -0
- package/skills/gemi-react-best-practices/rules/query-mutate-over-refetch.md +55 -0
- package/skills/gemi-react-best-practices/rules/query-no-hand-rolled-fetch.md +60 -0
- package/skills/gemi-react-best-practices/rules/query-revalidate-on-focus.md +44 -0
- package/skills/gemi-react-best-practices/rules/query-share-cache-key.md +51 -0
- package/skills/gemi-react-best-practices/rules/query-suspense-default.md +52 -0
- package/skills/gemi-react-best-practices/rules/routing-cache-policy-constants.md +53 -0
- package/skills/gemi-react-best-practices/rules/routing-middleware-dsl.md +60 -0
- package/skills/gemi-react-best-practices/rules/routing-resource-routes.md +59 -0
- package/skills/gemi-react-best-practices/rules/routing-routers-are-classes.md +55 -0
- package/skills/gemi-react-best-practices/rules/service-lazy-not-module-scope.md +63 -0
- package/skills/gemi-react-best-practices/rules/service-queue-is-in-memory.md +52 -0
- package/skills/gemi-react-best-practices/rules/service-static-token-and-name.md +52 -0
- package/skills/gemi-react-best-practices/rules/structure-discovered-vs-registered.md +71 -0
- package/skills/gemi-react-best-practices/rules/structure-do-not-reinvent-the-framework.md +58 -0
- package/skills/gemi-react-best-practices/rules/testing-assert-behaviour-over-markup.md +54 -0
- package/skills/gemi-react-best-practices/rules/testing-match-the-suite.md +57 -0
- package/skills/gemi-react-best-practices/rules/testing-page-seeds-real-inputs.md +65 -0
- package/dist/chunk-2khdxyjb.js.map +0 -10
- package/dist/chunk-4mcyyh1v.js +0 -4
- package/dist/chunk-fbvvqf9b.js +0 -4
- package/dist/chunk-hwhw98hc.js +0 -4
- package/dist/chunk-n412aa9s.js +0 -5
- package/dist/chunk-q0y0j3ne.js +0 -5
- package/dist/chunk-q0y0j3ne.js.map +0 -11
- package/dist/chunk-txhcx69q.js +0 -6
- package/dist/chunk-z2tcxwyr.js +0 -5
- package/dist/chunks/ThemeProvider-ByU4BQdL.js.map +0 -1
- /package/dist/{chunk-3gvjn3q4.js.map → chunk-cf7bvd12.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../ai/client/reducer.ts","../../../ai/client/sse.ts","../../../ai/useChat.tsx"],"sourcesContent":["import type {\n AgentContentPart,\n AgentError,\n AgentMessage,\n AgentStreamEvent,\n AgentStreamFrame,\n FinishReason,\n NestedRun,\n PendingToolCall,\n ToolCallPart,\n ToolShapes,\n} from \"../types\";\n\n/**\n * Frames to a message list.\n *\n * The rule the whole file is built around: **a client may be handed any\n * suffix of a run, and may be handed the same frame twice.** An attached\n * stream starts mid-message, a reconnect replays from a cursor, and a proxy\n * that retries is entitled to deliver a frame again. So nothing here tracks\n * \"the current message\" — every event that touches a message names it, and a\n * message the list has never heard of is created on the spot rather than\n * dropped.\n *\n * The two mechanisms that make that work:\n *\n * - `seq` guards replay. A frame at or below the state's cursor is a no-op,\n * and `applyFrame` returns the *same object* so a caller can tell nothing\n * happened. Without it a redelivered `text-delta` would append its text a\n * second time — a bug that looks fine until someone refreshes.\n * - a finished message is closed to further deltas. `seq` catches a redelivery\n * within a run, but not a server that replays a run from zero to a client\n * that already has the transcript; `message-end` is terminal, so a delta for\n * a message that already carries a finish reason is replay by definition.\n * - every mutation is either an append that the seq guard already protects,\n * or an upsert keyed by an id. Tool calls and tool results arrive more than\n * once (a call re-streams as its arguments grow), so those are keyed by\n * `toolCallId`, never pushed blindly.\n *\n * A sub-agent's transcript is built by this same function calling itself. A\n * `nested-event` is unwrapped onto the `NestedRun` hanging off its parent's\n * tool-call part and handed to `reduce` again, so every rule above holds inside\n * a nested run for free — a nested `message-start` that never arrived, a\n * nested delta after a nested `message-end`, a nested `run-end` closing off a\n * message that never ended. Two levels of nesting cost this file nothing over\n * one, which is the whole reason the wire has a `nested-event` rather than a\n * second reducer.\n */\n\nexport type ChatState<T extends ToolShapes = ToolShapes, O = unknown> = {\n messages: AgentMessage<T, O>[];\n /** Non-empty exactly while the conversation is holding a question. */\n pending: PendingToolCall<T>[];\n error: AgentError | null;\n runId?: string;\n threadId?: string;\n /**\n * The highest frame applied. This is the cursor `/attach` is asked to resume\n * from, which is why it lives in the state a client keeps rather than in the\n * stream reader that dies with the connection.\n */\n seq: number;\n /**\n * Which run `seq` counts within.\n *\n * Separate from `runId`, which is cleared when the run ends because a UI asks\n * it whether anything is live. The cursor outlives the run it belongs to: a\n * frame from run_1 redelivered after run_1 finished is still a duplicate, and\n * without somewhere to remember that, replaying a finished run would look\n * exactly like the start of a new one.\n */\n cursorRunId?: string;\n /**\n * The messages the run in hand has touched, oldest first.\n *\n * `run-end` and `markAborted` both have to finish off a message whose\n * `message-end` never arrived, and \"every assistant message with no finish\n * reason\" is the wrong set to do it over: a superseded turn from an earlier\n * run is sitting in exactly that state, and stamping it with *this* run's\n * reason would relabel an interrupted answer as a clean completion. So the\n * reducer remembers which messages this run actually wrote to, and finishes\n * off only those. Reset by `run-start`; on a mid-run attach it simply fills\n * up from the first frame that names a message, which is the same set.\n */\n runMessageIds: string[];\n /**\n * The deferred tools the model has pulled in during this run, as a set.\n *\n * Not a message part, because a `tool-search` names no tool call and no\n * message — it happens *before* any call, which is exactly what makes it\n * worth showing (\"looking for the right tool\" instead of an unexplained\n * pause). So unlike `tool-progress` and `nested-event`, which now have a home\n * on the tool-call part, this one had nowhere in `AgentMessage` to go that\n * would not have meant inventing a content part in a frozen contract.\n *\n * A set, not a log: union is the only accumulation that survives redelivery\n * without a guard, and a client attached mid-run and handed a `tool-search`\n * it has already applied must not show the same tool twice. Run-scoped and\n * reset by `run-start` — it describes a run in progress, so it is deliberately\n * not something `initialChatState` restores.\n */\n loadedTools: string[];\n finishReason?: FinishReason;\n};\n\nexport function initialChatState<T extends ToolShapes = ToolShapes, O = unknown>(\n init: {\n messages?: AgentMessage<T, O>[];\n pending?: PendingToolCall<T>[];\n threadId?: string;\n /** Where a restored client left off. `-1` means \"I have seen nothing\". */\n seq?: number;\n /**\n * Which run that `seq` counts within. A cursor without it is a number with\n * no units — frames number from zero in every run, so `seq: 10` means\n * nothing until you know whether the live run is the one that reached 10.\n */\n cursorRunId?: string;\n } = {},\n): ChatState<T, O> {\n return {\n messages: init.messages ? [...init.messages] : [],\n pending: init.pending ? [...init.pending] : [],\n error: null,\n threadId: init.threadId,\n seq: init.seq ?? -1,\n cursorRunId: init.cursorRunId,\n runMessageIds: [],\n loadedTools: [],\n };\n}\n\n/**\n * Apply one frame.\n *\n * `now` is an argument rather than a `Date.now()` call so the reducer stays a\n * pure function of its inputs — it is only ever read when a frame arrives for a\n * message the list has not seen, which is precisely the mid-run-attach path a\n * test needs to pin down.\n */\nexport function applyFrame<T extends ToolShapes = ToolShapes, O = unknown>(\n state: ChatState<T, O>,\n frame: AgentStreamFrame<T, O>,\n now: string = new Date().toISOString(),\n): ChatState<T, O> {\n // A `seq` is a position within *one* run, so the second turn of a\n // conversation numbers its frames from zero again and the cursor left over\n // from the first would silently swallow the whole answer. `run-start` naming\n // a run this state has not seen is the one frame entitled to drop the cursor.\n const startsNewRun = frame.event.type === \"run-start\" && frame.event.runId !== state.cursorRunId;\n // Identity, not a copy: the hook reads `next === prev` as \"already applied\"\n // and skips the callbacks that would otherwise fire twice.\n if (!startsNewRun && frame.seq <= state.seq) return state;\n return { ...reduce(state, frame.event, now), seq: frame.seq };\n}\n\nfunction reduce<T extends ToolShapes, O>(\n state: ChatState<T, O>,\n event: AgentStreamFrame<T, O>[\"event\"],\n now: string,\n): ChatState<T, O> {\n switch (event.type) {\n case \"run-start\":\n return {\n ...state,\n runId: event.runId,\n cursorRunId: event.runId,\n threadId: event.threadId ?? state.threadId,\n runMessageIds: [],\n loadedTools: [],\n finishReason: undefined,\n };\n\n case \"message-start\":\n return withMessage(state, event.messageId, now, (message) => ({\n ...message,\n role: event.role,\n }));\n\n // The two additive cases, and the only two that can double text. `seq`\n // catches an ordinary redelivery, but a client resuming with a cursor the\n // server ignores — or asked for the whole run when it already had the\n // transcript — gets frames it cannot recognise as duplicates. A finished\n // message is the one thing it can: `message-end` is terminal, so a delta\n // arriving for a message that already carries a finish reason is replay by\n // definition and appending it would show the answer twice.\n case \"text-delta\":\n if (isFinished(state, event.messageId)) return state;\n return withMessage(state, event.messageId, now, (message) => ({\n ...message,\n content: appendText(message.content, event.delta),\n }));\n\n case \"reasoning-delta\":\n if (isFinished(state, event.messageId)) return state;\n return withMessage(state, event.messageId, now, (message) => ({\n ...message,\n content: appendReasoning(message.content, event.delta, event.id),\n }));\n\n case \"output-delta\":\n // `snapshot` is the whole object so far, not an increment, so the raw\n // `delta` text is dropped here on purpose: replacing beats accumulating\n // when a client may have missed the first half of the JSON.\n return withMessage(state, event.messageId, now, (message) => ({\n ...message,\n content: upsert(\n message.content,\n { type: \"output\", value: event.snapshot as O, partial: true },\n (part) => part.type === \"output\",\n ),\n }));\n\n case \"tool-call\":\n // Keyed, because the same call re-streams as its arguments fill in — and\n // *merged* rather than replaced, which the plain upsert below cannot do.\n //\n // A `tool-call` frame is the model's half of the part. `progress` and\n // `nested` are the execution's half, and they arrive on events that name\n // no message, so a part that loses them cannot rebuild them from anything\n // later in the stream. Replacing wholesale is exactly how a run replayed\n // onto a transcript that already holds it drops a sub-agent's entire\n // transcript while appearing to converge.\n return withMessage(state, event.messageId, now, (message) => {\n const index = message.content.findIndex(\n (part) => part.type === \"tool-call\" && part.toolCallId === event.part.toolCallId,\n );\n const existing = index === -1 ? undefined : (message.content[index] as ToolCallPart<T>);\n // The frame wins where it says anything — a server that persists the\n // nested transcript and re-sends it is more authoritative than what this\n // client accumulated — and the part is kept where it does not.\n const progress = event.part.progress ?? existing?.progress;\n const nested = event.part.nested ?? existing?.nested;\n const part = {\n ...event.part,\n ...(progress ? { progress } : {}),\n ...(nested ? { nested } : {}),\n };\n const content = message.content.slice();\n if (index === -1) content.push(part);\n else content[index] = part;\n return { ...message, content };\n });\n\n case \"tool-search\": {\n // Accumulated as a set. Every other additive event in this file leans on\n // `seq` to survive redelivery; this one cannot, because a `tool-search`\n // carries no id at all — there is nothing to key an upsert on and nothing\n // to tell a second delivery from a second search. Union is what makes the\n // question moot: applying the same frame again is the same set, and two\n // genuinely different searches that both loaded `refundOrder` are, for a\n // UI naming the tools in play, the same fact twice.\n const loaded = event.loaded.filter((name) => !state.loadedTools.includes(name));\n if (loaded.length === 0) return state;\n return { ...state, loadedTools: [...state.loadedTools, ...loaded] };\n }\n\n case \"tool-progress\":\n // Append-only, exactly like a text delta, and protected the same two\n // ways: `seq` for redelivery within a run, and the finished-message check\n // inside `withToolCall` for a run replayed onto a transcript that already\n // holds it. The tool loop emits every yield before the message it belongs\n // to is finalised, so a progress datum for a finished message is replay\n // by definition — the same argument `text-delta` makes, and with the same\n // carve-out for a message the run parked on.\n //\n // Unbounded on purpose: a tool decides how often it yields, and truncating\n // its log here would lose data the UI asked for. What is bounded is the\n // *wire* — `useChat.send` strips `progress` from the history it posts, so\n // a tool that yields ten thousand times costs ten thousand entries in this\n // tab and nothing at all on every later turn. See `forWire`.\n return withToolCall(state, event.toolCallId, (part) => ({\n ...part,\n progress: [...(part.progress ?? []), event.data],\n }));\n\n case \"nested-event\": {\n // The recursion. The sub-run's transcript is an ordinary message list, so\n // it is reduced by this function rather than by a parallel one — which is\n // why an arbitrarily deep nesting is not a case handled here at all: an\n // inner `nested-event` simply lands back in this branch, one message list\n // further down.\n const nestedEvent = event as NestedEvent;\n return withToolCall(state, event.toolCallId, (part) => {\n const runs = part.nested ?? [];\n const index = runs.findIndex((run) => run.runId === nestedEvent.runId);\n // Created on the spot when unknown, for the same reason `withMessage`\n // creates a message it never saw start: a client attached mid-run gets\n // the sub-run's tail and no `run-start`, and dropping it would leave a\n // tool call that visibly does something looking like one that hangs.\n const run: NestedRun = runs[index] ?? {\n runId: nestedEvent.runId,\n agent: nestedEvent.agent,\n messages: [],\n };\n const next = applyNested(run, nestedEvent, now);\n const nested = index === -1 ? [...runs, next] : runs.map((r, i) => (i === index ? next : r));\n return { ...part, nested };\n });\n }\n\n case \"tool-result\": {\n const next = withMessage(state, event.messageId, now, (message) => ({\n ...message,\n content: upsert(\n message.content,\n event.part,\n (part) => part.type === \"tool-result\" && part.toolCallId === event.part.toolCallId,\n ),\n }));\n // A result is the answer to a pending call, whoever produced it — the\n // human here, or the server after an approval. Either way it is no longer\n // pending, and leaving it in the list would keep the UI asking.\n return {\n ...next,\n pending: next.pending.filter((call) => call.toolCallId !== event.part.toolCallId),\n };\n }\n\n case \"awaiting-input\":\n return { ...state, runId: event.runId, pending: [...event.pending] };\n\n case \"message-end\":\n return withMessage(state, event.messageId, now, (message) => ({\n ...message,\n finishReason: event.finishReason,\n content: message.content.map((part) =>\n part.type === \"output\" && part.partial ? { ...part, partial: false } : part,\n ),\n }));\n\n case \"usage\": {\n // The only event with no id of its own, so it goes on the last assistant\n // message — an assignment rather than an append, so a replay is harmless.\n //\n // The role test is the point. A run that counts its input tokens before\n // opening a message — one that errors early, or whose only output is a\n // tool call the client must resolve — emits `usage` while the last message\n // in the list is still the user's own optimistic turn, and token counts\n // rendered against what the user typed are simply false. With no assistant\n // message to hang it on, dropping it is the honest answer.\n //\n // Deliberately *not* narrowed to the messages this run touched: a client\n // resuming from a cursor mid-message has touched none of them yet, and\n // narrowing would make `usage` the one event that fails to converge from\n // an arbitrary cursor, which is the property the whole file is for.\n const index = lastIndexWhere(state.messages, (message) => message.role === \"assistant\");\n if (index === -1) return state;\n const messages = state.messages.slice();\n messages[index] = { ...messages[index]!, usage: event.usage };\n return { ...state, messages };\n }\n\n case \"error\":\n // Clearing `pending` here is what keeps \"pending is non-empty exactly\n // when the UI is awaiting input\" true: a run that errored is not holding\n // a question any more, it is holding a failure.\n return { ...state, error: event.error, pending: [] };\n\n case \"run-end\":\n return {\n ...state,\n runId: undefined,\n finishReason: event.finishReason,\n // A safety net for the client that attached late and never saw a\n // `message-end`: once the run is over nothing is still streaming, so a\n // message left without a finish reason would show a cursor forever.\n //\n // Scoped to this run's own messages. A turn superseded by a second send\n // is also sitting there unfinished, and it was *not* finished by this\n // run — stamping it \"stop\" would tell the user, and the model on the\n // next stateless turn, that an answer cut off mid-sentence ended\n // cleanly.\n messages: finishUnended(state, event.finishReason),\n };\n\n // Every event in the union is handled above, so this is reached only by a\n // server that speaks a newer protocol than this client. Kept as a silent\n // no-op rather than an exhaustiveness assertion for that reason: an old tab\n // meeting a new event must keep rendering the rest of the stream, and the\n // seq still advances so its cursor stays right.\n default:\n return state;\n }\n}\n\n/**\n * The interrupted turn, kept.\n *\n * `stop()` calls this before the server has said anything, because the point of\n * stopping is that the UI reacts now. Whatever the assistant had produced stays\n * in the list marked `aborted` — dropping it would lose text the user already\n * read, and would leave the transcript unable to say it was cut short.\n *\n * `pending` is untouched: a question the conversation is holding survives a\n * stopped run, and clearing it would silently discard the answer the user still\n * owes.\n */\nexport function markAborted<T extends ToolShapes = ToolShapes, O = unknown>(\n state: ChatState<T, O>,\n): ChatState<T, O> {\n return {\n ...state,\n runId: undefined,\n finishReason: \"aborted\",\n // This run's messages only, for the same reason `run-end` is scoped: an\n // older turn that was already cut short is not cut short again by this one,\n // and a restored transcript may legitimately carry an unfinished message\n // that has nothing to do with the run being stopped.\n messages: finishUnended(state, \"aborted\"),\n };\n}\n\n// --- helpers -------------------------------------------------------------\n\n/**\n * The messages this run wrote, finished off with `reason` if they never ended —\n * and, inside them, the sub-runs this run left mid-sentence.\n *\n * The nesting descent is not an extra: a `NestedRun` renders with the component\n * that renders `messages`, so a nested message with no `finishReason` draws a\n * live cursor inside a block that will never receive another frame. The parent\n * ending is the last word anything will say about it, and for `markAborted`\n * there is no other word available at all — `stop()` marks the transcript\n * optimistically, before the server has answered, so no forwarded nested\n * `run-end` can arrive in time.\n *\n * It recurses through `closeRun`, which builds a `ChatState` around the sub-run\n * and calls back here, so a sub-sub-run is closed by the same three lines that\n * close the top level — the same trick `applyNested` plays on the way in.\n */\nfunction finishUnended<T extends ToolShapes, O>(\n state: ChatState<T, O>,\n reason: FinishReason,\n): AgentMessage<T, O>[] {\n return state.messages.map((message) =>\n message.role === \"assistant\" && state.runMessageIds.includes(message.id)\n ? closeMessage(message, reason)\n : message,\n );\n}\n\n/**\n * One message of this run, and the sub-runs hanging off its tool calls.\n *\n * The message's own finish reason is only filled in when it is missing, but the\n * descent happens either way: the message a run parks on is *finished* while the\n * sub-run underneath it is still live, and that is the case where the block\n * stuck streaming is easiest to hit.\n */\nfunction closeMessage<T extends ToolShapes, O>(\n message: AgentMessage<T, O>,\n reason: FinishReason,\n): AgentMessage<T, O> {\n const content = message.content.map((part) =>\n part.type === \"tool-call\" && part.nested?.some((run) => run.finishReason === undefined)\n ? {\n ...part,\n nested: part.nested.map((run) =>\n run.finishReason === undefined ? closeRun(run, reason) : run,\n ),\n }\n : part,\n );\n if (message.finishReason === undefined) return { ...message, finishReason: reason, content };\n // Identity where nothing changed, so `run-end` on a settled transcript keeps\n // the parts a UI may be memoising on.\n const changed = content.some((part, index) => part !== message.content[index]);\n return changed ? { ...message, content } : message;\n}\n\n/** A sub-run the parent outlived, stamped with the parent's reason, recursively. */\nfunction closeRun(run: NestedRun, reason: FinishReason): NestedRun {\n const sub: ChatState = {\n messages: run.messages,\n pending: [],\n error: null,\n seq: -1,\n // Every message in the transcript, for the reason `applyNested` gives: a\n // `NestedRun` is keyed by `runId` and only ever written by events carrying\n // it, so there is no other run's work in here to protect.\n runMessageIds: run.messages.map((message) => message.id),\n loadedTools: [],\n };\n return { ...run, finishReason: reason, messages: finishUnended(sub, reason) };\n}\n\ntype NestedEvent = Extract<AgentStreamEvent, { type: \"nested-event\" }>;\n\n/**\n * Run one update over the tool-call part with this id, wherever it lives.\n *\n * `tool-progress` and `nested-event` are the only events that name a tool call\n * without naming a message, so the message has to be found rather than told —\n * a scan from the newest end, because the call being worked on is the one that\n * just arrived.\n *\n * **A frame for a tool call this transcript does not have is dropped.** That is\n * the mid-run `/attach` case the whole file is written for, and it is the one\n * place the usual answer — create it on the spot — is the wrong one: a\n * `ToolCallPart` needs a tool `name` and an `input`, and neither is on these\n * events. Inventing them would put a tool call the model never made into the\n * transcript, under a name that is not a key of the agent's tools, which breaks\n * the narrowing that is the reason the shapes are carried into the browser at\n * all. So the progress is lost and the transcript stays true, rather than the\n * other way round.\n *\n * In practice a client that has the cursor has the part: `useChat` documents\n * `cursor` and `initialMessages` as things to persist together, and a client\n * that persisted one without the other is already the case that doubles text.\n * Parking the orphans until their tool call shows up was considered and is not\n * worth it — the call is *earlier* in the stream, never later, so nothing would\n * ever drain the parking lot.\n */\nfunction withToolCall<T extends ToolShapes, O>(\n state: ChatState<T, O>,\n toolCallId: string,\n update: (part: ToolCallPart<T>) => ToolCallPart<T>,\n): ChatState<T, O> {\n for (let i = state.messages.length - 1; i >= 0; i--) {\n const message = state.messages[i]!;\n const index = message.content.findIndex(\n (part) => part.type === \"tool-call\" && part.toolCallId === toolCallId,\n );\n if (index === -1) continue;\n // The replay guard, and the same one the deltas use — with the one carve-out\n // a delta does not need.\n //\n // Within a run the tool loop runs every tool before it finalises the message\n // the calls sit in, so a frame for a call inside a finished message is that\n // run being replayed onto a transcript that already has it, and appending\n // would show the progress log twice.\n //\n // But a message the run *parked* on is not a closed message. `Agent.loop`\n // finalises it with `\"awaiting-input\"` **before** emitting `awaiting-input`,\n // and the very next turn re-enters that same tool call — `resolveAnswer`\n // calls `executeTool` against the call it found in the history — so its\n // `tool-progress` and `nested-event` frames arrive after their message\n // carries a finish reason. Treating those as replay dropped every frame of\n // the resumed execution while the `tool-result` (which goes through\n // `withMessage`, unguarded) still landed: the call went silent and then\n // produced an answer, which is the bug nesting exists to fix. Worse, in\n // stateless mode the nested transcript is what the next turn replays the\n // sub-run from, so the client posted back a sub-run frozen at the question.\n //\n // The carve-out is as narrow as the case: parked, *and* the call still open.\n // Once its result has landed nothing legitimately appends to it again, so a\n // replay of a parked run is still refused for every call it answered.\n if (message.finishReason !== undefined && !isReenterable(state, message, toolCallId)) {\n return state;\n }\n const content = message.content.slice();\n content[index] = update(content[index] as ToolCallPart<T>);\n const messages = state.messages.slice();\n messages[i] = { ...message, content };\n // Counted as a message this run touched, like every event that names one\n // directly — a client whose only frames for this message were nested ones\n // still needs `run-end` to close it, or it shows a cursor forever.\n const runMessageIds = state.runMessageIds.includes(message.id)\n ? state.runMessageIds\n : [...state.runMessageIds, message.id];\n return { ...state, runMessageIds, messages };\n }\n return state;\n}\n\n/**\n * One sub-run event, applied to the sub-run's own transcript.\n *\n * The sub-state is built and thrown away per event rather than kept, because\n * there is nowhere to keep it: `NestedRun` is a wire type and holds a\n * transcript, not a reducer state. Everything the inner `reduce` needs is\n * derivable from it.\n *\n * - `seq` is `-1` and this calls `reduce`, not `applyFrame`. A nested event\n * has no sequence of its own; it is numbered in the *parent's* stream, and\n * the parent's cursor has already refused the duplicates.\n * - `runMessageIds` is every message in the transcript, which for a nested\n * run is exactly right and needs none of the parent's care. The parent\n * scopes it because its list mixes runs; a `NestedRun` is keyed by `runId`\n * and only ever written by events carrying that id, so every message in it\n * belongs to the run that is ending.\n * - `pending` is dropped. An escalated question reaches the user on the\n * *parent's* `awaiting-input`, carrying the path that says which tool to\n * re-enter; the copy inside the sub-run's own stream carries no path, so\n * surfacing it would show the question twice and make one of them\n * unanswerable.\n */\nfunction applyNested(run: NestedRun, event: NestedEvent, now: string): NestedRun {\n const sub: ChatState = {\n messages: run.messages,\n pending: [],\n error: null,\n seq: -1,\n runMessageIds: run.messages.map((message) => message.id),\n loadedTools: [],\n finishReason: run.finishReason,\n };\n const next = reduce(sub, event.event, now);\n return {\n ...run,\n // A late label is still a label: `/attach` may deliver the sub-run's tail,\n // and every nested frame carries it.\n ...(event.label !== undefined ? { label: event.label } : {}),\n messages: next.messages,\n ...(next.finishReason !== undefined ? { finishReason: next.finishReason } : {}),\n // The sub-run's own total, lifted so a UI can price the block it is\n // rendering without walking its messages. An assignment, so redelivery is\n // harmless — the inner `reduce` has already put the same number on the\n // sub-run's last assistant message.\n ...(event.event.type === \"usage\" ? { usage: event.event.usage } : {}),\n };\n}\n\n/**\n * Whether a finished message's tool call can still be written to.\n *\n * Exactly one message can say yes: the one a run parked on, holding a call\n * nobody has answered yet. That is the resume turn — the tool is re-entered\n * from the top and streams progress and sub-run events against a message that\n * closed on the previous turn.\n *\n * \"Open\" is the server's own test, `Agent.openCalls`: a tool call with no\n * `tool-result` for it anywhere in the transcript. The result is the thing that\n * says the re-entry finished, and it always arrives *after* the frames this\n * guards — `attachToHistory` emits it once `executeTool` returns.\n */\nfunction isReenterable<T extends ToolShapes, O>(\n state: ChatState<T, O>,\n message: AgentMessage<T, O>,\n toolCallId: string,\n) {\n if (message.finishReason !== \"awaiting-input\") return false;\n return !state.messages.some((candidate) =>\n candidate.content.some((part) => part.type === \"tool-result\" && part.toolCallId === toolCallId),\n );\n}\n\nfunction isFinished<T extends ToolShapes, O>(state: ChatState<T, O>, messageId: string) {\n const message = state.messages.find((candidate) => candidate.id === messageId);\n return message !== undefined && message.finishReason !== undefined;\n}\n\nfunction lastIndexWhere<M>(items: M[], match: (item: M) => boolean) {\n for (let i = items.length - 1; i >= 0; i--) {\n if (match(items[i]!)) return i;\n }\n return -1;\n}\n\nfunction withMessage<T extends ToolShapes, O>(\n state: ChatState<T, O>,\n messageId: string,\n now: string,\n update: (message: AgentMessage<T, O>) => AgentMessage<T, O>,\n): ChatState<T, O> {\n const index = state.messages.findIndex((message) => message.id === messageId);\n // Every id-carrying event marks its message as one this run touched, which is\n // what `run-end` and `markAborted` read to know whose loose ends are theirs.\n const runMessageIds = state.runMessageIds.includes(messageId)\n ? state.runMessageIds\n : [...state.runMessageIds, messageId];\n if (index === -1) {\n // The mid-stream attach. Every id-carrying event describes an assistant\n // message — `message-start` is the only event that declares a role and it\n // declares that one — so a placeholder is enough to hang the tail on.\n const blank: AgentMessage<T, O> = {\n id: messageId,\n role: \"assistant\",\n content: [],\n createdAt: now,\n };\n return { ...state, runMessageIds, messages: [...state.messages, update(blank)] };\n }\n const messages = state.messages.slice();\n messages[index] = update(messages[index]!);\n return { ...state, runMessageIds, messages };\n}\n\nfunction appendText<T extends ToolShapes, O>(\n content: AgentContentPart<T, O>[],\n delta: string,\n): AgentContentPart<T, O>[] {\n const last = content[content.length - 1];\n // Coalesced into the trailing text part, but only if it *is* trailing: a\n // delta after a tool call opens a new part, which is what lets a UI render\n // \"thinking / call / answer\" in the order it happened.\n if (last && last.type === \"text\") {\n return [...content.slice(0, -1), { type: \"text\", text: last.text + delta }];\n }\n return [...content, { type: \"text\", text: delta }];\n}\n\n/**\n * Mirrors `appendReasoning` in Agent.ts, including its merge rule: deltas join\n * the last part only when the reasoning-item id matches, so a step that\n * produced two items stays two parts instead of flattening into one under the\n * wrong id. The two implementations have to agree, because a stateless client\n * posts this content straight back and the provider matches on that id.\n */\nfunction appendReasoning<T extends ToolShapes, O>(\n content: AgentContentPart<T, O>[],\n delta: string,\n id?: string,\n): AgentContentPart<T, O>[] {\n const last = content[content.length - 1];\n if (last && last.type === \"reasoning\" && last.id === id) {\n return [...content.slice(0, -1), { ...last, text: (last.text ?? \"\") + delta }];\n }\n return [...content, id ? { type: \"reasoning\", id, text: delta } : { type: \"reasoning\", text: delta }];\n}\n\nfunction upsert<T extends ToolShapes, O>(\n content: AgentContentPart<T, O>[],\n part: AgentContentPart<T, O>,\n match: (part: AgentContentPart<T, O>) => boolean,\n): AgentContentPart<T, O>[] {\n const index = content.findIndex(match);\n if (index === -1) return [...content, part];\n const next = content.slice();\n next[index] = part;\n return next;\n}\n","import type { AgentStreamFrame, ToolShapes } from \"../types\";\n\n/**\n * Bytes to frames.\n *\n * Split out of the hook because none of it is React: it is a byte-level parser,\n * and the interesting cases — a chunk boundary landing inside a `data:` line, a\n * multi-byte character split across two reads, a stream that dies mid-event —\n * are all reachable from a test with a string and no DOM.\n *\n * Deliberately not `EventSource`. The agent routes are POSTs carrying a turn,\n * `EventSource` can only GET, and reattachment wants the cursor in the request\n * body next to the thread id rather than in a header the browser owns.\n */\n\n/**\n * Feed it chunks, get frames. Holds whatever is left over between calls, which\n * is the whole point: `push()` is called once per `reader.read()` and those\n * boundaries have nothing to do with event boundaries.\n */\nexport class SSEFrameDecoder<T extends ToolShapes = ToolShapes, O = unknown> {\n private buffer = \"\";\n private textDecoder = new TextDecoder();\n /**\n * A `\\r` at the very end of a chunk is ambiguous — the next chunk may open\n * with the `\\n` that completes a CRLF, or with the next line. Holding it back\n * for one chunk is what keeps a boundary there from being read as a blank\n * line and cutting an event in half.\n */\n private pendingCR = false;\n private lastSeq = -1;\n\n /** The cursor to resume from: the highest `id:` seen. */\n get cursor(): number {\n return this.lastSeq;\n }\n\n push(chunk: Uint8Array | string): AgentStreamFrame<T, O>[] {\n this.append(\n typeof chunk === \"string\" ? chunk : this.textDecoder.decode(chunk, { stream: true }),\n );\n return this.drain();\n }\n\n /**\n * End of stream. Anything still buffered is an event with no terminating\n * blank line, which the SSE spec discards and so does this — a truncated tail\n * is usually truncated JSON, and half a `text-delta` appended to a message is\n * worse than a missing one.\n */\n flush(): AgentStreamFrame<T, O>[] {\n this.append(this.textDecoder.decode());\n const frames = this.drain();\n this.buffer = \"\";\n this.pendingCR = false;\n return frames;\n }\n\n /** Normalises every line ending to `\\n` so the rest of the parser only has to\n * know about one. */\n private append(text: string) {\n if (text === \"\") return;\n let rest = text;\n if (this.pendingCR) {\n this.buffer += \"\\n\";\n this.pendingCR = false;\n if (rest.startsWith(\"\\n\")) rest = rest.slice(1);\n }\n if (rest.endsWith(\"\\r\")) {\n rest = rest.slice(0, -1);\n this.pendingCR = true;\n }\n this.buffer += rest.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n }\n\n private drain(): AgentStreamFrame<T, O>[] {\n const frames: AgentStreamFrame<T, O>[] = [];\n let boundary = this.buffer.indexOf(\"\\n\\n\");\n while (boundary !== -1) {\n const block = this.buffer.slice(0, boundary);\n this.buffer = this.buffer.slice(boundary + 2);\n const frame = this.parse(block);\n if (frame) frames.push(frame);\n boundary = this.buffer.indexOf(\"\\n\\n\");\n }\n return frames;\n }\n\n private parse(block: string): AgentStreamFrame<T, O> | null {\n let id: string | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n // A comment. The server sends these as keepalives through a proxy that\n // would otherwise close an idle connection, and they carry nothing.\n if (line === \"\" || line.startsWith(\":\")) continue;\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n if (field === \"data\") data.push(value);\n else if (field === \"id\") id = value;\n // `event:` and `retry:` are read and dropped: the frame's own `type` is\n // the discriminator, so a second one on the transport could only disagree\n // with it.\n }\n\n if (data.length === 0) return null;\n\n let event: AgentStreamFrame<T, O>[\"event\"];\n try {\n // Multi-line data rejoins with `\\n`, per the spec — which is also what\n // makes a pretty-printed JSON payload survive the transport.\n event = JSON.parse(data.join(\"\\n\"));\n } catch {\n // One unparseable frame must not take the rest of the run with it.\n return null;\n }\n\n const parsed = id === undefined || id === \"\" ? Number.NaN : Number(id);\n // A frame with no usable `id` still needs a seq, because seq is what makes\n // a replayed frame a no-op downstream. Continuing the count is the reading\n // that keeps the cursor monotone; treating it as 0 would make every such\n // frame look already-applied.\n const seq = Number.isFinite(parsed) ? parsed : this.lastSeq + 1;\n this.lastSeq = seq;\n return { seq, event };\n }\n}\n\n/**\n * The same decoder over a `Response.body`.\n *\n * Cancels the reader when the caller stops consuming — a `break` out of the\n * `for await` is how the hook drops a stream on unmount, and without the cancel\n * the connection stays open behind it.\n */\nexport async function* decodeSSE<T extends ToolShapes = ToolShapes, O = unknown>(\n stream: ReadableStream<Uint8Array> | null | undefined,\n): AsyncGenerator<AgentStreamFrame<T, O>, void, void> {\n if (!stream) return;\n const reader = stream.getReader();\n const decoder = new SSEFrameDecoder<T, O>();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n yield* decoder.flush();\n return;\n }\n if (value) yield* decoder.push(value);\n }\n } finally {\n try {\n await reader.cancel();\n } catch {\n // Already errored or already closed; there is nothing left to release.\n }\n }\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { RPC } from \"../client/rpc\";\nimport { useParams } from \"../client/useParams\";\nimport { applyFrame, initialChatState, markAborted, type ChatState } from \"./client/reducer\";\nimport { decodeSSE } from \"./client/sse\";\nimport type {\n AgentError,\n AgentMessage,\n ClientToolResult,\n ClientTurn,\n PendingToolCall,\n ToolShapes,\n} from \"./types\";\n\n/**\n * The agent routes, picked out of the same `RPC` interface `useQuery` and\n * `useMutation` read. An agent is not a second kind of thing to register — it is\n * a route, so its key is its path and its types arrive the way every other\n * route's types do.\n */\ntype AgentRoutes = {\n [K in keyof RPC as RPC[K] extends { __agent: true } ? K : never]: RPC[K];\n};\n\ntype ToolsOf<P extends keyof AgentRoutes> = AgentRoutes[P] extends { tools: infer T }\n ? T extends ToolShapes\n ? T\n : ToolShapes\n : ToolShapes;\n\ntype OutputOf<P extends keyof AgentRoutes> = AgentRoutes[P] extends { output: infer O }\n ? O\n : unknown;\n\n/**\n * What the UI is waiting on.\n *\n * `awaiting-input` is its own state rather than a flavour of idle: the run is\n * over, but the conversation is holding a question, and a UI that cannot tell\n * the difference will either look hung or look finished.\n */\nexport type ChatStatus = \"idle\" | \"submitted\" | \"streaming\" | \"awaiting-input\" | \"error\";\n\nexport interface UseChatParams<P extends keyof AgentRoutes> {\n /** Continues a server-side thread. Omitted, the hook keeps history itself and\n * sends it with each request — the stateless default.\n *\n * The id is the store's, not the client's: an app route calls\n * `store.createThread` and hands the result to this hook, and an id the\n * store never minted — or has since expired — is a `thread_not_found` error\n * on the first turn. Only a store built with client-owned ids\n * (`new MemoryAgentStore({ clientOwnedIds: true })`) takes one the client\n * made up. */\n threadId?: string;\n /** Server-rendered or restored history. */\n initialMessages?: AgentMessage<ToolsOf<P>, OutputOf<P>>[];\n /**\n * Where the client that produced `initialMessages` left off, so `/attach` can\n * be asked for the tail rather than the whole run.\n *\n * Without it the hook has to say \"I have seen nothing\", and a run kept alive\n * past `run-end` — which `LiveRuns` does on purpose, so a refresh a second\n * late still sees the ending — replays from the top onto a transcript that\n * already has it. Take it from `cursor` on the previous mount and persist it\n * beside the messages; the two belong together, and restoring one without the\n * other is what produces a doubled answer.\n *\n * `runId` is half of it rather than decoration: frames number from zero in\n * every run, so a bare number cannot say whether it describes the run that is\n * live now. Given both, the server resumes when they match and replays from\n * the start when they do not.\n */\n cursor?: { runId: string; seq: number };\n /**\n * On mount, ask whether a run is still going on this thread and pick it back\n * up from where this client left off. Needs a `threadId` — that is the handle\n * that survives a refresh, which is why the client is never asked to stash a\n * `runId` of its own. Defaults to true.\n */\n attach?: boolean;\n /** Merged into the request body, for anything the agent's controller reads\n * off the request that is not a message. */\n body?: Record<string, unknown>;\n headers?: Record<string, string>;\n onFinish?: (message: AgentMessage<ToolsOf<P>, OutputOf<P>>) => void;\n onError?: (error: AgentError) => void;\n onAwaitingInput?: (pending: PendingToolCall<ToolsOf<P>>[]) => void;\n /**\n * The mount probe found no run to attach to.\n *\n * On one server this means what it says, and there is nothing to do. On more\n * than one it is ambiguous in a way neither end can resolve: a run lives in\n * the process that started it, `find` only ever searches that process, and a\n * refresh routed to a different instance gets the same answer as a thread\n * with nothing running. The server cannot tell the two apart, so it does not\n * pretend to.\n *\n * What an app can do is re-read the thread — the run is still going wherever\n * it is, and its messages land in the store when it finishes, so refetching\n * shows the answer that the stream would have shown arriving. Without this\n * the client simply sits on its own history, and the reply appears only when\n * something else happens to reload it.\n *\n * The better fix is upstream: route a session to one instance (on Azure App\n * Service that is ARR affinity, which is on by default) so the refresh lands\n * where its run is. This is the fallback for when it does not — a scale-in,\n * a new device, a cleared cookie.\n */\n onAttachMiss?: (params: { threadId: string }) => void;\n}\n\nexport interface UseChatResult<P extends keyof AgentRoutes> {\n /**\n * The transcript, tools and all.\n *\n * A `tool-call` part narrows on `name`, and with it `input`, `output` and\n * `progress` — the tool's own yields, in order, typed by what its `execute`\n * yields rather than as `unknown`. `nested` on that same part holds the\n * sub-agent runs the tool drove, each an ordinary `AgentMessage[]` with a\n * name and a label, so a component walks into it and renders it with whatever\n * it already renders `messages` with:\n *\n * {part.type === \"tool-call\" && part.nested?.map((run) => (\n * <Transcript key={run.runId} title={run.label ?? run.agent} messages={run.messages} />\n * ))}\n *\n * Those inner messages are typed with the default `ToolShapes`, not this\n * route's: a sub-agent's tools are its own, and typing its transcript with\n * the parent's tool names would be a lie the compiler tells.\n */\n messages: AgentMessage<ToolsOf<P>, OutputOf<P>>[];\n status: ChatStatus;\n /** Cleared by the next successful send, so a retry does not have to clear it. */\n error: AgentError | null;\n /** Set once the server has assigned one. */\n threadId?: string;\n /** The run being streamed or attached to, if any. */\n runId?: string;\n /**\n * How far this client has got, to hand back as `cursor` on the next mount.\n *\n * Persist it with `messages`: a restored transcript whose cursor was not\n * restored asks `/attach` for a run it already has, and additive deltas\n * arriving a second time are how an answer ends up printed twice. `runId` is\n * undefined until a run has named itself, and the pair is meaningless until\n * then — there is nothing to resume.\n */\n cursor: { runId?: string; seq: number };\n\n /**\n * The one way to advance the conversation. A string is shorthand for\n * `{ text }`; answers to pending calls go in the same turn, and may travel\n * with text.\n */\n sendMessage(turn: ClientTurn | string): Promise<void>;\n /**\n * Explicit cancel — a closed tab no longer stops a run, so this is what does.\n *\n * The UI stops immediately; the server call is what actually ends the\n * generation and any tool mid-flight. `messages` keeps the interrupted turn\n * with everything it had produced, marked `aborted`, so the transcript shows\n * where it was cut rather than losing text the user already read.\n */\n stop(): Promise<void>;\n /** Drops the last assistant turn and re-runs from the user turn before it. */\n regenerate(): Promise<void>;\n setMessages(messages: AgentMessage<ToolsOf<P>, OutputOf<P>>[]): void;\n\n /**\n * Non-empty exactly when `status === \"awaiting-input\"`.\n *\n * A call a *sub*-agent made arrives here too, with `path` naming the chain of\n * tool calls it is nested under. Nothing else about it is different, and that\n * is the point: it is answered by `approve`/`answer` like any other, and\n * `path` is there only so a UI can say which agent is asking.\n */\n pending: PendingToolCall<ToolsOf<P>>[];\n /**\n * The deferred tools the model has pulled in during the run in flight.\n *\n * Somewhere for a UI to put \"…looking for the right tool\" instead of an\n * unexplained pause. Run-scoped and not part of the transcript: it says what\n * is happening now, and is empty again on the next `run-start`, so it is not\n * something to persist beside `messages`.\n */\n loadedTools: string[];\n /**\n * Sugar over `sendMessage`. The hook carries each pending call's signature —\n * and its `path`, if the question came from a sub-agent — and hands both back\n * untouched, so an app answers with a boolean and never sees the mechanism\n * that makes answering trustworthy. Answering a nested question is the same\n * call as answering a top-level one.\n */\n approve(toolCallId: string, approve: boolean, reason?: string): Promise<void>;\n /** The same, for a `question` or `client` tool: the value is checked against\n * the tool's output schema server-side before the model sees it. */\n answer(toolCallId: string, output: unknown): Promise<void>;\n\n /** Uploads through the agent's own upload route and returns the id to put in\n * `sendMessage({ files })`. Here rather than in app code because the route is\n * derived from the same path. */\n attach(file: File): Promise<{ fileId: string; name: string; mimeType: string }>;\n}\n\n/**\n * What `POST /api<path>` receives.\n *\n * One route for every client turn, so the body has to say which mode it is in:\n * with a `threadId` the server owns the history, without one the client carries\n * it and sends the transcript *before* this turn — the turn itself is never in\n * `messages`, or the server would see it twice.\n */\nexport type AgentRequestBody = {\n turn: ClientTurn;\n threadId?: string;\n messages?: AgentMessage[];\n /**\n * A handle on this run, minted before the run has one.\n *\n * `stop` needs something to name, and the server's own `runId` does not reach\n * the client until `run-start` — a gap covering the network round trip and\n * the provider's time to first token, which is precisely the window a user\n * cancels in. A thread id would do it where there is one, but the stateless\n * first turn has neither. So the client names the run it is starting, and the\n * server is expected to remember the mapping for as long as the run lives.\n */\n clientRunId: string;\n} & Record<string, unknown>;\n\n/**\n * What `POST /api<path>/attach` receives: which thread, and how far this client\n * already got.\n *\n * `runId` says which run the cursor counts within. Absent, or naming a run that\n * is not the live one, the cursor cannot be honoured and the run has to be\n * replayed from the start.\n */\nexport type AgentAttachBody = { threadId: string; cursor: number; runId?: string };\n\n/**\n * What `POST /api<path>/stop` receives.\n *\n * Every field is optional because the client stops runs it cannot always name:\n * before `run-start` there is no `runId`, and a stateless first turn has no\n * `threadId` either. Whichever handle is present is enough — resolve by\n * `runId`, else by `clientRunId`, else the live run on `threadId`.\n */\nexport type AgentStopBody = { runId?: string; threadId?: string; clientRunId?: string };\n\nlet localIdCounter = 0;\n\n/** Ids the client mints: messages it authored, and the correlation id it names a\n * run by before the server has. Server-assigned ids are what keep a reattached\n * stream from duplicating a message, so the two must not collide — hence the\n * prefix, which also tells a server log which end invented an id. */\nfunction localId() {\n const suffix =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : `${Date.now()}-${localIdCounter++}`;\n return `local_${suffix}`;\n}\n\n/** The same substitution `useMutation` does, so a parameterised agent path\n * behaves like every other route in the package. */\nfunction applyRouteParams(url: string, params: Record<string, string>) {\n let out = url;\n for (const [key, value] of Object.entries(params)) {\n out = out.replace(`:${key}?`, value).replace(`:${key}`, value);\n }\n return out;\n}\n\nfunction isAbort(error: unknown) {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n (error as { name?: string }).name === \"AbortError\"\n );\n}\n\nasync function httpError(response: Response): Promise<AgentError> {\n // Anything that fails before the headers flush is an ordinary HTTP error —\n // auth, validation, an unknown agent — and never reaches the stream's own\n // error event, so it has to be translated into the same shape here.\n let message = response.statusText || `Request failed with status ${response.status}`;\n let code: AgentError[\"code\"] = response.status === 429 ? \"rate_limited\" : \"unknown\";\n try {\n const data = (await response.json()) as {\n error?: { code?: string; message?: string };\n message?: string;\n };\n message = data?.error?.message ?? data?.message ?? message;\n // The one server code an app has something to do about: the thread it\n // holds is gone, and the fix is a new one or a stateless send, neither of\n // which \"unknown\" would suggest. Other codes stay folded into the status,\n // because the union names what the client can act on, not what the server\n // can say.\n if (data?.error?.code === \"thread_not_found\") {\n code = \"thread_not_found\";\n }\n } catch {\n // Not JSON. The status line is all there is.\n }\n return {\n code,\n message,\n retryable: response.status === 429 || response.status >= 500,\n };\n}\n\nfunction turnFrom(message: AgentMessage): ClientTurn {\n let text = \"\";\n const files: NonNullable<ClientTurn[\"files\"]> = [];\n for (const part of message.content) {\n if (part.type === \"text\") text += part.text;\n if (part.type === \"file\") {\n files.push({ fileId: part.fileId, name: part.name, mimeType: part.mimeType });\n }\n }\n return { ...(text ? { text } : {}), ...(files.length > 0 ? { files } : {}) };\n}\n\n/**\n * The transcript as the server needs it, without the progress logs.\n *\n * In stateless mode `send` posts the whole history on *every* turn, so anything\n * that accumulates on a message is paid for again on each one. `progress` is\n * the only part of the transcript that grows without a bound the model imposes:\n * a generator tool yielding per chunk writes an entry per chunk, and a tool that\n * yielded five thousand times would put five thousand objects in the body of\n * turn 2, turn 3, and every turn after, forever.\n *\n * Nothing server-side reads it. It is not translated into a provider message,\n * `openCalls` matches on `toolCallId`, and the resume path replays a sub-agent\n * from `nested` — which is why `nested` is kept here, recursed into rather than\n * dropped, while `progress` is not. The client's own copy is untouched: this\n * shapes the request body only, so a UI still renders every yield it received.\n */\nfunction forWire(messages: AgentMessage[]): AgentMessage[] {\n return messages.map((message) => {\n // Identity for the overwhelming majority of messages, which have neither —\n // `nested` counts because a sub-agent's own yields are the same dead weight\n // one level down.\n const touched = message.content.some(\n (part) => part.type === \"tool-call\" && (part.progress || part.nested),\n );\n if (!touched) return message;\n return {\n ...message,\n content: message.content.map((part) => {\n if (part.type !== \"tool-call\") return part;\n const { progress: _progress, ...rest } = part;\n return rest.nested\n ? {\n ...rest,\n nested: rest.nested.map((run) => ({ ...run, messages: forWire(run.messages) })),\n }\n : rest;\n }),\n };\n });\n}\n\n/**\n * The path is the agent's route, exactly as mounted:\n *\n * const { messages, sendMessage } = useChat(\"/chat\")\n *\n * and a tool part inside `messages` narrows on `name`, giving `input` and\n * `output` their real types — which is the whole reason the tool tuple is\n * carried through `Agent`, `AgentRoute` and `RPC` instead of being erased at the\n * first boundary.\n */\nexport function useChat<P extends keyof AgentRoutes>(\n path: P,\n params: UseChatParams<P> = {},\n): UseChatResult<P> {\n const {\n threadId: initialThreadId,\n initialMessages,\n cursor: initialCursor,\n attach: attachOnMount = true,\n body: extraBody,\n headers,\n onFinish,\n onError,\n onAwaitingInput,\n onAttachMiss,\n } = params;\n\n const routeParams = useParams();\n const base = useMemo(\n // A route key may or may not carry a method prefix depending on how `RPC`\n // renders it; stripping one is free and stops `/api/POST:/chat` from being\n // a possibility.\n () => `/api${applyRouteParams(String(path).replace(/^[A-Z]+:/, \"\"), routeParams)}`,\n [path, routeParams],\n );\n\n /**\n * The state lives in a ref and is mirrored into `useState` for rendering.\n *\n * A stream applies frames faster than React commits, and the reducer needs\n * the result of the previous frame, not the value captured when the loop\n * started. The ref is the source of truth; `setState` is the notification.\n *\n * `any` inside, exact at the boundary: the wire carries erased tool shapes and\n * the route's real ones are re-asserted once, in the returned object.\n */\n const stateRef = useRef<ChatState<any, any> | null>(null);\n if (stateRef.current === null) {\n stateRef.current = initialChatState<any, any>({\n messages: initialMessages,\n threadId: initialThreadId,\n seq: initialCursor?.seq,\n cursorRunId: initialCursor?.runId,\n });\n }\n const [state, setState] = useState<ChatState<any, any>>(stateRef.current);\n const [phase, setPhase] = useState<\"idle\" | \"submitted\" | \"streaming\">(\"idle\");\n\n const mountedRef = useRef(true);\n // The request in flight, carrying the id `stop()` names it by. The two travel\n // together because a controller with no handle can only close the connection,\n // and closing the connection is exactly what no longer stops a run.\n const abortRef = useRef<{ controller: AbortController; clientRunId?: string } | null>(null);\n // The latest callbacks, so a stream started three renders ago still calls the\n // ones the component has now instead of a stale closure.\n const handlers = useRef({ onFinish, onError, onAwaitingInput, onAttachMiss });\n handlers.current = { onFinish, onError, onAwaitingInput, onAttachMiss };\n const requestRef = useRef({ base, headers, extraBody });\n requestRef.current = { base, headers, extraBody };\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n abortRef.current?.controller.abort();\n abortRef.current = null;\n };\n }, []);\n\n const commit = useCallback((next: ChatState<any, any>) => {\n stateRef.current = next;\n // Nothing after unmount: the stream loop is async and outlives the render\n // that started it.\n if (mountedRef.current) setState(next);\n }, []);\n\n const setPhaseSafe = useCallback((next: \"idle\" | \"submitted\" | \"streaming\") => {\n if (mountedRef.current) setPhase(next);\n }, []);\n\n /**\n * Report a failure without touching the conversation.\n *\n * `pending` deliberately survives. Most of what fails here has nothing to do\n * with the question the conversation is holding — an upload that 500s, an\n * answer aimed at a call this client never had — and the pending calls carry\n * the only signatures that can answer them, held nowhere else. Dropping them\n * because an unrelated request failed would leave the user unable to approve\n * anything at all, with a fresh turn (which the server reads as refusing\n * everything) the only way out.\n *\n * The failures that really do end the turn clear `pending` where they happen:\n * `send` clears it as the turn goes out, and the stream's own `error` event\n * clears it in the reducer.\n */\n const fail = useCallback(\n (error: AgentError) => {\n commit({ ...stateRef.current!, error });\n handlers.current.onError?.(error);\n },\n [commit],\n );\n\n const post = useCallback((url: string, payload: unknown, signal?: AbortSignal) => {\n const { headers: extraHeaders } = requestRef.current;\n return fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...extraHeaders },\n body: JSON.stringify(payload),\n signal,\n });\n }, []);\n\n /**\n * The server call that ends a run. Shared by `stop()` and by a `send` that\n * supersedes one; what each does with a failure is its own, because a stop\n * that did not land is a run still working through its tool loop and still\n * billing, behind a transcript that says it was cut — and whose problem that\n * is depends on who asked.\n */\n const postStop = useCallback(\n async (body: AgentStopBody, report: (error: AgentError) => void) => {\n const { base: url } = requestRef.current;\n try {\n const response = await post(`${url}/stop`, body);\n if (!response.ok) report(await httpError(response));\n } catch (error) {\n if (isAbort(error)) return;\n report({\n code: \"unknown\",\n message: error instanceof Error ? error.message : String(error),\n retryable: true,\n });\n }\n },\n [post],\n );\n\n const consume = useCallback(\n async (response: Response, signal: AbortSignal) => {\n for await (const frame of decodeSSE(response.body)) {\n // The signal is checked here as well as by `fetch`, because `stop()`\n // has to stop the *UI* whether or not the transport under it honours an\n // abort promptly. A token that lands after the user cancelled is worse\n // than one that never arrives.\n if (!mountedRef.current || signal.aborted) return;\n setPhaseSafe(\"streaming\");\n const previous = stateRef.current!;\n const next = applyFrame(previous, frame);\n // A frame at or below the cursor. Reattachment and retrying proxies both\n // produce these; applying the deltas again would double the text, and\n // firing `onFinish` again would double whatever the app does with it.\n if (next === previous) continue;\n commit(next);\n\n const event = frame.event;\n if (event.type === \"message-end\") {\n // Only for a message that was not already finished. `seq` catches an\n // ordinary redelivery, but a run replayed from the top onto a restored\n // transcript is all new to the cursor, and firing `onFinish` again\n // means an app that persists in it writes the message a second time.\n // A message-end is terminal, so a second one is replay by definition.\n const before = previous.messages.find((m: AgentMessage) => m.id === event.messageId);\n const message = next.messages.find((m: AgentMessage) => m.id === event.messageId);\n if (message && before?.finishReason === undefined) {\n handlers.current.onFinish?.(message);\n }\n } else if (event.type === \"awaiting-input\") {\n handlers.current.onAwaitingInput?.(event.pending as PendingToolCall<ToolsOf<P>>[]);\n } else if (event.type === \"error\") {\n handlers.current.onError?.(event.error);\n }\n }\n },\n [commit, setPhaseSafe],\n );\n\n const send = useCallback(\n async (turn: ClientTurn) => {\n const { base: url, extraBody: body } = requestRef.current;\n // One run at a time per hook. A second send while the first is streaming\n // is a user who changed their mind, not a request to interleave two\n // assistants into one transcript.\n //\n // The superseded turn is marked aborted as it is cut, not left to whatever\n // stamps it later: a message with no finish reason is indistinguishable\n // from one still streaming, and the next run's `run-end` would otherwise\n // find it and label an answer that stopped mid-sentence \"stop\". In\n // stateless mode that reason is posted back to the server on every later\n // turn, so the model would be told the interruption never happened.\n const superseded = abortRef.current;\n superseded?.controller.abort();\n const clientRunId = localId();\n const controller = new AbortController();\n abortRef.current = { controller, clientRunId };\n\n if (superseded) {\n // Aborting the fetch only closes the connection, and a closed connection\n // no longer stops a run: the superseded one would keep going server\n // side, blind to this turn and still billing. So it is stopped the way\n // `stop()` stops it, by the handles that name exactly that run — never\n // by `threadId`, which by the time this lands may name the run this\n // turn is about to start. A run this client did not start, and whose\n // `run-start` never arrived, has no such handle; the server ends it\n // anyway when the new turn reaches the thread. Not awaited: the server\n // orders the two, and a round trip before every \"changed my mind\" would\n // be paid for nothing.\n const { runId, threadId } = stateRef.current!;\n const body: AgentStopBody = {\n ...(runId ? { runId } : {}),\n ...(superseded.clientRunId ? { clientRunId: superseded.clientRunId } : {}),\n };\n if (Object.keys(body).length > 0) {\n // A failure here does not go through `fail`. The post is not awaited,\n // so its answer lands after this send has cleared `error` for the\n // turn going out, and a 502 from `/stop` would sit on an answer that\n // is streaming fine — a turn that succeeded, ending `error`. With a\n // thread the server ends the old run when this turn reaches it, so a\n // stop that failed changed nothing and there is nothing to say.\n // Without one this client is the only thing that knows the old run\n // is still going, so the app is told, and only told.\n void postStop(body, threadId ? () => {} : (error) => handlers.current.onError?.(error));\n }\n }\n\n const previous = superseded ? markAborted(stateRef.current!) : stateRef.current!;\n const history = previous.messages;\n const authored: AgentMessage[] =\n turn.text || turn.files?.length\n ? [\n {\n id: localId(),\n role: \"user\",\n content: [\n ...(turn.text ? [{ type: \"text\" as const, text: turn.text }] : []),\n ...(turn.files ?? []).map((file) => ({ type: \"file\" as const, ...file })),\n ],\n createdAt: new Date().toISOString(),\n },\n ]\n : [];\n\n commit({\n ...previous,\n messages: [...history, ...authored],\n // The error belonged to the attempt being retried. Leaving it up while\n // the retry streams is a UI that reports a failure and a success at once.\n error: null,\n // Answered or not, this turn resolves every pending call — the server\n // denies whatever the turn left out — so holding them would leave the UI\n // asking a question that is already settled. No local tool-result parts\n // are fabricated here: the server emits one per call, denials included,\n // and guessing at the output of a call it has not run yet would be a lie\n // the stream then contradicts.\n pending: [],\n });\n setPhaseSafe(\"submitted\");\n\n try {\n const payload: AgentRequestBody = {\n turn,\n clientRunId,\n ...(stateRef.current!.threadId\n ? { threadId: stateRef.current!.threadId }\n : // Stripped of the progress logs, which no part of the server\n // reads and which every later turn would otherwise re-upload.\n { messages: forWire(history) }),\n ...body,\n };\n const response = await post(url, payload, controller.signal);\n if (!response.ok) {\n fail(await httpError(response));\n return;\n }\n await consume(response, controller.signal);\n } catch (error) {\n // An abort is `stop()` or unmount; both already put the UI where it\n // belongs, and reporting it as a failure would be wrong twice.\n if (!isAbort(error)) {\n fail({\n code: \"unknown\",\n message: error instanceof Error ? error.message : String(error),\n retryable: true,\n });\n }\n } finally {\n if (abortRef.current?.controller === controller) {\n abortRef.current = null;\n setPhaseSafe(\"idle\");\n }\n }\n },\n [commit, consume, fail, post, postStop, setPhaseSafe],\n );\n\n const sendMessage = useCallback(\n (turn: ClientTurn | string) => send(typeof turn === \"string\" ? { text: turn } : turn),\n [send],\n );\n\n /**\n * Answers queued within one tick become one turn.\n *\n * This is not an optimisation. A turn that leaves a pending call unanswered\n * denies it, so sending three approvals as three turns would have the first\n * one refuse the other two — and the natural UI code, a loop over `pending`\n * calling `approve` for each, is exactly that shape. Coalescing on the\n * microtask is what makes the declared per-call signature safe to use the way\n * it reads.\n */\n const queued = useRef<{ results: ClientToolResult[]; flushed: Promise<void> } | null>(null);\n\n const queueResult = useCallback(\n (result: ClientToolResult) => {\n if (queued.current) {\n queued.current.results.push(result);\n return queued.current.flushed;\n }\n const results: ClientToolResult[] = [result];\n const flushed = Promise.resolve().then(() => {\n queued.current = null;\n return send({ toolResults: results });\n });\n queued.current = { results, flushed };\n return flushed;\n },\n [send],\n );\n\n /**\n * The half of a `ClientToolResult` the app never writes: the id, the\n * signature, and the path.\n *\n * All three are carried by the hook and handed back untouched, which is what\n * makes a sub-agent's question answerable with the same two lines as a\n * top-level one. `path` is absent for a call the parent's own tools made, so\n * an app cannot tell from its own code where the question came from — the only\n * difference is that `pending[i].path` is there to render if it wants to say\n * which agent is asking.\n */\n const resultFor = useCallback(\n (\n toolCallId: string,\n build: (base: { toolCallId: string; signature: string; path?: string[] }) => ClientToolResult,\n ) => {\n const matches = stateRef.current!.pending.filter(\n (candidate: PendingToolCall) => candidate.toolCallId === toolCallId,\n );\n const call = matches[0];\n if (!call) {\n // Not a network failure: the app answered a call this client is not\n // holding. Surfacing it beats sending a turn the server will reject for\n // a missing signature, which is where it would otherwise show up.\n fail({\n code: \"invalid_tool_result\",\n message: `No pending tool call ${toolCallId}`,\n toolCallId,\n retryable: false,\n });\n return null;\n }\n if (matches.length > 1) {\n // Nesting is what makes this reachable: two sub-runs, under two\n // different tools of the same parent turn, each holding a call. The ids\n // come from whichever provider ran each sub-run, so nothing guarantees\n // they differ, and the path is what tells them apart — but `approve` is\n // deliberately addressed by id alone, because an app must not have to\n // know a question was nested to answer it. Refusing beats guessing:\n // picking one would silently approve a tool the user was not looking at.\n fail({\n code: \"invalid_tool_result\",\n message: `Ambiguous tool call ${toolCallId}: ${matches.length} pending calls share it`,\n toolCallId,\n retryable: false,\n });\n return null;\n }\n // The signature travels back exactly as it arrived. It signs the input the\n // server proposed — and, for a nested call, the path it was proposed at —\n // so an app that never touches either cannot approve one thing and have\n // another run, or have the right answer applied to the wrong sub-agent.\n return build({\n toolCallId,\n signature: call.signature,\n ...(call.path ? { path: call.path } : {}),\n });\n },\n [fail],\n );\n\n const approve = useCallback(\n async (toolCallId: string, approved: boolean, reason?: string) => {\n const result = resultFor(toolCallId, (base) => ({\n ...base,\n approve: approved,\n ...(reason ? { reason } : {}),\n }));\n if (result) await queueResult(result);\n },\n [queueResult, resultFor],\n );\n\n const answer = useCallback(\n async (toolCallId: string, output: unknown) => {\n const result = resultFor(toolCallId, (base) => ({ ...base, output }));\n if (result) await queueResult(result);\n },\n [queueResult, resultFor],\n );\n\n const stop = useCallback(async () => {\n const { runId, threadId } = stateRef.current!;\n // The UI stops now. The POST below is what actually ends the generation and\n // any tool mid-flight, and it may take a moment; a user who clicked stop\n // must not keep watching tokens arrive while it flies.\n const inFlight = abortRef.current;\n inFlight?.controller.abort();\n abortRef.current = null;\n commit(markAborted(stateRef.current!));\n setPhaseSafe(\"idle\");\n // Whether there is anything to stop is a question about the *request*, not\n // about `runId`. Gating on `runId` meant the whole time-to-first-token\n // window — the seconds a user is most likely to cancel in, and longer when\n // the agent searches for deferred tools first — silently posted nothing,\n // leaving a tool loop running that a dropped connection does not touch. The\n // same hole reopened on a correct attach, whose tail carries no `run-start`.\n if (!inFlight && !runId) return;\n // Three handles, any of which the route can resolve by; which ones exist\n // depends on how far the run got. `clientRunId` is the one that is always\n // there for a turn this client started, which is what closes the window.\n const body: AgentStopBody = {\n ...(runId ? { runId } : {}),\n ...(threadId ? { threadId } : {}),\n ...(inFlight?.clientRunId ? { clientRunId: inFlight.clientRunId } : {}),\n };\n await postStop(body, fail);\n }, [commit, fail, postStop, setPhaseSafe]);\n\n const regenerate = useCallback(async () => {\n const messages = stateRef.current!.messages as AgentMessage[];\n let assistantIndex = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]!.role === \"assistant\") {\n assistantIndex = i;\n break;\n }\n }\n if (assistantIndex === -1) return;\n let userIndex = -1;\n for (let i = assistantIndex - 1; i >= 0; i--) {\n if (messages[i]!.role === \"user\") {\n userIndex = i;\n break;\n }\n }\n if (userIndex === -1) return;\n\n const turn = turnFrom(messages[userIndex]!);\n // The user turn goes too, because `send` re-appends it. Keeping it here and\n // sending it again would show the question twice.\n commit({\n ...stateRef.current!,\n messages: messages.slice(0, userIndex),\n pending: [],\n error: null,\n });\n await send(turn);\n }, [commit, send]);\n\n const setMessages = useCallback(\n (messages: AgentMessage<ToolsOf<P>, OutputOf<P>>[]) => {\n commit({ ...stateRef.current!, messages: messages as AgentMessage<any, any>[] });\n },\n [commit],\n );\n\n const uploadFile = useCallback(\n async (file: File) => {\n const { base: url, headers: extraHeaders } = requestRef.current;\n const form = new FormData();\n form.append(\"file\", file);\n // No Content-Type: the boundary is the browser's to write.\n const response = await fetch(`${url}/files`, {\n method: \"POST\",\n headers: { ...extraHeaders },\n body: form,\n });\n if (!response.ok) {\n const error = await httpError(response);\n fail(error);\n throw new Error(error.message);\n }\n const data = (await response.json()) as { fileId: string; name?: string; mimeType?: string };\n // The route only has to return the id; the name and type are already here,\n // so the caller gets something it can hand straight to `sendMessage`.\n return {\n fileId: data.fileId,\n name: data.name ?? file.name,\n mimeType: data.mimeType ?? file.type,\n };\n },\n [fail],\n );\n\n useEffect(() => {\n // Mount only, deliberately: `threadId` is the handle that survives a\n // refresh, and re-probing every time it changes would race a stream that is\n // already running on it.\n if (attachOnMount === false || !initialThreadId) return;\n const controller = new AbortController();\n // No `clientRunId`: this client did not start the run, so the thread is the\n // only handle it has on it — and `/attach` needs one anyway.\n abortRef.current = { controller };\n void (async () => {\n try {\n const body: AgentAttachBody = {\n threadId: initialThreadId,\n // The cursor the caller restored alongside `initialMessages`, so the\n // route can send the tail. Left at -1 it says \"I have seen nothing\",\n // and a run still inside its post-`run-end` TTL replays from the top\n // onto a transcript that already holds it.\n cursor: stateRef.current!.seq,\n ...(stateRef.current!.cursorRunId ? { runId: stateRef.current!.cursorRunId } : {}),\n };\n const response = await post(`${requestRef.current.base}/attach`, body, controller.signal);\n // Nothing running is the ordinary answer, not a failure: the route says\n // so with an empty response and the screen simply stays as it was —\n // except that on more than one instance it is also what a refresh\n // routed away from its run gets, which is not ordinary at all. The\n // client cannot tell; `onAttachMiss` is how an app that runs more than\n // one process gets to react.\n if (!response.ok || response.status === 204 || !response.body) {\n handlers.current.onAttachMiss?.({ threadId: initialThreadId });\n return;\n }\n await consume(response, controller.signal);\n } catch {\n // A probe that could not be made leaves the client exactly where a\n // client without `attach` would be — with its own history and no run.\n } finally {\n if (abortRef.current?.controller === controller) {\n abortRef.current = null;\n setPhaseSafe(\"idle\");\n }\n }\n })();\n return () => controller.abort();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const status: ChatStatus =\n // Ordered so the declared invariant — `pending` non-empty exactly when the\n // status is `awaiting-input` — is true by construction rather than by\n // everything downstream remembering to keep it.\n state.pending.length > 0\n ? \"awaiting-input\"\n : phase !== \"idle\"\n ? phase\n : state.error\n ? \"error\"\n : \"idle\";\n\n return {\n messages: state.messages as AgentMessage<ToolsOf<P>, OutputOf<P>>[],\n status,\n error: state.error,\n threadId: state.threadId,\n runId: state.runId,\n cursor: { runId: state.cursorRunId, seq: state.seq },\n sendMessage,\n stop,\n regenerate,\n setMessages,\n pending: state.pending as PendingToolCall<ToolsOf<P>>[],\n loadedTools: state.loadedTools,\n approve,\n answer,\n attach: uploadFile,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { AgentError, AgentMessage, AgentStreamFrame, FinishReason, PendingToolCall, ToolShapes } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Frames to a message list.
|
|
4
|
+
*
|
|
5
|
+
* The rule the whole file is built around: **a client may be handed any
|
|
6
|
+
* suffix of a run, and may be handed the same frame twice.** An attached
|
|
7
|
+
* stream starts mid-message, a reconnect replays from a cursor, and a proxy
|
|
8
|
+
* that retries is entitled to deliver a frame again. So nothing here tracks
|
|
9
|
+
* "the current message" — every event that touches a message names it, and a
|
|
10
|
+
* message the list has never heard of is created on the spot rather than
|
|
11
|
+
* dropped.
|
|
12
|
+
*
|
|
13
|
+
* The two mechanisms that make that work:
|
|
14
|
+
*
|
|
15
|
+
* - `seq` guards replay. A frame at or below the state's cursor is a no-op,
|
|
16
|
+
* and `applyFrame` returns the *same object* so a caller can tell nothing
|
|
17
|
+
* happened. Without it a redelivered `text-delta` would append its text a
|
|
18
|
+
* second time — a bug that looks fine until someone refreshes.
|
|
19
|
+
* - a finished message is closed to further deltas. `seq` catches a redelivery
|
|
20
|
+
* within a run, but not a server that replays a run from zero to a client
|
|
21
|
+
* that already has the transcript; `message-end` is terminal, so a delta for
|
|
22
|
+
* a message that already carries a finish reason is replay by definition.
|
|
23
|
+
* - every mutation is either an append that the seq guard already protects,
|
|
24
|
+
* or an upsert keyed by an id. Tool calls and tool results arrive more than
|
|
25
|
+
* once (a call re-streams as its arguments grow), so those are keyed by
|
|
26
|
+
* `toolCallId`, never pushed blindly.
|
|
27
|
+
*
|
|
28
|
+
* A sub-agent's transcript is built by this same function calling itself. A
|
|
29
|
+
* `nested-event` is unwrapped onto the `NestedRun` hanging off its parent's
|
|
30
|
+
* tool-call part and handed to `reduce` again, so every rule above holds inside
|
|
31
|
+
* a nested run for free — a nested `message-start` that never arrived, a
|
|
32
|
+
* nested delta after a nested `message-end`, a nested `run-end` closing off a
|
|
33
|
+
* message that never ended. Two levels of nesting cost this file nothing over
|
|
34
|
+
* one, which is the whole reason the wire has a `nested-event` rather than a
|
|
35
|
+
* second reducer.
|
|
36
|
+
*/
|
|
37
|
+
export type ChatState<T extends ToolShapes = ToolShapes, O = unknown> = {
|
|
38
|
+
messages: AgentMessage<T, O>[];
|
|
39
|
+
/** Non-empty exactly while the conversation is holding a question. */
|
|
40
|
+
pending: PendingToolCall<T>[];
|
|
41
|
+
error: AgentError | null;
|
|
42
|
+
runId?: string;
|
|
43
|
+
threadId?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The highest frame applied. This is the cursor `/attach` is asked to resume
|
|
46
|
+
* from, which is why it lives in the state a client keeps rather than in the
|
|
47
|
+
* stream reader that dies with the connection.
|
|
48
|
+
*/
|
|
49
|
+
seq: number;
|
|
50
|
+
/**
|
|
51
|
+
* Which run `seq` counts within.
|
|
52
|
+
*
|
|
53
|
+
* Separate from `runId`, which is cleared when the run ends because a UI asks
|
|
54
|
+
* it whether anything is live. The cursor outlives the run it belongs to: a
|
|
55
|
+
* frame from run_1 redelivered after run_1 finished is still a duplicate, and
|
|
56
|
+
* without somewhere to remember that, replaying a finished run would look
|
|
57
|
+
* exactly like the start of a new one.
|
|
58
|
+
*/
|
|
59
|
+
cursorRunId?: string;
|
|
60
|
+
/**
|
|
61
|
+
* The messages the run in hand has touched, oldest first.
|
|
62
|
+
*
|
|
63
|
+
* `run-end` and `markAborted` both have to finish off a message whose
|
|
64
|
+
* `message-end` never arrived, and "every assistant message with no finish
|
|
65
|
+
* reason" is the wrong set to do it over: a superseded turn from an earlier
|
|
66
|
+
* run is sitting in exactly that state, and stamping it with *this* run's
|
|
67
|
+
* reason would relabel an interrupted answer as a clean completion. So the
|
|
68
|
+
* reducer remembers which messages this run actually wrote to, and finishes
|
|
69
|
+
* off only those. Reset by `run-start`; on a mid-run attach it simply fills
|
|
70
|
+
* up from the first frame that names a message, which is the same set.
|
|
71
|
+
*/
|
|
72
|
+
runMessageIds: string[];
|
|
73
|
+
/**
|
|
74
|
+
* The deferred tools the model has pulled in during this run, as a set.
|
|
75
|
+
*
|
|
76
|
+
* Not a message part, because a `tool-search` names no tool call and no
|
|
77
|
+
* message — it happens *before* any call, which is exactly what makes it
|
|
78
|
+
* worth showing ("looking for the right tool" instead of an unexplained
|
|
79
|
+
* pause). So unlike `tool-progress` and `nested-event`, which now have a home
|
|
80
|
+
* on the tool-call part, this one had nowhere in `AgentMessage` to go that
|
|
81
|
+
* would not have meant inventing a content part in a frozen contract.
|
|
82
|
+
*
|
|
83
|
+
* A set, not a log: union is the only accumulation that survives redelivery
|
|
84
|
+
* without a guard, and a client attached mid-run and handed a `tool-search`
|
|
85
|
+
* it has already applied must not show the same tool twice. Run-scoped and
|
|
86
|
+
* reset by `run-start` — it describes a run in progress, so it is deliberately
|
|
87
|
+
* not something `initialChatState` restores.
|
|
88
|
+
*/
|
|
89
|
+
loadedTools: string[];
|
|
90
|
+
finishReason?: FinishReason;
|
|
91
|
+
};
|
|
92
|
+
export declare function initialChatState<T extends ToolShapes = ToolShapes, O = unknown>(init?: {
|
|
93
|
+
messages?: AgentMessage<T, O>[];
|
|
94
|
+
pending?: PendingToolCall<T>[];
|
|
95
|
+
threadId?: string;
|
|
96
|
+
/** Where a restored client left off. `-1` means "I have seen nothing". */
|
|
97
|
+
seq?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Which run that `seq` counts within. A cursor without it is a number with
|
|
100
|
+
* no units — frames number from zero in every run, so `seq: 10` means
|
|
101
|
+
* nothing until you know whether the live run is the one that reached 10.
|
|
102
|
+
*/
|
|
103
|
+
cursorRunId?: string;
|
|
104
|
+
}): ChatState<T, O>;
|
|
105
|
+
/**
|
|
106
|
+
* Apply one frame.
|
|
107
|
+
*
|
|
108
|
+
* `now` is an argument rather than a `Date.now()` call so the reducer stays a
|
|
109
|
+
* pure function of its inputs — it is only ever read when a frame arrives for a
|
|
110
|
+
* message the list has not seen, which is precisely the mid-run-attach path a
|
|
111
|
+
* test needs to pin down.
|
|
112
|
+
*/
|
|
113
|
+
export declare function applyFrame<T extends ToolShapes = ToolShapes, O = unknown>(state: ChatState<T, O>, frame: AgentStreamFrame<T, O>, now?: string): ChatState<T, O>;
|
|
114
|
+
/**
|
|
115
|
+
* The interrupted turn, kept.
|
|
116
|
+
*
|
|
117
|
+
* `stop()` calls this before the server has said anything, because the point of
|
|
118
|
+
* stopping is that the UI reacts now. Whatever the assistant had produced stays
|
|
119
|
+
* in the list marked `aborted` — dropping it would lose text the user already
|
|
120
|
+
* read, and would leave the transcript unable to say it was cut short.
|
|
121
|
+
*
|
|
122
|
+
* `pending` is untouched: a question the conversation is holding survives a
|
|
123
|
+
* stopped run, and clearing it would silently discard the answer the user still
|
|
124
|
+
* owes.
|
|
125
|
+
*/
|
|
126
|
+
export declare function markAborted<T extends ToolShapes = ToolShapes, O = unknown>(state: ChatState<T, O>): ChatState<T, O>;
|
|
127
|
+
//# sourceMappingURL=reducer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../../../ai/client/reducer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,UAAU,EACV,YAAY,EAEZ,gBAAgB,EAChB,YAAY,EAEZ,eAAe,EAEf,UAAU,EACX,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO,IAAI;IACtE,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC/B,sEAAsE;IACtE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO,EAC7E,IAAI,GAAE;IACJ,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAWjB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO,EACvE,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EACtB,KAAK,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAC7B,GAAG,GAAE,MAAiC,GACrC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAUjB;AAwOD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO,EACxE,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GACrB,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAWjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducer.test-d.d.ts","sourceRoot":"","sources":["../../../ai/client/reducer.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentStreamFrame, ToolShapes } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Bytes to frames.
|
|
4
|
+
*
|
|
5
|
+
* Split out of the hook because none of it is React: it is a byte-level parser,
|
|
6
|
+
* and the interesting cases — a chunk boundary landing inside a `data:` line, a
|
|
7
|
+
* multi-byte character split across two reads, a stream that dies mid-event —
|
|
8
|
+
* are all reachable from a test with a string and no DOM.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately not `EventSource`. The agent routes are POSTs carrying a turn,
|
|
11
|
+
* `EventSource` can only GET, and reattachment wants the cursor in the request
|
|
12
|
+
* body next to the thread id rather than in a header the browser owns.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Feed it chunks, get frames. Holds whatever is left over between calls, which
|
|
16
|
+
* is the whole point: `push()` is called once per `reader.read()` and those
|
|
17
|
+
* boundaries have nothing to do with event boundaries.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SSEFrameDecoder<T extends ToolShapes = ToolShapes, O = unknown> {
|
|
20
|
+
private buffer;
|
|
21
|
+
private textDecoder;
|
|
22
|
+
/**
|
|
23
|
+
* A `\r` at the very end of a chunk is ambiguous — the next chunk may open
|
|
24
|
+
* with the `\n` that completes a CRLF, or with the next line. Holding it back
|
|
25
|
+
* for one chunk is what keeps a boundary there from being read as a blank
|
|
26
|
+
* line and cutting an event in half.
|
|
27
|
+
*/
|
|
28
|
+
private pendingCR;
|
|
29
|
+
private lastSeq;
|
|
30
|
+
/** The cursor to resume from: the highest `id:` seen. */
|
|
31
|
+
get cursor(): number;
|
|
32
|
+
push(chunk: Uint8Array | string): AgentStreamFrame<T, O>[];
|
|
33
|
+
/**
|
|
34
|
+
* End of stream. Anything still buffered is an event with no terminating
|
|
35
|
+
* blank line, which the SSE spec discards and so does this — a truncated tail
|
|
36
|
+
* is usually truncated JSON, and half a `text-delta` appended to a message is
|
|
37
|
+
* worse than a missing one.
|
|
38
|
+
*/
|
|
39
|
+
flush(): AgentStreamFrame<T, O>[];
|
|
40
|
+
/** Normalises every line ending to `\n` so the rest of the parser only has to
|
|
41
|
+
* know about one. */
|
|
42
|
+
private append;
|
|
43
|
+
private drain;
|
|
44
|
+
private parse;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The same decoder over a `Response.body`.
|
|
48
|
+
*
|
|
49
|
+
* Cancels the reader when the caller stops consuming — a `break` out of the
|
|
50
|
+
* `for await` is how the hook drops a stream on unmount, and without the cancel
|
|
51
|
+
* the connection stays open behind it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function decodeSSE<T extends ToolShapes = ToolShapes, O = unknown>(stream: ReadableStream<Uint8Array> | null | undefined): AsyncGenerator<AgentStreamFrame<T, O>, void, void>;
|
|
54
|
+
//# sourceMappingURL=sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../ai/client/sse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE7D;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,qBAAa,eAAe,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO;IACzE,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,WAAW,CAAqB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAM;IAErB,yDAAyD;IACzD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;IAO1D;;;;;OAKG;IACH,KAAK,IAAI,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;IAQjC;0BACsB;IACtB,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,KAAK;CAwCd;AAED;;;;;;GAMG;AACH,wBAAuB,SAAS,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAAE,CAAC,GAAG,OAAO,EAC7E,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,SAAS,GACpD,cAAc,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAoBpD"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ApiRouter, type CreateRPC } from "../http";
|
|
2
|
+
import type { HttpRequest } from "../http/HttpRequest";
|
|
3
|
+
import { Agent, AgentTool, Skill, ToolNamespace } from "./Agent";
|
|
4
|
+
import { AgentController, type AgentHookContext, MemoryAgentStore } from "./AgentController";
|
|
5
|
+
import type { AgentMessage, PendingToolCall } from "./types";
|
|
6
|
+
declare const supportAgent: Agent<readonly [AgentTool<"grep", {
|
|
7
|
+
pattern: string;
|
|
8
|
+
filePath: string;
|
|
9
|
+
}, {
|
|
10
|
+
matches: string[];
|
|
11
|
+
}, never>, AgentTool<"bash", {
|
|
12
|
+
command: string;
|
|
13
|
+
}, {
|
|
14
|
+
output: string;
|
|
15
|
+
exitCode: number;
|
|
16
|
+
}, {
|
|
17
|
+
line: string;
|
|
18
|
+
}>, AgentTool<"charge", {
|
|
19
|
+
amountCents: number;
|
|
20
|
+
reason: string;
|
|
21
|
+
}, {
|
|
22
|
+
receiptId: string;
|
|
23
|
+
}, never>, AgentTool<"ask", {
|
|
24
|
+
question: string;
|
|
25
|
+
}, {
|
|
26
|
+
answer: string;
|
|
27
|
+
}, never>, AgentTool<"research", {
|
|
28
|
+
question: string;
|
|
29
|
+
}, {
|
|
30
|
+
summary: string;
|
|
31
|
+
turns: number;
|
|
32
|
+
}, never>, AgentTool<"triage", {
|
|
33
|
+
ticket: string;
|
|
34
|
+
}, {
|
|
35
|
+
verdict: string;
|
|
36
|
+
}, never>, ToolNamespace<"crm", readonly [AgentTool<"listOrders", {
|
|
37
|
+
customerId: string;
|
|
38
|
+
}, {
|
|
39
|
+
orderIds: any[];
|
|
40
|
+
}, never>, AgentTool<"orderDetail", {
|
|
41
|
+
orderId: string;
|
|
42
|
+
}, {
|
|
43
|
+
totalCents: number;
|
|
44
|
+
status: string;
|
|
45
|
+
}, never>, AgentTool<"refundOrder", {
|
|
46
|
+
orderId: string;
|
|
47
|
+
}, {
|
|
48
|
+
refundId: string;
|
|
49
|
+
}, never>]>], readonly [Skill<"refund-policy">], undefined>;
|
|
50
|
+
declare class SupportAgentController extends AgentController<typeof supportAgent> {
|
|
51
|
+
agent: Agent<readonly [AgentTool<"grep", {
|
|
52
|
+
pattern: string;
|
|
53
|
+
filePath: string;
|
|
54
|
+
}, {
|
|
55
|
+
matches: string[];
|
|
56
|
+
}, never>, AgentTool<"bash", {
|
|
57
|
+
command: string;
|
|
58
|
+
}, {
|
|
59
|
+
output: string;
|
|
60
|
+
exitCode: number;
|
|
61
|
+
}, {
|
|
62
|
+
line: string;
|
|
63
|
+
}>, AgentTool<"charge", {
|
|
64
|
+
amountCents: number;
|
|
65
|
+
reason: string;
|
|
66
|
+
}, {
|
|
67
|
+
receiptId: string;
|
|
68
|
+
}, never>, AgentTool<"ask", {
|
|
69
|
+
question: string;
|
|
70
|
+
}, {
|
|
71
|
+
answer: string;
|
|
72
|
+
}, never>, AgentTool<"research", {
|
|
73
|
+
question: string;
|
|
74
|
+
}, {
|
|
75
|
+
summary: string;
|
|
76
|
+
turns: number;
|
|
77
|
+
}, never>, AgentTool<"triage", {
|
|
78
|
+
ticket: string;
|
|
79
|
+
}, {
|
|
80
|
+
verdict: string;
|
|
81
|
+
}, never>, ToolNamespace<"crm", readonly [AgentTool<"listOrders", {
|
|
82
|
+
customerId: string;
|
|
83
|
+
}, {
|
|
84
|
+
orderIds: any[];
|
|
85
|
+
}, never>, AgentTool<"orderDetail", {
|
|
86
|
+
orderId: string;
|
|
87
|
+
}, {
|
|
88
|
+
totalCents: number;
|
|
89
|
+
status: string;
|
|
90
|
+
}, never>, AgentTool<"refundOrder", {
|
|
91
|
+
orderId: string;
|
|
92
|
+
}, {
|
|
93
|
+
refundId: string;
|
|
94
|
+
}, never>]>], readonly [Skill<"refund-policy">], undefined>;
|
|
95
|
+
store: MemoryAgentStore;
|
|
96
|
+
instructions(req: HttpRequest<any, any>): string;
|
|
97
|
+
protected onMessage(message: AgentMessage, ctx: AgentHookContext): Promise<void>;
|
|
98
|
+
protected onAwaitingInput(pending: PendingToolCall[], ctx: AgentHookContext): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
declare class Api extends ApiRouter {
|
|
101
|
+
routes: {
|
|
102
|
+
"/support": import("./AgentController").AgentRoute<typeof SupportAgentController>;
|
|
103
|
+
"/support/threads": import("../http/ApiRouter").RouteHandler<"POST", unknown, {
|
|
104
|
+
threadId: string;
|
|
105
|
+
}, unknown>;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* What an app's generated `gemi.d.ts` writes for it, spelled out here because
|
|
110
|
+
* this file is not an app.
|
|
111
|
+
*
|
|
112
|
+
* `useChat` takes its path from `RPC` — the same interface `useQuery` reads —
|
|
113
|
+
* so without this the key `"/support"` does not exist and the hook is
|
|
114
|
+
* uncallable. Writing it by hand is what makes the rest of `Chat` below a real
|
|
115
|
+
* test of the claim that the agent's tool types ride along on the route: the
|
|
116
|
+
* `part.output.matches` and `call.input.amountCents` further down are inferred
|
|
117
|
+
* through `Agent` -> `AgentRoute` -> `CreateRPC` -> `useChat`, and if any link
|
|
118
|
+
* erased them this file would stop compiling.
|
|
119
|
+
*
|
|
120
|
+
* It does not escape: nothing imports this file, and `exports` does not publish
|
|
121
|
+
* a `gemi/ai/example` subpath, so the emitted declaration is unreachable from
|
|
122
|
+
* an application and `"/support"` never appears in a real app's `RPC`.
|
|
123
|
+
*/
|
|
124
|
+
declare module "../client/rpc" {
|
|
125
|
+
interface RPC extends CreateRPC<Api> {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export {};
|
|
129
|
+
//# sourceMappingURL=example.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../../ai/example.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmL7D,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2DAahB,CAAC;AAUH,cAAM,sBAAuB,SAAQ,eAAe,CAAC,OAAO,YAAY,CAAC;IACvE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gEAAgB;IACrB,KAAK,mBAAgB;IAMrB,YAAY,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC;cAIvB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,gBAAgB;cAItD,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,gBAAgB;CAGlF;AAED,cAAM,GAAI,SAAQ,SAAS;IACzB,MAAM;;;;;MAgBJ;CACH;AAED;;;;;;;;;;;;;;;GAeG;AACH,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,GAAI,SAAQ,SAAS,CAAC,GAAG,CAAC;KAAG;CACxC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gemi/ai` — the server half of the agent module.
|
|
3
|
+
*
|
|
4
|
+
* THIS ENTRY IS NOT IMPORTABLE FROM A BROWSER BUNDLE, AND THAT IS THE POINT.
|
|
5
|
+
* `useChat` lives at `gemi/ai/client` instead. Two entries rather than one
|
|
6
|
+
* because a barrel is a runtime dependency, not a menu: a component importing
|
|
7
|
+
* `useChat` from here would evaluate this file, and this file reaches
|
|
8
|
+
* `AgentProvider` (which holds the OpenAI and Azure clients and reads
|
|
9
|
+
* `OPENAI_API_KEY` / `AZURE_OPENAI_API_KEY` out of the environment), `Agent`
|
|
10
|
+
* (whose registry closes over every tool's `execute` — the app's server code,
|
|
11
|
+
* with its database handles and its secrets in scope) and, through `Agent`,
|
|
12
|
+
* `signing.ts`, which reads `process.env.SECRET` and is the one value whose
|
|
13
|
+
* disclosure lets a client forge its own approvals.
|
|
14
|
+
*
|
|
15
|
+
* Tree-shaking is not a defence to rely on here. It is a bundler optimization
|
|
16
|
+
* that a dev server, a test runner and a misconfigured build all skip, and the
|
|
17
|
+
* failure is silent — the code ships and nobody looks. The import graph is the
|
|
18
|
+
* defence: `ai/client/index.ts` cannot reach any of the above, because nothing
|
|
19
|
+
* it imports does.
|
|
20
|
+
*
|
|
21
|
+
* The split is free rather than a compromise: `useChat` already depended on
|
|
22
|
+
* none of this. It reads its transport off `RPC` and its state out of a
|
|
23
|
+
* reducer over the wire frames, so the server types it needs (`AgentMessage`,
|
|
24
|
+
* `PendingToolCall`) come from `ai/types.ts`, which imports nothing at all.
|
|
25
|
+
*/
|
|
26
|
+
export { s } from "./Schema";
|
|
27
|
+
export type { AnySchema, Infer, JSONSchema, OptionalSchema, Schema } from "./Schema";
|
|
28
|
+
export type { AgentContentPart, AgentError, AgentErrorCode, AgentMessage, AgentStreamEvent, AgentStreamFrame, ClientToolResult, ClientTurn, FilePart, FinishReason, NestedRun, OutputPart, PendingToolCall, ReasoningPart, TextPart, ToolCallPart, ToolResultPart, ToolShape, ToolShapes, Usage, } from "./types";
|
|
29
|
+
export { Agent, AgentTool, PendingEscalation, Skill, SKILLS_NAMESPACE, ToolNamespace, } from "./Agent";
|
|
30
|
+
export type { AgentRun, AgentRunResult, AgentStreamParams, AnyAgent, AnyAgentTool, CreateAgentParams, NestedRunResult, OutputOf, ReasoningEffort, RunAgentParams, SkillDefinition, ToolContext, ToolDefinition, ToolEntry, ToolExecute, ToolShapesOf, } from "./Agent";
|
|
31
|
+
export { AgentProvider, AzureOpenAIProvider, OpenAIProvider } from "./AgentProvider";
|
|
32
|
+
export type { AzureConfig, ProviderCapabilities, ProviderConfig, ProviderEvent, ProviderStream, ProviderStreamParams, ProviderToolNamespace, ProviderToolSpec, } from "./AgentProvider";
|
|
33
|
+
export { AgentController, defaultAgentStore, FrameCursorEvictedError, liveRuns, LiveRunNotFoundError, MemoryAgentStore, MemoryLiveRuns, } from "./AgentController";
|
|
34
|
+
export type { AgentHookContext, AgentMiddlewareConfig, AgentRoute, AgentRouteMethod, AgentRouteRPC, AgentStore, LiveRuns, } from "./AgentController";
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../ai/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC;AAC7B,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKrF,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,UAAU,EACV,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,GACN,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,KAAK,EACL,SAAS,EAIT,iBAAiB,EACjB,KAAK,EACL,gBAAgB,EAChB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,QAAQ,EACR,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,QAAQ,EACR,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,EACX,cAAc,EACd,SAAS,EACT,WAAW,EACX,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACrF,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,QAAQ,GACT,MAAM,mBAAmB,CAAC"}
|