agents
Version:
A home for your AI agents
605 lines (604 loc) • 23.6 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 { t as LifecycleCapability } from "../../capability-DBAKc6UZ.js";
import { a as toTranscriptMessage, i as toResponseChunks, n as awaitsInput, o as toUIMessage, r as toResponseChunk, s as toUIMessageChunk, t as answerToolCall } from "../../turns-DCV3ycDl.js";
import { convertToModelMessages, jsonSchema, stepCountIs, streamText, toUIMessageStream, tool } from "ai";
//#region src/harness/ai-sdk/harness.ts
const PREFIX = "ai-sdk-harness:";
const WAKE_FN = "wake";
const DEFAULT_SESSION = "main";
const HEARTBEAT_MS = 3e4;
var _options = /* @__PURE__ */ new WeakMap();
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
var _runs = /* @__PURE__ */ new WeakMap();
var _driving = /* @__PURE__ */ new WeakMap();
var _watchers = /* @__PURE__ */ new WeakMap();
var _waiters = /* @__PURE__ */ new WeakMap();
var _AiSdkHarness_brand = /* @__PURE__ */ new WeakSet();
/**
* An agent harness around the AI SDK's `streamText`, hosted in a Durable
* Object: sessions, their transcripts, and a queue of operations, all in the
* object's storage. One operation runs at a time per session; a run is one
* `streamText` call over the session's transcript.
*
* The AI SDK keeps no state of its own, so a run that an eviction cuts short
* starts again from the saved transcript, and its unsaved output is lost.
* Steering is not supported: `whenBusy: "steer"` queues like a follow-up.
*
* @experimental The API may change between releases.
*/
var AiSdkHarness = class extends LifecycleCapability {
constructor(options) {
super("ai-sdk-harness");
_classPrivateMethodInitSpec(this, _AiSdkHarness_brand);
_classPrivateFieldInitSpec(this, _options, void 0);
_classPrivateFieldInitSpec(this, _heartbeatMs, void 0);
_classPrivateFieldInitSpec(this, _runs, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _driving, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _watchers, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _waiters, /* @__PURE__ */ new Map());
_classPrivateFieldSet2(_options, this, options);
_classPrivateFieldSet2(_heartbeatMs, this, options.heartbeatMs ?? HEARTBEAT_MS);
this.sessions = {
create: async () => _assertClassBrand(_AiSdkHarness_brand, this, _create).call(this, { id: crypto.randomUUID() }),
fork: async (from) => {
_assertClassBrand(_AiSdkHarness_brand, this, _ensure).call(this, from);
const session = _assertClassBrand(_AiSdkHarness_brand, this, _create).call(this, {
id: crypto.randomUUID(),
parent: from
});
for (const message of _assertClassBrand(_AiSdkHarness_brand, this, _messages).call(this, from)) _assertClassBrand(_AiSdkHarness_brand, this, _putMessage).call(this, session.id, message);
return session;
},
list: async () => {
_assertClassBrand(_AiSdkHarness_brand, this, _ensure).call(this, DEFAULT_SESSION);
return [..._get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).list({ prefix: `${PREFIX}s:` })].map(([, { id, parent }]) => ({
id,
...parent !== void 0 && { parent },
busy: _assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, id).length > 0
}));
}
};
}
async onStart() {
const sessions = [..._get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).list({ prefix: `${PREFIX}s:` })];
for (const [, session] of sessions) if (_assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, session.id).length > 0) await _assertClassBrand(_AiSdkHarness_brand, this, _wake).call(this, session.id);
}
async onJob(context) {
if (context.job.fn !== WAKE_FN) return;
const session = context.job.payload?.session;
if (typeof session !== "string") return;
if (!_classPrivateFieldGet2(_driving, this).has(session) && _assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, session).length === 0) return;
this.lifecycle.trackAlarmWork(_assertClassBrand(_AiSdkHarness_brand, this, _drive).call(this, session));
return { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
}
session(id = DEFAULT_SESSION) {
return {
id,
submit: (input, options) => _assertClassBrand(_AiSdkHarness_brand, this, _submit).call(this, id, input, options),
abort: (operationId) => _assertClassBrand(_AiSdkHarness_brand, this, _abort).call(this, id, operationId),
wait: (operationId, signal) => _assertClassBrand(_AiSdkHarness_brand, this, _wait).call(this, id, operationId, signal),
reset: (handoff) => _assertClassBrand(_AiSdkHarness_brand, this, _reset).call(this, id, handoff),
watch: async () => _assertClassBrand(_AiSdkHarness_brand, this, _watch).call(this, id)
};
}
};
async function _submit(session, input, options = {}) {
_assertClassBrand(_AiSdkHarness_brand, this, _ensure).call(this, session);
const operationId = options.operationId ?? crypto.randomUUID();
if (_assertClassBrand(_AiSdkHarness_brand, this, _operation).call(this, session, operationId)) return {
operationId,
session,
accepted: false
};
const op = {
operationId,
seq: _assertClassBrand(_AiSdkHarness_brand, this, _nextSeq).call(this, session),
input,
status: "queued"
};
_assertClassBrand(_AiSdkHarness_brand, this, _saveOperation).call(this, session, op);
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "operation",
status: {
operationId,
status: "queued"
}
}]);
await _assertClassBrand(_AiSdkHarness_brand, this, _wake).call(this, session);
_assertClassBrand(_AiSdkHarness_brand, this, _drive).call(this, session);
return {
operationId,
session,
accepted: true
};
}
async function _abort(session, operationId) {
const run = _classPrivateFieldGet2(_runs, this).get(session);
const targets = operationId ? [_assertClassBrand(_AiSdkHarness_brand, this, _operation).call(this, session, operationId)].filter((op) => op !== void 0 && isPending(op)) : _assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, session);
for (const op of targets) if (run?.operations.includes(op.operationId)) run.abort.abort();
else _assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, {
status: "unanswered",
reason: "withdrawn"
});
return targets.length > 0;
}
async function _wait(session, operationId, signal) {
const op = _assertClassBrand(_AiSdkHarness_brand, this, _operation).call(this, session, operationId);
if (!op) return {
operationId,
session,
status: "unanswered",
reason: "not_found"
};
const settled = isPending(op) ? await new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(signal.reason);
return;
}
const key = waiterKey(session, operationId);
const waiters = _classPrivateFieldGet2(_waiters, this).get(key) ?? /* @__PURE__ */ new Set();
_classPrivateFieldGet2(_waiters, this).set(key, waiters);
const done = (result) => {
signal?.removeEventListener("abort", aborted);
resolve(result);
};
const aborted = () => {
waiters.delete(done);
reject(signal?.reason);
};
waiters.add(done);
signal?.addEventListener("abort", aborted, { once: true });
}) : op;
return settled.status === "done" ? {
operationId,
session,
status: "done",
...settled.text !== void 0 && { text: settled.text }
} : {
operationId,
session,
status: "unanswered",
...settled.reason !== void 0 && { reason: settled.reason }
};
}
async function _reset(session, handoff) {
_assertClassBrand(_AiSdkHarness_brand, this, _ensure).call(this, session);
await _assertClassBrand(_AiSdkHarness_brand, this, _abort).call(this, session);
await _classPrivateFieldGet2(_driving, this).get(session);
for (const [key] of _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).list({ prefix: messagePrefix(session) })) _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).delete(key);
const events = [{ type: "reset" }];
if (handoff) {
const note = {
id: crypto.randomUUID(),
role: "system",
parts: [{
type: "text",
text: handoff
}]
};
_assertClassBrand(_AiSdkHarness_brand, this, _putMessage).call(this, session, note);
events.push({
type: "message",
message: _assertClassBrand(_AiSdkHarness_brand, this, _transcript).call(this, note)
});
}
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, events);
}
function _watch(session) {
_assertClassBrand(_AiSdkHarness_brand, this, _ensure).call(this, session);
const run = _classPrivateFieldGet2(_runs, this).get(session);
const state = {
messages: _assertClassBrand(_AiSdkHarness_brand, this, _messages).call(this, session).map((m) => _assertClassBrand(_AiSdkHarness_brand, this, _transcript).call(this, m)),
pending: _assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, session).map((op) => ({
operationId: op.operationId,
status: op.status === "placed" ? "placed" : "queued"
})),
...run && { run: {
operations: [...run.operations],
...run.partial.length > 0 && { partial: [...run.partial] }
} }
};
const watchers = _classPrivateFieldGet2(_watchers, this).get(session) ?? /* @__PURE__ */ new Set();
_classPrivateFieldGet2(_watchers, this).set(session, watchers);
const watcher = new Watcher(state, () => watchers.delete(watcher));
watchers.add(watcher);
return watcher;
}
/** Run the session's operations in order until none are left. */
function _drive(session) {
let driving = _classPrivateFieldGet2(_driving, this).get(session);
if (!driving) {
driving = (async () => {
try {
for (;;) {
const next = _assertClassBrand(_AiSdkHarness_brand, this, _pending).call(this, session)[0];
if (!next) return;
await _assertClassBrand(_AiSdkHarness_brand, this, _run).call(this, session, next);
}
} finally {
_classPrivateFieldGet2(_driving, this).delete(session);
}
})();
_classPrivateFieldGet2(_driving, this).set(session, driving);
}
return driving;
}
async function _run(session, op) {
const { operationId } = op;
let owner = "parts" in op.input ? op.input.from?.participantId : void 0;
if (op.status === "queued") {
let placed;
if ("parts" in op.input) placed = {
id: op.input.messageId ?? operationId,
role: "user",
parts: op.input.parts.map(toUIPart)
};
else {
const answered = _assertClassBrand(_AiSdkHarness_brand, this, _answer).call(this, session, op.input);
if (typeof answered === "string") {
_assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, {
status: "unanswered",
reason: answered
});
return;
}
placed = answered;
}
_assertClassBrand(_AiSdkHarness_brand, this, _putMessage).call(this, session, placed);
op = {
...op,
status: "placed"
};
_assertClassBrand(_AiSdkHarness_brand, this, _saveOperation).call(this, session, op);
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "message",
message: _assertClassBrand(_AiSdkHarness_brand, this, _transcript).call(this, placed)
}, {
type: "operation",
status: {
operationId,
status: "placed"
}
}]);
}
const messages = _assertClassBrand(_AiSdkHarness_brand, this, _messages).call(this, session);
const last = messages.at(-1);
if (last?.role === "assistant") {
owner = last.metadata?.owner;
if (awaitsInput$1(last)) {
_assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, { status: "done" });
return;
}
}
const run = {
operations: [operationId],
partial: [],
abort: new AbortController()
};
_classPrivateFieldGet2(_runs, this).set(session, run);
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "run-start",
operations: [operationId]
}]);
const { model, tools, system } = _classPrivateFieldGet2(_options, this);
let message;
let error;
try {
const stream = toUIMessageStream({
stream: streamText({
model,
...system !== void 0 && { system },
...tools && { tools },
stopWhen: _classPrivateFieldGet2(_options, this).stopWhen ?? stepCountIs(5),
messages: await convertToModelMessages(messages, tools && { tools }),
abortSignal: run.abort.signal
}).stream,
...tools && { tools },
originalMessages: messages,
generateMessageId: () => crypto.randomUUID(),
onEnd: ({ responseMessage }) => {
message = {
...responseMessage,
metadata: {
...responseMessage.metadata,
...owner !== void 0 && { owner }
}
};
}
});
for await (const chunk of toResponseChunks(stream, {
...tools && { tools },
...owner !== void 0 && { owner }
})) {
run.partial.push(chunk);
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "chunk",
chunk
}]);
}
} catch (caught) {
error = caught;
}
if (message) {
_assertClassBrand(_AiSdkHarness_brand, this, _putMessage).call(this, session, message);
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "message",
message: _assertClassBrand(_AiSdkHarness_brand, this, _transcript).call(this, message)
}]);
}
_classPrivateFieldGet2(_runs, this).delete(session);
if (run.abort.signal.aborted) _assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, {
status: "unanswered",
reason: "aborted"
});
else if (error !== void 0) {
console.error(`AI SDK run for "${operationId}" failed`, error);
_assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, {
status: "unanswered",
reason: error instanceof Error ? error.message : "failed"
});
} else _assertClassBrand(_AiSdkHarness_brand, this, _settle).call(this, session, op, {
status: "done",
text: message ? textOf(message) : ""
});
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "run-end",
operations: [operationId]
}]);
}
/** The message an answer updates, or why it is not wanted. */
function _answer(session, answer) {
const messages = _assertClassBrand(_AiSdkHarness_brand, this, _messages).call(this, session);
const event = answer.type === "approval" ? {
type: "approval-response",
approvalId: answer.approvalId,
approved: answer.approved,
...answer.reason !== void 0 && { reason: answer.reason }
} : {
type: "tool-result",
toolCallId: answer.toolCallId,
result: answer.result
};
if (answer.type === "tool-result") {
const owner = messages.find((m) => m.parts.some((p) => "toolCallId" in p && p.toolCallId === answer.toolCallId))?.metadata?.owner;
const from = answer.from?.participantId;
if (owner !== void 0 && from !== void 0 && owner !== from) return "not_owner";
}
return answerToolCall(messages, event) ?? "not_waiting";
}
function _settle(session, op, result) {
const settled = {
...op,
...result
};
_assertClassBrand(_AiSdkHarness_brand, this, _saveOperation).call(this, session, settled);
const { operationId } = op;
_assertClassBrand(_AiSdkHarness_brand, this, _emit).call(this, session, [{
type: "operation",
status: result.status === "done" ? {
operationId,
status: "done",
...result.text !== void 0 && { text: result.text }
} : {
operationId,
status: "unanswered",
reason: result.reason
}
}]);
const key = waiterKey(session, operationId);
for (const waiter of _classPrivateFieldGet2(_waiters, this).get(key) ?? []) waiter(settled);
_classPrivateFieldGet2(_waiters, this).delete(key);
}
function _wake(session) {
return this.lifecycle.jobs.push({
id: `${PREFIX}wake:${session}`,
fn: WAKE_FN,
time: Date.now(),
payload: { session },
singleflight: true
});
}
function _emit(session, events) {
for (const watcher of _classPrivateFieldGet2(_watchers, this).get(session) ?? []) watcher.push(events);
}
function _get_kv() {
return this.lifecycle.storage.kv;
}
function _create(session) {
_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(`${PREFIX}s:${session.id}`, session);
return this.session(session.id);
}
/**
* Sessions are created on first use, so any id names one. That is not an
* authorization gap: whoever reaches the agent may create sessions anyway.
*/
function _ensure(session) {
if (_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).get(`${PREFIX}s:${session}`) === void 0) _assertClassBrand(_AiSdkHarness_brand, this, _create).call(this, { id: session });
}
function _nextSeq(session) {
const key = `${PREFIX}seq:${session}`;
const seq = (_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).get(key) ?? 0) + 1;
_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(key, seq);
return seq;
}
function _messages(session) {
return [..._get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).list({ prefix: messagePrefix(session) })].map(([, message]) => message);
}
/** Save a message, in place of any with its id. */
function _putMessage(session, message) {
const indexKey = `${PREFIX}mi:${JSON.stringify(session)}:${message.id}`;
let key = _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).get(indexKey);
if (key === void 0 || _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).get(key) === void 0) {
key = messagePrefix(session) + pad(_assertClassBrand(_AiSdkHarness_brand, this, _nextSeq).call(this, session));
_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(indexKey, key);
}
_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(key, message);
}
function _operation(session, operationId) {
return _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).get(operationKey(session, operationId));
}
function _saveOperation(session, op) {
_get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(operationKey(session, op.operationId), op);
const pendingKey = `${pendingPrefix(session)}${pad(op.seq)}`;
if (isPending(op)) _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).put(pendingKey, op.operationId);
else _get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).delete(pendingKey);
}
/** Unsettled operations, oldest first. */
function _pending(session) {
return [..._get_kv.call(_assertClassBrand(_AiSdkHarness_brand, this)).list({ prefix: pendingPrefix(session) })].map(([, id]) => _assertClassBrand(_AiSdkHarness_brand, this, _operation).call(this, session, id)).filter((op) => op !== void 0);
}
function _transcript(message) {
const owner = message.metadata?.owner;
return toTranscriptMessage(message, {
..._classPrivateFieldGet2(_options, this).tools && { tools: _classPrivateFieldGet2(_options, this).tools },
...owner !== void 0 && { owner }
});
}
var _close = /* @__PURE__ */ new WeakMap();
var _listener = /* @__PURE__ */ new WeakMap();
var _queue = /* @__PURE__ */ new WeakMap();
var _draining = /* @__PURE__ */ new WeakMap();
var _stopped = /* @__PURE__ */ new WeakMap();
var _Watcher_brand = /* @__PURE__ */ new WeakSet();
/** One watch: events queue until `start`, then reach the listener in order. */
var Watcher = class {
constructor(state, onStop) {
this.state = state;
this.onStop = onStop;
_classPrivateMethodInitSpec(this, _Watcher_brand);
_classPrivateFieldInitSpec(this, _close, void 0);
_classPrivateFieldInitSpec(this, _listener, void 0);
_classPrivateFieldInitSpec(this, _queue, []);
_classPrivateFieldInitSpec(this, _draining, false);
_classPrivateFieldInitSpec(this, _stopped, false);
this.closed = new Promise((resolve) => {
_classPrivateFieldSet2(_close, this, resolve);
});
}
start(listener) {
_classPrivateFieldSet2(_listener, this, listener);
_assertClassBrand(_Watcher_brand, this, _drain).call(this);
}
async stop() {
_classPrivateFieldSet2(_stopped, this, true);
this.onStop();
_classPrivateFieldGet2(_close, this).call(this);
}
push(events) {
if (_classPrivateFieldGet2(_stopped, this)) return;
_classPrivateFieldGet2(_queue, this).push(events);
_assertClassBrand(_Watcher_brand, this, _drain).call(this);
}
};
async function _drain() {
if (!_classPrivateFieldGet2(_listener, this) || _classPrivateFieldGet2(_draining, this)) return;
_classPrivateFieldSet2(_draining, this, true);
try {
while (_classPrivateFieldGet2(_queue, this).length > 0 && !_classPrivateFieldGet2(_stopped, this)) {
const events = _classPrivateFieldGet2(_queue, this).shift();
try {
await _classPrivateFieldGet2(_listener, this).call(this, events);
} catch (error) {
console.error("A session watcher failed", error);
}
}
} finally {
_classPrivateFieldSet2(_draining, this, false);
}
}
function isPending(op) {
return op.status === "queued" || op.status === "placed";
}
/** Whether the message has a tool call waiting for a result or approval. */
function awaitsInput$1(message) {
return message.parts.some((part) => "toolCallId" in part && (part.state === "input-available" || part.state === "approval-requested"));
}
function textOf(message) {
return message.parts.map((part) => part.type === "text" ? part.text : "").join("");
}
function toUIPart(part) {
return part.type === "text" ? {
type: "text",
text: part.text
} : {
type: "file",
mediaType: part.mediaType,
url: part.url,
...part.filename !== void 0 && { filename: part.filename }
};
}
function pad(seq) {
return String(seq).padStart(12, "0");
}
function messagePrefix(session) {
return `${PREFIX}m:${JSON.stringify(session)}:`;
}
function pendingPrefix(session) {
return `${PREFIX}p:${JSON.stringify(session)}:`;
}
function operationKey(session, operationId) {
return `${PREFIX}o:${JSON.stringify(session)}:${operationId}`;
}
function waiterKey(session, operationId) {
return JSON.stringify([session, operationId]);
}
//#endregion
//#region src/experimental/channels/tool-schema.ts
const channelMessageJsonSchema = {
type: "object",
properties: {
title: {
type: "string",
description: "Optional title or topic for the message"
},
markdown: {
type: "string",
minLength: 1,
description: "Message content formatted as Markdown"
}
},
required: ["markdown"],
additionalProperties: false
};
function parseChannelMessage(value) {
if (typeof value === "object" && value !== null) {
const candidate = value;
if (typeof candidate.markdown === "string" && candidate.markdown.length > 0 && (candidate.title === void 0 || typeof candidate.title === "string")) return {
...typeof candidate.title === "string" ? { title: candidate.title } : {},
markdown: candidate.markdown
};
}
throw new Error("Expected an optional string title and a non-empty Markdown string");
}
//#endregion
//#region src/harness/ai-sdk/send-message-tool.ts
const channelMessageSchema = jsonSchema(channelMessageJsonSchema, { validate(value) {
try {
return {
success: true,
value: parseChannelMessage(value)
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
} });
/**
* Adapt one Host-resolved surface to an AI SDK tool.
*
* The caller chooses the key used in its ToolSet and owns model-facing policy
* such as the description, examples, metadata, and approval requirement.
*/
function createSendMessageTool(gateway, surface, options = {}) {
return tool({
...options,
inputSchema: channelMessageSchema,
execute: (message) => gateway.deliver(surface, message)
});
}
//#endregion
export { AiSdkHarness, answerToolCall, awaitsInput, createSendMessageTool, toResponseChunk, toResponseChunks, toTranscriptMessage, toUIMessage, toUIMessageChunk };
//# sourceMappingURL=index.js.map