agents 0.17.1 → 0.17.3
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/{agent-tools-CSnyGvJ2.d.ts → agent-tools-BeOheFBK.d.ts} +16 -2
- package/dist/{agent-tools-BXlsuX0d.js → agent-tools-y7zLfw4Q.js} +2 -2
- package/dist/agent-tools-y7zLfw4Q.js.map +1 -0
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.js +1 -1
- package/dist/chat/react.js +13 -2
- package/dist/chat/react.js.map +1 -1
- package/dist/react.js +1 -1
- package/package.json +4 -4
- package/dist/agent-tools-BXlsuX0d.js.map +0 -1
|
@@ -80,7 +80,21 @@ declare function applyAgentToolEvent<
|
|
|
80
80
|
interface AgentToolBroadcastHooks {
|
|
81
81
|
/** Live tailers per run; iterated to forward each progress chunk. */
|
|
82
82
|
forwarders: Map<string, Set<(chunk: AgentToolStoredChunk) => void>>;
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Per-run forwarded-chunk counter; advanced even with no tailer attached.
|
|
85
|
+
*
|
|
86
|
+
* This is deliberately a SEPARATE counter from the resumable stream's stored
|
|
87
|
+
* chunk_index — do NOT try to "simplify" it away by sequencing off the store
|
|
88
|
+
* position. Not every forwarded frame is durably stored: progress/milestone
|
|
89
|
+
* frames (`reportProgress`) ride the same `USE_CHAT_RESPONSE` wire type and
|
|
90
|
+
* are snooped + forwarded here, but persist out-of-band (progress snapshot /
|
|
91
|
+
* milestone rows), so they have no store position. Sourcing the sequence from
|
|
92
|
+
* the store would give them a colliding position and the tail's high-water
|
|
93
|
+
* dedupe (`emit`) would silently drop them, breaking live progress/milestone
|
|
94
|
+
* delivery to the parent. This counter sequences stored AND non-stored frames
|
|
95
|
+
* on one monotonic line; the tail realigns it to the stored high-water on each
|
|
96
|
+
* (re)attach so a replay→live handoff stays gap/duplicate-free.
|
|
97
|
+
*/
|
|
84
98
|
liveSequences: Map<string, number>;
|
|
85
99
|
/** Per-run last error body, captured for replay to a late-attaching tailer. */
|
|
86
100
|
lastErrors: Map<string, string>;
|
|
@@ -116,4 +130,4 @@ export {
|
|
|
116
130
|
interceptAgentToolBroadcast as s,
|
|
117
131
|
AgentToolBroadcastHooks as t
|
|
118
132
|
};
|
|
119
|
-
//# sourceMappingURL=agent-tools-
|
|
133
|
+
//# sourceMappingURL=agent-tools-BeOheFBK.d.ts.map
|
|
@@ -166,7 +166,7 @@ function applyToRun(prev, message) {
|
|
|
166
166
|
};
|
|
167
167
|
case "chunk": {
|
|
168
168
|
if (!seeded) return void 0;
|
|
169
|
-
const parts =
|
|
169
|
+
const parts = seeded.parts.map((part) => ({ ...part }));
|
|
170
170
|
let parsed;
|
|
171
171
|
try {
|
|
172
172
|
parsed = JSON.parse(event.body);
|
|
@@ -301,4 +301,4 @@ function interceptAgentToolBroadcast(msg, hooks) {
|
|
|
301
301
|
//#endregion
|
|
302
302
|
export { interceptAgentToolBroadcast as i, applyAgentToolEvent as n, createAgentToolEventState as r, AgentToolProgressEmitter as t };
|
|
303
303
|
|
|
304
|
-
//# sourceMappingURL=agent-tools-
|
|
304
|
+
//# sourceMappingURL=agent-tools-y7zLfw4Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools-y7zLfw4Q.js","names":[],"sources":["../src/chat/agent-tools.ts"],"sourcesContent":["import { applyChunkToParts, type MessagePart } from \"./message-builder\";\nimport {\n AGENT_TOOL_MILESTONE_PART,\n AGENT_TOOL_PROGRESS_PART\n} from \"../agent-tool-types\";\nimport type {\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolMilestone,\n AgentToolProgress,\n AgentToolProgressSnapshot,\n AgentToolRunPart,\n AgentToolRunState,\n AgentToolStoredChunk\n} from \"../agent-tool-types\";\n\n/**\n * Pull a reserved `data-agent-progress` chunk (emitted by a running sub-agent's\n * `reportProgress`) into a latest-wins snapshot. Returns `undefined` for any\n * other chunk so the caller keeps the prior snapshot.\n */\nfunction readAgentToolProgressChunk(\n chunk: unknown\n): AgentToolProgressSnapshot | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_PROGRESS_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: AgentToolProgress }).data ?? {};\n return {\n ...(typeof data.fraction === \"number\" ? { fraction: data.fraction } : {}),\n ...(typeof data.message === \"string\" ? { message: data.message } : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n ...(data.data !== undefined ? { data: data.data } : {}),\n at: Date.now()\n };\n}\n\n/**\n * Pull a reserved `data-agent-milestone` chunk into a durable milestone record,\n * or `undefined` for any other chunk. Milestones carry their own monotonic\n * `sequence` so the caller can dedupe replay-vs-live races.\n */\nfunction readAgentToolMilestoneChunk(\n chunk: unknown\n): AgentToolMilestone | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_MILESTONE_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: Partial<AgentToolMilestone> }).data ?? {};\n if (typeof data.name !== \"string\") return undefined;\n return {\n name: data.name,\n sequence: typeof data.sequence === \"number\" ? data.sequence : 0,\n at: typeof data.at === \"number\" ? data.at : Date.now(),\n ...(data.data !== undefined ? { data: data.data } : {})\n };\n}\n\n/**\n * Merge a milestone into a run's ordered milestone list, deduping on `sequence`\n * (idempotent across replay + live races) and keeping the list sorted.\n */\nfunction mergeMilestone(\n existing: AgentToolMilestone[] | undefined,\n milestone: AgentToolMilestone\n): AgentToolMilestone[] {\n // Returns `existing` unchanged (same reference) when the milestone is a dup,\n // so callers can identity-compare to detect a genuinely new milestone.\n if (existing?.some((m) => m.sequence === milestone.sequence)) return existing;\n const list = existing ? [...existing, milestone] : [milestone];\n list.sort((a, b) => a.sequence - b.sequence);\n return list;\n}\n\n/** Latest-wins coalescing window for `reportProgress` emits (per run). */\nconst AGENT_TOOL_PROGRESS_COALESCE_MS = 200;\n\nexport type AgentToolProgressEmitResult = \"emitted\" | \"coalesced\" | \"inactive\";\n\n/**\n * Host-injected seams the shared progress emitter needs. Keeps the per-host\n * `reportProgress` thin: Think / AIChatAgent supply how to resolve the active\n * agent-tool run, how to broadcast a chat-response frame, and how to persist the\n * latest snapshot on their own child-run table.\n */\nexport type AgentToolProgressEmitHooks = {\n /** The agent-tool run currently executing in this turn, or null. */\n resolveActiveRun: () => { runId: string; requestId: string } | null;\n /** Broadcast a chat-response frame (id = requestId) to clients/tailers. */\n broadcast: (requestId: string, chunkBody: string) => void;\n /** Persist the latest snapshot + signal timestamp on the child run row. */\n persistSnapshot: (\n runId: string,\n snapshot: {\n fraction?: number;\n message?: string;\n phase?: string;\n data?: unknown;\n },\n at: number\n ) => void;\n /**\n * Persist a durable milestone row, bump the run's signal timestamp, and return\n * the assigned monotonic per-run `sequence` (used to dedupe replay/live races).\n */\n persistMilestone: (\n runId: string,\n name: string,\n data: unknown,\n at: number\n ) => number;\n};\n\n/**\n * Shared implementation of `reportProgress` for chat hosts. Builds the reserved\n * transient `data-agent-progress` wire frame, coalesces bursts to a bounded\n * cadence (latest-wins; a `fraction >= 1` \"done\" frame always flushes), and\n * persists a latest snapshot. `data` rides the live frame but is only persisted\n * when the caller opts in via `{ persist: true }`.\n */\nexport class AgentToolProgressEmitter {\n private readonly _lastEmitAt = new Map<string, number>();\n\n constructor(private readonly hooks: AgentToolProgressEmitHooks) {}\n\n report(\n progress: AgentToolProgress,\n options?: { persist?: boolean }\n ): AgentToolProgressEmitResult {\n const active = this.hooks.resolveActiveRun();\n if (!active) return \"inactive\";\n const { runId, requestId } = active;\n const now = Date.now();\n\n // Durable milestone: never coalesced (each named boundary must land,\n // persist, and replay). Rides the stream as a PERSISTED data part.\n if (typeof progress.milestone === \"string\" && progress.milestone) {\n this._lastEmitAt.set(runId, now);\n const sequence = this.hooks.persistMilestone(\n runId,\n progress.milestone,\n progress.data,\n now\n );\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_MILESTONE_PART,\n data: {\n name: progress.milestone,\n sequence,\n at: now,\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n }\n })\n );\n return \"emitted\";\n }\n\n const last = this._lastEmitAt.get(runId) ?? 0;\n const isDone =\n typeof progress.fraction === \"number\" && progress.fraction >= 1;\n if (now - last < AGENT_TOOL_PROGRESS_COALESCE_MS && !isDone) {\n return \"coalesced\";\n }\n this._lastEmitAt.set(runId, now);\n\n const wire: AgentToolProgress = {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\" ? { phase: progress.phase } : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n };\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_PROGRESS_PART,\n transient: true,\n data: wire\n })\n );\n this.hooks.persistSnapshot(\n runId,\n {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(options?.persist && progress.data !== undefined\n ? { data: progress.data }\n : {})\n },\n now\n );\n return \"emitted\";\n }\n\n /** Drop coalescing state for a settled run (called on terminal). */\n forget(runId: string): void {\n this._lastEmitAt.delete(runId);\n }\n}\n\nfunction sortRuns<Part extends AgentToolRunPart>(\n runs: AgentToolRunState<Part>[]\n): AgentToolRunState<Part>[] {\n return [...runs].sort((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return a.runId.localeCompare(b.runId);\n });\n}\n\nfunction rebuildIndexes<Part extends AgentToolRunPart>(\n runsById: Record<string, AgentToolRunState<Part>>\n): Pick<AgentToolEventState<Part>, \"runsByToolCallId\" | \"unboundRuns\"> {\n const grouped: Record<string, AgentToolRunState<Part>[]> = {};\n const unboundRuns: AgentToolRunState<Part>[] = [];\n for (const run of Object.values(runsById)) {\n if (run.parentToolCallId) {\n grouped[run.parentToolCallId] = grouped[run.parentToolCallId] ?? [];\n grouped[run.parentToolCallId].push(run);\n } else {\n unboundRuns.push(run);\n }\n }\n for (const [toolCallId, runs] of Object.entries(grouped)) {\n grouped[toolCallId] = sortRuns(runs);\n }\n return { runsByToolCallId: grouped, unboundRuns: sortRuns(unboundRuns) };\n}\n\nfunction emptyRun<Part extends AgentToolRunPart>(\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const { event } = message;\n if (event.kind === \"started\") {\n return {\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n }\n return undefined;\n}\n\nfunction applyToRun<Part extends AgentToolRunPart>(\n prev: AgentToolRunState<Part> | undefined,\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const seeded = prev ?? emptyRun(message);\n const { event } = message;\n\n switch (event.kind) {\n case \"started\":\n if (\n seeded?.status === \"completed\" ||\n seeded?.status === \"error\" ||\n seeded?.status === \"aborted\" ||\n seeded?.status === \"interrupted\"\n ) {\n return seeded;\n }\n return {\n ...seeded,\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: seeded?.parts ?? [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n case \"chunk\": {\n if (!seeded) return undefined;\n // `applyChunkToParts` mutates part objects in place (e.g.\n // `lastTextPart.text += delta`). A shallow array copy (`[...seeded.parts]`)\n // keeps the *element* references shared with the previous state, so those\n // in-place mutations leak back into `prev`. React double-invokes setState\n // updaters in StrictMode / dev hydration, replaying each chunk against the\n // same (already-mutated) `prev` and doubling the text (#1835). Clone each\n // part so the reducer stays pure — every mutation here is to a top-level\n // field, so a per-part shallow copy is sufficient.\n const parts = seeded.parts.map((part) => ({ ...part }) as Part);\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.body);\n applyChunkToParts(\n parts as MessagePart[],\n parsed as Parameters<typeof applyChunkToParts>[1]\n );\n } catch {\n return seeded;\n }\n // Project a reserved `data-agent-progress` part onto the run's latest\n // progress snapshot so a tray can render a bar/phase without drilling in.\n // The part is transient (not persisted into `parts`), so it is read here\n // off the raw chunk rather than from the reduced parts array.\n const progress = readAgentToolProgressChunk(parsed);\n if (progress) {\n return { ...seeded, parts, progress };\n }\n // Durable milestones land as a persisted `data-agent-milestone` part:\n // append (deduped by sequence) to the run's milestone list, and reflect\n // any progress fields the milestone carried onto the latest snapshot.\n const milestone = readAgentToolMilestoneChunk(parsed);\n if (milestone) {\n const milestones = mergeMilestone(seeded.milestones, milestone);\n // Only advance the snapshot for a genuinely new, not-older milestone, so\n // a late replay of an earlier milestone never rolls `progress` backward.\n const isNew = milestones !== seeded.milestones;\n const notOlder =\n seeded.progress === undefined || milestone.at >= seeded.progress.at;\n if (!isNew || !notOlder) {\n return { ...seeded, parts, milestones };\n }\n const data = (parsed as { data?: AgentToolProgress }).data ?? {};\n const snapshot: AgentToolProgressSnapshot = {\n ...(typeof data.fraction === \"number\"\n ? { fraction: data.fraction }\n : {}),\n ...(typeof data.message === \"string\"\n ? { message: data.message }\n : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n milestone: milestone.name,\n at: milestone.at\n };\n return { ...seeded, parts, progress: snapshot, milestones };\n }\n return { ...seeded, parts };\n }\n case \"finished\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"completed\",\n summary: event.summary,\n error: undefined\n };\n case \"error\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"error\", error: event.error };\n case \"aborted\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"aborted\", error: event.reason };\n case \"interrupted\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"interrupted\",\n error: event.error,\n reason: event.reason,\n childStillRunning: event.childStillRunning\n };\n }\n}\n\nexport function createAgentToolEventState<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(): AgentToolEventState<Part> {\n return {\n runsById: {},\n runsByToolCallId: {},\n unboundRuns: []\n };\n}\n\nexport function applyAgentToolEvent<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(\n state: AgentToolEventState<Part>,\n message: AgentToolEventMessage\n): AgentToolEventState<Part> {\n if (message.type !== \"agent-tool-event\") return state;\n const runId = message.event.runId;\n const nextRun = applyToRun(state.runsById[runId], message);\n if (!nextRun) return state;\n\n const runsById = { ...state.runsById, [runId]: nextRun };\n return { runsById, ...rebuildIndexes(runsById) };\n}\n\nexport type {\n AgentToolEvent,\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolRunPart,\n AgentToolRunState\n} from \"../agent-tool-types\";\n\n/**\n * @internal Host substrate the {@link interceptAgentToolBroadcast} snoop reads,\n * abstracting the divergent per-host run-lookup and response-frame constant.\n */\nexport interface AgentToolBroadcastHooks {\n /** Live tailers per run; iterated to forward each progress chunk. */\n forwarders: Map<string, Set<(chunk: AgentToolStoredChunk) => void>>;\n /**\n * Per-run forwarded-chunk counter; advanced even with no tailer attached.\n *\n * This is deliberately a SEPARATE counter from the resumable stream's stored\n * chunk_index — do NOT try to \"simplify\" it away by sequencing off the store\n * position. Not every forwarded frame is durably stored: progress/milestone\n * frames (`reportProgress`) ride the same `USE_CHAT_RESPONSE` wire type and\n * are snooped + forwarded here, but persist out-of-band (progress snapshot /\n * milestone rows), so they have no store position. Sourcing the sequence from\n * the store would give them a colliding position and the tail's high-water\n * dedupe (`emit`) would silently drop them, breaking live progress/milestone\n * delivery to the parent. This counter sequences stored AND non-stored frames\n * on one monotonic line; the tail realigns it to the stored high-water on each\n * (re)attach so a replay→live handoff stays gap/duplicate-free.\n */\n liveSequences: Map<string, number>;\n /** Per-run last error body, captured for replay to a late-attaching tailer. */\n lastErrors: Map<string, string>;\n /** The host's use-chat-response wire type (`CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE`). */\n responseType: string;\n /** Resolve the agent-tool run that owns a turn request id, or null. */\n runForRequest: (requestId: string) => string | null;\n}\n\n/**\n * Snoop a host's outgoing chat frames while any agent-tool run is in flight and\n * forward the owning run's streamed body to its live tailers (or capture its\n * error), without altering the frame — the caller still broadcasts it (#1575).\n *\n * Shared verbatim by `@cloudflare/ai-chat` and `@cloudflare/think`; the only\n * per-host variance (the response-frame type constant and the run-lookup, whose\n * SQL differs) is supplied via {@link AgentToolBroadcastHooks}. Inspection runs\n * for a run's whole lifecycle (live sequences exist even with no tailer), so\n * error capture never depends on tailer timing. A frame belongs to a run iff it\n * carries that run's turn request id, so concurrent runs can't cross-contaminate\n * each other's progress or error state.\n */\nexport function interceptAgentToolBroadcast(\n msg: string | ArrayBuffer | ArrayBufferView,\n hooks: AgentToolBroadcastHooks\n): void {\n if (\n (hooks.forwarders.size > 0 || hooks.liveSequences.size > 0) &&\n typeof msg === \"string\"\n ) {\n try {\n const parsed = JSON.parse(msg) as {\n type?: unknown;\n body?: unknown;\n error?: unknown;\n id?: unknown;\n };\n if (parsed.type === hooks.responseType && typeof parsed.id === \"string\") {\n const runId = hooks.runForRequest(parsed.id);\n if (runId !== null) {\n if (parsed.error === true && typeof parsed.body === \"string\") {\n hooks.lastErrors.set(runId, parsed.body);\n } else if (\n typeof parsed.body === \"string\" &&\n parsed.body.length > 0\n ) {\n // Advance the live sequence even with no tailer attached so a tailer\n // registering mid-run resumes at the right offset.\n const sequence = hooks.liveSequences.get(runId) ?? 0;\n hooks.liveSequences.set(runId, sequence + 1);\n const chunk: AgentToolStoredChunk = { sequence, body: parsed.body };\n const forwarders = hooks.forwarders.get(runId);\n if (forwarders) {\n for (const forward of forwarders) forward(chunk);\n }\n }\n }\n }\n } catch {\n // Non-chat frames pass through unchanged.\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/chat/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
r as AgentToolProgressEmitResult,
|
|
19
19
|
s as interceptAgentToolBroadcast,
|
|
20
20
|
t as AgentToolBroadcastHooks
|
|
21
|
-
} from "../agent-tools-
|
|
21
|
+
} from "../agent-tools-BeOheFBK.js";
|
|
22
22
|
import { JSONSchema7, UIMessage } from "ai";
|
|
23
23
|
import { Connection } from "agents";
|
|
24
24
|
|
package/dist/chat/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as normalizeToolInput, n as getPartialStreamText, r as isReplayChunk, t as applyChunkToParts } from "../message-builder-BymO4N_D.js";
|
|
2
|
-
import { i as interceptAgentToolBroadcast, n as applyAgentToolEvent, r as createAgentToolEventState, t as AgentToolProgressEmitter } from "../agent-tools-
|
|
2
|
+
import { i as interceptAgentToolBroadcast, n as applyAgentToolEvent, r as createAgentToolEventState, t as AgentToolProgressEmitter } from "../agent-tools-y7zLfw4Q.js";
|
|
3
3
|
import { t as truncateToolOutput } from "../tool-output-truncation-CNnnGZQ3.js";
|
|
4
4
|
import { n as transition, r as StreamAccumulator, t as MessageType } from "../wire-types-nflOzNuU.js";
|
|
5
5
|
import { jsonSchema, tool } from "ai";
|
package/dist/chat/react.js
CHANGED
|
@@ -780,6 +780,8 @@ function useAgentChat(options) {
|
|
|
780
780
|
const resumeStreamRef = useRef(resumeStream);
|
|
781
781
|
resumeStreamRef.current = resumeStream;
|
|
782
782
|
const hasConnectedOnceRef = useRef(false);
|
|
783
|
+
const resumeInFlightRef = useRef(false);
|
|
784
|
+
const resumeGenerationRef = useRef(0);
|
|
783
785
|
const resumingToolContinuationRef = useRef(false);
|
|
784
786
|
const pendingToolContinuationRef = useRef(false);
|
|
785
787
|
const observedToolContinuationRequestIdRef = useRef(null);
|
|
@@ -1323,8 +1325,15 @@ function useAgentChat(options) {
|
|
|
1323
1325
|
hasConnectedOnceRef.current = true;
|
|
1324
1326
|
return;
|
|
1325
1327
|
}
|
|
1326
|
-
if (!resume || statusRef.current !== "ready" || resumingToolContinuationRef.current || customTransport.isAwaitingResume()) return;
|
|
1327
|
-
|
|
1328
|
+
if (!resume || statusRef.current !== "ready" || resumingToolContinuationRef.current || resumeInFlightRef.current || customTransport.isAwaitingResume()) return;
|
|
1329
|
+
const resumeStreamFn = resumeStreamRef.current;
|
|
1330
|
+
if (!resumeStreamFn) return;
|
|
1331
|
+
resumeInFlightRef.current = true;
|
|
1332
|
+
const myGeneration = resumeGenerationRef.current;
|
|
1333
|
+
Promise.resolve(resumeStreamFn()).catch(() => {}).finally(() => {
|
|
1334
|
+
if (resumeGenerationRef.current !== myGeneration) return;
|
|
1335
|
+
resumeInFlightRef.current = false;
|
|
1336
|
+
});
|
|
1328
1337
|
}
|
|
1329
1338
|
agent.addEventListener("message", onAgentMessage);
|
|
1330
1339
|
agent.addEventListener("close", onAgentClose);
|
|
@@ -1339,6 +1348,8 @@ function useAgentChat(options) {
|
|
|
1339
1348
|
setIsRecovering(false);
|
|
1340
1349
|
protectedStreamingAssistantRef.current = null;
|
|
1341
1350
|
localResponseIds.clear();
|
|
1351
|
+
resumeGenerationRef.current++;
|
|
1352
|
+
resumeInFlightRef.current = false;
|
|
1342
1353
|
};
|
|
1343
1354
|
}, [
|
|
1344
1355
|
agent,
|
package/dist/chat/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","names":["broadcastTransition"],"sources":["../../src/chat/ws-chat-transport.ts","../../src/chat/react.tsx"],"sourcesContent":["/**\n * WebSocket-based ChatTransport for useAgentChat.\n *\n * Replaces the aiFetch + DefaultChatTransport indirection with a direct\n * WebSocket implementation that speaks the CF_AGENT protocol natively.\n *\n * Data flow (old): WS → aiFetch fake Response → DefaultChatTransport → useChat\n * Data flow (new): WS → WebSocketChatTransport → useChat\n */\n\nimport type { ChatTransport, UIMessage, UIMessageChunk } from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { MessageType, type OutgoingMessage } from \"./wire-types\";\n\n/**\n * Short safety-net timeout for a resume probe when the server has said nothing.\n * Under normal operation the server answers a `STREAM_RESUME_REQUEST` with\n * `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.\n */\nconst RESUME_PROBE_TIMEOUT_MS = 5000;\n\n/**\n * Extended backstop applied once the server says a turn is pending\n * (`STREAM_PENDING`, #1784). The pre-stream window (queueing, MCP setup,\n * debounce, model latency) can exceed the short probe timeout, and the server\n * guarantees a follow-up `STREAM_RESUMING` or `STREAM_RESUME_NONE` — so we wait\n * much longer (refreshed on every keep-waiting frame) but still cap it so a\n * dropped follow-up degrades to a null resolve instead of hanging forever.\n */\nconst RESUME_PENDING_TIMEOUT_MS = 60000;\n\n/**\n * Agent-like interface for sending/receiving WebSocket messages.\n * Matches the shape returned by useAgent from agents/react.\n */\nexport interface AgentConnection {\n send: (data: string) => void;\n addEventListener: (\n type: string,\n listener: (event: MessageEvent) => void,\n options?: { signal?: AbortSignal }\n ) => void;\n removeEventListener: (\n type: string,\n listener: (event: MessageEvent) => void\n ) => void;\n}\n\nexport type WebSocketChatTransportOptions<\n ChatMessage extends UIMessage = UIMessage\n> = {\n /** The agent connection from useAgent */\n agent: AgentConnection;\n /**\n * Callback to prepare the request body before sending.\n * Can add custom headers, body fields, or credentials.\n */\n prepareBody?: (options: {\n messages: ChatMessage[];\n trigger: \"submit-message\" | \"regenerate-message\";\n messageId?: string;\n }) => Promise<Record<string, unknown>> | Record<string, unknown>;\n /**\n * Optional set to track active request IDs.\n * IDs are added when a request starts and removed when it completes.\n * Used by the onAgentMessage handler to skip messages already handled by the transport.\n */\n activeRequestIds?: Set<string>;\n /**\n * Whether generic client-side abort/cancel lifecycle should cancel the\n * server turn. Explicit cancellation via cancelActiveServerTurn() always\n * sends CF_AGENT_CHAT_REQUEST_CANCEL.\n * @default false\n */\n cancelOnClientAbort?: boolean;\n};\n\n/**\n * ChatTransport that sends messages over WebSocket and returns a\n * ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.\n * No fake fetch, no Response reconstruction, no double SSE parsing.\n */\nexport class WebSocketChatTransport<\n ChatMessage extends UIMessage = UIMessage\n> implements ChatTransport<ChatMessage> {\n agent: AgentConnection;\n private prepareBody?: WebSocketChatTransportOptions<ChatMessage>[\"prepareBody\"];\n private activeRequestIds?: Set<string>;\n private cancelOnClientAbort: boolean;\n\n // Pending resume resolver — set by reconnectToStream, called by\n // handleStreamResuming when onAgentMessage sees CF_AGENT_STREAM_RESUMING.\n private _resumeResolver: ((data: { id: string }) => void) | null = null;\n // Pending \"no stream\" resolver — called by handleStreamResumeNone\n // when onAgentMessage sees CF_AGENT_STREAM_RESUME_NONE.\n private _resumeNoneResolver: (() => void) | null = null;\n // Keep-waiting hook (#1784) — set by whichever resume path is currently\n // awaiting, called by handleStreamPending when onAgentMessage sees\n // CF_AGENT_STREAM_PENDING. Extends the path's probe timeout so a slow\n // pre-stream window (queue / MCP / model latency) does not resolve early.\n private _onStreamPending: (() => void) | null = null;\n // Set when a client-side tool result/approval is expected to trigger\n // a new continuation stream. In this mode reconnectToStream() returns\n // a deferred ReadableStream immediately so AI SDK status can transition\n // to \"submitted\" before the server starts streaming.\n private _expectToolContinuation = false;\n private _abortToolContinuation: (() => boolean) | null = null;\n private _activeServerTurnId: string | null = null;\n private _cancelAttachedStream: (() => boolean) | null = null;\n\n constructor(options: WebSocketChatTransportOptions<ChatMessage>) {\n this.agent = options.agent;\n this.prepareBody = options.prepareBody;\n this.activeRequestIds = options.activeRequestIds;\n this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;\n }\n\n setCancelOnClientAbort(cancelOnClientAbort: boolean) {\n this.cancelOnClientAbort = cancelOnClientAbort;\n }\n\n /**\n * Explicitly cancel the active server turn, if any.\n * This is separate from generic client-side abort/cancel lifecycle so\n * clients can detach locally without stopping server work.\n */\n cancelActiveServerTurn(): boolean {\n const requestId = this._activeServerTurnId;\n let cancelledRequest = false;\n\n if (requestId) {\n this.sendCancelFrame(requestId);\n this._cancelAttachedStream?.();\n this.clearActiveServerTurn(requestId);\n cancelledRequest = true;\n }\n\n const cancelledToolContinuation = this.abortActiveToolContinuation();\n return cancelledRequest || cancelledToolContinuation;\n }\n\n private sendCancelFrame(requestId: string) {\n try {\n this.agent.send(\n JSON.stringify({\n id: requestId,\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL\n })\n );\n } catch {\n // Ignore failures (e.g. agent already disconnected)\n }\n }\n\n private setActiveServerTurn(\n requestId: string,\n cancelAttachedStream: (() => boolean) | null\n ) {\n this._activeServerTurnId = requestId;\n this._cancelAttachedStream = cancelAttachedStream;\n }\n\n private clearActiveServerTurn(requestId: string) {\n if (this._activeServerTurnId === requestId) {\n this._activeServerTurnId = null;\n this._cancelAttachedStream = null;\n }\n }\n\n /**\n * Mark that the next reconnectToStream() call should attach to a\n * server-initiated tool continuation rather than a page-load resume.\n */\n expectToolContinuation() {\n this._expectToolContinuation = true;\n }\n\n /**\n * Abort the active client-side tool continuation stream, if one is attached\n * to a server request id.\n */\n abortActiveToolContinuation(): boolean {\n return this._abortToolContinuation?.() ?? false;\n }\n\n /**\n * True when the transport is waiting for a resume handshake.\n */\n isAwaitingResume(): boolean {\n return this._resumeResolver !== null || this._resumeNoneResolver !== null;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.\n * If reconnectToStream is waiting, this handles the resume handshake\n * (ACK + stream creation) and returns true. Otherwise returns false\n * so the caller can use its own fallback path.\n */\n handleStreamResuming(data: { id: string }): boolean {\n if (!this._resumeResolver) return false;\n this._resumeResolver(data);\n return true;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.\n * If reconnectToStream is waiting, resolves the promise with null\n * immediately (no 5-second timeout). Returns true if handled.\n */\n handleStreamResumeNone(): boolean {\n if (!this._resumeNoneResolver) return false;\n this._resumeNoneResolver();\n return true;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):\n * the server accepted a turn but its stream has not started yet. If a resume\n * path is awaiting, extend its probe timeout (so it keeps waiting for the\n * eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null\n * after the short window). Returns true if a waiting path consumed it.\n */\n handleStreamPending(): boolean {\n if (!this._onStreamPending) return false;\n this._onStreamPending();\n return true;\n }\n\n /**\n * Called by the hook's shared message handler when a server turn finishes\n * outside the currently attached transport stream, such as after local-only\n * client cleanup.\n */\n handleServerTurnCompleted(requestId: string) {\n this.clearActiveServerTurn(requestId);\n }\n\n /**\n * Register a server turn that is being rendered outside a transport-owned\n * stream, such as the hook's fallback cross-tab/resume observer path.\n */\n observeServerTurn(requestId: string) {\n this.setActiveServerTurn(requestId, null);\n }\n\n async sendMessages(options: {\n chatId: string;\n messages: ChatMessage[];\n abortSignal: AbortSignal | undefined;\n trigger: \"submit-message\" | \"regenerate-message\";\n messageId?: string;\n body?: object;\n headers?: Record<string, string> | Headers;\n metadata?: unknown;\n }): Promise<ReadableStream<UIMessageChunk>> {\n const requestId = nanoid(8);\n const abortController = new AbortController();\n let completed = false;\n let requestSent = false;\n\n // Build the request body\n let extraBody: Record<string, unknown> = {};\n if (this.prepareBody) {\n extraBody = await this.prepareBody({\n messages: options.messages,\n trigger: options.trigger,\n messageId: options.messageId\n });\n }\n if (options.body) {\n extraBody = {\n ...extraBody,\n ...(options.body as Record<string, unknown>)\n };\n }\n\n const bodyPayload = JSON.stringify({\n messages: options.messages,\n trigger: options.trigger,\n ...extraBody\n });\n\n // Track this request so the onAgentMessage handler skips it\n this.activeRequestIds?.add(requestId);\n\n // Create a ReadableStream<UIMessageChunk> that emits parsed chunks\n // as they arrive over the WebSocket\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n\n // Single cleanup helper — every terminal path (done, error, abort)\n // goes through here exactly once.\n // keepId: when true, do NOT remove requestId from activeIds. Used by\n // explicit cancellation so onAgentMessage skips in-flight chunks\n // and the server's final done:true signal until cleanup happens there.\n const finish = (\n action: () => void,\n keepId = false,\n clearServerTurn = true\n ) => {\n if (completed) return;\n completed = true;\n if (clearServerTurn) {\n this.clearActiveServerTurn(requestId);\n }\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (!keepId) {\n activeIds?.delete(requestId);\n }\n abortController.abort();\n };\n\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n\n const cancelActiveRequest = () => {\n if (completed) return false;\n finish(() => streamController.error(abortError), true);\n return true;\n };\n this.setActiveServerTurn(requestId, cancelActiveRequest);\n\n // Abort handler: terminate the local stream. By default, generic AI SDK\n // abort/cancel lifecycle is local-only so durable server turns can continue\n // and be resumed. Use cancelActiveServerTurn() for explicit user/app\n // cancellation, or cancelOnClientAbort for request-lifetime semantics.\n const onAbort = () => {\n if (completed) return;\n if (this.cancelOnClientAbort) {\n if (requestSent) {\n this.sendCancelFrame(requestId);\n }\n finish(() => streamController.error(abortError), requestSent);\n } else {\n finish(() => streamController.error(abortError), false, !requestSent);\n }\n };\n\n // streamController is assigned synchronously by start(), so it is\n // always available by the time onAbort or onMessage can fire.\n let streamController!: ReadableStreamDefaultController<UIMessageChunk>;\n\n const stream = new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<ChatMessage>;\n\n if (data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE) return;\n if (data.id !== requestId) return;\n\n if (data.error) {\n finish(() =>\n controller.error(new Error(data.body || \"Stream error\"))\n );\n return;\n }\n\n // Parse the body as UIMessageChunk and enqueue\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close());\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n finish(() => controller.close(), false, false);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: abortController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: abortController.signal\n });\n },\n cancel() {\n onAbort();\n }\n });\n\n // Handle abort from the caller\n if (options.abortSignal) {\n options.abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n if (options.abortSignal.aborted) onAbort();\n }\n\n if (completed) {\n return stream;\n }\n\n // Send the request over WebSocket\n requestSent = true;\n agent.send(\n JSON.stringify({\n id: requestId,\n init: {\n method: \"POST\",\n body: bodyPayload\n },\n type: MessageType.CF_AGENT_USE_CHAT_REQUEST\n })\n );\n\n return stream;\n }\n\n async reconnectToStream(_options: {\n chatId: string;\n }): Promise<ReadableStream<UIMessageChunk> | null> {\n if (this._expectToolContinuation) {\n this._expectToolContinuation = false;\n return this._createToolContinuationStream();\n }\n\n // Detect whether the server has an active stream for this chat.\n // Instead of registering our own addEventListener listener (which\n // races with onAgentMessage), we set _resumeResolver so that\n // onAgentMessage can call handleStreamResuming() synchronously\n // when it sees CF_AGENT_STREAM_RESUMING — eliminating the race.\n const activeIds = this.activeRequestIds;\n\n return new Promise<ReadableStream<UIMessageChunk> | null>((resolve) => {\n let resolved = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const done = (value: ReadableStream<UIMessageChunk> | null) => {\n if (resolved) return;\n resolved = true;\n this._resumeResolver = null;\n this._resumeNoneResolver = null;\n this._onStreamPending = null;\n if (timeout) clearTimeout(timeout);\n resolve(value);\n };\n\n // Keep-waiting (#1784): the server says a turn is accepted but its stream\n // hasn't started. Extend the short probe timeout to the longer backstop so\n // we wait for the eventual STREAM_RESUMING (or STREAM_RESUME_NONE) instead\n // of resolving null mid pre-stream window. Refreshed on every frame.\n this._onStreamPending = () => {\n if (resolved) return;\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(() => done(null), RESUME_PENDING_TIMEOUT_MS);\n };\n\n // Set the \"no stream\" resolver that handleStreamResumeNone() will call.\n // When onAgentMessage sees CF_AGENT_STREAM_RESUME_NONE, it calls\n // handleStreamResumeNone() which resolves immediately with null.\n this._resumeNoneResolver = () => done(null);\n\n // Set the resolver that handleStreamResuming() will call.\n // When onAgentMessage sees CF_AGENT_STREAM_RESUMING, it calls\n // handleStreamResuming() which invokes this callback.\n this._resumeResolver = (data: { id: string }) => {\n const requestId = data.id;\n\n // Track this request so onAgentMessage skips subsequent chunks\n activeIds?.add(requestId);\n\n const stream = this._createResumeStream(requestId);\n\n // Send ACK to server via the latest agent (the socket may\n // have been replaced since reconnectToStream was called).\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: requestId\n })\n );\n\n // Return a ReadableStream fed by the replayed + live chunks\n done(stream);\n };\n\n // Send the resume request. PartySocket queues sends when\n // the socket isn't open yet and flushes on connect, so\n // this works regardless of current readyState.\n try {\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST\n })\n );\n } catch {\n // WebSocket may already be closed\n }\n\n // Safety-net timeout: if the WebSocket never connects or the\n // server is unreachable, resolve null. Under normal operation\n // the server responds with STREAM_RESUMING, STREAM_RESUME_NONE, or\n // STREAM_PENDING (which extends this) well before this fires.\n timeout = setTimeout(() => done(null), RESUME_PROBE_TIMEOUT_MS);\n });\n }\n\n /**\n * Creates a deferred ReadableStream for client-side tool continuations.\n * The stream is returned immediately so AI SDK status becomes \"submitted\"\n * right after addToolOutput()/addToolApprovalResponse(), then it waits for\n * the server to announce the continuation via STREAM_RESUMING.\n */\n private _createToolContinuationStream(): ReadableStream<UIMessageChunk> {\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n const streamController = new AbortController();\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n let completed = false;\n let requestId: string | null = null;\n let readerController!: ReadableStreamDefaultController<UIMessageChunk>;\n let onResumeRef: ((data: { id: string }) => void) | null = null;\n let onResumeNoneRef: (() => void) | null = null;\n\n const clearHandshakeResolvers = (\n resumeResolver?: ((data: { id: string }) => void) | null,\n resumeNoneResolver?: (() => void) | null\n ) => {\n if (resumeResolver === undefined && resumeNoneResolver === undefined) {\n this._resumeResolver = null;\n this._resumeNoneResolver = null;\n return;\n }\n\n if (resumeResolver && this._resumeResolver === resumeResolver) {\n this._resumeResolver = null;\n }\n if (\n resumeNoneResolver &&\n this._resumeNoneResolver === resumeNoneResolver\n ) {\n this._resumeNoneResolver = null;\n }\n };\n\n const finish = (\n action: () => void,\n resumeResolver?: ((data: { id: string }) => void) | null,\n resumeNoneResolver?: (() => void) | null,\n keepRequestId = false\n ) => {\n if (completed) return;\n completed = true;\n this._abortToolContinuation = null;\n this._onStreamPending = null;\n clearHandshakeResolvers(resumeResolver, resumeNoneResolver);\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (requestId && !keepRequestId) {\n activeIds?.delete(requestId);\n }\n streamController.abort();\n };\n\n const transport = this;\n\n this._abortToolContinuation = () => {\n if (completed) {\n return false;\n }\n\n if (requestId === null) {\n // Handshake hasn't completed yet — close the stream and clear\n // resolvers so the subsequent onResume/handleStreamResuming\n // becomes a no-op.\n finish(\n () => readerController.error(abortError),\n onResumeRef,\n onResumeNoneRef\n );\n return true;\n }\n\n try {\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL,\n id: requestId\n })\n );\n } catch {\n // Ignore failures (e.g. agent already disconnected)\n }\n\n // keepRequestId=true: keep the ID in activeIds so onAgentMessage\n // skips in-flight chunks until the server's done:true cleans it up\n // (same pattern as sendMessages onAbort).\n finish(\n () => readerController.error(abortError),\n onResumeRef,\n onResumeNoneRef,\n true\n );\n return true;\n };\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n readerController = controller;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const onResumeNone = () => {\n if (timeout) clearTimeout(timeout);\n finish(() => controller.close(), onResume, onResumeNone);\n };\n\n const onResume = (data: { id: string }) => {\n if (requestId) return;\n\n requestId = data.id;\n activeIds?.add(requestId);\n clearHandshakeResolvers(onResume, onResumeNone);\n transport._onStreamPending = null;\n if (timeout) clearTimeout(timeout);\n\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: requestId\n })\n );\n };\n\n onResumeRef = onResume;\n onResumeNoneRef = onResumeNone;\n\n timeout = setTimeout(\n () => finish(() => controller.close(), onResume, onResumeNone),\n RESUME_PROBE_TIMEOUT_MS\n );\n\n // Keep-waiting (#1784): extend the probe window when the server reports\n // the continuation turn is accepted but its stream hasn't started.\n transport._onStreamPending = () => {\n if (completed) return;\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(\n () => finish(() => controller.close(), onResume, onResumeNone),\n RESUME_PENDING_TIMEOUT_MS\n );\n };\n\n transport._resumeResolver = onResume;\n transport._resumeNoneResolver = onResumeNone;\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<UIMessage>;\n\n if (\n data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE ||\n requestId == null ||\n data.id !== requestId\n ) {\n return;\n }\n\n if (data.error) {\n finish(\n () => controller.error(new Error(data.body || \"Stream error\")),\n onResume,\n onResumeNone\n );\n return;\n }\n\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close(), onResume, onResumeNone);\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n if (timeout) clearTimeout(timeout);\n finish(() => controller.close(), onResume, onResumeNone);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: streamController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: streamController.signal\n });\n\n try {\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST\n })\n );\n } catch {\n finish(() => controller.close());\n }\n },\n cancel() {\n if (requestId && transport.cancelOnClientAbort) {\n transport.sendCancelFrame(requestId);\n finish(() => {}, onResumeRef, onResumeNoneRef, true);\n } else {\n finish(() => {}, onResumeRef, onResumeNoneRef);\n }\n }\n });\n }\n\n /**\n * Creates a ReadableStream that receives resumed stream chunks\n * and forwards them to useChat as UIMessageChunk objects.\n */\n private _createResumeStream(\n requestId: string\n ): ReadableStream<UIMessageChunk> {\n // Read agent at resolve time (not when reconnectToStream was called)\n // so chunk listener attaches to the latest socket after _pk changes.\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n const chunkController = new AbortController();\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n let completed = false;\n\n const finish = (\n action: () => void,\n keepId = false,\n clearServerTurn = true\n ) => {\n if (completed) return;\n completed = true;\n if (clearServerTurn) {\n this.clearActiveServerTurn(requestId);\n }\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (!keepId) {\n activeIds?.delete(requestId);\n }\n chunkController.abort();\n };\n\n const cancelActiveRequest = () => {\n if (completed) return false;\n finish(() => streamController.error(abortError), true);\n return true;\n };\n this.setActiveServerTurn(requestId, cancelActiveRequest);\n\n let streamController!: ReadableStreamDefaultController<UIMessageChunk>;\n const transport = this;\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<UIMessage>;\n\n if (data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE) return;\n if (data.id !== requestId) return;\n\n if (data.error) {\n finish(() =>\n controller.error(new Error(data.body || \"Stream error\"))\n );\n return;\n }\n\n // Parse and enqueue the chunk\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close());\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n finish(() => controller.close(), false, false);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: chunkController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: chunkController.signal\n });\n },\n cancel() {\n if (transport.cancelOnClientAbort) {\n transport.sendCancelFrame(requestId);\n finish(() => {}, true);\n } else {\n finish(() => {}, false, false);\n }\n }\n });\n }\n}\n","import { useChat, type UseChatOptions } from \"@ai-sdk/react\";\nimport { getToolName, isToolUIPart } from \"ai\";\nimport type {\n ChatInit,\n JSONSchema7,\n Tool,\n UIMessage as Message,\n UIMessage\n} from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { use, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { OutgoingMessage } from \"./wire-types\";\nimport { MessageType } from \"./wire-types\";\nimport {\n transition as broadcastTransition,\n type BroadcastStreamState\n} from \"./broadcast-state\";\nimport {\n WebSocketChatTransport,\n type AgentConnection\n} from \"./ws-chat-transport\";\n\nexport { WebSocketChatTransport } from \"./ws-chat-transport\";\nexport type {\n AgentConnection,\n WebSocketChatTransportOptions\n} from \"./ws-chat-transport\";\n\n/**\n * One-shot deprecation warnings (warns once per key per session).\n */\nconst _deprecationWarnings = new Set<string>();\nfunction warnDeprecated(id: string, message: string) {\n if (!_deprecationWarnings.has(id)) {\n _deprecationWarnings.add(id);\n console.warn(`[agents/chat] Deprecated: ${message}`);\n }\n}\n\ntype AgentConnectionErrorLike = Error & {\n code: number;\n reason: string;\n wasClean: boolean;\n};\n\n// ── DEPRECATED TYPES AND FUNCTIONS ──────────────────────────────────\n// Everything in this section is deprecated and will be removed in the\n// next major version. Use server-side tools with tool() from \"ai\" and\n// the onToolCall callback in useAgentChat instead.\n\n/**\n * JSON Schema type for tool parameters.\n * Re-exported from the AI SDK for convenience.\n * @deprecated Import JSONSchema7 directly from \"ai\" instead. Will be removed in the next major version.\n */\nexport type JSONSchemaType = JSONSchema7;\n\n/**\n * Definition for a tool that can be executed on the client.\n * Tools with an `execute` function are automatically registered with the server.\n *\n * **For most apps**, define tools on the server with `tool()` from `\"ai\"` —\n * you get full Zod type safety and simpler code. Use `onToolCall` in\n * `useAgentChat` for tools that need browser-side execution.\n *\n * **For SDKs and platforms** where the tool surface is determined dynamically\n * by the embedding application at runtime, this type lets the client register\n * tools the server does not know about at deploy time.\n *\n * Note: Uses `parameters` (JSONSchema7) because client tools must be\n * serializable for the wire format. Zod schemas cannot be serialized.\n */\nexport type AITool<Input = unknown, Output = unknown> = {\n /** Human-readable description of what the tool does */\n description?: Tool[\"description\"];\n /** JSON Schema defining the tool's input parameters */\n parameters?: JSONSchema7;\n /**\n * @deprecated Use `parameters` instead. Will be removed in a future version.\n */\n inputSchema?: JSONSchema7;\n /**\n * Function to execute the tool on the client.\n * If provided, the tool schema is automatically sent to the server.\n */\n execute?: (input: Input) => Output | Promise<Output>;\n};\n\nimport type { ClientToolSchema } from \"./client-tools\";\nexport type { ClientToolSchema } from \"./client-tools\";\n\n/**\n * Extracts tool schemas from tools that have client-side execute functions.\n * These schemas are automatically sent to the server with each request.\n *\n * Called internally by `useAgentChat` when `tools` are provided.\n * Most apps do not need to call this directly.\n *\n * @param tools - Record of tool name to tool definition\n * @returns Array of tool schemas to send to server, or undefined if none\n */\nexport function extractClientToolSchemas(\n tools?: Record<string, AITool<unknown, unknown>>\n): ClientToolSchema[] | undefined {\n if (!tools) return undefined;\n\n const schemas: ClientToolSchema[] = Object.entries(tools)\n .filter(([_, tool]) => tool.execute) // Only tools with client-side execute\n .map(([name, tool]) => {\n if (tool.inputSchema && !tool.parameters) {\n console.warn(\n `[useAgentChat] Tool \"${name}\" uses deprecated 'inputSchema'. Please migrate to 'parameters'.`\n );\n }\n return {\n name,\n description: tool.description,\n parameters: tool.parameters ?? tool.inputSchema\n };\n });\n\n return schemas.length > 0 ? schemas : undefined;\n}\n\n// ── END DEPRECATED TYPES AND FUNCTIONS ─────────────────────────────\n\n// ── Tool part helpers ──────────────────────────────────────────────\n//\n// `isToolUIPart` and `getToolName` are exported by the AI SDK:\n// import { isToolUIPart, getToolName } from \"ai\";\n//\n// The helpers below provide additional typed accessors and a\n// simplified state mapping that the AI SDK doesn't offer.\n\n/**\n * Map internal tool part states to simplified UI-relevant states.\n *\n * @example\n * ```tsx\n * import { isToolUIPart } from \"ai\";\n * import { getToolPartState } from \"@cloudflare/ai-chat/react\";\n *\n * if (isToolUIPart(part)) {\n * const state = getToolPartState(part);\n * if (state === \"complete\") { ... }\n * if (state === \"waiting-approval\") { ... }\n * }\n * ```\n */\nexport function getToolPartState(\n part: UIMessage[\"parts\"][number]\n):\n | \"loading\"\n | \"streaming\"\n | \"waiting-approval\"\n | \"approved\"\n | \"complete\"\n | \"error\"\n | \"denied\" {\n const state = (part as { state?: string }).state;\n switch (state) {\n case \"input-streaming\":\n return \"streaming\";\n case \"approval-requested\":\n return \"waiting-approval\";\n case \"approval-responded\":\n return \"approved\";\n case \"output-available\":\n return \"complete\";\n case \"output-error\":\n return \"error\";\n case \"output-denied\":\n return \"denied\";\n default:\n return \"loading\";\n }\n}\n\n/** Get the tool call ID from a tool UI part. */\nexport function getToolCallId(part: UIMessage[\"parts\"][number]): string {\n return (part as { toolCallId: string }).toolCallId;\n}\n\n/** Get the tool input from a tool UI part (if available). */\nexport function getToolInput(\n part: UIMessage[\"parts\"][number]\n): unknown | undefined {\n return (part as { input?: unknown }).input;\n}\n\n/** Get the tool output from a tool UI part (if available). */\nexport function getToolOutput(\n part: UIMessage[\"parts\"][number]\n): unknown | undefined {\n return (part as { output?: unknown }).output;\n}\n\n/** Get the approval info from a tool UI part (if in approval state). */\nexport function getToolApproval(\n part: UIMessage[\"parts\"][number]\n): { id: string; approved?: boolean } | undefined {\n return (part as { approval?: { id: string; approved?: boolean } }).approval;\n}\n\n// ── END Tool part helpers ──────────────────────────────────────────\n\n// ── Standalone fetch ───────────────────────────────────────────────\n\nfunction agentNameToKebab(name: string): string {\n if (name === name.toUpperCase() && name !== name.toLowerCase()) {\n return name.toLowerCase().replace(/_/g, \"-\");\n }\n let result = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n result = result.startsWith(\"-\") ? result.slice(1) : result;\n return result.replace(/_/g, \"-\").replace(/-$/, \"\");\n}\n\n/**\n * Fetch messages from an agent's `/get-messages` HTTP endpoint.\n *\n * Use in framework route loaders to prefetch messages before the component\n * tree mounts, or anywhere you need messages outside a React hook.\n *\n * @example Standard routing\n * ```typescript\n * import { getAgentMessages } from \"@cloudflare/ai-chat/react\";\n *\n * const messages = await getAgentMessages({\n * host: \"https://my-app.workers.dev\",\n * agent: \"ChatAgent\",\n * name: \"session-123\"\n * });\n * ```\n *\n * @example With basePath (custom URL)\n * ```typescript\n * const messages = await getAgentMessages({\n * url: \"https://my-app.workers.dev/custom/path/get-messages\"\n * });\n * ```\n */\nexport async function getAgentMessages<M extends UIMessage = UIMessage>(\n options:\n | {\n host: string;\n agent: string;\n name: string;\n credentials?: RequestCredentials;\n headers?: HeadersInit;\n }\n | {\n url: string;\n credentials?: RequestCredentials;\n headers?: HeadersInit;\n }\n): Promise<M[]> {\n let messagesUrl: string;\n\n if (\"url\" in options) {\n messagesUrl = options.url;\n } else {\n const agentSlug = agentNameToKebab(options.agent);\n const base = options.host.endsWith(\"/\")\n ? options.host.slice(0, -1)\n : options.host;\n messagesUrl = `${base}/agents/${agentSlug}/${options.name}/get-messages`;\n }\n\n try {\n const response = await fetch(messagesUrl, {\n credentials: options.credentials,\n headers: options.headers\n });\n\n if (!response.ok) {\n console.warn(\n `[getAgentMessages] Failed to fetch: ${response.status} ${response.statusText}`\n );\n return [];\n }\n\n const text = await response.text();\n if (!text.trim()) return [];\n\n return JSON.parse(text) as M[];\n } catch (error) {\n console.warn(\"[getAgentMessages] Fetch error:\", error);\n return [];\n }\n}\n\n// ── END Standalone fetch ───────────────────────────────────────────\n\ntype GetInitialMessagesOptions = {\n agent: string;\n name: string;\n url?: string;\n};\n\n// v5 useChat parameters\ntype UseChatParams<M extends UIMessage = UIMessage> = ChatInit<M> &\n UseChatOptions<M>;\n\n/**\n * Options for preparing the send messages request.\n * Used by prepareSendMessagesRequest callback.\n */\nexport type PrepareSendMessagesRequestOptions<\n ChatMessage extends UIMessage = UIMessage\n> = {\n /** The chat ID */\n id: string;\n /** Messages to send */\n messages: ChatMessage[];\n /** What triggered this request */\n trigger: \"submit-message\" | \"regenerate-message\";\n /** ID of the message being sent (if applicable) */\n messageId?: string;\n /** Request metadata */\n requestMetadata?: unknown;\n /** Current body (if any) */\n body?: Record<string, unknown>;\n /** Current credentials (if any) */\n credentials?: RequestCredentials;\n /** Current headers (if any) */\n headers?: HeadersInit;\n /** API endpoint */\n api?: string;\n};\n\n/**\n * Return type for prepareSendMessagesRequest callback.\n * Allows customizing headers, body, and credentials for each request.\n * All fields are optional; only specify what you need to customize.\n */\nexport type PrepareSendMessagesRequestResult = {\n /** Custom headers to send with the request */\n headers?: HeadersInit;\n /** Custom body data to merge with the request */\n body?: Record<string, unknown>;\n /** Custom credentials option */\n credentials?: RequestCredentials;\n /** Custom API endpoint */\n api?: string;\n};\n\n/**\n * Options for addToolOutput function\n */\ntype AddToolOutputOptions = {\n /** The ID of the tool call to provide output for */\n toolCallId: string;\n /** The name of the tool (optional, for type safety) */\n toolName?: string;\n /** The output to provide */\n output?: unknown;\n /** Override the tool part state (e.g. \"output-error\" for custom denial) */\n state?: \"output-available\" | \"output-error\";\n /** Error message when state is \"output-error\" */\n errorText?: string;\n};\n\n/**\n * Callback for handling client-side tool execution.\n * Called when a tool without server-side execute is invoked.\n */\nexport type OnToolCallCallback = (options: {\n /** The tool call that needs to be handled */\n toolCall: {\n toolCallId: string;\n toolName: string;\n input: unknown;\n };\n /** Function to provide the tool output (or signal an error/denial) */\n addToolOutput: (options: Omit<AddToolOutputOptions, \"toolName\">) => void;\n}) => void | Promise<void>;\n\n/**\n * Options for the useAgentChat hook\n */\nexport type UseAgentChatOptions<\n // oxlint-disable-next-line no-unused-vars -- kept for backward compat\n State = unknown,\n ChatMessage extends UIMessage = UIMessage\n> = Omit<UseChatParams<ChatMessage>, \"fetch\" | \"onToolCall\"> & {\n /** Agent connection from useAgent (accepts both typed and untyped agents) */\n agent: AgentConnection & {\n agent: string;\n name: string;\n path?: ReadonlyArray<{ agent: string; name: string }>;\n connectionError?: AgentConnectionErrorLike | null;\n getHttpUrl: () => string;\n };\n getInitialMessages?:\n | undefined\n | null\n | ((options: GetInitialMessagesOptions) => Promise<ChatMessage[]>);\n /** Request credentials */\n credentials?: RequestCredentials;\n /** Request headers */\n headers?: HeadersInit;\n /**\n * Callback for handling client-side tool execution.\n * Called when a tool without server-side `execute` is invoked by the LLM.\n *\n * Use this for:\n * - Tools that need browser APIs (geolocation, camera, etc.)\n * - Tools that need user interaction before providing a result\n * - Tools requiring approval before execution\n *\n * @example\n * ```typescript\n * onToolCall: async ({ toolCall, addToolOutput }) => {\n * if (toolCall.toolName === 'getLocation') {\n * const position = await navigator.geolocation.getCurrentPosition();\n * addToolOutput({\n * toolCallId: toolCall.toolCallId,\n * output: { lat: position.coords.latitude, lng: position.coords.longitude }\n * });\n * }\n * }\n * ```\n */\n onToolCall?: OnToolCallCallback;\n /**\n * @deprecated Use `onToolCall` callback instead for automatic tool execution.\n * @description Whether to automatically resolve tool calls that do not require human interaction.\n * @experimental\n */\n experimental_automaticToolResolution?: boolean;\n /**\n * Tools that can be executed on the client. Tool schemas are automatically\n * sent to the server and tool calls are routed back for client execution.\n *\n * **For most apps**, define tools on the server with `tool()` from `\"ai\"`\n * and handle client-side execution via `onToolCall`. This gives you full\n * Zod type safety and keeps tool definitions in one place.\n *\n * **For SDKs and platforms** where tools are defined dynamically by the\n * embedding application at runtime, this option lets the client register\n * tools the server does not know about at deploy time.\n */\n tools?: Record<string, AITool<unknown, unknown>>;\n /**\n * @deprecated Use `needsApproval` on server-side tools instead.\n * @description Manual override for tools requiring confirmation.\n * If not provided, will auto-detect from tools object (tools without execute require confirmation).\n */\n toolsRequiringConfirmation?: string[];\n /**\n * When true (default), the server automatically continues the conversation\n * after receiving client-side tool results or approvals, similar to how\n * server-executed tools work with maxSteps in streamText. The continuation\n * is merged into the same assistant message.\n *\n * When false, the client must call sendMessage() after tool results\n * to continue the conversation, which creates a new assistant message.\n *\n * @default true\n */\n autoContinueAfterToolResult?: boolean;\n /**\n * @deprecated Use `sendAutomaticallyWhen` from AI SDK instead.\n *\n * When true (default), automatically sends the next message only after\n * all pending confirmation-required tool calls have been resolved.\n * When false, sends immediately after each tool result.\n *\n * Only applies when `autoContinueAfterToolResult` is false.\n *\n * @default true\n */\n autoSendAfterAllConfirmationsResolved?: boolean;\n /**\n * Set to false to disable automatic stream resumption.\n * @default true\n */\n resume?: boolean;\n /**\n * Whether generic client-side stream abort/cleanup should cancel the server\n * turn. By default, client cleanup is local-only so the server turn can\n * continue and be resumed on reconnect. Explicit stop() always cancels the\n * server turn.\n *\n * @default false\n */\n cancelOnClientAbort?: boolean;\n /**\n * Whether `setMessages` should also send the full client transcript to the\n * server as `CF_AGENT_CHAT_MESSAGES`. This is useful for flat transcript\n * stores such as `AIChatAgent`, but should be disabled for server-authoritative\n * hosts whose client messages are only a projection of richer storage.\n *\n * @default true\n */\n syncMessagesToServer?: boolean;\n /**\n * Custom data to include in every chat request body.\n * Accepts a static object or a function that returns one (for dynamic values).\n * These fields are available in `onChatMessage` via `options.body`.\n *\n * @example\n * ```typescript\n * // Static\n * body: { timezone: \"America/New_York\", userId: \"abc\" }\n *\n * // Dynamic (called on each send)\n * body: () => ({ token: getAuthToken(), timestamp: Date.now() })\n * ```\n */\n body?:\n | Record<string, unknown>\n | (() => Record<string, unknown> | Promise<Record<string, unknown>>);\n /**\n * Callback to customize the request before sending messages.\n * For most cases, use the `body` option instead.\n * Use this for advanced scenarios that need access to the messages or trigger type.\n *\n * Note: Client tool schemas are automatically sent when tools have `execute` functions.\n * This callback can add additional data alongside the auto-extracted schemas.\n */\n prepareSendMessagesRequest?: (\n options: PrepareSendMessagesRequestOptions<ChatMessage>\n ) =>\n | PrepareSendMessagesRequestResult\n | Promise<PrepareSendMessagesRequestResult>;\n};\n\n/**\n * Module-level cache for initial message fetches. Intentionally shared across\n * all useAgentChat instances to deduplicate requests during React Strict Mode\n * double-renders and re-renders. Cache keys include the agent URL, agent type,\n * and thread name to prevent cross-agent collisions.\n */\nconst requestCache = new Map<string, Promise<Message[]>>();\n\nfunction findLastAssistantMessage<ChatMessage extends UIMessage>(\n messages: ChatMessage[]\n): { index: number; message: ChatMessage } | null {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index];\n if (message.role === \"assistant\") {\n return { index, message };\n }\n }\n\n return null;\n}\n\nfunction moveMessageToEnd<ChatMessage extends UIMessage>(\n messages: ChatMessage[],\n messageId: string\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.id === messageId);\n if (idx < 0 || idx === messages.length - 1) return messages;\n\n const result = [...messages];\n const [msg] = result.splice(idx, 1);\n if (!msg) return messages;\n\n result.push(msg);\n return result;\n}\n\nfunction prependMissingHydratedMessages<ChatMessage extends UIMessage>(\n hydratedMessages: ChatMessage[],\n currentMessages: ChatMessage[]\n): ChatMessage[] {\n if (currentMessages.length === 0) {\n return hydratedMessages;\n }\n\n const currentMessageIds = new Set(\n currentMessages.map((message) => message.id)\n );\n const missingHydratedMessages = hydratedMessages.filter(\n (message) => !currentMessageIds.has(message.id)\n );\n\n if (missingHydratedMessages.length === 0) {\n return currentMessages;\n }\n\n // History fetched after mount predates messages already rendered locally\n // (for example an optimistic immediate send). Keep current copies for\n // matching IDs because they may include newer streamed/client state.\n return [...missingHydratedMessages, ...currentMessages];\n}\n\n/**\n * React hook for building AI chat interfaces using an Agent\n * @param options Chat options including the agent connection\n * @returns Chat interface controls and state with added clearHistory method\n */\n/**\n * Automatically detects which tools require confirmation based on their configuration.\n * Tools require confirmation if they have no execute function AND are not server-executed.\n * @param tools - Record of tool name to tool definition\n * @returns Array of tool names that require confirmation\n *\n * @deprecated Use `needsApproval` on server-side tools instead.\n */\nexport function detectToolsRequiringConfirmation(\n tools?: Record<string, AITool<unknown, unknown>>\n): string[] {\n warnDeprecated(\n \"detectToolsRequiringConfirmation\",\n \"detectToolsRequiringConfirmation() is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.\"\n );\n if (!tools) return [];\n\n return Object.entries(tools)\n .filter(([_name, tool]) => !tool.execute)\n .map(([name]) => name);\n}\n\nexport function useAgentChat<\n // oxlint-disable-next-line no-unused-vars -- kept for backward compat\n State = unknown,\n ChatMessage extends UIMessage = UIMessage\n>(\n options: UseAgentChatOptions<State, ChatMessage>\n): Omit<ReturnType<typeof useChat<ChatMessage>>, \"addToolOutput\"> & {\n clearHistory: () => void;\n /**\n * Provide output for a tool call. Use this for tools that require user interaction\n * or client-side execution.\n */\n addToolOutput: (opts: AddToolOutputOptions) => void;\n /**\n * Whether a server-initiated stream (e.g. from `saveMessages`,\n * auto-continuation, or another tab) is currently active, OR a\n * client-side tool call is awaiting resolution via `onToolCall`.\n * Covers the full \"turn-in-progress\" window from the consumer's\n * perspective, including the gap between the model emitting a\n * client-tool call and the server pushing a continuation after\n * `addToolOutput`. This is independent of the AI SDK's `status`\n * which only tracks client-initiated request/response cycles.\n */\n isServerStreaming: boolean;\n /**\n * Convenience flag: `true` when either the client-initiated stream\n * (`status === \"streaming\"`) or a server-initiated stream is active.\n * Use this for showing a universal streaming indicator.\n */\n isStreaming: boolean;\n /**\n * `true` while a durable chat turn is being recovered (interrupted by a\n * deploy/eviction or a stream-stall watchdog abort and now resuming, #1620).\n * Distinct from `isStreaming` — a recovering turn isn't producing tokens yet,\n * so a client can show a \"recovering…\" hint instead of looking frozen. Most\n * UIs treat `isStreaming || isRecovering` as \"busy\". Driven by the server's\n * `CF_AGENT_CHAT_RECOVERING` frames (also replayed on connect for\n * `@cloudflare/think`); cleared automatically on the next stream or terminal.\n */\n isRecovering: boolean;\n /**\n * `true` when the current `status`/`isServerStreaming` activity is\n * driven by a server-pushed tool continuation (i.e. the server is\n * auto-continuing the conversation after `addToolOutput` or\n * `addToolApprovalResponse`) rather than a fresh user submission.\n *\n * Use this to disambiguate \"user just sent a new message, awaiting\n * first token\" from \"mid-turn tool round-trip\" — e.g. when you want\n * a typing indicator only for the former:\n *\n * ```tsx\n * const showTypingIndicator = status === \"submitted\" && !isToolContinuation;\n * ```\n *\n * See issue #1365.\n */\n isToolContinuation: boolean;\n connectionError: AgentConnectionErrorLike | null;\n} {\n const {\n agent,\n getInitialMessages,\n messages: optionsInitialMessages,\n onToolCall,\n onData,\n experimental_automaticToolResolution,\n tools,\n toolsRequiringConfirmation: manualToolsRequiringConfirmation,\n autoContinueAfterToolResult = true, // Server auto-continues after tool results/approvals\n autoSendAfterAllConfirmationsResolved = true, // Legacy option for client-side batching\n resume = true, // Enable stream resumption by default\n cancelOnClientAbort = false,\n syncMessagesToServer = true,\n body: bodyOption,\n prepareSendMessagesRequest,\n ...rest\n } = options;\n\n // Emit deprecation warnings for deprecated options (once per session)\n if (manualToolsRequiringConfirmation) {\n warnDeprecated(\n \"useAgentChat.toolsRequiringConfirmation\",\n \"The 'toolsRequiringConfirmation' option is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.\"\n );\n }\n if (experimental_automaticToolResolution) {\n warnDeprecated(\n \"useAgentChat.experimental_automaticToolResolution\",\n \"The 'experimental_automaticToolResolution' option is deprecated. Use the onToolCall callback instead. Will be removed in the next major version.\"\n );\n }\n if (options.autoSendAfterAllConfirmationsResolved !== undefined) {\n warnDeprecated(\n \"useAgentChat.autoSendAfterAllConfirmationsResolved\",\n \"The 'autoSendAfterAllConfirmationsResolved' option is deprecated. Use sendAutomaticallyWhen from AI SDK instead. Will be removed in the next major version.\"\n );\n }\n\n // ── DEPRECATED: client-side tool confirmation ──────────────────────\n // This block will be removed when toolsRequiringConfirmation is removed.\n // Only call the deprecated function when deprecated options are actually used.\n const toolsRequiringConfirmation = useMemo(() => {\n if (manualToolsRequiringConfirmation) {\n return manualToolsRequiringConfirmation;\n }\n // Inline the logic from detectToolsRequiringConfirmation to avoid\n // emitting a deprecation warning when tools are provided via the\n // non-deprecated `tools` option.\n if (!tools) return [];\n return Object.entries(tools)\n .filter(([_name, tool]) => !tool.execute)\n .map(([name]) => name);\n }, [manualToolsRequiringConfirmation, tools]);\n\n // Keep refs to always point to the latest callbacks\n const onToolCallRef = useRef(onToolCall);\n onToolCallRef.current = onToolCall;\n const onDataRef = useRef(onData);\n onDataRef.current = onData;\n\n const rawHttpUrl = agent.getHttpUrl();\n const agentUrl = rawHttpUrl ? new URL(rawHttpUrl) : null;\n\n if (agentUrl) {\n agentUrl.searchParams.delete(\"_pk\");\n }\n const agentUrlString = agentUrl?.toString() ?? null;\n\n const agentAddressKey = Array.isArray(agent.path)\n ? JSON.stringify(agent.path.map((step) => [step.agent, step.name]))\n : JSON.stringify([[agent.agent ?? \"\", agent.name ?? \"\"]]);\n\n // Cache key for the request-dedup `requestCache` and the late-seed\n // effect. It uses the full root-first agent address when `useAgent`\n // provides one, so sub-agents with the same leaf class/name under\n // different parents do not share hydrated messages.\n //\n // - Query params like auth tokens change across page loads and\n // must not bust the cache, or Suspense re-triggers and breaks\n // stream resume (see issue #1223).\n // - The origin+pathname portion of the socket URL can legitimately\n // transition from empty → resolved on the second render when\n // `useAgent()` finishes its handshake. Including it here would\n // cause `doGetInitialMessages` to miss the cache after the URL\n // arrives, re-invoke the loader, and re-trigger Suspense — the\n // exact regression #1356 reports when a custom `getInitialMessages`\n // is provided.\n //\n // `resolvedInitialMessagesCacheKey` is still computed because the\n // `stableChatIdRef` logic below uses it to detect the URL-arrival\n // transition separately from identity changes.\n const resolvedInitialMessagesCacheKey = agentUrl\n ? `${agentUrl.origin}${agentUrl.pathname}|${agentAddressKey}`\n : null;\n const initialMessagesCacheKey = agentAddressKey;\n\n // Stable chat ID for `useChat({ id })`.\n //\n // The AI SDK recreates the underlying Chat instance whenever its `id`\n // changes, which aborts any in-flight `transport.reconnectToStream()`\n // (the resume path) and leaves the recreated Chat without any resume\n // having been fired on it — the AI SDK's `useEffect(() => {\n // if (resume) chatRef.current.resumeStream() }, [resume, chatRef])`\n // deps are object-stable, so the effect does not re-fire on recreation.\n // See issue #1356.\n //\n // Two things can move across renders and must NOT cause an id flip:\n //\n // 1. The origin+pathname of the socket URL can transition from\n // `null` → resolved on the second render when `useAgent()`\n // finishes its handshake. The Chat id must stay on its\n // first-render fallback through that transition; upgrading to\n // the URL-resolved key would recreate Chat and drop optimistic\n // messages sent immediately after mount.\n //\n // 2. `agent.name` can transition from the client-side fallback\n // (\"default\") to a server-assigned value when\n // `static options = { sendIdentityOnConnect: true }` is set and\n // the consumer uses the `basePath` pattern (the server owns the\n // DO instance name, not the browser). `useAgent` mutates the\n // same agent object's `.name` in place here.\n //\n // What IS a genuine chat switch: the consumer passes a different\n // `agent` object to `useAgentChat`. That's a new `useAgent({...})`\n // return value, typically from swapping or remounting a parent. We\n // detect this by reference equality — `useAgent`'s return is stable\n // across renders for a given mount, so a reference change is the\n // unambiguous \"chat switch\" signal.\n const stableChatIdRef = useRef<string | null>(null);\n const previousAgentRef = useRef<typeof agent | null>(null);\n const previousAgentAddressKeyRef = useRef<string | null>(null);\n const fallbackChatId = agentAddressKey;\n const agentPathChanged =\n Array.isArray(agent.path) &&\n previousAgentAddressKeyRef.current !== null &&\n previousAgentAddressKeyRef.current !== agentAddressKey;\n\n if (stableChatIdRef.current === null) {\n // First render: initialize.\n stableChatIdRef.current = resolvedInitialMessagesCacheKey ?? fallbackChatId;\n } else if (previousAgentRef.current !== agent || agentPathChanged) {\n // Consumer swapped in a different agent object, or the full\n // sub-agent address changed on a `useAgent` object — genuine chat switch.\n // Recompute from current values.\n stableChatIdRef.current = resolvedInitialMessagesCacheKey ?? fallbackChatId;\n }\n\n previousAgentRef.current = agent;\n previousAgentAddressKeyRef.current = agentAddressKey;\n\n // Keep a ref to always point to the latest agent instance.\n // Updated synchronously during render (not in useEffect) so the\n // transport's agent ref is always current. The transport is a\n // singleton whose .agent is reassigned every render — if we used\n // useEffect the assignment would lag behind, causing the transport\n // to send through a stale/closed socket (issue #929).\n const agentRef = useRef(agent);\n agentRef.current = agent;\n\n async function defaultGetInitialMessagesFetch({\n url\n }: GetInitialMessagesOptions) {\n if (!url) {\n return [];\n }\n const getMessagesUrl = new URL(url);\n getMessagesUrl.pathname += \"/get-messages\";\n const response = await fetch(getMessagesUrl.toString(), {\n credentials: options.credentials,\n headers: options.headers\n });\n\n if (!response.ok) {\n console.warn(\n `Failed to fetch initial messages: ${response.status} ${response.statusText}`\n );\n return [];\n }\n\n const text = await response.text();\n if (!text.trim()) {\n return [];\n }\n\n try {\n return JSON.parse(text) as ChatMessage[];\n } catch (error) {\n console.warn(\"Failed to parse initial messages JSON:\", error);\n return [];\n }\n }\n\n const getInitialMessagesFetch =\n getInitialMessages || defaultGetInitialMessagesFetch;\n\n function doGetInitialMessages(\n getInitialMessagesOptions: GetInitialMessagesOptions,\n cacheKey: string\n ) {\n if (requestCache.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Promise<ChatMessage[]>;\n }\n const promise = getInitialMessagesFetch(getInitialMessagesOptions);\n requestCache.set(cacheKey, promise);\n return promise;\n }\n\n const shouldFetchInitialMessages =\n getInitialMessages === null\n ? false\n : getInitialMessages\n ? true\n : !!agentUrlString;\n const initialMessagesPromise = !shouldFetchInitialMessages\n ? null\n : doGetInitialMessages(\n {\n agent: agent.agent,\n name: agent.name,\n url: agentUrlString ?? undefined\n },\n initialMessagesCacheKey\n );\n const initialMessages = initialMessagesPromise\n ? use(initialMessagesPromise)\n : (optionsInitialMessages ?? []);\n\n useEffect(() => {\n if (!initialMessagesPromise) {\n return;\n }\n requestCache.set(initialMessagesCacheKey, initialMessagesPromise!);\n return () => {\n if (\n requestCache.get(initialMessagesCacheKey) === initialMessagesPromise\n ) {\n requestCache.delete(initialMessagesCacheKey);\n }\n };\n }, [initialMessagesCacheKey, initialMessagesPromise]);\n\n // Use synchronous ref updates to avoid race conditions between effect runs.\n // This ensures the ref always has the latest value before any effect reads it.\n const toolsRef = useRef(tools);\n toolsRef.current = tools;\n\n const prepareSendMessagesRequestRef = useRef(prepareSendMessagesRequest);\n prepareSendMessagesRequestRef.current = prepareSendMessagesRequest;\n\n const bodyOptionRef = useRef(bodyOption);\n bodyOptionRef.current = bodyOption;\n\n /**\n * Tracks request IDs initiated by this tab via the transport.\n * Used by onAgentMessage to skip messages already handled by the transport.\n */\n const localRequestIdsRef = useRef<Set<string>>(new Set());\n const pendingReplayResumeRequestIdsRef = useRef<Set<string>>(new Set());\n const replayHydratedAssistantMessageIdsRef = useRef<Set<string>>(new Set());\n /**\n * Request ids this socket already ACKed via the fallback resume path.\n * The server sends CF_AGENT_STREAM_RESUMING for the same request from\n * both onConnect and its CF_AGENT_STREAM_RESUME_REQUEST handler (#1733).\n * The transport-handled path dedupes the second notify via\n * localRequestIdsRef, but the fallback path used to ACK both — triggering\n * a second full-buffer replay that duplicated streamed parts. Entries are\n * dropped when the turn completes; the whole set resets when the socket\n * closes, since a new connection legitimately needs a fresh ACK+replay.\n */\n const fallbackAckedResumeRequestIdsRef = useRef<Set<string>>(new Set());\n\n // WebSocket-based transport that speaks the CF_AGENT protocol natively.\n // Replaces the old aiFetch + DefaultChatTransport indirection.\n //\n // The transport is a true singleton (created once, never recreated) so\n // that the resolver set by reconnectToStream and the handleStreamResuming\n // call from onAgentMessage always operate on the SAME instance — even\n // when _pk changes (async queries, socket recreation) or React Strict\n // Mode double-mounts. The agent reference is updated every render so\n // sends always go through the latest socket.\n const customTransportRef = useRef<WebSocketChatTransport<ChatMessage> | null>(\n null\n );\n\n if (customTransportRef.current === null) {\n customTransportRef.current = new WebSocketChatTransport<ChatMessage>({\n agent: agentRef.current,\n activeRequestIds: localRequestIdsRef.current,\n cancelOnClientAbort,\n prepareBody: async ({ messages: msgs, trigger, messageId }) => {\n // Start with the top-level body option (static or dynamic)\n let extraBody: Record<string, unknown> = {};\n const currentBody = bodyOptionRef.current;\n if (currentBody) {\n const resolved =\n typeof currentBody === \"function\"\n ? await currentBody()\n : currentBody;\n extraBody = { ...resolved };\n }\n\n // Extract schemas from deprecated client tools (if any)\n // Only extract client tool schemas when deprecated tools option is used\n if (toolsRef.current) {\n const clientToolSchemas = extractClientToolSchemas(toolsRef.current);\n if (clientToolSchemas) {\n extraBody.clientTools = clientToolSchemas;\n }\n }\n\n // Apply user's prepareSendMessagesRequest callback (overrides body option)\n if (prepareSendMessagesRequestRef.current) {\n const userResult = await prepareSendMessagesRequestRef.current({\n id: (agentRef.current as unknown as { _pk: string })._pk,\n messages: msgs,\n trigger,\n messageId\n });\n if (userResult.body) {\n Object.assign(extraBody, userResult.body);\n }\n }\n\n return extraBody;\n }\n });\n }\n // Always point the transport at the latest socket so sends/listeners\n // go through the current connection after _pk changes.\n customTransportRef.current.agent = agentRef.current;\n customTransportRef.current.setCancelOnClientAbort(cancelOnClientAbort);\n const customTransport = customTransportRef.current;\n\n // Use a stable Chat ID that doesn't change when _pk changes.\n // The AI SDK recreates the Chat when `id` changes, which would\n // abandon any in-flight makeRequest (including resume) and the\n // resume effect wouldn't re-fire (deps are [resume, chatRef]).\n // Using the initial messages cache key (URL + agent + name) keeps\n // the Chat stable across socket recreations.\n const useChatHelpers = useChat<ChatMessage>({\n ...rest,\n onData,\n messages: initialMessages,\n transport: customTransport,\n id: stableChatIdRef.current,\n // Pass resume so useChat calls transport.reconnectToStream().\n // This lets the AI SDK track status (\"streaming\") during resume.\n resume\n });\n\n // Destructure stable method references from useChatHelpers.\n // These are individually memoized by the AI SDK (via useCallback), so they're\n // safe to use in dependency arrays without causing re-renders. Using them\n // directly instead of `useChatHelpers.method` avoids the exhaustive-deps\n // warning about the unstable `useChatHelpers` object.\n const {\n messages: chatMessages,\n setMessages,\n addToolResult,\n addToolApprovalResponse,\n sendMessage,\n resumeStream,\n status,\n stop\n } = useChatHelpers;\n\n const statusRef = useRef(status);\n statusRef.current = status;\n\n // Latest resumeStream, read by the reconnect re-probe effect without making\n // it a dependency (it would re-register the socket listeners on every render).\n const resumeStreamRef = useRef(resumeStream);\n resumeStreamRef.current = resumeStream;\n\n // Whether the socket has opened at least once. The AI SDK fires its own\n // resume on mount, so we only re-probe on SUBSEQUENT opens (transparent 1006\n // reconnects that don't remount the component) — see the reconnect re-probe\n // in the socket effect below (#1784).\n const hasConnectedOnceRef = useRef(false);\n\n const resumingToolContinuationRef = useRef(false);\n const pendingToolContinuationRef = useRef(false);\n const observedToolContinuationRequestIdRef = useRef<string | null>(null);\n const continuationLaunchTimerRef = useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n // Generation counter for tool continuations. Bumped on every\n // `startToolContinuation` entry and on any external reset path\n // (e.g. `clearHistory`). The `.finally()` handler captures its\n // generation at start time and only applies the cleanup if it still\n // matches — otherwise the promise is settling after a reset or after\n // a newer continuation has already taken over, and its reset would\n // clobber current state.\n const continuationGenerationRef = useRef(0);\n // Mirrors `resumingToolContinuationRef` as React state so consumers can\n // distinguish a user-initiated `status === \"submitted\"` from one driven\n // by a server-pushed tool continuation. The ref is kept for its\n // synchronous re-entry guard semantics; this state is purely for UI.\n // See issue #1365.\n const [isToolContinuation, setIsToolContinuation] = useState(false);\n\n // Shared reset for every path that wipes chat history — the local\n // `clearHistory()` call AND the server-pushed `CF_AGENT_CHAT_CLEAR`\n // handler (another tab or the server itself cleared the chat).\n // Without this, a tab with an in-flight tool continuation that\n // receives a cross-tab clear would render `isToolContinuation === true`\n // over an empty message list until the orphaned `resumeStream()`\n // promise eventually settles. Keep ref/state/generation in lockstep;\n // the generation bump ensures the pending `.finally()` is a no-op.\n const resetToolContinuation = useCallback(() => {\n continuationGenerationRef.current++;\n pendingToolContinuationRef.current = false;\n resumingToolContinuationRef.current = false;\n observedToolContinuationRequestIdRef.current = null;\n if (continuationLaunchTimerRef.current) {\n clearTimeout(continuationLaunchTimerRef.current);\n continuationLaunchTimerRef.current = null;\n }\n setIsToolContinuation(false);\n }, []);\n\n const scheduleToolContinuationLaunch = useCallback(() => {\n if (\n !pendingToolContinuationRef.current ||\n statusRef.current !== \"ready\" ||\n continuationLaunchTimerRef.current\n ) {\n return;\n }\n\n continuationLaunchTimerRef.current = setTimeout(() => {\n continuationLaunchTimerRef.current = null;\n\n if (\n !pendingToolContinuationRef.current ||\n statusRef.current !== \"ready\"\n ) {\n return;\n }\n\n pendingToolContinuationRef.current = false;\n const myGeneration = continuationGenerationRef.current;\n customTransport.expectToolContinuation();\n\n void resumeStream()\n .catch((error) => {\n console.error(\n \"[useAgentChat] Tool continuation resume failed:\",\n error\n );\n })\n .finally(() => {\n // Bail if a reset (clearHistory / cross-tab clear) or a newer\n // continuation has taken over since we started — otherwise this\n // stale settlement would flip the flags off while a newer\n // continuation is still in flight, and reopen the re-entry\n // guard spuriously.\n if (continuationGenerationRef.current !== myGeneration) return;\n resumingToolContinuationRef.current = false;\n setIsToolContinuation(false);\n });\n }, 0);\n }, [customTransport, resumeStream]);\n\n const startToolContinuation = useCallback(() => {\n if (!autoContinueAfterToolResult || resumingToolContinuationRef.current) {\n return;\n }\n\n ++continuationGenerationRef.current;\n resumingToolContinuationRef.current = true;\n pendingToolContinuationRef.current = true;\n setIsToolContinuation(true);\n scheduleToolContinuationLaunch();\n }, [autoContinueAfterToolResult, scheduleToolContinuationLaunch]);\n\n useEffect(() => {\n if (status === \"error\" && pendingToolContinuationRef.current) {\n resetToolContinuation();\n return;\n }\n\n scheduleToolContinuationLaunch();\n }, [resetToolContinuation, scheduleToolContinuationLaunch, status]);\n\n const stopWithToolContinuationAbort: typeof stop = useCallback(async () => {\n try {\n customTransport.cancelActiveServerTurn();\n await stop();\n } finally {\n customTransport.abortActiveToolContinuation();\n }\n }, [stop, customTransport]);\n\n const processedToolCalls = useRef(new Set<string>());\n const isResolvingToolsRef = useRef(false);\n // Counter to force the tool resolution effect to re-run after completing\n // a batch of tool calls. Without this, if new tool calls arrive while\n // isResolvingToolsRef is true (e.g. server auto-continuation), the effect\n // exits early and never retriggers because the ref reset doesn't cause\n // a re-render.\n const [toolResolutionTrigger, setToolResolutionTrigger] = useState(0);\n\n // Fix for issue #728: Track client-side tool results in local state\n // to ensure tool parts show output-available immediately after execution.\n const [clientToolResults, setClientToolResults] = useState<\n Map<string, unknown>\n >(new Map());\n\n // Ref to access current messages in callbacks without stale closures\n const messagesRef = useRef(chatMessages);\n messagesRef.current = chatMessages;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // Tracks which `initialMessagesCacheKey` we've already applied to the\n // underlying Chat. Used by the late-seed effect below, and flipped to\n // the current key whenever the chat is intentionally emptied so we\n // don't re-hydrate server history on top of a user-driven clear.\n const seededInitialMessagesKeyRef = useRef<string | null>(null);\n const markInitialMessagesSeeded = useCallback(() => {\n seededInitialMessagesKeyRef.current = initialMessagesCacheKey;\n }, [initialMessagesCacheKey]);\n\n // Late-seed: when the initial-messages promise resolves AFTER the Chat\n // was already mounted (the URL-not-ready-on-first-render case), the\n // AI SDK's `useChat({ messages })` won't re-ingest the new value.\n // This effect applies it once per cache key. If the user already sent\n // an optimistic first message, prepend any missing hydrated history\n // rather than overwriting or discarding either side. Existing in-memory\n // message copies win by ID because they may include newer stream/client\n // state than the fetch response.\n //\n // Crucially, we mark the key as handled on EVERY observation so that\n // subsequent emptying events (a server broadcast of\n // CF_AGENT_CHAT_MESSAGES with `[]`, a `setMessages([])` on this tab,\n // or an explicit `clearHistory()`) don't resurrect stale initial\n // messages on top of the clear.\n useEffect(() => {\n if (!initialMessagesPromise) {\n return;\n }\n if (seededInitialMessagesKeyRef.current === initialMessagesCacheKey) {\n return;\n }\n\n markInitialMessagesSeeded();\n setMessages((prevMessages: ChatMessage[]) =>\n prependMissingHydratedMessages(initialMessagesRef.current, prevMessages)\n );\n }, [\n initialMessagesCacheKey,\n initialMessagesPromise,\n markInitialMessagesSeeded,\n setMessages\n ]);\n\n const localResponseMessageIdsRef = useRef(new Map<string, string>());\n const protectedStreamingAssistantRef = useRef<{\n assistantId: string;\n anchorMessageId: string | null;\n } | null>(null);\n\n const preserveProtectedStreamingAssistant = useCallback(\n (messages: readonly ChatMessage[]): ChatMessage[] => {\n const protection = protectedStreamingAssistantRef.current;\n if (!protection) {\n return [...messages];\n }\n\n // If the incoming snapshot already contains the protected assistant AND a\n // later assistant message, the server transcript has advanced past the\n // message we were protecting — the snapshot is terminal/authoritative\n // (e.g. a Think HITL denial persisted the denied tool message, then\n // appended a follow-up assistant response explaining the denial).\n // Appending our protected copy to the tail would reorder the transcript\n // (#1778). Clear protection and trust the snapshot as-is; if a stream is\n // still active for the newer assistant it re-arms protection via its own\n // `start` chunk.\n const protectedIndex = messages.findIndex(\n (message) => message.id === protection.assistantId\n );\n if (\n protectedIndex >= 0 &&\n messages\n .slice(protectedIndex + 1)\n .some((message) => message.role === \"assistant\")\n ) {\n protectedStreamingAssistantRef.current = null;\n return [...messages];\n }\n\n const protectedAssistant =\n messagesRef.current.find(\n (message) => message.id === protection.assistantId\n ) ?? messages.find((message) => message.id === protection.assistantId);\n if (!protectedAssistant) {\n return [...messages];\n }\n\n return [\n ...messages.filter((message) => message.id !== protection.assistantId),\n protectedAssistant\n ];\n },\n []\n );\n\n const protectStreamingAssistantTail = useCallback(() => {\n if (statusRef.current !== \"streaming\") {\n return;\n }\n\n const assistantInfo = findLastAssistantMessage(messagesRef.current);\n if (!assistantInfo) {\n return;\n }\n\n if (\n protectedStreamingAssistantRef.current?.assistantId !==\n assistantInfo.message.id\n ) {\n protectedStreamingAssistantRef.current = {\n assistantId: assistantInfo.message.id,\n anchorMessageId:\n messagesRef.current[assistantInfo.index - 1]?.id ?? null\n };\n }\n\n setMessages((prevMessages: ChatMessage[]) => {\n const protection = protectedStreamingAssistantRef.current;\n if (!protection) {\n return prevMessages;\n }\n\n return moveMessageToEnd(prevMessages, protection.assistantId);\n });\n }, [setMessages]);\n\n const restoreProtectedStreamingAssistant = useCallback(\n (assistantId?: string) => {\n const protection = protectedStreamingAssistantRef.current;\n if (\n !protection ||\n (assistantId !== undefined && protection.assistantId !== assistantId)\n ) {\n return;\n }\n\n protectedStreamingAssistantRef.current = null;\n setMessages((prevMessages: ChatMessage[]) => {\n const sourceIdx = prevMessages.findIndex(\n (m) => m.id === protection.assistantId\n );\n if (sourceIdx < 0) return prevMessages;\n\n const result = [...prevMessages];\n const [msg] = result.splice(sourceIdx, 1);\n if (!msg) return prevMessages;\n\n if (protection.anchorMessageId === null) {\n result.unshift(msg);\n } else {\n const anchorIdx = result.findIndex(\n (m) => m.id === protection.anchorMessageId\n );\n result.splice(anchorIdx >= 0 ? anchorIdx + 1 : sourceIdx, 0, msg);\n }\n\n return result;\n });\n },\n [setMessages]\n );\n\n const resetMatchingHydratedAssistantForReplay = useCallback(\n (messageId: string) => {\n setMessages((prevMessages: ChatMessage[]) => {\n const lastMessage = prevMessages[prevMessages.length - 1];\n if (\n !lastMessage ||\n lastMessage.role !== \"assistant\" ||\n lastMessage.id !== messageId\n ) {\n return prevMessages;\n }\n\n // Initial message hydration can already contain the partially\n // persisted assistant response. Clear that assistant only once\n // replay proves it is rebuilding the same message; keeping the\n // shell preserves layout while avoiding duplicate text parts.\n replayHydratedAssistantMessageIdsRef.current.add(messageId);\n const next = [...prevMessages];\n next[next.length - 1] = { ...lastMessage, parts: [] };\n return next;\n });\n },\n [setMessages]\n );\n\n const collapseHydratedReplayTextParts = useCallback(\n (message: ChatMessage): ChatMessage => {\n const parts = message.parts;\n const nextParts = parts.filter((part, index) => {\n if (part.type !== \"text\" || !(\"text\" in part) || !part.text) {\n return true;\n }\n\n // Replayed streams rebuild from the first chunk. If the\n // hydrated assistant already had the same prefix, replay can\n // temporarily produce a second text part with the rebuilt text.\n return !parts.some((candidate, candidateIndex) => {\n if (candidateIndex <= index) return false;\n if (\n candidate.type !== \"text\" ||\n !(\"text\" in candidate) ||\n !candidate.text\n ) {\n return false;\n }\n return candidate.text.startsWith(part.text);\n });\n });\n\n return nextParts.length === parts.length\n ? message\n : { ...message, parts: nextParts };\n },\n []\n );\n\n useEffect(() => {\n if (replayHydratedAssistantMessageIdsRef.current.size === 0) return;\n\n const idsToCollapse = new Set(\n chatMessages\n .filter(\n (message) =>\n replayHydratedAssistantMessageIdsRef.current.has(message.id) &&\n message.role === \"assistant\" &&\n collapseHydratedReplayTextParts(message) !== message\n )\n .map((message) => message.id)\n );\n if (idsToCollapse.size === 0) return;\n\n setMessages((prevMessages: ChatMessage[]) => {\n let changed = false;\n const nextMessages = prevMessages.map((message) => {\n if (!idsToCollapse.has(message.id)) {\n return message;\n }\n\n const nextMessage = collapseHydratedReplayTextParts(message);\n if (nextMessage !== message) {\n changed = true;\n }\n return nextMessage;\n });\n\n return changed ? nextMessages : prevMessages;\n });\n }, [chatMessages, collapseHydratedReplayTextParts, setMessages]);\n\n // Shared reset for every path that wipes chat history — keep this\n // list in sync between `clearHistory()` (local user action) and the\n // `CF_AGENT_CHAT_CLEAR` broadcast handler (server/other-tab action).\n // Anything reset here must be safe to reset either way; broadcast-\n // specific state (`streamStateRef`, `isServerStreaming`) stays in\n // the broadcast handler because it describes cross-tab/server\n // streams that a local `clearHistory()` can't meaningfully cancel.\n const resetLocalChatState = useCallback(() => {\n markInitialMessagesSeeded();\n setMessages([]);\n setClientToolResults(new Map());\n setPendingOnToolCallIds(new Set());\n resetToolContinuation();\n processedToolCalls.current.clear();\n localResponseMessageIdsRef.current.clear();\n pendingReplayResumeRequestIdsRef.current.clear();\n fallbackAckedResumeRequestIdsRef.current.clear();\n replayHydratedAssistantMessageIdsRef.current.clear();\n protectedStreamingAssistantRef.current = null;\n }, [markInitialMessagesSeeded, setMessages, resetToolContinuation]);\n\n const sendMessageWithStreamingProtection: typeof sendMessage = useCallback(\n async (message, options) => {\n const request = sendMessage(message, options);\n\n if (\n message !== undefined &&\n !(\n typeof message === \"object\" &&\n message !== null &&\n \"messageId\" in message &&\n message.messageId != null\n )\n ) {\n protectStreamingAssistantTail();\n }\n\n return request;\n },\n [sendMessage, protectStreamingAssistantTail]\n );\n\n // Calculate pending confirmations for the latest assistant message\n const lastMessage = chatMessages[chatMessages.length - 1];\n\n const pendingConfirmations = (() => {\n if (!lastMessage || lastMessage.role !== \"assistant\") {\n return { messageId: undefined, toolCallIds: new Set<string>() };\n }\n\n const pendingIds = new Set<string>();\n for (const part of lastMessage.parts ?? []) {\n if (\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n toolsRequiringConfirmation.includes(getToolName(part))\n ) {\n pendingIds.add(part.toolCallId);\n }\n }\n return { messageId: lastMessage.id, toolCallIds: pendingIds };\n })();\n\n const pendingConfirmationsRef = useRef(pendingConfirmations);\n pendingConfirmationsRef.current = pendingConfirmations;\n const [pendingOnToolCallIds, setPendingOnToolCallIds] = useState<Set<string>>(\n () => new Set()\n );\n\n const finishOnToolCall = useCallback((toolCallId: string) => {\n setPendingOnToolCallIds((prev) => {\n if (!prev.has(toolCallId)) return prev;\n const next = new Set(prev);\n next.delete(toolCallId);\n return next;\n });\n }, []);\n\n // ── DEPRECATED: automatic tool resolution effect ────────────────────\n // This entire useEffect is deprecated. Use onToolCall instead.\n useEffect(() => {\n if (!experimental_automaticToolResolution) {\n return;\n }\n\n void toolResolutionTrigger;\n\n // Prevent re-entry while async operations are in progress\n if (isResolvingToolsRef.current) {\n return;\n }\n\n const lastMsg = chatMessages[chatMessages.length - 1];\n if (!lastMsg || lastMsg.role !== \"assistant\") {\n return;\n }\n\n const toolCalls = lastMsg.parts.filter(\n (part) =>\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n !processedToolCalls.current.has(part.toolCallId)\n );\n\n if (toolCalls.length > 0) {\n // Capture tools synchronously before async work\n const currentTools = toolsRef.current;\n const toolCallsToResolve = toolCalls.filter(\n (part) =>\n isToolUIPart(part) &&\n !toolsRequiringConfirmation.includes(getToolName(part)) &&\n currentTools?.[getToolName(part)]?.execute\n );\n\n if (toolCallsToResolve.length > 0) {\n isResolvingToolsRef.current = true;\n\n (async () => {\n try {\n const toolResults: Array<{\n toolCallId: string;\n toolName: string;\n output: unknown;\n }> = [];\n\n for (const part of toolCallsToResolve) {\n if (isToolUIPart(part)) {\n let toolOutput: unknown = null;\n const toolName = getToolName(part);\n const tool = currentTools?.[toolName];\n\n if (tool?.execute && part.input !== undefined) {\n try {\n toolOutput = await tool.execute(part.input);\n } catch (error) {\n toolOutput = `Error executing tool: ${error instanceof Error ? error.message : String(error)}`;\n }\n }\n\n processedToolCalls.current.add(part.toolCallId);\n\n toolResults.push({\n toolCallId: part.toolCallId,\n toolName,\n output: toolOutput\n });\n }\n }\n\n if (toolResults.length > 0) {\n // Send tool results to server first (server is source of truth)\n const clientToolSchemas = extractClientToolSchemas(currentTools);\n for (const result of toolResults) {\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId: result.toolCallId,\n toolName: result.toolName,\n output: result.output,\n autoContinue: autoContinueAfterToolResult,\n clientTools: clientToolSchemas\n })\n );\n }\n\n // Also update local state via AI SDK for immediate UI feedback\n await Promise.all(\n toolResults.map((result) =>\n addToolResult({\n tool: result.toolName,\n toolCallId: result.toolCallId,\n output: result.output\n })\n )\n );\n\n setClientToolResults((prev) => {\n const newMap = new Map(prev);\n for (const result of toolResults) {\n newMap.set(result.toolCallId, result.output);\n }\n return newMap;\n });\n\n startToolContinuation();\n }\n\n // Note: We don't call sendMessage() here anymore.\n // The server will continue the conversation after applying tool results.\n } finally {\n isResolvingToolsRef.current = false;\n // Trigger a re-run so any tool calls that arrived while we were\n // busy (e.g. from server auto-continuation) get picked up.\n setToolResolutionTrigger((c) => c + 1);\n }\n })();\n }\n }\n }, [\n chatMessages,\n experimental_automaticToolResolution,\n addToolResult,\n toolsRequiringConfirmation,\n autoContinueAfterToolResult,\n startToolContinuation,\n toolResolutionTrigger\n ]);\n\n // Helper function to send tool output to server\n const sendToolOutputToServer = useCallback(\n (\n toolCallId: string,\n toolName: string,\n output: unknown,\n state?: \"output-available\" | \"output-error\",\n errorText?: string\n ) => {\n const shouldAutoContinue =\n state === \"output-error\" ? false : autoContinueAfterToolResult;\n\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId,\n toolName,\n output,\n ...(state ? { state } : {}),\n ...(errorText !== undefined ? { errorText } : {}),\n // output-error is a deliberate client action — don't auto-continue.\n // This differs from addToolApprovalResponse (which auto-continues for\n // both approvals and rejections). To have the LLM respond to the error,\n // call sendMessage() after addToolOutput.\n autoContinue: shouldAutoContinue,\n clientTools: toolsRef.current\n ? extractClientToolSchemas(toolsRef.current)\n : undefined\n })\n );\n\n if (state !== \"output-error\") {\n setClientToolResults((prev) => new Map(prev).set(toolCallId, output));\n }\n\n if (shouldAutoContinue) {\n startToolContinuation();\n }\n },\n [autoContinueAfterToolResult, startToolContinuation]\n );\n\n // Helper function to send tool approval to server\n const sendToolApprovalToServer = useCallback(\n (toolCallId: string, approved: boolean) => {\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_APPROVAL,\n toolCallId,\n approved,\n autoContinue: autoContinueAfterToolResult\n })\n );\n\n if (autoContinueAfterToolResult) {\n startToolContinuation();\n }\n },\n [autoContinueAfterToolResult, startToolContinuation]\n );\n\n // Effect for new onToolCall callback pattern (v6 style)\n // This fires when there are tool calls that need client-side handling\n useEffect(() => {\n const currentOnToolCall = onToolCallRef.current;\n if (!currentOnToolCall) {\n return;\n }\n\n const lastMsg = chatMessages[chatMessages.length - 1];\n if (!lastMsg || lastMsg.role !== \"assistant\") {\n return;\n }\n\n // Find tool calls in input-available state that haven't been processed\n const pendingToolCalls = lastMsg.parts.filter(\n (part) =>\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n !processedToolCalls.current.has(part.toolCallId)\n );\n\n for (const part of pendingToolCalls) {\n if (isToolUIPart(part)) {\n const toolCallId = part.toolCallId;\n const toolName = getToolName(part);\n\n // Mark as processed to prevent re-triggering\n processedToolCalls.current.add(toolCallId);\n\n // track pending `onToolCall` calls to derive streaming state\n setPendingOnToolCallIds((prev) => {\n if (prev.has(toolCallId)) return prev;\n const next = new Set(prev);\n next.add(toolCallId);\n return next;\n });\n\n // Create addToolOutput function for this specific tool call\n const addToolOutput = (opts: AddToolOutputOptions) => {\n sendToolOutputToServer(\n opts.toolCallId,\n toolName,\n opts.output,\n opts.state,\n opts.errorText\n );\n\n // Update local state via AI SDK\n addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output:\n opts.state === \"output-error\"\n ? (opts.errorText ?? \"Tool execution denied by user\")\n : opts.output\n });\n };\n\n // Call the onToolCall callback\n // The callback is responsible for calling addToolOutput when ready\n let result: ReturnType<OnToolCallCallback>;\n try {\n result = currentOnToolCall({\n toolCall: { toolCallId, toolName, input: part.input },\n addToolOutput\n });\n } catch (error) {\n finishOnToolCall(toolCallId);\n throw error;\n }\n void Promise.resolve(result).finally(() => {\n finishOnToolCall(toolCallId);\n });\n }\n }\n }, [chatMessages, sendToolOutputToServer, addToolResult, finishOnToolCall]);\n\n const streamStateRef = useRef<BroadcastStreamState>({ status: \"idle\" });\n\n const [isServerStreaming, setIsServerStreaming] = useState(false);\n // #1620: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Driven\n // by the server's `CF_AGENT_CHAT_RECOVERING` frames; surfaced as a \"working,\n // not frozen\" hint distinct from active streaming.\n const [isRecovering, setIsRecovering] = useState(false);\n\n useEffect(() => {\n const localResponseIds = localResponseMessageIdsRef.current;\n\n /**\n * Unified message handler that parses JSON once and dispatches based on type.\n * Avoids duplicate parsing overhead from separate listeners.\n */\n function onAgentMessage(event: MessageEvent) {\n if (typeof event.data !== \"string\") return;\n\n let data: OutgoingMessage<ChatMessage>;\n try {\n data = JSON.parse(event.data) as OutgoingMessage<ChatMessage>;\n } catch (_error) {\n return;\n }\n\n switch (data.type) {\n case MessageType.CF_AGENT_CHAT_CLEAR:\n // Broadcast-specific resets (cross-tab stream tracking).\n streamStateRef.current = broadcastTransition(streamStateRef.current, {\n type: \"clear\"\n }).state;\n setIsServerStreaming(false);\n setIsRecovering(false);\n // Shared local-state reset — see `resetLocalChatState`.\n resetLocalChatState();\n break;\n\n case MessageType.CF_AGENT_CHAT_RECOVERING:\n // Durable recovery progress hint (#1620): show \"recovering…\" while a\n // turn is being resumed. Cleared by the server's `recovering: false`\n // frame on any terminal outcome (and locally on stream-resume /\n // terminal response / clear below, for a snappy handoff).\n setIsRecovering(Boolean(data.recovering));\n break;\n\n case MessageType.CF_AGENT_CHAT_MESSAGES: {\n let next = preserveProtectedStreamingAssistant(data.messages);\n // A cross-tab observer builds the in-flight assistant via the\n // broadcast accumulator, not the local transport — so\n // `protectedStreamingAssistantRef` is never armed for it. Without\n // this, a behind-the-stream snapshot would replace the observed\n // assistant's streamed parts until the next chunk re-merges them\n // (the same disappear/reappear the originating tab gets without the\n // start-chunk re-arm above). Re-apply the accumulator — it adopted\n // the server id from the `start` chunk, so `mergeInto` replaces the\n // snapshot's copy in place (or appends a not-yet-persisted turn).\n const observed = streamStateRef.current;\n if (\n observed.status === \"observing\" &&\n observed.accumulator.parts.length > 0\n ) {\n // Only re-apply the live accumulator when it is at least as\n // complete as the snapshot's copy of the same message. A fresh\n // observer rebuilding its accumulator from a chunk-0 replay can\n // briefly trail a fully-persisted snapshot; merging then would drop\n // parts until replay catches up. In steady-state live observing the\n // accumulator is always at or ahead of the snapshot, so this still\n // fixes the disappear/reappear flicker.\n const snapshotIdx = next.findIndex(\n (m) => m.id === observed.accumulator.messageId\n );\n const snapshotParts =\n snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;\n if (observed.accumulator.parts.length >= snapshotParts) {\n next = observed.accumulator.mergeInto(next) as ChatMessage[];\n }\n }\n setMessages(next);\n break;\n }\n\n case MessageType.CF_AGENT_MESSAGE_UPDATED:\n // Server updated a message (e.g., applied tool result)\n // Update the specific message in local state\n setMessages((prevMessages: ChatMessage[]) => {\n const updatedMessage = data.message;\n\n // First try to find by message ID\n let idx = prevMessages.findIndex((m) => m.id === updatedMessage.id);\n\n // If not found by ID, try to find by toolCallId\n // This handles the case where client has AI SDK-generated IDs\n // but server has server-generated IDs\n if (idx < 0) {\n const updatedToolCallIds = new Set(\n updatedMessage.parts\n .filter(\n (p: ChatMessage[\"parts\"][number]) =>\n \"toolCallId\" in p && p.toolCallId\n )\n .map(\n (p: ChatMessage[\"parts\"][number]) =>\n (p as { toolCallId: string }).toolCallId\n )\n );\n\n if (updatedToolCallIds.size > 0) {\n idx = prevMessages.findIndex((m) =>\n m.parts.some(\n (p) =>\n \"toolCallId\" in p &&\n updatedToolCallIds.has(\n (p as { toolCallId: string }).toolCallId\n )\n )\n );\n }\n }\n\n if (idx >= 0) {\n const updated = [...prevMessages];\n // Preserve the client's message ID but update the content\n updated[idx] = {\n ...updatedMessage,\n id: prevMessages[idx].id\n };\n return updated;\n }\n // Message not found — don't append. CF_AGENT_MESSAGE_UPDATED is\n // for updating existing messages (e.g. tool result/approval state\n // changes), not for adding new ones. If the message isn't in\n // client state yet, it will arrive via the transport stream\n // (same tab) or CF_AGENT_CHAT_MESSAGES (cross-tab).\n // Appending here causes temporary duplicates (#1094).\n return prevMessages;\n });\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUME_NONE:\n // Server confirmed no active stream — let the transport\n // resolve reconnectToStream immediately with null.\n customTransport.handleStreamResumeNone();\n break;\n\n case MessageType.CF_AGENT_STREAM_PENDING:\n // Server accepted a turn but its stream hasn't started yet (#1784):\n // tell the transport to keep waiting so its resume probe doesn't\n // resolve \"no stream\" mid pre-stream window. A later STREAM_RESUMING\n // (stream started) or STREAM_RESUME_NONE (settled without streaming)\n // resolves it. Advisory for clients not awaiting a resume.\n customTransport.handleStreamPending();\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUMING: {\n const isEarlyToolContinuation =\n resumingToolContinuationRef.current &&\n !customTransport.isAwaitingResume();\n if (!resume && !customTransport.isAwaitingResume()) {\n if (!isEarlyToolContinuation) return;\n }\n if (!resumingToolContinuationRef.current) {\n pendingReplayResumeRequestIdsRef.current.add(data.id);\n }\n // Let the transport handle it if reconnectToStream is waiting.\n // This is called synchronously — no addEventListener race.\n // The transport sends ACK, adds to activeRequestIds, and\n // creates the ReadableStream that feeds into useChat's pipeline\n // (which correctly sets status to \"streaming\"). This runs BEFORE\n // the fallback-ACK dedupe below so a fallback-observed stream can\n // still become transport-owned via a later resumeStream() — the\n // transport's replay is isolated from the broadcast accumulator\n // by localRequestIdsRef, so it cannot duplicate parts.\n if (customTransport.handleStreamResuming(data)) {\n return;\n }\n // Skip if the transport already handled this stream's resume\n // (server sends STREAM_RESUMING from both onConnect and the\n // RESUME_REQUEST handler — the second one must not trigger\n // a duplicate ACK / replay).\n if (localRequestIdsRef.current.has(data.id)) return;\n // Duplicate offer for a stream this socket already ACKed via the\n // fallback path (#1733): the server notifies from both onConnect\n // and the RESUME_REQUEST handler. With nobody waiting on the\n // handshake, re-ACKing would only trigger a second full-buffer\n // replay into the same accumulator; drop it.\n if (fallbackAckedResumeRequestIdsRef.current.has(data.id)) return;\n if (isEarlyToolContinuation) {\n pendingToolContinuationRef.current = false;\n observedToolContinuationRequestIdRef.current = data.id;\n if (continuationLaunchTimerRef.current) {\n clearTimeout(continuationLaunchTimerRef.current);\n continuationLaunchTimerRef.current = null;\n }\n }\n // Fallback for cross-tab broadcasts or cases where the\n // transport isn't expecting a resume.\n streamStateRef.current = broadcastTransition(streamStateRef.current, {\n type: \"resume-fallback\",\n streamId: data.id,\n messageId: nanoid()\n }).state;\n customTransport.observeServerTurn(data.id);\n setIsServerStreaming(true);\n // The recovered turn is now streaming live to us — it's no longer\n // \"recovering\", it's producing the answer (#1620).\n setIsRecovering(false);\n // Remember the ACK so a duplicate STREAM_RESUMING for the same\n // request on this socket doesn't trigger a second replay (#1733).\n fallbackAckedResumeRequestIdsRef.current.add(data.id);\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: data.id\n })\n );\n break;\n }\n\n case MessageType.CF_AGENT_USE_CHAT_RESPONSE: {\n if (localRequestIdsRef.current.has(data.id)) {\n if (data.body?.trim()) {\n try {\n const chunkData = JSON.parse(data.body) as {\n messageId?: string;\n type?: string;\n };\n if (\n chunkData.type === \"start\" &&\n typeof chunkData.messageId === \"string\"\n ) {\n localResponseIds.set(data.id, chunkData.messageId);\n // Re-arm streaming protection to the ACTUAL assistant id for\n // this turn. `protectStreamingAssistantTail` runs at send\n // time — before the assistant message is minted — so it can\n // only latch the PREVIOUS turn's id (or nothing on the first\n // turn). Without correcting it here, a mid-stream full-list\n // broadcast (`CF_AGENT_CHAT_MESSAGES`, which Think emits after\n // every tool result) replaces the live-streamed assistant\n // with a possibly-behind server snapshot, so its parts (e.g.\n // tool cards) briefly disappear and reappear. Continuations\n // are skipped: they extend the existing protected assistant.\n if (!data.continuation) {\n const protection = protectedStreamingAssistantRef.current;\n if (protection?.assistantId !== chunkData.messageId) {\n const msgs = messagesRef.current;\n const idx = msgs.findIndex(\n (m) => m.id === chunkData.messageId\n );\n const anchorMessageId =\n idx >= 0\n ? (msgs[idx - 1]?.id ?? null)\n : (msgs[msgs.length - 1]?.id ?? null);\n protectedStreamingAssistantRef.current = {\n assistantId: chunkData.messageId,\n anchorMessageId\n };\n }\n }\n // EVERY replayed `start` rebuilds the message from chunk 0,\n // so the matching trailing assistant must be reset each\n // time — not only while the resume request id is still\n // pending (#1733: a second replay otherwise stacks a\n // duplicate text part). Continuation replays are excluded:\n // they append to the existing assistant message, and\n // wiping it would drop the pre-continuation parts.\n if (\n data.replay &&\n !data.continuation &&\n !resumingToolContinuationRef.current &&\n observedToolContinuationRequestIdRef.current !== data.id\n ) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n resetMatchingHydratedAssistantForReplay(\n chunkData.messageId\n );\n }\n }\n } catch {\n // Ignore malformed local stream chunks.\n }\n }\n\n if (data.done || data.replayComplete) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n }\n if (data.done) {\n if (\n streamStateRef.current.status === \"observing\" &&\n streamStateRef.current.streamId === data.id\n ) {\n streamStateRef.current = { status: \"idle\" };\n setIsServerStreaming(false);\n }\n customTransport.handleServerTurnCompleted(data.id);\n restoreProtectedStreamingAssistant(localResponseIds.get(data.id));\n localResponseIds.delete(data.id);\n localRequestIdsRef.current.delete(data.id);\n fallbackAckedResumeRequestIdsRef.current.delete(data.id);\n }\n return;\n }\n\n let chunkData: unknown;\n if (\n data.replay &&\n streamStateRef.current.status !== \"observing\" &&\n !pendingReplayResumeRequestIdsRef.current.has(data.id)\n ) {\n return;\n }\n if (data.body?.trim()) {\n try {\n chunkData = JSON.parse(data.body);\n // Reset on EVERY replayed `start` (not only while the resume\n // request id is pending): replay rebuilds from chunk 0, so a\n // second replay whose `start` skipped the reset would stack a\n // duplicate text part on the frozen first one (#1733).\n // Continuation replays are excluded — they append to the\n // existing assistant message, and wiping it would drop the\n // pre-continuation parts.\n if (\n data.replay &&\n !data.continuation &&\n !resumingToolContinuationRef.current &&\n observedToolContinuationRequestIdRef.current !== data.id &&\n typeof (chunkData as Record<string, unknown>).messageId ===\n \"string\" &&\n (chunkData as Record<string, unknown>).type === \"start\"\n ) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n resetMatchingHydratedAssistantForReplay(\n (chunkData as { messageId: string }).messageId\n );\n }\n if (\n typeof (chunkData as Record<string, unknown>).type ===\n \"string\" &&\n (\n (chunkData as Record<string, unknown>).type as string\n ).startsWith(\"data-\") &&\n onDataRef.current\n ) {\n onDataRef.current(\n chunkData as Parameters<\n NonNullable<typeof onDataRef.current>\n >[0]\n );\n }\n } catch (parseError) {\n console.warn(\n \"[useAgentChat] Failed to parse stream chunk:\",\n parseError instanceof Error ? parseError.message : parseError,\n \"body:\",\n data.body?.slice(0, 100)\n );\n }\n }\n if (data.done || data.replayComplete) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n }\n if (data.done) {\n customTransport.handleServerTurnCompleted(data.id);\n fallbackAckedResumeRequestIdsRef.current.delete(data.id);\n // A terminal turn outcome resolves any in-progress recovery (#1620).\n setIsRecovering(false);\n }\n const completedObservedToolContinuation =\n data.done &&\n observedToolContinuationRequestIdRef.current === data.id;\n\n const result = broadcastTransition(streamStateRef.current, {\n type: \"response\",\n streamId: data.id,\n messageId: nanoid(),\n chunkData,\n done: data.done,\n error: data.error,\n replay: data.replay,\n replayComplete: data.replayComplete,\n continuation: data.continuation,\n currentMessages: data.continuation ? messagesRef.current : undefined\n });\n\n streamStateRef.current = result.state;\n if (result.messagesUpdate) {\n setMessages(\n result.messagesUpdate as unknown as (\n prev: ChatMessage[]\n ) => ChatMessage[]\n );\n }\n setIsServerStreaming(result.isStreaming);\n if (completedObservedToolContinuation) {\n resetToolContinuation();\n }\n break;\n }\n }\n }\n\n const fallbackAckedResumeRequestIds =\n fallbackAckedResumeRequestIdsRef.current;\n\n // A closed socket invalidates the per-socket resume-ACK dedupe: after a\n // reconnect the server sees a brand-new connection and must be ACKed\n // (and replay) again, so the previous entries must not suppress it.\n function onAgentClose() {\n fallbackAckedResumeRequestIds.clear();\n }\n\n // Reconnect re-probe (#1784): a transparent socket reconnect (e.g. a 1006\n // drop) does NOT remount the component, so the AI SDK's mount-time resume\n // never re-fires and a turn that was still pre-stream when the socket\n // dropped would leave AI SDK `status` stuck at \"ready\" even after the\n // server starts streaming. On every reconnect (not the first open) re-probe\n // through the transport so `status` recovers, mirroring a fresh mount. The\n // proactive STREAM_RESUMING the server also sends on connect is reconciled\n // with this probe by the existing #1733 dual-notify dedupe.\n function onAgentOpen() {\n if (!hasConnectedOnceRef.current) {\n hasConnectedOnceRef.current = true;\n return;\n }\n if (\n !resume ||\n statusRef.current !== \"ready\" ||\n resumingToolContinuationRef.current ||\n customTransport.isAwaitingResume()\n ) {\n return;\n }\n void Promise.resolve(resumeStreamRef.current?.()).catch(() => {});\n }\n\n agent.addEventListener(\"message\", onAgentMessage);\n agent.addEventListener(\"close\", onAgentClose);\n agent.addEventListener(\"open\", onAgentOpen);\n\n // Stream resume is now primarily handled by the transport's\n // reconnectToStream (which sends CF_AGENT_STREAM_RESUME_REQUEST).\n // The onAgentMessage handler above serves as fallback for cross-tab\n // broadcasts and cases where the transport didn't handle the resume.\n\n return () => {\n agent.removeEventListener(\"message\", onAgentMessage);\n agent.removeEventListener(\"close\", onAgentClose);\n agent.removeEventListener(\"open\", onAgentOpen);\n fallbackAckedResumeRequestIds.clear();\n streamStateRef.current = { status: \"idle\" };\n setIsServerStreaming(false);\n setIsRecovering(false);\n protectedStreamingAssistantRef.current = null;\n localResponseIds.clear();\n };\n }, [\n agent,\n setMessages,\n resume,\n customTransport,\n preserveProtectedStreamingAssistant,\n resetToolContinuation,\n resetMatchingHydratedAssistantForReplay,\n restoreProtectedStreamingAssistant,\n resetLocalChatState\n ]);\n\n // ── DEPRECATED: addToolResult wrapper with confirmation batching ────\n // This wrapper is deprecated. Use addToolOutput or addToolApprovalResponse instead.\n const addToolResultAndSendMessage: typeof addToolResult = async (args) => {\n const { toolCallId } = args;\n const toolName = \"tool\" in args ? args.tool : \"\";\n const output = \"output\" in args ? args.output : undefined;\n\n // Send tool result to server (server is source of truth)\n // Include flag to tell server whether to auto-continue\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId,\n toolName,\n output,\n autoContinue: autoContinueAfterToolResult,\n clientTools: toolsRef.current\n ? extractClientToolSchemas(toolsRef.current)\n : undefined\n })\n );\n\n setClientToolResults((prev) => new Map(prev).set(toolCallId, output));\n\n // Call AI SDK's addToolResult for local state update (non-blocking)\n // We don't await this since clientToolResults provides immediate UI feedback\n addToolResult(args);\n\n if (autoContinueAfterToolResult) {\n startToolContinuation();\n }\n\n // If server auto-continuation is disabled, client needs to trigger continuation\n if (!autoContinueAfterToolResult) {\n // Use legacy behavior: batch confirmations or send immediately\n if (!autoSendAfterAllConfirmationsResolved) {\n // Always send immediately\n sendMessage();\n return;\n }\n\n // Wait for all confirmations before sending\n const pending = pendingConfirmationsRef.current?.toolCallIds;\n if (!pending) {\n sendMessage();\n return;\n }\n\n const wasLast = pending.size === 1 && pending.has(toolCallId);\n if (pending.has(toolCallId)) {\n pending.delete(toolCallId);\n }\n\n if (wasLast || pending.size === 0) {\n sendMessage();\n }\n }\n // If autoContinueAfterToolResult is true, server handles continuation\n };\n\n // Wrapper that sends tool approval to server before updating local state.\n // This prevents duplicate messages by ensuring server updates the message\n // in place with the existing ID, rather than relying on ID resolution\n // when sendMessage() is called later.\n const addToolApprovalResponseAndNotifyServer: typeof addToolApprovalResponse =\n (args) => {\n const { id: approvalId, approved } = args;\n\n // Find the toolCallId from the approval ID\n // The approval ID is stored on the tool part's approval.id field\n let toolCallId: string | undefined;\n for (const msg of messagesRef.current) {\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"approval\" in part &&\n (part.approval as { id?: string })?.id === approvalId\n ) {\n toolCallId = part.toolCallId as string;\n break;\n }\n }\n if (toolCallId) break;\n }\n\n if (toolCallId) {\n // Send approval to server first (server updates message in place)\n sendToolApprovalToServer(toolCallId, approved);\n } else {\n console.warn(\n `[useAgentChat] addToolApprovalResponse: Could not find toolCallId for approval ID \"${approvalId}\". ` +\n \"Server will not be notified, which may cause duplicate messages.\"\n );\n }\n\n // Call AI SDK's addToolApprovalResponse for local state update\n addToolApprovalResponse(args);\n };\n\n // Fix for issue #728: Merge client-side tool results with messages\n // so tool parts show output-available immediately after execution\n const messagesWithToolResults = useMemo(() => {\n if (clientToolResults.size === 0) {\n return chatMessages;\n }\n return chatMessages.map((msg) => ({\n ...msg,\n parts: msg.parts.map((p) => {\n if (\n !(\"toolCallId\" in p) ||\n !(\"state\" in p) ||\n p.state !== \"input-available\" ||\n !clientToolResults.has(p.toolCallId)\n ) {\n return p;\n }\n return {\n ...p,\n state: \"output-available\" as const,\n output: clientToolResults.get(p.toolCallId)\n };\n })\n })) as ChatMessage[];\n }, [chatMessages, clientToolResults]);\n\n // Cleanup stale entries from clientToolResults when messages change\n // to prevent memory leak in long conversations.\n // Note: We intentionally exclude clientToolResults from deps to avoid infinite loops.\n // The functional update form gives us access to the previous state.\n useEffect(() => {\n // Collect all current toolCallIds from messages\n const currentToolCallIds = new Set<string>();\n for (const msg of chatMessages) {\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n currentToolCallIds.add(part.toolCallId);\n }\n }\n }\n\n // Use functional update to check and clean stale entries atomically\n setClientToolResults((prev) => {\n if (prev.size === 0) return prev;\n\n // Check if any entries are stale\n let hasStaleEntries = false;\n for (const toolCallId of prev.keys()) {\n if (!currentToolCallIds.has(toolCallId)) {\n hasStaleEntries = true;\n break;\n }\n }\n\n // Only create new Map if there are stale entries to remove\n if (!hasStaleEntries) return prev;\n\n const newMap = new Map<string, unknown>();\n for (const [id, output] of prev) {\n if (currentToolCallIds.has(id)) {\n newMap.set(id, output);\n }\n }\n return newMap;\n });\n\n // Also cleanup processedToolCalls to prevent issues in long conversations\n for (const toolCallId of processedToolCalls.current) {\n if (!currentToolCallIds.has(toolCallId)) {\n processedToolCalls.current.delete(toolCallId);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [chatMessages]);\n\n // Create addToolOutput function for external use\n const addToolOutput = useCallback(\n (opts: AddToolOutputOptions) => {\n const toolName = opts.toolName ?? \"\";\n sendToolOutputToServer(\n opts.toolCallId,\n toolName,\n opts.output,\n opts.state,\n opts.errorText\n );\n\n // Update local state via AI SDK\n addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output:\n opts.state === \"output-error\"\n ? (opts.errorText ?? \"Tool execution denied by user\")\n : opts.output\n });\n },\n [sendToolOutputToServer, addToolResult]\n );\n\n // Derive whether there are unresolved client-side tool calls on the\n // latest assistant message. The AI SDK's `streamText` on the server\n // ends the stream as soon as it emits a tool-call the server can't\n // execute, which drops `status` back to \"ready\" while the client's\n // async `onToolCall` handler is still running. Without this signal,\n // consumers see a blank \"nothing happening\" window for the full\n // duration of the client-side work — often a `fetch` taking seconds.\n //\n // We scope this to tool calls that have actual client-side work in flight:\n // - an `onToolCall` callback promise is still pending, OR\n // - a matching entry in the deprecated `tools` option has `execute`.\n // Tools waiting on explicit user confirmation are excluded — nothing\n // is happening until the user acts, so the \"busy\" indicator would be\n // misleading.\n //\n // Derivation (not a counter / not effect-tracked) so that the flag\n // self-heals as soon as the tool part transitions to `output-available`\n // via `addToolOutput` → `addToolResult`, or to any other terminal\n // state via a server-pushed message update.\n const lastAssistantMessage =\n messagesWithToolResults[messagesWithToolResults.length - 1];\n const hasPendingClientToolCalls = (() => {\n if (pendingOnToolCallIds.size === 0 && !tools) return false;\n if (!lastAssistantMessage || lastAssistantMessage.role !== \"assistant\") {\n return false;\n }\n for (const part of lastAssistantMessage.parts) {\n if (!isToolUIPart(part)) continue;\n if (part.state !== \"input-available\") continue;\n const toolName = getToolName(part);\n if (toolsRequiringConfirmation.includes(toolName)) continue;\n if (pendingOnToolCallIds.has(part.toolCallId)) return true;\n if (tools?.[toolName]?.execute) return true;\n }\n return false;\n })();\n\n const effectiveIsServerStreaming =\n isServerStreaming || hasPendingClientToolCalls;\n const isStreaming = status === \"streaming\" || effectiveIsServerStreaming;\n\n return {\n ...useChatHelpers,\n messages: messagesWithToolResults,\n isServerStreaming: effectiveIsServerStreaming,\n isStreaming,\n /**\n * True while a durable chat turn is being recovered (interrupted by a\n * deploy/eviction or a stream-stall watchdog abort and now resuming, #1620).\n * Distinct from `isStreaming` — a recovering turn isn't producing tokens\n * yet. Render a \"recovering…\" hint; most UIs treat `isStreaming ||\n * isRecovering` as \"busy\". Cleared automatically on the next stream/terminal.\n */\n isRecovering,\n isToolContinuation,\n connectionError: agent.connectionError ?? null,\n sendMessage: sendMessageWithStreamingProtection,\n stop: stopWithToolContinuationAbort,\n /**\n * Provide output for a tool call. Use this for tools that require user interaction\n * or client-side execution.\n */\n addToolOutput,\n /**\n * @deprecated Use `addToolOutput` instead.\n */\n addToolResult: addToolResultAndSendMessage,\n /**\n * Respond to a tool approval request. Use this for tools with `needsApproval`.\n * This wrapper notifies the server before updating local state, preventing\n * duplicate messages when sendMessage() is called afterward.\n */\n addToolApprovalResponse: addToolApprovalResponseAndNotifyServer,\n clearHistory: () => {\n resetLocalChatState();\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_CLEAR\n })\n );\n },\n setMessages: (messagesOrUpdater: Parameters<typeof setMessages>[0]) => {\n // Resolve functional updaters to get the actual messages array\n // before syncing to server. Without this, updater functions would\n // send an empty array and wipe server-side messages.\n let resolvedMessages: ChatMessage[];\n if (typeof messagesOrUpdater === \"function\") {\n resolvedMessages = messagesOrUpdater(messagesRef.current);\n } else {\n resolvedMessages = messagesOrUpdater;\n }\n\n if (resolvedMessages.length === 0) {\n markInitialMessagesSeeded();\n }\n setMessages(resolvedMessages);\n if (syncMessagesToServer) {\n agent.send(\n JSON.stringify({\n messages: resolvedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n })\n );\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.js","names":["broadcastTransition"],"sources":["../../src/chat/ws-chat-transport.ts","../../src/chat/react.tsx"],"sourcesContent":["/**\n * WebSocket-based ChatTransport for useAgentChat.\n *\n * Replaces the aiFetch + DefaultChatTransport indirection with a direct\n * WebSocket implementation that speaks the CF_AGENT protocol natively.\n *\n * Data flow (old): WS → aiFetch fake Response → DefaultChatTransport → useChat\n * Data flow (new): WS → WebSocketChatTransport → useChat\n */\n\nimport type { ChatTransport, UIMessage, UIMessageChunk } from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { MessageType, type OutgoingMessage } from \"./wire-types\";\n\n/**\n * Short safety-net timeout for a resume probe when the server has said nothing.\n * Under normal operation the server answers a `STREAM_RESUME_REQUEST` with\n * `STREAM_RESUMING`, `STREAM_RESUME_NONE`, or `STREAM_PENDING` well before this.\n */\nconst RESUME_PROBE_TIMEOUT_MS = 5000;\n\n/**\n * Extended backstop applied once the server says a turn is pending\n * (`STREAM_PENDING`, #1784). The pre-stream window (queueing, MCP setup,\n * debounce, model latency) can exceed the short probe timeout, and the server\n * guarantees a follow-up `STREAM_RESUMING` or `STREAM_RESUME_NONE` — so we wait\n * much longer (refreshed on every keep-waiting frame) but still cap it so a\n * dropped follow-up degrades to a null resolve instead of hanging forever.\n */\nconst RESUME_PENDING_TIMEOUT_MS = 60000;\n\n/**\n * Agent-like interface for sending/receiving WebSocket messages.\n * Matches the shape returned by useAgent from agents/react.\n */\nexport interface AgentConnection {\n send: (data: string) => void;\n addEventListener: (\n type: string,\n listener: (event: MessageEvent) => void,\n options?: { signal?: AbortSignal }\n ) => void;\n removeEventListener: (\n type: string,\n listener: (event: MessageEvent) => void\n ) => void;\n}\n\nexport type WebSocketChatTransportOptions<\n ChatMessage extends UIMessage = UIMessage\n> = {\n /** The agent connection from useAgent */\n agent: AgentConnection;\n /**\n * Callback to prepare the request body before sending.\n * Can add custom headers, body fields, or credentials.\n */\n prepareBody?: (options: {\n messages: ChatMessage[];\n trigger: \"submit-message\" | \"regenerate-message\";\n messageId?: string;\n }) => Promise<Record<string, unknown>> | Record<string, unknown>;\n /**\n * Optional set to track active request IDs.\n * IDs are added when a request starts and removed when it completes.\n * Used by the onAgentMessage handler to skip messages already handled by the transport.\n */\n activeRequestIds?: Set<string>;\n /**\n * Whether generic client-side abort/cancel lifecycle should cancel the\n * server turn. Explicit cancellation via cancelActiveServerTurn() always\n * sends CF_AGENT_CHAT_REQUEST_CANCEL.\n * @default false\n */\n cancelOnClientAbort?: boolean;\n};\n\n/**\n * ChatTransport that sends messages over WebSocket and returns a\n * ReadableStream<UIMessageChunk> that the AI SDK's useChat consumes directly.\n * No fake fetch, no Response reconstruction, no double SSE parsing.\n */\nexport class WebSocketChatTransport<\n ChatMessage extends UIMessage = UIMessage\n> implements ChatTransport<ChatMessage> {\n agent: AgentConnection;\n private prepareBody?: WebSocketChatTransportOptions<ChatMessage>[\"prepareBody\"];\n private activeRequestIds?: Set<string>;\n private cancelOnClientAbort: boolean;\n\n // Pending resume resolver — set by reconnectToStream, called by\n // handleStreamResuming when onAgentMessage sees CF_AGENT_STREAM_RESUMING.\n private _resumeResolver: ((data: { id: string }) => void) | null = null;\n // Pending \"no stream\" resolver — called by handleStreamResumeNone\n // when onAgentMessage sees CF_AGENT_STREAM_RESUME_NONE.\n private _resumeNoneResolver: (() => void) | null = null;\n // Keep-waiting hook (#1784) — set by whichever resume path is currently\n // awaiting, called by handleStreamPending when onAgentMessage sees\n // CF_AGENT_STREAM_PENDING. Extends the path's probe timeout so a slow\n // pre-stream window (queue / MCP / model latency) does not resolve early.\n private _onStreamPending: (() => void) | null = null;\n // Set when a client-side tool result/approval is expected to trigger\n // a new continuation stream. In this mode reconnectToStream() returns\n // a deferred ReadableStream immediately so AI SDK status can transition\n // to \"submitted\" before the server starts streaming.\n private _expectToolContinuation = false;\n private _abortToolContinuation: (() => boolean) | null = null;\n private _activeServerTurnId: string | null = null;\n private _cancelAttachedStream: (() => boolean) | null = null;\n\n constructor(options: WebSocketChatTransportOptions<ChatMessage>) {\n this.agent = options.agent;\n this.prepareBody = options.prepareBody;\n this.activeRequestIds = options.activeRequestIds;\n this.cancelOnClientAbort = options.cancelOnClientAbort ?? false;\n }\n\n setCancelOnClientAbort(cancelOnClientAbort: boolean) {\n this.cancelOnClientAbort = cancelOnClientAbort;\n }\n\n /**\n * Explicitly cancel the active server turn, if any.\n * This is separate from generic client-side abort/cancel lifecycle so\n * clients can detach locally without stopping server work.\n */\n cancelActiveServerTurn(): boolean {\n const requestId = this._activeServerTurnId;\n let cancelledRequest = false;\n\n if (requestId) {\n this.sendCancelFrame(requestId);\n this._cancelAttachedStream?.();\n this.clearActiveServerTurn(requestId);\n cancelledRequest = true;\n }\n\n const cancelledToolContinuation = this.abortActiveToolContinuation();\n return cancelledRequest || cancelledToolContinuation;\n }\n\n private sendCancelFrame(requestId: string) {\n try {\n this.agent.send(\n JSON.stringify({\n id: requestId,\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL\n })\n );\n } catch {\n // Ignore failures (e.g. agent already disconnected)\n }\n }\n\n private setActiveServerTurn(\n requestId: string,\n cancelAttachedStream: (() => boolean) | null\n ) {\n this._activeServerTurnId = requestId;\n this._cancelAttachedStream = cancelAttachedStream;\n }\n\n private clearActiveServerTurn(requestId: string) {\n if (this._activeServerTurnId === requestId) {\n this._activeServerTurnId = null;\n this._cancelAttachedStream = null;\n }\n }\n\n /**\n * Mark that the next reconnectToStream() call should attach to a\n * server-initiated tool continuation rather than a page-load resume.\n */\n expectToolContinuation() {\n this._expectToolContinuation = true;\n }\n\n /**\n * Abort the active client-side tool continuation stream, if one is attached\n * to a server request id.\n */\n abortActiveToolContinuation(): boolean {\n return this._abortToolContinuation?.() ?? false;\n }\n\n /**\n * True when the transport is waiting for a resume handshake.\n */\n isAwaitingResume(): boolean {\n return this._resumeResolver !== null || this._resumeNoneResolver !== null;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUMING.\n * If reconnectToStream is waiting, this handles the resume handshake\n * (ACK + stream creation) and returns true. Otherwise returns false\n * so the caller can use its own fallback path.\n */\n handleStreamResuming(data: { id: string }): boolean {\n if (!this._resumeResolver) return false;\n this._resumeResolver(data);\n return true;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_RESUME_NONE.\n * If reconnectToStream is waiting, resolves the promise with null\n * immediately (no 5-second timeout). Returns true if handled.\n */\n handleStreamResumeNone(): boolean {\n if (!this._resumeNoneResolver) return false;\n this._resumeNoneResolver();\n return true;\n }\n\n /**\n * Called by onAgentMessage when it receives CF_AGENT_STREAM_PENDING (#1784):\n * the server accepted a turn but its stream has not started yet. If a resume\n * path is awaiting, extend its probe timeout (so it keeps waiting for the\n * eventual STREAM_RESUMING / STREAM_RESUME_NONE instead of resolving null\n * after the short window). Returns true if a waiting path consumed it.\n */\n handleStreamPending(): boolean {\n if (!this._onStreamPending) return false;\n this._onStreamPending();\n return true;\n }\n\n /**\n * Called by the hook's shared message handler when a server turn finishes\n * outside the currently attached transport stream, such as after local-only\n * client cleanup.\n */\n handleServerTurnCompleted(requestId: string) {\n this.clearActiveServerTurn(requestId);\n }\n\n /**\n * Register a server turn that is being rendered outside a transport-owned\n * stream, such as the hook's fallback cross-tab/resume observer path.\n */\n observeServerTurn(requestId: string) {\n this.setActiveServerTurn(requestId, null);\n }\n\n async sendMessages(options: {\n chatId: string;\n messages: ChatMessage[];\n abortSignal: AbortSignal | undefined;\n trigger: \"submit-message\" | \"regenerate-message\";\n messageId?: string;\n body?: object;\n headers?: Record<string, string> | Headers;\n metadata?: unknown;\n }): Promise<ReadableStream<UIMessageChunk>> {\n const requestId = nanoid(8);\n const abortController = new AbortController();\n let completed = false;\n let requestSent = false;\n\n // Build the request body\n let extraBody: Record<string, unknown> = {};\n if (this.prepareBody) {\n extraBody = await this.prepareBody({\n messages: options.messages,\n trigger: options.trigger,\n messageId: options.messageId\n });\n }\n if (options.body) {\n extraBody = {\n ...extraBody,\n ...(options.body as Record<string, unknown>)\n };\n }\n\n const bodyPayload = JSON.stringify({\n messages: options.messages,\n trigger: options.trigger,\n ...extraBody\n });\n\n // Track this request so the onAgentMessage handler skips it\n this.activeRequestIds?.add(requestId);\n\n // Create a ReadableStream<UIMessageChunk> that emits parsed chunks\n // as they arrive over the WebSocket\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n\n // Single cleanup helper — every terminal path (done, error, abort)\n // goes through here exactly once.\n // keepId: when true, do NOT remove requestId from activeIds. Used by\n // explicit cancellation so onAgentMessage skips in-flight chunks\n // and the server's final done:true signal until cleanup happens there.\n const finish = (\n action: () => void,\n keepId = false,\n clearServerTurn = true\n ) => {\n if (completed) return;\n completed = true;\n if (clearServerTurn) {\n this.clearActiveServerTurn(requestId);\n }\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (!keepId) {\n activeIds?.delete(requestId);\n }\n abortController.abort();\n };\n\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n\n const cancelActiveRequest = () => {\n if (completed) return false;\n finish(() => streamController.error(abortError), true);\n return true;\n };\n this.setActiveServerTurn(requestId, cancelActiveRequest);\n\n // Abort handler: terminate the local stream. By default, generic AI SDK\n // abort/cancel lifecycle is local-only so durable server turns can continue\n // and be resumed. Use cancelActiveServerTurn() for explicit user/app\n // cancellation, or cancelOnClientAbort for request-lifetime semantics.\n const onAbort = () => {\n if (completed) return;\n if (this.cancelOnClientAbort) {\n if (requestSent) {\n this.sendCancelFrame(requestId);\n }\n finish(() => streamController.error(abortError), requestSent);\n } else {\n finish(() => streamController.error(abortError), false, !requestSent);\n }\n };\n\n // streamController is assigned synchronously by start(), so it is\n // always available by the time onAbort or onMessage can fire.\n let streamController!: ReadableStreamDefaultController<UIMessageChunk>;\n\n const stream = new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<ChatMessage>;\n\n if (data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE) return;\n if (data.id !== requestId) return;\n\n if (data.error) {\n finish(() =>\n controller.error(new Error(data.body || \"Stream error\"))\n );\n return;\n }\n\n // Parse the body as UIMessageChunk and enqueue\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close());\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n finish(() => controller.close(), false, false);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: abortController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: abortController.signal\n });\n },\n cancel() {\n onAbort();\n }\n });\n\n // Handle abort from the caller\n if (options.abortSignal) {\n options.abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n if (options.abortSignal.aborted) onAbort();\n }\n\n if (completed) {\n return stream;\n }\n\n // Send the request over WebSocket\n requestSent = true;\n agent.send(\n JSON.stringify({\n id: requestId,\n init: {\n method: \"POST\",\n body: bodyPayload\n },\n type: MessageType.CF_AGENT_USE_CHAT_REQUEST\n })\n );\n\n return stream;\n }\n\n async reconnectToStream(_options: {\n chatId: string;\n }): Promise<ReadableStream<UIMessageChunk> | null> {\n if (this._expectToolContinuation) {\n this._expectToolContinuation = false;\n return this._createToolContinuationStream();\n }\n\n // Detect whether the server has an active stream for this chat.\n // Instead of registering our own addEventListener listener (which\n // races with onAgentMessage), we set _resumeResolver so that\n // onAgentMessage can call handleStreamResuming() synchronously\n // when it sees CF_AGENT_STREAM_RESUMING — eliminating the race.\n const activeIds = this.activeRequestIds;\n\n return new Promise<ReadableStream<UIMessageChunk> | null>((resolve) => {\n let resolved = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const done = (value: ReadableStream<UIMessageChunk> | null) => {\n if (resolved) return;\n resolved = true;\n this._resumeResolver = null;\n this._resumeNoneResolver = null;\n this._onStreamPending = null;\n if (timeout) clearTimeout(timeout);\n resolve(value);\n };\n\n // Keep-waiting (#1784): the server says a turn is accepted but its stream\n // hasn't started. Extend the short probe timeout to the longer backstop so\n // we wait for the eventual STREAM_RESUMING (or STREAM_RESUME_NONE) instead\n // of resolving null mid pre-stream window. Refreshed on every frame.\n this._onStreamPending = () => {\n if (resolved) return;\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(() => done(null), RESUME_PENDING_TIMEOUT_MS);\n };\n\n // Set the \"no stream\" resolver that handleStreamResumeNone() will call.\n // When onAgentMessage sees CF_AGENT_STREAM_RESUME_NONE, it calls\n // handleStreamResumeNone() which resolves immediately with null.\n this._resumeNoneResolver = () => done(null);\n\n // Set the resolver that handleStreamResuming() will call.\n // When onAgentMessage sees CF_AGENT_STREAM_RESUMING, it calls\n // handleStreamResuming() which invokes this callback.\n this._resumeResolver = (data: { id: string }) => {\n const requestId = data.id;\n\n // Track this request so onAgentMessage skips subsequent chunks\n activeIds?.add(requestId);\n\n const stream = this._createResumeStream(requestId);\n\n // Send ACK to server via the latest agent (the socket may\n // have been replaced since reconnectToStream was called).\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: requestId\n })\n );\n\n // Return a ReadableStream fed by the replayed + live chunks\n done(stream);\n };\n\n // Send the resume request. PartySocket queues sends when\n // the socket isn't open yet and flushes on connect, so\n // this works regardless of current readyState.\n try {\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST\n })\n );\n } catch {\n // WebSocket may already be closed\n }\n\n // Safety-net timeout: if the WebSocket never connects or the\n // server is unreachable, resolve null. Under normal operation\n // the server responds with STREAM_RESUMING, STREAM_RESUME_NONE, or\n // STREAM_PENDING (which extends this) well before this fires.\n timeout = setTimeout(() => done(null), RESUME_PROBE_TIMEOUT_MS);\n });\n }\n\n /**\n * Creates a deferred ReadableStream for client-side tool continuations.\n * The stream is returned immediately so AI SDK status becomes \"submitted\"\n * right after addToolOutput()/addToolApprovalResponse(), then it waits for\n * the server to announce the continuation via STREAM_RESUMING.\n */\n private _createToolContinuationStream(): ReadableStream<UIMessageChunk> {\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n const streamController = new AbortController();\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n let completed = false;\n let requestId: string | null = null;\n let readerController!: ReadableStreamDefaultController<UIMessageChunk>;\n let onResumeRef: ((data: { id: string }) => void) | null = null;\n let onResumeNoneRef: (() => void) | null = null;\n\n const clearHandshakeResolvers = (\n resumeResolver?: ((data: { id: string }) => void) | null,\n resumeNoneResolver?: (() => void) | null\n ) => {\n if (resumeResolver === undefined && resumeNoneResolver === undefined) {\n this._resumeResolver = null;\n this._resumeNoneResolver = null;\n return;\n }\n\n if (resumeResolver && this._resumeResolver === resumeResolver) {\n this._resumeResolver = null;\n }\n if (\n resumeNoneResolver &&\n this._resumeNoneResolver === resumeNoneResolver\n ) {\n this._resumeNoneResolver = null;\n }\n };\n\n const finish = (\n action: () => void,\n resumeResolver?: ((data: { id: string }) => void) | null,\n resumeNoneResolver?: (() => void) | null,\n keepRequestId = false\n ) => {\n if (completed) return;\n completed = true;\n this._abortToolContinuation = null;\n this._onStreamPending = null;\n clearHandshakeResolvers(resumeResolver, resumeNoneResolver);\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (requestId && !keepRequestId) {\n activeIds?.delete(requestId);\n }\n streamController.abort();\n };\n\n const transport = this;\n\n this._abortToolContinuation = () => {\n if (completed) {\n return false;\n }\n\n if (requestId === null) {\n // Handshake hasn't completed yet — close the stream and clear\n // resolvers so the subsequent onResume/handleStreamResuming\n // becomes a no-op.\n finish(\n () => readerController.error(abortError),\n onResumeRef,\n onResumeNoneRef\n );\n return true;\n }\n\n try {\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL,\n id: requestId\n })\n );\n } catch {\n // Ignore failures (e.g. agent already disconnected)\n }\n\n // keepRequestId=true: keep the ID in activeIds so onAgentMessage\n // skips in-flight chunks until the server's done:true cleans it up\n // (same pattern as sendMessages onAbort).\n finish(\n () => readerController.error(abortError),\n onResumeRef,\n onResumeNoneRef,\n true\n );\n return true;\n };\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n readerController = controller;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const onResumeNone = () => {\n if (timeout) clearTimeout(timeout);\n finish(() => controller.close(), onResume, onResumeNone);\n };\n\n const onResume = (data: { id: string }) => {\n if (requestId) return;\n\n requestId = data.id;\n activeIds?.add(requestId);\n clearHandshakeResolvers(onResume, onResumeNone);\n transport._onStreamPending = null;\n if (timeout) clearTimeout(timeout);\n\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: requestId\n })\n );\n };\n\n onResumeRef = onResume;\n onResumeNoneRef = onResumeNone;\n\n timeout = setTimeout(\n () => finish(() => controller.close(), onResume, onResumeNone),\n RESUME_PROBE_TIMEOUT_MS\n );\n\n // Keep-waiting (#1784): extend the probe window when the server reports\n // the continuation turn is accepted but its stream hasn't started.\n transport._onStreamPending = () => {\n if (completed) return;\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(\n () => finish(() => controller.close(), onResume, onResumeNone),\n RESUME_PENDING_TIMEOUT_MS\n );\n };\n\n transport._resumeResolver = onResume;\n transport._resumeNoneResolver = onResumeNone;\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<UIMessage>;\n\n if (\n data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE ||\n requestId == null ||\n data.id !== requestId\n ) {\n return;\n }\n\n if (data.error) {\n finish(\n () => controller.error(new Error(data.body || \"Stream error\")),\n onResume,\n onResumeNone\n );\n return;\n }\n\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close(), onResume, onResumeNone);\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n if (timeout) clearTimeout(timeout);\n finish(() => controller.close(), onResume, onResumeNone);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: streamController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: streamController.signal\n });\n\n try {\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST\n })\n );\n } catch {\n finish(() => controller.close());\n }\n },\n cancel() {\n if (requestId && transport.cancelOnClientAbort) {\n transport.sendCancelFrame(requestId);\n finish(() => {}, onResumeRef, onResumeNoneRef, true);\n } else {\n finish(() => {}, onResumeRef, onResumeNoneRef);\n }\n }\n });\n }\n\n /**\n * Creates a ReadableStream that receives resumed stream chunks\n * and forwards them to useChat as UIMessageChunk objects.\n */\n private _createResumeStream(\n requestId: string\n ): ReadableStream<UIMessageChunk> {\n // Read agent at resolve time (not when reconnectToStream was called)\n // so chunk listener attaches to the latest socket after _pk changes.\n const agent = this.agent;\n const activeIds = this.activeRequestIds;\n const chunkController = new AbortController();\n const abortError = new Error(\"Aborted\");\n abortError.name = \"AbortError\";\n let completed = false;\n\n const finish = (\n action: () => void,\n keepId = false,\n clearServerTurn = true\n ) => {\n if (completed) return;\n completed = true;\n if (clearServerTurn) {\n this.clearActiveServerTurn(requestId);\n }\n try {\n action();\n } catch {\n // Stream may already be closed\n }\n if (!keepId) {\n activeIds?.delete(requestId);\n }\n chunkController.abort();\n };\n\n const cancelActiveRequest = () => {\n if (completed) return false;\n finish(() => streamController.error(abortError), true);\n return true;\n };\n this.setActiveServerTurn(requestId, cancelActiveRequest);\n\n let streamController!: ReadableStreamDefaultController<UIMessageChunk>;\n const transport = this;\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n\n const onMessage = (event: MessageEvent) => {\n try {\n const data = JSON.parse(\n event.data as string\n ) as OutgoingMessage<UIMessage>;\n\n if (data.type !== MessageType.CF_AGENT_USE_CHAT_RESPONSE) return;\n if (data.id !== requestId) return;\n\n if (data.error) {\n finish(() =>\n controller.error(new Error(data.body || \"Stream error\"))\n );\n return;\n }\n\n // Parse and enqueue the chunk\n if (data.body?.trim()) {\n try {\n const chunk = JSON.parse(data.body) as UIMessageChunk;\n controller.enqueue(chunk);\n } catch {\n // Skip malformed chunk bodies\n }\n }\n\n if (data.done) {\n finish(() => controller.close());\n }\n } catch {\n // Ignore non-JSON messages\n }\n };\n\n const onClose = () => {\n finish(() => controller.close(), false, false);\n };\n\n agent.addEventListener(\"message\", onMessage, {\n signal: chunkController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: chunkController.signal\n });\n },\n cancel() {\n if (transport.cancelOnClientAbort) {\n transport.sendCancelFrame(requestId);\n finish(() => {}, true);\n } else {\n finish(() => {}, false, false);\n }\n }\n });\n }\n}\n","import { useChat, type UseChatOptions } from \"@ai-sdk/react\";\nimport { getToolName, isToolUIPart } from \"ai\";\nimport type {\n ChatInit,\n JSONSchema7,\n Tool,\n UIMessage as Message,\n UIMessage\n} from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { use, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { OutgoingMessage } from \"./wire-types\";\nimport { MessageType } from \"./wire-types\";\nimport {\n transition as broadcastTransition,\n type BroadcastStreamState\n} from \"./broadcast-state\";\nimport {\n WebSocketChatTransport,\n type AgentConnection\n} from \"./ws-chat-transport\";\n\nexport { WebSocketChatTransport } from \"./ws-chat-transport\";\nexport type {\n AgentConnection,\n WebSocketChatTransportOptions\n} from \"./ws-chat-transport\";\n\n/**\n * One-shot deprecation warnings (warns once per key per session).\n */\nconst _deprecationWarnings = new Set<string>();\nfunction warnDeprecated(id: string, message: string) {\n if (!_deprecationWarnings.has(id)) {\n _deprecationWarnings.add(id);\n console.warn(`[agents/chat] Deprecated: ${message}`);\n }\n}\n\ntype AgentConnectionErrorLike = Error & {\n code: number;\n reason: string;\n wasClean: boolean;\n};\n\n// ── DEPRECATED TYPES AND FUNCTIONS ──────────────────────────────────\n// Everything in this section is deprecated and will be removed in the\n// next major version. Use server-side tools with tool() from \"ai\" and\n// the onToolCall callback in useAgentChat instead.\n\n/**\n * JSON Schema type for tool parameters.\n * Re-exported from the AI SDK for convenience.\n * @deprecated Import JSONSchema7 directly from \"ai\" instead. Will be removed in the next major version.\n */\nexport type JSONSchemaType = JSONSchema7;\n\n/**\n * Definition for a tool that can be executed on the client.\n * Tools with an `execute` function are automatically registered with the server.\n *\n * **For most apps**, define tools on the server with `tool()` from `\"ai\"` —\n * you get full Zod type safety and simpler code. Use `onToolCall` in\n * `useAgentChat` for tools that need browser-side execution.\n *\n * **For SDKs and platforms** where the tool surface is determined dynamically\n * by the embedding application at runtime, this type lets the client register\n * tools the server does not know about at deploy time.\n *\n * Note: Uses `parameters` (JSONSchema7) because client tools must be\n * serializable for the wire format. Zod schemas cannot be serialized.\n */\nexport type AITool<Input = unknown, Output = unknown> = {\n /** Human-readable description of what the tool does */\n description?: Tool[\"description\"];\n /** JSON Schema defining the tool's input parameters */\n parameters?: JSONSchema7;\n /**\n * @deprecated Use `parameters` instead. Will be removed in a future version.\n */\n inputSchema?: JSONSchema7;\n /**\n * Function to execute the tool on the client.\n * If provided, the tool schema is automatically sent to the server.\n */\n execute?: (input: Input) => Output | Promise<Output>;\n};\n\nimport type { ClientToolSchema } from \"./client-tools\";\nexport type { ClientToolSchema } from \"./client-tools\";\n\n/**\n * Extracts tool schemas from tools that have client-side execute functions.\n * These schemas are automatically sent to the server with each request.\n *\n * Called internally by `useAgentChat` when `tools` are provided.\n * Most apps do not need to call this directly.\n *\n * @param tools - Record of tool name to tool definition\n * @returns Array of tool schemas to send to server, or undefined if none\n */\nexport function extractClientToolSchemas(\n tools?: Record<string, AITool<unknown, unknown>>\n): ClientToolSchema[] | undefined {\n if (!tools) return undefined;\n\n const schemas: ClientToolSchema[] = Object.entries(tools)\n .filter(([_, tool]) => tool.execute) // Only tools with client-side execute\n .map(([name, tool]) => {\n if (tool.inputSchema && !tool.parameters) {\n console.warn(\n `[useAgentChat] Tool \"${name}\" uses deprecated 'inputSchema'. Please migrate to 'parameters'.`\n );\n }\n return {\n name,\n description: tool.description,\n parameters: tool.parameters ?? tool.inputSchema\n };\n });\n\n return schemas.length > 0 ? schemas : undefined;\n}\n\n// ── END DEPRECATED TYPES AND FUNCTIONS ─────────────────────────────\n\n// ── Tool part helpers ──────────────────────────────────────────────\n//\n// `isToolUIPart` and `getToolName` are exported by the AI SDK:\n// import { isToolUIPart, getToolName } from \"ai\";\n//\n// The helpers below provide additional typed accessors and a\n// simplified state mapping that the AI SDK doesn't offer.\n\n/**\n * Map internal tool part states to simplified UI-relevant states.\n *\n * @example\n * ```tsx\n * import { isToolUIPart } from \"ai\";\n * import { getToolPartState } from \"@cloudflare/ai-chat/react\";\n *\n * if (isToolUIPart(part)) {\n * const state = getToolPartState(part);\n * if (state === \"complete\") { ... }\n * if (state === \"waiting-approval\") { ... }\n * }\n * ```\n */\nexport function getToolPartState(\n part: UIMessage[\"parts\"][number]\n):\n | \"loading\"\n | \"streaming\"\n | \"waiting-approval\"\n | \"approved\"\n | \"complete\"\n | \"error\"\n | \"denied\" {\n const state = (part as { state?: string }).state;\n switch (state) {\n case \"input-streaming\":\n return \"streaming\";\n case \"approval-requested\":\n return \"waiting-approval\";\n case \"approval-responded\":\n return \"approved\";\n case \"output-available\":\n return \"complete\";\n case \"output-error\":\n return \"error\";\n case \"output-denied\":\n return \"denied\";\n default:\n return \"loading\";\n }\n}\n\n/** Get the tool call ID from a tool UI part. */\nexport function getToolCallId(part: UIMessage[\"parts\"][number]): string {\n return (part as { toolCallId: string }).toolCallId;\n}\n\n/** Get the tool input from a tool UI part (if available). */\nexport function getToolInput(\n part: UIMessage[\"parts\"][number]\n): unknown | undefined {\n return (part as { input?: unknown }).input;\n}\n\n/** Get the tool output from a tool UI part (if available). */\nexport function getToolOutput(\n part: UIMessage[\"parts\"][number]\n): unknown | undefined {\n return (part as { output?: unknown }).output;\n}\n\n/** Get the approval info from a tool UI part (if in approval state). */\nexport function getToolApproval(\n part: UIMessage[\"parts\"][number]\n): { id: string; approved?: boolean } | undefined {\n return (part as { approval?: { id: string; approved?: boolean } }).approval;\n}\n\n// ── END Tool part helpers ──────────────────────────────────────────\n\n// ── Standalone fetch ───────────────────────────────────────────────\n\nfunction agentNameToKebab(name: string): string {\n if (name === name.toUpperCase() && name !== name.toLowerCase()) {\n return name.toLowerCase().replace(/_/g, \"-\");\n }\n let result = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n result = result.startsWith(\"-\") ? result.slice(1) : result;\n return result.replace(/_/g, \"-\").replace(/-$/, \"\");\n}\n\n/**\n * Fetch messages from an agent's `/get-messages` HTTP endpoint.\n *\n * Use in framework route loaders to prefetch messages before the component\n * tree mounts, or anywhere you need messages outside a React hook.\n *\n * @example Standard routing\n * ```typescript\n * import { getAgentMessages } from \"@cloudflare/ai-chat/react\";\n *\n * const messages = await getAgentMessages({\n * host: \"https://my-app.workers.dev\",\n * agent: \"ChatAgent\",\n * name: \"session-123\"\n * });\n * ```\n *\n * @example With basePath (custom URL)\n * ```typescript\n * const messages = await getAgentMessages({\n * url: \"https://my-app.workers.dev/custom/path/get-messages\"\n * });\n * ```\n */\nexport async function getAgentMessages<M extends UIMessage = UIMessage>(\n options:\n | {\n host: string;\n agent: string;\n name: string;\n credentials?: RequestCredentials;\n headers?: HeadersInit;\n }\n | {\n url: string;\n credentials?: RequestCredentials;\n headers?: HeadersInit;\n }\n): Promise<M[]> {\n let messagesUrl: string;\n\n if (\"url\" in options) {\n messagesUrl = options.url;\n } else {\n const agentSlug = agentNameToKebab(options.agent);\n const base = options.host.endsWith(\"/\")\n ? options.host.slice(0, -1)\n : options.host;\n messagesUrl = `${base}/agents/${agentSlug}/${options.name}/get-messages`;\n }\n\n try {\n const response = await fetch(messagesUrl, {\n credentials: options.credentials,\n headers: options.headers\n });\n\n if (!response.ok) {\n console.warn(\n `[getAgentMessages] Failed to fetch: ${response.status} ${response.statusText}`\n );\n return [];\n }\n\n const text = await response.text();\n if (!text.trim()) return [];\n\n return JSON.parse(text) as M[];\n } catch (error) {\n console.warn(\"[getAgentMessages] Fetch error:\", error);\n return [];\n }\n}\n\n// ── END Standalone fetch ───────────────────────────────────────────\n\ntype GetInitialMessagesOptions = {\n agent: string;\n name: string;\n url?: string;\n};\n\n// v5 useChat parameters\ntype UseChatParams<M extends UIMessage = UIMessage> = ChatInit<M> &\n UseChatOptions<M>;\n\n/**\n * Options for preparing the send messages request.\n * Used by prepareSendMessagesRequest callback.\n */\nexport type PrepareSendMessagesRequestOptions<\n ChatMessage extends UIMessage = UIMessage\n> = {\n /** The chat ID */\n id: string;\n /** Messages to send */\n messages: ChatMessage[];\n /** What triggered this request */\n trigger: \"submit-message\" | \"regenerate-message\";\n /** ID of the message being sent (if applicable) */\n messageId?: string;\n /** Request metadata */\n requestMetadata?: unknown;\n /** Current body (if any) */\n body?: Record<string, unknown>;\n /** Current credentials (if any) */\n credentials?: RequestCredentials;\n /** Current headers (if any) */\n headers?: HeadersInit;\n /** API endpoint */\n api?: string;\n};\n\n/**\n * Return type for prepareSendMessagesRequest callback.\n * Allows customizing headers, body, and credentials for each request.\n * All fields are optional; only specify what you need to customize.\n */\nexport type PrepareSendMessagesRequestResult = {\n /** Custom headers to send with the request */\n headers?: HeadersInit;\n /** Custom body data to merge with the request */\n body?: Record<string, unknown>;\n /** Custom credentials option */\n credentials?: RequestCredentials;\n /** Custom API endpoint */\n api?: string;\n};\n\n/**\n * Options for addToolOutput function\n */\ntype AddToolOutputOptions = {\n /** The ID of the tool call to provide output for */\n toolCallId: string;\n /** The name of the tool (optional, for type safety) */\n toolName?: string;\n /** The output to provide */\n output?: unknown;\n /** Override the tool part state (e.g. \"output-error\" for custom denial) */\n state?: \"output-available\" | \"output-error\";\n /** Error message when state is \"output-error\" */\n errorText?: string;\n};\n\n/**\n * Callback for handling client-side tool execution.\n * Called when a tool without server-side execute is invoked.\n */\nexport type OnToolCallCallback = (options: {\n /** The tool call that needs to be handled */\n toolCall: {\n toolCallId: string;\n toolName: string;\n input: unknown;\n };\n /** Function to provide the tool output (or signal an error/denial) */\n addToolOutput: (options: Omit<AddToolOutputOptions, \"toolName\">) => void;\n}) => void | Promise<void>;\n\n/**\n * Options for the useAgentChat hook\n */\nexport type UseAgentChatOptions<\n // oxlint-disable-next-line no-unused-vars -- kept for backward compat\n State = unknown,\n ChatMessage extends UIMessage = UIMessage\n> = Omit<UseChatParams<ChatMessage>, \"fetch\" | \"onToolCall\"> & {\n /** Agent connection from useAgent (accepts both typed and untyped agents) */\n agent: AgentConnection & {\n agent: string;\n name: string;\n path?: ReadonlyArray<{ agent: string; name: string }>;\n connectionError?: AgentConnectionErrorLike | null;\n getHttpUrl: () => string;\n };\n getInitialMessages?:\n | undefined\n | null\n | ((options: GetInitialMessagesOptions) => Promise<ChatMessage[]>);\n /** Request credentials */\n credentials?: RequestCredentials;\n /** Request headers */\n headers?: HeadersInit;\n /**\n * Callback for handling client-side tool execution.\n * Called when a tool without server-side `execute` is invoked by the LLM.\n *\n * Use this for:\n * - Tools that need browser APIs (geolocation, camera, etc.)\n * - Tools that need user interaction before providing a result\n * - Tools requiring approval before execution\n *\n * @example\n * ```typescript\n * onToolCall: async ({ toolCall, addToolOutput }) => {\n * if (toolCall.toolName === 'getLocation') {\n * const position = await navigator.geolocation.getCurrentPosition();\n * addToolOutput({\n * toolCallId: toolCall.toolCallId,\n * output: { lat: position.coords.latitude, lng: position.coords.longitude }\n * });\n * }\n * }\n * ```\n */\n onToolCall?: OnToolCallCallback;\n /**\n * @deprecated Use `onToolCall` callback instead for automatic tool execution.\n * @description Whether to automatically resolve tool calls that do not require human interaction.\n * @experimental\n */\n experimental_automaticToolResolution?: boolean;\n /**\n * Tools that can be executed on the client. Tool schemas are automatically\n * sent to the server and tool calls are routed back for client execution.\n *\n * **For most apps**, define tools on the server with `tool()` from `\"ai\"`\n * and handle client-side execution via `onToolCall`. This gives you full\n * Zod type safety and keeps tool definitions in one place.\n *\n * **For SDKs and platforms** where tools are defined dynamically by the\n * embedding application at runtime, this option lets the client register\n * tools the server does not know about at deploy time.\n */\n tools?: Record<string, AITool<unknown, unknown>>;\n /**\n * @deprecated Use `needsApproval` on server-side tools instead.\n * @description Manual override for tools requiring confirmation.\n * If not provided, will auto-detect from tools object (tools without execute require confirmation).\n */\n toolsRequiringConfirmation?: string[];\n /**\n * When true (default), the server automatically continues the conversation\n * after receiving client-side tool results or approvals, similar to how\n * server-executed tools work with maxSteps in streamText. The continuation\n * is merged into the same assistant message.\n *\n * When false, the client must call sendMessage() after tool results\n * to continue the conversation, which creates a new assistant message.\n *\n * @default true\n */\n autoContinueAfterToolResult?: boolean;\n /**\n * @deprecated Use `sendAutomaticallyWhen` from AI SDK instead.\n *\n * When true (default), automatically sends the next message only after\n * all pending confirmation-required tool calls have been resolved.\n * When false, sends immediately after each tool result.\n *\n * Only applies when `autoContinueAfterToolResult` is false.\n *\n * @default true\n */\n autoSendAfterAllConfirmationsResolved?: boolean;\n /**\n * Set to false to disable automatic stream resumption.\n * @default true\n */\n resume?: boolean;\n /**\n * Whether generic client-side stream abort/cleanup should cancel the server\n * turn. By default, client cleanup is local-only so the server turn can\n * continue and be resumed on reconnect. Explicit stop() always cancels the\n * server turn.\n *\n * @default false\n */\n cancelOnClientAbort?: boolean;\n /**\n * Whether `setMessages` should also send the full client transcript to the\n * server as `CF_AGENT_CHAT_MESSAGES`. This is useful for flat transcript\n * stores such as `AIChatAgent`, but should be disabled for server-authoritative\n * hosts whose client messages are only a projection of richer storage.\n *\n * @default true\n */\n syncMessagesToServer?: boolean;\n /**\n * Custom data to include in every chat request body.\n * Accepts a static object or a function that returns one (for dynamic values).\n * These fields are available in `onChatMessage` via `options.body`.\n *\n * @example\n * ```typescript\n * // Static\n * body: { timezone: \"America/New_York\", userId: \"abc\" }\n *\n * // Dynamic (called on each send)\n * body: () => ({ token: getAuthToken(), timestamp: Date.now() })\n * ```\n */\n body?:\n | Record<string, unknown>\n | (() => Record<string, unknown> | Promise<Record<string, unknown>>);\n /**\n * Callback to customize the request before sending messages.\n * For most cases, use the `body` option instead.\n * Use this for advanced scenarios that need access to the messages or trigger type.\n *\n * Note: Client tool schemas are automatically sent when tools have `execute` functions.\n * This callback can add additional data alongside the auto-extracted schemas.\n */\n prepareSendMessagesRequest?: (\n options: PrepareSendMessagesRequestOptions<ChatMessage>\n ) =>\n | PrepareSendMessagesRequestResult\n | Promise<PrepareSendMessagesRequestResult>;\n};\n\n/**\n * Module-level cache for initial message fetches. Intentionally shared across\n * all useAgentChat instances to deduplicate requests during React Strict Mode\n * double-renders and re-renders. Cache keys include the agent URL, agent type,\n * and thread name to prevent cross-agent collisions.\n */\nconst requestCache = new Map<string, Promise<Message[]>>();\n\nfunction findLastAssistantMessage<ChatMessage extends UIMessage>(\n messages: ChatMessage[]\n): { index: number; message: ChatMessage } | null {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index];\n if (message.role === \"assistant\") {\n return { index, message };\n }\n }\n\n return null;\n}\n\nfunction moveMessageToEnd<ChatMessage extends UIMessage>(\n messages: ChatMessage[],\n messageId: string\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.id === messageId);\n if (idx < 0 || idx === messages.length - 1) return messages;\n\n const result = [...messages];\n const [msg] = result.splice(idx, 1);\n if (!msg) return messages;\n\n result.push(msg);\n return result;\n}\n\nfunction prependMissingHydratedMessages<ChatMessage extends UIMessage>(\n hydratedMessages: ChatMessage[],\n currentMessages: ChatMessage[]\n): ChatMessage[] {\n if (currentMessages.length === 0) {\n return hydratedMessages;\n }\n\n const currentMessageIds = new Set(\n currentMessages.map((message) => message.id)\n );\n const missingHydratedMessages = hydratedMessages.filter(\n (message) => !currentMessageIds.has(message.id)\n );\n\n if (missingHydratedMessages.length === 0) {\n return currentMessages;\n }\n\n // History fetched after mount predates messages already rendered locally\n // (for example an optimistic immediate send). Keep current copies for\n // matching IDs because they may include newer streamed/client state.\n return [...missingHydratedMessages, ...currentMessages];\n}\n\n/**\n * React hook for building AI chat interfaces using an Agent\n * @param options Chat options including the agent connection\n * @returns Chat interface controls and state with added clearHistory method\n */\n/**\n * Automatically detects which tools require confirmation based on their configuration.\n * Tools require confirmation if they have no execute function AND are not server-executed.\n * @param tools - Record of tool name to tool definition\n * @returns Array of tool names that require confirmation\n *\n * @deprecated Use `needsApproval` on server-side tools instead.\n */\nexport function detectToolsRequiringConfirmation(\n tools?: Record<string, AITool<unknown, unknown>>\n): string[] {\n warnDeprecated(\n \"detectToolsRequiringConfirmation\",\n \"detectToolsRequiringConfirmation() is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.\"\n );\n if (!tools) return [];\n\n return Object.entries(tools)\n .filter(([_name, tool]) => !tool.execute)\n .map(([name]) => name);\n}\n\nexport function useAgentChat<\n // oxlint-disable-next-line no-unused-vars -- kept for backward compat\n State = unknown,\n ChatMessage extends UIMessage = UIMessage\n>(\n options: UseAgentChatOptions<State, ChatMessage>\n): Omit<ReturnType<typeof useChat<ChatMessage>>, \"addToolOutput\"> & {\n clearHistory: () => void;\n /**\n * Provide output for a tool call. Use this for tools that require user interaction\n * or client-side execution.\n */\n addToolOutput: (opts: AddToolOutputOptions) => void;\n /**\n * Whether a server-initiated stream (e.g. from `saveMessages`,\n * auto-continuation, or another tab) is currently active, OR a\n * client-side tool call is awaiting resolution via `onToolCall`.\n * Covers the full \"turn-in-progress\" window from the consumer's\n * perspective, including the gap between the model emitting a\n * client-tool call and the server pushing a continuation after\n * `addToolOutput`. This is independent of the AI SDK's `status`\n * which only tracks client-initiated request/response cycles.\n */\n isServerStreaming: boolean;\n /**\n * Convenience flag: `true` when either the client-initiated stream\n * (`status === \"streaming\"`) or a server-initiated stream is active.\n * Use this for showing a universal streaming indicator.\n */\n isStreaming: boolean;\n /**\n * `true` while a durable chat turn is being recovered (interrupted by a\n * deploy/eviction or a stream-stall watchdog abort and now resuming, #1620).\n * Distinct from `isStreaming` — a recovering turn isn't producing tokens yet,\n * so a client can show a \"recovering…\" hint instead of looking frozen. Most\n * UIs treat `isStreaming || isRecovering` as \"busy\". Driven by the server's\n * `CF_AGENT_CHAT_RECOVERING` frames (also replayed on connect for\n * `@cloudflare/think`); cleared automatically on the next stream or terminal.\n */\n isRecovering: boolean;\n /**\n * `true` when the current `status`/`isServerStreaming` activity is\n * driven by a server-pushed tool continuation (i.e. the server is\n * auto-continuing the conversation after `addToolOutput` or\n * `addToolApprovalResponse`) rather than a fresh user submission.\n *\n * Use this to disambiguate \"user just sent a new message, awaiting\n * first token\" from \"mid-turn tool round-trip\" — e.g. when you want\n * a typing indicator only for the former:\n *\n * ```tsx\n * const showTypingIndicator = status === \"submitted\" && !isToolContinuation;\n * ```\n *\n * See issue #1365.\n */\n isToolContinuation: boolean;\n connectionError: AgentConnectionErrorLike | null;\n} {\n const {\n agent,\n getInitialMessages,\n messages: optionsInitialMessages,\n onToolCall,\n onData,\n experimental_automaticToolResolution,\n tools,\n toolsRequiringConfirmation: manualToolsRequiringConfirmation,\n autoContinueAfterToolResult = true, // Server auto-continues after tool results/approvals\n autoSendAfterAllConfirmationsResolved = true, // Legacy option for client-side batching\n resume = true, // Enable stream resumption by default\n cancelOnClientAbort = false,\n syncMessagesToServer = true,\n body: bodyOption,\n prepareSendMessagesRequest,\n ...rest\n } = options;\n\n // Emit deprecation warnings for deprecated options (once per session)\n if (manualToolsRequiringConfirmation) {\n warnDeprecated(\n \"useAgentChat.toolsRequiringConfirmation\",\n \"The 'toolsRequiringConfirmation' option is deprecated. Use needsApproval on server-side tools instead. Will be removed in the next major version.\"\n );\n }\n if (experimental_automaticToolResolution) {\n warnDeprecated(\n \"useAgentChat.experimental_automaticToolResolution\",\n \"The 'experimental_automaticToolResolution' option is deprecated. Use the onToolCall callback instead. Will be removed in the next major version.\"\n );\n }\n if (options.autoSendAfterAllConfirmationsResolved !== undefined) {\n warnDeprecated(\n \"useAgentChat.autoSendAfterAllConfirmationsResolved\",\n \"The 'autoSendAfterAllConfirmationsResolved' option is deprecated. Use sendAutomaticallyWhen from AI SDK instead. Will be removed in the next major version.\"\n );\n }\n\n // ── DEPRECATED: client-side tool confirmation ──────────────────────\n // This block will be removed when toolsRequiringConfirmation is removed.\n // Only call the deprecated function when deprecated options are actually used.\n const toolsRequiringConfirmation = useMemo(() => {\n if (manualToolsRequiringConfirmation) {\n return manualToolsRequiringConfirmation;\n }\n // Inline the logic from detectToolsRequiringConfirmation to avoid\n // emitting a deprecation warning when tools are provided via the\n // non-deprecated `tools` option.\n if (!tools) return [];\n return Object.entries(tools)\n .filter(([_name, tool]) => !tool.execute)\n .map(([name]) => name);\n }, [manualToolsRequiringConfirmation, tools]);\n\n // Keep refs to always point to the latest callbacks\n const onToolCallRef = useRef(onToolCall);\n onToolCallRef.current = onToolCall;\n const onDataRef = useRef(onData);\n onDataRef.current = onData;\n\n const rawHttpUrl = agent.getHttpUrl();\n const agentUrl = rawHttpUrl ? new URL(rawHttpUrl) : null;\n\n if (agentUrl) {\n agentUrl.searchParams.delete(\"_pk\");\n }\n const agentUrlString = agentUrl?.toString() ?? null;\n\n const agentAddressKey = Array.isArray(agent.path)\n ? JSON.stringify(agent.path.map((step) => [step.agent, step.name]))\n : JSON.stringify([[agent.agent ?? \"\", agent.name ?? \"\"]]);\n\n // Cache key for the request-dedup `requestCache` and the late-seed\n // effect. It uses the full root-first agent address when `useAgent`\n // provides one, so sub-agents with the same leaf class/name under\n // different parents do not share hydrated messages.\n //\n // - Query params like auth tokens change across page loads and\n // must not bust the cache, or Suspense re-triggers and breaks\n // stream resume (see issue #1223).\n // - The origin+pathname portion of the socket URL can legitimately\n // transition from empty → resolved on the second render when\n // `useAgent()` finishes its handshake. Including it here would\n // cause `doGetInitialMessages` to miss the cache after the URL\n // arrives, re-invoke the loader, and re-trigger Suspense — the\n // exact regression #1356 reports when a custom `getInitialMessages`\n // is provided.\n //\n // `resolvedInitialMessagesCacheKey` is still computed because the\n // `stableChatIdRef` logic below uses it to detect the URL-arrival\n // transition separately from identity changes.\n const resolvedInitialMessagesCacheKey = agentUrl\n ? `${agentUrl.origin}${agentUrl.pathname}|${agentAddressKey}`\n : null;\n const initialMessagesCacheKey = agentAddressKey;\n\n // Stable chat ID for `useChat({ id })`.\n //\n // The AI SDK recreates the underlying Chat instance whenever its `id`\n // changes, which aborts any in-flight `transport.reconnectToStream()`\n // (the resume path) and leaves the recreated Chat without any resume\n // having been fired on it — the AI SDK's `useEffect(() => {\n // if (resume) chatRef.current.resumeStream() }, [resume, chatRef])`\n // deps are object-stable, so the effect does not re-fire on recreation.\n // See issue #1356.\n //\n // Two things can move across renders and must NOT cause an id flip:\n //\n // 1. The origin+pathname of the socket URL can transition from\n // `null` → resolved on the second render when `useAgent()`\n // finishes its handshake. The Chat id must stay on its\n // first-render fallback through that transition; upgrading to\n // the URL-resolved key would recreate Chat and drop optimistic\n // messages sent immediately after mount.\n //\n // 2. `agent.name` can transition from the client-side fallback\n // (\"default\") to a server-assigned value when\n // `static options = { sendIdentityOnConnect: true }` is set and\n // the consumer uses the `basePath` pattern (the server owns the\n // DO instance name, not the browser). `useAgent` mutates the\n // same agent object's `.name` in place here.\n //\n // What IS a genuine chat switch: the consumer passes a different\n // `agent` object to `useAgentChat`. That's a new `useAgent({...})`\n // return value, typically from swapping or remounting a parent. We\n // detect this by reference equality — `useAgent`'s return is stable\n // across renders for a given mount, so a reference change is the\n // unambiguous \"chat switch\" signal.\n const stableChatIdRef = useRef<string | null>(null);\n const previousAgentRef = useRef<typeof agent | null>(null);\n const previousAgentAddressKeyRef = useRef<string | null>(null);\n const fallbackChatId = agentAddressKey;\n const agentPathChanged =\n Array.isArray(agent.path) &&\n previousAgentAddressKeyRef.current !== null &&\n previousAgentAddressKeyRef.current !== agentAddressKey;\n\n if (stableChatIdRef.current === null) {\n // First render: initialize.\n stableChatIdRef.current = resolvedInitialMessagesCacheKey ?? fallbackChatId;\n } else if (previousAgentRef.current !== agent || agentPathChanged) {\n // Consumer swapped in a different agent object, or the full\n // sub-agent address changed on a `useAgent` object — genuine chat switch.\n // Recompute from current values.\n stableChatIdRef.current = resolvedInitialMessagesCacheKey ?? fallbackChatId;\n }\n\n previousAgentRef.current = agent;\n previousAgentAddressKeyRef.current = agentAddressKey;\n\n // Keep a ref to always point to the latest agent instance.\n // Updated synchronously during render (not in useEffect) so the\n // transport's agent ref is always current. The transport is a\n // singleton whose .agent is reassigned every render — if we used\n // useEffect the assignment would lag behind, causing the transport\n // to send through a stale/closed socket (issue #929).\n const agentRef = useRef(agent);\n agentRef.current = agent;\n\n async function defaultGetInitialMessagesFetch({\n url\n }: GetInitialMessagesOptions) {\n if (!url) {\n return [];\n }\n const getMessagesUrl = new URL(url);\n getMessagesUrl.pathname += \"/get-messages\";\n const response = await fetch(getMessagesUrl.toString(), {\n credentials: options.credentials,\n headers: options.headers\n });\n\n if (!response.ok) {\n console.warn(\n `Failed to fetch initial messages: ${response.status} ${response.statusText}`\n );\n return [];\n }\n\n const text = await response.text();\n if (!text.trim()) {\n return [];\n }\n\n try {\n return JSON.parse(text) as ChatMessage[];\n } catch (error) {\n console.warn(\"Failed to parse initial messages JSON:\", error);\n return [];\n }\n }\n\n const getInitialMessagesFetch =\n getInitialMessages || defaultGetInitialMessagesFetch;\n\n function doGetInitialMessages(\n getInitialMessagesOptions: GetInitialMessagesOptions,\n cacheKey: string\n ) {\n if (requestCache.has(cacheKey)) {\n return requestCache.get(cacheKey)! as Promise<ChatMessage[]>;\n }\n const promise = getInitialMessagesFetch(getInitialMessagesOptions);\n requestCache.set(cacheKey, promise);\n return promise;\n }\n\n const shouldFetchInitialMessages =\n getInitialMessages === null\n ? false\n : getInitialMessages\n ? true\n : !!agentUrlString;\n const initialMessagesPromise = !shouldFetchInitialMessages\n ? null\n : doGetInitialMessages(\n {\n agent: agent.agent,\n name: agent.name,\n url: agentUrlString ?? undefined\n },\n initialMessagesCacheKey\n );\n const initialMessages = initialMessagesPromise\n ? use(initialMessagesPromise)\n : (optionsInitialMessages ?? []);\n\n useEffect(() => {\n if (!initialMessagesPromise) {\n return;\n }\n requestCache.set(initialMessagesCacheKey, initialMessagesPromise!);\n return () => {\n if (\n requestCache.get(initialMessagesCacheKey) === initialMessagesPromise\n ) {\n requestCache.delete(initialMessagesCacheKey);\n }\n };\n }, [initialMessagesCacheKey, initialMessagesPromise]);\n\n // Use synchronous ref updates to avoid race conditions between effect runs.\n // This ensures the ref always has the latest value before any effect reads it.\n const toolsRef = useRef(tools);\n toolsRef.current = tools;\n\n const prepareSendMessagesRequestRef = useRef(prepareSendMessagesRequest);\n prepareSendMessagesRequestRef.current = prepareSendMessagesRequest;\n\n const bodyOptionRef = useRef(bodyOption);\n bodyOptionRef.current = bodyOption;\n\n /**\n * Tracks request IDs initiated by this tab via the transport.\n * Used by onAgentMessage to skip messages already handled by the transport.\n */\n const localRequestIdsRef = useRef<Set<string>>(new Set());\n const pendingReplayResumeRequestIdsRef = useRef<Set<string>>(new Set());\n const replayHydratedAssistantMessageIdsRef = useRef<Set<string>>(new Set());\n /**\n * Request ids this socket already ACKed via the fallback resume path.\n * The server sends CF_AGENT_STREAM_RESUMING for the same request from\n * both onConnect and its CF_AGENT_STREAM_RESUME_REQUEST handler (#1733).\n * The transport-handled path dedupes the second notify via\n * localRequestIdsRef, but the fallback path used to ACK both — triggering\n * a second full-buffer replay that duplicated streamed parts. Entries are\n * dropped when the turn completes; the whole set resets when the socket\n * closes, since a new connection legitimately needs a fresh ACK+replay.\n */\n const fallbackAckedResumeRequestIdsRef = useRef<Set<string>>(new Set());\n\n // WebSocket-based transport that speaks the CF_AGENT protocol natively.\n // Replaces the old aiFetch + DefaultChatTransport indirection.\n //\n // The transport is a true singleton (created once, never recreated) so\n // that the resolver set by reconnectToStream and the handleStreamResuming\n // call from onAgentMessage always operate on the SAME instance — even\n // when _pk changes (async queries, socket recreation) or React Strict\n // Mode double-mounts. The agent reference is updated every render so\n // sends always go through the latest socket.\n const customTransportRef = useRef<WebSocketChatTransport<ChatMessage> | null>(\n null\n );\n\n if (customTransportRef.current === null) {\n customTransportRef.current = new WebSocketChatTransport<ChatMessage>({\n agent: agentRef.current,\n activeRequestIds: localRequestIdsRef.current,\n cancelOnClientAbort,\n prepareBody: async ({ messages: msgs, trigger, messageId }) => {\n // Start with the top-level body option (static or dynamic)\n let extraBody: Record<string, unknown> = {};\n const currentBody = bodyOptionRef.current;\n if (currentBody) {\n const resolved =\n typeof currentBody === \"function\"\n ? await currentBody()\n : currentBody;\n extraBody = { ...resolved };\n }\n\n // Extract schemas from deprecated client tools (if any)\n // Only extract client tool schemas when deprecated tools option is used\n if (toolsRef.current) {\n const clientToolSchemas = extractClientToolSchemas(toolsRef.current);\n if (clientToolSchemas) {\n extraBody.clientTools = clientToolSchemas;\n }\n }\n\n // Apply user's prepareSendMessagesRequest callback (overrides body option)\n if (prepareSendMessagesRequestRef.current) {\n const userResult = await prepareSendMessagesRequestRef.current({\n id: (agentRef.current as unknown as { _pk: string })._pk,\n messages: msgs,\n trigger,\n messageId\n });\n if (userResult.body) {\n Object.assign(extraBody, userResult.body);\n }\n }\n\n return extraBody;\n }\n });\n }\n // Always point the transport at the latest socket so sends/listeners\n // go through the current connection after _pk changes.\n customTransportRef.current.agent = agentRef.current;\n customTransportRef.current.setCancelOnClientAbort(cancelOnClientAbort);\n const customTransport = customTransportRef.current;\n\n // Use a stable Chat ID that doesn't change when _pk changes.\n // The AI SDK recreates the Chat when `id` changes, which would\n // abandon any in-flight makeRequest (including resume) and the\n // resume effect wouldn't re-fire (deps are [resume, chatRef]).\n // Using the initial messages cache key (URL + agent + name) keeps\n // the Chat stable across socket recreations.\n const useChatHelpers = useChat<ChatMessage>({\n ...rest,\n onData,\n messages: initialMessages,\n transport: customTransport,\n id: stableChatIdRef.current,\n // Pass resume so useChat calls transport.reconnectToStream().\n // This lets the AI SDK track status (\"streaming\") during resume.\n resume\n });\n\n // Destructure stable method references from useChatHelpers.\n // These are individually memoized by the AI SDK (via useCallback), so they're\n // safe to use in dependency arrays without causing re-renders. Using them\n // directly instead of `useChatHelpers.method` avoids the exhaustive-deps\n // warning about the unstable `useChatHelpers` object.\n const {\n messages: chatMessages,\n setMessages,\n addToolResult,\n addToolApprovalResponse,\n sendMessage,\n resumeStream,\n status,\n stop\n } = useChatHelpers;\n\n const statusRef = useRef(status);\n statusRef.current = status;\n\n // Latest resumeStream, read by the reconnect re-probe effect without making\n // it a dependency (it would re-register the socket listeners on every render).\n const resumeStreamRef = useRef(resumeStream);\n resumeStreamRef.current = resumeStream;\n\n // Whether the socket has opened at least once. The AI SDK fires its own\n // resume on mount, so we only re-probe on SUBSEQUENT opens (transparent 1006\n // reconnects that don't remount the component) — see the reconnect re-probe\n // in the socket effect below (#1784).\n const hasConnectedOnceRef = useRef(false);\n\n // True while a reconnect re-probe `resumeStream()` we issued is still in\n // flight. AI SDK's `Chat.makeRequest` has no concurrency guard — every\n // resume shares the single mutable `this.activeResponse`, and its `finally`\n // finalizer reads `this.activeResponse.state` with a bare (unguarded) read\n // before clearing it. Overlapping reconnect-driven resumes therefore race:\n // a later resume clears `activeResponse` before an earlier one's finalizer\n // runs, throwing `Cannot read properties of undefined (reading 'state')`\n // (#1837). The `onAgentOpen` guard's `statusRef` is lagging React state and\n // `isAwaitingResume()` only covers the handshake, so neither closes the\n // post-handshake / pre-status-propagation window. Serialize resumes here:\n // never issue a new re-probe `resumeStream()` while one is outstanding.\n const resumeInFlightRef = useRef(false);\n // Generation token for the in-flight resume. Bumped whenever the gate is\n // force-reset (socket-effect cleanup, e.g. an agent swap on `_pk` change).\n // A resume's `.finally` captures its generation and only clears the flag if\n // it still matches — so a stale, orphaned resume settling after a newer one\n // has taken over can't reopen the gate underneath it.\n const resumeGenerationRef = useRef(0);\n\n const resumingToolContinuationRef = useRef(false);\n const pendingToolContinuationRef = useRef(false);\n const observedToolContinuationRequestIdRef = useRef<string | null>(null);\n const continuationLaunchTimerRef = useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n // Generation counter for tool continuations. Bumped on every\n // `startToolContinuation` entry and on any external reset path\n // (e.g. `clearHistory`). The `.finally()` handler captures its\n // generation at start time and only applies the cleanup if it still\n // matches — otherwise the promise is settling after a reset or after\n // a newer continuation has already taken over, and its reset would\n // clobber current state.\n const continuationGenerationRef = useRef(0);\n // Mirrors `resumingToolContinuationRef` as React state so consumers can\n // distinguish a user-initiated `status === \"submitted\"` from one driven\n // by a server-pushed tool continuation. The ref is kept for its\n // synchronous re-entry guard semantics; this state is purely for UI.\n // See issue #1365.\n const [isToolContinuation, setIsToolContinuation] = useState(false);\n\n // Shared reset for every path that wipes chat history — the local\n // `clearHistory()` call AND the server-pushed `CF_AGENT_CHAT_CLEAR`\n // handler (another tab or the server itself cleared the chat).\n // Without this, a tab with an in-flight tool continuation that\n // receives a cross-tab clear would render `isToolContinuation === true`\n // over an empty message list until the orphaned `resumeStream()`\n // promise eventually settles. Keep ref/state/generation in lockstep;\n // the generation bump ensures the pending `.finally()` is a no-op.\n const resetToolContinuation = useCallback(() => {\n continuationGenerationRef.current++;\n pendingToolContinuationRef.current = false;\n resumingToolContinuationRef.current = false;\n observedToolContinuationRequestIdRef.current = null;\n if (continuationLaunchTimerRef.current) {\n clearTimeout(continuationLaunchTimerRef.current);\n continuationLaunchTimerRef.current = null;\n }\n setIsToolContinuation(false);\n }, []);\n\n const scheduleToolContinuationLaunch = useCallback(() => {\n if (\n !pendingToolContinuationRef.current ||\n statusRef.current !== \"ready\" ||\n continuationLaunchTimerRef.current\n ) {\n return;\n }\n\n continuationLaunchTimerRef.current = setTimeout(() => {\n continuationLaunchTimerRef.current = null;\n\n if (\n !pendingToolContinuationRef.current ||\n statusRef.current !== \"ready\"\n ) {\n return;\n }\n\n pendingToolContinuationRef.current = false;\n const myGeneration = continuationGenerationRef.current;\n customTransport.expectToolContinuation();\n\n void resumeStream()\n .catch((error) => {\n console.error(\n \"[useAgentChat] Tool continuation resume failed:\",\n error\n );\n })\n .finally(() => {\n // Bail if a reset (clearHistory / cross-tab clear) or a newer\n // continuation has taken over since we started — otherwise this\n // stale settlement would flip the flags off while a newer\n // continuation is still in flight, and reopen the re-entry\n // guard spuriously.\n if (continuationGenerationRef.current !== myGeneration) return;\n resumingToolContinuationRef.current = false;\n setIsToolContinuation(false);\n });\n }, 0);\n }, [customTransport, resumeStream]);\n\n const startToolContinuation = useCallback(() => {\n if (!autoContinueAfterToolResult || resumingToolContinuationRef.current) {\n return;\n }\n\n ++continuationGenerationRef.current;\n resumingToolContinuationRef.current = true;\n pendingToolContinuationRef.current = true;\n setIsToolContinuation(true);\n scheduleToolContinuationLaunch();\n }, [autoContinueAfterToolResult, scheduleToolContinuationLaunch]);\n\n useEffect(() => {\n if (status === \"error\" && pendingToolContinuationRef.current) {\n resetToolContinuation();\n return;\n }\n\n scheduleToolContinuationLaunch();\n }, [resetToolContinuation, scheduleToolContinuationLaunch, status]);\n\n const stopWithToolContinuationAbort: typeof stop = useCallback(async () => {\n try {\n customTransport.cancelActiveServerTurn();\n await stop();\n } finally {\n customTransport.abortActiveToolContinuation();\n }\n }, [stop, customTransport]);\n\n const processedToolCalls = useRef(new Set<string>());\n const isResolvingToolsRef = useRef(false);\n // Counter to force the tool resolution effect to re-run after completing\n // a batch of tool calls. Without this, if new tool calls arrive while\n // isResolvingToolsRef is true (e.g. server auto-continuation), the effect\n // exits early and never retriggers because the ref reset doesn't cause\n // a re-render.\n const [toolResolutionTrigger, setToolResolutionTrigger] = useState(0);\n\n // Fix for issue #728: Track client-side tool results in local state\n // to ensure tool parts show output-available immediately after execution.\n const [clientToolResults, setClientToolResults] = useState<\n Map<string, unknown>\n >(new Map());\n\n // Ref to access current messages in callbacks without stale closures\n const messagesRef = useRef(chatMessages);\n messagesRef.current = chatMessages;\n const initialMessagesRef = useRef(initialMessages);\n initialMessagesRef.current = initialMessages;\n\n // Tracks which `initialMessagesCacheKey` we've already applied to the\n // underlying Chat. Used by the late-seed effect below, and flipped to\n // the current key whenever the chat is intentionally emptied so we\n // don't re-hydrate server history on top of a user-driven clear.\n const seededInitialMessagesKeyRef = useRef<string | null>(null);\n const markInitialMessagesSeeded = useCallback(() => {\n seededInitialMessagesKeyRef.current = initialMessagesCacheKey;\n }, [initialMessagesCacheKey]);\n\n // Late-seed: when the initial-messages promise resolves AFTER the Chat\n // was already mounted (the URL-not-ready-on-first-render case), the\n // AI SDK's `useChat({ messages })` won't re-ingest the new value.\n // This effect applies it once per cache key. If the user already sent\n // an optimistic first message, prepend any missing hydrated history\n // rather than overwriting or discarding either side. Existing in-memory\n // message copies win by ID because they may include newer stream/client\n // state than the fetch response.\n //\n // Crucially, we mark the key as handled on EVERY observation so that\n // subsequent emptying events (a server broadcast of\n // CF_AGENT_CHAT_MESSAGES with `[]`, a `setMessages([])` on this tab,\n // or an explicit `clearHistory()`) don't resurrect stale initial\n // messages on top of the clear.\n useEffect(() => {\n if (!initialMessagesPromise) {\n return;\n }\n if (seededInitialMessagesKeyRef.current === initialMessagesCacheKey) {\n return;\n }\n\n markInitialMessagesSeeded();\n setMessages((prevMessages: ChatMessage[]) =>\n prependMissingHydratedMessages(initialMessagesRef.current, prevMessages)\n );\n }, [\n initialMessagesCacheKey,\n initialMessagesPromise,\n markInitialMessagesSeeded,\n setMessages\n ]);\n\n const localResponseMessageIdsRef = useRef(new Map<string, string>());\n const protectedStreamingAssistantRef = useRef<{\n assistantId: string;\n anchorMessageId: string | null;\n } | null>(null);\n\n const preserveProtectedStreamingAssistant = useCallback(\n (messages: readonly ChatMessage[]): ChatMessage[] => {\n const protection = protectedStreamingAssistantRef.current;\n if (!protection) {\n return [...messages];\n }\n\n // If the incoming snapshot already contains the protected assistant AND a\n // later assistant message, the server transcript has advanced past the\n // message we were protecting — the snapshot is terminal/authoritative\n // (e.g. a Think HITL denial persisted the denied tool message, then\n // appended a follow-up assistant response explaining the denial).\n // Appending our protected copy to the tail would reorder the transcript\n // (#1778). Clear protection and trust the snapshot as-is; if a stream is\n // still active for the newer assistant it re-arms protection via its own\n // `start` chunk.\n const protectedIndex = messages.findIndex(\n (message) => message.id === protection.assistantId\n );\n if (\n protectedIndex >= 0 &&\n messages\n .slice(protectedIndex + 1)\n .some((message) => message.role === \"assistant\")\n ) {\n protectedStreamingAssistantRef.current = null;\n return [...messages];\n }\n\n const protectedAssistant =\n messagesRef.current.find(\n (message) => message.id === protection.assistantId\n ) ?? messages.find((message) => message.id === protection.assistantId);\n if (!protectedAssistant) {\n return [...messages];\n }\n\n return [\n ...messages.filter((message) => message.id !== protection.assistantId),\n protectedAssistant\n ];\n },\n []\n );\n\n const protectStreamingAssistantTail = useCallback(() => {\n if (statusRef.current !== \"streaming\") {\n return;\n }\n\n const assistantInfo = findLastAssistantMessage(messagesRef.current);\n if (!assistantInfo) {\n return;\n }\n\n if (\n protectedStreamingAssistantRef.current?.assistantId !==\n assistantInfo.message.id\n ) {\n protectedStreamingAssistantRef.current = {\n assistantId: assistantInfo.message.id,\n anchorMessageId:\n messagesRef.current[assistantInfo.index - 1]?.id ?? null\n };\n }\n\n setMessages((prevMessages: ChatMessage[]) => {\n const protection = protectedStreamingAssistantRef.current;\n if (!protection) {\n return prevMessages;\n }\n\n return moveMessageToEnd(prevMessages, protection.assistantId);\n });\n }, [setMessages]);\n\n const restoreProtectedStreamingAssistant = useCallback(\n (assistantId?: string) => {\n const protection = protectedStreamingAssistantRef.current;\n if (\n !protection ||\n (assistantId !== undefined && protection.assistantId !== assistantId)\n ) {\n return;\n }\n\n protectedStreamingAssistantRef.current = null;\n setMessages((prevMessages: ChatMessage[]) => {\n const sourceIdx = prevMessages.findIndex(\n (m) => m.id === protection.assistantId\n );\n if (sourceIdx < 0) return prevMessages;\n\n const result = [...prevMessages];\n const [msg] = result.splice(sourceIdx, 1);\n if (!msg) return prevMessages;\n\n if (protection.anchorMessageId === null) {\n result.unshift(msg);\n } else {\n const anchorIdx = result.findIndex(\n (m) => m.id === protection.anchorMessageId\n );\n result.splice(anchorIdx >= 0 ? anchorIdx + 1 : sourceIdx, 0, msg);\n }\n\n return result;\n });\n },\n [setMessages]\n );\n\n const resetMatchingHydratedAssistantForReplay = useCallback(\n (messageId: string) => {\n setMessages((prevMessages: ChatMessage[]) => {\n const lastMessage = prevMessages[prevMessages.length - 1];\n if (\n !lastMessage ||\n lastMessage.role !== \"assistant\" ||\n lastMessage.id !== messageId\n ) {\n return prevMessages;\n }\n\n // Initial message hydration can already contain the partially\n // persisted assistant response. Clear that assistant only once\n // replay proves it is rebuilding the same message; keeping the\n // shell preserves layout while avoiding duplicate text parts.\n replayHydratedAssistantMessageIdsRef.current.add(messageId);\n const next = [...prevMessages];\n next[next.length - 1] = { ...lastMessage, parts: [] };\n return next;\n });\n },\n [setMessages]\n );\n\n const collapseHydratedReplayTextParts = useCallback(\n (message: ChatMessage): ChatMessage => {\n const parts = message.parts;\n const nextParts = parts.filter((part, index) => {\n if (part.type !== \"text\" || !(\"text\" in part) || !part.text) {\n return true;\n }\n\n // Replayed streams rebuild from the first chunk. If the\n // hydrated assistant already had the same prefix, replay can\n // temporarily produce a second text part with the rebuilt text.\n return !parts.some((candidate, candidateIndex) => {\n if (candidateIndex <= index) return false;\n if (\n candidate.type !== \"text\" ||\n !(\"text\" in candidate) ||\n !candidate.text\n ) {\n return false;\n }\n return candidate.text.startsWith(part.text);\n });\n });\n\n return nextParts.length === parts.length\n ? message\n : { ...message, parts: nextParts };\n },\n []\n );\n\n useEffect(() => {\n if (replayHydratedAssistantMessageIdsRef.current.size === 0) return;\n\n const idsToCollapse = new Set(\n chatMessages\n .filter(\n (message) =>\n replayHydratedAssistantMessageIdsRef.current.has(message.id) &&\n message.role === \"assistant\" &&\n collapseHydratedReplayTextParts(message) !== message\n )\n .map((message) => message.id)\n );\n if (idsToCollapse.size === 0) return;\n\n setMessages((prevMessages: ChatMessage[]) => {\n let changed = false;\n const nextMessages = prevMessages.map((message) => {\n if (!idsToCollapse.has(message.id)) {\n return message;\n }\n\n const nextMessage = collapseHydratedReplayTextParts(message);\n if (nextMessage !== message) {\n changed = true;\n }\n return nextMessage;\n });\n\n return changed ? nextMessages : prevMessages;\n });\n }, [chatMessages, collapseHydratedReplayTextParts, setMessages]);\n\n // Shared reset for every path that wipes chat history — keep this\n // list in sync between `clearHistory()` (local user action) and the\n // `CF_AGENT_CHAT_CLEAR` broadcast handler (server/other-tab action).\n // Anything reset here must be safe to reset either way; broadcast-\n // specific state (`streamStateRef`, `isServerStreaming`) stays in\n // the broadcast handler because it describes cross-tab/server\n // streams that a local `clearHistory()` can't meaningfully cancel.\n const resetLocalChatState = useCallback(() => {\n markInitialMessagesSeeded();\n setMessages([]);\n setClientToolResults(new Map());\n setPendingOnToolCallIds(new Set());\n resetToolContinuation();\n processedToolCalls.current.clear();\n localResponseMessageIdsRef.current.clear();\n pendingReplayResumeRequestIdsRef.current.clear();\n fallbackAckedResumeRequestIdsRef.current.clear();\n replayHydratedAssistantMessageIdsRef.current.clear();\n protectedStreamingAssistantRef.current = null;\n }, [markInitialMessagesSeeded, setMessages, resetToolContinuation]);\n\n const sendMessageWithStreamingProtection: typeof sendMessage = useCallback(\n async (message, options) => {\n const request = sendMessage(message, options);\n\n if (\n message !== undefined &&\n !(\n typeof message === \"object\" &&\n message !== null &&\n \"messageId\" in message &&\n message.messageId != null\n )\n ) {\n protectStreamingAssistantTail();\n }\n\n return request;\n },\n [sendMessage, protectStreamingAssistantTail]\n );\n\n // Calculate pending confirmations for the latest assistant message\n const lastMessage = chatMessages[chatMessages.length - 1];\n\n const pendingConfirmations = (() => {\n if (!lastMessage || lastMessage.role !== \"assistant\") {\n return { messageId: undefined, toolCallIds: new Set<string>() };\n }\n\n const pendingIds = new Set<string>();\n for (const part of lastMessage.parts ?? []) {\n if (\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n toolsRequiringConfirmation.includes(getToolName(part))\n ) {\n pendingIds.add(part.toolCallId);\n }\n }\n return { messageId: lastMessage.id, toolCallIds: pendingIds };\n })();\n\n const pendingConfirmationsRef = useRef(pendingConfirmations);\n pendingConfirmationsRef.current = pendingConfirmations;\n const [pendingOnToolCallIds, setPendingOnToolCallIds] = useState<Set<string>>(\n () => new Set()\n );\n\n const finishOnToolCall = useCallback((toolCallId: string) => {\n setPendingOnToolCallIds((prev) => {\n if (!prev.has(toolCallId)) return prev;\n const next = new Set(prev);\n next.delete(toolCallId);\n return next;\n });\n }, []);\n\n // ── DEPRECATED: automatic tool resolution effect ────────────────────\n // This entire useEffect is deprecated. Use onToolCall instead.\n useEffect(() => {\n if (!experimental_automaticToolResolution) {\n return;\n }\n\n void toolResolutionTrigger;\n\n // Prevent re-entry while async operations are in progress\n if (isResolvingToolsRef.current) {\n return;\n }\n\n const lastMsg = chatMessages[chatMessages.length - 1];\n if (!lastMsg || lastMsg.role !== \"assistant\") {\n return;\n }\n\n const toolCalls = lastMsg.parts.filter(\n (part) =>\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n !processedToolCalls.current.has(part.toolCallId)\n );\n\n if (toolCalls.length > 0) {\n // Capture tools synchronously before async work\n const currentTools = toolsRef.current;\n const toolCallsToResolve = toolCalls.filter(\n (part) =>\n isToolUIPart(part) &&\n !toolsRequiringConfirmation.includes(getToolName(part)) &&\n currentTools?.[getToolName(part)]?.execute\n );\n\n if (toolCallsToResolve.length > 0) {\n isResolvingToolsRef.current = true;\n\n (async () => {\n try {\n const toolResults: Array<{\n toolCallId: string;\n toolName: string;\n output: unknown;\n }> = [];\n\n for (const part of toolCallsToResolve) {\n if (isToolUIPart(part)) {\n let toolOutput: unknown = null;\n const toolName = getToolName(part);\n const tool = currentTools?.[toolName];\n\n if (tool?.execute && part.input !== undefined) {\n try {\n toolOutput = await tool.execute(part.input);\n } catch (error) {\n toolOutput = `Error executing tool: ${error instanceof Error ? error.message : String(error)}`;\n }\n }\n\n processedToolCalls.current.add(part.toolCallId);\n\n toolResults.push({\n toolCallId: part.toolCallId,\n toolName,\n output: toolOutput\n });\n }\n }\n\n if (toolResults.length > 0) {\n // Send tool results to server first (server is source of truth)\n const clientToolSchemas = extractClientToolSchemas(currentTools);\n for (const result of toolResults) {\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId: result.toolCallId,\n toolName: result.toolName,\n output: result.output,\n autoContinue: autoContinueAfterToolResult,\n clientTools: clientToolSchemas\n })\n );\n }\n\n // Also update local state via AI SDK for immediate UI feedback\n await Promise.all(\n toolResults.map((result) =>\n addToolResult({\n tool: result.toolName,\n toolCallId: result.toolCallId,\n output: result.output\n })\n )\n );\n\n setClientToolResults((prev) => {\n const newMap = new Map(prev);\n for (const result of toolResults) {\n newMap.set(result.toolCallId, result.output);\n }\n return newMap;\n });\n\n startToolContinuation();\n }\n\n // Note: We don't call sendMessage() here anymore.\n // The server will continue the conversation after applying tool results.\n } finally {\n isResolvingToolsRef.current = false;\n // Trigger a re-run so any tool calls that arrived while we were\n // busy (e.g. from server auto-continuation) get picked up.\n setToolResolutionTrigger((c) => c + 1);\n }\n })();\n }\n }\n }, [\n chatMessages,\n experimental_automaticToolResolution,\n addToolResult,\n toolsRequiringConfirmation,\n autoContinueAfterToolResult,\n startToolContinuation,\n toolResolutionTrigger\n ]);\n\n // Helper function to send tool output to server\n const sendToolOutputToServer = useCallback(\n (\n toolCallId: string,\n toolName: string,\n output: unknown,\n state?: \"output-available\" | \"output-error\",\n errorText?: string\n ) => {\n const shouldAutoContinue =\n state === \"output-error\" ? false : autoContinueAfterToolResult;\n\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId,\n toolName,\n output,\n ...(state ? { state } : {}),\n ...(errorText !== undefined ? { errorText } : {}),\n // output-error is a deliberate client action — don't auto-continue.\n // This differs from addToolApprovalResponse (which auto-continues for\n // both approvals and rejections). To have the LLM respond to the error,\n // call sendMessage() after addToolOutput.\n autoContinue: shouldAutoContinue,\n clientTools: toolsRef.current\n ? extractClientToolSchemas(toolsRef.current)\n : undefined\n })\n );\n\n if (state !== \"output-error\") {\n setClientToolResults((prev) => new Map(prev).set(toolCallId, output));\n }\n\n if (shouldAutoContinue) {\n startToolContinuation();\n }\n },\n [autoContinueAfterToolResult, startToolContinuation]\n );\n\n // Helper function to send tool approval to server\n const sendToolApprovalToServer = useCallback(\n (toolCallId: string, approved: boolean) => {\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_APPROVAL,\n toolCallId,\n approved,\n autoContinue: autoContinueAfterToolResult\n })\n );\n\n if (autoContinueAfterToolResult) {\n startToolContinuation();\n }\n },\n [autoContinueAfterToolResult, startToolContinuation]\n );\n\n // Effect for new onToolCall callback pattern (v6 style)\n // This fires when there are tool calls that need client-side handling\n useEffect(() => {\n const currentOnToolCall = onToolCallRef.current;\n if (!currentOnToolCall) {\n return;\n }\n\n const lastMsg = chatMessages[chatMessages.length - 1];\n if (!lastMsg || lastMsg.role !== \"assistant\") {\n return;\n }\n\n // Find tool calls in input-available state that haven't been processed\n const pendingToolCalls = lastMsg.parts.filter(\n (part) =>\n isToolUIPart(part) &&\n part.state === \"input-available\" &&\n !processedToolCalls.current.has(part.toolCallId)\n );\n\n for (const part of pendingToolCalls) {\n if (isToolUIPart(part)) {\n const toolCallId = part.toolCallId;\n const toolName = getToolName(part);\n\n // Mark as processed to prevent re-triggering\n processedToolCalls.current.add(toolCallId);\n\n // track pending `onToolCall` calls to derive streaming state\n setPendingOnToolCallIds((prev) => {\n if (prev.has(toolCallId)) return prev;\n const next = new Set(prev);\n next.add(toolCallId);\n return next;\n });\n\n // Create addToolOutput function for this specific tool call\n const addToolOutput = (opts: AddToolOutputOptions) => {\n sendToolOutputToServer(\n opts.toolCallId,\n toolName,\n opts.output,\n opts.state,\n opts.errorText\n );\n\n // Update local state via AI SDK\n addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output:\n opts.state === \"output-error\"\n ? (opts.errorText ?? \"Tool execution denied by user\")\n : opts.output\n });\n };\n\n // Call the onToolCall callback\n // The callback is responsible for calling addToolOutput when ready\n let result: ReturnType<OnToolCallCallback>;\n try {\n result = currentOnToolCall({\n toolCall: { toolCallId, toolName, input: part.input },\n addToolOutput\n });\n } catch (error) {\n finishOnToolCall(toolCallId);\n throw error;\n }\n void Promise.resolve(result).finally(() => {\n finishOnToolCall(toolCallId);\n });\n }\n }\n }, [chatMessages, sendToolOutputToServer, addToolResult, finishOnToolCall]);\n\n const streamStateRef = useRef<BroadcastStreamState>({ status: \"idle\" });\n\n const [isServerStreaming, setIsServerStreaming] = useState(false);\n // #1620: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Driven\n // by the server's `CF_AGENT_CHAT_RECOVERING` frames; surfaced as a \"working,\n // not frozen\" hint distinct from active streaming.\n const [isRecovering, setIsRecovering] = useState(false);\n\n useEffect(() => {\n const localResponseIds = localResponseMessageIdsRef.current;\n\n /**\n * Unified message handler that parses JSON once and dispatches based on type.\n * Avoids duplicate parsing overhead from separate listeners.\n */\n function onAgentMessage(event: MessageEvent) {\n if (typeof event.data !== \"string\") return;\n\n let data: OutgoingMessage<ChatMessage>;\n try {\n data = JSON.parse(event.data) as OutgoingMessage<ChatMessage>;\n } catch (_error) {\n return;\n }\n\n switch (data.type) {\n case MessageType.CF_AGENT_CHAT_CLEAR:\n // Broadcast-specific resets (cross-tab stream tracking).\n streamStateRef.current = broadcastTransition(streamStateRef.current, {\n type: \"clear\"\n }).state;\n setIsServerStreaming(false);\n setIsRecovering(false);\n // Shared local-state reset — see `resetLocalChatState`.\n resetLocalChatState();\n break;\n\n case MessageType.CF_AGENT_CHAT_RECOVERING:\n // Durable recovery progress hint (#1620): show \"recovering…\" while a\n // turn is being resumed. Cleared by the server's `recovering: false`\n // frame on any terminal outcome (and locally on stream-resume /\n // terminal response / clear below, for a snappy handoff).\n setIsRecovering(Boolean(data.recovering));\n break;\n\n case MessageType.CF_AGENT_CHAT_MESSAGES: {\n let next = preserveProtectedStreamingAssistant(data.messages);\n // A cross-tab observer builds the in-flight assistant via the\n // broadcast accumulator, not the local transport — so\n // `protectedStreamingAssistantRef` is never armed for it. Without\n // this, a behind-the-stream snapshot would replace the observed\n // assistant's streamed parts until the next chunk re-merges them\n // (the same disappear/reappear the originating tab gets without the\n // start-chunk re-arm above). Re-apply the accumulator — it adopted\n // the server id from the `start` chunk, so `mergeInto` replaces the\n // snapshot's copy in place (or appends a not-yet-persisted turn).\n const observed = streamStateRef.current;\n if (\n observed.status === \"observing\" &&\n observed.accumulator.parts.length > 0\n ) {\n // Only re-apply the live accumulator when it is at least as\n // complete as the snapshot's copy of the same message. A fresh\n // observer rebuilding its accumulator from a chunk-0 replay can\n // briefly trail a fully-persisted snapshot; merging then would drop\n // parts until replay catches up. In steady-state live observing the\n // accumulator is always at or ahead of the snapshot, so this still\n // fixes the disappear/reappear flicker.\n const snapshotIdx = next.findIndex(\n (m) => m.id === observed.accumulator.messageId\n );\n const snapshotParts =\n snapshotIdx >= 0 ? next[snapshotIdx].parts.length : 0;\n if (observed.accumulator.parts.length >= snapshotParts) {\n next = observed.accumulator.mergeInto(next) as ChatMessage[];\n }\n }\n setMessages(next);\n break;\n }\n\n case MessageType.CF_AGENT_MESSAGE_UPDATED:\n // Server updated a message (e.g., applied tool result)\n // Update the specific message in local state\n setMessages((prevMessages: ChatMessage[]) => {\n const updatedMessage = data.message;\n\n // First try to find by message ID\n let idx = prevMessages.findIndex((m) => m.id === updatedMessage.id);\n\n // If not found by ID, try to find by toolCallId\n // This handles the case where client has AI SDK-generated IDs\n // but server has server-generated IDs\n if (idx < 0) {\n const updatedToolCallIds = new Set(\n updatedMessage.parts\n .filter(\n (p: ChatMessage[\"parts\"][number]) =>\n \"toolCallId\" in p && p.toolCallId\n )\n .map(\n (p: ChatMessage[\"parts\"][number]) =>\n (p as { toolCallId: string }).toolCallId\n )\n );\n\n if (updatedToolCallIds.size > 0) {\n idx = prevMessages.findIndex((m) =>\n m.parts.some(\n (p) =>\n \"toolCallId\" in p &&\n updatedToolCallIds.has(\n (p as { toolCallId: string }).toolCallId\n )\n )\n );\n }\n }\n\n if (idx >= 0) {\n const updated = [...prevMessages];\n // Preserve the client's message ID but update the content\n updated[idx] = {\n ...updatedMessage,\n id: prevMessages[idx].id\n };\n return updated;\n }\n // Message not found — don't append. CF_AGENT_MESSAGE_UPDATED is\n // for updating existing messages (e.g. tool result/approval state\n // changes), not for adding new ones. If the message isn't in\n // client state yet, it will arrive via the transport stream\n // (same tab) or CF_AGENT_CHAT_MESSAGES (cross-tab).\n // Appending here causes temporary duplicates (#1094).\n return prevMessages;\n });\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUME_NONE:\n // Server confirmed no active stream — let the transport\n // resolve reconnectToStream immediately with null.\n customTransport.handleStreamResumeNone();\n break;\n\n case MessageType.CF_AGENT_STREAM_PENDING:\n // Server accepted a turn but its stream hasn't started yet (#1784):\n // tell the transport to keep waiting so its resume probe doesn't\n // resolve \"no stream\" mid pre-stream window. A later STREAM_RESUMING\n // (stream started) or STREAM_RESUME_NONE (settled without streaming)\n // resolves it. Advisory for clients not awaiting a resume.\n customTransport.handleStreamPending();\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUMING: {\n const isEarlyToolContinuation =\n resumingToolContinuationRef.current &&\n !customTransport.isAwaitingResume();\n if (!resume && !customTransport.isAwaitingResume()) {\n if (!isEarlyToolContinuation) return;\n }\n if (!resumingToolContinuationRef.current) {\n pendingReplayResumeRequestIdsRef.current.add(data.id);\n }\n // Let the transport handle it if reconnectToStream is waiting.\n // This is called synchronously — no addEventListener race.\n // The transport sends ACK, adds to activeRequestIds, and\n // creates the ReadableStream that feeds into useChat's pipeline\n // (which correctly sets status to \"streaming\"). This runs BEFORE\n // the fallback-ACK dedupe below so a fallback-observed stream can\n // still become transport-owned via a later resumeStream() — the\n // transport's replay is isolated from the broadcast accumulator\n // by localRequestIdsRef, so it cannot duplicate parts.\n if (customTransport.handleStreamResuming(data)) {\n return;\n }\n // Skip if the transport already handled this stream's resume\n // (server sends STREAM_RESUMING from both onConnect and the\n // RESUME_REQUEST handler — the second one must not trigger\n // a duplicate ACK / replay).\n if (localRequestIdsRef.current.has(data.id)) return;\n // Duplicate offer for a stream this socket already ACKed via the\n // fallback path (#1733): the server notifies from both onConnect\n // and the RESUME_REQUEST handler. With nobody waiting on the\n // handshake, re-ACKing would only trigger a second full-buffer\n // replay into the same accumulator; drop it.\n if (fallbackAckedResumeRequestIdsRef.current.has(data.id)) return;\n if (isEarlyToolContinuation) {\n pendingToolContinuationRef.current = false;\n observedToolContinuationRequestIdRef.current = data.id;\n if (continuationLaunchTimerRef.current) {\n clearTimeout(continuationLaunchTimerRef.current);\n continuationLaunchTimerRef.current = null;\n }\n }\n // Fallback for cross-tab broadcasts or cases where the\n // transport isn't expecting a resume.\n streamStateRef.current = broadcastTransition(streamStateRef.current, {\n type: \"resume-fallback\",\n streamId: data.id,\n messageId: nanoid()\n }).state;\n customTransport.observeServerTurn(data.id);\n setIsServerStreaming(true);\n // The recovered turn is now streaming live to us — it's no longer\n // \"recovering\", it's producing the answer (#1620).\n setIsRecovering(false);\n // Remember the ACK so a duplicate STREAM_RESUMING for the same\n // request on this socket doesn't trigger a second replay (#1733).\n fallbackAckedResumeRequestIdsRef.current.add(data.id);\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: data.id\n })\n );\n break;\n }\n\n case MessageType.CF_AGENT_USE_CHAT_RESPONSE: {\n if (localRequestIdsRef.current.has(data.id)) {\n if (data.body?.trim()) {\n try {\n const chunkData = JSON.parse(data.body) as {\n messageId?: string;\n type?: string;\n };\n if (\n chunkData.type === \"start\" &&\n typeof chunkData.messageId === \"string\"\n ) {\n localResponseIds.set(data.id, chunkData.messageId);\n // Re-arm streaming protection to the ACTUAL assistant id for\n // this turn. `protectStreamingAssistantTail` runs at send\n // time — before the assistant message is minted — so it can\n // only latch the PREVIOUS turn's id (or nothing on the first\n // turn). Without correcting it here, a mid-stream full-list\n // broadcast (`CF_AGENT_CHAT_MESSAGES`, which Think emits after\n // every tool result) replaces the live-streamed assistant\n // with a possibly-behind server snapshot, so its parts (e.g.\n // tool cards) briefly disappear and reappear. Continuations\n // are skipped: they extend the existing protected assistant.\n if (!data.continuation) {\n const protection = protectedStreamingAssistantRef.current;\n if (protection?.assistantId !== chunkData.messageId) {\n const msgs = messagesRef.current;\n const idx = msgs.findIndex(\n (m) => m.id === chunkData.messageId\n );\n const anchorMessageId =\n idx >= 0\n ? (msgs[idx - 1]?.id ?? null)\n : (msgs[msgs.length - 1]?.id ?? null);\n protectedStreamingAssistantRef.current = {\n assistantId: chunkData.messageId,\n anchorMessageId\n };\n }\n }\n // EVERY replayed `start` rebuilds the message from chunk 0,\n // so the matching trailing assistant must be reset each\n // time — not only while the resume request id is still\n // pending (#1733: a second replay otherwise stacks a\n // duplicate text part). Continuation replays are excluded:\n // they append to the existing assistant message, and\n // wiping it would drop the pre-continuation parts.\n if (\n data.replay &&\n !data.continuation &&\n !resumingToolContinuationRef.current &&\n observedToolContinuationRequestIdRef.current !== data.id\n ) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n resetMatchingHydratedAssistantForReplay(\n chunkData.messageId\n );\n }\n }\n } catch {\n // Ignore malformed local stream chunks.\n }\n }\n\n if (data.done || data.replayComplete) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n }\n if (data.done) {\n if (\n streamStateRef.current.status === \"observing\" &&\n streamStateRef.current.streamId === data.id\n ) {\n streamStateRef.current = { status: \"idle\" };\n setIsServerStreaming(false);\n }\n customTransport.handleServerTurnCompleted(data.id);\n restoreProtectedStreamingAssistant(localResponseIds.get(data.id));\n localResponseIds.delete(data.id);\n localRequestIdsRef.current.delete(data.id);\n fallbackAckedResumeRequestIdsRef.current.delete(data.id);\n }\n return;\n }\n\n let chunkData: unknown;\n if (\n data.replay &&\n streamStateRef.current.status !== \"observing\" &&\n !pendingReplayResumeRequestIdsRef.current.has(data.id)\n ) {\n return;\n }\n if (data.body?.trim()) {\n try {\n chunkData = JSON.parse(data.body);\n // Reset on EVERY replayed `start` (not only while the resume\n // request id is pending): replay rebuilds from chunk 0, so a\n // second replay whose `start` skipped the reset would stack a\n // duplicate text part on the frozen first one (#1733).\n // Continuation replays are excluded — they append to the\n // existing assistant message, and wiping it would drop the\n // pre-continuation parts.\n if (\n data.replay &&\n !data.continuation &&\n !resumingToolContinuationRef.current &&\n observedToolContinuationRequestIdRef.current !== data.id &&\n typeof (chunkData as Record<string, unknown>).messageId ===\n \"string\" &&\n (chunkData as Record<string, unknown>).type === \"start\"\n ) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n resetMatchingHydratedAssistantForReplay(\n (chunkData as { messageId: string }).messageId\n );\n }\n if (\n typeof (chunkData as Record<string, unknown>).type ===\n \"string\" &&\n (\n (chunkData as Record<string, unknown>).type as string\n ).startsWith(\"data-\") &&\n onDataRef.current\n ) {\n onDataRef.current(\n chunkData as Parameters<\n NonNullable<typeof onDataRef.current>\n >[0]\n );\n }\n } catch (parseError) {\n console.warn(\n \"[useAgentChat] Failed to parse stream chunk:\",\n parseError instanceof Error ? parseError.message : parseError,\n \"body:\",\n data.body?.slice(0, 100)\n );\n }\n }\n if (data.done || data.replayComplete) {\n pendingReplayResumeRequestIdsRef.current.delete(data.id);\n }\n if (data.done) {\n customTransport.handleServerTurnCompleted(data.id);\n fallbackAckedResumeRequestIdsRef.current.delete(data.id);\n // A terminal turn outcome resolves any in-progress recovery (#1620).\n setIsRecovering(false);\n }\n const completedObservedToolContinuation =\n data.done &&\n observedToolContinuationRequestIdRef.current === data.id;\n\n const result = broadcastTransition(streamStateRef.current, {\n type: \"response\",\n streamId: data.id,\n messageId: nanoid(),\n chunkData,\n done: data.done,\n error: data.error,\n replay: data.replay,\n replayComplete: data.replayComplete,\n continuation: data.continuation,\n currentMessages: data.continuation ? messagesRef.current : undefined\n });\n\n streamStateRef.current = result.state;\n if (result.messagesUpdate) {\n setMessages(\n result.messagesUpdate as unknown as (\n prev: ChatMessage[]\n ) => ChatMessage[]\n );\n }\n setIsServerStreaming(result.isStreaming);\n if (completedObservedToolContinuation) {\n resetToolContinuation();\n }\n break;\n }\n }\n }\n\n const fallbackAckedResumeRequestIds =\n fallbackAckedResumeRequestIdsRef.current;\n\n // A closed socket invalidates the per-socket resume-ACK dedupe: after a\n // reconnect the server sees a brand-new connection and must be ACKed\n // (and replay) again, so the previous entries must not suppress it.\n //\n // NOTE: deliberately does NOT reset `resumeInFlightRef` (#1837). The flag\n // is owned by the in-flight resume and cleared only by its own `.finally`\n // (or invalidated via the generation bump in this effect's cleanup).\n // Clearing it here would set it false while the resume may still be\n // mid-flight (post-handshake, before `makeRequest`'s finalizer runs),\n // re-coupling correctness to close/open task ordering and reopening the\n // overlap window. It is also unnecessary: a handshake-phase drop is already\n // gated by `isAwaitingResume()` (and the pending promise's resolver re-binds\n // to the new socket), and a streaming-phase drop closes the resume stream,\n // which settles `makeRequest` and clears the flag via its `.finally`.\n function onAgentClose() {\n fallbackAckedResumeRequestIds.clear();\n }\n\n // Reconnect re-probe (#1784): a transparent socket reconnect (e.g. a 1006\n // drop) does NOT remount the component, so the AI SDK's mount-time resume\n // never re-fires and a turn that was still pre-stream when the socket\n // dropped would leave AI SDK `status` stuck at \"ready\" even after the\n // server starts streaming. On every reconnect (not the first open) re-probe\n // through the transport so `status` recovers, mirroring a fresh mount. The\n // proactive STREAM_RESUMING the server also sends on connect is reconciled\n // with this probe by the existing #1733 dual-notify dedupe.\n function onAgentOpen() {\n if (!hasConnectedOnceRef.current) {\n hasConnectedOnceRef.current = true;\n return;\n }\n if (\n !resume ||\n statusRef.current !== \"ready\" ||\n resumingToolContinuationRef.current ||\n resumeInFlightRef.current ||\n customTransport.isAwaitingResume()\n ) {\n return;\n }\n const resumeStreamFn = resumeStreamRef.current;\n if (!resumeStreamFn) return;\n // Serialize resumes (#1837): hold the in-flight flag for the entire\n // resume lifetime so a rapid second `open` (reconnect storm) can't\n // overwrite the AI SDK's shared `activeResponse` mid-flight.\n resumeInFlightRef.current = true;\n const myGeneration = resumeGenerationRef.current;\n void Promise.resolve(resumeStreamFn())\n .catch(() => {})\n .finally(() => {\n // Skip if the gate was force-reset (agent swap) and a newer resume\n // has since taken over — clearing here would reopen it spuriously.\n if (resumeGenerationRef.current !== myGeneration) return;\n resumeInFlightRef.current = false;\n });\n }\n\n agent.addEventListener(\"message\", onAgentMessage);\n agent.addEventListener(\"close\", onAgentClose);\n agent.addEventListener(\"open\", onAgentOpen);\n\n // Stream resume is now primarily handled by the transport's\n // reconnectToStream (which sends CF_AGENT_STREAM_RESUME_REQUEST).\n // The onAgentMessage handler above serves as fallback for cross-tab\n // broadcasts and cases where the transport didn't handle the resume.\n\n return () => {\n agent.removeEventListener(\"message\", onAgentMessage);\n agent.removeEventListener(\"close\", onAgentClose);\n agent.removeEventListener(\"open\", onAgentOpen);\n fallbackAckedResumeRequestIds.clear();\n streamStateRef.current = { status: \"idle\" };\n setIsServerStreaming(false);\n setIsRecovering(false);\n protectedStreamingAssistantRef.current = null;\n localResponseIds.clear();\n // Don't let an orphaned re-probe (e.g. agent swapped on `_pk` change)\n // leave the serialization gate stuck closed for the next socket (#1837).\n // Bump the generation so the orphaned resume's late `.finally` can't\n // clear the gate out from under a newer resume on the next socket.\n resumeGenerationRef.current++;\n resumeInFlightRef.current = false;\n };\n }, [\n agent,\n setMessages,\n resume,\n customTransport,\n preserveProtectedStreamingAssistant,\n resetToolContinuation,\n resetMatchingHydratedAssistantForReplay,\n restoreProtectedStreamingAssistant,\n resetLocalChatState\n ]);\n\n // ── DEPRECATED: addToolResult wrapper with confirmation batching ────\n // This wrapper is deprecated. Use addToolOutput or addToolApprovalResponse instead.\n const addToolResultAndSendMessage: typeof addToolResult = async (args) => {\n const { toolCallId } = args;\n const toolName = \"tool\" in args ? args.tool : \"\";\n const output = \"output\" in args ? args.output : undefined;\n\n // Send tool result to server (server is source of truth)\n // Include flag to tell server whether to auto-continue\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId,\n toolName,\n output,\n autoContinue: autoContinueAfterToolResult,\n clientTools: toolsRef.current\n ? extractClientToolSchemas(toolsRef.current)\n : undefined\n })\n );\n\n setClientToolResults((prev) => new Map(prev).set(toolCallId, output));\n\n // Call AI SDK's addToolResult for local state update (non-blocking)\n // We don't await this since clientToolResults provides immediate UI feedback\n addToolResult(args);\n\n if (autoContinueAfterToolResult) {\n startToolContinuation();\n }\n\n // If server auto-continuation is disabled, client needs to trigger continuation\n if (!autoContinueAfterToolResult) {\n // Use legacy behavior: batch confirmations or send immediately\n if (!autoSendAfterAllConfirmationsResolved) {\n // Always send immediately\n sendMessage();\n return;\n }\n\n // Wait for all confirmations before sending\n const pending = pendingConfirmationsRef.current?.toolCallIds;\n if (!pending) {\n sendMessage();\n return;\n }\n\n const wasLast = pending.size === 1 && pending.has(toolCallId);\n if (pending.has(toolCallId)) {\n pending.delete(toolCallId);\n }\n\n if (wasLast || pending.size === 0) {\n sendMessage();\n }\n }\n // If autoContinueAfterToolResult is true, server handles continuation\n };\n\n // Wrapper that sends tool approval to server before updating local state.\n // This prevents duplicate messages by ensuring server updates the message\n // in place with the existing ID, rather than relying on ID resolution\n // when sendMessage() is called later.\n const addToolApprovalResponseAndNotifyServer: typeof addToolApprovalResponse =\n (args) => {\n const { id: approvalId, approved } = args;\n\n // Find the toolCallId from the approval ID\n // The approval ID is stored on the tool part's approval.id field\n let toolCallId: string | undefined;\n for (const msg of messagesRef.current) {\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"approval\" in part &&\n (part.approval as { id?: string })?.id === approvalId\n ) {\n toolCallId = part.toolCallId as string;\n break;\n }\n }\n if (toolCallId) break;\n }\n\n if (toolCallId) {\n // Send approval to server first (server updates message in place)\n sendToolApprovalToServer(toolCallId, approved);\n } else {\n console.warn(\n `[useAgentChat] addToolApprovalResponse: Could not find toolCallId for approval ID \"${approvalId}\". ` +\n \"Server will not be notified, which may cause duplicate messages.\"\n );\n }\n\n // Call AI SDK's addToolApprovalResponse for local state update\n addToolApprovalResponse(args);\n };\n\n // Fix for issue #728: Merge client-side tool results with messages\n // so tool parts show output-available immediately after execution\n const messagesWithToolResults = useMemo(() => {\n if (clientToolResults.size === 0) {\n return chatMessages;\n }\n return chatMessages.map((msg) => ({\n ...msg,\n parts: msg.parts.map((p) => {\n if (\n !(\"toolCallId\" in p) ||\n !(\"state\" in p) ||\n p.state !== \"input-available\" ||\n !clientToolResults.has(p.toolCallId)\n ) {\n return p;\n }\n return {\n ...p,\n state: \"output-available\" as const,\n output: clientToolResults.get(p.toolCallId)\n };\n })\n })) as ChatMessage[];\n }, [chatMessages, clientToolResults]);\n\n // Cleanup stale entries from clientToolResults when messages change\n // to prevent memory leak in long conversations.\n // Note: We intentionally exclude clientToolResults from deps to avoid infinite loops.\n // The functional update form gives us access to the previous state.\n useEffect(() => {\n // Collect all current toolCallIds from messages\n const currentToolCallIds = new Set<string>();\n for (const msg of chatMessages) {\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId) {\n currentToolCallIds.add(part.toolCallId);\n }\n }\n }\n\n // Use functional update to check and clean stale entries atomically\n setClientToolResults((prev) => {\n if (prev.size === 0) return prev;\n\n // Check if any entries are stale\n let hasStaleEntries = false;\n for (const toolCallId of prev.keys()) {\n if (!currentToolCallIds.has(toolCallId)) {\n hasStaleEntries = true;\n break;\n }\n }\n\n // Only create new Map if there are stale entries to remove\n if (!hasStaleEntries) return prev;\n\n const newMap = new Map<string, unknown>();\n for (const [id, output] of prev) {\n if (currentToolCallIds.has(id)) {\n newMap.set(id, output);\n }\n }\n return newMap;\n });\n\n // Also cleanup processedToolCalls to prevent issues in long conversations\n for (const toolCallId of processedToolCalls.current) {\n if (!currentToolCallIds.has(toolCallId)) {\n processedToolCalls.current.delete(toolCallId);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [chatMessages]);\n\n // Create addToolOutput function for external use\n const addToolOutput = useCallback(\n (opts: AddToolOutputOptions) => {\n const toolName = opts.toolName ?? \"\";\n sendToolOutputToServer(\n opts.toolCallId,\n toolName,\n opts.output,\n opts.state,\n opts.errorText\n );\n\n // Update local state via AI SDK\n addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output:\n opts.state === \"output-error\"\n ? (opts.errorText ?? \"Tool execution denied by user\")\n : opts.output\n });\n },\n [sendToolOutputToServer, addToolResult]\n );\n\n // Derive whether there are unresolved client-side tool calls on the\n // latest assistant message. The AI SDK's `streamText` on the server\n // ends the stream as soon as it emits a tool-call the server can't\n // execute, which drops `status` back to \"ready\" while the client's\n // async `onToolCall` handler is still running. Without this signal,\n // consumers see a blank \"nothing happening\" window for the full\n // duration of the client-side work — often a `fetch` taking seconds.\n //\n // We scope this to tool calls that have actual client-side work in flight:\n // - an `onToolCall` callback promise is still pending, OR\n // - a matching entry in the deprecated `tools` option has `execute`.\n // Tools waiting on explicit user confirmation are excluded — nothing\n // is happening until the user acts, so the \"busy\" indicator would be\n // misleading.\n //\n // Derivation (not a counter / not effect-tracked) so that the flag\n // self-heals as soon as the tool part transitions to `output-available`\n // via `addToolOutput` → `addToolResult`, or to any other terminal\n // state via a server-pushed message update.\n const lastAssistantMessage =\n messagesWithToolResults[messagesWithToolResults.length - 1];\n const hasPendingClientToolCalls = (() => {\n if (pendingOnToolCallIds.size === 0 && !tools) return false;\n if (!lastAssistantMessage || lastAssistantMessage.role !== \"assistant\") {\n return false;\n }\n for (const part of lastAssistantMessage.parts) {\n if (!isToolUIPart(part)) continue;\n if (part.state !== \"input-available\") continue;\n const toolName = getToolName(part);\n if (toolsRequiringConfirmation.includes(toolName)) continue;\n if (pendingOnToolCallIds.has(part.toolCallId)) return true;\n if (tools?.[toolName]?.execute) return true;\n }\n return false;\n })();\n\n const effectiveIsServerStreaming =\n isServerStreaming || hasPendingClientToolCalls;\n const isStreaming = status === \"streaming\" || effectiveIsServerStreaming;\n\n return {\n ...useChatHelpers,\n messages: messagesWithToolResults,\n isServerStreaming: effectiveIsServerStreaming,\n isStreaming,\n /**\n * True while a durable chat turn is being recovered (interrupted by a\n * deploy/eviction or a stream-stall watchdog abort and now resuming, #1620).\n * Distinct from `isStreaming` — a recovering turn isn't producing tokens\n * yet. Render a \"recovering…\" hint; most UIs treat `isStreaming ||\n * isRecovering` as \"busy\". Cleared automatically on the next stream/terminal.\n */\n isRecovering,\n isToolContinuation,\n connectionError: agent.connectionError ?? null,\n sendMessage: sendMessageWithStreamingProtection,\n stop: stopWithToolContinuationAbort,\n /**\n * Provide output for a tool call. Use this for tools that require user interaction\n * or client-side execution.\n */\n addToolOutput,\n /**\n * @deprecated Use `addToolOutput` instead.\n */\n addToolResult: addToolResultAndSendMessage,\n /**\n * Respond to a tool approval request. Use this for tools with `needsApproval`.\n * This wrapper notifies the server before updating local state, preventing\n * duplicate messages when sendMessage() is called afterward.\n */\n addToolApprovalResponse: addToolApprovalResponseAndNotifyServer,\n clearHistory: () => {\n resetLocalChatState();\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_CLEAR\n })\n );\n },\n setMessages: (messagesOrUpdater: Parameters<typeof setMessages>[0]) => {\n // Resolve functional updaters to get the actual messages array\n // before syncing to server. Without this, updater functions would\n // send an empty array and wipe server-side messages.\n let resolvedMessages: ChatMessage[];\n if (typeof messagesOrUpdater === \"function\") {\n resolvedMessages = messagesOrUpdater(messagesRef.current);\n } else {\n resolvedMessages = messagesOrUpdater;\n }\n\n if (resolvedMessages.length === 0) {\n markInitialMessagesSeeded();\n }\n setMessages(resolvedMessages);\n if (syncMessagesToServer) {\n agent.send(\n JSON.stringify({\n messages: resolvedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n })\n );\n }\n }\n };\n}\n"],"mappings":"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"}
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./types.js";
|
|
2
2
|
import { camelCaseToKebabCase } from "./utils.js";
|
|
3
3
|
import { AgentConnectionError, createStubProxy, isTerminalCloseEvent } from "./client.js";
|
|
4
|
-
import { n as applyAgentToolEvent, r as createAgentToolEventState } from "./agent-tools-
|
|
4
|
+
import { n as applyAgentToolEvent, r as createAgentToolEventState } from "./agent-tools-y7zLfw4Q.js";
|
|
5
5
|
import { usePartySocket } from "partysocket/react";
|
|
6
6
|
import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
//#region src/react.tsx
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"durable objects"
|
|
10
10
|
],
|
|
11
11
|
"type": "module",
|
|
12
|
-
"version": "0.17.
|
|
12
|
+
"version": "0.17.3",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
15
15
|
"directory": "packages/agents",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@ai-sdk/react": "^3.0.204",
|
|
43
43
|
"@modelcontextprotocol/conformance": "0.1.16",
|
|
44
|
-
"@tanstack/ai": "0.
|
|
44
|
+
"@tanstack/ai": "0.38.0",
|
|
45
45
|
"@types/react": "^19.2.17",
|
|
46
46
|
"@types/yargs": "^17.0.35",
|
|
47
47
|
"@vitest/browser-playwright": "^4.1.9",
|
|
48
|
-
"@x402/core": "^2.
|
|
49
|
-
"@x402/evm": "^2.
|
|
48
|
+
"@x402/core": "^2.17.0",
|
|
49
|
+
"@x402/evm": "^2.17.0",
|
|
50
50
|
"ai": "^6.0.202",
|
|
51
51
|
"chat": "^4.31.0",
|
|
52
52
|
"glob": "^13.0.6",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent-tools-BXlsuX0d.js","names":[],"sources":["../src/chat/agent-tools.ts"],"sourcesContent":["import { applyChunkToParts, type MessagePart } from \"./message-builder\";\nimport {\n AGENT_TOOL_MILESTONE_PART,\n AGENT_TOOL_PROGRESS_PART\n} from \"../agent-tool-types\";\nimport type {\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolMilestone,\n AgentToolProgress,\n AgentToolProgressSnapshot,\n AgentToolRunPart,\n AgentToolRunState,\n AgentToolStoredChunk\n} from \"../agent-tool-types\";\n\n/**\n * Pull a reserved `data-agent-progress` chunk (emitted by a running sub-agent's\n * `reportProgress`) into a latest-wins snapshot. Returns `undefined` for any\n * other chunk so the caller keeps the prior snapshot.\n */\nfunction readAgentToolProgressChunk(\n chunk: unknown\n): AgentToolProgressSnapshot | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_PROGRESS_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: AgentToolProgress }).data ?? {};\n return {\n ...(typeof data.fraction === \"number\" ? { fraction: data.fraction } : {}),\n ...(typeof data.message === \"string\" ? { message: data.message } : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n ...(data.data !== undefined ? { data: data.data } : {}),\n at: Date.now()\n };\n}\n\n/**\n * Pull a reserved `data-agent-milestone` chunk into a durable milestone record,\n * or `undefined` for any other chunk. Milestones carry their own monotonic\n * `sequence` so the caller can dedupe replay-vs-live races.\n */\nfunction readAgentToolMilestoneChunk(\n chunk: unknown\n): AgentToolMilestone | undefined {\n if (\n typeof chunk !== \"object\" ||\n chunk === null ||\n (chunk as { type?: unknown }).type !== AGENT_TOOL_MILESTONE_PART\n ) {\n return undefined;\n }\n const data = (chunk as { data?: Partial<AgentToolMilestone> }).data ?? {};\n if (typeof data.name !== \"string\") return undefined;\n return {\n name: data.name,\n sequence: typeof data.sequence === \"number\" ? data.sequence : 0,\n at: typeof data.at === \"number\" ? data.at : Date.now(),\n ...(data.data !== undefined ? { data: data.data } : {})\n };\n}\n\n/**\n * Merge a milestone into a run's ordered milestone list, deduping on `sequence`\n * (idempotent across replay + live races) and keeping the list sorted.\n */\nfunction mergeMilestone(\n existing: AgentToolMilestone[] | undefined,\n milestone: AgentToolMilestone\n): AgentToolMilestone[] {\n // Returns `existing` unchanged (same reference) when the milestone is a dup,\n // so callers can identity-compare to detect a genuinely new milestone.\n if (existing?.some((m) => m.sequence === milestone.sequence)) return existing;\n const list = existing ? [...existing, milestone] : [milestone];\n list.sort((a, b) => a.sequence - b.sequence);\n return list;\n}\n\n/** Latest-wins coalescing window for `reportProgress` emits (per run). */\nconst AGENT_TOOL_PROGRESS_COALESCE_MS = 200;\n\nexport type AgentToolProgressEmitResult = \"emitted\" | \"coalesced\" | \"inactive\";\n\n/**\n * Host-injected seams the shared progress emitter needs. Keeps the per-host\n * `reportProgress` thin: Think / AIChatAgent supply how to resolve the active\n * agent-tool run, how to broadcast a chat-response frame, and how to persist the\n * latest snapshot on their own child-run table.\n */\nexport type AgentToolProgressEmitHooks = {\n /** The agent-tool run currently executing in this turn, or null. */\n resolveActiveRun: () => { runId: string; requestId: string } | null;\n /** Broadcast a chat-response frame (id = requestId) to clients/tailers. */\n broadcast: (requestId: string, chunkBody: string) => void;\n /** Persist the latest snapshot + signal timestamp on the child run row. */\n persistSnapshot: (\n runId: string,\n snapshot: {\n fraction?: number;\n message?: string;\n phase?: string;\n data?: unknown;\n },\n at: number\n ) => void;\n /**\n * Persist a durable milestone row, bump the run's signal timestamp, and return\n * the assigned monotonic per-run `sequence` (used to dedupe replay/live races).\n */\n persistMilestone: (\n runId: string,\n name: string,\n data: unknown,\n at: number\n ) => number;\n};\n\n/**\n * Shared implementation of `reportProgress` for chat hosts. Builds the reserved\n * transient `data-agent-progress` wire frame, coalesces bursts to a bounded\n * cadence (latest-wins; a `fraction >= 1` \"done\" frame always flushes), and\n * persists a latest snapshot. `data` rides the live frame but is only persisted\n * when the caller opts in via `{ persist: true }`.\n */\nexport class AgentToolProgressEmitter {\n private readonly _lastEmitAt = new Map<string, number>();\n\n constructor(private readonly hooks: AgentToolProgressEmitHooks) {}\n\n report(\n progress: AgentToolProgress,\n options?: { persist?: boolean }\n ): AgentToolProgressEmitResult {\n const active = this.hooks.resolveActiveRun();\n if (!active) return \"inactive\";\n const { runId, requestId } = active;\n const now = Date.now();\n\n // Durable milestone: never coalesced (each named boundary must land,\n // persist, and replay). Rides the stream as a PERSISTED data part.\n if (typeof progress.milestone === \"string\" && progress.milestone) {\n this._lastEmitAt.set(runId, now);\n const sequence = this.hooks.persistMilestone(\n runId,\n progress.milestone,\n progress.data,\n now\n );\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_MILESTONE_PART,\n data: {\n name: progress.milestone,\n sequence,\n at: now,\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n }\n })\n );\n return \"emitted\";\n }\n\n const last = this._lastEmitAt.get(runId) ?? 0;\n const isDone =\n typeof progress.fraction === \"number\" && progress.fraction >= 1;\n if (now - last < AGENT_TOOL_PROGRESS_COALESCE_MS && !isDone) {\n return \"coalesced\";\n }\n this._lastEmitAt.set(runId, now);\n\n const wire: AgentToolProgress = {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\" ? { phase: progress.phase } : {}),\n ...(progress.data !== undefined ? { data: progress.data } : {})\n };\n this.hooks.broadcast(\n requestId,\n JSON.stringify({\n type: AGENT_TOOL_PROGRESS_PART,\n transient: true,\n data: wire\n })\n );\n this.hooks.persistSnapshot(\n runId,\n {\n ...(typeof progress.fraction === \"number\"\n ? { fraction: progress.fraction }\n : {}),\n ...(typeof progress.message === \"string\"\n ? { message: progress.message }\n : {}),\n ...(typeof progress.phase === \"string\"\n ? { phase: progress.phase }\n : {}),\n ...(options?.persist && progress.data !== undefined\n ? { data: progress.data }\n : {})\n },\n now\n );\n return \"emitted\";\n }\n\n /** Drop coalescing state for a settled run (called on terminal). */\n forget(runId: string): void {\n this._lastEmitAt.delete(runId);\n }\n}\n\nfunction sortRuns<Part extends AgentToolRunPart>(\n runs: AgentToolRunState<Part>[]\n): AgentToolRunState<Part>[] {\n return [...runs].sort((a, b) => {\n if (a.order !== b.order) return a.order - b.order;\n return a.runId.localeCompare(b.runId);\n });\n}\n\nfunction rebuildIndexes<Part extends AgentToolRunPart>(\n runsById: Record<string, AgentToolRunState<Part>>\n): Pick<AgentToolEventState<Part>, \"runsByToolCallId\" | \"unboundRuns\"> {\n const grouped: Record<string, AgentToolRunState<Part>[]> = {};\n const unboundRuns: AgentToolRunState<Part>[] = [];\n for (const run of Object.values(runsById)) {\n if (run.parentToolCallId) {\n grouped[run.parentToolCallId] = grouped[run.parentToolCallId] ?? [];\n grouped[run.parentToolCallId].push(run);\n } else {\n unboundRuns.push(run);\n }\n }\n for (const [toolCallId, runs] of Object.entries(grouped)) {\n grouped[toolCallId] = sortRuns(runs);\n }\n return { runsByToolCallId: grouped, unboundRuns: sortRuns(unboundRuns) };\n}\n\nfunction emptyRun<Part extends AgentToolRunPart>(\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const { event } = message;\n if (event.kind === \"started\") {\n return {\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n }\n return undefined;\n}\n\nfunction applyToRun<Part extends AgentToolRunPart>(\n prev: AgentToolRunState<Part> | undefined,\n message: AgentToolEventMessage\n): AgentToolRunState<Part> | undefined {\n const seeded = prev ?? emptyRun(message);\n const { event } = message;\n\n switch (event.kind) {\n case \"started\":\n if (\n seeded?.status === \"completed\" ||\n seeded?.status === \"error\" ||\n seeded?.status === \"aborted\" ||\n seeded?.status === \"interrupted\"\n ) {\n return seeded;\n }\n return {\n ...seeded,\n runId: event.runId,\n agentType: event.agentType,\n parentToolCallId: message.parentToolCallId,\n inputPreview: event.inputPreview,\n order: event.order,\n display: event.display,\n status: \"running\",\n parts: seeded?.parts ?? [],\n subAgent: { agent: event.agentType, name: event.runId }\n };\n case \"chunk\": {\n if (!seeded) return undefined;\n const parts = [...seeded.parts];\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.body);\n applyChunkToParts(\n parts as MessagePart[],\n parsed as Parameters<typeof applyChunkToParts>[1]\n );\n } catch {\n return seeded;\n }\n // Project a reserved `data-agent-progress` part onto the run's latest\n // progress snapshot so a tray can render a bar/phase without drilling in.\n // The part is transient (not persisted into `parts`), so it is read here\n // off the raw chunk rather than from the reduced parts array.\n const progress = readAgentToolProgressChunk(parsed);\n if (progress) {\n return { ...seeded, parts, progress };\n }\n // Durable milestones land as a persisted `data-agent-milestone` part:\n // append (deduped by sequence) to the run's milestone list, and reflect\n // any progress fields the milestone carried onto the latest snapshot.\n const milestone = readAgentToolMilestoneChunk(parsed);\n if (milestone) {\n const milestones = mergeMilestone(seeded.milestones, milestone);\n // Only advance the snapshot for a genuinely new, not-older milestone, so\n // a late replay of an earlier milestone never rolls `progress` backward.\n const isNew = milestones !== seeded.milestones;\n const notOlder =\n seeded.progress === undefined || milestone.at >= seeded.progress.at;\n if (!isNew || !notOlder) {\n return { ...seeded, parts, milestones };\n }\n const data = (parsed as { data?: AgentToolProgress }).data ?? {};\n const snapshot: AgentToolProgressSnapshot = {\n ...(typeof data.fraction === \"number\"\n ? { fraction: data.fraction }\n : {}),\n ...(typeof data.message === \"string\"\n ? { message: data.message }\n : {}),\n ...(typeof data.phase === \"string\" ? { phase: data.phase } : {}),\n milestone: milestone.name,\n at: milestone.at\n };\n return { ...seeded, parts, progress: snapshot, milestones };\n }\n return { ...seeded, parts };\n }\n case \"finished\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"completed\",\n summary: event.summary,\n error: undefined\n };\n case \"error\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"error\", error: event.error };\n case \"aborted\":\n if (!seeded) return undefined;\n return { ...seeded, status: \"aborted\", error: event.reason };\n case \"interrupted\":\n if (!seeded) return undefined;\n return {\n ...seeded,\n status: \"interrupted\",\n error: event.error,\n reason: event.reason,\n childStillRunning: event.childStillRunning\n };\n }\n}\n\nexport function createAgentToolEventState<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(): AgentToolEventState<Part> {\n return {\n runsById: {},\n runsByToolCallId: {},\n unboundRuns: []\n };\n}\n\nexport function applyAgentToolEvent<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(\n state: AgentToolEventState<Part>,\n message: AgentToolEventMessage\n): AgentToolEventState<Part> {\n if (message.type !== \"agent-tool-event\") return state;\n const runId = message.event.runId;\n const nextRun = applyToRun(state.runsById[runId], message);\n if (!nextRun) return state;\n\n const runsById = { ...state.runsById, [runId]: nextRun };\n return { runsById, ...rebuildIndexes(runsById) };\n}\n\nexport type {\n AgentToolEvent,\n AgentToolEventMessage,\n AgentToolEventState,\n AgentToolRunPart,\n AgentToolRunState\n} from \"../agent-tool-types\";\n\n/**\n * @internal Host substrate the {@link interceptAgentToolBroadcast} snoop reads,\n * abstracting the divergent per-host run-lookup and response-frame constant.\n */\nexport interface AgentToolBroadcastHooks {\n /** Live tailers per run; iterated to forward each progress chunk. */\n forwarders: Map<string, Set<(chunk: AgentToolStoredChunk) => void>>;\n /** Per-run forwarded-chunk counter; advanced even with no tailer attached. */\n liveSequences: Map<string, number>;\n /** Per-run last error body, captured for replay to a late-attaching tailer. */\n lastErrors: Map<string, string>;\n /** The host's use-chat-response wire type (`CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE`). */\n responseType: string;\n /** Resolve the agent-tool run that owns a turn request id, or null. */\n runForRequest: (requestId: string) => string | null;\n}\n\n/**\n * Snoop a host's outgoing chat frames while any agent-tool run is in flight and\n * forward the owning run's streamed body to its live tailers (or capture its\n * error), without altering the frame — the caller still broadcasts it (#1575).\n *\n * Shared verbatim by `@cloudflare/ai-chat` and `@cloudflare/think`; the only\n * per-host variance (the response-frame type constant and the run-lookup, whose\n * SQL differs) is supplied via {@link AgentToolBroadcastHooks}. Inspection runs\n * for a run's whole lifecycle (live sequences exist even with no tailer), so\n * error capture never depends on tailer timing. A frame belongs to a run iff it\n * carries that run's turn request id, so concurrent runs can't cross-contaminate\n * each other's progress or error state.\n */\nexport function interceptAgentToolBroadcast(\n msg: string | ArrayBuffer | ArrayBufferView,\n hooks: AgentToolBroadcastHooks\n): void {\n if (\n (hooks.forwarders.size > 0 || hooks.liveSequences.size > 0) &&\n typeof msg === \"string\"\n ) {\n try {\n const parsed = JSON.parse(msg) as {\n type?: unknown;\n body?: unknown;\n error?: unknown;\n id?: unknown;\n };\n if (parsed.type === hooks.responseType && typeof parsed.id === \"string\") {\n const runId = hooks.runForRequest(parsed.id);\n if (runId !== null) {\n if (parsed.error === true && typeof parsed.body === \"string\") {\n hooks.lastErrors.set(runId, parsed.body);\n } else if (\n typeof parsed.body === \"string\" &&\n parsed.body.length > 0\n ) {\n // Advance the live sequence even with no tailer attached so a tailer\n // registering mid-run resumes at the right offset.\n const sequence = hooks.liveSequences.get(runId) ?? 0;\n hooks.liveSequences.set(runId, sequence + 1);\n const chunk: AgentToolStoredChunk = { sequence, body: parsed.body };\n const forwarders = hooks.forwarders.get(runId);\n if (forwarders) {\n for (const forward of forwarders) forward(chunk);\n }\n }\n }\n }\n } catch {\n // Non-chat frames pass through unchanged.\n }\n }\n}\n"],"mappings":";;;;;;;;AAqBA,SAAS,2BACP,OACuC;CACvC,IACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAA,uBAE9B;CAEF,MAAM,OAAQ,MAAuC,QAAQ,CAAC;CAC9D,OAAO;EACL,GAAI,OAAO,KAAK,aAAa,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACvE,GAAI,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;EACpE,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC9D,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EACrD,IAAI,KAAK,IAAI;CACf;AACF;;;;;;AAOA,SAAS,4BACP,OACgC;CAChC,IACE,OAAO,UAAU,YACjB,UAAU,QACT,MAA6B,SAAA,wBAE9B;CAEF,MAAM,OAAQ,MAAiD,QAAQ,CAAC;CACxE,IAAI,OAAO,KAAK,SAAS,UAAU,OAAO,KAAA;CAC1C,OAAO;EACL,MAAM,KAAK;EACX,UAAU,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;EAC9D,IAAI,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK,KAAK,IAAI;EACrD,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;CACvD;AACF;;;;;AAMA,SAAS,eACP,UACA,WACsB;CAGtB,IAAI,UAAU,MAAM,MAAM,EAAE,aAAa,UAAU,QAAQ,GAAG,OAAO;CACrE,MAAM,OAAO,WAAW,CAAC,GAAG,UAAU,SAAS,IAAI,CAAC,SAAS;CAC7D,KAAK,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAC3C,OAAO;AACT;;AAGA,MAAM,kCAAkC;;;;;;;;AA6CxC,IAAa,2BAAb,MAAsC;CAGpC,YAAY,OAAoD;EAAnC,KAAA,QAAA;EAF7B,KAAiB,8BAAc,IAAI,IAAoB;CAEU;CAEjE,OACE,UACA,SAC6B;EAC7B,MAAM,SAAS,KAAK,MAAM,iBAAiB;EAC3C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,OAAO,cAAc;EAC7B,MAAM,MAAM,KAAK,IAAI;EAIrB,IAAI,OAAO,SAAS,cAAc,YAAY,SAAS,WAAW;GAChE,KAAK,YAAY,IAAI,OAAO,GAAG;GAC/B,MAAM,WAAW,KAAK,MAAM,iBAC1B,OACA,SAAS,WACT,SAAS,MACT,GACF;GACA,KAAK,MAAM,UACT,WACA,KAAK,UAAU;IACb,MAAM;IACN,MAAM;KACJ,MAAM,SAAS;KACf;KACA,IAAI;KACJ,GAAI,OAAO,SAAS,aAAa,WAC7B,EAAE,UAAU,SAAS,SAAS,IAC9B,CAAC;KACL,GAAI,OAAO,SAAS,YAAY,WAC5B,EAAE,SAAS,SAAS,QAAQ,IAC5B,CAAC;KACL,GAAI,OAAO,SAAS,UAAU,WAC1B,EAAE,OAAO,SAAS,MAAM,IACxB,CAAC;KACL,GAAI,SAAS,SAAS,KAAA,IAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC;IAC/D;GACF,CAAC,CACH;GACA,OAAO;EACT;EAEA,MAAM,OAAO,KAAK,YAAY,IAAI,KAAK,KAAK;EAC5C,MAAM,SACJ,OAAO,SAAS,aAAa,YAAY,SAAS,YAAY;EAChE,IAAI,MAAM,OAAO,mCAAmC,CAAC,QACnD,OAAO;EAET,KAAK,YAAY,IAAI,OAAO,GAAG;EAE/B,MAAM,OAA0B;GAC9B,GAAI,OAAO,SAAS,aAAa,WAC7B,EAAE,UAAU,SAAS,SAAS,IAC9B,CAAC;GACL,GAAI,OAAO,SAAS,YAAY,WAC5B,EAAE,SAAS,SAAS,QAAQ,IAC5B,CAAC;GACL,GAAI,OAAO,SAAS,UAAU,WAAW,EAAE,OAAO,SAAS,MAAM,IAAI,CAAC;GACtE,GAAI,SAAS,SAAS,KAAA,IAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC;EAC/D;EACA,KAAK,MAAM,UACT,WACA,KAAK,UAAU;GACb,MAAM;GACN,WAAW;GACX,MAAM;EACR,CAAC,CACH;EACA,KAAK,MAAM,gBACT,OACA;GACE,GAAI,OAAO,SAAS,aAAa,WAC7B,EAAE,UAAU,SAAS,SAAS,IAC9B,CAAC;GACL,GAAI,OAAO,SAAS,YAAY,WAC5B,EAAE,SAAS,SAAS,QAAQ,IAC5B,CAAC;GACL,GAAI,OAAO,SAAS,UAAU,WAC1B,EAAE,OAAO,SAAS,MAAM,IACxB,CAAC;GACL,GAAI,SAAS,WAAW,SAAS,SAAS,KAAA,IACtC,EAAE,MAAM,SAAS,KAAK,IACtB,CAAC;EACP,GACA,GACF;EACA,OAAO;CACT;;CAGA,OAAO,OAAqB;EAC1B,KAAK,YAAY,OAAO,KAAK;CAC/B;AACF;AAEA,SAAS,SACP,MAC2B;CAC3B,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;EAC9B,IAAI,EAAE,UAAU,EAAE,OAAO,OAAO,EAAE,QAAQ,EAAE;EAC5C,OAAO,EAAE,MAAM,cAAc,EAAE,KAAK;CACtC,CAAC;AACH;AAEA,SAAS,eACP,UACqE;CACrE,MAAM,UAAqD,CAAC;CAC5D,MAAM,cAAyC,CAAC;CAChD,KAAK,MAAM,OAAO,OAAO,OAAO,QAAQ,GACtC,IAAI,IAAI,kBAAkB;EACxB,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,qBAAqB,CAAC;EAClE,QAAQ,IAAI,iBAAiB,CAAC,KAAK,GAAG;CACxC,OACE,YAAY,KAAK,GAAG;CAGxB,KAAK,MAAM,CAAC,YAAY,SAAS,OAAO,QAAQ,OAAO,GACrD,QAAQ,cAAc,SAAS,IAAI;CAErC,OAAO;EAAE,kBAAkB;EAAS,aAAa,SAAS,WAAW;CAAE;AACzE;AAEA,SAAS,SACP,SACqC;CACrC,MAAM,EAAE,UAAU;CAClB,IAAI,MAAM,SAAS,WACjB,OAAO;EACL,OAAO,MAAM;EACb,WAAW,MAAM;EACjB,kBAAkB,QAAQ;EAC1B,cAAc,MAAM;EACpB,OAAO,MAAM;EACb,SAAS,MAAM;EACf,QAAQ;EACR,OAAO,CAAC;EACR,UAAU;GAAE,OAAO,MAAM;GAAW,MAAM,MAAM;EAAM;CACxD;AAGJ;AAEA,SAAS,WACP,MACA,SACqC;CACrC,MAAM,SAAS,QAAQ,SAAS,OAAO;CACvC,MAAM,EAAE,UAAU;CAElB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,IACE,QAAQ,WAAW,eACnB,QAAQ,WAAW,WACnB,QAAQ,WAAW,aACnB,QAAQ,WAAW,eAEnB,OAAO;GAET,OAAO;IACL,GAAG;IACH,OAAO,MAAM;IACb,WAAW,MAAM;IACjB,kBAAkB,QAAQ;IAC1B,cAAc,MAAM;IACpB,OAAO,MAAM;IACb,SAAS,MAAM;IACf,QAAQ;IACR,OAAO,QAAQ,SAAS,CAAC;IACzB,UAAU;KAAE,OAAO,MAAM;KAAW,MAAM,MAAM;IAAM;GACxD;EACF,KAAK,SAAS;GACZ,IAAI,CAAC,QAAQ,OAAO,KAAA;GACpB,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK;GAC9B,IAAI;GACJ,IAAI;IACF,SAAS,KAAK,MAAM,MAAM,IAAI;IAC9B,kBACE,OACA,MACF;GACF,QAAQ;IACN,OAAO;GACT;GAKA,MAAM,WAAW,2BAA2B,MAAM;GAClD,IAAI,UACF,OAAO;IAAE,GAAG;IAAQ;IAAO;GAAS;GAKtC,MAAM,YAAY,4BAA4B,MAAM;GACpD,IAAI,WAAW;IACb,MAAM,aAAa,eAAe,OAAO,YAAY,SAAS;IAG9D,MAAM,QAAQ,eAAe,OAAO;IACpC,MAAM,WACJ,OAAO,aAAa,KAAA,KAAa,UAAU,MAAM,OAAO,SAAS;IACnE,IAAI,CAAC,SAAS,CAAC,UACb,OAAO;KAAE,GAAG;KAAQ;KAAO;IAAW;IAExC,MAAM,OAAQ,OAAwC,QAAQ,CAAC;IAC/D,MAAM,WAAsC;KAC1C,GAAI,OAAO,KAAK,aAAa,WACzB,EAAE,UAAU,KAAK,SAAS,IAC1B,CAAC;KACL,GAAI,OAAO,KAAK,YAAY,WACxB,EAAE,SAAS,KAAK,QAAQ,IACxB,CAAC;KACL,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;KAC9D,WAAW,UAAU;KACrB,IAAI,UAAU;IAChB;IACA,OAAO;KAAE,GAAG;KAAQ;KAAO,UAAU;KAAU;IAAW;GAC5D;GACA,OAAO;IAAE,GAAG;IAAQ;GAAM;EAC5B;EACA,KAAK;GACH,IAAI,CAAC,QAAQ,OAAO,KAAA;GACpB,OAAO;IACL,GAAG;IACH,QAAQ;IACR,SAAS,MAAM;IACf,OAAO,KAAA;GACT;EACF,KAAK;GACH,IAAI,CAAC,QAAQ,OAAO,KAAA;GACpB,OAAO;IAAE,GAAG;IAAQ,QAAQ;IAAS,OAAO,MAAM;GAAM;EAC1D,KAAK;GACH,IAAI,CAAC,QAAQ,OAAO,KAAA;GACpB,OAAO;IAAE,GAAG;IAAQ,QAAQ;IAAW,OAAO,MAAM;GAAO;EAC7D,KAAK;GACH,IAAI,CAAC,QAAQ,OAAO,KAAA;GACpB,OAAO;IACL,GAAG;IACH,QAAQ;IACR,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,mBAAmB,MAAM;GAC3B;CACJ;AACF;AAEA,SAAgB,4BAEe;CAC7B,OAAO;EACL,UAAU,CAAC;EACX,kBAAkB,CAAC;EACnB,aAAa,CAAC;CAChB;AACF;AAEA,SAAgB,oBAGd,OACA,SAC2B;CAC3B,IAAI,QAAQ,SAAS,oBAAoB,OAAO;CAChD,MAAM,QAAQ,QAAQ,MAAM;CAC5B,MAAM,UAAU,WAAW,MAAM,SAAS,QAAQ,OAAO;CACzD,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,WAAW;EAAE,GAAG,MAAM;GAAW,QAAQ;CAAQ;CACvD,OAAO;EAAE;EAAU,GAAG,eAAe,QAAQ;CAAE;AACjD;;;;;;;;;;;;;;AAwCA,SAAgB,4BACd,KACA,OACM;CACN,KACG,MAAM,WAAW,OAAO,KAAK,MAAM,cAAc,OAAO,MACzD,OAAO,QAAQ,UAEf,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAG;EAM7B,IAAI,OAAO,SAAS,MAAM,gBAAgB,OAAO,OAAO,OAAO,UAAU;GACvE,MAAM,QAAQ,MAAM,cAAc,OAAO,EAAE;GAC3C,IAAI,UAAU;QACR,OAAO,UAAU,QAAQ,OAAO,OAAO,SAAS,UAClD,MAAM,WAAW,IAAI,OAAO,OAAO,IAAI;SAClC,IACL,OAAO,OAAO,SAAS,YACvB,OAAO,KAAK,SAAS,GACrB;KAGA,MAAM,WAAW,MAAM,cAAc,IAAI,KAAK,KAAK;KACnD,MAAM,cAAc,IAAI,OAAO,WAAW,CAAC;KAC3C,MAAM,QAA8B;MAAE;MAAU,MAAM,OAAO;KAAK;KAClE,MAAM,aAAa,MAAM,WAAW,IAAI,KAAK;KAC7C,IAAI,YACF,KAAK,MAAM,WAAW,YAAY,QAAQ,KAAK;IAEnD;;EAEJ;CACF,QAAQ,CAER;AAEJ"}
|