agents
Version:
A home for your AI agents
349 lines (348 loc) • 15.8 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 applyChunks } from "../../../apply-chunks-1FheRb9W.js";
//#region src/experimental/channels/web/client.ts
var _url = /* @__PURE__ */ new WeakMap();
var _followUrl = /* @__PURE__ */ new WeakMap();
var _conversationId = /* @__PURE__ */ new WeakMap();
var _operations = /* @__PURE__ */ new WeakMap();
var _socket = /* @__PURE__ */ new WeakMap();
var _closed = /* @__PURE__ */ new WeakMap();
var _connected = /* @__PURE__ */ new WeakMap();
var _reconnect = /* @__PURE__ */ new WeakMap();
var _you = /* @__PURE__ */ new WeakMap();
var _transcript = /* @__PURE__ */ new WeakMap();
var _turns = /* @__PURE__ */ new WeakMap();
var _responses = /* @__PURE__ */ new WeakMap();
var _outbox = /* @__PURE__ */ new WeakMap();
var _conversations = /* @__PURE__ */ new WeakMap();
var _lists = /* @__PURE__ */ new WeakMap();
var _listeners = /* @__PURE__ */ new WeakMap();
var _activity = /* @__PURE__ */ new WeakMap();
var _state = /* @__PURE__ */ new WeakMap();
var _WebChannelClient_brand = /* @__PURE__ */ new WeakSet();
/** A browser client for the Web Channel, with reconnects. */
var WebChannelClient = class {
constructor(url, options = {}) {
_classPrivateMethodInitSpec(this, _WebChannelClient_brand);
_classPrivateFieldInitSpec(this, _url, void 0);
_classPrivateFieldInitSpec(this, _followUrl, void 0);
_classPrivateFieldInitSpec(this, _conversationId, void 0);
_classPrivateFieldInitSpec(this, _operations, []);
_classPrivateFieldInitSpec(this, _socket, void 0);
_classPrivateFieldInitSpec(this, _closed, false);
_classPrivateFieldInitSpec(this, _connected, false);
_classPrivateFieldInitSpec(this, _reconnect, void 0);
_classPrivateFieldInitSpec(this, _you, void 0);
_classPrivateFieldInitSpec(this, _transcript, []);
_classPrivateFieldInitSpec(this, _turns, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _responses, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _outbox, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _conversations, void 0);
_classPrivateFieldInitSpec(this, _lists, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Set());
_classPrivateFieldInitSpec(this, _activity, /* @__PURE__ */ new Set());
_classPrivateFieldInitSpec(this, _state, {
connected: false,
operations: [],
messages: [],
turns: []
});
_classPrivateFieldSet2(_url, this, String(url));
_classPrivateFieldSet2(_followUrl, this, options.followUrl ?? defaultFollowUrl);
_assertClassBrand(_WebChannelClient_brand, this, _connect).call(this);
}
get state() {
return _classPrivateFieldGet2(_state, this);
}
/** Calls the listener on every change. Returns the unsubscribe. */
subscribe(listener) {
_classPrivateFieldGet2(_listeners, this).add(listener);
return () => _classPrivateFieldGet2(_listeners, this).delete(listener);
}
/**
* Calls the listener with each turn status, response chunk and response
* ending as it arrives, including chunks that arrive after the turn has
* settled. Returns the unsubscribe.
*/
onActivity(listener) {
_classPrivateFieldGet2(_activity, this).add(listener);
return () => _classPrivateFieldGet2(_activity, this).delete(listener);
}
/** The chunks received so far for a response this client is reading. */
chunksOf(responseId) {
return _classPrivateFieldGet2(_responses, this).get(responseId)?.chunks;
}
/**
* Resolves once the agent accepts the event, and rejects if it refuses.
* While disconnected the event waits, and it is sent again after a
* reconnect until acknowledged; Channels drops repeats by `eventId`.
* Rejects once the client is closed.
*/
send(event) {
if (_classPrivateFieldGet2(_closed, this)) return Promise.reject(/* @__PURE__ */ new Error("Closed"));
const eventId = event.eventId ?? crypto.randomUUID();
const complete = {
...event,
eventId
};
return new Promise((resolve, reject) => {
const accepted = () => {
if (complete.type === "message") _assertClassBrand(_WebChannelClient_brand, this, _upsert).call(this, [complete.message]);
resolve();
};
_classPrivateFieldGet2(_outbox, this).set(eventId, {
event: complete,
resolve: accepted,
reject
});
if (_classPrivateFieldGet2(_connected, this)) _assertClassBrand(_WebChannelClient_brand, this, _send).call(this, {
type: "channels:event",
event: complete
});
});
}
/** The agent's conversations. Rejects while disconnected or closed. */
listConversations() {
if (_classPrivateFieldGet2(_closed, this)) return Promise.reject(/* @__PURE__ */ new Error("Closed"));
if (!_classPrivateFieldGet2(_connected, this)) return Promise.reject(/* @__PURE__ */ new Error("Not connected"));
const requestId = crypto.randomUUID();
return new Promise((resolve, reject) => {
_classPrivateFieldGet2(_lists, this).set(requestId, {
resolve,
reject
});
_assertClassBrand(_WebChannelClient_brand, this, _send).call(this, {
type: "channels:list-conversations",
requestId
});
});
}
/**
* Follow another conversation: reconnect to it, as after a create or
* fork. Events still waiting for an ack are rejected, since they were
* meant for the conversation left behind.
*/
follow(conversationId) {
if (_classPrivateFieldGet2(_closed, this) || conversationId === _classPrivateFieldGet2(_conversationId, this)) return;
_assertClassBrand(_WebChannelClient_brand, this, _rejectPending).call(this, /* @__PURE__ */ new Error("Switched conversation"));
_classPrivateFieldSet2(_url, this, String(_classPrivateFieldGet2(_followUrl, this).call(this, new URL(_classPrivateFieldGet2(_url, this)), conversationId)));
const previous = _classPrivateFieldGet2(_socket, this);
_classPrivateFieldSet2(_connected, this, false);
_assertClassBrand(_WebChannelClient_brand, this, _cancelReconnect).call(this);
_assertClassBrand(_WebChannelClient_brand, this, _connect).call(this);
previous?.close();
_assertClassBrand(_WebChannelClient_brand, this, _update).call(this);
}
/** Closes the connection and rejects events still waiting for an ack. */
close() {
_classPrivateFieldSet2(_closed, this, true);
_assertClassBrand(_WebChannelClient_brand, this, _cancelReconnect).call(this);
_classPrivateFieldGet2(_socket, this)?.close();
_assertClassBrand(_WebChannelClient_brand, this, _rejectPending).call(this, /* @__PURE__ */ new Error("Closed"));
}
};
function _cancelReconnect() {
clearTimeout(_classPrivateFieldGet2(_reconnect, this));
_classPrivateFieldSet2(_reconnect, this, void 0);
}
function _rejectPending(error) {
for (const pending of _classPrivateFieldGet2(_outbox, this).values()) pending.reject(error);
_classPrivateFieldGet2(_outbox, this).clear();
for (const pending of _classPrivateFieldGet2(_lists, this).values()) pending.reject(error);
_classPrivateFieldGet2(_lists, this).clear();
}
function _connect() {
const socket = new WebSocket(_classPrivateFieldGet2(_url, this));
_classPrivateFieldSet2(_socket, this, socket);
socket.addEventListener("message", (event) => {
if (socket !== _classPrivateFieldGet2(_socket, this)) return;
if (typeof event.data !== "string") return;
const frame = JSON.parse(event.data);
if (typeof frame.type === "string" && frame.type.startsWith("channels:")) _assertClassBrand(_WebChannelClient_brand, this, _receive).call(this, frame);
});
socket.addEventListener("close", () => {
if (socket !== _classPrivateFieldGet2(_socket, this)) return;
_classPrivateFieldSet2(_connected, this, false);
for (const pending of _classPrivateFieldGet2(_lists, this).values()) pending.reject(/* @__PURE__ */ new Error("Disconnected"));
_classPrivateFieldGet2(_lists, this).clear();
_assertClassBrand(_WebChannelClient_brand, this, _update).call(this);
if (_classPrivateFieldGet2(_closed, this) || socket !== _classPrivateFieldGet2(_socket, this)) return;
_classPrivateFieldSet2(_reconnect, this, setTimeout(() => {
_classPrivateFieldSet2(_reconnect, this, void 0);
_assertClassBrand(_WebChannelClient_brand, this, _connect).call(this);
}, 1e3));
});
}
function _send(frame) {
if (_classPrivateFieldGet2(_socket, this)?.readyState === WebSocket.OPEN) _classPrivateFieldGet2(_socket, this).send(JSON.stringify(frame));
}
function _receive(frame) {
if (frame.type !== "channels:snapshot" && frame.type !== "channels:ack" && frame.type !== "channels:conversations" && frame.conversationId !== _classPrivateFieldGet2(_conversationId, this)) return;
switch (frame.type) {
case "channels:snapshot":
if (frame.conversationId !== _classPrivateFieldGet2(_conversationId, this)) {
if (_classPrivateFieldGet2(_conversationId, this) !== void 0) {
_classPrivateFieldGet2(_responses, this).clear();
_classPrivateFieldSet2(_url, this, String(_classPrivateFieldGet2(_followUrl, this).call(this, new URL(_classPrivateFieldGet2(_url, this)), frame.conversationId)));
}
_classPrivateFieldSet2(_conversationId, this, frame.conversationId);
}
_classPrivateFieldSet2(_operations, this, frame.operations);
_classPrivateFieldSet2(_connected, this, true);
_classPrivateFieldSet2(_you, this, frame.you);
_classPrivateFieldSet2(_transcript, this, frame.messages);
_classPrivateFieldGet2(_turns, this).clear();
_assertClassBrand(_WebChannelClient_brand, this, _resume).call(this, frame.turns);
for (const turn of frame.turns) _assertClassBrand(_WebChannelClient_brand, this, _setTurn).call(this, turn);
for (const { event } of _classPrivateFieldGet2(_outbox, this).values()) _assertClassBrand(_WebChannelClient_brand, this, _send).call(this, {
type: "channels:event",
event
});
break;
case "channels:turn":
_assertClassBrand(_WebChannelClient_brand, this, _setTurn).call(this, frame.turn);
_assertClassBrand(_WebChannelClient_brand, this, _update).call(this);
_assertClassBrand(_WebChannelClient_brand, this, _emit).call(this, {
type: "turn",
turn: frame.turn
});
return;
case "channels:messages":
_assertClassBrand(_WebChannelClient_brand, this, _upsert).call(this, frame.messages);
return;
case "channels:chunks": {
const response = _classPrivateFieldGet2(_responses, this).get(frame.responseId);
const skip = (response?.chunks.length ?? 0) - frame.from;
if (!response || skip < 0) return;
const fresh = frame.chunks.slice(skip);
if (fresh.length === 0) return;
response.chunks.push(...fresh);
_assertClassBrand(_WebChannelClient_brand, this, _emit).call(this, {
type: "chunks",
responseId: frame.responseId,
turnId: response.turnId,
chunks: fresh
});
if (!response.shown) return;
break;
}
case "channels:ack": {
const ack = _classPrivateFieldGet2(_outbox, this).get(frame.eventId);
_classPrivateFieldGet2(_outbox, this).delete(frame.eventId);
if (frame.error) ack?.reject(new Error(frame.error));
else ack?.resolve();
return;
}
case "channels:end": {
const response = _classPrivateFieldGet2(_responses, this).get(frame.responseId);
if (!response) return;
response.ended = true;
if (!response.shown || frame.state === "not-found") _classPrivateFieldGet2(_responses, this).delete(frame.responseId);
_assertClassBrand(_WebChannelClient_brand, this, _emit).call(this, {
type: "end",
responseId: frame.responseId,
turnId: response.turnId,
state: frame.state
});
if (frame.state === "not-found") break;
return;
}
case "channels:caught-up": return;
case "channels:conversations": {
const request = frame.requestId === void 0 ? void 0 : _classPrivateFieldGet2(_lists, this).get(frame.requestId);
if (request) _classPrivateFieldGet2(_lists, this).delete(frame.requestId ?? "");
if (frame.error) {
request?.reject(new Error(frame.error));
return;
}
if (frame.conversations) {
_classPrivateFieldSet2(_conversations, this, frame.conversations);
request?.resolve(frame.conversations);
}
break;
}
}
_assertClassBrand(_WebChannelClient_brand, this, _update).call(this);
}
function _upsert(messages) {
for (const message of messages) {
const index = _classPrivateFieldGet2(_transcript, this).findIndex((m) => m.id === message.id);
if (index === -1) _classPrivateFieldGet2(_transcript, this).push(message);
else _classPrivateFieldGet2(_transcript, this)[index] = message;
}
_assertClassBrand(_WebChannelClient_brand, this, _update).call(this);
}
function _emit(activity) {
for (const listener of _classPrivateFieldGet2(_activity, this)) listener(activity);
}
/** After a reconnect, keep reading still-running responses where we were. */
function _resume(turns) {
const running = new Set(turns.flatMap((turn) => turn.status === "running" ? [turn.responseId] : []));
for (const [responseId, response] of _classPrivateFieldGet2(_responses, this)) {
if (!running.has(responseId)) {
_classPrivateFieldGet2(_responses, this).delete(responseId);
continue;
}
_assertClassBrand(_WebChannelClient_brand, this, _send).call(this, {
type: "channels:subscribe",
responseId,
from: response.chunks.length
});
}
}
function _setTurn(turn) {
const responseId = turn.status === "running" ? turn.responseId : void 0;
for (const [id, response] of _classPrivateFieldGet2(_responses, this)) if (response.turnId === turn.turnId && id !== responseId) {
response.shown = false;
if (response.ended) _classPrivateFieldGet2(_responses, this).delete(id);
}
if (turn.status !== "settled" || turn.outcome === "awaiting-input") _classPrivateFieldGet2(_turns, this).set(turn.turnId, turn);
else _classPrivateFieldGet2(_turns, this).delete(turn.turnId);
if (turn.status === "running" && !_classPrivateFieldGet2(_responses, this).has(turn.responseId)) {
_classPrivateFieldGet2(_responses, this).set(turn.responseId, {
turnId: turn.turnId,
...turn.extends !== void 0 && { extends: turn.extends },
chunks: [],
shown: true,
ended: false
});
_assertClassBrand(_WebChannelClient_brand, this, _send).call(this, {
type: "channels:subscribe",
responseId: turn.responseId
});
}
}
function _update() {
const messages = [..._classPrivateFieldGet2(_transcript, this)];
for (const [responseId, live] of _classPrivateFieldGet2(_responses, this)) {
if (!live.shown) continue;
const index = messages.findIndex((m) => m.id === live.extends);
const message = applyChunks(index === -1 ? {
id: responseId,
role: "assistant",
parts: []
} : messages[index], live.chunks);
if (index === -1) messages.push(message);
else messages[index] = message;
}
_classPrivateFieldSet2(_state, this, {
connected: _classPrivateFieldGet2(_connected, this),
..._classPrivateFieldGet2(_conversationId, this) !== void 0 && { conversationId: _classPrivateFieldGet2(_conversationId, this) },
operations: _classPrivateFieldGet2(_operations, this),
..._classPrivateFieldGet2(_you, this) && { you: _classPrivateFieldGet2(_you, this) },
messages,
turns: [..._classPrivateFieldGet2(_turns, this).values()],
..._classPrivateFieldGet2(_conversations, this) && { conversations: _classPrivateFieldGet2(_conversations, this) }
});
for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(_classPrivateFieldGet2(_state, this));
}
function defaultFollowUrl(url, conversationId) {
const next = new URL(url);
const match = /^\/channels\/([^/]+)(?:\/[^/]+)?$/.exec(url.pathname);
if (match) next.pathname = `/channels/${match[1]}/${encodeURIComponent(conversationId)}`;
return next;
}
//#endregion
export { WebChannelClient };
//# sourceMappingURL=client.js.map