UNPKG

agents

Version:
203 lines (201 loc) 6.67 kB
import { t as MessageType } from "./types-BITaDFf-.js"; import { t as camelCaseToKebabCase } from "./utils-B49TmLCI.js"; import { PartySocket } from "partysocket"; //#region src/client.ts /** * WebSocket client for connecting to an Agent */ var AgentClient = class extends PartySocket { /** * @deprecated Use agentFetch instead */ static fetch(_opts) { throw new Error("AgentClient.fetch is not implemented, use agentFetch instead"); } /** * Promise that resolves when identity has been received from the server. * Useful for waiting before making calls that depend on knowing the instance. * Resets on connection close so it can be awaited again after reconnect. */ get ready() { return this._readyPromise; } _resetReady() { this._readyPromise = new Promise((resolve) => { this._resolveReady = resolve; }); } constructor(options) { const agentNamespace = camelCaseToKebabCase(options.agent); const socketOptions = options.basePath ? { basePath: options.basePath, path: options.path, ...options } : { party: agentNamespace, prefix: "agents", room: options.name || "default", path: options.path, ...options }; super(socketOptions); this.identified = false; this._pendingCalls = /* @__PURE__ */ new Map(); this._previousName = null; this._previousAgent = null; this.agent = agentNamespace; this.name = options.name || "default"; this.options = options; this._resetReady(); this.addEventListener("message", (event) => { if (typeof event.data === "string") { let parsedMessage; try { parsedMessage = JSON.parse(event.data); } catch (_error) { return; } if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) { const oldName = this._previousName; const oldAgent = this._previousAgent; const newName = parsedMessage.name; const newAgent = parsedMessage.agent; this.identified = true; this._resolveReady(); if (oldName !== null && oldAgent !== null && (oldName !== newName || oldAgent !== newAgent)) if (this.options.onIdentityChange) this.options.onIdentityChange(oldName, newName, oldAgent, newAgent); else { const agentChanged = oldAgent !== newAgent; const nameChanged = oldName !== newName; let changeDescription = ""; if (agentChanged && nameChanged) changeDescription = `agent "${oldAgent}""${newAgent}", instance "${oldName}""${newName}"`; else if (agentChanged) changeDescription = `agent "${oldAgent}""${newAgent}"`; else changeDescription = `instance "${oldName}""${newName}"`; console.warn(`[agents] Identity changed on reconnect: ${changeDescription}. This can happen with server-side routing (e.g., basePath with getAgentByName) where the instance is determined by auth/session. Provide onIdentityChange callback to handle this explicitly, or ignore if this is expected for your routing pattern.`); } this._previousName = newName; this._previousAgent = newAgent; this.name = newName; this.agent = newAgent; this.options.onIdentity?.(newName, newAgent); return; } if (parsedMessage.type === MessageType.CF_AGENT_STATE) { this.options.onStateUpdate?.(parsedMessage.state, "server"); return; } if (parsedMessage.type === MessageType.RPC) { const response = parsedMessage; const pending = this._pendingCalls.get(response.id); if (!pending) return; if (!response.success) { pending.reject(new Error(response.error)); this._pendingCalls.delete(response.id); pending.stream?.onError?.(response.error); return; } if ("done" in response) if (response.done) { pending.resolve(response.result); this._pendingCalls.delete(response.id); pending.stream?.onDone?.(response.result); } else pending.stream?.onChunk?.(response.result); else { pending.resolve(response.result); this._pendingCalls.delete(response.id); } } } }); this.addEventListener("close", () => { this.identified = false; this._resetReady(); this._rejectPendingCalls("Connection closed"); }); } /** * Reject all pending RPC calls with the given reason. */ _rejectPendingCalls(reason) { const error = new Error(reason); for (const pending of this._pendingCalls.values()) { pending.reject(error); pending.stream?.onError?.(reason); } this._pendingCalls.clear(); } setState(state) { this.send(JSON.stringify({ state, type: MessageType.CF_AGENT_STATE })); this.options.onStateUpdate?.(state, "client"); } /** * Close the connection and immediately reject all pending RPC calls. * This provides immediate feedback on intentional close rather than * waiting for the WebSocket close handshake to complete. * * Note: Any calls made after `close()` will be rejected when the * underlying WebSocket close event fires. */ close(code, reason) { this._rejectPendingCalls("Connection closed"); super.close(code, reason); } async call(method, args = [], options) { return new Promise((resolve, reject) => { const id = crypto.randomUUID(); let timeoutId; const isLegacyFormat = options && ("onChunk" in options || "onDone" in options || "onError" in options); const streamOptions = isLegacyFormat ? options : options?.stream; const timeout = isLegacyFormat ? void 0 : options?.timeout; if (timeout) timeoutId = setTimeout(() => { const pending = this._pendingCalls.get(id); this._pendingCalls.delete(id); const errorMessage = `RPC call to ${method} timed out after ${timeout}ms`; pending?.stream?.onError?.(errorMessage); reject(new Error(errorMessage)); }, timeout); this._pendingCalls.set(id, { reject: (e) => { if (timeoutId) clearTimeout(timeoutId); reject(e); }, resolve: (value) => { if (timeoutId) clearTimeout(timeoutId); resolve(value); }, stream: streamOptions, type: null }); const request = { args, id, method, type: MessageType.RPC }; this.send(JSON.stringify(request)); }); } }; /** * Make an HTTP request to an Agent * @param opts Connection options * @param init Request initialization options * @returns Promise resolving to a Response */ function agentFetch(opts, init) { const agentNamespace = camelCaseToKebabCase(opts.agent); if (opts.basePath) return PartySocket.fetch({ basePath: opts.basePath, ...opts }, init); return PartySocket.fetch({ party: agentNamespace, prefix: "agents", room: opts.name || "default", ...opts }, init); } //#endregion export { AgentClient, agentFetch }; //# sourceMappingURL=client.js.map