agents
Version:
A home for your AI agents
203 lines (201 loc) • 6.67 kB
JavaScript
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