agents 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -12
- package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
- package/dist/agent-tools.d.ts +44 -28
- package/dist/agent-tools.js +16 -7
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.d.ts +5 -3
- package/dist/browser/ai.js +88 -8
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.js +13 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
- package/dist/chat/index.d.ts +51 -15
- package/dist/chat/index.js +59 -4
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +19 -180
- package/dist/chat/react.js +93 -600
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.d.ts +10 -0
- package/dist/chat/transport.js +2 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
- package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
- package/dist/connector-CptFKzRh.js.map +1 -0
- package/dist/current-agent-CuMErtly.d.ts +283 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-CaBjfz4J.js +41 -0
- package/dist/diagnostics-CaBjfz4J.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +3 -2
- package/dist/experimental/memory/session/index.js +15 -10
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
- package/dist/index.d.ts +102 -91
- package/dist/index.js +12 -7235
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +48 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.d.ts +1 -130
- package/dist/observability/ai/index.js +88 -433
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +6 -9
- package/dist/react.js.map +1 -1
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-CR9RHGos.js +857 -0
- package/dist/scheduler-CR9RHGos.js.map +1 -0
- package/dist/scheduler-DQoTGoAW.d.ts +224 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-5W6JNKVb.js +6613 -0
- package/dist/src-5W6JNKVb.js.map +1 -0
- package/dist/sub-routing.d.ts +18 -6
- package/dist/sub-routing.js +92 -2
- package/dist/sub-routing.js.map +1 -1
- package/dist/wire-types-CnMt6_HR.js +47 -0
- package/dist/wire-types-CnMt6_HR.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -22
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
- package/dist/ws-chat-transport-rWwta645.js +698 -0
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/adding-to-existing-project.md +4 -2
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +29 -0
- package/docs/browse-the-web.md +16 -1
- package/docs/chat-agents.md +16 -13
- package/docs/client-sdk.md +12 -8
- package/docs/configuration.md +7 -1
- package/docs/cross-domain-authentication.md +7 -35
- package/docs/durable-execution.md +1 -1
- package/docs/email.md +2 -13
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +16 -13
- package/docs/index.md +1 -0
- package/docs/lifecycle.md +323 -0
- package/docs/long-running-agents.md +13 -15
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +6 -0
- package/docs/mcp-transports.md +18 -12
- package/docs/migration-to-ai-sdk-v5.md +2 -2
- package/docs/migration-to-ai-sdk-v6.md +5 -1
- package/docs/observability.md +33 -77
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +27 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sub-agents.md +47 -2
- package/docs/webhooks.md +109 -136
- package/docs/workflows.md +9 -1
- package/package.json +41 -23
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/connector-KEJnl6e5.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.js.map +0 -1
- package/dist/wire-types-CU9rLoeS.js.map +0 -1
- package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-chat-transport-rWwta645.js","names":[],"sources":["../src/chat/replay-batch.ts","../src/chat/ws-chat-transport.ts"],"sourcesContent":["import type { UIMessageChunk } from \"ai\";\n\n/**\n * Batches chunks replayed during stream resume.\n *\n * Replaying every chunk separately can exceed React's update limit. Adjacent\n * deltas for the same message part are merged until the replay pauses or ends.\n */\nexport class ReplayChunkBatch {\n private chunks: UIMessageChunk[] = [];\n private tailMergeKey: string | null = null;\n private cancelWindow: (() => void) | undefined;\n private startMessageId: string | undefined;\n\n constructor(\n private readonly controller: ReadableStreamDefaultController<UIMessageChunk>,\n private readonly closeWindow: (flush: () => void) => () => void = (\n flush\n ) => {\n const timer = setTimeout(flush, 0);\n return () => clearTimeout(timer);\n }\n ) {}\n\n get bufferedStartMessageId(): string | undefined {\n return this.startMessageId;\n }\n\n get isEmpty(): boolean {\n return this.chunks.length === 0;\n }\n\n push(chunk: UIMessageChunk): void {\n const key = mergeKeyOf(chunk);\n const tail = this.chunks[this.chunks.length - 1];\n\n if (tail !== undefined && key !== null && key === this.tailMergeKey) {\n appendDeltaText(tail, chunk);\n return;\n }\n\n if (this.chunks.length === 0) {\n this.cancelWindow = this.closeWindow(() => {\n this.cancelWindow = undefined;\n this.flush();\n });\n }\n\n if (chunk.type === \"start\") {\n this.startMessageId = chunk.messageId;\n }\n this.chunks.push(chunk);\n this.tailMergeKey = key;\n }\n\n discard(): void {\n this.cancelWindow?.();\n this.cancelWindow = undefined;\n this.chunks = [];\n this.tailMergeKey = null;\n this.startMessageId = undefined;\n }\n\n flush(): void {\n this.cancelWindow?.();\n this.cancelWindow = undefined;\n\n if (this.chunks.length === 0) return;\n const batch = this.chunks;\n this.chunks = [];\n this.tailMergeKey = null;\n this.startMessageId = undefined;\n try {\n for (const chunk of batch) {\n this.controller.enqueue(chunk);\n }\n } catch {\n // The stream is already closed; callers still need to finish cleanup.\n }\n }\n\n enqueueNow(chunk: UIMessageChunk): void {\n this.controller.enqueue(chunk);\n }\n\n closeNow(): void {\n this.controller.close();\n }\n\n errorNow(error: Error): void {\n this.controller.error(error);\n }\n}\n\ntype ChatResponseFrame = {\n body?: string;\n continuation?: boolean;\n done?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n};\n\n/** Buffers replay chunks and flushes them before live or boundary frames. */\nexport function applyChatResponseFrame(\n batch: ReplayChunkBatch,\n frame: ChatResponseFrame\n): void {\n const endsReplayBurst =\n frame.replay !== true ||\n frame.done === true ||\n frame.replayComplete === true;\n\n const body = frame.body?.trim();\n if (!body) {\n if (endsReplayBurst) batch.flush();\n return;\n }\n\n let chunk: UIMessageChunk;\n try {\n chunk = JSON.parse(body) as UIMessageChunk;\n } catch {\n // Do not leave buffered chunks behind a malformed boundary frame.\n if (endsReplayBurst) batch.flush();\n return;\n }\n\n if (endsReplayBurst) {\n batch.flush();\n batch.enqueueNow(chunk);\n return;\n }\n\n if (supersedesBufferedPass(batch, chunk, frame)) {\n batch.discard();\n }\n\n batch.push(chunk);\n}\n\nfunction supersedesBufferedPass(\n batch: ReplayChunkBatch,\n chunk: UIMessageChunk,\n frame: ChatResponseFrame\n): boolean {\n return (\n chunk.type === \"start\" &&\n frame.continuation !== true &&\n chunk.messageId !== undefined &&\n chunk.messageId === batch.bufferedStartMessageId\n );\n}\n\n/**\n * Reports an error after buffered content. `controller.error()` would discard\n * chunks that have not reached the consumer.\n */\nexport function failChatStream(batch: ReplayChunkBatch, message: string): void {\n if (batch.isEmpty) {\n batch.errorNow(new Error(message));\n return;\n }\n\n batch.flush();\n batch.enqueueNow({ errorText: message, type: \"error\" });\n batch.closeNow();\n}\n\nfunction mergeKeyOf(chunk: UIMessageChunk): string | null {\n switch (chunk.type) {\n case \"text-delta\":\n case \"reasoning-delta\":\n return chunk.providerMetadata === undefined\n ? `${chunk.type}\\u0000${chunk.id}`\n : null;\n case \"tool-input-delta\":\n return `tool-input-delta\\u0000${chunk.toolCallId}`;\n default:\n return null;\n }\n}\n\nfunction appendDeltaText(target: UIMessageChunk, source: UIMessageChunk): void {\n if (\n target.type === \"tool-input-delta\" &&\n source.type === \"tool-input-delta\"\n ) {\n target.inputTextDelta += source.inputTextDelta;\n return;\n }\n if (\n (target.type === \"text-delta\" || target.type === \"reasoning-delta\") &&\n (source.type === \"text-delta\" || source.type === \"reasoning-delta\")\n ) {\n target.delta += source.delta;\n }\n}\n","/**\n * Framework-neutral WebSocket transport for AI SDK chat clients.\n *\n * Speaks the CF_AGENT protocol directly and returns AI SDK UI message chunks\n * without reconstructing an HTTP response or parsing an SSE stream.\n */\n\nimport type { ChatTransport, UIMessage, UIMessageChunk } from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport {\n applyChatResponseFrame,\n failChatStream,\n ReplayChunkBatch\n} from \"./replay-batch\";\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 `AgentClient` from `agents/client` and the connection returned by\n * `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 framework-neutral AgentClient or useAgent connection. */\n agent: AgentConnection;\n /**\n * Callback to add custom fields to the request body before sending.\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 *\n * This low-level transport handles new and regenerated request streams plus\n * cancellation. Higher-level protocol coordination such as automatic reconnect\n * resume, cross-tab transcript synchronization, and client-tool continuations\n * is provided by integrations such as `useAgentChat`.\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:\n | ((data: { probeId?: string }) => boolean)\n | 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 // Retransmits the current handshake on a replacement socket without starting\n // a second AI SDK resume request. Set only while a resolver is active.\n private _retryResumeProbe: (() => 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 // Local-only detach for a resume stream owned by an obsolete Chat/agent\n // generation. Unlike explicit cancellation, this never cancels server work.\n private _detachResumeStream: (() => 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 /**\n * Point the singleton transport at a new Agent connection. A pending resolver\n * belongs to the old Chat/socket generation and must settle before messages\n * from the replacement connection can be consumed (#1914 review).\n */\n setAgent(agent: AgentConnection) {\n if (this.agent === agent) return;\n this.resetResumeState();\n this.agent = agent;\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 * Settle and detach the current handshake without interpreting it as a\n * server-idle response. Used when the owning hook/agent generation changes.\n */\n cancelPendingResume(): boolean {\n const resolveNone = this._resumeNoneResolver;\n if (!resolveNone) return false;\n return resolveNone({});\n }\n\n /**\n * Invalidate all client-side resume state for an obsolete hook/agent\n * generation without cancelling its durable server turn.\n */\n resetResumeState(): void {\n this._expectToolContinuation = false;\n this.cancelPendingResume();\n this._detachResumeStream?.();\n }\n\n /**\n * Re-send the active handshake request on the latest socket generation. This\n * preserves one AI SDK resume operation while recovering a request/reply lost\n * with the previous WebSocket.\n */\n retryPendingResume(): boolean {\n const retry = this._retryResumeProbe;\n if (!retry) return false;\n retry();\n return true;\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(data: { probeId?: string } = {}): boolean {\n if (!this._resumeNoneResolver) return false;\n return this._resumeNoneResolver(data);\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 // A transport has one handshake slot. Returning null for an unexpected\n // concurrent caller is safer than overwriting callbacks owned by the first\n // AI SDK request (StrictMode/manual overlap); the hook serializes its normal\n // mount, tool, public, and reconnect entry points.\n if (this.isAwaitingResume()) return null;\n\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 another message listener (which races with the\n // hook), expose identity-owned callbacks consumed synchronously by the\n // shared handler.\n const activeIds = this.activeRequestIds;\n const probeId = nanoid(8);\n\n return new Promise<ReadableStream<UIMessageChunk> | null>((resolve) => {\n let resolved = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let resumeResolver: ((data: { id: string }) => void) | null = null;\n let resumeNoneResolver: ((data: { probeId?: string }) => boolean) | null =\n null;\n let onStreamPending: (() => void) | null = null;\n let retryResumeProbe: (() => void) | null = null;\n\n const clearOwnedCallbacks = () => {\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 if (onStreamPending && this._onStreamPending === onStreamPending) {\n this._onStreamPending = null;\n }\n if (retryResumeProbe && this._retryResumeProbe === retryResumeProbe) {\n this._retryResumeProbe = null;\n }\n };\n\n const done = (value: ReadableStream<UIMessageChunk> | null) => {\n if (resolved) return;\n resolved = true;\n clearOwnedCallbacks();\n if (timeout) clearTimeout(timeout);\n resolve(value);\n };\n\n const armTimeout = (delay: number) => {\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(() => done(null), delay);\n };\n\n // Keep-waiting (#1784): the server says a turn is accepted but its stream\n // has not started. Extend this operation's own timeout only.\n onStreamPending = () => {\n if (resolved) return;\n armTimeout(RESUME_PENDING_TIMEOUT_MS);\n };\n\n resumeNoneResolver = (data) => {\n if (data.probeId && data.probeId !== probeId) return false;\n done(null);\n return true;\n };\n\n resumeResolver = (data: { id: string }) => {\n const requestId = data.id;\n activeIds?.add(requestId);\n const stream = this._createResumeStream(requestId);\n\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_ACK,\n id: requestId\n })\n );\n\n done(stream);\n };\n\n // Re-arm both the request and timeout on each replacement socket. This is\n // a retransmission of this resolver's handshake, not a second Chat resume.\n retryResumeProbe = () => {\n if (resolved) return;\n armTimeout(RESUME_PROBE_TIMEOUT_MS);\n try {\n this.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST,\n probeId\n })\n );\n } catch {\n // The next socket open retries again; the timeout remains a backstop.\n }\n };\n\n this._onStreamPending = onStreamPending;\n this._resumeNoneResolver = resumeNoneResolver;\n this._resumeResolver = resumeResolver;\n this._retryResumeProbe = retryResumeProbe;\n retryResumeProbe();\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> | null =\n null;\n // Held outside start() so the close and detach paths can flush replayed\n // content before they close the stream.\n let replayBatch: ReplayChunkBatch | null = null;\n const probeId = nanoid(8);\n let onResumeRef: ((data: { id: string }) => void) | null = null;\n let onResumeNoneRef: ((data: { probeId?: string }) => boolean) | null =\n null;\n let onStreamPendingRef: (() => void) | null = null;\n let retryResumeProbeRef: (() => void) | null = null;\n let abortToolContinuationRef: (() => boolean) | null = null;\n let detachResumeStreamRef: (() => boolean) | null = null;\n\n const clearOwnedHandshake = () => {\n if (onResumeRef && this._resumeResolver === onResumeRef) {\n this._resumeResolver = null;\n }\n if (onResumeNoneRef && this._resumeNoneResolver === onResumeNoneRef) {\n this._resumeNoneResolver = null;\n }\n if (onStreamPendingRef && this._onStreamPending === onStreamPendingRef) {\n this._onStreamPending = null;\n }\n if (\n retryResumeProbeRef &&\n this._retryResumeProbe === retryResumeProbeRef\n ) {\n this._retryResumeProbe = null;\n }\n };\n\n const finish = (action: () => void, keepRequestId = false) => {\n if (completed) return;\n completed = true;\n if (this._abortToolContinuation === abortToolContinuationRef) {\n this._abortToolContinuation = null;\n }\n if (this._detachResumeStream === detachResumeStreamRef) {\n this._detachResumeStream = null;\n }\n clearOwnedHandshake();\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 abortToolContinuationRef = () => {\n if (completed) return false;\n\n if (requestId === null) {\n finish(() => readerController?.error(abortError));\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 // Keep the ID so the shared message handler ignores in-flight chunks\n // until the server's terminal frame performs its normal cleanup.\n finish(() => readerController?.error(abortError), true);\n return true;\n };\n this._abortToolContinuation = abortToolContinuationRef;\n detachResumeStreamRef = () => {\n if (completed) return false;\n finish(() => {\n replayBatch?.flush();\n readerController?.close();\n });\n return true;\n };\n this._detachResumeStream = detachResumeStreamRef;\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n readerController = controller;\n const batch = new ReplayChunkBatch(controller);\n replayBatch = batch;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const armTimeout = (delay: number) => {\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(() => finish(() => controller.close()), delay);\n };\n\n const onResumeNone = (data: { probeId?: string }) => {\n if (data.probeId && data.probeId !== probeId) return false;\n finish(() => controller.close());\n return true;\n };\n\n const onResume = (data: { id: string }) => {\n if (requestId) return;\n\n requestId = data.id;\n activeIds?.add(requestId);\n clearOwnedHandshake();\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 const onStreamPending = () => {\n if (completed) return;\n armTimeout(RESUME_PENDING_TIMEOUT_MS);\n };\n\n const retryResumeProbe = () => {\n if (completed || requestId !== null) return;\n armTimeout(RESUME_PROBE_TIMEOUT_MS);\n try {\n transport.agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUME_REQUEST,\n probeId\n })\n );\n } catch {\n // A later socket open can retry this same deferred handshake.\n }\n };\n\n onResumeRef = onResume;\n onResumeNoneRef = onResumeNone;\n onStreamPendingRef = onStreamPending;\n retryResumeProbeRef = retryResumeProbe;\n transport._resumeResolver = onResume;\n transport._resumeNoneResolver = onResumeNone;\n transport._onStreamPending = onStreamPending;\n transport._retryResumeProbe = retryResumeProbe;\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 (\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(() => failChatStream(batch, data.body || \"Stream error\"));\n return;\n }\n\n applyChatResponseFrame(batch, data);\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(() => {\n batch.flush();\n controller.close();\n });\n\n agent.addEventListener(\"message\", onMessage, {\n signal: streamController.signal\n });\n agent.addEventListener(\"close\", onClose, {\n signal: streamController.signal\n });\n\n retryResumeProbe();\n },\n cancel() {\n if (requestId && transport.cancelOnClientAbort) {\n transport.sendCancelFrame(requestId);\n finish(() => {}, true);\n } else {\n finish(() => {});\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 let detachResumeStream: (() => boolean) | null = null;\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 if (this._detachResumeStream === detachResumeStream) {\n this._detachResumeStream = null;\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 let streamController: ReadableStreamDefaultController<UIMessageChunk> | null =\n null;\n // Held outside start() so the close and detach paths can flush replayed\n // content before they close the stream.\n let replayBatch: ReplayChunkBatch | null = null;\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 const transport = this;\n detachResumeStream = () => {\n if (completed) return false;\n finish(() => {\n replayBatch?.flush();\n streamController?.close();\n });\n return true;\n };\n this._detachResumeStream = detachResumeStream;\n\n return new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n const batch = new ReplayChunkBatch(controller);\n replayBatch = batch;\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(() => failChatStream(batch, data.body || \"Stream error\"));\n return;\n }\n\n applyChatResponseFrame(batch, data);\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(\n () => {\n batch.flush();\n controller.close();\n },\n false,\n false\n );\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"],"mappings":"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"}
|
|
@@ -32,7 +32,7 @@ npm install agents hono-agents
|
|
|
32
32
|
Create a new file for your agent (e.g., `src/agents/counter.ts`):
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
|
-
import { Agent } from "agents";
|
|
35
|
+
import { Agent, callable } from "agents";
|
|
36
36
|
|
|
37
37
|
type CounterState = {
|
|
38
38
|
count: number;
|
|
@@ -41,11 +41,13 @@ type CounterState = {
|
|
|
41
41
|
export class Counter extends Agent<Env, CounterState> {
|
|
42
42
|
initialState: CounterState = { count: 0 };
|
|
43
43
|
|
|
44
|
+
@callable()
|
|
44
45
|
increment() {
|
|
45
46
|
this.setState({ count: this.state.count + 1 });
|
|
46
47
|
return this.state.count;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
@callable()
|
|
49
51
|
decrement() {
|
|
50
52
|
this.setState({ count: this.state.count - 1 });
|
|
51
53
|
return this.state.count;
|
|
@@ -371,7 +373,7 @@ By default, agents are routed at `/agents/{agent-name}/{instance-name}`. You can
|
|
|
371
373
|
import { routeAgentRequest } from "agents";
|
|
372
374
|
|
|
373
375
|
const agentResponse = await routeAgentRequest(request, env, {
|
|
374
|
-
prefix: "
|
|
376
|
+
prefix: "api/agents" // Now routes at /api/agents/{agent-name}/{instance-name}
|
|
375
377
|
});
|
|
376
378
|
```
|
|
377
379
|
|
package/docs/agent-class.md
CHANGED
|
@@ -6,10 +6,15 @@ This document tries to bridge that gap, empowering any developer aiming to get s
|
|
|
6
6
|
|
|
7
7
|
# What is the Agent?
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
That being said, `Agent` does **not** extend `DurableObject` directly but instead `Server`. `Server` is a class provided by [PartyKit](https://github.com/cloudflare/partykit/tree/main/packages/partyserver).
|
|
9
|
+
`Agent` directly extends Cloudflare's `DurableObject`, so every Agent is a globally addressable, single-threaded compute instance with durable KV/SQLite storage. If you are unfamiliar with the platform primitive, start with [What are Durable Objects](https://developers.cloudflare.com/durable-objects/).
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
Each Agent composes a `Lifecycle` instance. The lifecycle installs request, alarm, and hibernating WebSocket entry points while the Agent supplies semantic callbacks and higher-level features:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
DurableObject
|
|
15
|
+
└── Agent
|
|
16
|
+
└── owns Lifecycle
|
|
17
|
+
```
|
|
13
18
|
|
|
14
19
|
## Layer 0: Durable Object
|
|
15
20
|
|
|
@@ -73,11 +78,11 @@ export class MyDurableObject extends DurableObject {
|
|
|
73
78
|
}
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
###
|
|
81
|
+
### Alarms
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
`Lifecycle` owns the Agent's physical Durable Object alarm because schedules, keep-alive, fibers, sub-agents, and other capabilities share the same alarm slot. Do not override `alarm()` or call `this.ctx.storage.setAlarm()` from an Agent feature; one caller could overwrite another feature's wake-up.
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
Use `this.schedule()` for named Agent callbacks. A reusable capability with its own durable work implements `getNextAlarm()` and `onAlarm()`, then calls `this.lifecycle.alarms.rearm()` after its durable state changes. See [Durable Object lifecycle](./lifecycle.md#shared-alarm-ownership) and [Scheduling](./scheduling.md).
|
|
81
86
|
|
|
82
87
|
### `this.ctx`
|
|
83
88
|
|
|
@@ -102,104 +107,43 @@ const token = kv.get("someToken");
|
|
|
102
107
|
|
|
103
108
|
Lastly, it's worth mentioning that the DO also has the Worker `Env` in `this.env`. Read more [here](https://developers.cloudflare.com/workers/runtime-apis/bindings).
|
|
104
109
|
|
|
105
|
-
## Layer 1:
|
|
106
|
-
|
|
107
|
-
Now that you've seen what Durable Objects come with out-of-the-box, what [PartyKit](https://github.com/cloudflare/partykit)'s `Server` (package `partyserver`) implements will be clearer. It's an **opinionated `DurableObject` wrapper that improves DX by hiding away DO primitives in favor of more developer friendly callbacks**.
|
|
108
|
-
|
|
109
|
-
An important note is that `Server` **does NOT persist to the DO storage** so you will not see extra storage operations by using it.
|
|
110
|
-
|
|
111
|
-
### Addressing
|
|
110
|
+
## Layer 1: lifecycle composition
|
|
112
111
|
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
Compare this to the DO addressing [example above](#rpc).
|
|
112
|
+
`Agent` uses `Lifecycle.install(this)`, an explicit side-effect-named factory that constructs the lifecycle and installs the platform-facing `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and `webSocketError` handlers. Agent subclasses implement semantic callbacks instead of a second base class:
|
|
116
113
|
|
|
117
114
|
```ts
|
|
118
|
-
|
|
119
|
-
const stub = await getServerByName(env.MY_DO, "foo");
|
|
120
|
-
|
|
121
|
-
// We can still call RPC methods.
|
|
122
|
-
await stub.bar();
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Since we have a URL addressing scheme, we also get access to `routePartykitRequest()`.
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
129
|
-
// Behind the scenes, PartyKit normalizes your DO binding names
|
|
130
|
-
// and tries to do some pattern matching.
|
|
131
|
-
const res = await routePartykitRequest(request, env);
|
|
132
|
-
|
|
133
|
-
if (res) return res;
|
|
134
|
-
|
|
135
|
-
return Response("Not found", { status: 404 });
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
You can have a look at [the implementation](https://github.com/cloudflare/partykit/blob/main/packages/partyserver/src/index.ts#L122) if you're interested.
|
|
140
|
-
|
|
141
|
-
### `onStart`
|
|
142
|
-
|
|
143
|
-
The extra plumbing that `Server` includes on addressing allows it to expose an `onStart` callback that is **executed every time the DO starts up** (the DO was evicted, hibernated or never created at all) and **before any `fetch` or RPC**.
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
class MyServer extends Server {
|
|
115
|
+
class MyAgent extends Agent {
|
|
147
116
|
onStart() {
|
|
148
|
-
//
|
|
149
|
-
// to run every time the DO is started up.
|
|
150
|
-
const sql = this.ctx.storage.sql;
|
|
151
|
-
sql.exec(`...`);
|
|
117
|
+
// Runs once per in-memory lifetime before work is handled.
|
|
152
118
|
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### `onRequest` and `onConnect`
|
|
157
119
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
class MyServer extends Server {
|
|
162
|
-
async onRequest(request: Request) {
|
|
163
|
-
const url = new URL(request.url);
|
|
164
|
-
|
|
165
|
-
return new Response(`Hello from ${url.origin}!`);
|
|
120
|
+
onRequest(request: Request) {
|
|
121
|
+
return new Response(`Hello from ${request.url}`);
|
|
166
122
|
}
|
|
167
123
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const url = new URL(request.url);
|
|
171
|
-
|
|
172
|
-
// Connections are a WebSocket wrapper
|
|
173
|
-
conn.send(`Hello from ${url.origin}!`);
|
|
124
|
+
onConnect(connection: Connection) {
|
|
125
|
+
connection.send("connected");
|
|
174
126
|
}
|
|
175
127
|
}
|
|
176
128
|
```
|
|
177
129
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Just as `onConnect` is the callback for every new connection, `Server` also provides wrappers on top of the default callbacks from the `DurableObject` class: `onMessage`, `onClose` and `onError`.
|
|
181
|
-
|
|
182
|
-
There's also `this.broadcast` that sends a WS message to all connected clients (no magic, just a loop over `this.getConnections()`!).
|
|
183
|
-
|
|
184
|
-
### `this.name`
|
|
130
|
+
Lifecycle WebSockets always use Cloudflare's Hibernation API. Idle clients stay connected while the Durable Object can leave memory; constructor fields and `onStart` run again when a message wakes it. Persist anything needed across wakes in storage or `connection.state`.
|
|
185
131
|
|
|
186
|
-
|
|
132
|
+
Reusable capabilities can be installed through `this.lifecycle.use(capability)`. Capabilities start before Agent startup, can intercept requests before `onRequest`, and process alarms before `onAlarm`. See [Durable Object lifecycle](./lifecycle.md).
|
|
187
133
|
|
|
188
|
-
|
|
134
|
+
### Identity
|
|
189
135
|
|
|
190
|
-
-
|
|
191
|
-
- the name is longer than 1,024 bytes;
|
|
192
|
-
- the alarm firing was scheduled before 2026-03-15, or was scheduled from a context that itself had no name (reschedule it from a `fetch()` or RPC handler where the name is available).
|
|
136
|
+
Since [2026-03-15](https://developers.cloudflare.com/changelog/post/2026-03-15-durable-object-id-name/), Workers exposes the name used by `idFromName()` or `getByName()` as `ctx.id.name`, including in alarm handlers. Agents and Agent facets use named IDs, and `this.name` projects that native identity.
|
|
193
137
|
|
|
194
|
-
For
|
|
138
|
+
For migration, lifecycle can read an existing `__ps_name` value written by an older release. It never writes a duplicate name. Raw IDs, `idFromString()`, and names over 1,024 bytes do not provide native identity. Alarms created before 2026-03-15 must be rescheduled from a named fetch or RPC handler.
|
|
195
139
|
|
|
196
140
|
## Layer 2: Agent
|
|
197
141
|
|
|
198
|
-
|
|
142
|
+
The `Agent` class directly extends `DurableObject`, composes the lifecycle above, and provides opinionated primitives for stateful, schedulable, and observable agents that can communicate via RPC, WebSockets, and (even!) email.
|
|
199
143
|
|
|
200
144
|
### `this.state` and `this.setState()`
|
|
201
145
|
|
|
202
|
-
One of the core features of `Agent` is **automatic state persistence**. Developers define the shape of their state via the generic parameter and `initialState` (which is only used if no state exists in storage), and the Agent handles loading, saving, and broadcasting state changes (
|
|
146
|
+
One of the core features of `Agent` is **automatic state persistence**. Developers define the shape of their state via the generic parameter and `initialState` (which is only used if no state exists in storage), and the Agent handles loading, saving, and broadcasting state changes (using its lifecycle-managed WebSocket connections).
|
|
203
147
|
|
|
204
148
|
`this.state` is a getter that lazily loads state from storage (SQL). **State is persisted across DO evictions** when it's updated with `this.setState()`, which automatically serializes the state and writes it back to storage.
|
|
205
149
|
There's also `this.onStateChanged` that you can override to react to state changes.
|
|
@@ -250,7 +194,7 @@ class MyAgent extends Agent {
|
|
|
250
194
|
|
|
251
195
|
### RPC and Callable Methods
|
|
252
196
|
|
|
253
|
-
`agents` take Durable Objects RPC one step forward by implementing RPC through WebSockets, so clients can also call methods on the Agent directly. To make a method callable through WS, developers can use the `@callable` decorator. Methods can return a serializable value or
|
|
197
|
+
`agents` take Durable Objects RPC one step forward by implementing RPC through WebSockets, so clients can also call methods on the Agent directly. To make a method callable through WS, developers can use the `@callable` decorator. Methods can return a serializable value or stream chunks (when using `@callable({ streaming: true })`).
|
|
254
198
|
|
|
255
199
|
```ts
|
|
256
200
|
class MyAgent extends Agent {
|
|
@@ -419,7 +363,7 @@ function someUtilityFunction() {
|
|
|
419
363
|
|
|
420
364
|
### `this.onError`
|
|
421
365
|
|
|
422
|
-
`Agent
|
|
366
|
+
`Agent.onError` handles both WebSocket errors and other Agent errors. It is called with a `Connection` or `unknown` error.
|
|
423
367
|
|
|
424
368
|
```ts
|
|
425
369
|
class MyAgent extends Agent {
|
|
@@ -495,14 +439,12 @@ application; return a recovery result to update the retained status record.
|
|
|
495
439
|
|
|
496
440
|
### Routing
|
|
497
441
|
|
|
498
|
-
|
|
442
|
+
Use `getAgentByName` for named RPC stubs and `routeAgentRequest` for `/agents/:class/:name` HTTP and WebSocket routing.
|
|
499
443
|
|
|
500
444
|
```ts
|
|
501
|
-
// Same API as getServerByName
|
|
502
445
|
const stub = await getAgentByName(env.MY_DO, "foo");
|
|
503
446
|
// ...
|
|
504
447
|
|
|
505
|
-
// Same API as routeServerRequest
|
|
506
448
|
const res = await routeAgentRequest(request, env);
|
|
507
449
|
|
|
508
450
|
if (res) return res;
|
package/docs/agent-tools.md
CHANGED
|
@@ -48,6 +48,35 @@ export class Assistant extends Think<Env> {
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
### Use Valibot or another schema library
|
|
52
|
+
|
|
53
|
+
`agentTool()` accepts the AI SDK's flexible schema format. For Valibot, wrap the
|
|
54
|
+
schema with `valibotSchema()` so the AI SDK receives both runtime validation and
|
|
55
|
+
the JSON Schema required by the model. Use `@ai-sdk/valibot` v2 with AI SDK 6
|
|
56
|
+
and v3 with AI SDK 7:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { valibotSchema } from "@ai-sdk/valibot";
|
|
60
|
+
import { agentTool } from "agents/agent-tools";
|
|
61
|
+
import * as v from "valibot";
|
|
62
|
+
|
|
63
|
+
const researchInput = valibotSchema(
|
|
64
|
+
v.object({
|
|
65
|
+
query: v.pipe(v.string(), v.minLength(3))
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const research = agentTool(Researcher, {
|
|
70
|
+
description: "Research one topic in depth.",
|
|
71
|
+
inputSchema: researchInput
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also provide a Zod schema, a Standard JSON Schema-compatible schema, or
|
|
76
|
+
a raw JSON Schema wrapped with `jsonSchema()` from `ai`. A validation-only
|
|
77
|
+
Standard Schema is not sufficient for a tool input because it does not provide
|
|
78
|
+
the JSON Schema sent to the model.
|
|
79
|
+
|
|
51
80
|
The child can also be an `AIChatAgent`:
|
|
52
81
|
|
|
53
82
|
```ts
|
package/docs/browse-the-web.md
CHANGED
|
@@ -244,7 +244,7 @@ const md = await browserMarkdown(this.env.BROWSER, { url });
|
|
|
244
244
|
const data = await browserExtract<{ price: number }>(this.env.BROWSER, {
|
|
245
245
|
url,
|
|
246
246
|
prompt: "the product price",
|
|
247
|
-
response_format: { type: "json_schema",
|
|
247
|
+
response_format: { type: "json_schema", json_schema: priceSchema }
|
|
248
248
|
});
|
|
249
249
|
```
|
|
250
250
|
|
|
@@ -279,6 +279,21 @@ class Researcher extends Think<Env> {
|
|
|
279
279
|
|
|
280
280
|
Quick Actions require a Worker `compatibility_date` of `2026-03-24` or later and `remote: true` on the browser binding for local `wrangler dev`.
|
|
281
281
|
|
|
282
|
+
## Using Kitesurf
|
|
283
|
+
|
|
284
|
+
To use Kitesurf with the CDP-based `browser_execute` tool, select it in the session options:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const tools = createBrowserTools({
|
|
288
|
+
ctx: this.ctx,
|
|
289
|
+
browser: this.env.BROWSER,
|
|
290
|
+
loader: this.env.LOADER,
|
|
291
|
+
session: { browser: "kitesurf" }
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
A Kitesurf browser is scoped to its CDP WebSocket. It therefore supports only one-shot execution. Session reuse, pause and resume, Live View, recording, `keepAliveMs`, protocol discovery, and Kitesurf-backed Quick Actions are not available.
|
|
296
|
+
|
|
282
297
|
## Live View and human-in-the-loop
|
|
283
298
|
|
|
284
299
|
[Live View](https://developers.cloudflare.com/browser-run/features/live-view/) lets a human open a URL and watch — or take control of — a running browser session in real time. It is the building block for human-in-the-loop steps such as logging in, solving a CAPTCHA, completing MFA, or entering data you do not want to pass through an automation script.
|
package/docs/chat-agents.md
CHANGED
|
@@ -25,7 +25,7 @@ Built on the [AI SDK](https://ai-sdk.dev) and Cloudflare Durable Objects, you ge
|
|
|
25
25
|
### Install
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
npm install @cloudflare/ai-chat agents ai workers-ai-provider
|
|
28
|
+
npm install @cloudflare/ai-chat agents ai @ai-sdk/react workers-ai-provider
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
### Server
|
|
@@ -590,17 +590,11 @@ If you do not pass `abortSignal` to `streamText`, the LLM call will continue run
|
|
|
590
590
|
|
|
591
591
|
### Stream Recovery
|
|
592
592
|
|
|
593
|
-
When a Durable Object is evicted mid-stream (code update, inactivity timeout, resource limit), the LLM connection is severed permanently and the in-memory streaming state is lost. `
|
|
593
|
+
When a Durable Object is evicted mid-stream (code update, inactivity timeout, resource limit), the LLM connection is severed permanently and the in-memory streaming state is lost. Durable recovery wraps every `AIChatAgent` and `Think` chat turn in a [`runFiber()`](./durable-execution.md), providing automatic `keepAlive` during streaming and a recovery hook on restart.
|
|
594
594
|
|
|
595
|
-
|
|
596
|
-
export class ChatAgent extends AIChatAgent {
|
|
597
|
-
override chatRecovery = true;
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
When enabled, every `onChatMessage` call runs inside a fiber. If the agent is evicted mid-stream, the fiber row survives in SQLite. On the next activation, the framework detects the interrupted fiber, reconstructs the partial response from buffered stream chunks, and calls `onChatRecovery`.
|
|
595
|
+
If the agent is evicted mid-stream, the fiber row survives in SQLite. On the next activation, the framework detects the interrupted fiber, reconstructs the partial response from buffered stream chunks, and calls `onChatRecovery`.
|
|
602
596
|
|
|
603
|
-
|
|
597
|
+
Durable recovery is always enabled. Use `chatRecovery` only to tune its budgets and terminal behavior.
|
|
604
598
|
|
|
605
599
|
> **Assign `chatRecovery` as a class field or in the constructor — never in `onStart()`.** On every wake the SDK evaluates recovery budgets (and may seal an interrupted turn, firing `onExhausted`) _before_ your `onStart()` body runs. A config produced inside `onStart()` is therefore read as the built-in defaults at the moment recovery decides, so your `maxRecoveryWork` / `shouldKeepRecovering` / `onExhausted` silently never apply to the recovery that matters. The SDK logs a one-time warning if it detects `chatRecovery` being assigned during `onStart()`.
|
|
606
600
|
|
|
@@ -610,8 +604,6 @@ Override to implement provider-specific recovery. The default behavior persists
|
|
|
610
604
|
|
|
611
605
|
```typescript
|
|
612
606
|
export class ChatAgent extends AIChatAgent {
|
|
613
|
-
override chatRecovery = true;
|
|
614
|
-
|
|
615
607
|
override async onChatRecovery(
|
|
616
608
|
ctx: ChatRecoveryContext
|
|
617
609
|
): Promise<ChatRecoveryOptions> {
|
|
@@ -659,7 +651,16 @@ Settled work is never dropped: `persist: false` only suppresses persistence of a
|
|
|
659
651
|
|
|
660
652
|
When recovery happens before any stream chunks were written, there is no partial assistant message to continue. If the latest persisted message is still the unanswered user message from the interrupted turn, the framework retries that turn automatically unless `continue` is `false`.
|
|
661
653
|
|
|
662
|
-
|
|
654
|
+
#### Controlling automatic continuation
|
|
655
|
+
|
|
656
|
+
Durable bookkeeping stays enabled even when automatic continuation is not appropriate:
|
|
657
|
+
|
|
658
|
+
- **Retries or side effects are unsafe:** override `onChatRecovery()` and return `{ continue: false }`. Persist idempotency keys or completion records before external side effects so a recovered turn can tell whether work already happened.
|
|
659
|
+
- **Cancellation must survive eviction:** an `AbortSignal` only cancels the current in-memory turn. Also persist cancellation intent in agent state or SQL, read it in `onChatRecovery()`, and return `{ continue: false }` when cancellation was requested.
|
|
660
|
+
- **Cost must be bounded:** set `maxAttempts`, `noProgressTimeoutMs`, `maxRecoveryWork`, and `maxOomRetries`. Use `shouldKeepRecovering` with durable spend data to stop later attempts. The predicate is not bound to the agent instance, so read spend from a store keyed by `ctx.recoveryRootRequestId`.
|
|
661
|
+
- **A provider can resume without a new model call:** use `this.stash()` to save its response ID, retrieve that response in `onChatRecovery()`, and return `{ persist: false, continue: false }`.
|
|
662
|
+
|
|
663
|
+
`chatRecovery` can be configured with budgets and terminal behavior:
|
|
663
664
|
|
|
664
665
|
```typescript
|
|
665
666
|
override chatRecovery = {
|
|
@@ -956,6 +957,8 @@ async onChatMessage() {
|
|
|
956
957
|
}
|
|
957
958
|
```
|
|
958
959
|
|
|
960
|
+
The `inputSchema` accepts the AI SDK's flexible schema format, so you are not limited to Zod. You can also use Valibot, a Standard JSON Schema-compatible schema, or a raw JSON Schema wrapped with `jsonSchema()` from `ai`. See [Use Valibot or another schema library](./agent-tools.md#use-valibot-or-another-schema-library) for details.
|
|
961
|
+
|
|
959
962
|
### Client-Side Tools
|
|
960
963
|
|
|
961
964
|
Define a tool on the server without `execute`, then handle it on the client with `onToolCall`. Use this for tools that need browser APIs:
|
package/docs/client-sdk.md
CHANGED
|
@@ -264,17 +264,21 @@ const result = await agent.stub.processData({ input: "test" });
|
|
|
264
264
|
|
|
265
265
|
### Streaming Responses
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
Mark a callable method as streaming. The framework passes a
|
|
268
|
+
`StreamingResponse` as its first argument:
|
|
268
269
|
|
|
269
270
|
```typescript
|
|
270
|
-
// Agent-side
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
271
|
+
// Agent-side
|
|
272
|
+
import { Agent, callable, type StreamingResponse } from "agents";
|
|
273
|
+
|
|
274
|
+
export class AIAgent extends Agent {
|
|
275
|
+
@callable({ streaming: true })
|
|
276
|
+
async generateText(stream: StreamingResponse, prompt: string) {
|
|
277
|
+
for (const chunk of ["Generated response for ", prompt]) {
|
|
278
|
+
stream.send(chunk);
|
|
276
279
|
}
|
|
277
|
-
|
|
280
|
+
stream.end();
|
|
281
|
+
}
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
// Client-side:
|
package/docs/configuration.md
CHANGED
|
@@ -617,12 +617,18 @@ Define environments in wrangler.jsonc:
|
|
|
617
617
|
"env": {
|
|
618
618
|
"staging": {
|
|
619
619
|
"name": "my-agent-staging",
|
|
620
|
+
"durable_objects": {
|
|
621
|
+
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
|
|
622
|
+
},
|
|
620
623
|
"vars": {
|
|
621
624
|
"ENVIRONMENT": "staging"
|
|
622
625
|
}
|
|
623
626
|
},
|
|
624
627
|
"production": {
|
|
625
628
|
"name": "my-agent-production",
|
|
629
|
+
"durable_objects": {
|
|
630
|
+
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }]
|
|
631
|
+
},
|
|
626
632
|
"vars": {
|
|
627
633
|
"ENVIRONMENT": "production"
|
|
628
634
|
}
|
|
@@ -647,7 +653,7 @@ npx wrangler secret put OPENAI_API_KEY --env production
|
|
|
647
653
|
|
|
648
654
|
### Separate Durable Objects
|
|
649
655
|
|
|
650
|
-
Each environment gets its own Durable Objects. Staging agents
|
|
656
|
+
Durable Object bindings are not inherited by named environments, so repeat them in every environment as shown above. Each environment gets its own Durable Objects. Staging agents do not share state with production agents.
|
|
651
657
|
|
|
652
658
|
To explicitly separate:
|
|
653
659
|
|
|
@@ -24,7 +24,7 @@ If the client and server share the origin, the browser will send cookies during
|
|
|
24
24
|
|
|
25
25
|
### Cross origin
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Cross-origin cookie behavior depends on the cookie's domain and `SameSite` attributes, whether the two origins are same-site, and browser third-party cookie policy. If you cannot rely on a cookie, pass a short-lived credential in the URL query and verify it on the server.
|
|
28
28
|
|
|
29
29
|
## Usage examples
|
|
30
30
|
|
|
@@ -79,50 +79,22 @@ function ChatComponent() {
|
|
|
79
79
|
|
|
80
80
|
### JWT refresh pattern
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
`useAgent` resolves an async query before connecting and reevaluates it when reconnecting. Return a fresh, short-lived application token each time:
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
85
|
import { useAgent } from "agents/react";
|
|
86
|
-
import { useCallback
|
|
87
|
-
|
|
88
|
-
const validateToken = async (token: string) => {
|
|
89
|
-
// An example of how you might implement this
|
|
90
|
-
const res = await fetch(`${API_HOST}/api/users/me`, {
|
|
91
|
-
headers: {
|
|
92
|
-
Authorization: `Bearer ${token}`
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return res.ok;
|
|
97
|
-
};
|
|
86
|
+
import { useCallback } from "react";
|
|
98
87
|
|
|
99
|
-
|
|
100
|
-
// Depends on implementation:
|
|
101
|
-
// - You could use a longer-lived token to refresh the expired token
|
|
102
|
-
// - De-auth the app and prompt the user to log in manually
|
|
103
|
-
// - ...
|
|
104
|
-
};
|
|
88
|
+
declare function getShortLivedAccessToken(): Promise<string>;
|
|
105
89
|
|
|
106
90
|
function useJWTAgent(agentName: string) {
|
|
107
91
|
const asyncQuery = useCallback(async () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// If no token OR the token is no longer valid
|
|
111
|
-
// request a fresh token
|
|
112
|
-
if (!token && !(await validateToken(token))) {
|
|
113
|
-
token = await refreshToken();
|
|
114
|
-
localStorage.setItem("jwt", token);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
token
|
|
119
|
-
};
|
|
92
|
+
return { token: await getShortLivedAccessToken() };
|
|
120
93
|
}, []);
|
|
121
94
|
|
|
122
|
-
|
|
95
|
+
return useAgent({
|
|
123
96
|
agent: agentName,
|
|
124
|
-
query: asyncQuery
|
|
125
|
-
queryDeps: [] // Run on mount
|
|
97
|
+
query: asyncQuery
|
|
126
98
|
});
|
|
127
99
|
}
|
|
128
100
|
```
|
|
@@ -466,7 +466,7 @@ Key points:
|
|
|
466
466
|
|
|
467
467
|
### Chat recovery
|
|
468
468
|
|
|
469
|
-
`AIChatAgent`
|
|
469
|
+
`AIChatAgent` and `Think` build on fibers for LLM streaming recovery. Every chat turn is wrapped in a fiber automatically. The framework handles the internal recovery path and exposes `onChatRecovery` for provider-specific strategies. See [Long-Running Agents: Recovering interrupted LLM streams](./long-running-agents.md#recovering-interrupted-llm-streams) and the [`forever-chat` example](https://github.com/cloudflare/agents/tree/main/experimental/forever-chat).
|
|
470
470
|
|
|
471
471
|
## Concurrent fibers
|
|
472
472
|
|