agents
Version:
A home for your AI agents
195 lines (194 loc) • 6.69 kB
JavaScript
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