agents 0.12.4 → 0.13.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 (62) hide show
  1. package/README.md +7 -7
  2. package/dist/{agent-tool-types-CM_50fcV.d.ts → agent-tool-types-BVgYyKO9.d.ts} +52 -18
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/agent-tools-BAdX1vdI.js.map +1 -1
  5. package/dist/{agent-tools-BylX6WXG.d.ts → agent-tools-C-Ch8Thl.d.ts} +2 -2
  6. package/dist/agent-tools.d.ts +1 -1
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/ai-chat-agent.js.map +1 -1
  9. package/dist/ai-chat-v5-migration.js.map +1 -1
  10. package/dist/ai-react.js.map +1 -1
  11. package/dist/ai-types.js.map +1 -1
  12. package/dist/browser/ai.js +1 -1
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.js +1 -1
  15. package/dist/browser/tanstack-ai.js +1 -1
  16. package/dist/browser/tanstack-ai.js.map +1 -1
  17. package/dist/chat/index.d.ts +2 -2
  18. package/dist/chat/index.js.map +1 -1
  19. package/dist/{classPrivateFieldGet2-CS51BNGR.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/client-D1kFXo80.js.map +1 -1
  22. package/dist/client.d.ts +1 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/codemode/ai.js.map +1 -1
  25. package/dist/{compaction-helpers-bYvP1o2S.d.ts → compaction-helpers-DAe-xiVY.d.ts} +33 -15
  26. package/dist/compaction-helpers-DvcZnvQ1.js.map +1 -1
  27. package/dist/email.d.ts +1 -1
  28. package/dist/email.js.map +1 -1
  29. package/dist/experimental/memory/session/index.d.ts +247 -34
  30. package/dist/experimental/memory/session/index.js +540 -135
  31. package/dist/experimental/memory/session/index.js.map +1 -1
  32. package/dist/experimental/memory/utils/index.d.ts +1 -1
  33. package/dist/experimental/memory/utils/index.js.map +1 -1
  34. package/dist/experimental/webmcp.js.map +1 -1
  35. package/dist/index.d.ts +25 -25
  36. package/dist/index.js +90 -24
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal_context.js.map +1 -1
  39. package/dist/mcp/client.d.ts +1 -1
  40. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  41. package/dist/mcp/index.d.ts +15 -15
  42. package/dist/mcp/index.js +21 -45
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/x402.js.map +1 -1
  45. package/dist/observability/index.js.map +1 -1
  46. package/dist/react.d.ts +1 -1
  47. package/dist/react.js.map +1 -1
  48. package/dist/retries.js.map +1 -1
  49. package/dist/schedule.js.map +1 -1
  50. package/dist/serializable.d.ts +1 -1
  51. package/dist/{shared-DzJYHisH.js → shared-CiKaIK4h.js} +4 -5
  52. package/dist/{shared-DzJYHisH.js.map → shared-CiKaIK4h.js.map} +1 -1
  53. package/dist/sub-routing.d.ts +1 -1
  54. package/dist/sub-routing.js.map +1 -1
  55. package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/utils.js.map +1 -1
  58. package/dist/vite.js.map +1 -1
  59. package/dist/workflow-types.js.map +1 -1
  60. package/dist/workflows.d.ts +1 -1
  61. package/dist/workflows.js.map +1 -1
  62. package/package.json +6 -6
@@ -11,7 +11,7 @@ import {
11
11
  s as computeSummaryBudget,
12
12
  t as COMPACTION_PREFIX,
13
13
  u as isCompactionMessage
14
- } from "../../../compaction-helpers-bYvP1o2S.js";
14
+ } from "../../../compaction-helpers-DAe-xiVY.js";
15
15
 
16
16
  //#region src/experimental/memory/utils/tokens.d.ts
17
17
  /** Approximate characters per token for English text */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/experimental/memory/utils/compaction.ts"],"sourcesContent":["/**\n * Read-time context truncation.\n *\n * Truncates older tool outputs and long text before sending to the LLM.\n * Structured tool outputs keep their container shape so tool-specific\n * `toModelOutput` handlers can safely replay older results.\n * Does NOT mutate stored messages — operates on a copy.\n */\n\nimport { truncateToolOutput } from \"../../../chat/tool-output-truncation\";\nimport type { SessionMessage } from \"../session/types\";\n\nexport interface TruncateOptions {\n /** Number of recent messages to keep intact (default: 4) */\n keepRecent?: number;\n /** Max chars for tool outputs in older messages (default: 500) */\n maxToolOutputChars?: number;\n /** Max chars for text parts in older messages (default: 10000) */\n maxTextChars?: number;\n}\n\n/**\n * Truncate tool outputs and long text in older messages.\n * Returns a new array — input messages are not mutated.\n *\n * Recent messages (last `keepRecent`) are left intact.\n * Older messages get tool outputs and long text truncated. Structured tool\n * outputs are truncated in place instead of being replaced by raw strings.\n *\n * Use in assembleContext() before sending to the LLM:\n * ```typescript\n * async assembleContext() {\n * const history = this.sessions.getHistory(this._sessionId);\n * const truncated = truncateOlderMessages(history);\n * return convertToModelMessages(truncated);\n * }\n * ```\n */\nexport function truncateOlderMessages(\n messages: SessionMessage[],\n options?: TruncateOptions\n): SessionMessage[] {\n const keepRecent = options?.keepRecent ?? 4;\n const maxToolOutput = options?.maxToolOutputChars ?? 500;\n const maxText = options?.maxTextChars ?? 10000;\n\n if (messages.length <= keepRecent) return messages;\n\n const cutoff = messages.length - keepRecent;\n const result: SessionMessage[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (i >= cutoff) {\n result.push(messages[i]);\n continue;\n }\n\n const msg = messages[i];\n let changed = false;\n\n const truncatedParts = msg.parts.map((part) => {\n // Truncate tool outputs\n if (\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"output\" in part\n ) {\n const output = (part as { output?: unknown }).output;\n if (output !== undefined) {\n const truncated = truncateToolOutput(output, maxToolOutput);\n if (truncated.truncated) {\n changed = true;\n return {\n ...part,\n output: truncated.output\n };\n }\n }\n }\n\n // Truncate long text\n if (part.type === \"text\" && \"text\" in part) {\n const text = (part as { text: string }).text;\n if (text.length > maxText) {\n changed = true;\n return {\n ...part,\n text: `${text.slice(0, maxText)}... [truncated ${text.length} chars]`\n };\n }\n }\n\n return part;\n });\n\n result.push(\n changed ? ({ ...msg, parts: truncatedParts } as SessionMessage) : msg\n );\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,sBACd,UACA,SACkB;CAClB,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,gBAAgB,SAAS,sBAAsB;CACrD,MAAM,UAAU,SAAS,gBAAgB;CAEzC,IAAI,SAAS,UAAU,YAAY,OAAO;CAE1C,MAAM,SAAS,SAAS,SAAS;CACjC,MAAM,SAA2B,EAAE;CAEnC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,IAAI,KAAK,QAAQ;GACf,OAAO,KAAK,SAAS,GAAG;GACxB;;EAGF,MAAM,MAAM,SAAS;EACrB,IAAI,UAAU;EAEd,MAAM,iBAAiB,IAAI,MAAM,KAAK,SAAS;GAE7C,KACG,KAAK,KAAK,WAAW,QAAQ,IAAI,KAAK,SAAS,mBAChD,YAAY,MACZ;IACA,MAAM,SAAU,KAA8B;IAC9C,IAAI,WAAW,KAAA,GAAW;KACxB,MAAM,YAAY,mBAAmB,QAAQ,cAAc;KAC3D,IAAI,UAAU,WAAW;MACvB,UAAU;MACV,OAAO;OACL,GAAG;OACH,QAAQ,UAAU;OACnB;;;;GAMP,IAAI,KAAK,SAAS,UAAU,UAAU,MAAM;IAC1C,MAAM,OAAQ,KAA0B;IACxC,IAAI,KAAK,SAAS,SAAS;KACzB,UAAU;KACV,OAAO;MACL,GAAG;MACH,MAAM,GAAG,KAAK,MAAM,GAAG,QAAQ,CAAC,iBAAiB,KAAK,OAAO;MAC9D;;;GAIL,OAAO;IACP;EAEF,OAAO,KACL,UAAW;GAAE,GAAG;GAAK,OAAO;GAAgB,GAAsB,IACnE;;CAGH,OAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/experimental/memory/utils/compaction.ts"],"sourcesContent":["/**\n * Read-time context truncation.\n *\n * Truncates older tool outputs and long text before sending to the LLM.\n * Structured tool outputs keep their container shape so tool-specific\n * `toModelOutput` handlers can safely replay older results.\n * Does NOT mutate stored messages — operates on a copy.\n */\n\nimport { truncateToolOutput } from \"../../../chat/tool-output-truncation\";\nimport type { SessionMessage } from \"../session/types\";\n\nexport interface TruncateOptions {\n /** Number of recent messages to keep intact (default: 4) */\n keepRecent?: number;\n /** Max chars for tool outputs in older messages (default: 500) */\n maxToolOutputChars?: number;\n /** Max chars for text parts in older messages (default: 10000) */\n maxTextChars?: number;\n}\n\n/**\n * Truncate tool outputs and long text in older messages.\n * Returns a new array — input messages are not mutated.\n *\n * Recent messages (last `keepRecent`) are left intact.\n * Older messages get tool outputs and long text truncated. Structured tool\n * outputs are truncated in place instead of being replaced by raw strings.\n *\n * Use in assembleContext() before sending to the LLM:\n * ```typescript\n * async assembleContext() {\n * const history = this.sessions.getHistory(this._sessionId);\n * const truncated = truncateOlderMessages(history);\n * return convertToModelMessages(truncated);\n * }\n * ```\n */\nexport function truncateOlderMessages(\n messages: SessionMessage[],\n options?: TruncateOptions\n): SessionMessage[] {\n const keepRecent = options?.keepRecent ?? 4;\n const maxToolOutput = options?.maxToolOutputChars ?? 500;\n const maxText = options?.maxTextChars ?? 10000;\n\n if (messages.length <= keepRecent) return messages;\n\n const cutoff = messages.length - keepRecent;\n const result: SessionMessage[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (i >= cutoff) {\n result.push(messages[i]);\n continue;\n }\n\n const msg = messages[i];\n let changed = false;\n\n const truncatedParts = msg.parts.map((part) => {\n // Truncate tool outputs\n if (\n (part.type.startsWith(\"tool-\") || part.type === \"dynamic-tool\") &&\n \"output\" in part\n ) {\n const output = (part as { output?: unknown }).output;\n if (output !== undefined) {\n const truncated = truncateToolOutput(output, maxToolOutput);\n if (truncated.truncated) {\n changed = true;\n return {\n ...part,\n output: truncated.output\n };\n }\n }\n }\n\n // Truncate long text\n if (part.type === \"text\" && \"text\" in part) {\n const text = (part as { text: string }).text;\n if (text.length > maxText) {\n changed = true;\n return {\n ...part,\n text: `${text.slice(0, maxText)}... [truncated ${text.length} chars]`\n };\n }\n }\n\n return part;\n });\n\n result.push(\n changed ? ({ ...msg, parts: truncatedParts } as SessionMessage) : msg\n );\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,sBACd,UACA,SACkB;CAClB,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,gBAAgB,SAAS,sBAAsB;CACrD,MAAM,UAAU,SAAS,gBAAgB;CAEzC,IAAI,SAAS,UAAU,YAAY,OAAO;CAE1C,MAAM,SAAS,SAAS,SAAS;CACjC,MAAM,SAA2B,CAAC;CAElC,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,IAAI,KAAK,QAAQ;GACf,OAAO,KAAK,SAAS,EAAE;GACvB;EACF;EAEA,MAAM,MAAM,SAAS;EACrB,IAAI,UAAU;EAEd,MAAM,iBAAiB,IAAI,MAAM,KAAK,SAAS;GAE7C,KACG,KAAK,KAAK,WAAW,OAAO,KAAK,KAAK,SAAS,mBAChD,YAAY,MACZ;IACA,MAAM,SAAU,KAA8B;IAC9C,IAAI,WAAW,KAAA,GAAW;KACxB,MAAM,YAAY,mBAAmB,QAAQ,aAAa;KAC1D,IAAI,UAAU,WAAW;MACvB,UAAU;MACV,OAAO;OACL,GAAG;OACH,QAAQ,UAAU;MACpB;KACF;IACF;GACF;GAGA,IAAI,KAAK,SAAS,UAAU,UAAU,MAAM;IAC1C,MAAM,OAAQ,KAA0B;IACxC,IAAI,KAAK,SAAS,SAAS;KACzB,UAAU;KACV,OAAO;MACL,GAAG;MACH,MAAM,GAAG,KAAK,MAAM,GAAG,OAAO,EAAE,iBAAiB,KAAK,OAAO;KAC/D;IACF;GACF;GAEA,OAAO;EACT,CAAC;EAED,OAAO,KACL,UAAW;GAAE,GAAG;GAAK,OAAO;EAAe,IAAuB,GACpE;CACF;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"webmcp.js","names":[],"sources":["../../src/experimental/webmcp.ts"],"sourcesContent":["/**\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!\n * !! !!\n * !! This API is under active development and WILL break between !!\n * !! releases. Google's WebMCP API (navigator.modelContext) is still !!\n * !! in early preview and subject to change. !!\n * !! !!\n * !! If you use this, pin your agents version and expect to rewrite !!\n * !! your code when upgrading. !!\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n *\n * WebMCP adapter for Cloudflare Agents SDK.\n *\n * Bridges tools registered on an McpAgent server to Chrome's native\n * navigator.modelContext API, so browser-native agents can discover\n * and call them without extra infrastructure.\n *\n * @example Bridge a remote McpAgent endpoint into the page\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * const handle = await registerWebMcp({ url: \"/mcp\" });\n *\n * // Later, to clean up:\n * await handle.dispose();\n * ```\n *\n * @example Mix in-page tools with bridged tools (recommended pattern)\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * // 1. Register page-local tools — things only the page can do\n * navigator.modelContext?.registerTool({\n * name: \"scroll_to_section\",\n * description: \"Scroll the page to a named section\",\n * inputSchema: {\n * type: \"object\",\n * properties: { id: { type: \"string\" } },\n * required: [\"id\"]\n * },\n * async execute({ id }) {\n * document.getElementById(String(id))?.scrollIntoView({ behavior: \"smooth\" });\n * return \"ok\";\n * }\n * });\n *\n * // 2. Bridge server tools — things that need durable storage / auth / DB access\n * const handle = await registerWebMcp({\n * url: \"/mcp\",\n * prefix: \"remote.\", // optional namespace to avoid collisions\n * getHeaders: async () => ({ Authorization: `Bearer ${await getToken()}` })\n * });\n *\n * // The browser AI sees both kinds of tools side by side.\n * ```\n *\n * @experimental This API is not yet stable and may change.\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { ToolListChangedNotificationSchema } from \"@modelcontextprotocol/sdk/types.js\";\n\n// ── WebMCP browser API surface (Chrome's navigator.modelContext) ─────\n\ninterface ModelContextToolAnnotations {\n readOnlyHint?: boolean;\n}\n\ninterface ModelContextClient {\n requestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContextTool {\n name: string;\n description: string;\n inputSchema?: Record<string, unknown>;\n execute: (\n input: Record<string, unknown>,\n client: ModelContextClient\n ) => Promise<unknown>;\n annotations?: ModelContextToolAnnotations;\n}\n\ninterface ModelContextRegisterToolOptions {\n signal?: AbortSignal;\n}\n\ninterface ModelContext {\n registerTool(\n tool: ModelContextTool,\n options?: ModelContextRegisterToolOptions\n ): void;\n}\n\ndeclare global {\n interface Navigator {\n modelContext?: ModelContext;\n }\n}\n\n// ── Internal types ───────────────────────────────────────────────────\n\ninterface McpTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n annotations?: { readOnlyHint?: boolean };\n}\n\ninterface McpToolCallResult {\n content: Array<{\n type: string;\n text?: string;\n data?: string;\n mimeType?: string;\n }>;\n isError?: boolean;\n}\n\n/**\n * Logger interface for adapter diagnostics. Defaults to `console`.\n * Pass a no-op implementation (or `quiet: true`) to silence output.\n */\nexport interface WebMcpLogger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nconst DEFAULT_LOGGER: WebMcpLogger = {\n info: (...args) => console.info(\"[webmcp-adapter]\", ...args),\n warn: (...args) => console.warn(\"[webmcp-adapter]\", ...args),\n error: (...args) => console.error(\"[webmcp-adapter]\", ...args)\n};\n\nconst SILENT_LOGGER: WebMcpLogger = {\n info: () => {},\n warn: () => {},\n error: () => {}\n};\n\n// ── MCP transport wrapper ────────────────────────────────────────────\n\nclass McpHttpClient {\n private _client: Client;\n private _transport: StreamableHTTPClientTransport;\n private _onToolsChanged?: () => void;\n private _timeoutMs?: number;\n\n constructor(\n url: string,\n headers?: Record<string, string>,\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>,\n timeoutMs?: number\n ) {\n const resolvedUrl = new URL(url, globalThis.location?.origin);\n this._timeoutMs = timeoutMs;\n\n const transportOptions: ConstructorParameters<\n typeof StreamableHTTPClientTransport\n >[1] = {\n requestInit: { headers: headers ?? {} }\n };\n\n if (getHeaders) {\n transportOptions.fetch = async (input, init) => {\n const dynamic = await getHeaders();\n const merged = new Headers(init?.headers);\n for (const [k, v] of Object.entries(dynamic)) {\n merged.set(k, v);\n }\n return globalThis.fetch(input, {\n ...init,\n headers: merged\n });\n };\n }\n\n this._transport = new StreamableHTTPClientTransport(\n resolvedUrl,\n transportOptions\n );\n\n this._client = new Client(\n { name: \"webmcp-adapter\", version: \"0.1.0\" },\n { capabilities: {} }\n );\n }\n\n async initialize(signal?: AbortSignal): Promise<void> {\n await this._client.connect(this._transport);\n\n this._client.setNotificationHandler(\n ToolListChangedNotificationSchema,\n async () => {\n if (signal?.aborted) return;\n this._onToolsChanged?.();\n }\n );\n }\n\n async listTools(signal?: AbortSignal): Promise<McpTool[]> {\n const allTools: McpTool[] = [];\n let cursor: string | undefined;\n do {\n if (signal?.aborted) throw new DOMException(\"Aborted\", \"AbortError\");\n const result = await this._client.listTools(\n cursor ? { cursor } : undefined,\n { signal, timeout: this._timeoutMs }\n );\n for (const t of result.tools) {\n allTools.push({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as Record<string, unknown> | undefined,\n annotations: t.annotations\n ? { readOnlyHint: t.annotations.readOnlyHint }\n : undefined\n });\n }\n cursor = result.nextCursor;\n } while (cursor);\n return allTools;\n }\n\n async callTool(\n name: string,\n args: Record<string, unknown>,\n signal?: AbortSignal\n ): Promise<McpToolCallResult> {\n const result = await this._client.callTool(\n { name, arguments: args },\n undefined,\n { signal, timeout: this._timeoutMs }\n );\n if (\"content\" in result) {\n return {\n content: (\n result.content as Array<{\n type: string;\n text?: string;\n data?: string;\n }>\n ).map((c) => ({\n type: c.type,\n text: \"text\" in c ? (c.text as string) : undefined,\n data: \"data\" in c ? (c.data as string) : undefined,\n mimeType: \"mimeType\" in c ? (c.mimeType as string) : undefined\n })),\n isError: \"isError\" in result ? (result.isError as boolean) : false\n };\n }\n return { content: [], isError: false };\n }\n\n listenForChanges(onToolsChanged: () => void): void {\n this._onToolsChanged = onToolsChanged;\n }\n\n async close(): Promise<void> {\n try {\n await this._client.close();\n } catch {\n // Closing a never-connected or already-closed client is fine.\n }\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\nexport interface WebMcpOptions {\n /** URL of the MCP endpoint (absolute or relative, e.g. `\"/mcp\"`). */\n url: string;\n /**\n * Additional headers to include in every request to the MCP server.\n * Useful for static authentication (e.g. `{ Authorization: \"Bearer <token>\" }`).\n */\n headers?: Record<string, string>;\n /**\n * Async function that returns headers for each request.\n * Called before every request, useful for tokens that refresh.\n * If both `headers` and `getHeaders` are provided, they are merged\n * with `getHeaders` values taking precedence.\n */\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;\n /**\n * If true, listen for `tools/list_changed` notifications and re-sync\n * tools with `navigator.modelContext`. The adapter opens an SSE GET to\n * the MCP endpoint to receive notifications; servers that don't support\n * server-initiated streams (e.g. respond `405` on GET) gracefully degrade.\n * @default true\n */\n watch?: boolean;\n /**\n * Optional namespace prefix prepended to every tool name registered with\n * `navigator.modelContext`. Useful when bridging multiple MCP servers, or\n * when the page also registers in-page tools and you want to avoid\n * collisions. The original (unprefixed) name is still used on the wire\n * when calling the server.\n *\n * @example `prefix: \"remote.\"` turns `search` into `remote.search`.\n */\n prefix?: string;\n /**\n * Per-request timeout (in milliseconds) applied to `tools/list` and\n * `tools/call`. If the server doesn't respond in time, the request is\n * aborted and the resulting error is surfaced through the normal error\n * paths (`onError` for sync, rejection for tool execution).\n */\n timeoutMs?: number;\n /**\n * Custom logger. Defaults to `console` with a `[webmcp-adapter]` prefix.\n * Pass `{ info: () => {}, warn: () => {}, error: () => {} }` to silence.\n */\n logger?: WebMcpLogger;\n /** Convenience shortcut for `logger: SILENT_LOGGER`. @default false */\n quiet?: boolean;\n /**\n * Called whenever the adapter performs a successful sync (initial load\n * and on `tools/list_changed`). Receives the tools as the server returned\n * them (with their original, unprefixed names).\n */\n onSync?: (tools: McpTool[]) => void;\n /**\n * Called when an error occurs during background work that the caller\n * cannot otherwise observe — specifically: a watch-mode re-sync failure.\n *\n * **Not** called for:\n * - Initialization failures (those reject the `registerWebMcp` promise).\n * - Per-tool execution failures (those reject the `execute` promise the\n * browser host awaits; Chrome surfaces them to the AI).\n */\n onError?: (error: Error) => void;\n}\n\nexport interface WebMcpHandle {\n /**\n * Currently registered tool names (with `prefix` applied). Returns a fresh\n * snapshot on each access — safe to mutate.\n */\n readonly tools: ReadonlyArray<string>;\n /**\n * Re-fetch the tool list from the server and re-register everything.\n * If a sync is already in flight (from a `tools/list_changed` notification\n * or a previous `refresh()` call), returns the in-flight promise rather\n * than starting a second sync.\n */\n refresh(): Promise<void>;\n /**\n * Unregister all tools, signal any in-flight work to abort, and close the\n * MCP connection. Safe to call multiple times.\n */\n dispose(): Promise<void>;\n /** True after `dispose()` has been called at least once. */\n readonly disposed: boolean;\n}\n\n/**\n * Discovers tools from a Cloudflare McpAgent endpoint and registers them\n * with Chrome's native `navigator.modelContext` API.\n *\n * On browsers without `navigator.modelContext` (everything except recent\n * Chrome with the relevant flags), this function is a no-op and returns a\n * handle with an empty tools array. No network request is made.\n *\n * @example\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * const handle = await registerWebMcp({ url: \"/mcp\" });\n * console.log(\"Registered tools:\", handle.tools);\n *\n * // Clean up when done (e.g. in a React effect cleanup)\n * await handle.dispose();\n * ```\n *\n * See the JSDoc on the module itself for the recommended \"in-page tools +\n * remote tools\" composition pattern.\n */\nexport async function registerWebMcp(\n options: WebMcpOptions\n): Promise<WebMcpHandle> {\n const {\n url,\n headers,\n getHeaders,\n watch = true,\n prefix = \"\",\n timeoutMs,\n logger: userLogger,\n quiet = false,\n onSync,\n onError\n } = options;\n\n const logger = quiet ? SILENT_LOGGER : (userLogger ?? DEFAULT_LOGGER);\n\n const registeredTools: string[] = [];\n const toolControllers = new Map<string, AbortController>();\n const lifecycleController = new AbortController();\n let disposed = false;\n let inflightSync: Promise<void> | null = null;\n\n if (!navigator.modelContext) {\n logger.info(\n \"navigator.modelContext not available — skipping registration. \" +\n \"This is expected on non-Chrome browsers.\"\n );\n onSync?.([]);\n return {\n get tools() {\n return [];\n },\n get disposed() {\n return disposed;\n },\n refresh: async () => {},\n dispose: async () => {\n disposed = true;\n }\n };\n }\n\n const modelContext: ModelContext = navigator.modelContext;\n const client = new McpHttpClient(url, headers, getHeaders, timeoutMs);\n\n function unregisterAll(): void {\n for (const controller of toolControllers.values()) {\n controller.abort();\n }\n toolControllers.clear();\n registeredTools.length = 0;\n }\n\n function registerTools(tools: McpTool[]): void {\n for (const tool of tools) {\n const registeredName = `${prefix}${tool.name}`;\n const toolDef: ModelContextTool = {\n name: registeredName,\n description: tool.description ?? tool.name,\n ...(tool.inputSchema ? { inputSchema: tool.inputSchema } : {}),\n ...(tool.annotations\n ? { annotations: { readOnlyHint: tool.annotations.readOnlyHint } }\n : {}),\n execute: async (input: Record<string, unknown>) => {\n if (disposed) {\n throw new Error(\"WebMCP adapter has been disposed\");\n }\n const result = await client.callTool(\n tool.name,\n input,\n lifecycleController.signal\n );\n\n if (result.isError) {\n const errorText = result.content\n .map((c) => c.text ?? \"\")\n .join(\"\\n\");\n throw new Error(errorText || \"Tool execution failed\");\n }\n\n const parts: string[] = [];\n let sawUnsupported = false;\n for (const c of result.content) {\n if (c.type === \"text\" && c.text) {\n parts.push(c.text);\n } else if (c.type === \"image\" && c.data) {\n parts.push(`data:${c.mimeType ?? \"image/png\"};base64,${c.data}`);\n } else if (c.data) {\n parts.push(c.data);\n sawUnsupported = true;\n } else {\n sawUnsupported = true;\n }\n }\n if (sawUnsupported) {\n logger.warn(\n `Tool \"${tool.name}\" returned content type(s) the adapter` +\n \" cannot fully represent as a string.\"\n );\n }\n return parts.join(\"\\n\");\n }\n };\n\n try {\n const controller = new AbortController();\n modelContext.registerTool(toolDef, { signal: controller.signal });\n toolControllers.set(registeredName, controller);\n registeredTools.push(registeredName);\n } catch (err) {\n logger.warn(`Failed to register tool \"${registeredName}\":`, err);\n }\n }\n }\n\n // Serialize syncs: if one is already running, share its promise. This\n // prevents the unregister/listTools/registerTools sequence from\n // interleaving when both `refresh()` and a `tools/list_changed`\n // notification fire concurrently.\n function syncTools(): Promise<void> {\n if (disposed) return Promise.resolve();\n if (inflightSync) return inflightSync;\n inflightSync = (async () => {\n try {\n const tools = await client.listTools(lifecycleController.signal);\n if (disposed) return;\n unregisterAll();\n registerTools(tools);\n onSync?.(tools);\n } finally {\n inflightSync = null;\n }\n })();\n return inflightSync;\n }\n\n try {\n await client.initialize(lifecycleController.signal);\n await syncTools();\n\n if (watch) {\n client.listenForChanges(() => {\n if (disposed) return;\n syncTools().catch((err: unknown) => {\n if (disposed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n logger.warn(\"Watch-mode sync failed:\", error);\n onError?.(error);\n });\n });\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n logger.error(\"Initialization failed:\", error);\n // Best-effort cleanup so a failed init doesn't leak the transport.\n await client.close();\n throw error;\n }\n\n return {\n get tools() {\n return [...registeredTools];\n },\n get disposed() {\n return disposed;\n },\n refresh: syncTools,\n async dispose() {\n if (disposed) return;\n disposed = true;\n lifecycleController.abort();\n unregisterAll();\n // Wait for any in-flight sync to settle so callers can rely on\n // a quiet adapter after `await handle.dispose()`.\n try {\n await inflightSync;\n } catch {\n // Already surfaced via onError or thrown to the original caller.\n }\n await client.close();\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"webmcp.js","names":[],"sources":["../../src/experimental/webmcp.ts"],"sourcesContent":["/**\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!\n * !! !!\n * !! This API is under active development and WILL break between !!\n * !! releases. Google's WebMCP API (navigator.modelContext) is still !!\n * !! in early preview and subject to change. !!\n * !! !!\n * !! If you use this, pin your agents version and expect to rewrite !!\n * !! your code when upgrading. !!\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n *\n * WebMCP adapter for Cloudflare Agents SDK.\n *\n * Bridges tools registered on an McpAgent server to Chrome's native\n * navigator.modelContext API, so browser-native agents can discover\n * and call them without extra infrastructure.\n *\n * @example Bridge a remote McpAgent endpoint into the page\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * const handle = await registerWebMcp({ url: \"/mcp\" });\n *\n * // Later, to clean up:\n * await handle.dispose();\n * ```\n *\n * @example Mix in-page tools with bridged tools (recommended pattern)\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * // 1. Register page-local tools — things only the page can do\n * navigator.modelContext?.registerTool({\n * name: \"scroll_to_section\",\n * description: \"Scroll the page to a named section\",\n * inputSchema: {\n * type: \"object\",\n * properties: { id: { type: \"string\" } },\n * required: [\"id\"]\n * },\n * async execute({ id }) {\n * document.getElementById(String(id))?.scrollIntoView({ behavior: \"smooth\" });\n * return \"ok\";\n * }\n * });\n *\n * // 2. Bridge server tools — things that need durable storage / auth / DB access\n * const handle = await registerWebMcp({\n * url: \"/mcp\",\n * prefix: \"remote.\", // optional namespace to avoid collisions\n * getHeaders: async () => ({ Authorization: `Bearer ${await getToken()}` })\n * });\n *\n * // The browser AI sees both kinds of tools side by side.\n * ```\n *\n * @experimental This API is not yet stable and may change.\n */\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { ToolListChangedNotificationSchema } from \"@modelcontextprotocol/sdk/types.js\";\n\n// ── WebMCP browser API surface (Chrome's navigator.modelContext) ─────\n\ninterface ModelContextToolAnnotations {\n readOnlyHint?: boolean;\n}\n\ninterface ModelContextClient {\n requestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>;\n}\n\ninterface ModelContextTool {\n name: string;\n description: string;\n inputSchema?: Record<string, unknown>;\n execute: (\n input: Record<string, unknown>,\n client: ModelContextClient\n ) => Promise<unknown>;\n annotations?: ModelContextToolAnnotations;\n}\n\ninterface ModelContextRegisterToolOptions {\n signal?: AbortSignal;\n}\n\ninterface ModelContext {\n registerTool(\n tool: ModelContextTool,\n options?: ModelContextRegisterToolOptions\n ): void;\n}\n\ndeclare global {\n interface Navigator {\n modelContext?: ModelContext;\n }\n}\n\n// ── Internal types ───────────────────────────────────────────────────\n\ninterface McpTool {\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n annotations?: { readOnlyHint?: boolean };\n}\n\ninterface McpToolCallResult {\n content: Array<{\n type: string;\n text?: string;\n data?: string;\n mimeType?: string;\n }>;\n isError?: boolean;\n}\n\n/**\n * Logger interface for adapter diagnostics. Defaults to `console`.\n * Pass a no-op implementation (or `quiet: true`) to silence output.\n */\nexport interface WebMcpLogger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nconst DEFAULT_LOGGER: WebMcpLogger = {\n info: (...args) => console.info(\"[webmcp-adapter]\", ...args),\n warn: (...args) => console.warn(\"[webmcp-adapter]\", ...args),\n error: (...args) => console.error(\"[webmcp-adapter]\", ...args)\n};\n\nconst SILENT_LOGGER: WebMcpLogger = {\n info: () => {},\n warn: () => {},\n error: () => {}\n};\n\n// ── MCP transport wrapper ────────────────────────────────────────────\n\nclass McpHttpClient {\n private _client: Client;\n private _transport: StreamableHTTPClientTransport;\n private _onToolsChanged?: () => void;\n private _timeoutMs?: number;\n\n constructor(\n url: string,\n headers?: Record<string, string>,\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>,\n timeoutMs?: number\n ) {\n const resolvedUrl = new URL(url, globalThis.location?.origin);\n this._timeoutMs = timeoutMs;\n\n const transportOptions: ConstructorParameters<\n typeof StreamableHTTPClientTransport\n >[1] = {\n requestInit: { headers: headers ?? {} }\n };\n\n if (getHeaders) {\n transportOptions.fetch = async (input, init) => {\n const dynamic = await getHeaders();\n const merged = new Headers(init?.headers);\n for (const [k, v] of Object.entries(dynamic)) {\n merged.set(k, v);\n }\n return globalThis.fetch(input, {\n ...init,\n headers: merged\n });\n };\n }\n\n this._transport = new StreamableHTTPClientTransport(\n resolvedUrl,\n transportOptions\n );\n\n this._client = new Client(\n { name: \"webmcp-adapter\", version: \"0.1.0\" },\n { capabilities: {} }\n );\n }\n\n async initialize(signal?: AbortSignal): Promise<void> {\n await this._client.connect(this._transport);\n\n this._client.setNotificationHandler(\n ToolListChangedNotificationSchema,\n async () => {\n if (signal?.aborted) return;\n this._onToolsChanged?.();\n }\n );\n }\n\n async listTools(signal?: AbortSignal): Promise<McpTool[]> {\n const allTools: McpTool[] = [];\n let cursor: string | undefined;\n do {\n if (signal?.aborted) throw new DOMException(\"Aborted\", \"AbortError\");\n const result = await this._client.listTools(\n cursor ? { cursor } : undefined,\n { signal, timeout: this._timeoutMs }\n );\n for (const t of result.tools) {\n allTools.push({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as Record<string, unknown> | undefined,\n annotations: t.annotations\n ? { readOnlyHint: t.annotations.readOnlyHint }\n : undefined\n });\n }\n cursor = result.nextCursor;\n } while (cursor);\n return allTools;\n }\n\n async callTool(\n name: string,\n args: Record<string, unknown>,\n signal?: AbortSignal\n ): Promise<McpToolCallResult> {\n const result = await this._client.callTool(\n { name, arguments: args },\n undefined,\n { signal, timeout: this._timeoutMs }\n );\n if (\"content\" in result) {\n return {\n content: (\n result.content as Array<{\n type: string;\n text?: string;\n data?: string;\n }>\n ).map((c) => ({\n type: c.type,\n text: \"text\" in c ? (c.text as string) : undefined,\n data: \"data\" in c ? (c.data as string) : undefined,\n mimeType: \"mimeType\" in c ? (c.mimeType as string) : undefined\n })),\n isError: \"isError\" in result ? (result.isError as boolean) : false\n };\n }\n return { content: [], isError: false };\n }\n\n listenForChanges(onToolsChanged: () => void): void {\n this._onToolsChanged = onToolsChanged;\n }\n\n async close(): Promise<void> {\n try {\n await this._client.close();\n } catch {\n // Closing a never-connected or already-closed client is fine.\n }\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────\n\nexport interface WebMcpOptions {\n /** URL of the MCP endpoint (absolute or relative, e.g. `\"/mcp\"`). */\n url: string;\n /**\n * Additional headers to include in every request to the MCP server.\n * Useful for static authentication (e.g. `{ Authorization: \"Bearer <token>\" }`).\n */\n headers?: Record<string, string>;\n /**\n * Async function that returns headers for each request.\n * Called before every request, useful for tokens that refresh.\n * If both `headers` and `getHeaders` are provided, they are merged\n * with `getHeaders` values taking precedence.\n */\n getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;\n /**\n * If true, listen for `tools/list_changed` notifications and re-sync\n * tools with `navigator.modelContext`. The adapter opens an SSE GET to\n * the MCP endpoint to receive notifications; servers that don't support\n * server-initiated streams (e.g. respond `405` on GET) gracefully degrade.\n * @default true\n */\n watch?: boolean;\n /**\n * Optional namespace prefix prepended to every tool name registered with\n * `navigator.modelContext`. Useful when bridging multiple MCP servers, or\n * when the page also registers in-page tools and you want to avoid\n * collisions. The original (unprefixed) name is still used on the wire\n * when calling the server.\n *\n * @example `prefix: \"remote.\"` turns `search` into `remote.search`.\n */\n prefix?: string;\n /**\n * Per-request timeout (in milliseconds) applied to `tools/list` and\n * `tools/call`. If the server doesn't respond in time, the request is\n * aborted and the resulting error is surfaced through the normal error\n * paths (`onError` for sync, rejection for tool execution).\n */\n timeoutMs?: number;\n /**\n * Custom logger. Defaults to `console` with a `[webmcp-adapter]` prefix.\n * Pass `{ info: () => {}, warn: () => {}, error: () => {} }` to silence.\n */\n logger?: WebMcpLogger;\n /** Convenience shortcut for `logger: SILENT_LOGGER`. @default false */\n quiet?: boolean;\n /**\n * Called whenever the adapter performs a successful sync (initial load\n * and on `tools/list_changed`). Receives the tools as the server returned\n * them (with their original, unprefixed names).\n */\n onSync?: (tools: McpTool[]) => void;\n /**\n * Called when an error occurs during background work that the caller\n * cannot otherwise observe — specifically: a watch-mode re-sync failure.\n *\n * **Not** called for:\n * - Initialization failures (those reject the `registerWebMcp` promise).\n * - Per-tool execution failures (those reject the `execute` promise the\n * browser host awaits; Chrome surfaces them to the AI).\n */\n onError?: (error: Error) => void;\n}\n\nexport interface WebMcpHandle {\n /**\n * Currently registered tool names (with `prefix` applied). Returns a fresh\n * snapshot on each access — safe to mutate.\n */\n readonly tools: ReadonlyArray<string>;\n /**\n * Re-fetch the tool list from the server and re-register everything.\n * If a sync is already in flight (from a `tools/list_changed` notification\n * or a previous `refresh()` call), returns the in-flight promise rather\n * than starting a second sync.\n */\n refresh(): Promise<void>;\n /**\n * Unregister all tools, signal any in-flight work to abort, and close the\n * MCP connection. Safe to call multiple times.\n */\n dispose(): Promise<void>;\n /** True after `dispose()` has been called at least once. */\n readonly disposed: boolean;\n}\n\n/**\n * Discovers tools from a Cloudflare McpAgent endpoint and registers them\n * with Chrome's native `navigator.modelContext` API.\n *\n * On browsers without `navigator.modelContext` (everything except recent\n * Chrome with the relevant flags), this function is a no-op and returns a\n * handle with an empty tools array. No network request is made.\n *\n * @example\n * ```ts\n * import { registerWebMcp } from \"agents/experimental/webmcp\";\n *\n * const handle = await registerWebMcp({ url: \"/mcp\" });\n * console.log(\"Registered tools:\", handle.tools);\n *\n * // Clean up when done (e.g. in a React effect cleanup)\n * await handle.dispose();\n * ```\n *\n * See the JSDoc on the module itself for the recommended \"in-page tools +\n * remote tools\" composition pattern.\n */\nexport async function registerWebMcp(\n options: WebMcpOptions\n): Promise<WebMcpHandle> {\n const {\n url,\n headers,\n getHeaders,\n watch = true,\n prefix = \"\",\n timeoutMs,\n logger: userLogger,\n quiet = false,\n onSync,\n onError\n } = options;\n\n const logger = quiet ? SILENT_LOGGER : (userLogger ?? DEFAULT_LOGGER);\n\n const registeredTools: string[] = [];\n const toolControllers = new Map<string, AbortController>();\n const lifecycleController = new AbortController();\n let disposed = false;\n let inflightSync: Promise<void> | null = null;\n\n if (!navigator.modelContext) {\n logger.info(\n \"navigator.modelContext not available — skipping registration. \" +\n \"This is expected on non-Chrome browsers.\"\n );\n onSync?.([]);\n return {\n get tools() {\n return [];\n },\n get disposed() {\n return disposed;\n },\n refresh: async () => {},\n dispose: async () => {\n disposed = true;\n }\n };\n }\n\n const modelContext: ModelContext = navigator.modelContext;\n const client = new McpHttpClient(url, headers, getHeaders, timeoutMs);\n\n function unregisterAll(): void {\n for (const controller of toolControllers.values()) {\n controller.abort();\n }\n toolControllers.clear();\n registeredTools.length = 0;\n }\n\n function registerTools(tools: McpTool[]): void {\n for (const tool of tools) {\n const registeredName = `${prefix}${tool.name}`;\n const toolDef: ModelContextTool = {\n name: registeredName,\n description: tool.description ?? tool.name,\n ...(tool.inputSchema ? { inputSchema: tool.inputSchema } : {}),\n ...(tool.annotations\n ? { annotations: { readOnlyHint: tool.annotations.readOnlyHint } }\n : {}),\n execute: async (input: Record<string, unknown>) => {\n if (disposed) {\n throw new Error(\"WebMCP adapter has been disposed\");\n }\n const result = await client.callTool(\n tool.name,\n input,\n lifecycleController.signal\n );\n\n if (result.isError) {\n const errorText = result.content\n .map((c) => c.text ?? \"\")\n .join(\"\\n\");\n throw new Error(errorText || \"Tool execution failed\");\n }\n\n const parts: string[] = [];\n let sawUnsupported = false;\n for (const c of result.content) {\n if (c.type === \"text\" && c.text) {\n parts.push(c.text);\n } else if (c.type === \"image\" && c.data) {\n parts.push(`data:${c.mimeType ?? \"image/png\"};base64,${c.data}`);\n } else if (c.data) {\n parts.push(c.data);\n sawUnsupported = true;\n } else {\n sawUnsupported = true;\n }\n }\n if (sawUnsupported) {\n logger.warn(\n `Tool \"${tool.name}\" returned content type(s) the adapter` +\n \" cannot fully represent as a string.\"\n );\n }\n return parts.join(\"\\n\");\n }\n };\n\n try {\n const controller = new AbortController();\n modelContext.registerTool(toolDef, { signal: controller.signal });\n toolControllers.set(registeredName, controller);\n registeredTools.push(registeredName);\n } catch (err) {\n logger.warn(`Failed to register tool \"${registeredName}\":`, err);\n }\n }\n }\n\n // Serialize syncs: if one is already running, share its promise. This\n // prevents the unregister/listTools/registerTools sequence from\n // interleaving when both `refresh()` and a `tools/list_changed`\n // notification fire concurrently.\n function syncTools(): Promise<void> {\n if (disposed) return Promise.resolve();\n if (inflightSync) return inflightSync;\n inflightSync = (async () => {\n try {\n const tools = await client.listTools(lifecycleController.signal);\n if (disposed) return;\n unregisterAll();\n registerTools(tools);\n onSync?.(tools);\n } finally {\n inflightSync = null;\n }\n })();\n return inflightSync;\n }\n\n try {\n await client.initialize(lifecycleController.signal);\n await syncTools();\n\n if (watch) {\n client.listenForChanges(() => {\n if (disposed) return;\n syncTools().catch((err: unknown) => {\n if (disposed) return;\n const error = err instanceof Error ? err : new Error(String(err));\n logger.warn(\"Watch-mode sync failed:\", error);\n onError?.(error);\n });\n });\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n logger.error(\"Initialization failed:\", error);\n // Best-effort cleanup so a failed init doesn't leak the transport.\n await client.close();\n throw error;\n }\n\n return {\n get tools() {\n return [...registeredTools];\n },\n get disposed() {\n return disposed;\n },\n refresh: syncTools,\n async dispose() {\n if (disposed) return;\n disposed = true;\n lifecycleController.abort();\n unregisterAll();\n // Wait for any in-flight sync to settle so callers can rely on\n // a quiet adapter after `await handle.dispose()`.\n try {\n await inflightSync;\n } catch {\n // Already surfaced via onError or thrown to the original caller.\n }\n await client.close();\n }\n };\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  x as AgentNamespace,
60
60
  y as AgentContext,
61
61
  z as Schedule
62
- } from "./agent-tool-types-CM_50fcV.js";
62
+ } from "./agent-tool-types-BVgYyKO9.js";
63
63
  import { t as RetryOptions } from "./retries-BVdRl5ZE.js";
64
64
  import {
65
65
  n as AgentsOAuthProvider,
@@ -74,27 +74,27 @@ export {
74
74
  Agent,
75
75
  AgentContext,
76
76
  AgentGetOptions,
77
- AgentMcpOAuthProvider,
77
+ type AgentMcpOAuthProvider,
78
78
  AgentNamespace,
79
79
  AgentOptions,
80
80
  AgentStaticOptions,
81
- AgentToolChildAdapter,
82
- AgentToolDisplayMetadata,
83
- AgentToolEvent,
84
- AgentToolEventMessage,
85
- AgentToolEventState,
86
- AgentToolLifecycleResult,
87
- AgentToolRunInfo,
88
- AgentToolRunInspection,
89
- AgentToolRunState,
90
- AgentToolRunStatus,
91
- AgentToolStoredChunk,
92
- AgentToolTerminalStatus,
93
- AgentsOAuthProvider,
81
+ type AgentToolChildAdapter,
82
+ type AgentToolDisplayMetadata,
83
+ type AgentToolEvent,
84
+ type AgentToolEventMessage,
85
+ type AgentToolEventState,
86
+ type AgentToolLifecycleResult,
87
+ type AgentToolRunInfo,
88
+ type AgentToolRunInspection,
89
+ type AgentToolRunState,
90
+ type AgentToolRunStatus,
91
+ type AgentToolStoredChunk,
92
+ type AgentToolTerminalStatus,
93
+ type AgentsOAuthProvider,
94
94
  CallableMetadata,
95
- ChatCapableAgentClass,
96
- Connection,
97
- ConnectionContext,
95
+ type ChatCapableAgentClass,
96
+ type Connection,
97
+ type ConnectionContext,
98
98
  DEFAULT_AGENT_STATIC_OPTIONS,
99
99
  DurableObjectOAuthClientProvider,
100
100
  EmailRoutingOptions,
@@ -108,10 +108,10 @@ export {
108
108
  QueueItem,
109
109
  RPCRequest,
110
110
  RPCResponse,
111
- RetryOptions,
112
- RoutingRetryOptions,
113
- RunAgentToolOptions,
114
- RunAgentToolResult,
111
+ type RetryOptions,
112
+ type RoutingRetryOptions,
113
+ type RunAgentToolOptions,
114
+ type RunAgentToolResult,
115
115
  SUB_PREFIX,
116
116
  Schedule,
117
117
  ScheduleCriteria,
@@ -120,10 +120,10 @@ export {
120
120
  StateUpdateMessage,
121
121
  StreamingResponse,
122
122
  SubAgentClass,
123
- SubAgentPathMatch,
123
+ type SubAgentPathMatch,
124
124
  SubAgentStub,
125
- TransportType,
126
- WSMessage,
125
+ type TransportType,
126
+ type WSMessage,
127
127
  __DO_NOT_USE_WILL_BREAK__agentContext,
128
128
  callable,
129
129
  createHeaderBasedEmailResolver,
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
2
2
  import { MessageType } from "./types.js";
3
- import { camelCaseToKebabCase } from "./utils.js";
3
+ import { camelCaseToKebabCase, isInternalJsStubProp } from "./utils.js";
4
4
  import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
5
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-CS51BNGR.js";
5
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-Evpt0SEr.js";
6
6
  import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
7
7
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
8
8
  import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-D1kFXo80.js";
@@ -12,7 +12,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
12
12
  import { parseCronExpression } from "cron-schedule";
13
13
  import { nanoid } from "nanoid";
14
14
  import { EmailMessage } from "cloudflare:email";
15
- import { RpcTarget } from "cloudflare:workers";
15
+ import { RpcTarget, exports } from "cloudflare:workers";
16
16
  import { Server, getServerByName, routePartykitRequest } from "partyserver";
17
17
  //#region src/index.ts
18
18
  let _Symbol$dispose;
@@ -3092,7 +3092,29 @@ var Agent = class Agent extends Server {
3092
3092
  * @internal
3093
3093
  */
3094
3094
  async _cf_invokeSubAgent(className, name, method, args) {
3095
- const handle = await this._cf_resolveSubAgent(className, name);
3095
+ const stub = await this._cf_resolveSubAgent(className, name);
3096
+ return await this._cf_invokeStubMethod(stub, className, method, args);
3097
+ }
3098
+ /**
3099
+ * Bridge method used by `parentAgent()` when the requested parent is
3100
+ * itself a facet (and therefore has no top-level env namespace).
3101
+ * The root receives the full root-first target path, then each hop
3102
+ * delegates to the next facet using that facet's own `ctx.facets`.
3103
+ *
3104
+ * @internal
3105
+ */
3106
+ async _cf_invokeSubAgentPath(path, method, args) {
3107
+ const [self, next, ...rest] = path;
3108
+ if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
3109
+ const ownClassName = this.constructor.name;
3110
+ if (self.className !== ownClassName || self.name !== this.name) throw new Error(`Sub-agent path invocation reached ${ownClassName}("${this.name}") but expected ${self.className}("${self.name}").`);
3111
+ if (!next) return await this._cf_invokeStubMethod(this, this.constructor.name, method, args);
3112
+ const child = await this._cf_resolveSubAgent(next.className, next.name);
3113
+ if (rest.length === 0) return await this._cf_invokeStubMethod(child, next.className, method, args);
3114
+ return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
3115
+ }
3116
+ async _cf_invokeStubMethod(stub, className, method, args) {
3117
+ const handle = stub;
3096
3118
  if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
3097
3119
  return await handle[method](...args);
3098
3120
  }
@@ -3116,11 +3138,11 @@ var Agent = class Agent extends Server {
3116
3138
  *
3117
3139
  * The facet's name (and `this.name` getter) is handled entirely by
3118
3140
  * partyserver via `ctx.id.name`, which is populated because the
3119
- * parent passed an explicit `id: parentNs.idFromName(name)` to
3141
+ * parent passed an explicit named Durable Object id to
3120
3142
  * `ctx.facets.get()` — see {@link _cf_resolveSubAgent}. No
3121
3143
  * `setName()` call or `__ps_name` storage write is needed; the
3122
- * facet's name survives cold wake automatically because the
3123
- * factory re-runs and `idFromName` is deterministic.
3144
+ * facet's name survives cold wake automatically because the factory
3145
+ * re-runs and `idFromName` is deterministic.
3124
3146
  *
3125
3147
  * @internal Called by {@link subAgent}.
3126
3148
  */
@@ -3167,26 +3189,33 @@ var Agent = class Agent extends Server {
3167
3189
  }];
3168
3190
  }
3169
3191
  /**
3170
- * Resolve a typed RPC stub for this facet's **immediate** parent
3192
+ * Resolve a typed parent stub for this facet's **immediate** parent
3171
3193
  * agent.
3172
3194
  *
3173
3195
  * Symmetric with `subAgent(Cls, name)`: while `subAgent` opens a
3174
3196
  * stub from parent to child, `parentAgent` opens one from child
3175
3197
  * to parent. Pass the direct parent's class reference — the
3176
3198
  * framework verifies it matches the last entry of
3177
- * `this.parentPath` at runtime, then looks up `env[Cls.name]` to
3178
- * find the namespace binding.
3199
+ * `this.parentPath` at runtime. If the parent is a top-level
3200
+ * Durable Object, the framework returns the normal namespace stub.
3201
+ * If the parent is itself a facet, the framework returns a bridge
3202
+ * proxy that routes method calls through the root/supervisor and
3203
+ * then down the recorded facet path.
3179
3204
  *
3180
3205
  * `this.parentPath` is root-first, so the direct parent is the
3181
3206
  * **last** entry: `this.parentPath.at(-1)`. For grandparents and
3182
3207
  * further ancestors, iterate `this.parentPath` and use
3183
3208
  * `getAgentByName(env.X, this.parentPath[i].name)` directly.
3184
3209
  *
3185
- * Assumes the standard "binding name matches class name" convention.
3186
- * If your `wrangler.jsonc` binds the parent under a different name
3187
- * (e.g. `{ class_name: "Inbox", name: "MY_INBOX" }`), call
3188
- * `getAgentByName(env.MY_INBOX, this.parentPath.at(-1)!.name)`
3189
- * directly instead.
3210
+ * For top-level parents, the framework first checks `env[Cls.name]`,
3211
+ * then falls back to the Worker `exports` object. This supports
3212
+ * custom binding names as long as the parent class is exported under
3213
+ * its class name.
3214
+ *
3215
+ * Facet-parent stubs route normal HTTP `.fetch()` calls through the
3216
+ * same root bridge as RPC methods. WebSocket upgrade requests are
3217
+ * not supported yet because WebSocket handles cannot be serialized
3218
+ * over RPC.
3190
3219
  *
3191
3220
  * @experimental The API surface may change before stabilizing.
3192
3221
  *
@@ -3194,7 +3223,8 @@ var Agent = class Agent extends Server {
3194
3223
  * @throws If `Cls.name` doesn't match the recorded direct-parent
3195
3224
  * class (guards against accidentally reaching the wrong
3196
3225
  * DO, especially in nested Root → Mid → Leaf chains).
3197
- * @throws If no env binding named `Cls.name` is found.
3226
+ * @throws If no namespace is found for a top-level parent, or no
3227
+ * root namespace is available for a facet parent bridge.
3198
3228
  *
3199
3229
  * @example
3200
3230
  * ```ts
@@ -3211,10 +3241,46 @@ var Agent = class Agent extends Server {
3211
3241
  const parent = this._parentPath[this._parentPath.length - 1];
3212
3242
  if (!parent) throw new Error(`parentAgent(): ${this.constructor.name} is not a facet — only sub-agents (spawned via \`subAgent()\`) have a parent.`);
3213
3243
  if (cls.name !== parent.className) throw new Error(`parentAgent(${cls.name}): this facet's recorded parent class is "${parent.className}", not "${cls.name}". Pass the class whose constructor actually spawned this facet.`);
3214
- const binding = this.env[cls.name];
3215
- if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level binding "${cls.name}" found in env. If the parent is bound under a different name (e.g. "MY_${cls.name.toUpperCase()}"), use \`getAgentByName(env.MY_${cls.name.toUpperCase()}, this.parentPath.at(-1)!.name)\` directly.`);
3244
+ if (this._parentPath.length > 1) return await this._cf_parentAgentFacetProxy(cls.name, this._parentPath);
3245
+ const binding = this._cf_getTopLevelNamespaceByClassName(cls.name);
3246
+ if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level namespace for "${cls.name}" was found in env or worker exports. Make sure the parent class is exported under that class name and registered as a Durable Object binding.`);
3216
3247
  return await getServerByName(binding, parent.name);
3217
3248
  }
3249
+ _cf_getTopLevelNamespaceByClassName(className) {
3250
+ return this._cf_asDurableObjectNamespace(this.env[className]) ?? this._cf_asDurableObjectNamespace(exports[className]);
3251
+ }
3252
+ _cf_asDurableObjectNamespace(candidate) {
3253
+ const binding = candidate;
3254
+ return binding?.idFromName ? binding : void 0;
3255
+ }
3256
+ async _cf_parentAgentFacetProxy(className, parentPath) {
3257
+ const [root] = parentPath;
3258
+ if (!root) throw new Error(`parentAgent(${className}): parent path is empty.`);
3259
+ const rootBinding = this._cf_getTopLevelNamespaceByClassName(root.className);
3260
+ if (!rootBinding) throw new Error(`parentAgent(${className}): direct parent is a facet, but no top-level root namespace "${root.className}" was found in env or worker exports to bridge the call.`);
3261
+ const rootStubPromise = getServerByName(rootBinding, root.name);
3262
+ const targetPath = parentPath.map((step) => ({ ...step }));
3263
+ const invokeBridge = async (method, args) => {
3264
+ return await (await rootStubPromise)._cf_invokeSubAgentPath(targetPath, method, args);
3265
+ };
3266
+ const owner = this;
3267
+ return new Proxy({}, { get(_target, prop) {
3268
+ if (isInternalJsStubProp(prop)) return void 0;
3269
+ if (typeof prop !== "string") return void 0;
3270
+ if (prop === "fetch") return async (input, init) => {
3271
+ if (owner._cf_isWebSocketUpgradeRequest(input, init)) throw new Error(`parentAgent(${className}).fetch() does not support WebSocket upgrade requests yet. Use externally routed sub-agent URLs for WebSocket connections.`);
3272
+ return await invokeBridge(prop, [input, init]);
3273
+ };
3274
+ return async (...args) => {
3275
+ return await invokeBridge(prop, args);
3276
+ };
3277
+ } });
3278
+ }
3279
+ _cf_isWebSocketUpgradeRequest(input, init) {
3280
+ const initHeaders = init?.headers ? new Headers(init.headers) : void 0;
3281
+ const requestHeaders = input instanceof Request ? new Headers(input.headers) : void 0;
3282
+ return initHeaders?.get("Upgrade")?.toLowerCase() === "websocket" || requestHeaders?.get("Upgrade")?.toLowerCase() === "websocket";
3283
+ }
3218
3284
  /**
3219
3285
  * Get or create a named sub-agent — a child Durable Object (facet)
3220
3286
  * with its own isolated SQLite storage running on the same machine.
@@ -3796,13 +3862,13 @@ var Agent = class Agent extends Server {
3796
3862
  if (!Cls) throw new Error(`Sub-agent class "${className}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
3797
3863
  if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
3798
3864
  const facetKey = `${className}\0${name}`;
3799
- const parentClassName = this.constructor.name;
3800
- const parentNs = ctx.exports[parentClassName];
3801
- if (!parentNs?.idFromName) {
3802
- const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(parentClassName) ? ` The class name "${parentClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
3803
- throw new Error(`Sub-agent bootstrap requires the parent class "${parentClassName}" to be bound as a Durable Object namespace, but ctx.exports["${parentClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the parent agent class is registered in your wrangler.jsonc durable_objects.bindings under its class name.`);
3865
+ const rootClassName = this._parentPath[0]?.className ?? this.constructor.name;
3866
+ const rootNs = ctx.exports[rootClassName];
3867
+ if (!rootNs?.idFromName) {
3868
+ const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(rootClassName) ? ` The class name "${rootClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
3869
+ throw new Error(`Sub-agent bootstrap requires the root agent class "${rootClassName}" to be available as a Durable Object namespace, but ctx.exports["${rootClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the root agent class is exported under that class name and registered in your wrangler.jsonc durable_objects.bindings.`);
3804
3870
  }
3805
- const facetId = parentNs.idFromName(name);
3871
+ const facetId = rootNs.idFromName(name);
3806
3872
  const stub = ctx.facets.get(facetKey, () => ({
3807
3873
  class: Cls,
3808
3874
  id: facetId