UNPKG

agents

Version:

A home for your AI agents

266 lines (265 loc) • 9.85 kB
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../../classPrivateFieldGet2-DZBYAB34.js"; import { t as _classPrivateMethodInitSpec } from "../../../classPrivateMethodInitSpec-qMjJ6sHQ.js"; import { a as toTranscriptMessage, s as toUIMessageChunk } from "../../../turns-DCV3ycDl.js"; //#region src/experimental/channels/web/ai-sdk.ts var _client = /* @__PURE__ */ new WeakMap(); var _tools = /* @__PURE__ */ new WeakMap(); var _WebChannelChatTransport_brand = /* @__PURE__ */ new WeakSet(); /** * An AI SDK `ChatTransport` over a Web Channel connection, for `useChat` * and `@ai-sdk/tui`. Each call streams one turn until it settles, or until * it waits for input the transport cannot give. * * The agent owns the transcript, so only the newest user message or the * newest answers to pending tool calls are sent; the rest of `messages` is * ignored. Regenerating a message is not supported. */ var WebChannelChatTransport = class { constructor(client, options = {}) { _classPrivateMethodInitSpec(this, _WebChannelChatTransport_brand); _classPrivateFieldInitSpec(this, _client, void 0); _classPrivateFieldInitSpec(this, _tools, void 0); _classPrivateFieldSet2(_client, this, client); _classPrivateFieldSet2(_tools, this, options.tools); } async sendMessages({ trigger, messages, abortSignal }) { if (trigger === "regenerate-message") throw new Error("Channels cannot regenerate a message. Send a new message instead."); await _assertClassBrand(_WebChannelChatTransport_brand, this, _connected).call(this); const last = messages.at(-1); if (last?.role === "user") { const id = crypto.randomUUID(); const message = { ...toTranscriptMessage(last), id, role: "user" }; return _assertClassBrand(_WebChannelChatTransport_brand, this, _follow).call(this, { startedBy: id, events: [{ type: "message", eventId: id, message, ..._assertClassBrand(_WebChannelChatTransport_brand, this, _clientTools).call(this) }], signal: abortSignal }); } const answers = last ? _assertClassBrand(_WebChannelChatTransport_brand, this, _answers).call(this, last) : []; if (answers.length === 0) throw new Error("Nothing to send: the last message is neither a user message nor an answer to a pending tool call."); return _assertClassBrand(_WebChannelChatTransport_brand, this, _follow).call(this, { turnId: answers[0].turnId, events: answers, signal: abortSignal }); } async reconnectToStream() { await _assertClassBrand(_WebChannelChatTransport_brand, this, _connected).call(this); const running = _classPrivateFieldGet2(_client, this).state.turns.find((turn) => turn.status === "running"); if (running?.status !== "running") return null; return _assertClassBrand(_WebChannelChatTransport_brand, this, _follow).call(this, { turnId: running.turnId, responseId: running.responseId, events: [] }); } }; function _connected() { if (_classPrivateFieldGet2(_client, this).state.connected) return Promise.resolve(); return new Promise((resolve) => { const off = _classPrivateFieldGet2(_client, this).subscribe((state) => { if (!state.connected) return; off(); resolve(); }); }); } function _clientTools() { return _classPrivateFieldGet2(_tools, this) ? { clientTools: Object.keys(_classPrivateFieldGet2(_tools, this)).map((name) => ({ name })) } : {}; } /** Answers in the message to tool calls the agent is waiting on. */ function _answers(message) { const answers = []; for (const part of toTranscriptMessage(message).parts) { if (part.type !== "tool") continue; const pending = _assertClassBrand(_WebChannelChatTransport_brand, this, _pending).call(this, part.toolCallId); if (!pending) continue; const { turnId, call } = pending; if (call.state === "approval-requested" && call.approval && part.state === "approval-responded" && part.approval?.approved !== void 0) answers.push({ type: "approval-response", turnId, approvalId: call.approval.id, approved: part.approval.approved, ...part.approval.reason !== void 0 && { reason: part.approval.reason } }); else if (call.state === "input-available" && (part.state === "output-available" || part.state === "output-error")) answers.push({ type: "tool-result", turnId, toolCallId: part.toolCallId, result: part.state === "output-available" ? { ok: true, output: part.output ?? null } : { ok: false, errorText: part.errorText }, ..._assertClassBrand(_WebChannelChatTransport_brand, this, _clientTools).call(this) }); } return answers; } /** A tool call waiting for input, and the turn waiting on it. */ function _pending(toolCallId) { const { messages, turns } = _classPrivateFieldGet2(_client, this).state; for (const message of messages) { const call = message.parts.find((part) => part.type === "tool" && part.toolCallId === toolCallId); if (!call) continue; const turn = turns.find((t) => t.status === "settled" && t.outcome === "awaiting-input" && t.messageIds.includes(message.id)); return turn && { turnId: turn.turnId, call }; } } /** Calls in a settled turn that this transport can run for its participant. */ function _runnable(turn) { const { messages, you } = _classPrivateFieldGet2(_client, this).state; if (turn.status !== "settled" || !_classPrivateFieldGet2(_tools, this) || !you) return []; return messages.filter((message) => turn.messageIds.includes(message.id)).flatMap((message) => message.parts).filter((part) => part.type === "tool" && part.state === "input-available" && part.owner === you.id && Object.hasOwn(_classPrivateFieldGet2(_tools, this) ?? {}, part.toolName)); } function _follow(follow) { const client = _classPrivateFieldGet2(_client, this); let { turnId, responseId } = follow; let responseEnded = responseId === void 0; let settled; let answering = false; let offline = false; let done = false; const cleanup = []; return new ReadableStream({ start: (controller) => { const enqueue = (chunk) => { if (!done) controller.enqueue(chunk); }; const finish = (...last) => { if (done) return; for (const chunk of last) controller.enqueue(chunk); done = true; for (const off of cleanup) off(); controller.close(); }; const fail = (error) => finish({ type: "error", errorText: error instanceof Error ? error.message : String(error) }); const send = (event) => client.send(event).catch((error) => fail(error)); const answer = async (calls) => { answering = true; settled = void 0; for (const call of calls) { const run = _classPrivateFieldGet2(_tools, this)?.[call.toolName]; let result; try { result = { ok: true, output: await run?.(call.input ?? null) ?? null }; enqueue({ type: "tool-output-available", toolCallId: call.toolCallId, output: result.output }); } catch (error) { result = { ok: false, errorText: String(error) }; enqueue({ type: "tool-output-error", toolCallId: call.toolCallId, errorText: result.errorText }); } if (!turnId) return; await send({ type: "tool-result", turnId, toolCallId: call.toolCallId, result, ..._assertClassBrand(_WebChannelChatTransport_brand, this, _clientTools).call(this) }); } answering = false; }; const settle = () => { if (!settled || settled.status !== "settled") return; if (!responseEnded || answering) return; switch (settled.outcome) { case "awaiting-input": { const calls = _assertClassBrand(_WebChannelChatTransport_brand, this, _runnable).call(this, settled); if (calls.length > 0) { answer(calls); return; } return finish({ type: "finish" }); } case "completed": return finish({ type: "finish" }); case "aborted": return finish({ type: "abort" }); case "failed": return finish({ type: "error", errorText: settled.error ?? "The turn failed." }); } }; enqueue(follow.startedBy ? { type: "start", messageId: crypto.randomUUID() } : { type: "start" }); if (responseId) for (const chunk of client.chunksOf(responseId) ?? []) enqueue(toUIMessageChunk(chunk)); cleanup.push(client.onActivity((activity) => { if (activity.type === "turn") { const turn = activity.turn; if (!turnId && turn.startedBy === follow.startedBy) turnId = turn.turnId; if (turn.turnId !== turnId) return; if (turn.status === "running" && turn.responseId !== responseId) { responseId = turn.responseId; responseEnded = false; } settled = turn.status === "settled" ? turn : void 0; return settle(); } if (activity.responseId !== responseId) return; if (activity.type === "chunks") for (const chunk of activity.chunks) enqueue(toUIMessageChunk(chunk)); else { responseEnded = true; settle(); } }), client.subscribe((state) => { if (!state.connected) { offline = true; return; } if (!offline) return; offline = false; if (turnId && !state.turns.some((t) => t.turnId === turnId)) finish({ type: "finish" }); })); const abort = () => { if (turnId) client.send({ type: "cancel", turnId }).catch(() => {}); finish(); }; if (follow.signal?.aborted) return abort(); follow.signal?.addEventListener("abort", abort, { once: true }); cleanup.push(() => follow.signal?.removeEventListener("abort", abort)); for (const event of follow.events) send(event); }, cancel: () => { done = true; for (const off of cleanup) off(); } }); } //#endregion export { WebChannelChatTransport }; //# sourceMappingURL=ai-sdk.js.map