agents 0.11.9 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +37 -1
  2. package/dist/{index-DSwOzhhd.d.ts → agent-tool-types-tBGRsPm0.d.ts} +584 -99
  3. package/dist/agent-tool-types.d.ts +34 -0
  4. package/dist/agent-tool-types.js +1 -0
  5. package/dist/agent-tools-BAdX1vdI.js +425 -0
  6. package/dist/agent-tools-BAdX1vdI.js.map +1 -0
  7. package/dist/agent-tools-CIO14miM.d.ts +14 -0
  8. package/dist/agent-tools.d.ts +68 -0
  9. package/dist/agent-tools.js +51 -0
  10. package/dist/agent-tools.js.map +1 -0
  11. package/dist/browser/ai.d.ts +1 -1
  12. package/dist/browser/ai.js +2 -2
  13. package/dist/browser/index.d.ts +1 -1
  14. package/dist/browser/index.js +1 -1
  15. package/dist/browser/tanstack-ai.d.ts +1 -1
  16. package/dist/browser/tanstack-ai.js +1 -1
  17. package/dist/chat/index.d.ts +3 -1
  18. package/dist/chat/index.js +3 -300
  19. package/dist/chat/index.js.map +1 -1
  20. package/dist/client.d.ts +2 -2
  21. package/dist/{compaction-helpers-C_cN3z55.js → compaction-helpers-CSaqCmdE.js} +1 -1
  22. package/dist/{compaction-helpers-C_cN3z55.js.map → compaction-helpers-CSaqCmdE.js.map} +1 -1
  23. package/dist/{compaction-helpers-YzCLvunJ.d.ts → compaction-helpers-D92Ipstp.d.ts} +1 -1
  24. package/dist/experimental/memory/session/index.d.ts +1 -1
  25. package/dist/experimental/memory/session/index.js +1 -1
  26. package/dist/experimental/memory/utils/index.d.ts +1 -1
  27. package/dist/experimental/memory/utils/index.js +1 -1
  28. package/dist/index.d.ts +74 -42
  29. package/dist/index.js +1393 -296
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/client.d.ts +1 -1
  32. package/dist/mcp/index.d.ts +1 -1
  33. package/dist/react.d.ts +16 -2
  34. package/dist/react.js +42 -1
  35. package/dist/react.js.map +1 -1
  36. package/dist/{serializable-Bg8ARWlN.d.ts → serializable-Brg7fRds.d.ts} +1 -1
  37. package/dist/serializable.d.ts +1 -1
  38. package/dist/{shared-mfBbxjS1.js → shared-C6l4ZKRN.js} +1 -1
  39. package/dist/{shared-mfBbxjS1.js.map → shared-C6l4ZKRN.js.map} +1 -1
  40. package/dist/{shared-BUHZFGTk.d.ts → shared-Ch9slKdI.d.ts} +1 -1
  41. package/dist/sub-routing.d.ts +6 -6
  42. package/dist/workflows.d.ts +1 -1
  43. package/package.json +9 -4
@@ -1,2 +1,2 @@
1
- import { B as MCPConnectionResult, G as RegisterServerOptions, H as MCPOAuthCallbackResult, I as MCPClientManager, K as getNamespacedData, L as MCPClientManagerOptions, R as MCPClientOAuthCallbackConfig, U as MCPServerFilter, V as MCPDiscoverResult, W as MCPServerOptions, z as MCPClientOAuthResult } from "../index-DSwOzhhd.js";
1
+ import { $ as MCPClientManagerOptions, Q as MCPClientManager, at as MCPServerFilter, ct as getNamespacedData, et as MCPClientOAuthCallbackConfig, it as MCPOAuthCallbackResult, nt as MCPConnectionResult, ot as MCPServerOptions, rt as MCPDiscoverResult, st as RegisterServerOptions, tt as MCPClientOAuthResult } from "../agent-tool-types-tBGRsPm0.js";
2
2
  export { MCPClientManager, MCPClientManagerOptions, MCPClientOAuthCallbackConfig, MCPClientOAuthResult, MCPConnectionResult, MCPDiscoverResult, MCPOAuthCallbackResult, MCPServerFilter, MCPServerOptions, RegisterServerOptions, getNamespacedData };
@@ -1,2 +1,2 @@
1
- import { $ as ElicitRequestSchema, B as MCPConnectionResult, J as RPCClientTransportOptions, Q as ElicitRequest, R as MCPClientOAuthCallbackConfig, V as MCPDiscoverResult, W as MCPServerOptions, X as RPCServerTransportOptions, Y as RPCServerTransport, Z as RPC_DO_PREFIX, at as McpAuthContext, ct as WorkerTransport, dt as StreamableHTTPEdgeClientTransport, et as ElicitResult, ft as McpClientOptions, it as experimental_createMcpHandler, lt as WorkerTransportOptions, nt as CreateMcpHandlerOptions, ot as getMcpAuthContext, q as RPCClientTransport, rt as createMcpHandler, st as TransportState, tt as McpAgent, ut as SSEEdgeClientTransport, z as MCPClientOAuthResult } from "../index-DSwOzhhd.js";
1
+ import { Ct as TransportState, Dt as StreamableHTTPEdgeClientTransport, Et as SSEEdgeClientTransport, Ot as McpClientOptions, St as getMcpAuthContext, Tt as WorkerTransportOptions, _t as McpAgent, bt as experimental_createMcpHandler, dt as RPCServerTransport, et as MCPClientOAuthCallbackConfig, ft as RPCServerTransportOptions, gt as ElicitResult, ht as ElicitRequestSchema, lt as RPCClientTransport, mt as ElicitRequest, nt as MCPConnectionResult, ot as MCPServerOptions, pt as RPC_DO_PREFIX, rt as MCPDiscoverResult, tt as MCPClientOAuthResult, ut as RPCClientTransportOptions, vt as CreateMcpHandlerOptions, wt as WorkerTransport, xt as McpAuthContext, yt as createMcpHandler } from "../agent-tool-types-tBGRsPm0.js";
2
2
  export { CreateMcpHandlerOptions, ElicitRequest, ElicitRequestSchema, ElicitResult, MCPClientOAuthCallbackConfig, MCPClientOAuthResult, MCPConnectionResult, MCPDiscoverResult, MCPServerOptions, McpAgent, McpAuthContext, McpClientOptions, RPCClientTransport, RPCClientTransportOptions, RPCServerTransport, RPCServerTransportOptions, RPC_DO_PREFIX, SSEEdgeClientTransport, StreamableHTTPEdgeClientTransport, TransportState, WorkerTransport, WorkerTransportOptions, createMcpHandler, experimental_createMcpHandler, getMcpAuthContext };
package/dist/react.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { v as MCPServersState } from "./index-DSwOzhhd.js";
1
+ import {
2
+ N as MCPServersState,
3
+ l as AgentToolRunState
4
+ } from "./agent-tool-types-tBGRsPm0.js";
2
5
  import {
3
6
  AgentPromiseReturnType,
4
7
  AgentStub,
@@ -205,6 +208,17 @@ declare function useAgent<
205
208
  stub: AgentStub<AgentT>;
206
209
  getHttpUrl: () => string;
207
210
  };
211
+ type AgentToolEventAgent = Pick<
212
+ PartySocket,
213
+ "addEventListener" | "removeEventListener"
214
+ >;
215
+ declare function useAgentToolEvents(options: { agent: AgentToolEventAgent }): {
216
+ runsById: Record<string, AgentToolRunState>;
217
+ runsByToolCallId: Record<string, AgentToolRunState[]>;
218
+ unboundRuns: AgentToolRunState[];
219
+ getRunsForToolCall(toolCallId: string): AgentToolRunState[];
220
+ resetLocalState(): void;
221
+ };
208
222
  //#endregion
209
- export { UseAgentOptions, _testUtils, useAgent };
223
+ export { UseAgentOptions, _testUtils, useAgent, useAgentToolEvents };
210
224
  //# sourceMappingURL=react.d.ts.map
package/dist/react.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import "./types.js";
2
2
  import { camelCaseToKebabCase } from "./utils.js";
3
3
  import { createStubProxy } from "./client.js";
4
+ import { n as createAgentToolEventState, t as applyAgentToolEvent } from "./agent-tools-BAdX1vdI.js";
4
5
  import { usePartySocket } from "partysocket/react";
5
6
  import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
6
7
  //#region src/react.tsx
@@ -307,7 +308,47 @@ function useAgent(options) {
307
308
  if (identity.agent !== identity.agent.toLowerCase()) console.warn("Agent name: " + identity.agent + " should probably be in lowercase. Received: " + identity.agent);
308
309
  return agent;
309
310
  }
311
+ function agentToolDedupeKey(message) {
312
+ return [
313
+ message.parentToolCallId ?? "",
314
+ message.event.runId,
315
+ String(message.sequence)
316
+ ].join("\0");
317
+ }
318
+ function useAgentToolEvents(options) {
319
+ const { agent } = options;
320
+ const [state, setState] = useState(() => createAgentToolEventState());
321
+ const seenRef = useRef(/* @__PURE__ */ new Set());
322
+ useEffect(() => {
323
+ const onMessage = (event) => {
324
+ if (typeof event.data !== "string") return;
325
+ let message;
326
+ try {
327
+ message = JSON.parse(event.data);
328
+ } catch {
329
+ return;
330
+ }
331
+ if (message.type !== "agent-tool-event") return;
332
+ const key = agentToolDedupeKey(message);
333
+ if (seenRef.current.has(key)) return;
334
+ seenRef.current.add(key);
335
+ setState((prev) => applyAgentToolEvent(prev, message));
336
+ };
337
+ agent.addEventListener("message", onMessage);
338
+ return () => agent.removeEventListener("message", onMessage);
339
+ }, [agent]);
340
+ const resetLocalState = useCallback(() => {
341
+ seenRef.current.clear();
342
+ setState(createAgentToolEventState());
343
+ }, []);
344
+ const getRunsForToolCall = useCallback((toolCallId) => state.runsByToolCallId[toolCallId] ?? [], [state.runsByToolCallId]);
345
+ return {
346
+ ...state,
347
+ getRunsForToolCall,
348
+ resetLocalState
349
+ };
350
+ }
310
351
  //#endregion
311
- export { _testUtils, useAgent };
352
+ export { _testUtils, useAgent, useAgentToolEvents };
312
353
 
313
354
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import type { PartySocket } from \"partysocket\";\nimport { usePartySocket } from \"partysocket/react\";\nimport { useCallback, useRef, use, useMemo, useState, useEffect } from \"react\";\nimport type { MCPServersState, RPCRequest, RPCResponse } from \"./\";\nimport type {\n AgentPromiseReturnType,\n AgentStub,\n OptionalAgentMethods,\n RequiredAgentMethods,\n StreamOptions,\n UntypedAgentStub\n} from \"./client\";\nimport { createStubProxy } from \"./client\";\nimport { camelCaseToKebabCase } from \"./utils\";\nimport { MessageType } from \"./types\";\n\ntype QueryObject = Record<string, string | null>;\n\ninterface CacheEntry {\n promise: Promise<QueryObject>;\n expiresAt: number;\n}\n\nconst queryCache = new Map<string, CacheEntry>();\n\nfunction createCacheKey(\n agentNamespace: string,\n name: string | undefined,\n subChainOrDeps: ReadonlyArray<{ agent: string; name: string }> | unknown[],\n deps?: unknown[]\n): string {\n // Backwards-compatible overload: if called with 3 args, the third\n // argument is `deps` and `subChain` defaults to empty. With 4 args,\n // the third is the sub-chain. This keeps existing callers (and\n // the `_testUtils` surface) working while letting new callers\n // include the nested chain in the cache key.\n //\n // Empty sub-chain must produce the same key as the old 3-arg\n // form, so nested-addressing code can opt-in without invalidating\n // existing caches.\n if (deps === undefined) {\n return JSON.stringify([\n agentNamespace,\n name || \"default\",\n ...(subChainOrDeps as unknown[])\n ]);\n }\n const subChain = subChainOrDeps as ReadonlyArray<{\n agent: string;\n name: string;\n }>;\n if (subChain.length === 0) {\n return JSON.stringify([agentNamespace, name || \"default\", ...deps]);\n }\n return JSON.stringify([\n agentNamespace,\n name || \"default\",\n subChain.map((s) => [s.agent, s.name]),\n ...deps\n ]);\n}\n\n/** Build a URL path tail `/sub/{agent-kebab}/{name}/...` from a sub chain. */\nfunction buildSubPath(\n subChain: ReadonlyArray<{ agent: string; name: string }>,\n extraPath?: string\n): string {\n if (subChain.length === 0) return extraPath ?? \"\";\n const parts = subChain.flatMap((step) => [\n \"sub\",\n camelCaseToKebabCase(step.agent),\n encodeURIComponent(step.name)\n ]);\n const combined = parts.join(\"/\");\n if (extraPath) {\n const trimmed = extraPath.startsWith(\"/\") ? extraPath.slice(1) : extraPath;\n return `${combined}/${trimmed}`;\n }\n return combined;\n}\n\nfunction getCacheEntry(key: string): CacheEntry | undefined {\n const entry = queryCache.get(key);\n if (!entry) return undefined;\n\n if (Date.now() >= entry.expiresAt) {\n queryCache.delete(key);\n return undefined;\n }\n\n return entry;\n}\n\nfunction setCacheEntry(\n key: string,\n promise: Promise<QueryObject>,\n cacheTtl: number\n): CacheEntry {\n const entry: CacheEntry = {\n promise,\n expiresAt: Date.now() + cacheTtl\n };\n queryCache.set(key, entry);\n return entry;\n}\n\nfunction deleteCacheEntry(key: string): void {\n queryCache.delete(key);\n}\n\n// Export for testing purposes\nexport const _testUtils = {\n queryCache,\n setCacheEntry,\n getCacheEntry,\n deleteCacheEntry,\n clearCache: () => queryCache.clear(),\n createStubProxy,\n createCacheKey\n};\n\n/**\n * Options for the useAgent hook\n * @template State Type of the Agent's state\n */\nexport type UseAgentOptions<State = unknown> = Omit<\n Parameters<typeof usePartySocket>[0],\n \"party\" | \"room\" | \"query\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the client connects to this path directly.\n * Server must handle routing manually (e.g., with getAgentByName + fetch).\n * @example\n * // Client connects to /user, server routes based on session\n * useAgent({ agent: \"UserAgent\", basePath: \"user\" })\n */\n basePath?: string;\n /** Query parameters - can be static object or async function */\n query?: QueryObject | (() => Promise<QueryObject>);\n /** Dependencies for async query caching */\n queryDeps?: unknown[];\n /** Cache TTL in milliseconds for auth tokens/time-sensitive data */\n cacheTtl?: number;\n /** Called when the Agent's state is updated */\n onStateUpdate?: (state: State, source: \"server\" | \"client\") => void;\n /** Called when a state update fails (e.g., connection is readonly) */\n onStateUpdateError?: (error: string) => void;\n /** Called when MCP server state is updated */\n onMcpUpdate?: (mcpServers: MCPServersState) => void;\n /**\n * Called when the server sends the agent's identity on connect.\n * Useful when using basePath, as the actual instance name is determined server-side.\n * @param name The actual agent instance name\n * @param agent The agent class name (kebab-case)\n */\n onIdentity?: (name: string, agent: string) => void;\n /**\n * Called when identity changes on reconnect (different instance than before).\n * If not provided and identity changes, a warning will be logged.\n * @param oldName Previous instance name\n * @param newName New instance name\n * @param oldAgent Previous agent class name\n * @param newAgent New agent class name\n */\n onIdentityChange?: (\n oldName: string,\n newName: string,\n oldAgent: string,\n newAgent: string\n ) => void;\n /**\n * Additional path to append to the URL.\n * Works with both standard routing and basePath.\n * @example\n * // With basePath: /user/settings\n * { basePath: \"user\", path: \"settings\" }\n * // Standard: /agents/my-agent/room/settings\n * { agent: \"MyAgent\", name: \"room\", path: \"settings\" }\n */\n path?: string;\n /**\n * Connect to a sub-agent (facet) via its parent. Flat array,\n * root-first. Each step addresses one parent↔child hop.\n *\n * The hook's returned `.agent` / `.name` report the **leaf**\n * identity (the deepest entry in `sub`), so downstream hooks\n * like `useAgentChat` see the child they actually talk to.\n * `.path` exposes the full chain for observability, deep links,\n * and reconnect keying.\n *\n * @example\n * ```ts\n * // Two-level nesting: Inbox (Alice) → Chat (abc)\n * useAgent({\n * agent: \"inbox\", name: userId,\n * sub: [{ agent: \"chat\", name: chatId }]\n * });\n *\n * // Three-level: tenant → inbox → chat\n * useAgent({\n * agent: \"tenant\", name: tenantId,\n * sub: [\n * { agent: \"inbox\", name: userId },\n * { agent: \"chat\", name: chatId }\n * ]\n * });\n * ```\n *\n * @experimental The API surface may change before stabilizing.\n */\n sub?: ReadonlyArray<{ agent: string; name: string }>;\n};\n\ntype OptionalArgsAgentMethodCall<AgentT> = <\n K extends keyof OptionalAgentMethods<AgentT>\n>(\n method: K,\n args?: Parameters<OptionalAgentMethods<AgentT>[K]>,\n streamOptions?: StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype RequiredArgsAgentMethodCall<AgentT> = <\n K extends keyof RequiredAgentMethods<AgentT>\n>(\n method: K,\n args: Parameters<RequiredAgentMethods<AgentT>[K]>,\n streamOptions?: StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype AgentMethodCall<AgentT> = OptionalArgsAgentMethodCall<AgentT> &\n RequiredArgsAgentMethodCall<AgentT>;\n\ntype UntypedAgentMethodCall = <T = unknown>(\n method: string,\n args?: unknown[],\n streamOptions?: StreamOptions\n) => Promise<T>;\n\n/**\n * React hook for connecting to an Agent\n */\nexport function useAgent<State = unknown>(\n options: UseAgentOptions<State>\n): Omit<PartySocket, \"path\"> & {\n agent: string;\n name: string;\n /** Full root-first address chain, including leaf. Single entry when `sub` isn't set. */\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n};\nexport function useAgent<\n AgentT extends {\n get state(): State;\n },\n State\n>(\n options: UseAgentOptions<State>\n): Omit<PartySocket, \"path\"> & {\n agent: string;\n name: string;\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: AgentMethodCall<AgentT>;\n stub: AgentStub<AgentT>;\n getHttpUrl: () => string;\n};\nexport function useAgent<State>(options: UseAgentOptions<unknown>): Omit<\n PartySocket,\n \"path\"\n> & {\n agent: string;\n name: string;\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall | AgentMethodCall<unknown>;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n} {\n const agentNamespace = camelCaseToKebabCase(options.agent);\n // NOTE: `path` is destructured out (as `userPath`) so it does NOT\n // end up in `restOptions`. Spreading `restOptions` after the\n // computed `path: combinedPath` would otherwise let the user's raw\n // `path` overwrite the combined sub-agent URL, dropping every\n // `/sub/{child}/{name}` segment on the way to the socket.\n const {\n query,\n queryDeps,\n cacheTtl,\n sub: subOption,\n path: userPath,\n ...restOptions\n } = options;\n\n const subChain = useMemo(\n () => (subOption ?? []).map((s) => ({ agent: s.agent, name: s.name })),\n // Stable serialization — deep changes re-memoize.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(subOption ?? [])]\n );\n\n // The \"leaf\" is the deepest entry in the chain; it's what\n // downstream code (useAgentChat etc.) should see as the\n // authoritative identity.\n const leafAgent =\n subChain.length > 0 ? subChain[subChain.length - 1].agent : options.agent;\n const leafName =\n subChain.length > 0\n ? subChain[subChain.length - 1].name\n : options.name || \"default\";\n\n // Full root-first chain, including the leaf. Exposed as `.path`\n // and used for cache keying so nested sessions with the same leaf\n // name don't collide.\n const fullPath = useMemo(\n () => [\n { agent: options.agent, name: options.name || \"default\" },\n ...subChain\n ],\n [options.agent, options.name, subChain]\n );\n\n // Keep track of pending RPC calls\n const pendingCallsRef = useRef(\n new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n }\n >()\n );\n\n const cacheKey = useMemo(\n () =>\n createCacheKey(agentNamespace, options.name, subChain, queryDeps || []),\n [agentNamespace, options.name, subChain, queryDeps]\n );\n\n // Track current cache key in a ref for use in onClose handler.\n // This ensures we invalidate the correct cache entry when the connection closes,\n // even if the component re-renders with different props before onClose fires.\n // We update synchronously during render (not in useEffect) to avoid race\n // conditions where onClose could fire before the effect runs.\n const cacheKeyRef = useRef(cacheKey);\n cacheKeyRef.current = cacheKey;\n\n const ttl = cacheTtl ?? 5 * 60 * 1000;\n\n // Track cache invalidation to force re-render when TTL expires\n const [cacheInvalidatedAt, setCacheInvalidatedAt] = useState<number>(0);\n\n // Disable socket while waiting for async query to refresh after disconnect\n const isAsyncQuery = query && typeof query === \"function\";\n const [awaitingQueryRefresh, setAwaitingQueryRefresh] = useState(false);\n\n // Get or create the query promise\n const queryPromise = useMemo(() => {\n // Re-run when cache is invalidated after TTL expiry\n void cacheInvalidatedAt;\n\n if (!query || typeof query !== \"function\") {\n return null;\n }\n\n // Always check cache first to deduplicate concurrent requests\n const cached = getCacheEntry(cacheKey);\n if (cached) {\n return cached.promise;\n }\n\n // Create new promise\n const promise = query().catch((error) => {\n console.error(\n `[useAgent] Query failed for agent \"${options.agent}\":`,\n error\n );\n deleteCacheEntry(cacheKey);\n throw error;\n });\n\n // Always cache to deduplicate concurrent requests\n setCacheEntry(cacheKey, promise, ttl);\n\n return promise;\n }, [cacheKey, query, options.agent, ttl, cacheInvalidatedAt]);\n\n // Schedule cache invalidation when TTL expires\n useEffect(() => {\n if (!queryPromise || ttl <= 0) return;\n\n const entry = getCacheEntry(cacheKey);\n if (!entry) return;\n\n const timeUntilExpiry = entry.expiresAt - Date.now();\n\n // Always set a timer (with min 0ms) to ensure cleanup function is returned\n const timer = setTimeout(\n () => {\n deleteCacheEntry(cacheKey);\n setCacheInvalidatedAt(Date.now());\n },\n Math.max(0, timeUntilExpiry)\n );\n\n return () => clearTimeout(timer);\n }, [cacheKey, queryPromise, ttl]);\n\n let resolvedQuery: QueryObject | undefined;\n\n if (query) {\n if (typeof query === \"function\") {\n // Use React's use() to resolve the promise\n const queryResult = use(queryPromise!);\n\n // Check for non-primitive values and warn\n if (queryResult) {\n for (const [key, value] of Object.entries(queryResult)) {\n if (\n value !== null &&\n value !== undefined &&\n typeof value !== \"string\" &&\n typeof value !== \"number\" &&\n typeof value !== \"boolean\"\n ) {\n console.warn(\n `[useAgent] Query parameter \"${key}\" is an object and will be converted to \"[object Object]\". ` +\n \"Query parameters should be string, number, boolean, or null.\"\n );\n }\n }\n resolvedQuery = queryResult;\n }\n } else {\n // Sync query - use directly\n resolvedQuery = query;\n }\n }\n\n // Re-enable socket after async query resolves\n useEffect(() => {\n if (awaitingQueryRefresh && resolvedQuery !== undefined) {\n setAwaitingQueryRefresh(false);\n }\n }, [awaitingQueryRefresh, resolvedQuery]);\n\n // Track agent state for reactivity — updated on server broadcasts and client setState\n const [agentState, setAgentState] = useState<State | undefined>(undefined);\n\n // Store identity in React state for reactivity. Seed with the\n // leaf's address — what the server will echo back in\n // `cf_agent_identity`.\n const [identity, setIdentity] = useState({\n name: leafName,\n agent: camelCaseToKebabCase(leafAgent),\n identified: false\n });\n\n // Track previous identity for change detection\n const previousIdentityRef = useRef<{\n name: string | null;\n agent: string | null;\n }>({ name: null, agent: null });\n\n // Ready promise - resolves when identity is received, resets on close\n const readyRef = useRef<\n { promise: Promise<void>; resolve: () => void } | undefined\n >(undefined);\n\n const resetReady = () => {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n readyRef.current = { promise, resolve: resolve! };\n };\n\n if (!readyRef.current) {\n resetReady();\n }\n\n // Combine the sub-agent chain with the user-provided `path`.\n // Order matters: `/sub/{child}/{name}/...` comes before `path` so\n // the server sees the hierarchy it expects.\n const combinedPath = useMemo(\n () => buildSubPath(subChain, userPath),\n [subChain, userPath]\n );\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n const socketOptions = options.basePath\n ? {\n basePath: options.basePath,\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions\n }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions\n };\n\n const socketEnabled = !awaitingQueryRefresh && (restOptions.enabled ?? true);\n\n const agent = usePartySocket({\n ...socketOptions,\n enabled: socketEnabled,\n onMessage: (message) => {\n if (typeof message.data === \"string\") {\n let parsedMessage: Record<string, unknown>;\n try {\n parsedMessage = JSON.parse(message.data);\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return options.onMessage?.(message);\n }\n if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) {\n const oldName = previousIdentityRef.current.name;\n const oldAgent = previousIdentityRef.current.agent;\n const newName = parsedMessage.name as string;\n const newAgent = parsedMessage.agent as string;\n\n // Update reactive state (triggers re-render)\n setIdentity({ name: newName, agent: newAgent, identified: true });\n\n // Resolve ready promise\n readyRef.current?.resolve();\n\n // Detect identity change on reconnect\n if (\n oldName !== null &&\n oldAgent !== null &&\n (oldName !== newName || oldAgent !== newAgent)\n ) {\n if (options.onIdentityChange) {\n options.onIdentityChange(oldName, newName, oldAgent, newAgent);\n } else {\n const agentChanged = oldAgent !== newAgent;\n const nameChanged = oldName !== newName;\n let changeDescription = \"\";\n if (agentChanged && nameChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\", instance \"${oldName}\" → \"${newName}\"`;\n } else if (agentChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\"`;\n } else {\n changeDescription = `instance \"${oldName}\" → \"${newName}\"`;\n }\n console.warn(\n `[agents] Identity changed on reconnect: ${changeDescription}. ` +\n \"This can happen with server-side routing (e.g., basePath with getAgentByName) \" +\n \"where the instance is determined by auth/session. \" +\n \"Provide onIdentityChange callback to handle this explicitly, \" +\n \"or ignore if this is expected for your routing pattern.\"\n );\n }\n }\n\n // Track for next change detection\n previousIdentityRef.current = { name: newName, agent: newAgent };\n\n // Call onIdentity callback\n options.onIdentity?.(newName, newAgent);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE) {\n setAgentState(parsedMessage.state as State);\n options.onStateUpdate?.(parsedMessage.state as State, \"server\");\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE_ERROR) {\n options.onStateUpdateError?.(parsedMessage.error as string);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_MCP_SERVERS) {\n options.onMcpUpdate?.(parsedMessage.mcp as MCPServersState);\n return;\n }\n if (parsedMessage.type === MessageType.RPC) {\n const response = parsedMessage as RPCResponse;\n const pending = pendingCallsRef.current.get(response.id);\n if (!pending) return;\n\n if (!response.success) {\n pending.reject(new Error(response.error));\n pendingCallsRef.current.delete(response.id);\n pending.stream?.onError?.(response.error);\n return;\n }\n\n // Handle streaming responses\n if (\"done\" in response) {\n if (response.done) {\n pending.resolve(response.result);\n pendingCallsRef.current.delete(response.id);\n pending.stream?.onDone?.(response.result);\n } else {\n pending.stream?.onChunk?.(response.result);\n }\n } else {\n // Non-streaming response\n pending.resolve(response.result);\n pendingCallsRef.current.delete(response.id);\n }\n return;\n }\n }\n options.onMessage?.(message);\n },\n onClose: (event: CloseEvent) => {\n // Reset ready state for next connection\n resetReady();\n setIdentity((prev) => ({ ...prev, identified: false }));\n\n // Pause reconnection for async queries until fresh query params are ready\n if (isAsyncQuery) {\n setAwaitingQueryRefresh(true);\n }\n\n // Invalidate cache and trigger re-render to fetch fresh query params\n deleteCacheEntry(cacheKeyRef.current);\n setCacheInvalidatedAt(Date.now());\n\n // Reject all pending calls (consistent with AgentClient behavior)\n const error = new Error(\"Connection closed\");\n for (const pending of pendingCallsRef.current.values()) {\n pending.reject(error);\n pending.stream?.onError?.(\"Connection closed\");\n }\n pendingCallsRef.current.clear();\n\n // Call user's onClose if provided\n options.onClose?.(event);\n }\n }) as PartySocket & {\n agent: string;\n name: string;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n };\n // Create the call method\n const call = useCallback(\n <T = unknown,>(\n method: string,\n args: unknown[] = [],\n streamOptions?: StreamOptions\n ): Promise<T> => {\n return new Promise((resolve, reject) => {\n const id = crypto.randomUUID();\n pendingCallsRef.current.set(id, {\n reject,\n resolve: resolve as (value: unknown) => void,\n stream: streamOptions\n });\n\n const request: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n\n agent.send(JSON.stringify(request));\n });\n },\n [agent]\n );\n\n agent.setState = (newState: State) => {\n agent.send(\n JSON.stringify({ state: newState, type: MessageType.CF_AGENT_STATE })\n );\n setAgentState(newState);\n options.onStateUpdate?.(newState, \"client\");\n };\n\n agent.call = call;\n // Use reactive identity state (updates on identity message)\n agent.agent = identity.agent;\n agent.name = identity.name;\n // Full root-first chain including the leaf. Computed from the\n // user-provided options — the server doesn't need to echo it\n // back because the client already knows. Write past the\n // PartySocket `.path: string` shape via an unknown cast — the\n // overload signatures expose this as `ReadonlyArray<...>`.\n (\n agent as unknown as { path: ReadonlyArray<{ agent: string; name: string }> }\n ).path = fullPath;\n agent.identified = identity.identified;\n agent.ready = readyRef.current!.promise;\n agent.state = agentState;\n // Memoize stub so it's referentially stable across renders\n // (call is already stable via useCallback)\n const stub = useMemo(() => createStubProxy(call), [call]);\n agent.stub = stub;\n agent.getHttpUrl = () => {\n // TODO: upstream to partysocket — expose an HTTP URL property\n // @ts-expect-error accessing protected PartySocket internals\n const wsUrl: string = (agent._url as string | null) || agent._pkurl || \"\";\n return wsUrl.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\");\n };\n\n // warn if agent isn't in lowercase\n if (identity.agent !== identity.agent.toLowerCase()) {\n console.warn(\n \"Agent name: \" +\n identity.agent +\n \" should probably be in lowercase. Received: \" +\n identity.agent\n );\n }\n\n // The overload signatures return `Omit<PartySocket, \"path\"> & { path: ... }`,\n // but `agent` is inferred as the raw PartySocket. Cast to satisfy\n // the overload contract — the runtime override of `agent.path`\n // above ensures the shape matches.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return agent as any;\n}\n"],"mappings":";;;;;;AAuBA,MAAM,6BAAa,IAAI,KAAyB;AAEhD,SAAS,eACP,gBACA,MACA,gBACA,MACQ;AAUR,KAAI,SAAS,KAAA,EACX,QAAO,KAAK,UAAU;EACpB;EACA,QAAQ;EACR,GAAI;EACL,CAAC;CAEJ,MAAM,WAAW;AAIjB,KAAI,SAAS,WAAW,EACtB,QAAO,KAAK,UAAU;EAAC;EAAgB,QAAQ;EAAW,GAAG;EAAK,CAAC;AAErE,QAAO,KAAK,UAAU;EACpB;EACA,QAAQ;EACR,SAAS,KAAK,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC;EACtC,GAAG;EACJ,CAAC;;;AAIJ,SAAS,aACP,UACA,WACQ;AACR,KAAI,SAAS,WAAW,EAAG,QAAO,aAAa;CAM/C,MAAM,WALQ,SAAS,SAAS,SAAS;EACvC;EACA,qBAAqB,KAAK,MAAM;EAChC,mBAAmB,KAAK,KAAK;EAC9B,CACqB,CAAC,KAAK,IAAI;AAChC,KAAI,UAEF,QAAO,GAAG,SAAS,GADH,UAAU,WAAW,IAAI,GAAG,UAAU,MAAM,EAAE,GAAG;AAGnE,QAAO;;AAGT,SAAS,cAAc,KAAqC;CAC1D,MAAM,QAAQ,WAAW,IAAI,IAAI;AACjC,KAAI,CAAC,MAAO,QAAO,KAAA;AAEnB,KAAI,KAAK,KAAK,IAAI,MAAM,WAAW;AACjC,aAAW,OAAO,IAAI;AACtB;;AAGF,QAAO;;AAGT,SAAS,cACP,KACA,SACA,UACY;CACZ,MAAM,QAAoB;EACxB;EACA,WAAW,KAAK,KAAK,GAAG;EACzB;AACD,YAAW,IAAI,KAAK,MAAM;AAC1B,QAAO;;AAGT,SAAS,iBAAiB,KAAmB;AAC3C,YAAW,OAAO,IAAI;;AAIxB,MAAa,aAAa;CACxB;CACA;CACA;CACA;CACA,kBAAkB,WAAW,OAAO;CACpC;CACA;CACD;AAiKD,SAAgB,SAAgB,SAc9B;CACA,MAAM,iBAAiB,qBAAqB,QAAQ,MAAM;CAM1D,MAAM,EACJ,OACA,WACA,UACA,KAAK,WACL,MAAM,UACN,GAAG,gBACD;CAEJ,MAAM,WAAW,eACR,aAAa,EAAE,EAAE,KAAK,OAAO;EAAE,OAAO,EAAE;EAAO,MAAM,EAAE;EAAM,EAAE,EAGtE,CAAC,KAAK,UAAU,aAAa,EAAE,CAAC,CAAC,CAClC;CAKD,MAAM,YACJ,SAAS,SAAS,IAAI,SAAS,SAAS,SAAS,GAAG,QAAQ,QAAQ;CACtE,MAAM,WACJ,SAAS,SAAS,IACd,SAAS,SAAS,SAAS,GAAG,OAC9B,QAAQ,QAAQ;CAKtB,MAAM,WAAW,cACT,CACJ;EAAE,OAAO,QAAQ;EAAO,MAAM,QAAQ,QAAQ;EAAW,EACzD,GAAG,SACJ,EACD;EAAC,QAAQ;EAAO,QAAQ;EAAM;EAAS,CACxC;CAGD,MAAM,kBAAkB,uBACtB,IAAI,KAOD,CACJ;CAED,MAAM,WAAW,cAEb,eAAe,gBAAgB,QAAQ,MAAM,UAAU,aAAa,EAAE,CAAC,EACzE;EAAC;EAAgB,QAAQ;EAAM;EAAU;EAAU,CACpD;CAOD,MAAM,cAAc,OAAO,SAAS;AACpC,aAAY,UAAU;CAEtB,MAAM,MAAM,YAAY,MAAS;CAGjC,MAAM,CAAC,oBAAoB,yBAAyB,SAAiB,EAAE;CAGvE,MAAM,eAAe,SAAS,OAAO,UAAU;CAC/C,MAAM,CAAC,sBAAsB,2BAA2B,SAAS,MAAM;CAGvE,MAAM,eAAe,cAAc;AAIjC,MAAI,CAAC,SAAS,OAAO,UAAU,WAC7B,QAAO;EAIT,MAAM,SAAS,cAAc,SAAS;AACtC,MAAI,OACF,QAAO,OAAO;EAIhB,MAAM,UAAU,OAAO,CAAC,OAAO,UAAU;AACvC,WAAQ,MACN,sCAAsC,QAAQ,MAAM,KACpD,MACD;AACD,oBAAiB,SAAS;AAC1B,SAAM;IACN;AAGF,gBAAc,UAAU,SAAS,IAAI;AAErC,SAAO;IACN;EAAC;EAAU;EAAO,QAAQ;EAAO;EAAK;EAAmB,CAAC;AAG7D,iBAAgB;AACd,MAAI,CAAC,gBAAgB,OAAO,EAAG;EAE/B,MAAM,QAAQ,cAAc,SAAS;AACrC,MAAI,CAAC,MAAO;EAEZ,MAAM,kBAAkB,MAAM,YAAY,KAAK,KAAK;EAGpD,MAAM,QAAQ,iBACN;AACJ,oBAAiB,SAAS;AAC1B,yBAAsB,KAAK,KAAK,CAAC;KAEnC,KAAK,IAAI,GAAG,gBAAgB,CAC7B;AAED,eAAa,aAAa,MAAM;IAC/B;EAAC;EAAU;EAAc;EAAI,CAAC;CAEjC,IAAI;AAEJ,KAAI,MACF,KAAI,OAAO,UAAU,YAAY;EAE/B,MAAM,cAAc,IAAI,aAAc;AAGtC,MAAI,aAAa;AACf,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,CACpD,KACE,UAAU,QACV,UAAU,KAAA,KACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,UAEjB,SAAQ,KACN,+BAA+B,IAAI,yHAEpC;AAGL,mBAAgB;;OAIlB,iBAAgB;AAKpB,iBAAgB;AACd,MAAI,wBAAwB,kBAAkB,KAAA,EAC5C,yBAAwB,MAAM;IAE/B,CAAC,sBAAsB,cAAc,CAAC;CAGzC,MAAM,CAAC,YAAY,iBAAiB,SAA4B,KAAA,EAAU;CAK1E,MAAM,CAAC,UAAU,eAAe,SAAS;EACvC,MAAM;EACN,OAAO,qBAAqB,UAAU;EACtC,YAAY;EACb,CAAC;CAGF,MAAM,sBAAsB,OAGzB;EAAE,MAAM;EAAM,OAAO;EAAM,CAAC;CAG/B,MAAM,WAAW,OAEf,KAAA,EAAU;CAEZ,MAAM,mBAAmB;EACvB,IAAI;AAIJ,WAAS,UAAU;GAAE,SAAA,IAHD,SAAe,MAAM;AACvC,cAAU;KAEgB;GAAW;GAAU;;AAGnD,KAAI,CAAC,SAAS,QACZ,aAAY;CAMd,MAAM,eAAe,cACb,aAAa,UAAU,SAAS,EACtC,CAAC,UAAU,SAAS,CACrB;CAGD,MAAM,gBAAgB,QAAQ,WAC1B;EACE,UAAU,QAAQ;EAClB,MAAM,gBAAgB,KAAA;EACtB,OAAO;EACP,GAAG;EACJ,GACD;EACE,OAAO;EACP,QAAQ;EACR,MAAM,QAAQ,QAAQ;EACtB,MAAM,gBAAgB,KAAA;EACtB,OAAO;EACP,GAAG;EACJ;CAEL,MAAM,gBAAgB,CAAC,yBAAyB,YAAY,WAAW;CAEvE,MAAM,QAAQ,eAAe;EAC3B,GAAG;EACH,SAAS;EACT,YAAY,YAAY;AACtB,OAAI,OAAO,QAAQ,SAAS,UAAU;IACpC,IAAI;AACJ,QAAI;AACF,qBAAgB,KAAK,MAAM,QAAQ,KAAK;aACjC,QAAQ;AAGf,YAAO,QAAQ,YAAY,QAAQ;;AAErC,QAAI,cAAc,SAAA,qBAAwC;KACxD,MAAM,UAAU,oBAAoB,QAAQ;KAC5C,MAAM,WAAW,oBAAoB,QAAQ;KAC7C,MAAM,UAAU,cAAc;KAC9B,MAAM,WAAW,cAAc;AAG/B,iBAAY;MAAE,MAAM;MAAS,OAAO;MAAU,YAAY;MAAM,CAAC;AAGjE,cAAS,SAAS,SAAS;AAG3B,SACE,YAAY,QACZ,aAAa,SACZ,YAAY,WAAW,aAAa,UAErC,KAAI,QAAQ,iBACV,SAAQ,iBAAiB,SAAS,SAAS,UAAU,SAAS;UACzD;MACL,MAAM,eAAe,aAAa;MAClC,MAAM,cAAc,YAAY;MAChC,IAAI,oBAAoB;AACxB,UAAI,gBAAgB,YAClB,qBAAoB,UAAU,SAAS,OAAO,SAAS,eAAe,QAAQ,OAAO,QAAQ;eACpF,aACT,qBAAoB,UAAU,SAAS,OAAO,SAAS;UAEvD,qBAAoB,aAAa,QAAQ,OAAO,QAAQ;AAE1D,cAAQ,KACN,2CAA2C,kBAAkB,wPAK9D;;AAKL,yBAAoB,UAAU;MAAE,MAAM;MAAS,OAAO;MAAU;AAGhE,aAAQ,aAAa,SAAS,SAAS;AACvC;;AAEF,QAAI,cAAc,SAAA,kBAAqC;AACrD,mBAAc,cAAc,MAAe;AAC3C,aAAQ,gBAAgB,cAAc,OAAgB,SAAS;AAC/D;;AAEF,QAAI,cAAc,SAAA,wBAA2C;AAC3D,aAAQ,qBAAqB,cAAc,MAAgB;AAC3D;;AAEF,QAAI,cAAc,SAAA,wBAA2C;AAC3D,aAAQ,cAAc,cAAc,IAAuB;AAC3D;;AAEF,QAAI,cAAc,SAAA,OAA0B;KAC1C,MAAM,WAAW;KACjB,MAAM,UAAU,gBAAgB,QAAQ,IAAI,SAAS,GAAG;AACxD,SAAI,CAAC,QAAS;AAEd,SAAI,CAAC,SAAS,SAAS;AACrB,cAAQ,OAAO,IAAI,MAAM,SAAS,MAAM,CAAC;AACzC,sBAAgB,QAAQ,OAAO,SAAS,GAAG;AAC3C,cAAQ,QAAQ,UAAU,SAAS,MAAM;AACzC;;AAIF,SAAI,UAAU,SACZ,KAAI,SAAS,MAAM;AACjB,cAAQ,QAAQ,SAAS,OAAO;AAChC,sBAAgB,QAAQ,OAAO,SAAS,GAAG;AAC3C,cAAQ,QAAQ,SAAS,SAAS,OAAO;WAEzC,SAAQ,QAAQ,UAAU,SAAS,OAAO;UAEvC;AAEL,cAAQ,QAAQ,SAAS,OAAO;AAChC,sBAAgB,QAAQ,OAAO,SAAS,GAAG;;AAE7C;;;AAGJ,WAAQ,YAAY,QAAQ;;EAE9B,UAAU,UAAsB;AAE9B,eAAY;AACZ,gBAAa,UAAU;IAAE,GAAG;IAAM,YAAY;IAAO,EAAE;AAGvD,OAAI,aACF,yBAAwB,KAAK;AAI/B,oBAAiB,YAAY,QAAQ;AACrC,yBAAsB,KAAK,KAAK,CAAC;GAGjC,MAAM,wBAAQ,IAAI,MAAM,oBAAoB;AAC5C,QAAK,MAAM,WAAW,gBAAgB,QAAQ,QAAQ,EAAE;AACtD,YAAQ,OAAO,MAAM;AACrB,YAAQ,QAAQ,UAAU,oBAAoB;;AAEhD,mBAAgB,QAAQ,OAAO;AAG/B,WAAQ,UAAU,MAAM;;EAE3B,CAAC;CAYF,MAAM,OAAO,aAET,QACA,OAAkB,EAAE,EACpB,kBACe;AACf,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,KAAK,OAAO,YAAY;AAC9B,mBAAgB,QAAQ,IAAI,IAAI;IAC9B;IACS;IACT,QAAQ;IACT,CAAC;GAEF,MAAM,UAAsB;IAC1B;IACA;IACA;IACA,MAAA;IACD;AAED,SAAM,KAAK,KAAK,UAAU,QAAQ,CAAC;IACnC;IAEJ,CAAC,MAAM,CACR;AAED,OAAM,YAAY,aAAoB;AACpC,QAAM,KACJ,KAAK,UAAU;GAAE,OAAO;GAAU,MAAA;GAAkC,CAAC,CACtE;AACD,gBAAc,SAAS;AACvB,UAAQ,gBAAgB,UAAU,SAAS;;AAG7C,OAAM,OAAO;AAEb,OAAM,QAAQ,SAAS;AACvB,OAAM,OAAO,SAAS;AAOpB,OACA,OAAO;AACT,OAAM,aAAa,SAAS;AAC5B,OAAM,QAAQ,SAAS,QAAS;AAChC,OAAM,QAAQ;AAId,OAAM,OADO,cAAc,gBAAgB,KAAK,EAAE,CAAC,KAAK,CACvC;AACjB,OAAM,mBAAmB;AAIvB,UADuB,MAAM,QAA0B,MAAM,UAAU,IAC1D,QAAQ,SAAS,UAAU,CAAC,QAAQ,UAAU,WAAW;;AAIxE,KAAI,SAAS,UAAU,SAAS,MAAM,aAAa,CACjD,SAAQ,KACN,iBACE,SAAS,QACT,iDACA,SAAS,MACZ;AAQH,QAAO"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import type { PartySocket } from \"partysocket\";\nimport { usePartySocket } from \"partysocket/react\";\nimport { useCallback, useRef, use, useMemo, useState, useEffect } from \"react\";\nimport type { MCPServersState, RPCRequest, RPCResponse } from \"./\";\nimport type {\n AgentPromiseReturnType,\n AgentStub,\n OptionalAgentMethods,\n RequiredAgentMethods,\n StreamOptions,\n UntypedAgentStub\n} from \"./client\";\nimport { createStubProxy } from \"./client\";\nimport { camelCaseToKebabCase } from \"./utils\";\nimport { MessageType } from \"./types\";\nimport {\n applyAgentToolEvent,\n createAgentToolEventState,\n type AgentToolEventMessage,\n type AgentToolEventState,\n type AgentToolRunState\n} from \"./chat/agent-tools\";\n\ntype QueryObject = Record<string, string | null>;\n\ninterface CacheEntry {\n promise: Promise<QueryObject>;\n expiresAt: number;\n}\n\nconst queryCache = new Map<string, CacheEntry>();\n\nfunction createCacheKey(\n agentNamespace: string,\n name: string | undefined,\n subChainOrDeps: ReadonlyArray<{ agent: string; name: string }> | unknown[],\n deps?: unknown[]\n): string {\n // Backwards-compatible overload: if called with 3 args, the third\n // argument is `deps` and `subChain` defaults to empty. With 4 args,\n // the third is the sub-chain. This keeps existing callers (and\n // the `_testUtils` surface) working while letting new callers\n // include the nested chain in the cache key.\n //\n // Empty sub-chain must produce the same key as the old 3-arg\n // form, so nested-addressing code can opt-in without invalidating\n // existing caches.\n if (deps === undefined) {\n return JSON.stringify([\n agentNamespace,\n name || \"default\",\n ...(subChainOrDeps as unknown[])\n ]);\n }\n const subChain = subChainOrDeps as ReadonlyArray<{\n agent: string;\n name: string;\n }>;\n if (subChain.length === 0) {\n return JSON.stringify([agentNamespace, name || \"default\", ...deps]);\n }\n return JSON.stringify([\n agentNamespace,\n name || \"default\",\n subChain.map((s) => [s.agent, s.name]),\n ...deps\n ]);\n}\n\n/** Build a URL path tail `/sub/{agent-kebab}/{name}/...` from a sub chain. */\nfunction buildSubPath(\n subChain: ReadonlyArray<{ agent: string; name: string }>,\n extraPath?: string\n): string {\n if (subChain.length === 0) return extraPath ?? \"\";\n const parts = subChain.flatMap((step) => [\n \"sub\",\n camelCaseToKebabCase(step.agent),\n encodeURIComponent(step.name)\n ]);\n const combined = parts.join(\"/\");\n if (extraPath) {\n const trimmed = extraPath.startsWith(\"/\") ? extraPath.slice(1) : extraPath;\n return `${combined}/${trimmed}`;\n }\n return combined;\n}\n\nfunction getCacheEntry(key: string): CacheEntry | undefined {\n const entry = queryCache.get(key);\n if (!entry) return undefined;\n\n if (Date.now() >= entry.expiresAt) {\n queryCache.delete(key);\n return undefined;\n }\n\n return entry;\n}\n\nfunction setCacheEntry(\n key: string,\n promise: Promise<QueryObject>,\n cacheTtl: number\n): CacheEntry {\n const entry: CacheEntry = {\n promise,\n expiresAt: Date.now() + cacheTtl\n };\n queryCache.set(key, entry);\n return entry;\n}\n\nfunction deleteCacheEntry(key: string): void {\n queryCache.delete(key);\n}\n\n// Export for testing purposes\nexport const _testUtils = {\n queryCache,\n setCacheEntry,\n getCacheEntry,\n deleteCacheEntry,\n clearCache: () => queryCache.clear(),\n createStubProxy,\n createCacheKey\n};\n\n/**\n * Options for the useAgent hook\n * @template State Type of the Agent's state\n */\nexport type UseAgentOptions<State = unknown> = Omit<\n Parameters<typeof usePartySocket>[0],\n \"party\" | \"room\" | \"query\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the client connects to this path directly.\n * Server must handle routing manually (e.g., with getAgentByName + fetch).\n * @example\n * // Client connects to /user, server routes based on session\n * useAgent({ agent: \"UserAgent\", basePath: \"user\" })\n */\n basePath?: string;\n /** Query parameters - can be static object or async function */\n query?: QueryObject | (() => Promise<QueryObject>);\n /** Dependencies for async query caching */\n queryDeps?: unknown[];\n /** Cache TTL in milliseconds for auth tokens/time-sensitive data */\n cacheTtl?: number;\n /** Called when the Agent's state is updated */\n onStateUpdate?: (state: State, source: \"server\" | \"client\") => void;\n /** Called when a state update fails (e.g., connection is readonly) */\n onStateUpdateError?: (error: string) => void;\n /** Called when MCP server state is updated */\n onMcpUpdate?: (mcpServers: MCPServersState) => void;\n /**\n * Called when the server sends the agent's identity on connect.\n * Useful when using basePath, as the actual instance name is determined server-side.\n * @param name The actual agent instance name\n * @param agent The agent class name (kebab-case)\n */\n onIdentity?: (name: string, agent: string) => void;\n /**\n * Called when identity changes on reconnect (different instance than before).\n * If not provided and identity changes, a warning will be logged.\n * @param oldName Previous instance name\n * @param newName New instance name\n * @param oldAgent Previous agent class name\n * @param newAgent New agent class name\n */\n onIdentityChange?: (\n oldName: string,\n newName: string,\n oldAgent: string,\n newAgent: string\n ) => void;\n /**\n * Additional path to append to the URL.\n * Works with both standard routing and basePath.\n * @example\n * // With basePath: /user/settings\n * { basePath: \"user\", path: \"settings\" }\n * // Standard: /agents/my-agent/room/settings\n * { agent: \"MyAgent\", name: \"room\", path: \"settings\" }\n */\n path?: string;\n /**\n * Connect to a sub-agent (facet) via its parent. Flat array,\n * root-first. Each step addresses one parent↔child hop.\n *\n * The hook's returned `.agent` / `.name` report the **leaf**\n * identity (the deepest entry in `sub`), so downstream hooks\n * like `useAgentChat` see the child they actually talk to.\n * `.path` exposes the full chain for observability, deep links,\n * and reconnect keying.\n *\n * @example\n * ```ts\n * // Two-level nesting: Inbox (Alice) → Chat (abc)\n * useAgent({\n * agent: \"inbox\", name: userId,\n * sub: [{ agent: \"chat\", name: chatId }]\n * });\n *\n * // Three-level: tenant → inbox → chat\n * useAgent({\n * agent: \"tenant\", name: tenantId,\n * sub: [\n * { agent: \"inbox\", name: userId },\n * { agent: \"chat\", name: chatId }\n * ]\n * });\n * ```\n *\n * @experimental The API surface may change before stabilizing.\n */\n sub?: ReadonlyArray<{ agent: string; name: string }>;\n};\n\ntype OptionalArgsAgentMethodCall<AgentT> = <\n K extends keyof OptionalAgentMethods<AgentT>\n>(\n method: K,\n args?: Parameters<OptionalAgentMethods<AgentT>[K]>,\n streamOptions?: StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype RequiredArgsAgentMethodCall<AgentT> = <\n K extends keyof RequiredAgentMethods<AgentT>\n>(\n method: K,\n args: Parameters<RequiredAgentMethods<AgentT>[K]>,\n streamOptions?: StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype AgentMethodCall<AgentT> = OptionalArgsAgentMethodCall<AgentT> &\n RequiredArgsAgentMethodCall<AgentT>;\n\ntype UntypedAgentMethodCall = <T = unknown>(\n method: string,\n args?: unknown[],\n streamOptions?: StreamOptions\n) => Promise<T>;\n\n/**\n * React hook for connecting to an Agent\n */\nexport function useAgent<State = unknown>(\n options: UseAgentOptions<State>\n): Omit<PartySocket, \"path\"> & {\n agent: string;\n name: string;\n /** Full root-first address chain, including leaf. Single entry when `sub` isn't set. */\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n};\nexport function useAgent<\n AgentT extends {\n get state(): State;\n },\n State\n>(\n options: UseAgentOptions<State>\n): Omit<PartySocket, \"path\"> & {\n agent: string;\n name: string;\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: AgentMethodCall<AgentT>;\n stub: AgentStub<AgentT>;\n getHttpUrl: () => string;\n};\nexport function useAgent<State>(options: UseAgentOptions<unknown>): Omit<\n PartySocket,\n \"path\"\n> & {\n agent: string;\n name: string;\n path: ReadonlyArray<{ agent: string; name: string }>;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall | AgentMethodCall<unknown>;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n} {\n const agentNamespace = camelCaseToKebabCase(options.agent);\n // NOTE: `path` is destructured out (as `userPath`) so it does NOT\n // end up in `restOptions`. Spreading `restOptions` after the\n // computed `path: combinedPath` would otherwise let the user's raw\n // `path` overwrite the combined sub-agent URL, dropping every\n // `/sub/{child}/{name}` segment on the way to the socket.\n const {\n query,\n queryDeps,\n cacheTtl,\n sub: subOption,\n path: userPath,\n ...restOptions\n } = options;\n\n const subChain = useMemo(\n () => (subOption ?? []).map((s) => ({ agent: s.agent, name: s.name })),\n // Stable serialization — deep changes re-memoize.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(subOption ?? [])]\n );\n\n // The \"leaf\" is the deepest entry in the chain; it's what\n // downstream code (useAgentChat etc.) should see as the\n // authoritative identity.\n const leafAgent =\n subChain.length > 0 ? subChain[subChain.length - 1].agent : options.agent;\n const leafName =\n subChain.length > 0\n ? subChain[subChain.length - 1].name\n : options.name || \"default\";\n\n // Full root-first chain, including the leaf. Exposed as `.path`\n // and used for cache keying so nested sessions with the same leaf\n // name don't collide.\n const fullPath = useMemo(\n () => [\n { agent: options.agent, name: options.name || \"default\" },\n ...subChain\n ],\n [options.agent, options.name, subChain]\n );\n\n // Keep track of pending RPC calls\n const pendingCallsRef = useRef(\n new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n }\n >()\n );\n\n const cacheKey = useMemo(\n () =>\n createCacheKey(agentNamespace, options.name, subChain, queryDeps || []),\n [agentNamespace, options.name, subChain, queryDeps]\n );\n\n // Track current cache key in a ref for use in onClose handler.\n // This ensures we invalidate the correct cache entry when the connection closes,\n // even if the component re-renders with different props before onClose fires.\n // We update synchronously during render (not in useEffect) to avoid race\n // conditions where onClose could fire before the effect runs.\n const cacheKeyRef = useRef(cacheKey);\n cacheKeyRef.current = cacheKey;\n\n const ttl = cacheTtl ?? 5 * 60 * 1000;\n\n // Track cache invalidation to force re-render when TTL expires\n const [cacheInvalidatedAt, setCacheInvalidatedAt] = useState<number>(0);\n\n // Disable socket while waiting for async query to refresh after disconnect\n const isAsyncQuery = query && typeof query === \"function\";\n const [awaitingQueryRefresh, setAwaitingQueryRefresh] = useState(false);\n\n // Get or create the query promise\n const queryPromise = useMemo(() => {\n // Re-run when cache is invalidated after TTL expiry\n void cacheInvalidatedAt;\n\n if (!query || typeof query !== \"function\") {\n return null;\n }\n\n // Always check cache first to deduplicate concurrent requests\n const cached = getCacheEntry(cacheKey);\n if (cached) {\n return cached.promise;\n }\n\n // Create new promise\n const promise = query().catch((error) => {\n console.error(\n `[useAgent] Query failed for agent \"${options.agent}\":`,\n error\n );\n deleteCacheEntry(cacheKey);\n throw error;\n });\n\n // Always cache to deduplicate concurrent requests\n setCacheEntry(cacheKey, promise, ttl);\n\n return promise;\n }, [cacheKey, query, options.agent, ttl, cacheInvalidatedAt]);\n\n // Schedule cache invalidation when TTL expires\n useEffect(() => {\n if (!queryPromise || ttl <= 0) return;\n\n const entry = getCacheEntry(cacheKey);\n if (!entry) return;\n\n const timeUntilExpiry = entry.expiresAt - Date.now();\n\n // Always set a timer (with min 0ms) to ensure cleanup function is returned\n const timer = setTimeout(\n () => {\n deleteCacheEntry(cacheKey);\n setCacheInvalidatedAt(Date.now());\n },\n Math.max(0, timeUntilExpiry)\n );\n\n return () => clearTimeout(timer);\n }, [cacheKey, queryPromise, ttl]);\n\n let resolvedQuery: QueryObject | undefined;\n\n if (query) {\n if (typeof query === \"function\") {\n // Use React's use() to resolve the promise\n const queryResult = use(queryPromise!);\n\n // Check for non-primitive values and warn\n if (queryResult) {\n for (const [key, value] of Object.entries(queryResult)) {\n if (\n value !== null &&\n value !== undefined &&\n typeof value !== \"string\" &&\n typeof value !== \"number\" &&\n typeof value !== \"boolean\"\n ) {\n console.warn(\n `[useAgent] Query parameter \"${key}\" is an object and will be converted to \"[object Object]\". ` +\n \"Query parameters should be string, number, boolean, or null.\"\n );\n }\n }\n resolvedQuery = queryResult;\n }\n } else {\n // Sync query - use directly\n resolvedQuery = query;\n }\n }\n\n // Re-enable socket after async query resolves\n useEffect(() => {\n if (awaitingQueryRefresh && resolvedQuery !== undefined) {\n setAwaitingQueryRefresh(false);\n }\n }, [awaitingQueryRefresh, resolvedQuery]);\n\n // Track agent state for reactivity — updated on server broadcasts and client setState\n const [agentState, setAgentState] = useState<State | undefined>(undefined);\n\n // Store identity in React state for reactivity. Seed with the\n // leaf's address — what the server will echo back in\n // `cf_agent_identity`.\n const [identity, setIdentity] = useState({\n name: leafName,\n agent: camelCaseToKebabCase(leafAgent),\n identified: false\n });\n\n // Track previous identity for change detection\n const previousIdentityRef = useRef<{\n name: string | null;\n agent: string | null;\n }>({ name: null, agent: null });\n\n // Ready promise - resolves when identity is received, resets on close\n const readyRef = useRef<\n { promise: Promise<void>; resolve: () => void } | undefined\n >(undefined);\n\n const resetReady = () => {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n readyRef.current = { promise, resolve: resolve! };\n };\n\n if (!readyRef.current) {\n resetReady();\n }\n\n // Combine the sub-agent chain with the user-provided `path`.\n // Order matters: `/sub/{child}/{name}/...` comes before `path` so\n // the server sees the hierarchy it expects.\n const combinedPath = useMemo(\n () => buildSubPath(subChain, userPath),\n [subChain, userPath]\n );\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n const socketOptions = options.basePath\n ? {\n basePath: options.basePath,\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions\n }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions\n };\n\n const socketEnabled = !awaitingQueryRefresh && (restOptions.enabled ?? true);\n\n const agent = usePartySocket({\n ...socketOptions,\n enabled: socketEnabled,\n onMessage: (message) => {\n if (typeof message.data === \"string\") {\n let parsedMessage: Record<string, unknown>;\n try {\n parsedMessage = JSON.parse(message.data);\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return options.onMessage?.(message);\n }\n if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) {\n const oldName = previousIdentityRef.current.name;\n const oldAgent = previousIdentityRef.current.agent;\n const newName = parsedMessage.name as string;\n const newAgent = parsedMessage.agent as string;\n\n // Update reactive state (triggers re-render)\n setIdentity({ name: newName, agent: newAgent, identified: true });\n\n // Resolve ready promise\n readyRef.current?.resolve();\n\n // Detect identity change on reconnect\n if (\n oldName !== null &&\n oldAgent !== null &&\n (oldName !== newName || oldAgent !== newAgent)\n ) {\n if (options.onIdentityChange) {\n options.onIdentityChange(oldName, newName, oldAgent, newAgent);\n } else {\n const agentChanged = oldAgent !== newAgent;\n const nameChanged = oldName !== newName;\n let changeDescription = \"\";\n if (agentChanged && nameChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\", instance \"${oldName}\" → \"${newName}\"`;\n } else if (agentChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\"`;\n } else {\n changeDescription = `instance \"${oldName}\" → \"${newName}\"`;\n }\n console.warn(\n `[agents] Identity changed on reconnect: ${changeDescription}. ` +\n \"This can happen with server-side routing (e.g., basePath with getAgentByName) \" +\n \"where the instance is determined by auth/session. \" +\n \"Provide onIdentityChange callback to handle this explicitly, \" +\n \"or ignore if this is expected for your routing pattern.\"\n );\n }\n }\n\n // Track for next change detection\n previousIdentityRef.current = { name: newName, agent: newAgent };\n\n // Call onIdentity callback\n options.onIdentity?.(newName, newAgent);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE) {\n setAgentState(parsedMessage.state as State);\n options.onStateUpdate?.(parsedMessage.state as State, \"server\");\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE_ERROR) {\n options.onStateUpdateError?.(parsedMessage.error as string);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_MCP_SERVERS) {\n options.onMcpUpdate?.(parsedMessage.mcp as MCPServersState);\n return;\n }\n if (parsedMessage.type === MessageType.RPC) {\n const response = parsedMessage as RPCResponse;\n const pending = pendingCallsRef.current.get(response.id);\n if (!pending) return;\n\n if (!response.success) {\n pending.reject(new Error(response.error));\n pendingCallsRef.current.delete(response.id);\n pending.stream?.onError?.(response.error);\n return;\n }\n\n // Handle streaming responses\n if (\"done\" in response) {\n if (response.done) {\n pending.resolve(response.result);\n pendingCallsRef.current.delete(response.id);\n pending.stream?.onDone?.(response.result);\n } else {\n pending.stream?.onChunk?.(response.result);\n }\n } else {\n // Non-streaming response\n pending.resolve(response.result);\n pendingCallsRef.current.delete(response.id);\n }\n return;\n }\n }\n options.onMessage?.(message);\n },\n onClose: (event: CloseEvent) => {\n // Reset ready state for next connection\n resetReady();\n setIdentity((prev) => ({ ...prev, identified: false }));\n\n // Pause reconnection for async queries until fresh query params are ready\n if (isAsyncQuery) {\n setAwaitingQueryRefresh(true);\n }\n\n // Invalidate cache and trigger re-render to fetch fresh query params\n deleteCacheEntry(cacheKeyRef.current);\n setCacheInvalidatedAt(Date.now());\n\n // Reject all pending calls (consistent with AgentClient behavior)\n const error = new Error(\"Connection closed\");\n for (const pending of pendingCallsRef.current.values()) {\n pending.reject(error);\n pending.stream?.onError?.(\"Connection closed\");\n }\n pendingCallsRef.current.clear();\n\n // Call user's onClose if provided\n options.onClose?.(event);\n }\n }) as PartySocket & {\n agent: string;\n name: string;\n identified: boolean;\n ready: Promise<void>;\n state: State | undefined;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n };\n // Create the call method\n const call = useCallback(\n <T = unknown,>(\n method: string,\n args: unknown[] = [],\n streamOptions?: StreamOptions\n ): Promise<T> => {\n return new Promise((resolve, reject) => {\n const id = crypto.randomUUID();\n pendingCallsRef.current.set(id, {\n reject,\n resolve: resolve as (value: unknown) => void,\n stream: streamOptions\n });\n\n const request: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n\n agent.send(JSON.stringify(request));\n });\n },\n [agent]\n );\n\n agent.setState = (newState: State) => {\n agent.send(\n JSON.stringify({ state: newState, type: MessageType.CF_AGENT_STATE })\n );\n setAgentState(newState);\n options.onStateUpdate?.(newState, \"client\");\n };\n\n agent.call = call;\n // Use reactive identity state (updates on identity message)\n agent.agent = identity.agent;\n agent.name = identity.name;\n // Full root-first chain including the leaf. Computed from the\n // user-provided options — the server doesn't need to echo it\n // back because the client already knows. Write past the\n // PartySocket `.path: string` shape via an unknown cast — the\n // overload signatures expose this as `ReadonlyArray<...>`.\n (\n agent as unknown as { path: ReadonlyArray<{ agent: string; name: string }> }\n ).path = fullPath;\n agent.identified = identity.identified;\n agent.ready = readyRef.current!.promise;\n agent.state = agentState;\n // Memoize stub so it's referentially stable across renders\n // (call is already stable via useCallback)\n const stub = useMemo(() => createStubProxy(call), [call]);\n agent.stub = stub;\n agent.getHttpUrl = () => {\n // TODO: upstream to partysocket — expose an HTTP URL property\n // @ts-expect-error accessing protected PartySocket internals\n const wsUrl: string = (agent._url as string | null) || agent._pkurl || \"\";\n return wsUrl.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\");\n };\n\n // warn if agent isn't in lowercase\n if (identity.agent !== identity.agent.toLowerCase()) {\n console.warn(\n \"Agent name: \" +\n identity.agent +\n \" should probably be in lowercase. Received: \" +\n identity.agent\n );\n }\n\n // The overload signatures return `Omit<PartySocket, \"path\"> & { path: ... }`,\n // but `agent` is inferred as the raw PartySocket. Cast to satisfy\n // the overload contract — the runtime override of `agent.path`\n // above ensures the shape matches.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return agent as any;\n}\n\ntype AgentToolEventAgent = Pick<\n PartySocket,\n \"addEventListener\" | \"removeEventListener\"\n>;\n\nfunction agentToolDedupeKey(message: AgentToolEventMessage): string {\n return [\n message.parentToolCallId ?? \"\",\n message.event.runId,\n String(message.sequence)\n ].join(\"\\0\");\n}\n\nexport function useAgentToolEvents(options: { agent: AgentToolEventAgent }): {\n runsById: Record<string, AgentToolRunState>;\n runsByToolCallId: Record<string, AgentToolRunState[]>;\n unboundRuns: AgentToolRunState[];\n getRunsForToolCall(toolCallId: string): AgentToolRunState[];\n resetLocalState(): void;\n} {\n const { agent } = options;\n const [state, setState] = useState<AgentToolEventState>(() =>\n createAgentToolEventState()\n );\n const seenRef = useRef(new Set<string>());\n\n useEffect(() => {\n const onMessage = (event: MessageEvent) => {\n if (typeof event.data !== \"string\") return;\n let message: AgentToolEventMessage;\n try {\n message = JSON.parse(event.data) as AgentToolEventMessage;\n } catch {\n return;\n }\n if (message.type !== \"agent-tool-event\") return;\n const key = agentToolDedupeKey(message);\n if (seenRef.current.has(key)) return;\n seenRef.current.add(key);\n setState((prev) => applyAgentToolEvent(prev, message));\n };\n\n agent.addEventListener(\"message\", onMessage);\n return () => agent.removeEventListener(\"message\", onMessage);\n }, [agent]);\n\n const resetLocalState = useCallback(() => {\n seenRef.current.clear();\n setState(createAgentToolEventState());\n }, []);\n\n const getRunsForToolCall = useCallback(\n (toolCallId: string) => state.runsByToolCallId[toolCallId] ?? [],\n [state.runsByToolCallId]\n );\n\n return {\n ...state,\n getRunsForToolCall,\n resetLocalState\n };\n}\n"],"mappings":"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"}
@@ -128,4 +128,4 @@ export {
128
128
  SerializableReturnValue as r,
129
129
  Method as t
130
130
  };
131
- //# sourceMappingURL=serializable-Bg8ARWlN.d.ts.map
131
+ //# sourceMappingURL=serializable-Brg7fRds.d.ts.map
@@ -3,5 +3,5 @@ import {
3
3
  n as RPCMethod,
4
4
  r as SerializableReturnValue,
5
5
  t as Method
6
- } from "./serializable-Bg8ARWlN.js";
6
+ } from "./serializable-Brg7fRds.js";
7
7
  export { Method, RPCMethod, SerializableReturnValue, SerializableValue };
@@ -434,4 +434,4 @@ function createBrowserToolHandlers(options) {
434
434
  //#endregion
435
435
  export { connectBrowser as a, CdpSession as i, SEARCH_DESCRIPTION as n, connectUrl as o, createBrowserToolHandlers as r, EXECUTE_DESCRIPTION as t };
436
436
 
437
- //# sourceMappingURL=shared-mfBbxjS1.js.map
437
+ //# sourceMappingURL=shared-C6l4ZKRN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-mfBbxjS1.js","names":[],"sources":["../src/browser/cdp-session.ts","../src/browser/truncate.ts","../src/browser/shared.ts"],"sourcesContent":["interface PendingCommand {\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n method: string;\n sessionId?: string;\n startedAt: number;\n}\n\ninterface DebugEntry {\n at: string;\n type: string;\n [key: string]: unknown;\n}\n\nexport interface CdpSendOptions {\n timeoutMs?: number;\n sessionId?: string;\n}\n\nexport interface CdpAttachOptions {\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_DEBUG_ENTRIES = 400;\n\n/**\n * A CDP session over an open WebSocket. Manages command correlation,\n * timeouts, target sessions, and a debug event ring buffer.\n *\n * Used host-side (not in the sandbox) — the sandbox calls into this\n * via DynamicWorkerExecutor's ToolDispatcher RPC.\n */\nexport class CdpSession {\n #socket: WebSocket;\n #nextId = 1;\n #pending = new Map<number, PendingCommand>();\n #debugLog: DebugEntry[] = [];\n #defaultTimeoutMs: number;\n #dispose?: () => void;\n\n constructor(\n socket: WebSocket,\n defaultTimeoutMs = DEFAULT_TIMEOUT_MS,\n dispose?: () => void\n ) {\n this.#socket = socket;\n this.#defaultTimeoutMs = defaultTimeoutMs;\n this.#dispose = dispose;\n\n socket.addEventListener(\"message\", (event) => this.#handleMessage(event));\n socket.addEventListener(\"error\", () => {\n this.#rejectAll(new Error(\"CDP socket error\"));\n });\n socket.addEventListener(\"close\", () => {\n this.#rejectAll(new Error(\"CDP connection closed\"));\n });\n }\n\n send(\n method: string,\n params?: unknown,\n options: CdpSendOptions = {}\n ): Promise<unknown> {\n const id = this.#nextId++;\n const timeoutMs = options.timeoutMs ?? this.#defaultTimeoutMs;\n const sessionId =\n typeof options.sessionId === \"string\" && options.sessionId.length > 0\n ? options.sessionId\n : undefined;\n\n const domain = typeof method === \"string\" ? method.split(\".\")[0] : \"\";\n if (!sessionId && domain && ![\"Browser\", \"Target\"].includes(domain)) {\n this.#recordDebug(\"warning\", {\n id,\n method,\n reason: \"target-scoped method sent without sessionId\"\n });\n }\n\n const result = new Promise<unknown>((resolve, reject) => {\n const startedAt = performance.now();\n const timeoutId = setTimeout(() => {\n this.#pending.delete(id);\n reject(\n new Error(`CDP command timed out after ${timeoutMs}ms: ${method}`)\n );\n }, timeoutMs);\n this.#pending.set(id, {\n resolve,\n reject,\n timeoutId,\n method,\n sessionId,\n startedAt\n });\n });\n\n this.#recordDebug(\"send\", { id, method, sessionId, timeoutMs });\n this.#socket.send(JSON.stringify({ id, method, params, sessionId }));\n return result;\n }\n\n async attachToTarget(\n targetId: string,\n options: CdpAttachOptions = {}\n ): Promise<string> {\n if (typeof targetId !== \"string\" || !targetId) {\n throw new Error(\"attachToTarget requires a targetId\");\n }\n\n const result = (await this.send(\n \"Target.attachToTarget\",\n {\n targetId,\n flatten: true\n },\n { timeoutMs: options.timeoutMs }\n )) as { sessionId?: string };\n\n const sessionId = result?.sessionId ?? \"\";\n if (!sessionId) {\n throw new Error(\n `Target.attachToTarget did not return a sessionId for target ${targetId}`\n );\n }\n\n this.#recordDebug(\"attach\", { targetId, sessionId });\n return sessionId;\n }\n\n getDebugLog(limit = 50): DebugEntry[] {\n const normalized = Number.isFinite(limit)\n ? Math.max(1, Math.floor(limit))\n : 50;\n return this.#debugLog.slice(-normalized);\n }\n\n clearDebugLog(): void {\n this.#debugLog = [];\n }\n\n close(): void {\n this.#rejectAll(new Error(\"CDP session closed\"));\n try {\n this.#socket.close(1000, \"Done\");\n } catch {\n // socket may already be closed\n }\n this.#dispose?.();\n }\n\n #rejectAll(error: Error): void {\n for (const [id, pending] of this.#pending.entries()) {\n clearTimeout(pending.timeoutId);\n this.#pending.delete(id);\n pending.reject(error);\n }\n }\n\n #handleMessage(event: MessageEvent): void {\n if (typeof event.data !== \"string\") {\n return;\n }\n\n let payload: Record<string, unknown>;\n try {\n payload = JSON.parse(event.data) as Record<string, unknown>;\n } catch {\n return;\n }\n\n this.#recordDebug(\"receive\", {\n id: payload.id,\n method: payload.method,\n sessionId: payload.sessionId,\n hasError: !!payload.error\n });\n\n if (typeof payload.id !== \"number\") {\n return;\n }\n\n const pending = this.#pending.get(payload.id);\n if (!pending) {\n return;\n }\n\n clearTimeout(pending.timeoutId);\n this.#pending.delete(payload.id);\n\n if (payload.error) {\n const err = payload.error as { code?: unknown; message?: string };\n const code = err.code ?? \"unknown\";\n const message = err.message ?? \"CDP error\";\n pending.reject(\n new Error(`CDP error ${code}: ${message} for ${pending.method}`)\n );\n return;\n }\n\n pending.resolve(payload.result);\n }\n\n #recordDebug(type: string, data: Record<string, unknown>): void {\n this.#debugLog.push({\n at: new Date().toISOString(),\n type,\n ...data\n });\n if (this.#debugLog.length > MAX_DEBUG_ENTRIES) {\n this.#debugLog.splice(0, this.#debugLog.length - MAX_DEBUG_ENTRIES);\n }\n }\n}\n\n/**\n * Connect to a browser via the Browser Rendering binding (Fetcher).\n * Establishes a CDP WebSocket through the binding's fetch interface.\n */\nexport async function connectBrowser(\n browser: Fetcher,\n timeoutMs?: number\n): Promise<CdpSession> {\n const response = await browser.fetch(\n \"https://localhost/v1/devtools/browser\",\n {\n headers: { Upgrade: \"websocket\" }\n }\n );\n\n const ws = response.webSocket;\n if (!ws) {\n throw new Error(\n \"Browser Rendering binding did not return a WebSocket. \" +\n \"Ensure the 'browser' binding is configured in wrangler.jsonc.\"\n );\n }\n\n const sessionId = response.headers.get(\"cf-browser-session-id\");\n if (!sessionId) {\n throw new Error(\n \"Browser Rendering binding did not include a session ID when opening the CDP WebSocket\"\n );\n }\n\n ws.accept();\n return new CdpSession(ws, timeoutMs, () => {\n void browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}`, {\n method: \"DELETE\"\n });\n });\n}\n\nconst LOCALHOST_HOSTS = new Set([\n \"localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"[::1]\"\n]);\n\n/**\n * Connect to a browser via a CDP base URL (e.g. http://localhost:9222).\n * Discovers the WebSocket debugger URL via /json/version,\n * rewrites localhost URLs to the base URL host, and opens the WebSocket.\n *\n * Useful for local development with `chrome --remote-debugging-port=9222`\n * or when connecting through a tunnel.\n */\nexport async function connectUrl(\n baseUrl: string,\n options?: { timeoutMs?: number; headers?: Record<string, string> }\n): Promise<CdpSession> {\n const endpoint = new URL(\"/json/version\", baseUrl).toString();\n const response = await fetch(endpoint, {\n headers: options?.headers\n });\n if (!response.ok) {\n throw new Error(\n `Failed to discover CDP endpoint at ${endpoint}: ${response.status}`\n );\n }\n\n const payload = (await response.json()) as {\n webSocketDebuggerUrl?: string;\n };\n if (!payload.webSocketDebuggerUrl) {\n throw new Error(\"CDP /json/version did not include webSocketDebuggerUrl\");\n }\n\n let wsUrl = payload.webSocketDebuggerUrl;\n const parsed = new URL(wsUrl);\n if (LOCALHOST_HOSTS.has(parsed.hostname)) {\n const base = new URL(baseUrl);\n parsed.hostname = base.hostname;\n parsed.port = base.port;\n parsed.protocol = base.protocol;\n } else {\n // Workers runtime requires fetch + Upgrade header for outbound WebSockets\n parsed.protocol = parsed.protocol === \"wss:\" ? \"https:\" : \"http:\";\n }\n const fetchUrl = parsed.toString();\n\n const wsResponse = await fetch(fetchUrl, {\n headers: { ...options?.headers, Upgrade: \"websocket\" }\n });\n const ws = wsResponse.webSocket;\n if (!ws) {\n throw new Error(\n `Failed to establish CDP WebSocket at ${fetchUrl} (status ${wsResponse.status})`\n );\n }\n ws.accept();\n\n return new CdpSession(ws, options?.timeoutMs);\n}\n","const CHARS_PER_TOKEN = 4;\nconst MAX_TOKENS = 6000;\nconst MAX_CHARS = CHARS_PER_TOKEN * MAX_TOKENS;\n\nexport function truncateResponse(content: unknown): string {\n const text =\n typeof content === \"string\"\n ? content\n : (JSON.stringify(content, null, 2) ?? \"null\");\n if (text.length <= MAX_CHARS) {\n return text;\n }\n\n const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN);\n return `${text.slice(0, MAX_CHARS)}\\n\\n--- TRUNCATED ---\\nResponse was ~${estimatedTokens.toLocaleString()} tokens (limit: ${MAX_TOKENS.toLocaleString()}). Use more specific queries to reduce response size.`;\n}\n","import type { ResolvedProvider } from \"@cloudflare/codemode\";\nimport { DynamicWorkerExecutor } from \"@cloudflare/codemode\";\nimport { CdpSession, connectBrowser, connectUrl } from \"./cdp-session\";\nimport { truncateResponse } from \"./truncate\";\n\nexport interface BrowserToolsOptions {\n /** Browser Rendering binding (Fetcher) — used in production */\n browser?: Fetcher;\n /** Optional CDP base URL override (e.g. http://localhost:9222) */\n cdpUrl?: string;\n /** Headers to send with CDP URL discovery requests (e.g. Access headers) */\n cdpHeaders?: Record<string, string>;\n /** Loader binding for sandboxed code execution */\n loader: WorkerLoader;\n /** Execution timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\ninterface RawCdpCommand {\n name: string;\n description?: string;\n}\n\ninterface RawCdpEvent {\n name: string;\n description?: string;\n}\n\ninterface RawCdpType {\n id: string;\n description?: string;\n}\n\n/** Raw CDP protocol domain from `/json/protocol` */\ninterface RawCdpDomain {\n domain: string;\n description?: string;\n commands?: RawCdpCommand[];\n events?: RawCdpEvent[];\n types?: RawCdpType[];\n}\n\ninterface SearchableCdpSpec {\n domains: Array<{\n name: string;\n description?: string;\n commands: Array<{ name: string; method: string; description?: string }>;\n events: Array<{ name: string; event: string; description?: string }>;\n types: Array<{ id: string; name: string; description?: string }>;\n }>;\n}\n\nconst specCache = new Map<\n string,\n { spec: SearchableCdpSpec; cachedAt: number }\n>();\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport const SEARCH_DESCRIPTION = `Search the Chrome DevTools Protocol spec using JavaScript code.\n\nAvailable in your code:\n\ndeclare const spec: {\n get(): Promise<{\n domains: Array<{\n name: string;\n description?: string;\n commands: Array<{ name: string; method: string; description?: string }>;\n events: Array<{ name: string; event: string; description?: string }>;\n types: Array<{ id: string; name: string; description?: string }>;\n }>;\n }>;\n};\n\nWrite an async arrow function in JavaScript. Do NOT use TypeScript syntax.\n\nExample:\nasync () => {\n const s = await spec.get();\n return s.domains\n .find(d => d.name === \"Network\")\n .commands.filter(c => c.description?.toLowerCase().includes(\"intercept\"))\n .map(c => ({ method: c.method, description: c.description }));\n}`;\n\nfunction normalizeCdpSpec(spec: {\n domains?: RawCdpDomain[];\n}): SearchableCdpSpec {\n return {\n domains: (spec.domains ?? []).map((domain) => ({\n name: domain.domain,\n description: domain.description,\n commands: (domain.commands ?? []).map((command) => ({\n name: command.name,\n method: `${domain.domain}.${command.name}`,\n description: command.description\n })),\n events: (domain.events ?? []).map((event) => ({\n name: event.name,\n event: `${domain.domain}.${event.name}`,\n description: event.description\n })),\n types: (domain.types ?? []).map((type) => ({\n id: type.id,\n name: `${domain.domain}.${type.id}`,\n description: type.description\n }))\n }))\n };\n}\n\nfunction getSpecCacheKey(\n source: string,\n headers?: Record<string, string>\n): string {\n const headerEntries = Object.entries(headers ?? {}).sort(([a], [b]) =>\n a.localeCompare(b)\n );\n return `${source}:${JSON.stringify(headerEntries)}`;\n}\n\nasync function getCachedSpec(\n key: string,\n load: () => Promise<{ domains?: RawCdpDomain[] }>\n): Promise<SearchableCdpSpec> {\n const cached = specCache.get(key);\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.spec;\n }\n\n const spec = normalizeCdpSpec(await load());\n specCache.set(key, { spec, cachedAt: Date.now() });\n return spec;\n}\n\nasync function fetchCdpSpecFromUrl(\n cdpBaseUrl: string,\n headers?: Record<string, string>\n): Promise<SearchableCdpSpec> {\n const endpoint = new URL(\"/json/protocol\", cdpBaseUrl).toString();\n\n return getCachedSpec(getSpecCacheKey(endpoint, headers), async () => {\n const response = await fetch(endpoint, { headers });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch CDP spec from ${endpoint}: ${response.status}`\n );\n }\n\n return (await response.json()) as { domains?: RawCdpDomain[] };\n });\n}\n\nasync function fetchCdpSpecFromBrowser(\n browser: Fetcher\n): Promise<SearchableCdpSpec> {\n return getCachedSpec(\"browser-binding\", async () => {\n const createResponse = await browser.fetch(\n \"https://localhost/v1/devtools/browser\",\n {\n method: \"POST\"\n }\n );\n\n if (!createResponse.ok) {\n throw new Error(\n \"Failed to create Browser Rendering session for protocol fetch: \" +\n `${createResponse.status}`\n );\n }\n\n const payload = (await createResponse.json()) as { sessionId?: string };\n const sessionId = payload.sessionId;\n if (!sessionId) {\n throw new Error(\n \"Browser Rendering session response did not include a sessionId\"\n );\n }\n\n try {\n const response = await browser.fetch(\n `https://localhost/v1/devtools/browser/${sessionId}/json/protocol`\n );\n\n if (!response.ok) {\n throw new Error(\n \"Failed to fetch CDP spec from Browser Rendering: \" +\n `${response.status}`\n );\n }\n\n return (await response.json()) as { domains?: RawCdpDomain[] };\n } finally {\n try {\n await browser.fetch(\n `https://localhost/v1/devtools/browser/${sessionId}`,\n {\n method: \"DELETE\"\n }\n );\n } catch {\n // Cleanup failure should not mask the original result or error\n }\n }\n });\n}\n\nexport const EXECUTE_DESCRIPTION = `Execute CDP commands against a live browser session using JavaScript code.\n\nAvailable in your code:\n\ndeclare const cdp: {\n send(method: string, params?: unknown, options?: {\n timeoutMs?: number;\n sessionId?: string;\n }): Promise<unknown>;\n attachToTarget(targetId: string, options?: {\n timeoutMs?: number;\n }): Promise<string>;\n getDebugLog(limit?: number): Promise<unknown[]>;\n clearDebugLog(): Promise<void>;\n};\n\nWrite an async arrow function in JavaScript. Do NOT use TypeScript syntax.\n\nFor page-scoped commands such as Page.*, Runtime.*, and DOM.*, first create or select a target, call cdp.attachToTarget(targetId), and pass the returned sessionId in command options.\n\nExample:\nasync () => {\n return await cdp.send(\"Browser.getVersion\");\n}\n\nPage example:\nasync () => {\n const { targetId } = await cdp.send(\"Target.createTarget\", {\n url: \"about:blank\"\n });\n const sessionId = await cdp.attachToTarget(targetId);\n await cdp.send(\"Page.enable\", {}, { sessionId });\n await cdp.send(\n \"Page.navigate\",\n { url: \"https://example.com\" },\n { sessionId }\n );\n const { result } = await cdp.send(\n \"Runtime.evaluate\",\n { expression: \"document.title\" },\n { sessionId }\n );\n return result.value;\n}`;\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport interface ToolResult {\n text: string;\n isError?: boolean;\n}\n\nlet didWarnExperimental = false;\n\nexport function createBrowserToolHandlers(options: BrowserToolsOptions) {\n if (!didWarnExperimental) {\n didWarnExperimental = true;\n console.warn(\n \"[agents/browser] Browser tools are experimental and may change in a future release.\"\n );\n }\n const executor = new DynamicWorkerExecutor({\n loader: options.loader,\n timeout: options.timeout\n });\n\n async function search(code: string): Promise<ToolResult> {\n try {\n let specSource: SearchableCdpSpec;\n\n if (options.cdpUrl) {\n specSource = await fetchCdpSpecFromUrl(\n options.cdpUrl,\n options.cdpHeaders\n );\n } else if (options.browser) {\n specSource = await fetchCdpSpecFromBrowser(options.browser);\n } else {\n return {\n text: \"Either 'browser' (Fetcher binding) or 'cdpUrl' must be provided\",\n isError: true\n };\n }\n\n const providers: ResolvedProvider[] = [\n {\n name: \"spec\",\n fns: { get: async () => specSource }\n }\n ];\n const result = await executor.execute(code, providers);\n if (result.error) {\n return { text: result.error, isError: true };\n }\n return { text: truncateResponse(result.result) };\n } catch (error) {\n return { text: formatError(error), isError: true };\n }\n }\n\n async function execute(code: string): Promise<ToolResult> {\n let session: CdpSession | undefined;\n try {\n if (options.cdpUrl) {\n session = await connectUrl(options.cdpUrl, {\n timeoutMs: options.timeout,\n headers: options.cdpHeaders\n });\n } else if (options.browser) {\n session = await connectBrowser(options.browser, options.timeout);\n } else {\n return {\n text: \"Either 'browser' (Fetcher binding) or 'cdpUrl' must be provided\",\n isError: true\n };\n }\n\n const providers: ResolvedProvider[] = [\n {\n name: \"cdp\",\n fns: {\n send: async (method: unknown, params: unknown, opts: unknown) =>\n session!.send(\n method as string,\n params,\n opts as { timeoutMs?: number; sessionId?: string }\n ),\n attachToTarget: async (targetId: unknown, opts: unknown) =>\n session!.attachToTarget(\n targetId as string,\n opts as { timeoutMs?: number }\n ),\n getDebugLog: async (limit: unknown) =>\n session!.getDebugLog(limit as number | undefined),\n clearDebugLog: async () => session!.clearDebugLog()\n },\n positionalArgs: true\n }\n ];\n\n const result = await executor.execute(code, providers);\n if (result.error) {\n return { text: result.error, isError: true };\n }\n return { text: truncateResponse(result.result) };\n } catch (error) {\n return { text: formatError(error), isError: true };\n } finally {\n session?.close();\n }\n }\n\n return { search, execute };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-C6l4ZKRN.js","names":[],"sources":["../src/browser/cdp-session.ts","../src/browser/truncate.ts","../src/browser/shared.ts"],"sourcesContent":["interface PendingCommand {\n resolve: (result: unknown) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n method: string;\n sessionId?: string;\n startedAt: number;\n}\n\ninterface DebugEntry {\n at: string;\n type: string;\n [key: string]: unknown;\n}\n\nexport interface CdpSendOptions {\n timeoutMs?: number;\n sessionId?: string;\n}\n\nexport interface CdpAttachOptions {\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_DEBUG_ENTRIES = 400;\n\n/**\n * A CDP session over an open WebSocket. Manages command correlation,\n * timeouts, target sessions, and a debug event ring buffer.\n *\n * Used host-side (not in the sandbox) — the sandbox calls into this\n * via DynamicWorkerExecutor's ToolDispatcher RPC.\n */\nexport class CdpSession {\n #socket: WebSocket;\n #nextId = 1;\n #pending = new Map<number, PendingCommand>();\n #debugLog: DebugEntry[] = [];\n #defaultTimeoutMs: number;\n #dispose?: () => void;\n\n constructor(\n socket: WebSocket,\n defaultTimeoutMs = DEFAULT_TIMEOUT_MS,\n dispose?: () => void\n ) {\n this.#socket = socket;\n this.#defaultTimeoutMs = defaultTimeoutMs;\n this.#dispose = dispose;\n\n socket.addEventListener(\"message\", (event) => this.#handleMessage(event));\n socket.addEventListener(\"error\", () => {\n this.#rejectAll(new Error(\"CDP socket error\"));\n });\n socket.addEventListener(\"close\", () => {\n this.#rejectAll(new Error(\"CDP connection closed\"));\n });\n }\n\n send(\n method: string,\n params?: unknown,\n options: CdpSendOptions = {}\n ): Promise<unknown> {\n const id = this.#nextId++;\n const timeoutMs = options.timeoutMs ?? this.#defaultTimeoutMs;\n const sessionId =\n typeof options.sessionId === \"string\" && options.sessionId.length > 0\n ? options.sessionId\n : undefined;\n\n const domain = typeof method === \"string\" ? method.split(\".\")[0] : \"\";\n if (!sessionId && domain && ![\"Browser\", \"Target\"].includes(domain)) {\n this.#recordDebug(\"warning\", {\n id,\n method,\n reason: \"target-scoped method sent without sessionId\"\n });\n }\n\n const result = new Promise<unknown>((resolve, reject) => {\n const startedAt = performance.now();\n const timeoutId = setTimeout(() => {\n this.#pending.delete(id);\n reject(\n new Error(`CDP command timed out after ${timeoutMs}ms: ${method}`)\n );\n }, timeoutMs);\n this.#pending.set(id, {\n resolve,\n reject,\n timeoutId,\n method,\n sessionId,\n startedAt\n });\n });\n\n this.#recordDebug(\"send\", { id, method, sessionId, timeoutMs });\n this.#socket.send(JSON.stringify({ id, method, params, sessionId }));\n return result;\n }\n\n async attachToTarget(\n targetId: string,\n options: CdpAttachOptions = {}\n ): Promise<string> {\n if (typeof targetId !== \"string\" || !targetId) {\n throw new Error(\"attachToTarget requires a targetId\");\n }\n\n const result = (await this.send(\n \"Target.attachToTarget\",\n {\n targetId,\n flatten: true\n },\n { timeoutMs: options.timeoutMs }\n )) as { sessionId?: string };\n\n const sessionId = result?.sessionId ?? \"\";\n if (!sessionId) {\n throw new Error(\n `Target.attachToTarget did not return a sessionId for target ${targetId}`\n );\n }\n\n this.#recordDebug(\"attach\", { targetId, sessionId });\n return sessionId;\n }\n\n getDebugLog(limit = 50): DebugEntry[] {\n const normalized = Number.isFinite(limit)\n ? Math.max(1, Math.floor(limit))\n : 50;\n return this.#debugLog.slice(-normalized);\n }\n\n clearDebugLog(): void {\n this.#debugLog = [];\n }\n\n close(): void {\n this.#rejectAll(new Error(\"CDP session closed\"));\n try {\n this.#socket.close(1000, \"Done\");\n } catch {\n // socket may already be closed\n }\n this.#dispose?.();\n }\n\n #rejectAll(error: Error): void {\n for (const [id, pending] of this.#pending.entries()) {\n clearTimeout(pending.timeoutId);\n this.#pending.delete(id);\n pending.reject(error);\n }\n }\n\n #handleMessage(event: MessageEvent): void {\n if (typeof event.data !== \"string\") {\n return;\n }\n\n let payload: Record<string, unknown>;\n try {\n payload = JSON.parse(event.data) as Record<string, unknown>;\n } catch {\n return;\n }\n\n this.#recordDebug(\"receive\", {\n id: payload.id,\n method: payload.method,\n sessionId: payload.sessionId,\n hasError: !!payload.error\n });\n\n if (typeof payload.id !== \"number\") {\n return;\n }\n\n const pending = this.#pending.get(payload.id);\n if (!pending) {\n return;\n }\n\n clearTimeout(pending.timeoutId);\n this.#pending.delete(payload.id);\n\n if (payload.error) {\n const err = payload.error as { code?: unknown; message?: string };\n const code = err.code ?? \"unknown\";\n const message = err.message ?? \"CDP error\";\n pending.reject(\n new Error(`CDP error ${code}: ${message} for ${pending.method}`)\n );\n return;\n }\n\n pending.resolve(payload.result);\n }\n\n #recordDebug(type: string, data: Record<string, unknown>): void {\n this.#debugLog.push({\n at: new Date().toISOString(),\n type,\n ...data\n });\n if (this.#debugLog.length > MAX_DEBUG_ENTRIES) {\n this.#debugLog.splice(0, this.#debugLog.length - MAX_DEBUG_ENTRIES);\n }\n }\n}\n\n/**\n * Connect to a browser via the Browser Rendering binding (Fetcher).\n * Establishes a CDP WebSocket through the binding's fetch interface.\n */\nexport async function connectBrowser(\n browser: Fetcher,\n timeoutMs?: number\n): Promise<CdpSession> {\n const response = await browser.fetch(\n \"https://localhost/v1/devtools/browser\",\n {\n headers: { Upgrade: \"websocket\" }\n }\n );\n\n const ws = response.webSocket;\n if (!ws) {\n throw new Error(\n \"Browser Rendering binding did not return a WebSocket. \" +\n \"Ensure the 'browser' binding is configured in wrangler.jsonc.\"\n );\n }\n\n const sessionId = response.headers.get(\"cf-browser-session-id\");\n if (!sessionId) {\n throw new Error(\n \"Browser Rendering binding did not include a session ID when opening the CDP WebSocket\"\n );\n }\n\n ws.accept();\n return new CdpSession(ws, timeoutMs, () => {\n void browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}`, {\n method: \"DELETE\"\n });\n });\n}\n\nconst LOCALHOST_HOSTS = new Set([\n \"localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"[::1]\"\n]);\n\n/**\n * Connect to a browser via a CDP base URL (e.g. http://localhost:9222).\n * Discovers the WebSocket debugger URL via /json/version,\n * rewrites localhost URLs to the base URL host, and opens the WebSocket.\n *\n * Useful for local development with `chrome --remote-debugging-port=9222`\n * or when connecting through a tunnel.\n */\nexport async function connectUrl(\n baseUrl: string,\n options?: { timeoutMs?: number; headers?: Record<string, string> }\n): Promise<CdpSession> {\n const endpoint = new URL(\"/json/version\", baseUrl).toString();\n const response = await fetch(endpoint, {\n headers: options?.headers\n });\n if (!response.ok) {\n throw new Error(\n `Failed to discover CDP endpoint at ${endpoint}: ${response.status}`\n );\n }\n\n const payload = (await response.json()) as {\n webSocketDebuggerUrl?: string;\n };\n if (!payload.webSocketDebuggerUrl) {\n throw new Error(\"CDP /json/version did not include webSocketDebuggerUrl\");\n }\n\n let wsUrl = payload.webSocketDebuggerUrl;\n const parsed = new URL(wsUrl);\n if (LOCALHOST_HOSTS.has(parsed.hostname)) {\n const base = new URL(baseUrl);\n parsed.hostname = base.hostname;\n parsed.port = base.port;\n parsed.protocol = base.protocol;\n } else {\n // Workers runtime requires fetch + Upgrade header for outbound WebSockets\n parsed.protocol = parsed.protocol === \"wss:\" ? \"https:\" : \"http:\";\n }\n const fetchUrl = parsed.toString();\n\n const wsResponse = await fetch(fetchUrl, {\n headers: { ...options?.headers, Upgrade: \"websocket\" }\n });\n const ws = wsResponse.webSocket;\n if (!ws) {\n throw new Error(\n `Failed to establish CDP WebSocket at ${fetchUrl} (status ${wsResponse.status})`\n );\n }\n ws.accept();\n\n return new CdpSession(ws, options?.timeoutMs);\n}\n","const CHARS_PER_TOKEN = 4;\nconst MAX_TOKENS = 6000;\nconst MAX_CHARS = CHARS_PER_TOKEN * MAX_TOKENS;\n\nexport function truncateResponse(content: unknown): string {\n const text =\n typeof content === \"string\"\n ? content\n : (JSON.stringify(content, null, 2) ?? \"null\");\n if (text.length <= MAX_CHARS) {\n return text;\n }\n\n const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN);\n return `${text.slice(0, MAX_CHARS)}\\n\\n--- TRUNCATED ---\\nResponse was ~${estimatedTokens.toLocaleString()} tokens (limit: ${MAX_TOKENS.toLocaleString()}). Use more specific queries to reduce response size.`;\n}\n","import type { ResolvedProvider } from \"@cloudflare/codemode\";\nimport { DynamicWorkerExecutor } from \"@cloudflare/codemode\";\nimport { CdpSession, connectBrowser, connectUrl } from \"./cdp-session\";\nimport { truncateResponse } from \"./truncate\";\n\nexport interface BrowserToolsOptions {\n /** Browser Rendering binding (Fetcher) — used in production */\n browser?: Fetcher;\n /** Optional CDP base URL override (e.g. http://localhost:9222) */\n cdpUrl?: string;\n /** Headers to send with CDP URL discovery requests (e.g. Access headers) */\n cdpHeaders?: Record<string, string>;\n /** Loader binding for sandboxed code execution */\n loader: WorkerLoader;\n /** Execution timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\ninterface RawCdpCommand {\n name: string;\n description?: string;\n}\n\ninterface RawCdpEvent {\n name: string;\n description?: string;\n}\n\ninterface RawCdpType {\n id: string;\n description?: string;\n}\n\n/** Raw CDP protocol domain from `/json/protocol` */\ninterface RawCdpDomain {\n domain: string;\n description?: string;\n commands?: RawCdpCommand[];\n events?: RawCdpEvent[];\n types?: RawCdpType[];\n}\n\ninterface SearchableCdpSpec {\n domains: Array<{\n name: string;\n description?: string;\n commands: Array<{ name: string; method: string; description?: string }>;\n events: Array<{ name: string; event: string; description?: string }>;\n types: Array<{ id: string; name: string; description?: string }>;\n }>;\n}\n\nconst specCache = new Map<\n string,\n { spec: SearchableCdpSpec; cachedAt: number }\n>();\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nexport const SEARCH_DESCRIPTION = `Search the Chrome DevTools Protocol spec using JavaScript code.\n\nAvailable in your code:\n\ndeclare const spec: {\n get(): Promise<{\n domains: Array<{\n name: string;\n description?: string;\n commands: Array<{ name: string; method: string; description?: string }>;\n events: Array<{ name: string; event: string; description?: string }>;\n types: Array<{ id: string; name: string; description?: string }>;\n }>;\n }>;\n};\n\nWrite an async arrow function in JavaScript. Do NOT use TypeScript syntax.\n\nExample:\nasync () => {\n const s = await spec.get();\n return s.domains\n .find(d => d.name === \"Network\")\n .commands.filter(c => c.description?.toLowerCase().includes(\"intercept\"))\n .map(c => ({ method: c.method, description: c.description }));\n}`;\n\nfunction normalizeCdpSpec(spec: {\n domains?: RawCdpDomain[];\n}): SearchableCdpSpec {\n return {\n domains: (spec.domains ?? []).map((domain) => ({\n name: domain.domain,\n description: domain.description,\n commands: (domain.commands ?? []).map((command) => ({\n name: command.name,\n method: `${domain.domain}.${command.name}`,\n description: command.description\n })),\n events: (domain.events ?? []).map((event) => ({\n name: event.name,\n event: `${domain.domain}.${event.name}`,\n description: event.description\n })),\n types: (domain.types ?? []).map((type) => ({\n id: type.id,\n name: `${domain.domain}.${type.id}`,\n description: type.description\n }))\n }))\n };\n}\n\nfunction getSpecCacheKey(\n source: string,\n headers?: Record<string, string>\n): string {\n const headerEntries = Object.entries(headers ?? {}).sort(([a], [b]) =>\n a.localeCompare(b)\n );\n return `${source}:${JSON.stringify(headerEntries)}`;\n}\n\nasync function getCachedSpec(\n key: string,\n load: () => Promise<{ domains?: RawCdpDomain[] }>\n): Promise<SearchableCdpSpec> {\n const cached = specCache.get(key);\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.spec;\n }\n\n const spec = normalizeCdpSpec(await load());\n specCache.set(key, { spec, cachedAt: Date.now() });\n return spec;\n}\n\nasync function fetchCdpSpecFromUrl(\n cdpBaseUrl: string,\n headers?: Record<string, string>\n): Promise<SearchableCdpSpec> {\n const endpoint = new URL(\"/json/protocol\", cdpBaseUrl).toString();\n\n return getCachedSpec(getSpecCacheKey(endpoint, headers), async () => {\n const response = await fetch(endpoint, { headers });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch CDP spec from ${endpoint}: ${response.status}`\n );\n }\n\n return (await response.json()) as { domains?: RawCdpDomain[] };\n });\n}\n\nasync function fetchCdpSpecFromBrowser(\n browser: Fetcher\n): Promise<SearchableCdpSpec> {\n return getCachedSpec(\"browser-binding\", async () => {\n const createResponse = await browser.fetch(\n \"https://localhost/v1/devtools/browser\",\n {\n method: \"POST\"\n }\n );\n\n if (!createResponse.ok) {\n throw new Error(\n \"Failed to create Browser Rendering session for protocol fetch: \" +\n `${createResponse.status}`\n );\n }\n\n const payload = (await createResponse.json()) as { sessionId?: string };\n const sessionId = payload.sessionId;\n if (!sessionId) {\n throw new Error(\n \"Browser Rendering session response did not include a sessionId\"\n );\n }\n\n try {\n const response = await browser.fetch(\n `https://localhost/v1/devtools/browser/${sessionId}/json/protocol`\n );\n\n if (!response.ok) {\n throw new Error(\n \"Failed to fetch CDP spec from Browser Rendering: \" +\n `${response.status}`\n );\n }\n\n return (await response.json()) as { domains?: RawCdpDomain[] };\n } finally {\n try {\n await browser.fetch(\n `https://localhost/v1/devtools/browser/${sessionId}`,\n {\n method: \"DELETE\"\n }\n );\n } catch {\n // Cleanup failure should not mask the original result or error\n }\n }\n });\n}\n\nexport const EXECUTE_DESCRIPTION = `Execute CDP commands against a live browser session using JavaScript code.\n\nAvailable in your code:\n\ndeclare const cdp: {\n send(method: string, params?: unknown, options?: {\n timeoutMs?: number;\n sessionId?: string;\n }): Promise<unknown>;\n attachToTarget(targetId: string, options?: {\n timeoutMs?: number;\n }): Promise<string>;\n getDebugLog(limit?: number): Promise<unknown[]>;\n clearDebugLog(): Promise<void>;\n};\n\nWrite an async arrow function in JavaScript. Do NOT use TypeScript syntax.\n\nFor page-scoped commands such as Page.*, Runtime.*, and DOM.*, first create or select a target, call cdp.attachToTarget(targetId), and pass the returned sessionId in command options.\n\nExample:\nasync () => {\n return await cdp.send(\"Browser.getVersion\");\n}\n\nPage example:\nasync () => {\n const { targetId } = await cdp.send(\"Target.createTarget\", {\n url: \"about:blank\"\n });\n const sessionId = await cdp.attachToTarget(targetId);\n await cdp.send(\"Page.enable\", {}, { sessionId });\n await cdp.send(\n \"Page.navigate\",\n { url: \"https://example.com\" },\n { sessionId }\n );\n const { result } = await cdp.send(\n \"Runtime.evaluate\",\n { expression: \"document.title\" },\n { sessionId }\n );\n return result.value;\n}`;\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport interface ToolResult {\n text: string;\n isError?: boolean;\n}\n\nlet didWarnExperimental = false;\n\nexport function createBrowserToolHandlers(options: BrowserToolsOptions) {\n if (!didWarnExperimental) {\n didWarnExperimental = true;\n console.warn(\n \"[agents/browser] Browser tools are experimental and may change in a future release.\"\n );\n }\n const executor = new DynamicWorkerExecutor({\n loader: options.loader,\n timeout: options.timeout\n });\n\n async function search(code: string): Promise<ToolResult> {\n try {\n let specSource: SearchableCdpSpec;\n\n if (options.cdpUrl) {\n specSource = await fetchCdpSpecFromUrl(\n options.cdpUrl,\n options.cdpHeaders\n );\n } else if (options.browser) {\n specSource = await fetchCdpSpecFromBrowser(options.browser);\n } else {\n return {\n text: \"Either 'browser' (Fetcher binding) or 'cdpUrl' must be provided\",\n isError: true\n };\n }\n\n const providers: ResolvedProvider[] = [\n {\n name: \"spec\",\n fns: { get: async () => specSource }\n }\n ];\n const result = await executor.execute(code, providers);\n if (result.error) {\n return { text: result.error, isError: true };\n }\n return { text: truncateResponse(result.result) };\n } catch (error) {\n return { text: formatError(error), isError: true };\n }\n }\n\n async function execute(code: string): Promise<ToolResult> {\n let session: CdpSession | undefined;\n try {\n if (options.cdpUrl) {\n session = await connectUrl(options.cdpUrl, {\n timeoutMs: options.timeout,\n headers: options.cdpHeaders\n });\n } else if (options.browser) {\n session = await connectBrowser(options.browser, options.timeout);\n } else {\n return {\n text: \"Either 'browser' (Fetcher binding) or 'cdpUrl' must be provided\",\n isError: true\n };\n }\n\n const providers: ResolvedProvider[] = [\n {\n name: \"cdp\",\n fns: {\n send: async (method: unknown, params: unknown, opts: unknown) =>\n session!.send(\n method as string,\n params,\n opts as { timeoutMs?: number; sessionId?: string }\n ),\n attachToTarget: async (targetId: unknown, opts: unknown) =>\n session!.attachToTarget(\n targetId as string,\n opts as { timeoutMs?: number }\n ),\n getDebugLog: async (limit: unknown) =>\n session!.getDebugLog(limit as number | undefined),\n clearDebugLog: async () => session!.clearDebugLog()\n },\n positionalArgs: true\n }\n ];\n\n const result = await executor.execute(code, providers);\n if (result.error) {\n return { text: result.error, isError: true };\n }\n return { text: truncateResponse(result.result) };\n } catch (error) {\n return { text: formatError(error), isError: true };\n } finally {\n session?.close();\n }\n }\n\n return { search, execute };\n}\n"],"mappings":"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"}
@@ -31,4 +31,4 @@ export {
31
31
  SEARCH_DESCRIPTION as r,
32
32
  BrowserToolsOptions as t
33
33
  };
34
- //# sourceMappingURL=shared-BUHZFGTk.d.ts.map
34
+ //# sourceMappingURL=shared-Ch9slKdI.d.ts.map
@@ -1,10 +1,10 @@
1
1
  import {
2
- _t as parseSubAgentPath,
3
- gt as getSubAgentByName,
4
- ht as SubAgentPathMatch,
5
- mt as SUB_PREFIX,
6
- vt as routeSubAgentRequest
7
- } from "./index-DSwOzhhd.js";
2
+ At as SUB_PREFIX,
3
+ Mt as getSubAgentByName,
4
+ Nt as parseSubAgentPath,
5
+ Pt as routeSubAgentRequest,
6
+ jt as SubAgentPathMatch
7
+ } from "./agent-tool-types-tBGRsPm0.js";
8
8
  export {
9
9
  SUB_PREFIX,
10
10
  SubAgentPathMatch,
@@ -1,4 +1,4 @@
1
- import { r as Agent } from "./index-DSwOzhhd.js";
1
+ import { v as Agent } from "./agent-tool-types-tBGRsPm0.js";
2
2
  import {
3
3
  S as WorkflowTrackingRow,
4
4
  _ as WorkflowPage,
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "durable objects"
10
10
  ],
11
11
  "type": "module",
12
- "version": "0.11.9",
12
+ "version": "0.12.0",
13
13
  "license": "MIT",
14
14
  "repository": {
15
15
  "directory": "packages/agents",
@@ -36,17 +36,17 @@
36
36
  "yargs": "^18.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@ai-sdk/react": "^3.0.170",
39
+ "@ai-sdk/react": "^3.0.172",
40
40
  "@tanstack/ai": "^0.14.0",
41
41
  "@types/react": "^19.2.14",
42
42
  "@types/yargs": "^17.0.35",
43
43
  "@vitest/browser-playwright": "^4.1.5",
44
44
  "@x402/core": "^2.11.0",
45
45
  "@x402/evm": "^2.11.0",
46
- "ai": "^6.0.168",
46
+ "ai": "^6.0.170",
47
47
  "react": "^19.2.5",
48
48
  "vitest-browser-react": "^2.2.0",
49
- "zod": "^4.3.6"
49
+ "zod": "^4.4.1"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@cloudflare/ai-chat": ">=0.5.2 <1.0.0",
@@ -96,6 +96,11 @@
96
96
  "import": "./dist/client.js",
97
97
  "require": "./dist/client.js"
98
98
  },
99
+ "./agent-tools": {
100
+ "types": "./dist/agent-tools.d.ts",
101
+ "import": "./dist/agent-tools.js",
102
+ "require": "./dist/agent-tools.js"
103
+ },
99
104
  "./codemode/ai": {
100
105
  "types": "./dist/codemode/ai.d.ts",
101
106
  "import": "./dist/codemode/ai.js",