UNPKG

agents

Version:
273 lines (271 loc) 9.17 kB
import { t as MessageType } from "./types-BITaDFf-.js"; import { t as camelCaseToKebabCase } from "./utils-B49TmLCI.js"; import { usePartySocket } from "partysocket/react"; import { use, useCallback, useEffect, useMemo, useRef, useState } from "react"; //#region src/react.tsx const queryCache = /* @__PURE__ */ new Map(); function createCacheKey(agentNamespace, name, deps) { return JSON.stringify([ agentNamespace, name || "default", ...deps ]); } function getCacheEntry(key) { const entry = queryCache.get(key); if (!entry) return void 0; if (Date.now() >= entry.expiresAt) { queryCache.delete(key); return; } return entry; } function setCacheEntry(key, promise, cacheTtl) { const entry = { promise, expiresAt: Date.now() + cacheTtl }; queryCache.set(key, entry); return entry; } function deleteCacheEntry(key) { queryCache.delete(key); } /** * Creates a proxy that wraps RPC method calls. * Internal JS methods (toJSON, then, etc.) return undefined to avoid * triggering RPC calls during serialization (e.g., console.log) */ function createStubProxy(call) { return new Proxy({}, { get: (_target, method) => { if (typeof method === "symbol" || method === "toJSON" || method === "then" || method === "catch" || method === "finally" || method === "valueOf" || method === "toString" || method === "constructor" || method === "prototype" || method === "$$typeof" || method === "@@toStringTag" || method === "asymmetricMatch" || method === "nodeType") return; return (...args) => call(method, args); } }); } const _testUtils = { queryCache, setCacheEntry, getCacheEntry, deleteCacheEntry, clearCache: () => queryCache.clear(), createStubProxy, createCacheKey }; function useAgent(options) { const agentNamespace = camelCaseToKebabCase(options.agent); const { query, queryDeps, cacheTtl, ...restOptions } = options; const pendingCallsRef = useRef(/* @__PURE__ */ new Map()); const cacheKey = useMemo(() => createCacheKey(agentNamespace, options.name, queryDeps || []), [ agentNamespace, options.name, queryDeps ]); const cacheKeyRef = useRef(cacheKey); cacheKeyRef.current = cacheKey; const ttl = cacheTtl ?? 300 * 1e3; const [cacheInvalidatedAt, setCacheInvalidatedAt] = useState(0); const isAsyncQuery = query && typeof query === "function"; const [awaitingQueryRefresh, setAwaitingQueryRefresh] = useState(false); const queryPromise = useMemo(() => { if (!query || typeof query !== "function") return null; const cached = getCacheEntry(cacheKey); if (cached) return cached.promise; const promise = query().catch((error) => { console.error(`[useAgent] Query failed for agent "${options.agent}":`, error); deleteCacheEntry(cacheKey); throw error; }); setCacheEntry(cacheKey, promise, ttl); return promise; }, [ cacheKey, query, options.agent, ttl, cacheInvalidatedAt ]); useEffect(() => { if (!queryPromise || ttl <= 0) return; const entry = getCacheEntry(cacheKey); if (!entry) return; const timeUntilExpiry = entry.expiresAt - Date.now(); const timer = setTimeout(() => { deleteCacheEntry(cacheKey); setCacheInvalidatedAt(Date.now()); }, Math.max(0, timeUntilExpiry)); return () => clearTimeout(timer); }, [ cacheKey, queryPromise, ttl ]); let resolvedQuery; if (query) if (typeof query === "function") { const queryResult = use(queryPromise); if (queryResult) { for (const [key, value] of Object.entries(queryResult)) if (value !== null && value !== void 0 && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") console.warn(`[useAgent] Query parameter "${key}" is an object and will be converted to "[object Object]". Query parameters should be string, number, boolean, or null.`); resolvedQuery = queryResult; } } else resolvedQuery = query; useEffect(() => { if (awaitingQueryRefresh && resolvedQuery !== void 0) setAwaitingQueryRefresh(false); }, [awaitingQueryRefresh, resolvedQuery]); const [identity, setIdentity] = useState({ name: options.name || "default", agent: agentNamespace, identified: false }); const previousIdentityRef = useRef({ name: null, agent: null }); const readyRef = useRef(void 0); const resetReady = () => { let resolve; readyRef.current = { promise: new Promise((r) => { resolve = r; }), resolve }; }; if (!readyRef.current) resetReady(); const socketOptions = options.basePath ? { basePath: options.basePath, path: options.path, query: resolvedQuery, ...restOptions } : { party: agentNamespace, prefix: "agents", room: options.name || "default", path: options.path, query: resolvedQuery, ...restOptions }; const socketEnabled = !awaitingQueryRefresh && (restOptions.enabled ?? true); const agent = usePartySocket({ ...socketOptions, enabled: socketEnabled, onMessage: (message) => { if (typeof message.data === "string") { let parsedMessage; try { parsedMessage = JSON.parse(message.data); } catch (_error) { return options.onMessage?.(message); } if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) { const oldName = previousIdentityRef.current.name; const oldAgent = previousIdentityRef.current.agent; const newName = parsedMessage.name; const newAgent = parsedMessage.agent; setIdentity({ name: newName, agent: newAgent, identified: true }); readyRef.current?.resolve(); if (oldName !== null && oldAgent !== null && (oldName !== newName || oldAgent !== newAgent)) if (options.onIdentityChange) 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.`); } previousIdentityRef.current = { name: newName, agent: newAgent }; options.onIdentity?.(newName, newAgent); return; } if (parsedMessage.type === MessageType.CF_AGENT_STATE) { options.onStateUpdate?.(parsedMessage.state, "server"); return; } if (parsedMessage.type === MessageType.CF_AGENT_MCP_SERVERS) { options.onMcpUpdate?.(parsedMessage.mcp); return; } if (parsedMessage.type === MessageType.RPC) { const response = parsedMessage; const pending = pendingCallsRef.current.get(response.id); if (!pending) return; if (!response.success) { pending.reject(new Error(response.error)); pendingCallsRef.current.delete(response.id); pending.stream?.onError?.(response.error); return; } if ("done" in response) if (response.done) { pending.resolve(response.result); pendingCallsRef.current.delete(response.id); pending.stream?.onDone?.(response.result); } else pending.stream?.onChunk?.(response.result); else { pending.resolve(response.result); pendingCallsRef.current.delete(response.id); } return; } } options.onMessage?.(message); }, onClose: (event) => { resetReady(); setIdentity((prev) => ({ ...prev, identified: false })); if (isAsyncQuery) setAwaitingQueryRefresh(true); deleteCacheEntry(cacheKeyRef.current); setCacheInvalidatedAt(Date.now()); const error = /* @__PURE__ */ new Error("Connection closed"); for (const pending of pendingCallsRef.current.values()) { pending.reject(error); pending.stream?.onError?.("Connection closed"); } pendingCallsRef.current.clear(); options.onClose?.(event); } }); const call = useCallback((method, args = [], streamOptions) => { return new Promise((resolve, reject) => { const id = crypto.randomUUID(); pendingCallsRef.current.set(id, { reject, resolve, stream: streamOptions }); const request = { args, id, method, type: MessageType.RPC }; agent.send(JSON.stringify(request)); }); }, [agent]); agent.setState = (state) => { agent.send(JSON.stringify({ state, type: MessageType.CF_AGENT_STATE })); options.onStateUpdate?.(state, "client"); }; agent.call = call; agent.agent = identity.agent; agent.name = identity.name; agent.identified = identity.identified; agent.ready = readyRef.current.promise; agent.stub = createStubProxy(call); if (identity.agent !== identity.agent.toLowerCase()) console.warn("Agent name: " + identity.agent + " should probably be in lowercase. Received: " + identity.agent); return agent; } //#endregion export { _testUtils, useAgent }; //# sourceMappingURL=react.js.map