agents 0.2.35 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-agent.d.ts +35 -2
- package/dist/ai-chat-agent.js +26 -0
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-react.d.ts +75 -3
- package/dist/ai-react.js +60 -1
- package/dist/ai-react.js.map +1 -1
- package/dist/{client-BINtT7y-.d.ts → client-DFotUKH_.d.ts} +2 -2
- package/dist/{index-CxZRDFxS.d.ts → index-CT2tCrLr.d.ts} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/mcp/client.d.ts +1 -1
- package/dist/mcp/index.d.ts +2 -2
- package/dist/react.d.ts +2 -2
- package/package.json +7 -7
package/dist/ai-chat-agent.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./context-DcbQ8o7k.js";
|
|
2
|
-
import "./client-
|
|
2
|
+
import "./client-DFotUKH_.js";
|
|
3
3
|
import "./ai-types-0OnT3FHg.js";
|
|
4
|
-
import { n as AgentContext, t as Agent } from "./index-
|
|
4
|
+
import { n as AgentContext, t as Agent } from "./index-CT2tCrLr.js";
|
|
5
5
|
import {
|
|
6
6
|
JSONSchema7,
|
|
7
7
|
StreamTextOnFinishCallback,
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
*
|
|
19
19
|
* Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)
|
|
20
20
|
* because this is the wire format. Zod schemas cannot be serialized.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Define tools on the server using `tool()` from "ai" instead.
|
|
23
|
+
* For tools that need client-side execution, omit the `execute` function
|
|
24
|
+
* and handle them via the `onToolCall` callback in `useAgentChat`.
|
|
21
25
|
*/
|
|
22
26
|
type ClientToolSchema = {
|
|
23
27
|
/** Unique name for the tool */
|
|
@@ -37,6 +41,9 @@ type OnChatMessageOptions = {
|
|
|
37
41
|
* Tool schemas sent from the client for dynamic tool registration.
|
|
38
42
|
* These represent tools that will be executed on the client side.
|
|
39
43
|
* Use `createToolsFromClientSchemas()` to convert these to AI SDK tool format.
|
|
44
|
+
*
|
|
45
|
+
* @deprecated Define tools on the server instead. Use `onToolCall` callback
|
|
46
|
+
* in `useAgentChat` for client-side execution.
|
|
40
47
|
*/
|
|
41
48
|
clientTools?: ClientToolSchema[];
|
|
42
49
|
};
|
|
@@ -48,6 +55,32 @@ type OnChatMessageOptions = {
|
|
|
48
55
|
*
|
|
49
56
|
* @param clientTools - Array of tool schemas from the client
|
|
50
57
|
* @returns Record of AI SDK tools that can be spread into your tools object
|
|
58
|
+
*
|
|
59
|
+
* @deprecated Define tools on the server using `tool()` from "ai" instead.
|
|
60
|
+
* For tools that need client-side execution, omit the `execute` function
|
|
61
|
+
* and handle them via the `onToolCall` callback in `useAgentChat`.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Server: Define tool without execute
|
|
66
|
+
* const tools = {
|
|
67
|
+
* getLocation: tool({
|
|
68
|
+
* description: "Get user's location",
|
|
69
|
+
* inputSchema: z.object({})
|
|
70
|
+
* // No execute = client must handle
|
|
71
|
+
* })
|
|
72
|
+
* };
|
|
73
|
+
*
|
|
74
|
+
* // Client: Handle in onToolCall
|
|
75
|
+
* useAgentChat({
|
|
76
|
+
* onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
77
|
+
* if (toolCall.toolName === 'getLocation') {
|
|
78
|
+
* const pos = await navigator.geolocation.getCurrentPosition();
|
|
79
|
+
* addToolOutput({ toolCallId: toolCall.toolCallId, output: pos });
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
51
84
|
*/
|
|
52
85
|
declare function createToolsFromClientSchemas(
|
|
53
86
|
clientTools?: ClientToolSchema[]
|
package/dist/ai-chat-agent.js
CHANGED
|
@@ -17,6 +17,32 @@ import { nanoid } from "nanoid";
|
|
|
17
17
|
*
|
|
18
18
|
* @param clientTools - Array of tool schemas from the client
|
|
19
19
|
* @returns Record of AI SDK tools that can be spread into your tools object
|
|
20
|
+
*
|
|
21
|
+
* @deprecated Define tools on the server using `tool()` from "ai" instead.
|
|
22
|
+
* For tools that need client-side execution, omit the `execute` function
|
|
23
|
+
* and handle them via the `onToolCall` callback in `useAgentChat`.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Server: Define tool without execute
|
|
28
|
+
* const tools = {
|
|
29
|
+
* getLocation: tool({
|
|
30
|
+
* description: "Get user's location",
|
|
31
|
+
* inputSchema: z.object({})
|
|
32
|
+
* // No execute = client must handle
|
|
33
|
+
* })
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* // Client: Handle in onToolCall
|
|
37
|
+
* useAgentChat({
|
|
38
|
+
* onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
39
|
+
* if (toolCall.toolName === 'getLocation') {
|
|
40
|
+
* const pos = await navigator.geolocation.getCurrentPosition();
|
|
41
|
+
* addToolOutput({ toolCallId: toolCall.toolCallId, output: pos });
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
20
46
|
*/
|
|
21
47
|
function createToolsFromClientSchemas(clientTools) {
|
|
22
48
|
if (!clientTools || clientTools.length === 0) return {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-chat-agent.js","names":["ctx","data: IncomingMessage","newMetadata: ProviderMetadata | undefined","message: ChatMessage | undefined","updatedMessage: ChatMessage","message: ChatMessage","activeTextParts: Record<string, TextUIPart>","activeReasoningParts: Record<string, ReasoningUIPart>","partialToolCalls: Record<\n string,\n { text: string; index: number; toolName: string; dynamic?: boolean }\n >","part","options","isToolUIPart","data: UIMessageChunk","textPart: TextUIPart","reasoningPart: ReasoningUIPart","getToolName","eventToSend: unknown","mergedMessage: ChatMessage"],"sources":["../src/ai-chat-agent.ts"],"sourcesContent":["import type {\n UIMessage as ChatMessage,\n DynamicToolUIPart,\n JSONSchema7,\n ProviderMetadata,\n ReasoningUIPart,\n StreamTextOnFinishCallback,\n TextUIPart,\n Tool,\n ToolSet,\n ToolUIPart,\n UIMessageChunk\n} from \"ai\";\nimport { tool, jsonSchema } from \"ai\";\nimport {\n Agent,\n type AgentContext,\n type Connection,\n type ConnectionContext,\n type WSMessage\n} from \"./\";\nimport { agentContext } from \"./context\";\nimport {\n MessageType,\n type IncomingMessage,\n type OutgoingMessage\n} from \"./ai-types\";\nimport { autoTransformMessages } from \"./ai-chat-v5-migration\";\nimport { nanoid } from \"nanoid\";\n\n/**\n * Schema for a client-defined tool sent from the browser.\n * These tools are executed on the client, not the server.\n *\n * Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)\n * because this is the wire format. Zod schemas cannot be serialized.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\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\n/**\n * Options passed to the onChatMessage handler.\n */\nexport type OnChatMessageOptions = {\n /** AbortSignal for cancelling the request */\n abortSignal?: AbortSignal;\n /**\n * Tool schemas sent from the client for dynamic tool registration.\n * These represent tools that will be executed on the client side.\n * Use `createToolsFromClientSchemas()` to convert these to AI SDK tool format.\n */\n clientTools?: ClientToolSchema[];\n};\n\n/**\n * Converts client tool schemas to AI SDK tool format.\n *\n * These tools have no `execute` function - when the AI model calls them,\n * the tool call is sent back to the client for execution.\n *\n * @param clientTools - Array of tool schemas from the client\n * @returns Record of AI SDK tools that can be spread into your tools object\n */\nexport function createToolsFromClientSchemas(\n clientTools?: ClientToolSchema[]\n): ToolSet {\n if (!clientTools || clientTools.length === 0) {\n return {};\n }\n\n // Check for duplicate tool names\n const seenNames = new Set<string>();\n for (const t of clientTools) {\n if (seenNames.has(t.name)) {\n console.warn(\n `[createToolsFromClientSchemas] Duplicate tool name \"${t.name}\" found. Later definitions will override earlier ones.`\n );\n }\n seenNames.add(t.name);\n }\n\n return Object.fromEntries(\n clientTools.map((t) => [\n t.name,\n tool({\n description: t.description ?? \"\",\n inputSchema: jsonSchema(t.parameters ?? { type: \"object\" })\n // No execute function = tool call is sent back to client\n })\n ])\n );\n}\n\n/** Number of chunks to buffer before flushing to SQLite */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\n/** Maximum age for a \"streaming\" stream before considering it stale (ms) - 5 minutes */\nconst STREAM_STALE_THRESHOLD_MS = 5 * 60 * 1000;\n/** Default cleanup interval for old streams (ms) - every 10 minutes */\nconst CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\n/** Default age threshold for cleaning up completed streams (ms) - 24 hours */\nconst CLEANUP_AGE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\nconst decoder = new TextDecoder();\n\n/**\n * Stored stream chunk for resumable streaming\n */\ntype StreamChunk = {\n id: string;\n stream_id: string;\n body: string;\n chunk_index: number;\n created_at: number;\n};\n\n/**\n * Stream metadata for tracking active streams\n */\ntype StreamMetadata = {\n id: string;\n request_id: string;\n status: \"streaming\" | \"completed\" | \"error\";\n created_at: number;\n completed_at: number | null;\n};\n\n/**\n * Extension of Agent with built-in chat capabilities\n * @template Env Environment type containing bindings\n */\nexport class AIChatAgent<\n Env extends Cloudflare.Env = Cloudflare.Env,\n State = unknown\n> extends Agent<Env, State> {\n /**\n * Map of message `id`s to `AbortController`s\n * useful to propagate request cancellation signals for any external calls made by the agent\n */\n private _chatMessageAbortControllers: Map<string, AbortController>;\n\n /**\n * Currently active stream ID for resumable streaming.\n * Stored in memory for quick access; persisted in stream_metadata table.\n * @internal Protected for testing purposes.\n */\n protected _activeStreamId: string | null = null;\n\n /**\n * Request ID associated with the active stream.\n * @internal Protected for testing purposes.\n */\n protected _activeRequestId: string | null = null;\n\n /**\n * The message currently being streamed. Used to apply tool results\n * before the message is persisted.\n * @internal\n */\n private _streamingMessage: ChatMessage | null = null;\n\n /**\n * Promise that resolves when the current stream completes.\n * Used to wait for message persistence before continuing after tool results.\n * @internal\n */\n private _streamCompletionPromise: Promise<void> | null = null;\n private _streamCompletionResolve: (() => void) | null = null;\n\n /**\n * Current chunk index for the active stream\n */\n private _streamChunkIndex = 0;\n\n /**\n * Buffer for stream chunks pending write to SQLite.\n * Chunks are batched and flushed when buffer reaches CHUNK_BUFFER_SIZE.\n */\n private _chunkBuffer: Array<{\n id: string;\n streamId: string;\n body: string;\n index: number;\n }> = [];\n\n /**\n * Lock to prevent concurrent flush operations\n */\n private _isFlushingChunks = false;\n\n /**\n * Timestamp of the last cleanup operation for old streams\n */\n private _lastCleanupTime = 0;\n\n /** Array of chat messages for the current conversation */\n messages: ChatMessage[];\n\n constructor(ctx: AgentContext, env: Env) {\n super(ctx, env);\n this.sql`create table if not exists cf_ai_chat_agent_messages (\n id text primary key,\n message text not null,\n created_at datetime default current_timestamp\n )`;\n\n // Create tables for automatic resumable streaming\n this.sql`create table if not exists cf_ai_chat_stream_chunks (\n id text primary key,\n stream_id text not null,\n body text not null,\n chunk_index integer not null,\n created_at integer not null\n )`;\n\n this.sql`create table if not exists cf_ai_chat_stream_metadata (\n id text primary key,\n request_id text not null,\n status text not null,\n created_at integer not null,\n completed_at integer\n )`;\n\n this.sql`create index if not exists idx_stream_chunks_stream_id \n on cf_ai_chat_stream_chunks(stream_id, chunk_index)`;\n\n // Load messages and automatically transform them to v5 format\n const rawMessages = this._loadMessagesFromDb();\n\n // Automatic migration following https://jhak.im/blog/ai-sdk-migration-handling-previously-saved-messages\n this.messages = autoTransformMessages(rawMessages);\n\n this._chatMessageAbortControllers = new Map();\n\n // Check for any active streams from a previous session\n this._restoreActiveStream();\n const _onConnect = this.onConnect.bind(this);\n this.onConnect = async (connection: Connection, ctx: ConnectionContext) => {\n // Notify client about active streams that can be resumed\n if (this._activeStreamId) {\n this._notifyStreamResuming(connection);\n }\n // Call consumer's onConnect\n return _onConnect(connection, ctx);\n };\n\n // Wrap onMessage\n const _onMessage = this.onMessage.bind(this);\n this.onMessage = async (connection: Connection, message: WSMessage) => {\n // Handle AIChatAgent's internal messages first\n if (typeof message === \"string\") {\n let data: IncomingMessage;\n try {\n data = JSON.parse(message) as IncomingMessage;\n } catch (_error) {\n // Not JSON, forward to consumer\n return _onMessage(connection, message);\n }\n\n // Handle chat request\n if (\n data.type === MessageType.CF_AGENT_USE_CHAT_REQUEST &&\n data.init.method === \"POST\"\n ) {\n const { body } = data.init;\n const parsed = JSON.parse(body as string);\n const { messages, clientTools } = parsed as {\n messages: ChatMessage[];\n clientTools?: ClientToolSchema[];\n };\n\n // Automatically transform any incoming messages\n const transformedMessages = autoTransformMessages(messages);\n\n this._broadcastChatMessage(\n {\n messages: transformedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n },\n [connection.id]\n );\n\n await this.persistMessages(transformedMessages, [connection.id]);\n\n this.observability?.emit(\n {\n displayMessage: \"Chat message request\",\n id: data.id,\n payload: {},\n timestamp: Date.now(),\n type: \"message:request\"\n },\n this.ctx\n );\n\n const chatMessageId = data.id;\n const abortSignal = this._getAbortSignal(chatMessageId);\n\n return this._tryCatchChat(async () => {\n // Wrap in agentContext.run() to propagate connection context to onChatMessage\n // This ensures getCurrentAgent() returns the connection inside tool execute functions\n return agentContext.run(\n {\n agent: this,\n connection,\n request: undefined,\n email: undefined\n },\n async () => {\n const response = await this.onChatMessage(\n async (_finishResult) => {\n this._removeAbortController(chatMessageId);\n\n this.observability?.emit(\n {\n displayMessage: \"Chat message response\",\n id: data.id,\n payload: {},\n timestamp: Date.now(),\n type: \"message:response\"\n },\n this.ctx\n );\n },\n {\n abortSignal,\n clientTools\n }\n );\n\n if (response) {\n await this._reply(data.id, response, [connection.id]);\n } else {\n console.warn(\n `[AIChatAgent] onChatMessage returned no response for chatMessageId: ${chatMessageId}`\n );\n this._broadcastChatMessage(\n {\n body: \"No response was generated by the agent.\",\n done: true,\n id: data.id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n },\n [connection.id]\n );\n }\n }\n );\n });\n }\n\n // Handle clear chat\n if (data.type === MessageType.CF_AGENT_CHAT_CLEAR) {\n this._destroyAbortControllers();\n this.sql`delete from cf_ai_chat_agent_messages`;\n this.sql`delete from cf_ai_chat_stream_chunks`;\n this.sql`delete from cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n this.messages = [];\n this._broadcastChatMessage(\n { type: MessageType.CF_AGENT_CHAT_CLEAR },\n [connection.id]\n );\n return;\n }\n\n // Handle message replacement\n if (data.type === MessageType.CF_AGENT_CHAT_MESSAGES) {\n const transformedMessages = autoTransformMessages(data.messages);\n await this.persistMessages(transformedMessages, [connection.id]);\n return;\n }\n\n // Handle request cancellation\n if (data.type === MessageType.CF_AGENT_CHAT_REQUEST_CANCEL) {\n this._cancelChatRequest(data.id);\n return;\n }\n\n // Handle stream resume acknowledgment\n if (data.type === MessageType.CF_AGENT_STREAM_RESUME_ACK) {\n if (\n this._activeStreamId &&\n this._activeRequestId &&\n this._activeRequestId === data.id\n ) {\n this._sendStreamChunks(\n connection,\n this._activeStreamId,\n this._activeRequestId\n );\n }\n return;\n }\n\n // Handle client-side tool result\n if (data.type === MessageType.CF_AGENT_TOOL_RESULT) {\n const { toolCallId, toolName, output, autoContinue } = data;\n\n // Apply the tool result\n this._applyToolResult(toolCallId, toolName, output).then(\n (applied) => {\n // Only auto-continue if client requested it (opt-in behavior)\n // This mimics server-executed tool behavior where the LLM\n // automatically continues after seeing tool results\n if (applied && autoContinue) {\n // Wait for the original stream to complete and message to be persisted\n // before calling onChatMessage, so this.messages includes the tool result\n const waitForStream = async () => {\n if (this._streamCompletionPromise) {\n await this._streamCompletionPromise;\n } else {\n // If no promise, wait a bit for the stream to finish\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n };\n\n waitForStream().then(() => {\n const continuationId = nanoid();\n const abortSignal = this._getAbortSignal(continuationId);\n\n this._tryCatchChat(async () => {\n return agentContext.run(\n {\n agent: this,\n connection,\n request: undefined,\n email: undefined\n },\n async () => {\n const response = await this.onChatMessage(\n async (_finishResult) => {\n this._removeAbortController(continuationId);\n\n this.observability?.emit(\n {\n displayMessage:\n \"Chat message response (tool continuation)\",\n id: continuationId,\n payload: {},\n timestamp: Date.now(),\n type: \"message:response\"\n },\n this.ctx\n );\n },\n {\n abortSignal\n }\n );\n\n if (response) {\n // Pass continuation flag to merge parts into last assistant message\n // Note: We pass an empty excludeBroadcastIds array because the sender\n // NEEDS to receive the continuation stream. Unlike regular chat requests\n // where aiFetch handles the response, tool continuations have no listener\n // waiting - the client relies on the broadcast.\n await this._reply(\n continuationId,\n response,\n [], // Don't exclude sender - they need the continuation\n { continuation: true }\n );\n }\n }\n );\n });\n });\n }\n }\n );\n return;\n }\n }\n\n // Forward unhandled messages to consumer's onMessage\n return _onMessage(connection, message);\n };\n }\n\n /**\n * Restore active stream state if the agent was restarted during streaming.\n * Called during construction to recover any interrupted streams.\n * Validates stream freshness to avoid sending stale resume notifications.\n * @internal Protected for testing purposes.\n */\n protected _restoreActiveStream() {\n const activeStreams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata \n where status = 'streaming' \n order by created_at desc \n limit 1\n `;\n\n if (activeStreams && activeStreams.length > 0) {\n const stream = activeStreams[0];\n const streamAge = Date.now() - stream.created_at;\n\n // Check if stream is stale; delete to free storage\n if (streamAge > STREAM_STALE_THRESHOLD_MS) {\n this\n .sql`delete from cf_ai_chat_stream_chunks where stream_id = ${stream.id}`;\n this\n .sql`delete from cf_ai_chat_stream_metadata where id = ${stream.id}`;\n console.warn(\n `[AIChatAgent] Deleted stale stream ${stream.id} (age: ${Math.round(streamAge / 1000)}s)`\n );\n return;\n }\n\n this._activeStreamId = stream.id;\n this._activeRequestId = stream.request_id;\n\n // Get the last chunk index\n const lastChunk = this.sql<{ max_index: number }>`\n select max(chunk_index) as max_index \n from cf_ai_chat_stream_chunks \n where stream_id = ${this._activeStreamId}\n `;\n this._streamChunkIndex =\n lastChunk && lastChunk[0]?.max_index != null\n ? lastChunk[0].max_index + 1\n : 0;\n }\n }\n\n /**\n * Notify a connection about an active stream that can be resumed.\n * The client should respond with CF_AGENT_STREAM_RESUME_ACK to receive chunks.\n * Uses in-memory state for request ID - no extra DB lookup needed.\n * @param connection - The WebSocket connection to notify\n */\n private _notifyStreamResuming(connection: Connection) {\n if (!this._activeStreamId || !this._activeRequestId) {\n return;\n }\n\n // Notify client - they will send ACK when ready\n connection.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUMING,\n id: this._activeRequestId\n })\n );\n }\n\n /**\n * Send stream chunks to a connection after receiving ACK.\n * @param connection - The WebSocket connection\n * @param streamId - The stream to replay\n * @param requestId - The original request ID\n */\n private _sendStreamChunks(\n connection: Connection,\n streamId: string,\n requestId: string\n ) {\n // Flush any pending chunks first to ensure we have the latest\n this._flushChunkBuffer();\n\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n `;\n\n // Send all stored chunks\n for (const chunk of chunks || []) {\n connection.send(\n JSON.stringify({\n body: chunk.body,\n done: false,\n id: requestId,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n })\n );\n }\n\n // If the stream is no longer active (completed), send done signal\n // We track active state in memory, no need to query DB\n if (this._activeStreamId !== streamId) {\n connection.send(\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n })\n );\n }\n }\n\n /**\n * Buffer a stream chunk for batch write to SQLite.\n * @param streamId - The stream this chunk belongs to\n * @param body - The serialized chunk body\n * @internal Protected for testing purposes.\n */\n protected _storeStreamChunk(streamId: string, body: string) {\n // Force flush if buffer is at max to prevent memory issues\n if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) {\n this._flushChunkBuffer();\n }\n\n this._chunkBuffer.push({\n id: nanoid(),\n streamId,\n body,\n index: this._streamChunkIndex\n });\n this._streamChunkIndex++;\n\n // Flush when buffer reaches threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this._flushChunkBuffer();\n }\n }\n\n /**\n * Flush buffered chunks to SQLite in a single batch.\n * Uses a lock to prevent concurrent flush operations.\n * @internal Protected for testing purposes.\n */\n protected _flushChunkBuffer() {\n // Prevent concurrent flushes\n if (this._isFlushingChunks || this._chunkBuffer.length === 0) {\n return;\n }\n\n this._isFlushingChunks = true;\n try {\n const chunks = this._chunkBuffer;\n this._chunkBuffer = [];\n\n // Batch insert all chunks\n const now = Date.now();\n for (const chunk of chunks) {\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${chunk.id}, ${chunk.streamId}, ${chunk.body}, ${chunk.index}, ${now})\n `;\n }\n } finally {\n this._isFlushingChunks = false;\n }\n }\n\n /**\n * Start tracking a new stream for resumable streaming.\n * Creates metadata entry in SQLite and sets up tracking state.\n * @param requestId - The unique ID of the chat request\n * @returns The generated stream ID\n * @internal Protected for testing purposes.\n */\n protected _startStream(requestId: string): string {\n // Flush any pending chunks from previous streams to prevent mixing\n this._flushChunkBuffer();\n\n const streamId = nanoid();\n this._activeStreamId = streamId;\n this._activeRequestId = requestId;\n this._streamChunkIndex = 0;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()})\n `;\n\n return streamId;\n }\n\n /**\n * Mark a stream as completed and flush any pending chunks.\n * @param streamId - The stream to mark as completed\n * @internal Protected for testing purposes.\n */\n protected _completeStream(streamId: string) {\n // Flush any pending chunks before completing\n this._flushChunkBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'completed', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n\n // Periodically clean up old streams (not on every completion)\n this._maybeCleanupOldStreams();\n }\n\n /**\n * Clean up old completed streams if enough time has passed since last cleanup.\n * This prevents database growth while avoiding cleanup overhead on every stream completion.\n */\n private _maybeCleanupOldStreams() {\n const now = Date.now();\n if (now - this._lastCleanupTime < CLEANUP_INTERVAL_MS) {\n return;\n }\n this._lastCleanupTime = now;\n\n const cutoff = now - CLEANUP_AGE_THRESHOLD_MS;\n this.sql`\n delete from cf_ai_chat_stream_chunks \n where stream_id in (\n select id from cf_ai_chat_stream_metadata \n where status = 'completed' and completed_at < ${cutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata \n where status = 'completed' and completed_at < ${cutoff}\n `;\n }\n\n private _broadcastChatMessage(message: OutgoingMessage, exclude?: string[]) {\n this.broadcast(JSON.stringify(message), exclude);\n }\n\n private _loadMessagesFromDb(): ChatMessage[] {\n const rows =\n this.sql`select * from cf_ai_chat_agent_messages order by created_at` ||\n [];\n return rows\n .map((row) => {\n try {\n return JSON.parse(row.message as string);\n } catch (error) {\n console.error(`Failed to parse message ${row.id}:`, error);\n return null;\n }\n })\n .filter((msg): msg is ChatMessage => msg !== null);\n }\n\n override async onRequest(request: Request): Promise<Response> {\n return this._tryCatchChat(async () => {\n const url = new URL(request.url);\n\n if (url.pathname.endsWith(\"/get-messages\")) {\n const messages = this._loadMessagesFromDb();\n return Response.json(messages);\n }\n\n return super.onRequest(request);\n });\n }\n\n private async _tryCatchChat<T>(fn: () => T | Promise<T>) {\n try {\n return await fn();\n } catch (e) {\n throw this.onError(e);\n }\n }\n\n /**\n * Handle incoming chat messages and generate a response\n * @param onFinish Callback to be called when the response is finished\n * @param options Options including abort signal and client-defined tools\n * @returns Response to send to the client or undefined\n */\n async onChatMessage(\n // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later\n onFinish: StreamTextOnFinishCallback<ToolSet>,\n // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later\n options?: OnChatMessageOptions\n ): Promise<Response | undefined> {\n throw new Error(\n \"recieved a chat message, override onChatMessage and return a Response to send to the client\"\n );\n }\n\n /**\n * Save messages on the server side\n * @param messages Chat messages to save\n */\n async saveMessages(messages: ChatMessage[]) {\n await this.persistMessages(messages);\n await this._tryCatchChat(async () => {\n const response = await this.onChatMessage(() => {});\n if (response) this._reply(crypto.randomUUID(), response);\n });\n }\n\n async persistMessages(\n messages: ChatMessage[],\n excludeBroadcastIds: string[] = []\n ) {\n // Merge incoming messages with existing server state to preserve tool outputs.\n // This is critical for client-side tools: the client sends messages without\n // tool outputs, but the server has them via _applyToolResult.\n const mergedMessages = this._mergeIncomingWithServerState(messages);\n\n // Persist the merged messages\n for (const message of mergedMessages) {\n // Strip OpenAI item IDs to prevent \"Duplicate item found\" errors\n // when using the OpenAI Responses API. These IDs are assigned by OpenAI\n // and if sent back in subsequent requests, cause duplicate detection.\n const sanitizedMessage = this._sanitizeMessageForPersistence(message);\n const messageToSave = this._resolveMessageForToolMerge(sanitizedMessage);\n this.sql`\n insert into cf_ai_chat_agent_messages (id, message)\n values (${messageToSave.id}, ${JSON.stringify(messageToSave)})\n on conflict(id) do update set message = excluded.message\n `;\n }\n\n // refresh in-memory messages\n const persisted = this._loadMessagesFromDb();\n this.messages = autoTransformMessages(persisted);\n this._broadcastChatMessage(\n {\n messages: mergedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n },\n excludeBroadcastIds\n );\n }\n\n /**\n * Merges incoming messages with existing server state.\n * This preserves tool outputs that the server has (via _applyToolResult)\n * but the client doesn't have yet.\n *\n * @param incomingMessages - Messages from the client\n * @returns Messages with server's tool outputs preserved\n */\n private _mergeIncomingWithServerState(\n incomingMessages: ChatMessage[]\n ): ChatMessage[] {\n // Build a map of toolCallId -> output from existing server messages\n const serverToolOutputs = new Map<string, unknown>();\n for (const msg of this.messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\" &&\n \"output\" in part\n ) {\n serverToolOutputs.set(\n part.toolCallId as string,\n (part as { output: unknown }).output\n );\n }\n }\n }\n\n // If server has no tool outputs, return incoming messages as-is\n if (serverToolOutputs.size === 0) {\n return incomingMessages;\n }\n\n // Merge server's tool outputs into incoming messages\n return incomingMessages.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n // If this is a tool part in input-available state and server has the output\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"input-available\" &&\n serverToolOutputs.has(part.toolCallId as string)\n ) {\n hasChanges = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output: serverToolOutputs.get(part.toolCallId as string)\n };\n }\n return part;\n }) as ChatMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n }\n\n /**\n * Resolves a message for persistence, handling tool result merging.\n * If the message contains tool parts with output-available state, checks if there's\n * an existing message with the same toolCallId that should be updated instead of\n * creating a duplicate. This prevents the \"Duplicate item found\" error from OpenAI\n * when client-side tool results arrive in a new request.\n *\n * @param message - The message to potentially merge\n * @returns The message with the correct ID (either original or merged)\n */\n private _resolveMessageForToolMerge(message: ChatMessage): ChatMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n // Check if this message has tool parts with output-available state\n for (const part of message.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\"\n ) {\n const toolCallId = part.toolCallId as string;\n\n // Look for an existing message with this toolCallId in input-available state\n const existingMessage = this._findMessageByToolCallId(toolCallId);\n if (existingMessage && existingMessage.id !== message.id) {\n // Found a match - merge by using the existing message's ID\n // This ensures the SQL upsert updates the existing row\n return {\n ...message,\n id: existingMessage.id\n };\n }\n }\n }\n\n return message;\n }\n\n /**\n * Finds an existing assistant message that contains a tool part with the given toolCallId.\n * Used to detect when a tool result should update an existing message rather than\n * creating a new one.\n *\n * @param toolCallId - The tool call ID to search for\n * @returns The existing message if found, undefined otherwise\n */\n private _findMessageByToolCallId(\n toolCallId: string\n ): ChatMessage | undefined {\n for (const msg of this.messages) {\n if (msg.role !== \"assistant\") continue;\n\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n }\n\n /**\n * Sanitizes a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * This handles two issues with the OpenAI Responses API:\n *\n * 1. **Duplicate item IDs**: The AI SDK's @ai-sdk/openai provider (v2.0.x+)\n * defaults to using OpenAI's Responses API which assigns unique itemIds\n * to each message part. When these IDs are persisted and sent back,\n * OpenAI rejects them as duplicates.\n *\n * 2. **Empty reasoning parts**: OpenAI may return reasoning parts with empty\n * text and encrypted content. These cause \"Non-OpenAI reasoning parts are\n * not supported\" warnings when sent back via convertToModelMessages().\n *\n * @param message - The message to sanitize\n * @returns A new message with ephemeral provider data removed\n */\n private _sanitizeMessageForPersistence(message: ChatMessage): ChatMessage {\n // First, filter out empty reasoning parts (they have no useful content)\n const filteredParts = message.parts.filter((part) => {\n if (part.type === \"reasoning\") {\n const reasoningPart = part as ReasoningUIPart;\n // Remove reasoning parts that have no text content\n // These are typically placeholders with only encrypted content\n if (!reasoningPart.text || reasoningPart.text.trim() === \"\") {\n return false;\n }\n }\n return true;\n });\n\n // Then sanitize remaining parts by stripping OpenAI-specific ephemeral data\n const sanitizedParts = filteredParts.map((part) => {\n let sanitizedPart = part;\n\n // Strip providerMetadata.openai.itemId and reasoningEncryptedContent\n if (\n \"providerMetadata\" in sanitizedPart &&\n sanitizedPart.providerMetadata &&\n typeof sanitizedPart.providerMetadata === \"object\" &&\n \"openai\" in sanitizedPart.providerMetadata\n ) {\n sanitizedPart = this._stripOpenAIMetadata(\n sanitizedPart,\n \"providerMetadata\"\n );\n }\n\n // Also check callProviderMetadata for tool parts\n if (\n \"callProviderMetadata\" in sanitizedPart &&\n sanitizedPart.callProviderMetadata &&\n typeof sanitizedPart.callProviderMetadata === \"object\" &&\n \"openai\" in sanitizedPart.callProviderMetadata\n ) {\n sanitizedPart = this._stripOpenAIMetadata(\n sanitizedPart,\n \"callProviderMetadata\"\n );\n }\n\n return sanitizedPart;\n }) as ChatMessage[\"parts\"];\n\n return { ...message, parts: sanitizedParts };\n }\n\n /**\n * Helper to strip OpenAI-specific ephemeral fields from a metadata object.\n * Removes itemId and reasoningEncryptedContent while preserving other fields.\n */\n private _stripOpenAIMetadata<T extends ChatMessage[\"parts\"][number]>(\n part: T,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n ): T {\n const metadata = (part as Record<string, unknown>)[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n\n if (!metadata?.openai) return part;\n\n const openaiMeta = metadata.openai;\n\n // Remove ephemeral fields: itemId and reasoningEncryptedContent\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = openaiMeta;\n\n // Determine what to keep\n const hasOtherOpenaiFields = Object.keys(restOpenai).length > 0;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let newMetadata: ProviderMetadata | undefined;\n if (hasOtherOpenaiFields) {\n newMetadata = {\n ...restMetadata,\n openai: restOpenai\n } as ProviderMetadata;\n } else if (Object.keys(restMetadata).length > 0) {\n newMetadata = restMetadata as ProviderMetadata;\n }\n\n // Create new part without the old metadata\n const { [metadataKey]: _oldMeta, ...restPart } = part as Record<\n string,\n unknown\n >;\n\n if (newMetadata) {\n return { ...restPart, [metadataKey]: newMetadata } as T;\n }\n return restPart as T;\n }\n\n /**\n * Applies a tool result to an existing assistant message.\n * This is used when the client sends CF_AGENT_TOOL_RESULT for client-side tools.\n * The server is the source of truth, so we update the message here and broadcast\n * the update to all clients.\n *\n * @param toolCallId - The tool call ID this result is for\n * @param toolName - The name of the tool\n * @param output - The output from the tool execution\n * @returns true if the result was applied, false if the message was not found\n */\n private async _applyToolResult(\n toolCallId: string,\n _toolName: string,\n output: unknown\n ): Promise<boolean> {\n // Find the message with this tool call\n // First check the currently streaming message\n let message: ChatMessage | undefined;\n\n // Check streaming message first\n if (this._streamingMessage) {\n for (const part of this._streamingMessage.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n message = this._streamingMessage;\n break;\n }\n }\n }\n\n // If not found in streaming message, retry persisted messages\n if (!message) {\n for (let attempt = 0; attempt < 10; attempt++) {\n message = this._findMessageByToolCallId(toolCallId);\n if (message) break;\n // Wait 100ms before retrying\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n if (!message) {\n // The tool result will be included when\n // the client sends the follow-up message via sendMessage().\n console.warn(\n `[AIChatAgent] _applyToolResult: Could not find message with toolCallId ${toolCallId} after retries`\n );\n return false;\n }\n\n // Check if this is the streaming message (not yet persisted)\n const isStreamingMessage = message === this._streamingMessage;\n\n // Update the tool part with the output\n let updated = false;\n if (isStreamingMessage) {\n // Update in place - the message will be persisted when streaming completes\n for (const part of message.parts) {\n if (\n \"toolCallId\" in part &&\n part.toolCallId === toolCallId &&\n \"state\" in part &&\n part.state === \"input-available\"\n ) {\n (part as { state: string; output?: unknown }).state =\n \"output-available\";\n (part as { state: string; output?: unknown }).output = output;\n updated = true;\n break;\n }\n }\n } else {\n // For persisted messages, create updated parts\n const updatedParts = message.parts.map((part) => {\n if (\n \"toolCallId\" in part &&\n part.toolCallId === toolCallId &&\n \"state\" in part &&\n part.state === \"input-available\"\n ) {\n updated = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output\n };\n }\n return part;\n }) as ChatMessage[\"parts\"];\n\n if (updated) {\n // Create the updated message and strip OpenAI item IDs\n const updatedMessage: ChatMessage = this._sanitizeMessageForPersistence(\n {\n ...message,\n parts: updatedParts\n }\n );\n\n // Persist the updated message\n this.sql`\n update cf_ai_chat_agent_messages \n set message = ${JSON.stringify(updatedMessage)}\n where id = ${message.id}\n `;\n\n // Reload messages to update in-memory state\n const persisted = this._loadMessagesFromDb();\n this.messages = autoTransformMessages(persisted);\n }\n }\n\n if (!updated) {\n console.warn(\n `[AIChatAgent] _applyToolResult: Tool part with toolCallId ${toolCallId} not in input-available state`\n );\n return false;\n }\n\n // Broadcast the update to all clients (only for persisted messages)\n // For streaming messages, the update will be included when persisted\n if (!isStreamingMessage) {\n // Re-fetch the message for broadcast since we modified it\n const broadcastMessage = this._findMessageByToolCallId(toolCallId);\n if (broadcastMessage) {\n this._broadcastChatMessage({\n type: MessageType.CF_AGENT_MESSAGE_UPDATED,\n message: broadcastMessage\n });\n }\n }\n\n // Note: We don't automatically continue the conversation here.\n // The client is responsible for sending a follow-up request if needed.\n // This avoids re-entering onChatMessage with unexpected state.\n\n return true;\n }\n\n private async _reply(\n id: string,\n response: Response,\n excludeBroadcastIds: string[] = [],\n options: { continuation?: boolean } = {}\n ) {\n const { continuation = false } = options;\n\n return this._tryCatchChat(async () => {\n if (!response.body) {\n // Send empty response if no body\n this._broadcastChatMessage({\n body: \"\",\n done: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n return;\n }\n\n // Start tracking this stream for resumability\n const streamId = this._startStream(id);\n\n /* Lazy loading ai sdk, because putting it in module scope is\n * causing issues with startup time.\n * The only place it's used is in _reply, which only matters after\n * a chat message is received.\n * So it's safe to delay loading it until a chat message is received.\n */\n const { getToolName, isToolUIPart, parsePartialJson } =\n await import(\"ai\");\n\n const reader = response.body.getReader();\n\n // Parsing state adapted from:\n // https://github.com/vercel/ai/blob/main/packages/ai/src/ui-message-stream/ui-message-chunks.ts#L295\n const message: ChatMessage = {\n id: `assistant_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`, // default\n role: \"assistant\",\n parts: []\n };\n // Track the streaming message so tool results can be applied before persistence\n this._streamingMessage = message;\n // Set up completion promise for tool continuation to wait on\n this._streamCompletionPromise = new Promise((resolve) => {\n this._streamCompletionResolve = resolve;\n });\n let activeTextParts: Record<string, TextUIPart> = {};\n let activeReasoningParts: Record<string, ReasoningUIPart> = {};\n const partialToolCalls: Record<\n string,\n { text: string; index: number; toolName: string; dynamic?: boolean }\n > = {};\n\n function updateDynamicToolPart(\n options: {\n toolName: string;\n toolCallId: string;\n providerExecuted?: boolean;\n } & (\n | {\n state: \"input-streaming\";\n input: unknown;\n }\n | {\n state: \"input-available\";\n input: unknown;\n providerMetadata?: ProviderMetadata;\n }\n | {\n state: \"output-available\";\n input: unknown;\n output: unknown;\n preliminary: boolean | undefined;\n }\n | {\n state: \"output-error\";\n input: unknown;\n errorText: string;\n providerMetadata?: ProviderMetadata;\n }\n )\n ) {\n const part = message.parts.find(\n (part) =>\n part.type === \"dynamic-tool\" &&\n part.toolCallId === options.toolCallId\n ) as DynamicToolUIPart | undefined;\n\n const anyOptions = options as Record<string, unknown>;\n const anyPart = part as Record<string, unknown>;\n\n if (part != null) {\n part.state = options.state;\n anyPart.toolName = options.toolName;\n anyPart.input = anyOptions.input;\n anyPart.output = anyOptions.output;\n anyPart.errorText = anyOptions.errorText;\n anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;\n anyPart.preliminary = anyOptions.preliminary;\n\n if (\n anyOptions.providerMetadata != null &&\n part.state === \"input-available\"\n ) {\n part.callProviderMetadata =\n anyOptions.providerMetadata as ProviderMetadata;\n }\n } else {\n message.parts.push({\n type: \"dynamic-tool\",\n toolName: options.toolName,\n toolCallId: options.toolCallId,\n state: options.state,\n input: anyOptions.input,\n output: anyOptions.output,\n errorText: anyOptions.errorText,\n preliminary: anyOptions.preliminary,\n ...(anyOptions.providerMetadata != null\n ? { callProviderMetadata: anyOptions.providerMetadata }\n : {})\n } as DynamicToolUIPart);\n }\n }\n\n function updateToolPart(\n options: {\n toolName: string;\n toolCallId: string;\n providerExecuted?: boolean;\n } & (\n | {\n state: \"input-streaming\";\n input: unknown;\n providerExecuted?: boolean;\n }\n | {\n state: \"input-available\";\n input: unknown;\n providerExecuted?: boolean;\n providerMetadata?: ProviderMetadata;\n }\n | {\n state: \"output-available\";\n input: unknown;\n output: unknown;\n providerExecuted?: boolean;\n preliminary?: boolean;\n }\n | {\n state: \"output-error\";\n input: unknown;\n rawInput?: unknown;\n errorText: string;\n providerExecuted?: boolean;\n providerMetadata?: ProviderMetadata;\n }\n )\n ) {\n const part = message.parts.find(\n (part) =>\n isToolUIPart(part) &&\n (part as ToolUIPart).toolCallId === options.toolCallId\n ) as ToolUIPart | undefined;\n\n const anyOptions = options as Record<string, unknown>;\n const anyPart = part as Record<string, unknown>;\n\n if (part != null) {\n part.state = options.state;\n anyPart.input = anyOptions.input;\n anyPart.output = anyOptions.output;\n anyPart.errorText = anyOptions.errorText;\n anyPart.rawInput = anyOptions.rawInput;\n anyPart.preliminary = anyOptions.preliminary;\n\n // once providerExecuted is set, it stays for streaming\n anyPart.providerExecuted =\n anyOptions.providerExecuted ?? part.providerExecuted;\n\n if (\n anyOptions.providerMetadata != null &&\n part.state === \"input-available\"\n ) {\n part.callProviderMetadata =\n anyOptions.providerMetadata as ProviderMetadata;\n }\n } else {\n message.parts.push({\n type: `tool-${options.toolName}`,\n toolCallId: options.toolCallId,\n state: options.state,\n input: anyOptions.input,\n output: anyOptions.output,\n rawInput: anyOptions.rawInput,\n errorText: anyOptions.errorText,\n providerExecuted: anyOptions.providerExecuted,\n preliminary: anyOptions.preliminary,\n ...(anyOptions.providerMetadata != null\n ? { callProviderMetadata: anyOptions.providerMetadata }\n : {})\n } as ToolUIPart);\n }\n }\n\n async function updateMessageMetadata(metadata: unknown) {\n if (metadata != null) {\n const mergedMetadata =\n message.metadata != null\n ? { ...message.metadata, ...metadata } // TODO: do proper merging\n : metadata;\n\n message.metadata = mergedMetadata;\n }\n }\n\n let streamCompleted = false;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n // Mark the stream as completed\n this._completeStream(streamId);\n streamCompleted = true;\n // Send final completion signal\n this._broadcastChatMessage({\n body: \"\",\n done: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n break;\n }\n\n const chunk = decoder.decode(value);\n\n // Determine response format based on content-type\n const contentType = response.headers.get(\"content-type\") || \"\";\n const isSSE = contentType.includes(\"text/event-stream\");\n\n // After streaming is complete, persist the complete assistant's response\n if (isSSE) {\n // Parse AI SDK v5 SSE format and extract text deltas\n const lines = chunk.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data: \") && line !== \"data: [DONE]\") {\n try {\n const data: UIMessageChunk = JSON.parse(line.slice(6)); // Remove 'data: ' prefix\n switch (data.type) {\n case \"text-start\": {\n const textPart: TextUIPart = {\n type: \"text\",\n text: \"\",\n providerMetadata: data.providerMetadata,\n state: \"streaming\"\n };\n activeTextParts[data.id] = textPart;\n message.parts.push(textPart);\n break;\n }\n\n case \"text-delta\": {\n const textPart = activeTextParts[data.id];\n textPart.text += data.delta;\n textPart.providerMetadata =\n data.providerMetadata ?? textPart.providerMetadata;\n break;\n }\n\n case \"text-end\": {\n const textPart = activeTextParts[data.id];\n textPart.state = \"done\";\n textPart.providerMetadata =\n data.providerMetadata ?? textPart.providerMetadata;\n delete activeTextParts[data.id];\n break;\n }\n\n case \"reasoning-start\": {\n const reasoningPart: ReasoningUIPart = {\n type: \"reasoning\",\n text: \"\",\n providerMetadata: data.providerMetadata,\n state: \"streaming\"\n };\n activeReasoningParts[data.id] = reasoningPart;\n message.parts.push(reasoningPart);\n break;\n }\n\n case \"reasoning-delta\": {\n const reasoningPart = activeReasoningParts[data.id];\n reasoningPart.text += data.delta;\n reasoningPart.providerMetadata =\n data.providerMetadata ?? reasoningPart.providerMetadata;\n break;\n }\n\n case \"reasoning-end\": {\n const reasoningPart = activeReasoningParts[data.id];\n reasoningPart.providerMetadata =\n data.providerMetadata ?? reasoningPart.providerMetadata;\n reasoningPart.state = \"done\";\n delete activeReasoningParts[data.id];\n\n break;\n }\n\n case \"file\": {\n message.parts.push({\n type: \"file\",\n mediaType: data.mediaType,\n url: data.url\n });\n\n break;\n }\n\n case \"source-url\": {\n message.parts.push({\n type: \"source-url\",\n sourceId: data.sourceId,\n url: data.url,\n title: data.title,\n providerMetadata: data.providerMetadata\n });\n\n break;\n }\n\n case \"source-document\": {\n message.parts.push({\n type: \"source-document\",\n sourceId: data.sourceId,\n mediaType: data.mediaType,\n title: data.title,\n filename: data.filename,\n providerMetadata: data.providerMetadata\n });\n\n break;\n }\n\n case \"tool-input-start\": {\n const toolInvocations =\n message.parts.filter(isToolUIPart);\n\n // add the partial tool call to the map\n partialToolCalls[data.toolCallId] = {\n text: \"\",\n toolName: data.toolName,\n index: toolInvocations.length,\n dynamic: data.dynamic\n };\n\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-streaming\",\n input: undefined\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-streaming\",\n input: undefined\n });\n }\n\n break;\n }\n\n case \"tool-input-delta\": {\n const partialToolCall = partialToolCalls[data.toolCallId];\n\n partialToolCall.text += data.inputTextDelta;\n\n const partialArgsResult = await parsePartialJson(\n partialToolCall.text\n );\n const partialArgs = (\n partialArgsResult as { value: Record<string, unknown> }\n ).value;\n\n if (partialToolCall.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: partialToolCall.toolName,\n state: \"input-streaming\",\n input: partialArgs\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: partialToolCall.toolName,\n state: \"input-streaming\",\n input: partialArgs\n });\n }\n\n break;\n }\n\n case \"tool-input-available\": {\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-available\",\n input: data.input,\n providerMetadata: data.providerMetadata\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-available\",\n input: data.input,\n providerExecuted: data.providerExecuted,\n providerMetadata: data.providerMetadata\n });\n }\n\n // TODO: Do we want to expose onToolCall?\n\n // invoke the onToolCall callback if it exists. This is blocking.\n // In the future we should make this non-blocking, which\n // requires additional state management for error handling etc.\n // Skip calling onToolCall for provider-executed tools since they are already executed\n // if (onToolCall && !data.providerExecuted) {\n // await onToolCall({\n // toolCall: data\n // });\n // }\n break;\n }\n\n case \"tool-input-error\": {\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"output-error\",\n input: data.input,\n errorText: data.errorText,\n providerMetadata: data.providerMetadata\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"output-error\",\n input: undefined,\n rawInput: data.input,\n errorText: data.errorText,\n providerExecuted: data.providerExecuted,\n providerMetadata: data.providerMetadata\n });\n }\n\n break;\n }\n\n case \"tool-output-available\": {\n if (data.dynamic) {\n const toolInvocations = message.parts.filter(\n (part) => part.type === \"dynamic-tool\"\n ) as DynamicToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: toolInvocation.toolName,\n state: \"output-available\",\n input: toolInvocation.input,\n output: data.output,\n preliminary: data.preliminary\n });\n } else {\n const toolInvocations = message.parts.filter(\n isToolUIPart\n ) as ToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: getToolName(toolInvocation),\n state: \"output-available\",\n input: toolInvocation.input,\n output: data.output,\n providerExecuted: data.providerExecuted,\n preliminary: data.preliminary\n });\n }\n\n break;\n }\n\n case \"tool-output-error\": {\n if (data.dynamic) {\n const toolInvocations = message.parts.filter(\n (part) => part.type === \"dynamic-tool\"\n ) as DynamicToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: toolInvocation.toolName,\n state: \"output-error\",\n input: toolInvocation.input,\n errorText: data.errorText\n });\n } else {\n const toolInvocations = message.parts.filter(\n isToolUIPart\n ) as ToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: getToolName(toolInvocation),\n state: \"output-error\",\n input: toolInvocation.input,\n rawInput:\n \"rawInput\" in toolInvocation\n ? toolInvocation.rawInput\n : undefined,\n errorText: data.errorText\n });\n }\n\n break;\n }\n\n case \"start-step\": {\n // add a step boundary part to the message\n message.parts.push({ type: \"step-start\" });\n break;\n }\n\n case \"finish-step\": {\n // reset the current text and reasoning parts\n activeTextParts = {};\n activeReasoningParts = {};\n break;\n }\n\n case \"start\": {\n if (data.messageId != null) {\n message.id = data.messageId;\n }\n\n await updateMessageMetadata(data.messageMetadata);\n\n break;\n }\n\n case \"finish\": {\n await updateMessageMetadata(data.messageMetadata);\n break;\n }\n\n case \"message-metadata\": {\n await updateMessageMetadata(data.messageMetadata);\n break;\n }\n\n case \"error\": {\n this._broadcastChatMessage({\n error: true,\n body: data.errorText ?? JSON.stringify(data),\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n });\n\n break;\n }\n // Do we want to handle data parts?\n }\n\n // Convert internal AI SDK stream events to valid UIMessageStreamPart format.\n // The \"finish\" event with \"finishReason\" is an internal LanguageModelV3StreamPart,\n // not a UIMessageStreamPart (which expects \"messageMetadata\" instead).\n // See: https://github.com/cloudflare/agents/issues/677\n let eventToSend: unknown = data;\n if (data.type === \"finish\" && \"finishReason\" in data) {\n const { finishReason, ...rest } = data as {\n finishReason: string;\n [key: string]: unknown;\n };\n eventToSend = {\n ...rest,\n type: \"finish\",\n messageMetadata: { finishReason }\n };\n }\n\n // Store chunk for replay on reconnection\n const chunkBody = JSON.stringify(eventToSend);\n this._storeStreamChunk(streamId, chunkBody);\n\n // Forward the converted event to the client\n this._broadcastChatMessage({\n body: chunkBody,\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n } catch (_error) {\n // Skip malformed JSON lines silently\n }\n }\n }\n } else {\n // Handle plain text responses (e.g., from generateText)\n // Treat the entire chunk as a text delta to preserve exact formatting\n if (chunk.length > 0) {\n message.parts.push({ type: \"text\", text: chunk });\n // Synthesize a text-delta event so clients can stream-render\n const chunkBody = JSON.stringify({\n type: \"text-delta\",\n delta: chunk\n });\n // Store chunk for replay on reconnection\n this._storeStreamChunk(streamId, chunkBody);\n this._broadcastChatMessage({\n body: chunkBody,\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n }\n }\n }\n } catch (error) {\n // Mark stream as error if not already completed\n if (!streamCompleted) {\n this._markStreamError(streamId);\n // Notify clients of the error\n this._broadcastChatMessage({\n body: error instanceof Error ? error.message : \"Stream error\",\n done: true,\n error: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n }\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n if (message.parts.length > 0) {\n if (continuation) {\n // Find the last assistant message and append parts to it\n let lastAssistantIdx = -1;\n for (let i = this.messages.length - 1; i >= 0; i--) {\n if (this.messages[i].role === \"assistant\") {\n lastAssistantIdx = i;\n break;\n }\n }\n if (lastAssistantIdx >= 0) {\n const lastAssistant = this.messages[lastAssistantIdx];\n const mergedMessage: ChatMessage = {\n ...lastAssistant,\n parts: [...lastAssistant.parts, ...message.parts]\n };\n const updatedMessages = [...this.messages];\n updatedMessages[lastAssistantIdx] = mergedMessage;\n await this.persistMessages(updatedMessages, excludeBroadcastIds);\n } else {\n // No assistant message to append to, create new one\n await this.persistMessages(\n [...this.messages, message],\n excludeBroadcastIds\n );\n }\n } else {\n await this.persistMessages(\n [...this.messages, message],\n excludeBroadcastIds\n );\n }\n }\n\n // Clear the streaming message reference and resolve completion promise\n this._streamingMessage = null;\n if (this._streamCompletionResolve) {\n this._streamCompletionResolve();\n this._streamCompletionResolve = null;\n this._streamCompletionPromise = null;\n }\n });\n }\n\n /**\n * Mark a stream as errored and clean up state.\n * @param streamId - The stream to mark as errored\n * @internal Protected for testing purposes.\n */\n protected _markStreamError(streamId: string) {\n // Flush any pending chunks before marking error\n this._flushChunkBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'error', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n }\n\n /**\n * For the given message id, look up its associated AbortController\n * If the AbortController does not exist, create and store one in memory\n *\n * returns the AbortSignal associated with the AbortController\n */\n private _getAbortSignal(id: string): AbortSignal | undefined {\n // Defensive check, since we're coercing message types at the moment\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this._chatMessageAbortControllers.has(id)) {\n this._chatMessageAbortControllers.set(id, new AbortController());\n }\n\n return this._chatMessageAbortControllers.get(id)?.signal;\n }\n\n /**\n * Remove an abort controller from the cache of pending message responses\n */\n private _removeAbortController(id: string) {\n this._chatMessageAbortControllers.delete(id);\n }\n\n /**\n * Propagate an abort signal for any requests associated with the given message id\n */\n private _cancelChatRequest(id: string) {\n if (this._chatMessageAbortControllers.has(id)) {\n const abortController = this._chatMessageAbortControllers.get(id);\n abortController?.abort();\n }\n }\n\n /**\n * Abort all pending requests and clear the cache of AbortControllers\n */\n private _destroyAbortControllers() {\n for (const controller of this._chatMessageAbortControllers.values()) {\n controller?.abort();\n }\n this._chatMessageAbortControllers.clear();\n }\n\n /**\n * When the DO is destroyed, cancel all pending requests and clean up resources\n */\n async destroy() {\n this._destroyAbortControllers();\n\n // Flush any remaining chunks before cleanup\n this._flushChunkBuffer();\n\n // Clean up stream tables\n this.sql`drop table if exists cf_ai_chat_stream_chunks`;\n this.sql`drop table if exists cf_ai_chat_stream_metadata`;\n\n // Clear active stream state\n this._activeStreamId = null;\n this._activeRequestId = null;\n\n await super.destroy();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-chat-agent.js","names":["ctx","data: IncomingMessage","newMetadata: ProviderMetadata | undefined","message: ChatMessage | undefined","updatedMessage: ChatMessage","message: ChatMessage","activeTextParts: Record<string, TextUIPart>","activeReasoningParts: Record<string, ReasoningUIPart>","partialToolCalls: Record<\n string,\n { text: string; index: number; toolName: string; dynamic?: boolean }\n >","part","options","isToolUIPart","data: UIMessageChunk","textPart: TextUIPart","reasoningPart: ReasoningUIPart","getToolName","eventToSend: unknown","mergedMessage: ChatMessage"],"sources":["../src/ai-chat-agent.ts"],"sourcesContent":["import type {\n UIMessage as ChatMessage,\n DynamicToolUIPart,\n JSONSchema7,\n ProviderMetadata,\n ReasoningUIPart,\n StreamTextOnFinishCallback,\n TextUIPart,\n Tool,\n ToolSet,\n ToolUIPart,\n UIMessageChunk\n} from \"ai\";\nimport { tool, jsonSchema } from \"ai\";\nimport {\n Agent,\n type AgentContext,\n type Connection,\n type ConnectionContext,\n type WSMessage\n} from \"./\";\nimport { agentContext } from \"./context\";\nimport {\n MessageType,\n type IncomingMessage,\n type OutgoingMessage\n} from \"./ai-types\";\nimport { autoTransformMessages } from \"./ai-chat-v5-migration\";\nimport { nanoid } from \"nanoid\";\n\n/**\n * Schema for a client-defined tool sent from the browser.\n * These tools are executed on the client, not the server.\n *\n * Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)\n * because this is the wire format. Zod schemas cannot be serialized.\n *\n * @deprecated Define tools on the server using `tool()` from \"ai\" instead.\n * For tools that need client-side execution, omit the `execute` function\n * and handle them via the `onToolCall` callback in `useAgentChat`.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\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\n/**\n * Options passed to the onChatMessage handler.\n */\nexport type OnChatMessageOptions = {\n /** AbortSignal for cancelling the request */\n abortSignal?: AbortSignal;\n /**\n * Tool schemas sent from the client for dynamic tool registration.\n * These represent tools that will be executed on the client side.\n * Use `createToolsFromClientSchemas()` to convert these to AI SDK tool format.\n *\n * @deprecated Define tools on the server instead. Use `onToolCall` callback\n * in `useAgentChat` for client-side execution.\n */\n clientTools?: ClientToolSchema[];\n};\n\n/**\n * Converts client tool schemas to AI SDK tool format.\n *\n * These tools have no `execute` function - when the AI model calls them,\n * the tool call is sent back to the client for execution.\n *\n * @param clientTools - Array of tool schemas from the client\n * @returns Record of AI SDK tools that can be spread into your tools object\n *\n * @deprecated Define tools on the server using `tool()` from \"ai\" instead.\n * For tools that need client-side execution, omit the `execute` function\n * and handle them via the `onToolCall` callback in `useAgentChat`.\n *\n * @example\n * ```typescript\n * // Server: Define tool without execute\n * const tools = {\n * getLocation: tool({\n * description: \"Get user's location\",\n * inputSchema: z.object({})\n * // No execute = client must handle\n * })\n * };\n *\n * // Client: Handle in onToolCall\n * useAgentChat({\n * onToolCall: async ({ toolCall, addToolOutput }) => {\n * if (toolCall.toolName === 'getLocation') {\n * const pos = await navigator.geolocation.getCurrentPosition();\n * addToolOutput({ toolCallId: toolCall.toolCallId, output: pos });\n * }\n * }\n * });\n * ```\n */\nexport function createToolsFromClientSchemas(\n clientTools?: ClientToolSchema[]\n): ToolSet {\n if (!clientTools || clientTools.length === 0) {\n return {};\n }\n\n // Check for duplicate tool names\n const seenNames = new Set<string>();\n for (const t of clientTools) {\n if (seenNames.has(t.name)) {\n console.warn(\n `[createToolsFromClientSchemas] Duplicate tool name \"${t.name}\" found. Later definitions will override earlier ones.`\n );\n }\n seenNames.add(t.name);\n }\n\n return Object.fromEntries(\n clientTools.map((t) => [\n t.name,\n tool({\n description: t.description ?? \"\",\n inputSchema: jsonSchema(t.parameters ?? { type: \"object\" })\n // No execute function = tool call is sent back to client\n })\n ])\n );\n}\n\n/** Number of chunks to buffer before flushing to SQLite */\nconst CHUNK_BUFFER_SIZE = 10;\n/** Maximum buffer size to prevent memory issues on rapid reconnections */\nconst CHUNK_BUFFER_MAX_SIZE = 100;\n/** Maximum age for a \"streaming\" stream before considering it stale (ms) - 5 minutes */\nconst STREAM_STALE_THRESHOLD_MS = 5 * 60 * 1000;\n/** Default cleanup interval for old streams (ms) - every 10 minutes */\nconst CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\n/** Default age threshold for cleaning up completed streams (ms) - 24 hours */\nconst CLEANUP_AGE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\n\nconst decoder = new TextDecoder();\n\n/**\n * Stored stream chunk for resumable streaming\n */\ntype StreamChunk = {\n id: string;\n stream_id: string;\n body: string;\n chunk_index: number;\n created_at: number;\n};\n\n/**\n * Stream metadata for tracking active streams\n */\ntype StreamMetadata = {\n id: string;\n request_id: string;\n status: \"streaming\" | \"completed\" | \"error\";\n created_at: number;\n completed_at: number | null;\n};\n\n/**\n * Extension of Agent with built-in chat capabilities\n * @template Env Environment type containing bindings\n */\nexport class AIChatAgent<\n Env extends Cloudflare.Env = Cloudflare.Env,\n State = unknown\n> extends Agent<Env, State> {\n /**\n * Map of message `id`s to `AbortController`s\n * useful to propagate request cancellation signals for any external calls made by the agent\n */\n private _chatMessageAbortControllers: Map<string, AbortController>;\n\n /**\n * Currently active stream ID for resumable streaming.\n * Stored in memory for quick access; persisted in stream_metadata table.\n * @internal Protected for testing purposes.\n */\n protected _activeStreamId: string | null = null;\n\n /**\n * Request ID associated with the active stream.\n * @internal Protected for testing purposes.\n */\n protected _activeRequestId: string | null = null;\n\n /**\n * The message currently being streamed. Used to apply tool results\n * before the message is persisted.\n * @internal\n */\n private _streamingMessage: ChatMessage | null = null;\n\n /**\n * Promise that resolves when the current stream completes.\n * Used to wait for message persistence before continuing after tool results.\n * @internal\n */\n private _streamCompletionPromise: Promise<void> | null = null;\n private _streamCompletionResolve: (() => void) | null = null;\n\n /**\n * Current chunk index for the active stream\n */\n private _streamChunkIndex = 0;\n\n /**\n * Buffer for stream chunks pending write to SQLite.\n * Chunks are batched and flushed when buffer reaches CHUNK_BUFFER_SIZE.\n */\n private _chunkBuffer: Array<{\n id: string;\n streamId: string;\n body: string;\n index: number;\n }> = [];\n\n /**\n * Lock to prevent concurrent flush operations\n */\n private _isFlushingChunks = false;\n\n /**\n * Timestamp of the last cleanup operation for old streams\n */\n private _lastCleanupTime = 0;\n\n /** Array of chat messages for the current conversation */\n messages: ChatMessage[];\n\n constructor(ctx: AgentContext, env: Env) {\n super(ctx, env);\n this.sql`create table if not exists cf_ai_chat_agent_messages (\n id text primary key,\n message text not null,\n created_at datetime default current_timestamp\n )`;\n\n // Create tables for automatic resumable streaming\n this.sql`create table if not exists cf_ai_chat_stream_chunks (\n id text primary key,\n stream_id text not null,\n body text not null,\n chunk_index integer not null,\n created_at integer not null\n )`;\n\n this.sql`create table if not exists cf_ai_chat_stream_metadata (\n id text primary key,\n request_id text not null,\n status text not null,\n created_at integer not null,\n completed_at integer\n )`;\n\n this.sql`create index if not exists idx_stream_chunks_stream_id \n on cf_ai_chat_stream_chunks(stream_id, chunk_index)`;\n\n // Load messages and automatically transform them to v5 format\n const rawMessages = this._loadMessagesFromDb();\n\n // Automatic migration following https://jhak.im/blog/ai-sdk-migration-handling-previously-saved-messages\n this.messages = autoTransformMessages(rawMessages);\n\n this._chatMessageAbortControllers = new Map();\n\n // Check for any active streams from a previous session\n this._restoreActiveStream();\n const _onConnect = this.onConnect.bind(this);\n this.onConnect = async (connection: Connection, ctx: ConnectionContext) => {\n // Notify client about active streams that can be resumed\n if (this._activeStreamId) {\n this._notifyStreamResuming(connection);\n }\n // Call consumer's onConnect\n return _onConnect(connection, ctx);\n };\n\n // Wrap onMessage\n const _onMessage = this.onMessage.bind(this);\n this.onMessage = async (connection: Connection, message: WSMessage) => {\n // Handle AIChatAgent's internal messages first\n if (typeof message === \"string\") {\n let data: IncomingMessage;\n try {\n data = JSON.parse(message) as IncomingMessage;\n } catch (_error) {\n // Not JSON, forward to consumer\n return _onMessage(connection, message);\n }\n\n // Handle chat request\n if (\n data.type === MessageType.CF_AGENT_USE_CHAT_REQUEST &&\n data.init.method === \"POST\"\n ) {\n const { body } = data.init;\n const parsed = JSON.parse(body as string);\n const { messages, clientTools } = parsed as {\n messages: ChatMessage[];\n clientTools?: ClientToolSchema[];\n };\n\n // Automatically transform any incoming messages\n const transformedMessages = autoTransformMessages(messages);\n\n this._broadcastChatMessage(\n {\n messages: transformedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n },\n [connection.id]\n );\n\n await this.persistMessages(transformedMessages, [connection.id]);\n\n this.observability?.emit(\n {\n displayMessage: \"Chat message request\",\n id: data.id,\n payload: {},\n timestamp: Date.now(),\n type: \"message:request\"\n },\n this.ctx\n );\n\n const chatMessageId = data.id;\n const abortSignal = this._getAbortSignal(chatMessageId);\n\n return this._tryCatchChat(async () => {\n // Wrap in agentContext.run() to propagate connection context to onChatMessage\n // This ensures getCurrentAgent() returns the connection inside tool execute functions\n return agentContext.run(\n {\n agent: this,\n connection,\n request: undefined,\n email: undefined\n },\n async () => {\n const response = await this.onChatMessage(\n async (_finishResult) => {\n this._removeAbortController(chatMessageId);\n\n this.observability?.emit(\n {\n displayMessage: \"Chat message response\",\n id: data.id,\n payload: {},\n timestamp: Date.now(),\n type: \"message:response\"\n },\n this.ctx\n );\n },\n {\n abortSignal,\n clientTools\n }\n );\n\n if (response) {\n await this._reply(data.id, response, [connection.id]);\n } else {\n console.warn(\n `[AIChatAgent] onChatMessage returned no response for chatMessageId: ${chatMessageId}`\n );\n this._broadcastChatMessage(\n {\n body: \"No response was generated by the agent.\",\n done: true,\n id: data.id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n },\n [connection.id]\n );\n }\n }\n );\n });\n }\n\n // Handle clear chat\n if (data.type === MessageType.CF_AGENT_CHAT_CLEAR) {\n this._destroyAbortControllers();\n this.sql`delete from cf_ai_chat_agent_messages`;\n this.sql`delete from cf_ai_chat_stream_chunks`;\n this.sql`delete from cf_ai_chat_stream_metadata`;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n this.messages = [];\n this._broadcastChatMessage(\n { type: MessageType.CF_AGENT_CHAT_CLEAR },\n [connection.id]\n );\n return;\n }\n\n // Handle message replacement\n if (data.type === MessageType.CF_AGENT_CHAT_MESSAGES) {\n const transformedMessages = autoTransformMessages(data.messages);\n await this.persistMessages(transformedMessages, [connection.id]);\n return;\n }\n\n // Handle request cancellation\n if (data.type === MessageType.CF_AGENT_CHAT_REQUEST_CANCEL) {\n this._cancelChatRequest(data.id);\n return;\n }\n\n // Handle stream resume acknowledgment\n if (data.type === MessageType.CF_AGENT_STREAM_RESUME_ACK) {\n if (\n this._activeStreamId &&\n this._activeRequestId &&\n this._activeRequestId === data.id\n ) {\n this._sendStreamChunks(\n connection,\n this._activeStreamId,\n this._activeRequestId\n );\n }\n return;\n }\n\n // Handle client-side tool result\n if (data.type === MessageType.CF_AGENT_TOOL_RESULT) {\n const { toolCallId, toolName, output, autoContinue } = data;\n\n // Apply the tool result\n this._applyToolResult(toolCallId, toolName, output).then(\n (applied) => {\n // Only auto-continue if client requested it (opt-in behavior)\n // This mimics server-executed tool behavior where the LLM\n // automatically continues after seeing tool results\n if (applied && autoContinue) {\n // Wait for the original stream to complete and message to be persisted\n // before calling onChatMessage, so this.messages includes the tool result\n const waitForStream = async () => {\n if (this._streamCompletionPromise) {\n await this._streamCompletionPromise;\n } else {\n // If no promise, wait a bit for the stream to finish\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n };\n\n waitForStream().then(() => {\n const continuationId = nanoid();\n const abortSignal = this._getAbortSignal(continuationId);\n\n this._tryCatchChat(async () => {\n return agentContext.run(\n {\n agent: this,\n connection,\n request: undefined,\n email: undefined\n },\n async () => {\n const response = await this.onChatMessage(\n async (_finishResult) => {\n this._removeAbortController(continuationId);\n\n this.observability?.emit(\n {\n displayMessage:\n \"Chat message response (tool continuation)\",\n id: continuationId,\n payload: {},\n timestamp: Date.now(),\n type: \"message:response\"\n },\n this.ctx\n );\n },\n {\n abortSignal\n }\n );\n\n if (response) {\n // Pass continuation flag to merge parts into last assistant message\n // Note: We pass an empty excludeBroadcastIds array because the sender\n // NEEDS to receive the continuation stream. Unlike regular chat requests\n // where aiFetch handles the response, tool continuations have no listener\n // waiting - the client relies on the broadcast.\n await this._reply(\n continuationId,\n response,\n [], // Don't exclude sender - they need the continuation\n { continuation: true }\n );\n }\n }\n );\n });\n });\n }\n }\n );\n return;\n }\n }\n\n // Forward unhandled messages to consumer's onMessage\n return _onMessage(connection, message);\n };\n }\n\n /**\n * Restore active stream state if the agent was restarted during streaming.\n * Called during construction to recover any interrupted streams.\n * Validates stream freshness to avoid sending stale resume notifications.\n * @internal Protected for testing purposes.\n */\n protected _restoreActiveStream() {\n const activeStreams = this.sql<StreamMetadata>`\n select * from cf_ai_chat_stream_metadata \n where status = 'streaming' \n order by created_at desc \n limit 1\n `;\n\n if (activeStreams && activeStreams.length > 0) {\n const stream = activeStreams[0];\n const streamAge = Date.now() - stream.created_at;\n\n // Check if stream is stale; delete to free storage\n if (streamAge > STREAM_STALE_THRESHOLD_MS) {\n this\n .sql`delete from cf_ai_chat_stream_chunks where stream_id = ${stream.id}`;\n this\n .sql`delete from cf_ai_chat_stream_metadata where id = ${stream.id}`;\n console.warn(\n `[AIChatAgent] Deleted stale stream ${stream.id} (age: ${Math.round(streamAge / 1000)}s)`\n );\n return;\n }\n\n this._activeStreamId = stream.id;\n this._activeRequestId = stream.request_id;\n\n // Get the last chunk index\n const lastChunk = this.sql<{ max_index: number }>`\n select max(chunk_index) as max_index \n from cf_ai_chat_stream_chunks \n where stream_id = ${this._activeStreamId}\n `;\n this._streamChunkIndex =\n lastChunk && lastChunk[0]?.max_index != null\n ? lastChunk[0].max_index + 1\n : 0;\n }\n }\n\n /**\n * Notify a connection about an active stream that can be resumed.\n * The client should respond with CF_AGENT_STREAM_RESUME_ACK to receive chunks.\n * Uses in-memory state for request ID - no extra DB lookup needed.\n * @param connection - The WebSocket connection to notify\n */\n private _notifyStreamResuming(connection: Connection) {\n if (!this._activeStreamId || !this._activeRequestId) {\n return;\n }\n\n // Notify client - they will send ACK when ready\n connection.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_STREAM_RESUMING,\n id: this._activeRequestId\n })\n );\n }\n\n /**\n * Send stream chunks to a connection after receiving ACK.\n * @param connection - The WebSocket connection\n * @param streamId - The stream to replay\n * @param requestId - The original request ID\n */\n private _sendStreamChunks(\n connection: Connection,\n streamId: string,\n requestId: string\n ) {\n // Flush any pending chunks first to ensure we have the latest\n this._flushChunkBuffer();\n\n const chunks = this.sql<StreamChunk>`\n select * from cf_ai_chat_stream_chunks \n where stream_id = ${streamId} \n order by chunk_index asc\n `;\n\n // Send all stored chunks\n for (const chunk of chunks || []) {\n connection.send(\n JSON.stringify({\n body: chunk.body,\n done: false,\n id: requestId,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n })\n );\n }\n\n // If the stream is no longer active (completed), send done signal\n // We track active state in memory, no need to query DB\n if (this._activeStreamId !== streamId) {\n connection.send(\n JSON.stringify({\n body: \"\",\n done: true,\n id: requestId,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n })\n );\n }\n }\n\n /**\n * Buffer a stream chunk for batch write to SQLite.\n * @param streamId - The stream this chunk belongs to\n * @param body - The serialized chunk body\n * @internal Protected for testing purposes.\n */\n protected _storeStreamChunk(streamId: string, body: string) {\n // Force flush if buffer is at max to prevent memory issues\n if (this._chunkBuffer.length >= CHUNK_BUFFER_MAX_SIZE) {\n this._flushChunkBuffer();\n }\n\n this._chunkBuffer.push({\n id: nanoid(),\n streamId,\n body,\n index: this._streamChunkIndex\n });\n this._streamChunkIndex++;\n\n // Flush when buffer reaches threshold\n if (this._chunkBuffer.length >= CHUNK_BUFFER_SIZE) {\n this._flushChunkBuffer();\n }\n }\n\n /**\n * Flush buffered chunks to SQLite in a single batch.\n * Uses a lock to prevent concurrent flush operations.\n * @internal Protected for testing purposes.\n */\n protected _flushChunkBuffer() {\n // Prevent concurrent flushes\n if (this._isFlushingChunks || this._chunkBuffer.length === 0) {\n return;\n }\n\n this._isFlushingChunks = true;\n try {\n const chunks = this._chunkBuffer;\n this._chunkBuffer = [];\n\n // Batch insert all chunks\n const now = Date.now();\n for (const chunk of chunks) {\n this.sql`\n insert into cf_ai_chat_stream_chunks (id, stream_id, body, chunk_index, created_at)\n values (${chunk.id}, ${chunk.streamId}, ${chunk.body}, ${chunk.index}, ${now})\n `;\n }\n } finally {\n this._isFlushingChunks = false;\n }\n }\n\n /**\n * Start tracking a new stream for resumable streaming.\n * Creates metadata entry in SQLite and sets up tracking state.\n * @param requestId - The unique ID of the chat request\n * @returns The generated stream ID\n * @internal Protected for testing purposes.\n */\n protected _startStream(requestId: string): string {\n // Flush any pending chunks from previous streams to prevent mixing\n this._flushChunkBuffer();\n\n const streamId = nanoid();\n this._activeStreamId = streamId;\n this._activeRequestId = requestId;\n this._streamChunkIndex = 0;\n\n this.sql`\n insert into cf_ai_chat_stream_metadata (id, request_id, status, created_at)\n values (${streamId}, ${requestId}, 'streaming', ${Date.now()})\n `;\n\n return streamId;\n }\n\n /**\n * Mark a stream as completed and flush any pending chunks.\n * @param streamId - The stream to mark as completed\n * @internal Protected for testing purposes.\n */\n protected _completeStream(streamId: string) {\n // Flush any pending chunks before completing\n this._flushChunkBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'completed', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n\n // Periodically clean up old streams (not on every completion)\n this._maybeCleanupOldStreams();\n }\n\n /**\n * Clean up old completed streams if enough time has passed since last cleanup.\n * This prevents database growth while avoiding cleanup overhead on every stream completion.\n */\n private _maybeCleanupOldStreams() {\n const now = Date.now();\n if (now - this._lastCleanupTime < CLEANUP_INTERVAL_MS) {\n return;\n }\n this._lastCleanupTime = now;\n\n const cutoff = now - CLEANUP_AGE_THRESHOLD_MS;\n this.sql`\n delete from cf_ai_chat_stream_chunks \n where stream_id in (\n select id from cf_ai_chat_stream_metadata \n where status = 'completed' and completed_at < ${cutoff}\n )\n `;\n this.sql`\n delete from cf_ai_chat_stream_metadata \n where status = 'completed' and completed_at < ${cutoff}\n `;\n }\n\n private _broadcastChatMessage(message: OutgoingMessage, exclude?: string[]) {\n this.broadcast(JSON.stringify(message), exclude);\n }\n\n private _loadMessagesFromDb(): ChatMessage[] {\n const rows =\n this.sql`select * from cf_ai_chat_agent_messages order by created_at` ||\n [];\n return rows\n .map((row) => {\n try {\n return JSON.parse(row.message as string);\n } catch (error) {\n console.error(`Failed to parse message ${row.id}:`, error);\n return null;\n }\n })\n .filter((msg): msg is ChatMessage => msg !== null);\n }\n\n override async onRequest(request: Request): Promise<Response> {\n return this._tryCatchChat(async () => {\n const url = new URL(request.url);\n\n if (url.pathname.endsWith(\"/get-messages\")) {\n const messages = this._loadMessagesFromDb();\n return Response.json(messages);\n }\n\n return super.onRequest(request);\n });\n }\n\n private async _tryCatchChat<T>(fn: () => T | Promise<T>) {\n try {\n return await fn();\n } catch (e) {\n throw this.onError(e);\n }\n }\n\n /**\n * Handle incoming chat messages and generate a response\n * @param onFinish Callback to be called when the response is finished\n * @param options Options including abort signal and client-defined tools\n * @returns Response to send to the client or undefined\n */\n async onChatMessage(\n // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later\n onFinish: StreamTextOnFinishCallback<ToolSet>,\n // biome-ignore lint/correctness/noUnusedFunctionParameters: overridden later\n options?: OnChatMessageOptions\n ): Promise<Response | undefined> {\n throw new Error(\n \"recieved a chat message, override onChatMessage and return a Response to send to the client\"\n );\n }\n\n /**\n * Save messages on the server side\n * @param messages Chat messages to save\n */\n async saveMessages(messages: ChatMessage[]) {\n await this.persistMessages(messages);\n await this._tryCatchChat(async () => {\n const response = await this.onChatMessage(() => {});\n if (response) this._reply(crypto.randomUUID(), response);\n });\n }\n\n async persistMessages(\n messages: ChatMessage[],\n excludeBroadcastIds: string[] = []\n ) {\n // Merge incoming messages with existing server state to preserve tool outputs.\n // This is critical for client-side tools: the client sends messages without\n // tool outputs, but the server has them via _applyToolResult.\n const mergedMessages = this._mergeIncomingWithServerState(messages);\n\n // Persist the merged messages\n for (const message of mergedMessages) {\n // Strip OpenAI item IDs to prevent \"Duplicate item found\" errors\n // when using the OpenAI Responses API. These IDs are assigned by OpenAI\n // and if sent back in subsequent requests, cause duplicate detection.\n const sanitizedMessage = this._sanitizeMessageForPersistence(message);\n const messageToSave = this._resolveMessageForToolMerge(sanitizedMessage);\n this.sql`\n insert into cf_ai_chat_agent_messages (id, message)\n values (${messageToSave.id}, ${JSON.stringify(messageToSave)})\n on conflict(id) do update set message = excluded.message\n `;\n }\n\n // refresh in-memory messages\n const persisted = this._loadMessagesFromDb();\n this.messages = autoTransformMessages(persisted);\n this._broadcastChatMessage(\n {\n messages: mergedMessages,\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n },\n excludeBroadcastIds\n );\n }\n\n /**\n * Merges incoming messages with existing server state.\n * This preserves tool outputs that the server has (via _applyToolResult)\n * but the client doesn't have yet.\n *\n * @param incomingMessages - Messages from the client\n * @returns Messages with server's tool outputs preserved\n */\n private _mergeIncomingWithServerState(\n incomingMessages: ChatMessage[]\n ): ChatMessage[] {\n // Build a map of toolCallId -> output from existing server messages\n const serverToolOutputs = new Map<string, unknown>();\n for (const msg of this.messages) {\n if (msg.role !== \"assistant\") continue;\n for (const part of msg.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\" &&\n \"output\" in part\n ) {\n serverToolOutputs.set(\n part.toolCallId as string,\n (part as { output: unknown }).output\n );\n }\n }\n }\n\n // If server has no tool outputs, return incoming messages as-is\n if (serverToolOutputs.size === 0) {\n return incomingMessages;\n }\n\n // Merge server's tool outputs into incoming messages\n return incomingMessages.map((msg) => {\n if (msg.role !== \"assistant\") return msg;\n\n let hasChanges = false;\n const updatedParts = msg.parts.map((part) => {\n // If this is a tool part in input-available state and server has the output\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"input-available\" &&\n serverToolOutputs.has(part.toolCallId as string)\n ) {\n hasChanges = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output: serverToolOutputs.get(part.toolCallId as string)\n };\n }\n return part;\n }) as ChatMessage[\"parts\"];\n\n return hasChanges ? { ...msg, parts: updatedParts } : msg;\n });\n }\n\n /**\n * Resolves a message for persistence, handling tool result merging.\n * If the message contains tool parts with output-available state, checks if there's\n * an existing message with the same toolCallId that should be updated instead of\n * creating a duplicate. This prevents the \"Duplicate item found\" error from OpenAI\n * when client-side tool results arrive in a new request.\n *\n * @param message - The message to potentially merge\n * @returns The message with the correct ID (either original or merged)\n */\n private _resolveMessageForToolMerge(message: ChatMessage): ChatMessage {\n if (message.role !== \"assistant\") {\n return message;\n }\n\n // Check if this message has tool parts with output-available state\n for (const part of message.parts) {\n if (\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\"\n ) {\n const toolCallId = part.toolCallId as string;\n\n // Look for an existing message with this toolCallId in input-available state\n const existingMessage = this._findMessageByToolCallId(toolCallId);\n if (existingMessage && existingMessage.id !== message.id) {\n // Found a match - merge by using the existing message's ID\n // This ensures the SQL upsert updates the existing row\n return {\n ...message,\n id: existingMessage.id\n };\n }\n }\n }\n\n return message;\n }\n\n /**\n * Finds an existing assistant message that contains a tool part with the given toolCallId.\n * Used to detect when a tool result should update an existing message rather than\n * creating a new one.\n *\n * @param toolCallId - The tool call ID to search for\n * @returns The existing message if found, undefined otherwise\n */\n private _findMessageByToolCallId(\n toolCallId: string\n ): ChatMessage | undefined {\n for (const msg of this.messages) {\n if (msg.role !== \"assistant\") continue;\n\n for (const part of msg.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n return msg;\n }\n }\n }\n return undefined;\n }\n\n /**\n * Sanitizes a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * This handles two issues with the OpenAI Responses API:\n *\n * 1. **Duplicate item IDs**: The AI SDK's @ai-sdk/openai provider (v2.0.x+)\n * defaults to using OpenAI's Responses API which assigns unique itemIds\n * to each message part. When these IDs are persisted and sent back,\n * OpenAI rejects them as duplicates.\n *\n * 2. **Empty reasoning parts**: OpenAI may return reasoning parts with empty\n * text and encrypted content. These cause \"Non-OpenAI reasoning parts are\n * not supported\" warnings when sent back via convertToModelMessages().\n *\n * @param message - The message to sanitize\n * @returns A new message with ephemeral provider data removed\n */\n private _sanitizeMessageForPersistence(message: ChatMessage): ChatMessage {\n // First, filter out empty reasoning parts (they have no useful content)\n const filteredParts = message.parts.filter((part) => {\n if (part.type === \"reasoning\") {\n const reasoningPart = part as ReasoningUIPart;\n // Remove reasoning parts that have no text content\n // These are typically placeholders with only encrypted content\n if (!reasoningPart.text || reasoningPart.text.trim() === \"\") {\n return false;\n }\n }\n return true;\n });\n\n // Then sanitize remaining parts by stripping OpenAI-specific ephemeral data\n const sanitizedParts = filteredParts.map((part) => {\n let sanitizedPart = part;\n\n // Strip providerMetadata.openai.itemId and reasoningEncryptedContent\n if (\n \"providerMetadata\" in sanitizedPart &&\n sanitizedPart.providerMetadata &&\n typeof sanitizedPart.providerMetadata === \"object\" &&\n \"openai\" in sanitizedPart.providerMetadata\n ) {\n sanitizedPart = this._stripOpenAIMetadata(\n sanitizedPart,\n \"providerMetadata\"\n );\n }\n\n // Also check callProviderMetadata for tool parts\n if (\n \"callProviderMetadata\" in sanitizedPart &&\n sanitizedPart.callProviderMetadata &&\n typeof sanitizedPart.callProviderMetadata === \"object\" &&\n \"openai\" in sanitizedPart.callProviderMetadata\n ) {\n sanitizedPart = this._stripOpenAIMetadata(\n sanitizedPart,\n \"callProviderMetadata\"\n );\n }\n\n return sanitizedPart;\n }) as ChatMessage[\"parts\"];\n\n return { ...message, parts: sanitizedParts };\n }\n\n /**\n * Helper to strip OpenAI-specific ephemeral fields from a metadata object.\n * Removes itemId and reasoningEncryptedContent while preserving other fields.\n */\n private _stripOpenAIMetadata<T extends ChatMessage[\"parts\"][number]>(\n part: T,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n ): T {\n const metadata = (part as Record<string, unknown>)[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n\n if (!metadata?.openai) return part;\n\n const openaiMeta = metadata.openai;\n\n // Remove ephemeral fields: itemId and reasoningEncryptedContent\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = openaiMeta;\n\n // Determine what to keep\n const hasOtherOpenaiFields = Object.keys(restOpenai).length > 0;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let newMetadata: ProviderMetadata | undefined;\n if (hasOtherOpenaiFields) {\n newMetadata = {\n ...restMetadata,\n openai: restOpenai\n } as ProviderMetadata;\n } else if (Object.keys(restMetadata).length > 0) {\n newMetadata = restMetadata as ProviderMetadata;\n }\n\n // Create new part without the old metadata\n const { [metadataKey]: _oldMeta, ...restPart } = part as Record<\n string,\n unknown\n >;\n\n if (newMetadata) {\n return { ...restPart, [metadataKey]: newMetadata } as T;\n }\n return restPart as T;\n }\n\n /**\n * Applies a tool result to an existing assistant message.\n * This is used when the client sends CF_AGENT_TOOL_RESULT for client-side tools.\n * The server is the source of truth, so we update the message here and broadcast\n * the update to all clients.\n *\n * @param toolCallId - The tool call ID this result is for\n * @param toolName - The name of the tool\n * @param output - The output from the tool execution\n * @returns true if the result was applied, false if the message was not found\n */\n private async _applyToolResult(\n toolCallId: string,\n _toolName: string,\n output: unknown\n ): Promise<boolean> {\n // Find the message with this tool call\n // First check the currently streaming message\n let message: ChatMessage | undefined;\n\n // Check streaming message first\n if (this._streamingMessage) {\n for (const part of this._streamingMessage.parts) {\n if (\"toolCallId\" in part && part.toolCallId === toolCallId) {\n message = this._streamingMessage;\n break;\n }\n }\n }\n\n // If not found in streaming message, retry persisted messages\n if (!message) {\n for (let attempt = 0; attempt < 10; attempt++) {\n message = this._findMessageByToolCallId(toolCallId);\n if (message) break;\n // Wait 100ms before retrying\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n if (!message) {\n // The tool result will be included when\n // the client sends the follow-up message via sendMessage().\n console.warn(\n `[AIChatAgent] _applyToolResult: Could not find message with toolCallId ${toolCallId} after retries`\n );\n return false;\n }\n\n // Check if this is the streaming message (not yet persisted)\n const isStreamingMessage = message === this._streamingMessage;\n\n // Update the tool part with the output\n let updated = false;\n if (isStreamingMessage) {\n // Update in place - the message will be persisted when streaming completes\n for (const part of message.parts) {\n if (\n \"toolCallId\" in part &&\n part.toolCallId === toolCallId &&\n \"state\" in part &&\n part.state === \"input-available\"\n ) {\n (part as { state: string; output?: unknown }).state =\n \"output-available\";\n (part as { state: string; output?: unknown }).output = output;\n updated = true;\n break;\n }\n }\n } else {\n // For persisted messages, create updated parts\n const updatedParts = message.parts.map((part) => {\n if (\n \"toolCallId\" in part &&\n part.toolCallId === toolCallId &&\n \"state\" in part &&\n part.state === \"input-available\"\n ) {\n updated = true;\n return {\n ...part,\n state: \"output-available\" as const,\n output\n };\n }\n return part;\n }) as ChatMessage[\"parts\"];\n\n if (updated) {\n // Create the updated message and strip OpenAI item IDs\n const updatedMessage: ChatMessage = this._sanitizeMessageForPersistence(\n {\n ...message,\n parts: updatedParts\n }\n );\n\n // Persist the updated message\n this.sql`\n update cf_ai_chat_agent_messages \n set message = ${JSON.stringify(updatedMessage)}\n where id = ${message.id}\n `;\n\n // Reload messages to update in-memory state\n const persisted = this._loadMessagesFromDb();\n this.messages = autoTransformMessages(persisted);\n }\n }\n\n if (!updated) {\n console.warn(\n `[AIChatAgent] _applyToolResult: Tool part with toolCallId ${toolCallId} not in input-available state`\n );\n return false;\n }\n\n // Broadcast the update to all clients (only for persisted messages)\n // For streaming messages, the update will be included when persisted\n if (!isStreamingMessage) {\n // Re-fetch the message for broadcast since we modified it\n const broadcastMessage = this._findMessageByToolCallId(toolCallId);\n if (broadcastMessage) {\n this._broadcastChatMessage({\n type: MessageType.CF_AGENT_MESSAGE_UPDATED,\n message: broadcastMessage\n });\n }\n }\n\n // Note: We don't automatically continue the conversation here.\n // The client is responsible for sending a follow-up request if needed.\n // This avoids re-entering onChatMessage with unexpected state.\n\n return true;\n }\n\n private async _reply(\n id: string,\n response: Response,\n excludeBroadcastIds: string[] = [],\n options: { continuation?: boolean } = {}\n ) {\n const { continuation = false } = options;\n\n return this._tryCatchChat(async () => {\n if (!response.body) {\n // Send empty response if no body\n this._broadcastChatMessage({\n body: \"\",\n done: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n return;\n }\n\n // Start tracking this stream for resumability\n const streamId = this._startStream(id);\n\n /* Lazy loading ai sdk, because putting it in module scope is\n * causing issues with startup time.\n * The only place it's used is in _reply, which only matters after\n * a chat message is received.\n * So it's safe to delay loading it until a chat message is received.\n */\n const { getToolName, isToolUIPart, parsePartialJson } =\n await import(\"ai\");\n\n const reader = response.body.getReader();\n\n // Parsing state adapted from:\n // https://github.com/vercel/ai/blob/main/packages/ai/src/ui-message-stream/ui-message-chunks.ts#L295\n const message: ChatMessage = {\n id: `assistant_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`, // default\n role: \"assistant\",\n parts: []\n };\n // Track the streaming message so tool results can be applied before persistence\n this._streamingMessage = message;\n // Set up completion promise for tool continuation to wait on\n this._streamCompletionPromise = new Promise((resolve) => {\n this._streamCompletionResolve = resolve;\n });\n let activeTextParts: Record<string, TextUIPart> = {};\n let activeReasoningParts: Record<string, ReasoningUIPart> = {};\n const partialToolCalls: Record<\n string,\n { text: string; index: number; toolName: string; dynamic?: boolean }\n > = {};\n\n function updateDynamicToolPart(\n options: {\n toolName: string;\n toolCallId: string;\n providerExecuted?: boolean;\n } & (\n | {\n state: \"input-streaming\";\n input: unknown;\n }\n | {\n state: \"input-available\";\n input: unknown;\n providerMetadata?: ProviderMetadata;\n }\n | {\n state: \"output-available\";\n input: unknown;\n output: unknown;\n preliminary: boolean | undefined;\n }\n | {\n state: \"output-error\";\n input: unknown;\n errorText: string;\n providerMetadata?: ProviderMetadata;\n }\n )\n ) {\n const part = message.parts.find(\n (part) =>\n part.type === \"dynamic-tool\" &&\n part.toolCallId === options.toolCallId\n ) as DynamicToolUIPart | undefined;\n\n const anyOptions = options as Record<string, unknown>;\n const anyPart = part as Record<string, unknown>;\n\n if (part != null) {\n part.state = options.state;\n anyPart.toolName = options.toolName;\n anyPart.input = anyOptions.input;\n anyPart.output = anyOptions.output;\n anyPart.errorText = anyOptions.errorText;\n anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;\n anyPart.preliminary = anyOptions.preliminary;\n\n if (\n anyOptions.providerMetadata != null &&\n part.state === \"input-available\"\n ) {\n part.callProviderMetadata =\n anyOptions.providerMetadata as ProviderMetadata;\n }\n } else {\n message.parts.push({\n type: \"dynamic-tool\",\n toolName: options.toolName,\n toolCallId: options.toolCallId,\n state: options.state,\n input: anyOptions.input,\n output: anyOptions.output,\n errorText: anyOptions.errorText,\n preliminary: anyOptions.preliminary,\n ...(anyOptions.providerMetadata != null\n ? { callProviderMetadata: anyOptions.providerMetadata }\n : {})\n } as DynamicToolUIPart);\n }\n }\n\n function updateToolPart(\n options: {\n toolName: string;\n toolCallId: string;\n providerExecuted?: boolean;\n } & (\n | {\n state: \"input-streaming\";\n input: unknown;\n providerExecuted?: boolean;\n }\n | {\n state: \"input-available\";\n input: unknown;\n providerExecuted?: boolean;\n providerMetadata?: ProviderMetadata;\n }\n | {\n state: \"output-available\";\n input: unknown;\n output: unknown;\n providerExecuted?: boolean;\n preliminary?: boolean;\n }\n | {\n state: \"output-error\";\n input: unknown;\n rawInput?: unknown;\n errorText: string;\n providerExecuted?: boolean;\n providerMetadata?: ProviderMetadata;\n }\n )\n ) {\n const part = message.parts.find(\n (part) =>\n isToolUIPart(part) &&\n (part as ToolUIPart).toolCallId === options.toolCallId\n ) as ToolUIPart | undefined;\n\n const anyOptions = options as Record<string, unknown>;\n const anyPart = part as Record<string, unknown>;\n\n if (part != null) {\n part.state = options.state;\n anyPart.input = anyOptions.input;\n anyPart.output = anyOptions.output;\n anyPart.errorText = anyOptions.errorText;\n anyPart.rawInput = anyOptions.rawInput;\n anyPart.preliminary = anyOptions.preliminary;\n\n // once providerExecuted is set, it stays for streaming\n anyPart.providerExecuted =\n anyOptions.providerExecuted ?? part.providerExecuted;\n\n if (\n anyOptions.providerMetadata != null &&\n part.state === \"input-available\"\n ) {\n part.callProviderMetadata =\n anyOptions.providerMetadata as ProviderMetadata;\n }\n } else {\n message.parts.push({\n type: `tool-${options.toolName}`,\n toolCallId: options.toolCallId,\n state: options.state,\n input: anyOptions.input,\n output: anyOptions.output,\n rawInput: anyOptions.rawInput,\n errorText: anyOptions.errorText,\n providerExecuted: anyOptions.providerExecuted,\n preliminary: anyOptions.preliminary,\n ...(anyOptions.providerMetadata != null\n ? { callProviderMetadata: anyOptions.providerMetadata }\n : {})\n } as ToolUIPart);\n }\n }\n\n async function updateMessageMetadata(metadata: unknown) {\n if (metadata != null) {\n const mergedMetadata =\n message.metadata != null\n ? { ...message.metadata, ...metadata } // TODO: do proper merging\n : metadata;\n\n message.metadata = mergedMetadata;\n }\n }\n\n let streamCompleted = false;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n // Mark the stream as completed\n this._completeStream(streamId);\n streamCompleted = true;\n // Send final completion signal\n this._broadcastChatMessage({\n body: \"\",\n done: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n break;\n }\n\n const chunk = decoder.decode(value);\n\n // Determine response format based on content-type\n const contentType = response.headers.get(\"content-type\") || \"\";\n const isSSE = contentType.includes(\"text/event-stream\");\n\n // After streaming is complete, persist the complete assistant's response\n if (isSSE) {\n // Parse AI SDK v5 SSE format and extract text deltas\n const lines = chunk.split(\"\\n\");\n for (const line of lines) {\n if (line.startsWith(\"data: \") && line !== \"data: [DONE]\") {\n try {\n const data: UIMessageChunk = JSON.parse(line.slice(6)); // Remove 'data: ' prefix\n switch (data.type) {\n case \"text-start\": {\n const textPart: TextUIPart = {\n type: \"text\",\n text: \"\",\n providerMetadata: data.providerMetadata,\n state: \"streaming\"\n };\n activeTextParts[data.id] = textPart;\n message.parts.push(textPart);\n break;\n }\n\n case \"text-delta\": {\n const textPart = activeTextParts[data.id];\n textPart.text += data.delta;\n textPart.providerMetadata =\n data.providerMetadata ?? textPart.providerMetadata;\n break;\n }\n\n case \"text-end\": {\n const textPart = activeTextParts[data.id];\n textPart.state = \"done\";\n textPart.providerMetadata =\n data.providerMetadata ?? textPart.providerMetadata;\n delete activeTextParts[data.id];\n break;\n }\n\n case \"reasoning-start\": {\n const reasoningPart: ReasoningUIPart = {\n type: \"reasoning\",\n text: \"\",\n providerMetadata: data.providerMetadata,\n state: \"streaming\"\n };\n activeReasoningParts[data.id] = reasoningPart;\n message.parts.push(reasoningPart);\n break;\n }\n\n case \"reasoning-delta\": {\n const reasoningPart = activeReasoningParts[data.id];\n reasoningPart.text += data.delta;\n reasoningPart.providerMetadata =\n data.providerMetadata ?? reasoningPart.providerMetadata;\n break;\n }\n\n case \"reasoning-end\": {\n const reasoningPart = activeReasoningParts[data.id];\n reasoningPart.providerMetadata =\n data.providerMetadata ?? reasoningPart.providerMetadata;\n reasoningPart.state = \"done\";\n delete activeReasoningParts[data.id];\n\n break;\n }\n\n case \"file\": {\n message.parts.push({\n type: \"file\",\n mediaType: data.mediaType,\n url: data.url\n });\n\n break;\n }\n\n case \"source-url\": {\n message.parts.push({\n type: \"source-url\",\n sourceId: data.sourceId,\n url: data.url,\n title: data.title,\n providerMetadata: data.providerMetadata\n });\n\n break;\n }\n\n case \"source-document\": {\n message.parts.push({\n type: \"source-document\",\n sourceId: data.sourceId,\n mediaType: data.mediaType,\n title: data.title,\n filename: data.filename,\n providerMetadata: data.providerMetadata\n });\n\n break;\n }\n\n case \"tool-input-start\": {\n const toolInvocations =\n message.parts.filter(isToolUIPart);\n\n // add the partial tool call to the map\n partialToolCalls[data.toolCallId] = {\n text: \"\",\n toolName: data.toolName,\n index: toolInvocations.length,\n dynamic: data.dynamic\n };\n\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-streaming\",\n input: undefined\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-streaming\",\n input: undefined\n });\n }\n\n break;\n }\n\n case \"tool-input-delta\": {\n const partialToolCall = partialToolCalls[data.toolCallId];\n\n partialToolCall.text += data.inputTextDelta;\n\n const partialArgsResult = await parsePartialJson(\n partialToolCall.text\n );\n const partialArgs = (\n partialArgsResult as { value: Record<string, unknown> }\n ).value;\n\n if (partialToolCall.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: partialToolCall.toolName,\n state: \"input-streaming\",\n input: partialArgs\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: partialToolCall.toolName,\n state: \"input-streaming\",\n input: partialArgs\n });\n }\n\n break;\n }\n\n case \"tool-input-available\": {\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-available\",\n input: data.input,\n providerMetadata: data.providerMetadata\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"input-available\",\n input: data.input,\n providerExecuted: data.providerExecuted,\n providerMetadata: data.providerMetadata\n });\n }\n\n // TODO: Do we want to expose onToolCall?\n\n // invoke the onToolCall callback if it exists. This is blocking.\n // In the future we should make this non-blocking, which\n // requires additional state management for error handling etc.\n // Skip calling onToolCall for provider-executed tools since they are already executed\n // if (onToolCall && !data.providerExecuted) {\n // await onToolCall({\n // toolCall: data\n // });\n // }\n break;\n }\n\n case \"tool-input-error\": {\n if (data.dynamic) {\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"output-error\",\n input: data.input,\n errorText: data.errorText,\n providerMetadata: data.providerMetadata\n });\n } else {\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: data.toolName,\n state: \"output-error\",\n input: undefined,\n rawInput: data.input,\n errorText: data.errorText,\n providerExecuted: data.providerExecuted,\n providerMetadata: data.providerMetadata\n });\n }\n\n break;\n }\n\n case \"tool-output-available\": {\n if (data.dynamic) {\n const toolInvocations = message.parts.filter(\n (part) => part.type === \"dynamic-tool\"\n ) as DynamicToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: toolInvocation.toolName,\n state: \"output-available\",\n input: toolInvocation.input,\n output: data.output,\n preliminary: data.preliminary\n });\n } else {\n const toolInvocations = message.parts.filter(\n isToolUIPart\n ) as ToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: getToolName(toolInvocation),\n state: \"output-available\",\n input: toolInvocation.input,\n output: data.output,\n providerExecuted: data.providerExecuted,\n preliminary: data.preliminary\n });\n }\n\n break;\n }\n\n case \"tool-output-error\": {\n if (data.dynamic) {\n const toolInvocations = message.parts.filter(\n (part) => part.type === \"dynamic-tool\"\n ) as DynamicToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n\n updateDynamicToolPart({\n toolCallId: data.toolCallId,\n toolName: toolInvocation.toolName,\n state: \"output-error\",\n input: toolInvocation.input,\n errorText: data.errorText\n });\n } else {\n const toolInvocations = message.parts.filter(\n isToolUIPart\n ) as ToolUIPart[];\n\n const toolInvocation = toolInvocations.find(\n (invocation) =>\n invocation.toolCallId === data.toolCallId\n );\n\n if (!toolInvocation)\n throw new Error(\"Tool invocation not found\");\n updateToolPart({\n toolCallId: data.toolCallId,\n toolName: getToolName(toolInvocation),\n state: \"output-error\",\n input: toolInvocation.input,\n rawInput:\n \"rawInput\" in toolInvocation\n ? toolInvocation.rawInput\n : undefined,\n errorText: data.errorText\n });\n }\n\n break;\n }\n\n case \"start-step\": {\n // add a step boundary part to the message\n message.parts.push({ type: \"step-start\" });\n break;\n }\n\n case \"finish-step\": {\n // reset the current text and reasoning parts\n activeTextParts = {};\n activeReasoningParts = {};\n break;\n }\n\n case \"start\": {\n if (data.messageId != null) {\n message.id = data.messageId;\n }\n\n await updateMessageMetadata(data.messageMetadata);\n\n break;\n }\n\n case \"finish\": {\n await updateMessageMetadata(data.messageMetadata);\n break;\n }\n\n case \"message-metadata\": {\n await updateMessageMetadata(data.messageMetadata);\n break;\n }\n\n case \"error\": {\n this._broadcastChatMessage({\n error: true,\n body: data.errorText ?? JSON.stringify(data),\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE\n });\n\n break;\n }\n // Do we want to handle data parts?\n }\n\n // Convert internal AI SDK stream events to valid UIMessageStreamPart format.\n // The \"finish\" event with \"finishReason\" is an internal LanguageModelV3StreamPart,\n // not a UIMessageStreamPart (which expects \"messageMetadata\" instead).\n // See: https://github.com/cloudflare/agents/issues/677\n let eventToSend: unknown = data;\n if (data.type === \"finish\" && \"finishReason\" in data) {\n const { finishReason, ...rest } = data as {\n finishReason: string;\n [key: string]: unknown;\n };\n eventToSend = {\n ...rest,\n type: \"finish\",\n messageMetadata: { finishReason }\n };\n }\n\n // Store chunk for replay on reconnection\n const chunkBody = JSON.stringify(eventToSend);\n this._storeStreamChunk(streamId, chunkBody);\n\n // Forward the converted event to the client\n this._broadcastChatMessage({\n body: chunkBody,\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n } catch (_error) {\n // Skip malformed JSON lines silently\n }\n }\n }\n } else {\n // Handle plain text responses (e.g., from generateText)\n // Treat the entire chunk as a text delta to preserve exact formatting\n if (chunk.length > 0) {\n message.parts.push({ type: \"text\", text: chunk });\n // Synthesize a text-delta event so clients can stream-render\n const chunkBody = JSON.stringify({\n type: \"text-delta\",\n delta: chunk\n });\n // Store chunk for replay on reconnection\n this._storeStreamChunk(streamId, chunkBody);\n this._broadcastChatMessage({\n body: chunkBody,\n done: false,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n }\n }\n }\n } catch (error) {\n // Mark stream as error if not already completed\n if (!streamCompleted) {\n this._markStreamError(streamId);\n // Notify clients of the error\n this._broadcastChatMessage({\n body: error instanceof Error ? error.message : \"Stream error\",\n done: true,\n error: true,\n id,\n type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,\n ...(continuation && { continuation: true })\n });\n }\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n if (message.parts.length > 0) {\n if (continuation) {\n // Find the last assistant message and append parts to it\n let lastAssistantIdx = -1;\n for (let i = this.messages.length - 1; i >= 0; i--) {\n if (this.messages[i].role === \"assistant\") {\n lastAssistantIdx = i;\n break;\n }\n }\n if (lastAssistantIdx >= 0) {\n const lastAssistant = this.messages[lastAssistantIdx];\n const mergedMessage: ChatMessage = {\n ...lastAssistant,\n parts: [...lastAssistant.parts, ...message.parts]\n };\n const updatedMessages = [...this.messages];\n updatedMessages[lastAssistantIdx] = mergedMessage;\n await this.persistMessages(updatedMessages, excludeBroadcastIds);\n } else {\n // No assistant message to append to, create new one\n await this.persistMessages(\n [...this.messages, message],\n excludeBroadcastIds\n );\n }\n } else {\n await this.persistMessages(\n [...this.messages, message],\n excludeBroadcastIds\n );\n }\n }\n\n // Clear the streaming message reference and resolve completion promise\n this._streamingMessage = null;\n if (this._streamCompletionResolve) {\n this._streamCompletionResolve();\n this._streamCompletionResolve = null;\n this._streamCompletionPromise = null;\n }\n });\n }\n\n /**\n * Mark a stream as errored and clean up state.\n * @param streamId - The stream to mark as errored\n * @internal Protected for testing purposes.\n */\n protected _markStreamError(streamId: string) {\n // Flush any pending chunks before marking error\n this._flushChunkBuffer();\n\n this.sql`\n update cf_ai_chat_stream_metadata \n set status = 'error', completed_at = ${Date.now()} \n where id = ${streamId}\n `;\n this._activeStreamId = null;\n this._activeRequestId = null;\n this._streamChunkIndex = 0;\n }\n\n /**\n * For the given message id, look up its associated AbortController\n * If the AbortController does not exist, create and store one in memory\n *\n * returns the AbortSignal associated with the AbortController\n */\n private _getAbortSignal(id: string): AbortSignal | undefined {\n // Defensive check, since we're coercing message types at the moment\n if (typeof id !== \"string\") {\n return undefined;\n }\n\n if (!this._chatMessageAbortControllers.has(id)) {\n this._chatMessageAbortControllers.set(id, new AbortController());\n }\n\n return this._chatMessageAbortControllers.get(id)?.signal;\n }\n\n /**\n * Remove an abort controller from the cache of pending message responses\n */\n private _removeAbortController(id: string) {\n this._chatMessageAbortControllers.delete(id);\n }\n\n /**\n * Propagate an abort signal for any requests associated with the given message id\n */\n private _cancelChatRequest(id: string) {\n if (this._chatMessageAbortControllers.has(id)) {\n const abortController = this._chatMessageAbortControllers.get(id);\n abortController?.abort();\n }\n }\n\n /**\n * Abort all pending requests and clear the cache of AbortControllers\n */\n private _destroyAbortControllers() {\n for (const controller of this._chatMessageAbortControllers.values()) {\n controller?.abort();\n }\n this._chatMessageAbortControllers.clear();\n }\n\n /**\n * When the DO is destroyed, cancel all pending requests and clean up resources\n */\n async destroy() {\n this._destroyAbortControllers();\n\n // Flush any remaining chunks before cleanup\n this._flushChunkBuffer();\n\n // Clean up stream tables\n this.sql`drop table if exists cf_ai_chat_stream_chunks`;\n this.sql`drop table if exists cf_ai_chat_stream_metadata`;\n\n // Clear active stream state\n this._activeStreamId = null;\n this._activeRequestId = null;\n\n await super.destroy();\n }\n}\n"],"mappings":"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"}
|
package/dist/ai-react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./context-DcbQ8o7k.js";
|
|
2
|
-
import "./client-
|
|
2
|
+
import "./client-DFotUKH_.js";
|
|
3
3
|
import "./ai-types-0OnT3FHg.js";
|
|
4
|
-
import "./index-
|
|
4
|
+
import "./index-CT2tCrLr.js";
|
|
5
5
|
import "./client-CdM5I962.js";
|
|
6
6
|
import { useAgent } from "./react.js";
|
|
7
7
|
import { ChatInit, JSONSchema7, Tool, UIMessage } from "ai";
|
|
@@ -21,6 +21,10 @@ type JSONSchemaType = JSONSchema7;
|
|
|
21
21
|
* Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)
|
|
22
22
|
* because client tools must be serializable for the wire format. Zod schemas cannot be
|
|
23
23
|
* serialized, so we require raw JSON Schema here.
|
|
24
|
+
*
|
|
25
|
+
* @deprecated Use AI SDK's native tool pattern instead. Define tools on the server with
|
|
26
|
+
* `tool()` from "ai", and handle client-side execution via the `onToolCall` callback
|
|
27
|
+
* in `useAgentChat`. For tools requiring user approval, use `needsApproval` on the server.
|
|
24
28
|
*/
|
|
25
29
|
type AITool<Input = unknown, Output = unknown> = {
|
|
26
30
|
/** Human-readable description of what the tool does */
|
|
@@ -41,6 +45,8 @@ type AITool<Input = unknown, Output = unknown> = {
|
|
|
41
45
|
* Schema for a client tool sent to the server.
|
|
42
46
|
* This is the wire format - what gets sent in the request body.
|
|
43
47
|
* Must match the server-side ClientToolSchema type in ai-chat-agent.ts.
|
|
48
|
+
*
|
|
49
|
+
* @deprecated Use AI SDK's native tool pattern instead. Define tools on the server.
|
|
44
50
|
*/
|
|
45
51
|
type ClientToolSchema = {
|
|
46
52
|
/** Unique name for the tool */
|
|
@@ -55,6 +61,9 @@ type ClientToolSchema = {
|
|
|
55
61
|
* These schemas are automatically sent to the server with each request.
|
|
56
62
|
* @param tools - Record of tool name to tool definition
|
|
57
63
|
* @returns Array of tool schemas to send to server, or undefined if none
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Use AI SDK's native tool pattern instead. Define tools on the server
|
|
66
|
+
* and use `onToolCall` callback for client-side execution.
|
|
58
67
|
*/
|
|
59
68
|
declare function extractClientToolSchemas(
|
|
60
69
|
tools?: Record<string, AITool<unknown, unknown>>
|
|
@@ -107,6 +116,20 @@ type PrepareSendMessagesRequestResult = {
|
|
|
107
116
|
/** Custom API endpoint */
|
|
108
117
|
api?: string;
|
|
109
118
|
};
|
|
119
|
+
/**
|
|
120
|
+
* Callback for handling client-side tool execution.
|
|
121
|
+
* Called when a tool without server-side execute is invoked.
|
|
122
|
+
*/
|
|
123
|
+
type OnToolCallCallback = (options: {
|
|
124
|
+
/** The tool call that needs to be handled */
|
|
125
|
+
toolCall: {
|
|
126
|
+
toolCallId: string;
|
|
127
|
+
toolName: string;
|
|
128
|
+
input: unknown;
|
|
129
|
+
};
|
|
130
|
+
/** Function to provide the tool output */
|
|
131
|
+
addToolOutput: (options: { toolCallId: string; output: unknown }) => void;
|
|
132
|
+
}) => void | Promise<void>;
|
|
110
133
|
/**
|
|
111
134
|
* Options for the useAgentChat hook
|
|
112
135
|
*/
|
|
@@ -125,15 +148,43 @@ type UseAgentChatOptions<
|
|
|
125
148
|
/** Request headers */
|
|
126
149
|
headers?: HeadersInit;
|
|
127
150
|
/**
|
|
151
|
+
* Callback for handling client-side tool execution.
|
|
152
|
+
* Called when a tool without server-side `execute` is invoked by the LLM.
|
|
153
|
+
*
|
|
154
|
+
* Use this for:
|
|
155
|
+
* - Tools that need browser APIs (geolocation, camera, etc.)
|
|
156
|
+
* - Tools that need user interaction before providing a result
|
|
157
|
+
* - Tools requiring approval before execution
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* onToolCall: async ({ toolCall, addToolOutput }) => {
|
|
162
|
+
* if (toolCall.toolName === 'getLocation') {
|
|
163
|
+
* const position = await navigator.geolocation.getCurrentPosition();
|
|
164
|
+
* addToolOutput({
|
|
165
|
+
* toolCallId: toolCall.toolCallId,
|
|
166
|
+
* output: { lat: position.coords.latitude, lng: position.coords.longitude }
|
|
167
|
+
* });
|
|
168
|
+
* }
|
|
169
|
+
* }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
onToolCall?: OnToolCallCallback;
|
|
173
|
+
/**
|
|
174
|
+
* @deprecated Use `onToolCall` callback instead for automatic tool execution.
|
|
128
175
|
* @description Whether to automatically resolve tool calls that do not require human interaction.
|
|
129
176
|
* @experimental
|
|
130
177
|
*/
|
|
131
178
|
experimental_automaticToolResolution?: boolean;
|
|
132
179
|
/**
|
|
180
|
+
* @deprecated Use `onToolCall` callback instead. Define tools on the server
|
|
181
|
+
* and handle client-side execution via `onToolCall`.
|
|
182
|
+
*
|
|
133
183
|
* Tools that can be executed on the client.
|
|
134
184
|
*/
|
|
135
185
|
tools?: Record<string, AITool<unknown, unknown>>;
|
|
136
186
|
/**
|
|
187
|
+
* @deprecated Use `needsApproval` on server-side tools instead.
|
|
137
188
|
* @description Manual override for tools requiring confirmation.
|
|
138
189
|
* If not provided, will auto-detect from tools object (tools without execute require confirmation).
|
|
139
190
|
*/
|
|
@@ -151,6 +202,8 @@ type UseAgentChatOptions<
|
|
|
151
202
|
*/
|
|
152
203
|
autoContinueAfterToolResult?: boolean;
|
|
153
204
|
/**
|
|
205
|
+
* @deprecated Use `sendAutomaticallyWhen` from AI SDK instead.
|
|
206
|
+
*
|
|
154
207
|
* When true (default), automatically sends the next message only after
|
|
155
208
|
* all pending confirmation-required tool calls have been resolved.
|
|
156
209
|
* When false, sends immediately after each tool result.
|
|
@@ -188,23 +241,42 @@ type UseAgentChatOptions<
|
|
|
188
241
|
* Tools require confirmation if they have no execute function AND are not server-executed.
|
|
189
242
|
* @param tools - Record of tool name to tool definition
|
|
190
243
|
* @returns Array of tool names that require confirmation
|
|
244
|
+
*
|
|
245
|
+
* @deprecated Use `needsApproval` on server-side tools instead.
|
|
191
246
|
*/
|
|
192
247
|
declare function detectToolsRequiringConfirmation(
|
|
193
248
|
tools?: Record<string, AITool<unknown, unknown>>
|
|
194
249
|
): string[];
|
|
250
|
+
/**
|
|
251
|
+
* Return type for addToolOutput function
|
|
252
|
+
*/
|
|
253
|
+
type AddToolOutputOptions = {
|
|
254
|
+
/** The ID of the tool call to provide output for */
|
|
255
|
+
toolCallId: string;
|
|
256
|
+
/** The name of the tool (optional, for type safety) */
|
|
257
|
+
toolName?: string;
|
|
258
|
+
/** The output to provide */
|
|
259
|
+
output: unknown;
|
|
260
|
+
};
|
|
195
261
|
declare function useAgentChat<
|
|
196
262
|
State = unknown,
|
|
197
263
|
ChatMessage extends UIMessage = UIMessage
|
|
198
264
|
>(
|
|
199
265
|
options: UseAgentChatOptions<State, ChatMessage>
|
|
200
|
-
): ReturnType<typeof useChat<ChatMessage
|
|
266
|
+
): Omit<ReturnType<typeof useChat<ChatMessage>>, "addToolOutput"> & {
|
|
201
267
|
clearHistory: () => void;
|
|
268
|
+
/**
|
|
269
|
+
* Provide output for a tool call. Use this for tools that require user interaction
|
|
270
|
+
* or client-side execution.
|
|
271
|
+
*/
|
|
272
|
+
addToolOutput: (opts: AddToolOutputOptions) => void;
|
|
202
273
|
};
|
|
203
274
|
//#endregion
|
|
204
275
|
export {
|
|
205
276
|
AITool,
|
|
206
277
|
ClientToolSchema,
|
|
207
278
|
JSONSchemaType,
|
|
279
|
+
OnToolCallCallback,
|
|
208
280
|
PrepareSendMessagesRequestOptions,
|
|
209
281
|
PrepareSendMessagesRequestResult,
|
|
210
282
|
detectToolsRequiringConfirmation,
|
package/dist/ai-react.js
CHANGED
|
@@ -10,6 +10,9 @@ import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
10
10
|
* These schemas are automatically sent to the server with each request.
|
|
11
11
|
* @param tools - Record of tool name to tool definition
|
|
12
12
|
* @returns Array of tool schemas to send to server, or undefined if none
|
|
13
|
+
*
|
|
14
|
+
* @deprecated Use AI SDK's native tool pattern instead. Define tools on the server
|
|
15
|
+
* and use `onToolCall` callback for client-side execution.
|
|
13
16
|
*/
|
|
14
17
|
function extractClientToolSchemas(tools) {
|
|
15
18
|
if (!tools) return void 0;
|
|
@@ -34,14 +37,18 @@ const requestCache = /* @__PURE__ */ new Map();
|
|
|
34
37
|
* Tools require confirmation if they have no execute function AND are not server-executed.
|
|
35
38
|
* @param tools - Record of tool name to tool definition
|
|
36
39
|
* @returns Array of tool names that require confirmation
|
|
40
|
+
*
|
|
41
|
+
* @deprecated Use `needsApproval` on server-side tools instead.
|
|
37
42
|
*/
|
|
38
43
|
function detectToolsRequiringConfirmation(tools) {
|
|
39
44
|
if (!tools) return [];
|
|
40
45
|
return Object.entries(tools).filter(([_name, tool$1]) => !tool$1.execute).map(([name]) => name);
|
|
41
46
|
}
|
|
42
47
|
function useAgentChat(options) {
|
|
43
|
-
const { agent, getInitialMessages, messages: optionsInitialMessages, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = false, autoSendAfterAllConfirmationsResolved = true, resume = true, prepareSendMessagesRequest, ...rest } = options;
|
|
48
|
+
const { agent, getInitialMessages, messages: optionsInitialMessages, onToolCall, experimental_automaticToolResolution, tools, toolsRequiringConfirmation: manualToolsRequiringConfirmation, autoContinueAfterToolResult = false, autoSendAfterAllConfirmationsResolved = true, resume = true, prepareSendMessagesRequest, ...rest } = options;
|
|
44
49
|
const toolsRequiringConfirmation = manualToolsRequiringConfirmation ?? detectToolsRequiringConfirmation(tools);
|
|
50
|
+
const onToolCallRef = useRef(onToolCall);
|
|
51
|
+
onToolCallRef.current = onToolCall;
|
|
45
52
|
const agentUrl = new URL(`${(agent._url || agent._pkurl)?.replace("ws://", "http://").replace("wss://", "https://")}`);
|
|
46
53
|
agentUrl.searchParams.delete("_pk");
|
|
47
54
|
const agentUrlString = agentUrl.toString();
|
|
@@ -290,6 +297,48 @@ function useAgentChat(options) {
|
|
|
290
297
|
toolsRequiringConfirmation,
|
|
291
298
|
autoContinueAfterToolResult
|
|
292
299
|
]);
|
|
300
|
+
const sendToolOutputToServer = useCallback((toolCallId, toolName, output) => {
|
|
301
|
+
agentRef.current.send(JSON.stringify({
|
|
302
|
+
type: MessageType.CF_AGENT_TOOL_RESULT,
|
|
303
|
+
toolCallId,
|
|
304
|
+
toolName,
|
|
305
|
+
output,
|
|
306
|
+
autoContinue: autoContinueAfterToolResult
|
|
307
|
+
}));
|
|
308
|
+
setClientToolResults((prev) => new Map(prev).set(toolCallId, output));
|
|
309
|
+
}, [autoContinueAfterToolResult]);
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
const currentOnToolCall = onToolCallRef.current;
|
|
312
|
+
if (!currentOnToolCall) return;
|
|
313
|
+
const lastMessage$1 = useChatHelpers.messages[useChatHelpers.messages.length - 1];
|
|
314
|
+
if (!lastMessage$1 || lastMessage$1.role !== "assistant") return;
|
|
315
|
+
const pendingToolCalls = lastMessage$1.parts.filter((part) => isToolUIPart(part) && part.state === "input-available" && !processedToolCalls.current.has(part.toolCallId));
|
|
316
|
+
for (const part of pendingToolCalls) if (isToolUIPart(part)) {
|
|
317
|
+
const toolCallId = part.toolCallId;
|
|
318
|
+
const toolName = getToolName(part);
|
|
319
|
+
processedToolCalls.current.add(toolCallId);
|
|
320
|
+
const addToolOutput$1 = (opts) => {
|
|
321
|
+
sendToolOutputToServer(opts.toolCallId, toolName, opts.output);
|
|
322
|
+
useChatHelpers.addToolResult({
|
|
323
|
+
tool: toolName,
|
|
324
|
+
toolCallId: opts.toolCallId,
|
|
325
|
+
output: opts.output
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
currentOnToolCall({
|
|
329
|
+
toolCall: {
|
|
330
|
+
toolCallId,
|
|
331
|
+
toolName,
|
|
332
|
+
input: part.input
|
|
333
|
+
},
|
|
334
|
+
addToolOutput: addToolOutput$1
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}, [
|
|
338
|
+
useChatHelpers.messages,
|
|
339
|
+
sendToolOutputToServer,
|
|
340
|
+
useChatHelpers.addToolResult
|
|
341
|
+
]);
|
|
293
342
|
/**
|
|
294
343
|
* Contains the request ID, accumulated message parts, and a unique message ID.
|
|
295
344
|
* Used for both resumed streams and real-time broadcasts from other tabs.
|
|
@@ -553,9 +602,19 @@ function useAgentChat(options) {
|
|
|
553
602
|
});
|
|
554
603
|
for (const toolCallId of processedToolCalls.current) if (!currentToolCallIds.has(toolCallId)) processedToolCalls.current.delete(toolCallId);
|
|
555
604
|
}, [useChatHelpers.messages]);
|
|
605
|
+
const addToolOutput = useCallback((opts) => {
|
|
606
|
+
const toolName = opts.toolName ?? "";
|
|
607
|
+
sendToolOutputToServer(opts.toolCallId, toolName, opts.output);
|
|
608
|
+
useChatHelpers.addToolResult({
|
|
609
|
+
tool: toolName,
|
|
610
|
+
toolCallId: opts.toolCallId,
|
|
611
|
+
output: opts.output
|
|
612
|
+
});
|
|
613
|
+
}, [sendToolOutputToServer, useChatHelpers.addToolResult]);
|
|
556
614
|
return {
|
|
557
615
|
...useChatHelpers,
|
|
558
616
|
messages: messagesWithToolResults,
|
|
617
|
+
addToolOutput,
|
|
559
618
|
addToolResult: addToolResultAndSendMessage,
|
|
560
619
|
clearHistory: () => {
|
|
561
620
|
useChatHelpers.setMessages([]);
|
package/dist/ai-react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-react.js","names":["schemas: ClientToolSchema[]","tool","options","controller: ReadableStreamDefaultController","data: OutgoingMessage<ChatMessage>","customTransport: ChatTransport<ChatMessage>","body: Record<string, unknown>","headers: HeadersInit | undefined","credentials: RequestCredentials | undefined","api: string | undefined","lastMessage","toolResults: Array<{\n toolCallId: string;\n toolName: string;\n output: unknown;\n }>","toolOutput: unknown","existingParts: ChatMessage[\"parts\"]","addToolResultAndSendMessage: typeof useChatHelpers.addToolResult"],"sources":["../src/ai-react.tsx"],"sourcesContent":["import { useChat, type UseChatOptions } from \"@ai-sdk/react\";\nimport { getToolName, isToolUIPart } from \"ai\";\nimport type {\n ChatInit,\n ChatTransport,\n JSONSchema7,\n Tool,\n UIMessage as Message,\n UIMessage\n} from \"ai\";\nimport { DefaultChatTransport } from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { use, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { OutgoingMessage } from \"./ai-types\";\nimport { MessageType } from \"./ai-types\";\nimport type { useAgent } from \"./react\";\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.\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 * Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)\n * because client tools must be serializable for the wire format. Zod schemas cannot be\n * serialized, so we require raw JSON Schema here.\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\n/**\n * Schema for a client tool sent to the server.\n * This is the wire format - what gets sent in the request body.\n * Must match the server-side ClientToolSchema type in ai-chat-agent.ts.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\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\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 * @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\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 * Internal type for AI SDK transport\n * @internal\n */\ntype InternalPrepareResult = {\n body: Record<string, unknown>;\n headers?: HeadersInit;\n credentials?: RequestCredentials;\n api?: string;\n};\n\n/**\n * Options for the useAgentChat hook\n */\ntype UseAgentChatOptions<\n State,\n ChatMessage extends UIMessage = UIMessage\n> = Omit<UseChatParams<ChatMessage>, \"fetch\"> & {\n /** Agent connection from useAgent */\n agent: ReturnType<typeof useAgent<State>>;\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 * @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.\n\n */\n tools?: Record<string, AITool<unknown, unknown>>;\n /**\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, the server automatically continues the conversation after\n * receiving client-side tool results, similar to how server-executed tools\n * work with maxSteps in streamText. The continuation is merged into the\n * same assistant message.\n *\n * When false (default), the client calls sendMessage() after tool results\n * to continue the conversation, which creates a new assistant message.\n *\n * @default false\n */\n autoContinueAfterToolResult?: boolean;\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 * Callback to customize the request before sending messages.\n * Use this for advanced scenarios like adding custom headers or dynamic context.\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\nconst requestCache = new Map<string, Promise<Message[]>>();\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 */\nexport function detectToolsRequiringConfirmation(\n tools?: Record<string, AITool<unknown, unknown>>\n): string[] {\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 State = unknown,\n ChatMessage extends UIMessage = UIMessage\n>(\n options: UseAgentChatOptions<State, ChatMessage>\n): ReturnType<typeof useChat<ChatMessage>> & {\n clearHistory: () => void;\n} {\n const {\n agent,\n getInitialMessages,\n messages: optionsInitialMessages,\n experimental_automaticToolResolution,\n tools,\n toolsRequiringConfirmation: manualToolsRequiringConfirmation,\n autoContinueAfterToolResult = false, // Opt-in to server auto-continuation\n autoSendAfterAllConfirmationsResolved = true, // Legacy option for client-side batching\n resume = true, // Enable stream resumption by default\n prepareSendMessagesRequest,\n ...rest\n } = options;\n\n // Auto-detect tools requiring confirmation, or use manual override\n const toolsRequiringConfirmation =\n manualToolsRequiringConfirmation ?? detectToolsRequiringConfirmation(tools);\n\n const agentUrl = new URL(\n `${// @ts-expect-error we're using a protected _url property that includes query params\n ((agent._url as string | null) || agent._pkurl)\n ?.replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")}`\n );\n\n agentUrl.searchParams.delete(\"_pk\");\n const agentUrlString = agentUrl.toString();\n\n // we need to include agent.name in cache key to prevent collisions during agent switching.\n // The URL may be stale between updateProperties() and reconnect(), but agent.name\n // is updated synchronously, so each thread gets its own cache entry\n const initialMessagesCacheKey = `${agentUrlString}|${agent.agent ?? \"\"}|${agent.name ?? \"\"}`;\n\n // Keep a ref to always point to the latest agent instance\n const agentRef = useRef(agent);\n useEffect(() => {\n agentRef.current = agent;\n }, [agent]);\n\n async function defaultGetInitialMessagesFetch({\n url\n }: GetInitialMessagesOptions) {\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 initialMessagesPromise =\n getInitialMessages === null\n ? null\n : doGetInitialMessages(\n {\n agent: agent.agent,\n name: agent.name,\n url: agentUrlString\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 const aiFetch = useCallback(\n async (request: RequestInfo | URL, options: RequestInit = {}) => {\n const {\n method,\n keepalive,\n headers,\n body,\n redirect,\n integrity,\n signal,\n credentials,\n mode,\n referrer,\n referrerPolicy,\n window\n } = options;\n const id = nanoid(8);\n const abortController = new AbortController();\n let controller: ReadableStreamDefaultController;\n const currentAgent = agentRef.current;\n\n // Track this request ID so the onAgentMessage handler knows to skip it\n // (this tab's aiFetch listener handles its own stream)\n localRequestIdsRef.current.add(id);\n\n signal?.addEventListener(\"abort\", () => {\n currentAgent.send(\n JSON.stringify({\n id,\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL\n })\n );\n\n // NOTE - If we wanted to, we could preserve the \"interrupted\" message here, with the code below\n // However, I think it might be the responsibility of the library user to implement that behavior manually?\n // Reasoning: This code could be subject to collisions, as it \"force saves\" the messages we have locally\n //\n // agent.send(JSON.stringify({\n // type: MessageType.CF_AGENT_CHAT_MESSAGES,\n // messages: ... /* some way of getting current messages ref? */\n // }))\n abortController.abort();\n // Make sure to also close the stream (cf. https://github.com/cloudflare/agents-starter/issues/69)\n try {\n controller.close();\n } catch {\n // Stream may already be errored or closed\n }\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n });\n\n currentAgent.addEventListener(\n \"message\",\n (event) => {\n let data: OutgoingMessage<ChatMessage>;\n try {\n data = JSON.parse(event.data) as OutgoingMessage<ChatMessage>;\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return;\n }\n if (data.type === MessageType.CF_AGENT_USE_CHAT_RESPONSE) {\n if (data.id === id) {\n if (data.error) {\n controller.error(new Error(data.body));\n abortController.abort();\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n } else {\n // Only enqueue non-empty data to prevent JSON parsing errors\n if (data.body?.trim()) {\n controller.enqueue(\n new TextEncoder().encode(`data: ${data.body}\\n\\n`)\n );\n }\n if (data.done) {\n try {\n controller.close();\n } catch {\n // Stream may already be errored or closed\n }\n abortController.abort();\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n }\n }\n }\n }\n },\n { signal: abortController.signal }\n );\n\n const stream = new ReadableStream({\n start(c) {\n controller = c;\n },\n cancel(reason?: unknown) {\n console.warn(\n \"[agents/ai-react] cancelling stream\",\n id,\n reason || \"no reason\"\n );\n }\n });\n\n currentAgent.send(\n JSON.stringify({\n id,\n init: {\n body,\n credentials,\n headers,\n integrity,\n keepalive,\n method,\n mode,\n redirect,\n referrer,\n referrerPolicy,\n window\n },\n type: MessageType.CF_AGENT_USE_CHAT_REQUEST,\n url: request.toString()\n })\n );\n\n return new Response(stream);\n },\n []\n );\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 customTransport: ChatTransport<ChatMessage> = useMemo(\n () => ({\n sendMessages: async (\n sendMessageOptions: Parameters<\n typeof DefaultChatTransport.prototype.sendMessages\n >[0]\n ) => {\n // Extract schemas from tools with execute functions\n const clientToolSchemas = extractClientToolSchemas(toolsRef.current);\n\n const combinedPrepare =\n clientToolSchemas || prepareSendMessagesRequestRef.current\n ? async (\n prepareOptions: PrepareSendMessagesRequestOptions<ChatMessage>\n ): Promise<InternalPrepareResult> => {\n // Start with auto-extracted client tool schemas\n let body: Record<string, unknown> = {};\n let headers: HeadersInit | undefined;\n let credentials: RequestCredentials | undefined;\n let api: string | undefined;\n\n if (clientToolSchemas) {\n body = {\n id: prepareOptions.id,\n messages: prepareOptions.messages,\n trigger: prepareOptions.trigger,\n clientTools: clientToolSchemas\n };\n }\n\n // Apply prepareSendMessagesRequest callback for additional customization\n if (prepareSendMessagesRequestRef.current) {\n const userResult =\n await prepareSendMessagesRequestRef.current(prepareOptions);\n\n // user's callback can override or extend\n headers = userResult.headers;\n credentials = userResult.credentials;\n api = userResult.api;\n body = {\n ...body,\n ...(userResult.body ?? {})\n };\n }\n\n return { body, headers, credentials, api };\n }\n : undefined;\n\n const transport = new DefaultChatTransport<ChatMessage>({\n api: agentUrlString,\n fetch: aiFetch,\n prepareSendMessagesRequest: combinedPrepare\n });\n return transport.sendMessages(sendMessageOptions);\n },\n reconnectToStream: async () => null\n }),\n [agentUrlString, aiFetch]\n );\n\n const useChatHelpers = useChat<ChatMessage>({\n ...rest,\n messages: initialMessages,\n transport: customTransport,\n id: agent._pk\n // Note: We handle stream resumption via WebSocket instead of HTTP,\n // so we don't pass 'resume' to useChat. The onStreamResuming handler\n // automatically resumes active streams when the WebSocket reconnects.\n });\n\n const processedToolCalls = useRef(new Set<string>());\n const isResolvingToolsRef = useRef(false);\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(useChatHelpers.messages);\n messagesRef.current = useChatHelpers.messages;\n\n // Calculate pending confirmations for the latest assistant message\n const lastMessage =\n useChatHelpers.messages[useChatHelpers.messages.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\n // Automatic tool resolution effect.\n useEffect(() => {\n if (!experimental_automaticToolResolution) {\n return;\n }\n\n // Prevent re-entry while async operations are in progress\n if (isResolvingToolsRef.current) {\n return;\n }\n\n const lastMessage =\n useChatHelpers.messages[useChatHelpers.messages.length - 1];\n if (!lastMessage || lastMessage.role !== \"assistant\") {\n return;\n }\n\n const toolCalls = lastMessage.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 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 })\n );\n }\n\n // Also update local state via AI SDK for immediate UI feedback\n await Promise.all(\n toolResults.map((result) =>\n useChatHelpers.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\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 }\n })();\n }\n }\n }, [\n useChatHelpers.messages,\n experimental_automaticToolResolution,\n useChatHelpers.addToolResult,\n toolsRequiringConfirmation,\n autoContinueAfterToolResult\n ]);\n\n /**\n * Contains the request ID, accumulated message parts, and a unique message ID.\n * Used for both resumed streams and real-time broadcasts from other tabs.\n */\n const activeStreamRef = useRef<{\n id: string;\n messageId: string;\n parts: ChatMessage[\"parts\"];\n } | null>(null);\n\n /**\n * Tracks request IDs initiated by this tab via aiFetch.\n * Used to distinguish local requests from broadcasts.\n */\n const localRequestIdsRef = useRef<Set<string>>(new Set());\n\n useEffect(() => {\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 useChatHelpers.setMessages([]);\n break;\n\n case MessageType.CF_AGENT_CHAT_MESSAGES:\n useChatHelpers.setMessages(data.messages);\n break;\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 useChatHelpers.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, append it\n return [...prevMessages, updatedMessage];\n });\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUMING:\n if (!resume) return;\n // Clear any previous incomplete active stream to prevent memory leak\n activeStreamRef.current = null;\n // Initialize active stream state with unique ID\n activeStreamRef.current = {\n id: data.id,\n messageId: nanoid(),\n parts: []\n };\n // Send ACK to server - we're ready to receive chunks\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 case MessageType.CF_AGENT_USE_CHAT_RESPONSE: {\n // Skip if this is a response to a request this tab initiated\n // (handled by the aiFetch listener instead)\n if (localRequestIdsRef.current.has(data.id)) return;\n\n // For continuations, find the last assistant message ID to append to\n const isContinuation = data.continuation === true;\n\n // Initialize stream state for broadcasts from other tabs\n if (\n !activeStreamRef.current ||\n activeStreamRef.current.id !== data.id\n ) {\n let messageId = nanoid();\n let existingParts: ChatMessage[\"parts\"] = [];\n\n // For continuations, use the last assistant message's ID and parts\n if (isContinuation) {\n const currentMessages = messagesRef.current;\n for (let i = currentMessages.length - 1; i >= 0; i--) {\n if (currentMessages[i].role === \"assistant\") {\n messageId = currentMessages[i].id;\n existingParts = [...currentMessages[i].parts];\n break;\n }\n }\n }\n\n activeStreamRef.current = {\n id: data.id,\n messageId,\n parts: existingParts\n };\n }\n\n const activeMsg = activeStreamRef.current;\n\n if (data.body?.trim()) {\n try {\n const chunkData = JSON.parse(data.body);\n\n // Handle all chunk types for complete message reconstruction\n switch (chunkData.type) {\n case \"text-start\": {\n activeMsg.parts.push({\n type: \"text\",\n text: \"\",\n state: \"streaming\"\n });\n break;\n }\n case \"text-delta\": {\n const lastTextPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"text\");\n if (lastTextPart && lastTextPart.type === \"text\") {\n lastTextPart.text += chunkData.delta;\n } else {\n // Handle plain text responses (no text-start)\n activeMsg.parts.push({\n type: \"text\",\n text: chunkData.delta\n });\n }\n break;\n }\n case \"text-end\": {\n const lastTextPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"text\");\n if (lastTextPart && \"state\" in lastTextPart) {\n lastTextPart.state = \"done\";\n }\n break;\n }\n case \"reasoning-start\": {\n activeMsg.parts.push({\n type: \"reasoning\",\n text: \"\",\n state: \"streaming\"\n });\n break;\n }\n case \"reasoning-delta\": {\n const lastReasoningPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"reasoning\");\n if (\n lastReasoningPart &&\n lastReasoningPart.type === \"reasoning\"\n ) {\n lastReasoningPart.text += chunkData.delta;\n }\n break;\n }\n case \"reasoning-end\": {\n const lastReasoningPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"reasoning\");\n if (lastReasoningPart && \"state\" in lastReasoningPart) {\n lastReasoningPart.state = \"done\";\n }\n break;\n }\n case \"file\": {\n activeMsg.parts.push({\n type: \"file\",\n mediaType: chunkData.mediaType,\n url: chunkData.url\n });\n break;\n }\n case \"source-url\": {\n activeMsg.parts.push({\n type: \"source-url\",\n sourceId: chunkData.sourceId,\n url: chunkData.url,\n title: chunkData.title\n });\n break;\n }\n case \"source-document\": {\n activeMsg.parts.push({\n type: \"source-document\",\n sourceId: chunkData.sourceId,\n mediaType: chunkData.mediaType,\n title: chunkData.title,\n filename: chunkData.filename\n });\n break;\n }\n case \"tool-input-available\": {\n // Add tool call part when input is available\n activeMsg.parts.push({\n type: `tool-${chunkData.toolName}`,\n toolCallId: chunkData.toolCallId,\n toolName: chunkData.toolName,\n state: \"input-available\",\n input: chunkData.input\n } as ChatMessage[\"parts\"][number]);\n break;\n }\n case \"tool-output-available\": {\n // Update existing tool part with output using immutable pattern\n activeMsg.parts = activeMsg.parts.map((p) => {\n if (\n \"toolCallId\" in p &&\n p.toolCallId === chunkData.toolCallId &&\n \"state\" in p\n ) {\n return {\n ...p,\n state: \"output-available\",\n output: chunkData.output\n } as ChatMessage[\"parts\"][number];\n }\n return p;\n });\n break;\n }\n case \"step-start\": {\n activeMsg.parts.push({ type: \"step-start\" });\n break;\n }\n // Other chunk types (tool-input-start, tool-input-delta, etc.)\n // are intermediate states - the final state will be captured above\n }\n\n // Update messages with the partial response\n useChatHelpers.setMessages((prevMessages: ChatMessage[]) => {\n if (!activeMsg) return prevMessages;\n\n const existingIdx = prevMessages.findIndex(\n (m) => m.id === activeMsg.messageId\n );\n\n const partialMessage = {\n id: activeMsg.messageId,\n role: \"assistant\" as const,\n parts: [...activeMsg.parts]\n } as unknown as ChatMessage;\n\n if (existingIdx >= 0) {\n const updated = [...prevMessages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...prevMessages, partialMessage];\n });\n } catch (parseError) {\n // Log corrupted chunk for debugging - could indicate data loss\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) // Truncate for logging\n );\n }\n }\n\n // Clear on completion or error\n if (data.done || data.error) {\n activeStreamRef.current = null;\n }\n break;\n }\n }\n }\n\n agent.addEventListener(\"message\", onAgentMessage);\n return () => {\n agent.removeEventListener(\"message\", onAgentMessage);\n // Clear active stream state on cleanup to prevent memory leak\n activeStreamRef.current = null;\n };\n }, [agent, useChatHelpers.setMessages, resume]);\n\n // Wrapper that sends tool result to server and optionally continues conversation.\n const addToolResultAndSendMessage: typeof useChatHelpers.addToolResult =\n 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 })\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 useChatHelpers.addToolResult(args);\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 useChatHelpers.sendMessage();\n return;\n }\n\n // Wait for all confirmations before sending\n const pending = pendingConfirmationsRef.current?.toolCallIds;\n if (!pending) {\n useChatHelpers.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 useChatHelpers.sendMessage();\n }\n }\n // If autoContinueAfterToolResult is true, server handles continuation\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 useChatHelpers.messages;\n }\n return useChatHelpers.messages.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 }, [useChatHelpers.messages, 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 useChatHelpers.messages) {\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 }, [useChatHelpers.messages]);\n\n return {\n ...useChatHelpers,\n messages: messagesWithToolResults,\n addToolResult: addToolResultAndSendMessage,\n clearHistory: () => {\n useChatHelpers.setMessages([]);\n setClientToolResults(new Map());\n processedToolCalls.current.clear();\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_CLEAR\n })\n );\n },\n setMessages: (\n messages: Parameters<typeof useChatHelpers.setMessages>[0]\n ) => {\n useChatHelpers.setMessages(messages);\n agent.send(\n JSON.stringify({\n messages: Array.isArray(messages) ? messages : [],\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n })\n );\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-react.js","names":["schemas: ClientToolSchema[]","tool","options","controller: ReadableStreamDefaultController","data: OutgoingMessage<ChatMessage>","customTransport: ChatTransport<ChatMessage>","body: Record<string, unknown>","headers: HeadersInit | undefined","credentials: RequestCredentials | undefined","api: string | undefined","lastMessage","toolResults: Array<{\n toolCallId: string;\n toolName: string;\n output: unknown;\n }>","toolOutput: unknown","addToolOutput","existingParts: ChatMessage[\"parts\"]","addToolResultAndSendMessage: typeof useChatHelpers.addToolResult"],"sources":["../src/ai-react.tsx"],"sourcesContent":["import { useChat, type UseChatOptions } from \"@ai-sdk/react\";\nimport { getToolName, isToolUIPart } from \"ai\";\nimport type {\n ChatInit,\n ChatTransport,\n JSONSchema7,\n Tool,\n UIMessage as Message,\n UIMessage\n} from \"ai\";\nimport { DefaultChatTransport } from \"ai\";\nimport { nanoid } from \"nanoid\";\nimport { use, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { OutgoingMessage } from \"./ai-types\";\nimport { MessageType } from \"./ai-types\";\nimport type { useAgent } from \"./react\";\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.\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 * Note: Uses `parameters` (JSONSchema7) rather than AI SDK's `inputSchema` (FlexibleSchema)\n * because client tools must be serializable for the wire format. Zod schemas cannot be\n * serialized, so we require raw JSON Schema here.\n *\n * @deprecated Use AI SDK's native tool pattern instead. Define tools on the server with\n * `tool()` from \"ai\", and handle client-side execution via the `onToolCall` callback\n * in `useAgentChat`. For tools requiring user approval, use `needsApproval` on the server.\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\n/**\n * Schema for a client tool sent to the server.\n * This is the wire format - what gets sent in the request body.\n * Must match the server-side ClientToolSchema type in ai-chat-agent.ts.\n *\n * @deprecated Use AI SDK's native tool pattern instead. Define tools on the server.\n */\nexport type ClientToolSchema = {\n /** Unique name for the tool */\n name: string;\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\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 * @param tools - Record of tool name to tool definition\n * @returns Array of tool schemas to send to server, or undefined if none\n *\n * @deprecated Use AI SDK's native tool pattern instead. Define tools on the server\n * and use `onToolCall` callback for client-side execution.\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\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 * Internal type for AI SDK transport\n * @internal\n */\ntype InternalPrepareResult = {\n body: Record<string, unknown>;\n headers?: HeadersInit;\n credentials?: RequestCredentials;\n api?: 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 */\n addToolOutput: (options: { toolCallId: string; output: unknown }) => void;\n}) => void | Promise<void>;\n\n/**\n * Options for the useAgentChat hook\n */\ntype UseAgentChatOptions<\n State,\n ChatMessage extends UIMessage = UIMessage\n> = Omit<UseChatParams<ChatMessage>, \"fetch\"> & {\n /** Agent connection from useAgent */\n agent: ReturnType<typeof useAgent<State>>;\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 * @deprecated Use `onToolCall` callback instead. Define tools on the server\n * and handle client-side execution via `onToolCall`.\n *\n * Tools that can be executed on the client.\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, the server automatically continues the conversation after\n * receiving client-side tool results, similar to how server-executed tools\n * work with maxSteps in streamText. The continuation is merged into the\n * same assistant message.\n *\n * When false (default), the client calls sendMessage() after tool results\n * to continue the conversation, which creates a new assistant message.\n *\n * @default false\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 * Callback to customize the request before sending messages.\n * Use this for advanced scenarios like adding custom headers or dynamic context.\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\nconst requestCache = new Map<string, Promise<Message[]>>();\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 if (!tools) return [];\n\n return Object.entries(tools)\n .filter(([_name, tool]) => !tool.execute)\n .map(([name]) => name);\n}\n\n/**\n * Return type 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};\n\nexport function useAgentChat<\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 const {\n agent,\n getInitialMessages,\n messages: optionsInitialMessages,\n onToolCall,\n experimental_automaticToolResolution,\n tools,\n toolsRequiringConfirmation: manualToolsRequiringConfirmation,\n autoContinueAfterToolResult = false, // Opt-in to server auto-continuation\n autoSendAfterAllConfirmationsResolved = true, // Legacy option for client-side batching\n resume = true, // Enable stream resumption by default\n prepareSendMessagesRequest,\n ...rest\n } = options;\n\n // Auto-detect tools requiring confirmation, or use manual override\n // @deprecated - this will be removed when toolsRequiringConfirmation is removed\n const toolsRequiringConfirmation =\n manualToolsRequiringConfirmation ?? detectToolsRequiringConfirmation(tools);\n\n // Keep a ref to always point to the latest onToolCall callback\n const onToolCallRef = useRef(onToolCall);\n onToolCallRef.current = onToolCall;\n\n const agentUrl = new URL(\n `${// @ts-expect-error we're using a protected _url property that includes query params\n ((agent._url as string | null) || agent._pkurl)\n ?.replace(\"ws://\", \"http://\")\n .replace(\"wss://\", \"https://\")}`\n );\n\n agentUrl.searchParams.delete(\"_pk\");\n const agentUrlString = agentUrl.toString();\n\n // we need to include agent.name in cache key to prevent collisions during agent switching.\n // The URL may be stale between updateProperties() and reconnect(), but agent.name\n // is updated synchronously, so each thread gets its own cache entry\n const initialMessagesCacheKey = `${agentUrlString}|${agent.agent ?? \"\"}|${agent.name ?? \"\"}`;\n\n // Keep a ref to always point to the latest agent instance\n const agentRef = useRef(agent);\n useEffect(() => {\n agentRef.current = agent;\n }, [agent]);\n\n async function defaultGetInitialMessagesFetch({\n url\n }: GetInitialMessagesOptions) {\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 initialMessagesPromise =\n getInitialMessages === null\n ? null\n : doGetInitialMessages(\n {\n agent: agent.agent,\n name: agent.name,\n url: agentUrlString\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 const aiFetch = useCallback(\n async (request: RequestInfo | URL, options: RequestInit = {}) => {\n const {\n method,\n keepalive,\n headers,\n body,\n redirect,\n integrity,\n signal,\n credentials,\n mode,\n referrer,\n referrerPolicy,\n window\n } = options;\n const id = nanoid(8);\n const abortController = new AbortController();\n let controller: ReadableStreamDefaultController;\n const currentAgent = agentRef.current;\n\n // Track this request ID so the onAgentMessage handler knows to skip it\n // (this tab's aiFetch listener handles its own stream)\n localRequestIdsRef.current.add(id);\n\n signal?.addEventListener(\"abort\", () => {\n currentAgent.send(\n JSON.stringify({\n id,\n type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL\n })\n );\n\n // NOTE - If we wanted to, we could preserve the \"interrupted\" message here, with the code below\n // However, I think it might be the responsibility of the library user to implement that behavior manually?\n // Reasoning: This code could be subject to collisions, as it \"force saves\" the messages we have locally\n //\n // agent.send(JSON.stringify({\n // type: MessageType.CF_AGENT_CHAT_MESSAGES,\n // messages: ... /* some way of getting current messages ref? */\n // }))\n abortController.abort();\n // Make sure to also close the stream (cf. https://github.com/cloudflare/agents-starter/issues/69)\n try {\n controller.close();\n } catch {\n // Stream may already be errored or closed\n }\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n });\n\n currentAgent.addEventListener(\n \"message\",\n (event) => {\n let data: OutgoingMessage<ChatMessage>;\n try {\n data = JSON.parse(event.data) as OutgoingMessage<ChatMessage>;\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return;\n }\n if (data.type === MessageType.CF_AGENT_USE_CHAT_RESPONSE) {\n if (data.id === id) {\n if (data.error) {\n controller.error(new Error(data.body));\n abortController.abort();\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n } else {\n // Only enqueue non-empty data to prevent JSON parsing errors\n if (data.body?.trim()) {\n controller.enqueue(\n new TextEncoder().encode(`data: ${data.body}\\n\\n`)\n );\n }\n if (data.done) {\n try {\n controller.close();\n } catch {\n // Stream may already be errored or closed\n }\n abortController.abort();\n // Clean up the request ID tracking\n localRequestIdsRef.current.delete(id);\n }\n }\n }\n }\n },\n { signal: abortController.signal }\n );\n\n const stream = new ReadableStream({\n start(c) {\n controller = c;\n },\n cancel(reason?: unknown) {\n console.warn(\n \"[agents/ai-react] cancelling stream\",\n id,\n reason || \"no reason\"\n );\n }\n });\n\n currentAgent.send(\n JSON.stringify({\n id,\n init: {\n body,\n credentials,\n headers,\n integrity,\n keepalive,\n method,\n mode,\n redirect,\n referrer,\n referrerPolicy,\n window\n },\n type: MessageType.CF_AGENT_USE_CHAT_REQUEST,\n url: request.toString()\n })\n );\n\n return new Response(stream);\n },\n []\n );\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 customTransport: ChatTransport<ChatMessage> = useMemo(\n () => ({\n sendMessages: async (\n sendMessageOptions: Parameters<\n typeof DefaultChatTransport.prototype.sendMessages\n >[0]\n ) => {\n // Extract schemas from tools with execute functions\n const clientToolSchemas = extractClientToolSchemas(toolsRef.current);\n\n const combinedPrepare =\n clientToolSchemas || prepareSendMessagesRequestRef.current\n ? async (\n prepareOptions: PrepareSendMessagesRequestOptions<ChatMessage>\n ): Promise<InternalPrepareResult> => {\n // Start with auto-extracted client tool schemas\n let body: Record<string, unknown> = {};\n let headers: HeadersInit | undefined;\n let credentials: RequestCredentials | undefined;\n let api: string | undefined;\n\n if (clientToolSchemas) {\n body = {\n id: prepareOptions.id,\n messages: prepareOptions.messages,\n trigger: prepareOptions.trigger,\n clientTools: clientToolSchemas\n };\n }\n\n // Apply prepareSendMessagesRequest callback for additional customization\n if (prepareSendMessagesRequestRef.current) {\n const userResult =\n await prepareSendMessagesRequestRef.current(prepareOptions);\n\n // user's callback can override or extend\n headers = userResult.headers;\n credentials = userResult.credentials;\n api = userResult.api;\n body = {\n ...body,\n ...(userResult.body ?? {})\n };\n }\n\n return { body, headers, credentials, api };\n }\n : undefined;\n\n const transport = new DefaultChatTransport<ChatMessage>({\n api: agentUrlString,\n fetch: aiFetch,\n prepareSendMessagesRequest: combinedPrepare\n });\n return transport.sendMessages(sendMessageOptions);\n },\n reconnectToStream: async () => null\n }),\n [agentUrlString, aiFetch]\n );\n\n const useChatHelpers = useChat<ChatMessage>({\n ...rest,\n messages: initialMessages,\n transport: customTransport,\n id: agent._pk\n // Note: We handle stream resumption via WebSocket instead of HTTP,\n // so we don't pass 'resume' to useChat. The onStreamResuming handler\n // automatically resumes active streams when the WebSocket reconnects.\n });\n\n const processedToolCalls = useRef(new Set<string>());\n const isResolvingToolsRef = useRef(false);\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(useChatHelpers.messages);\n messagesRef.current = useChatHelpers.messages;\n\n // Calculate pending confirmations for the latest assistant message\n const lastMessage =\n useChatHelpers.messages[useChatHelpers.messages.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\n // Automatic tool resolution effect.\n useEffect(() => {\n if (!experimental_automaticToolResolution) {\n return;\n }\n\n // Prevent re-entry while async operations are in progress\n if (isResolvingToolsRef.current) {\n return;\n }\n\n const lastMessage =\n useChatHelpers.messages[useChatHelpers.messages.length - 1];\n if (!lastMessage || lastMessage.role !== \"assistant\") {\n return;\n }\n\n const toolCalls = lastMessage.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 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 })\n );\n }\n\n // Also update local state via AI SDK for immediate UI feedback\n await Promise.all(\n toolResults.map((result) =>\n useChatHelpers.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\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 }\n })();\n }\n }\n }, [\n useChatHelpers.messages,\n experimental_automaticToolResolution,\n useChatHelpers.addToolResult,\n toolsRequiringConfirmation,\n autoContinueAfterToolResult\n ]);\n\n // Helper function to send tool output to server\n const sendToolOutputToServer = useCallback(\n (toolCallId: string, toolName: string, output: unknown) => {\n agentRef.current.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_TOOL_RESULT,\n toolCallId,\n toolName,\n output,\n autoContinue: autoContinueAfterToolResult\n })\n );\n\n setClientToolResults((prev) => new Map(prev).set(toolCallId, output));\n },\n [autoContinueAfterToolResult]\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 lastMessage =\n useChatHelpers.messages[useChatHelpers.messages.length - 1];\n if (!lastMessage || lastMessage.role !== \"assistant\") {\n return;\n }\n\n // Find tool calls in input-available state that haven't been processed\n const pendingToolCalls = lastMessage.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 // Create addToolOutput function for this specific tool call\n const addToolOutput = (opts: {\n toolCallId: string;\n output: unknown;\n }) => {\n sendToolOutputToServer(opts.toolCallId, toolName, opts.output);\n\n // Update local state via AI SDK\n useChatHelpers.addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output: opts.output\n });\n };\n\n // Call the onToolCall callback\n // The callback is responsible for calling addToolOutput when ready\n currentOnToolCall({\n toolCall: {\n toolCallId,\n toolName,\n input: part.input\n },\n addToolOutput\n });\n }\n }\n }, [\n useChatHelpers.messages,\n sendToolOutputToServer,\n useChatHelpers.addToolResult\n ]);\n\n /**\n * Contains the request ID, accumulated message parts, and a unique message ID.\n * Used for both resumed streams and real-time broadcasts from other tabs.\n */\n const activeStreamRef = useRef<{\n id: string;\n messageId: string;\n parts: ChatMessage[\"parts\"];\n } | null>(null);\n\n /**\n * Tracks request IDs initiated by this tab via aiFetch.\n * Used to distinguish local requests from broadcasts.\n */\n const localRequestIdsRef = useRef<Set<string>>(new Set());\n\n useEffect(() => {\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 useChatHelpers.setMessages([]);\n break;\n\n case MessageType.CF_AGENT_CHAT_MESSAGES:\n useChatHelpers.setMessages(data.messages);\n break;\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 useChatHelpers.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, append it\n return [...prevMessages, updatedMessage];\n });\n break;\n\n case MessageType.CF_AGENT_STREAM_RESUMING:\n if (!resume) return;\n // Clear any previous incomplete active stream to prevent memory leak\n activeStreamRef.current = null;\n // Initialize active stream state with unique ID\n activeStreamRef.current = {\n id: data.id,\n messageId: nanoid(),\n parts: []\n };\n // Send ACK to server - we're ready to receive chunks\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 case MessageType.CF_AGENT_USE_CHAT_RESPONSE: {\n // Skip if this is a response to a request this tab initiated\n // (handled by the aiFetch listener instead)\n if (localRequestIdsRef.current.has(data.id)) return;\n\n // For continuations, find the last assistant message ID to append to\n const isContinuation = data.continuation === true;\n\n // Initialize stream state for broadcasts from other tabs\n if (\n !activeStreamRef.current ||\n activeStreamRef.current.id !== data.id\n ) {\n let messageId = nanoid();\n let existingParts: ChatMessage[\"parts\"] = [];\n\n // For continuations, use the last assistant message's ID and parts\n if (isContinuation) {\n const currentMessages = messagesRef.current;\n for (let i = currentMessages.length - 1; i >= 0; i--) {\n if (currentMessages[i].role === \"assistant\") {\n messageId = currentMessages[i].id;\n existingParts = [...currentMessages[i].parts];\n break;\n }\n }\n }\n\n activeStreamRef.current = {\n id: data.id,\n messageId,\n parts: existingParts\n };\n }\n\n const activeMsg = activeStreamRef.current;\n\n if (data.body?.trim()) {\n try {\n const chunkData = JSON.parse(data.body);\n\n // Handle all chunk types for complete message reconstruction\n switch (chunkData.type) {\n case \"text-start\": {\n activeMsg.parts.push({\n type: \"text\",\n text: \"\",\n state: \"streaming\"\n });\n break;\n }\n case \"text-delta\": {\n const lastTextPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"text\");\n if (lastTextPart && lastTextPart.type === \"text\") {\n lastTextPart.text += chunkData.delta;\n } else {\n // Handle plain text responses (no text-start)\n activeMsg.parts.push({\n type: \"text\",\n text: chunkData.delta\n });\n }\n break;\n }\n case \"text-end\": {\n const lastTextPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"text\");\n if (lastTextPart && \"state\" in lastTextPart) {\n lastTextPart.state = \"done\";\n }\n break;\n }\n case \"reasoning-start\": {\n activeMsg.parts.push({\n type: \"reasoning\",\n text: \"\",\n state: \"streaming\"\n });\n break;\n }\n case \"reasoning-delta\": {\n const lastReasoningPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"reasoning\");\n if (\n lastReasoningPart &&\n lastReasoningPart.type === \"reasoning\"\n ) {\n lastReasoningPart.text += chunkData.delta;\n }\n break;\n }\n case \"reasoning-end\": {\n const lastReasoningPart = [...activeMsg.parts]\n .reverse()\n .find((p) => p.type === \"reasoning\");\n if (lastReasoningPart && \"state\" in lastReasoningPart) {\n lastReasoningPart.state = \"done\";\n }\n break;\n }\n case \"file\": {\n activeMsg.parts.push({\n type: \"file\",\n mediaType: chunkData.mediaType,\n url: chunkData.url\n });\n break;\n }\n case \"source-url\": {\n activeMsg.parts.push({\n type: \"source-url\",\n sourceId: chunkData.sourceId,\n url: chunkData.url,\n title: chunkData.title\n });\n break;\n }\n case \"source-document\": {\n activeMsg.parts.push({\n type: \"source-document\",\n sourceId: chunkData.sourceId,\n mediaType: chunkData.mediaType,\n title: chunkData.title,\n filename: chunkData.filename\n });\n break;\n }\n case \"tool-input-available\": {\n // Add tool call part when input is available\n activeMsg.parts.push({\n type: `tool-${chunkData.toolName}`,\n toolCallId: chunkData.toolCallId,\n toolName: chunkData.toolName,\n state: \"input-available\",\n input: chunkData.input\n } as ChatMessage[\"parts\"][number]);\n break;\n }\n case \"tool-output-available\": {\n // Update existing tool part with output using immutable pattern\n activeMsg.parts = activeMsg.parts.map((p) => {\n if (\n \"toolCallId\" in p &&\n p.toolCallId === chunkData.toolCallId &&\n \"state\" in p\n ) {\n return {\n ...p,\n state: \"output-available\",\n output: chunkData.output\n } as ChatMessage[\"parts\"][number];\n }\n return p;\n });\n break;\n }\n case \"step-start\": {\n activeMsg.parts.push({ type: \"step-start\" });\n break;\n }\n // Other chunk types (tool-input-start, tool-input-delta, etc.)\n // are intermediate states - the final state will be captured above\n }\n\n // Update messages with the partial response\n useChatHelpers.setMessages((prevMessages: ChatMessage[]) => {\n if (!activeMsg) return prevMessages;\n\n const existingIdx = prevMessages.findIndex(\n (m) => m.id === activeMsg.messageId\n );\n\n const partialMessage = {\n id: activeMsg.messageId,\n role: \"assistant\" as const,\n parts: [...activeMsg.parts]\n } as unknown as ChatMessage;\n\n if (existingIdx >= 0) {\n const updated = [...prevMessages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...prevMessages, partialMessage];\n });\n } catch (parseError) {\n // Log corrupted chunk for debugging - could indicate data loss\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) // Truncate for logging\n );\n }\n }\n\n // Clear on completion or error\n if (data.done || data.error) {\n activeStreamRef.current = null;\n }\n break;\n }\n }\n }\n\n agent.addEventListener(\"message\", onAgentMessage);\n return () => {\n agent.removeEventListener(\"message\", onAgentMessage);\n // Clear active stream state on cleanup to prevent memory leak\n activeStreamRef.current = null;\n };\n }, [agent, useChatHelpers.setMessages, resume]);\n\n // Wrapper that sends tool result to server and optionally continues conversation.\n const addToolResultAndSendMessage: typeof useChatHelpers.addToolResult =\n 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 })\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 useChatHelpers.addToolResult(args);\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 useChatHelpers.sendMessage();\n return;\n }\n\n // Wait for all confirmations before sending\n const pending = pendingConfirmationsRef.current?.toolCallIds;\n if (!pending) {\n useChatHelpers.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 useChatHelpers.sendMessage();\n }\n }\n // If autoContinueAfterToolResult is true, server handles continuation\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 useChatHelpers.messages;\n }\n return useChatHelpers.messages.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 }, [useChatHelpers.messages, 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 useChatHelpers.messages) {\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 }, [useChatHelpers.messages]);\n\n // Create addToolOutput function for external use\n const addToolOutput = useCallback(\n (opts: { toolCallId: string; toolName?: string; output: unknown }) => {\n const toolName = opts.toolName ?? \"\";\n sendToolOutputToServer(opts.toolCallId, toolName, opts.output);\n\n // Update local state via AI SDK\n useChatHelpers.addToolResult({\n tool: toolName,\n toolCallId: opts.toolCallId,\n output: opts.output\n });\n },\n [sendToolOutputToServer, useChatHelpers.addToolResult]\n );\n\n return {\n ...useChatHelpers,\n messages: messagesWithToolResults,\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 clearHistory: () => {\n useChatHelpers.setMessages([]);\n setClientToolResults(new Map());\n processedToolCalls.current.clear();\n agent.send(\n JSON.stringify({\n type: MessageType.CF_AGENT_CHAT_CLEAR\n })\n );\n },\n setMessages: (\n messages: Parameters<typeof useChatHelpers.setMessages>[0]\n ) => {\n useChatHelpers.setMessages(messages);\n agent.send(\n JSON.stringify({\n messages: Array.isArray(messages) ? messages : [],\n type: MessageType.CF_AGENT_CHAT_MESSAGES\n })\n );\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -310,7 +310,7 @@ declare class MCPClientConnection {
|
|
|
310
310
|
*/
|
|
311
311
|
getTransport(
|
|
312
312
|
transportType: BaseTransportType
|
|
313
|
-
):
|
|
313
|
+
): StreamableHTTPClientTransport | SSEClientTransport;
|
|
314
314
|
private tryConnect;
|
|
315
315
|
private _capabilityErrorHandler;
|
|
316
316
|
}
|
|
@@ -831,4 +831,4 @@ export {
|
|
|
831
831
|
MCPClientManager as t,
|
|
832
832
|
MCPConnectionState as u
|
|
833
833
|
};
|
|
834
|
-
//# sourceMappingURL=client-
|
|
834
|
+
//# sourceMappingURL=client-DFotUKH_.d.ts.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
h as TransportType,
|
|
4
4
|
t as MCPClientManager,
|
|
5
5
|
u as MCPConnectionState
|
|
6
|
-
} from "./client-
|
|
6
|
+
} from "./client-DFotUKH_.js";
|
|
7
7
|
import { t as Observability } from "./index-DLuxm_9W.js";
|
|
8
8
|
import { n as MessageType } from "./ai-types-0OnT3FHg.js";
|
|
9
9
|
import {
|
|
@@ -574,4 +574,4 @@ export {
|
|
|
574
574
|
callable as x,
|
|
575
575
|
StreamingResponse as y
|
|
576
576
|
};
|
|
577
|
-
//# sourceMappingURL=index-
|
|
577
|
+
//# sourceMappingURL=index-CT2tCrLr.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as AgentEmail } from "./context-DcbQ8o7k.js";
|
|
2
|
-
import { h as TransportType } from "./client-
|
|
2
|
+
import { h as TransportType } from "./client-DFotUKH_.js";
|
|
3
3
|
import "./ai-types-0OnT3FHg.js";
|
|
4
4
|
import {
|
|
5
5
|
C as createCatchAllEmailResolver,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
w as createHeaderBasedEmailResolver,
|
|
32
32
|
x as callable,
|
|
33
33
|
y as StreamingResponse
|
|
34
|
-
} from "./index-
|
|
34
|
+
} from "./index-CT2tCrLr.js";
|
|
35
35
|
export {
|
|
36
36
|
Agent,
|
|
37
37
|
AgentContext,
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MCPConnectionResult, c as RegisterServerOptions, i as MCPClientOAuthResult, l as getNamespacedData, n as MCPClientManagerOptions, o as MCPDiscoverResult, r as MCPClientOAuthCallbackConfig, s as MCPServerOptions, t as MCPClientManager } from "../client-
|
|
1
|
+
import { a as MCPConnectionResult, c as RegisterServerOptions, i as MCPClientOAuthResult, l as getNamespacedData, n as MCPClientManagerOptions, o as MCPDiscoverResult, r as MCPClientOAuthCallbackConfig, s as MCPServerOptions, t as MCPClientManager } from "../client-DFotUKH_.js";
|
|
2
2
|
export { MCPClientManager, MCPClientManagerOptions, MCPClientOAuthCallbackConfig, MCPClientOAuthResult, MCPConnectionResult, MCPDiscoverResult, MCPServerOptions, RegisterServerOptions, getNamespacedData };
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../context-DcbQ8o7k.js";
|
|
2
|
-
import { a as MCPConnectionResult, d as BaseTransportType, f as CORSOptions, i as MCPClientOAuthResult, m as ServeOptions, o as MCPDiscoverResult, p as MaybePromise, r as MCPClientOAuthCallbackConfig, s as MCPServerOptions } from "../client-
|
|
2
|
+
import { a as MCPConnectionResult, d as BaseTransportType, f as CORSOptions, i as MCPClientOAuthResult, m as ServeOptions, o as MCPDiscoverResult, p as MaybePromise, r as MCPClientOAuthCallbackConfig, s as MCPServerOptions } from "../client-DFotUKH_.js";
|
|
3
3
|
import "../ai-types-0OnT3FHg.js";
|
|
4
|
-
import { o as Connection, s as ConnectionContext, t as Agent } from "../index-
|
|
4
|
+
import { o as Connection, s as ConnectionContext, t as Agent } from "../index-CT2tCrLr.js";
|
|
5
5
|
import { SSEClientTransport, SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
6
|
import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
7
|
import { ElicitRequest, ElicitRequestSchema, ElicitResult, ElicitResult as ElicitResult$1, JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js";
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./context-DcbQ8o7k.js";
|
|
2
|
-
import "./client-
|
|
2
|
+
import "./client-DFotUKH_.js";
|
|
3
3
|
import "./ai-types-0OnT3FHg.js";
|
|
4
|
-
import { p as MCPServersState, t as Agent } from "./index-
|
|
4
|
+
import { p as MCPServersState, t as Agent } from "./index-CT2tCrLr.js";
|
|
5
5
|
import { n as RPCMethod, t as Method } from "./serializable-Crsj26mx.js";
|
|
6
6
|
import { i as StreamOptions } from "./client-CdM5I962.js";
|
|
7
7
|
import { PartySocket } from "partysocket";
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"durable objects"
|
|
10
10
|
],
|
|
11
11
|
"type": "module",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.3.0",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"repository": {
|
|
15
15
|
"directory": "packages/agents",
|
|
@@ -37,21 +37,21 @@
|
|
|
37
37
|
"zod-to-ts": "^2.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@ai-sdk/openai": "
|
|
41
|
-
"@ai-sdk/react": "
|
|
40
|
+
"@ai-sdk/openai": "^3.0.0",
|
|
41
|
+
"@ai-sdk/react": "^3.0.0",
|
|
42
42
|
"@cloudflare/workers-oauth-provider": "^0.2.2",
|
|
43
43
|
"@types/react": "^19.2.7",
|
|
44
44
|
"@types/yargs": "^17.0.35",
|
|
45
|
-
"ai": "
|
|
45
|
+
"ai": "^6.0.0",
|
|
46
46
|
"react": "^19.2.3",
|
|
47
47
|
"vitest-browser-react": "^1.0.1",
|
|
48
48
|
"x402": "^1.0.1",
|
|
49
49
|
"zod": "^4.1.8"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@ai-sdk/openai": "
|
|
53
|
-
"@ai-sdk/react": "
|
|
54
|
-
"ai": "
|
|
52
|
+
"@ai-sdk/openai": "^3.0.0",
|
|
53
|
+
"@ai-sdk/react": "^3.0.0",
|
|
54
|
+
"ai": "^6.0.0",
|
|
55
55
|
"react": "^19.0.0",
|
|
56
56
|
"viem": ">=2.0.0",
|
|
57
57
|
"x402": "^0.7.1",
|