UNPKG

agents

Version:

A home for your AI agents

195 lines (194 loc) • 6.69 kB
import { t as CHAT_MESSAGE_TYPES } from "./protocol-eXeJxhgu.js"; import { jsonSchema, tool } from "ai"; //#region src/chat/client-tools.ts /** * Converts client tool schemas to AI SDK tool format. * * By default these tools have no `execute` function — when the AI model calls * them, the tool call is sent back to the client for execution. * * When `options.execute` is provided, each tool is built WITH an `execute` that * delegates to it. This is used by the RPC path (e.g. a parent agent driving a * Think sub-agent) so the model's client-tool call is resolved inline within * the same turn. * * @param clientTools - Array of tool schemas from the client * @param options - Optional `execute` delegate to run the tools inline * @returns Record of AI SDK tools that can be spread into your tools object */ function createToolsFromClientSchemas(clientTools, options) { if (!clientTools || clientTools.length === 0) return {}; const seenNames = /* @__PURE__ */ new Set(); for (const t of clientTools) { if (seenNames.has(t.name)) console.warn(`[createToolsFromClientSchemas] Duplicate tool name "${t.name}" found. Later definitions will override earlier ones.`); seenNames.add(t.name); } const execute = options?.execute; const createTool = tool; return Object.fromEntries(clientTools.map((t) => [t.name, createTool({ description: typeof t.description === "function" ? "" : t.description ?? "", inputSchema: jsonSchema(t.parameters ?? { type: "object" }), ...execute ? { execute: (input, executeOptions) => execute({ toolName: t.name, input, toolCallId: executeOptions?.toolCallId ?? "" }) } : {} })])); } //#endregion //#region src/chat/parse-protocol.ts /** * Protocol Message Parser — typed parsing of cf_agent_chat_* WebSocket messages. * * Parses raw WebSocket messages into a discriminated union of protocol events. * Both AIChatAgent and Think can use this instead of manual JSON.parse + type checking. */ /** * Parse a raw WebSocket message string into a typed protocol event. * * Returns `null` if the message is not valid JSON or not a recognized * protocol message type. Callers should fall through to the user's * `onMessage` handler when `null` is returned. * * @example * ```typescript * const event = parseProtocolMessage(rawMessage); * if (!event) return userOnMessage(connection, rawMessage); * * switch (event.type) { * case "chat-request": { ... } * case "clear": { ... } * case "tool-result": { ... } * } * ``` */ function parseProtocolMessage(raw) { let data; try { data = JSON.parse(raw); } catch { return null; } const wireType = data.type; if (!wireType) return null; switch (wireType) { case CHAT_MESSAGE_TYPES.USE_CHAT_REQUEST: return { type: "chat-request", id: data.id, init: data.init ?? {} }; case CHAT_MESSAGE_TYPES.CHAT_CLEAR: return { type: "clear" }; case CHAT_MESSAGE_TYPES.CHAT_REQUEST_CANCEL: return { type: "cancel", id: data.id }; case CHAT_MESSAGE_TYPES.TOOL_RESULT: return { type: "tool-result", toolCallId: data.toolCallId, toolName: data.toolName ?? "", output: data.output, state: data.state, errorText: data.errorText, autoContinue: data.autoContinue, clientTools: data.clientTools }; case CHAT_MESSAGE_TYPES.TOOL_APPROVAL: return { type: "tool-approval", toolCallId: data.toolCallId, approved: data.approved, autoContinue: data.autoContinue }; case CHAT_MESSAGE_TYPES.STREAM_RESUME_REQUEST: return { type: "stream-resume-request", ...typeof data.probeId === "string" ? { probeId: data.probeId } : {} }; case CHAT_MESSAGE_TYPES.STREAM_RESUME_ACK: return { type: "stream-resume-ack", id: data.id }; case CHAT_MESSAGE_TYPES.CHAT_MESSAGES: return { type: "messages", messages: data.messages ?? [] }; default: return null; } } //#endregion //#region src/chat/stall-watchdog.ts /** * Shared inactivity watchdog for UI-message streams. * * A model/transport stream can park indefinitely without ever throwing (a hung * provider, a wedged transport). Left unguarded, the consumer read-loop waits * forever. {@link iterateWithStallWatchdog} wraps such a stream so that a gap of * `timeoutMs` between chunks aborts the upstream and throws * {@link ChatStreamStalledError}, letting the consumer route the stall into * bounded recovery (#1626) — a transient hang is retried within the existing * recovery budget — while genuine in-band errors stay terminal. * * @internal Sibling-package support for `@cloudflare/ai-chat` and * `@cloudflare/think`, not a public API. See * `design/rfc-chat-recovery-foundation.md`. */ /** * Thrown by {@link iterateWithStallWatchdog} when the inactivity watchdog fires * (a model/transport stream that parks without ever throwing). Distinct from * in-band model/stream errors so the read-loop catch can route a stall into * bounded recovery (#1626) — a transient hang is retried within the existing * recovery budget — while genuine errors stay terminal. */ var ChatStreamStalledError = class extends Error { constructor(message) { super(message); this.isChatStreamStall = true; this.name = "ChatStreamStalledError"; } }; /** * Wrap a UI-message stream with an inactivity watchdog. If no chunk arrives * within `timeoutMs`, `onStall` runs (aborting the upstream model stream) and * the iterator throws, so the consumer loop exits with a terminal error * instead of parking forever on a hung provider/transport. `timeoutMs <= 0` * passes the source through untouched. */ async function* iterateWithStallWatchdog(source, timeoutMs, onStall) { if (!(timeoutMs > 0)) { yield* source; return; } const iterator = source[Symbol.asyncIterator](); let selfAborted = false; try { while (true) { let timer; let stalled = false; const stall = new Promise((_, reject) => { timer = setTimeout(() => { stalled = true; reject(new ChatStreamStalledError(`Chat stream stalled: no activity for ${timeoutMs}ms; the turn was aborted by the stall watchdog.`)); }, timeoutMs); }); const nextPromise = iterator.next(); nextPromise.catch(() => {}); let next; try { next = await Promise.race([nextPromise, stall]); } catch (err) { if (stalled) { selfAborted = true; onStall(); } throw err; } finally { if (timer !== void 0) clearTimeout(timer); } if (next.done) return; yield next.value; } } finally { if (!selfAborted) await iterator.return?.(void 0).catch(() => {}); } } //#endregion export { createToolsFromClientSchemas as i, iterateWithStallWatchdog as n, parseProtocolMessage as r, ChatStreamStalledError as t }; //# sourceMappingURL=stall-watchdog-BpRF2uwG.js.map