UNPKG

agents

Version:

A home for your AI agents

349 lines (348 loc) • 15.8 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 { 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