agents
Version:
A home for your AI agents
266 lines (265 loc) • 9.85 kB
JavaScript
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