agents 0.20.1 → 0.22.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 (169) hide show
  1. package/README.md +51 -12
  2. package/dist/{agent-tool-types-BC-WFlsz.d.ts → agent-routing-CnkaHb-v.d.ts} +3305 -3138
  3. package/dist/agent-routing.d.ts +14 -0
  4. package/dist/agent-routing.js +187 -0
  5. package/dist/agent-routing.js.map +1 -0
  6. package/dist/agent-tool-types.d.ts +26 -26
  7. package/dist/{agent-tools-DeHe9Xov.d.ts → agent-tools-C0XJqWzB.d.ts} +7 -7
  8. package/dist/agent-tools.d.ts +44 -28
  9. package/dist/agent-tools.js +16 -7
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/browser/ai.d.ts +5 -3
  12. package/dist/browser/ai.js +88 -8
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.d.ts +1 -1
  15. package/dist/browser/index.js +1 -1
  16. package/dist/browser/tanstack-ai.js +13 -1
  17. package/dist/browser/tanstack-ai.js.map +1 -1
  18. package/dist/capability-BjSKYpzg.js +42 -0
  19. package/dist/capability-BjSKYpzg.js.map +1 -0
  20. package/dist/capability-runner-CvHGZqUu.d.ts +150 -0
  21. package/dist/chat/index.d.ts +51 -15
  22. package/dist/chat/index.js +59 -4
  23. package/dist/chat/index.js.map +1 -1
  24. package/dist/chat/react.d.ts +19 -180
  25. package/dist/chat/react.js +93 -600
  26. package/dist/chat/react.js.map +1 -1
  27. package/dist/chat/transport.d.ts +10 -0
  28. package/dist/chat/transport.js +2 -0
  29. package/dist/chat-sdk/index.d.ts +4 -4
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
  32. package/dist/client-jagG8a9_.js.map +1 -0
  33. package/dist/client.d.ts +1 -1
  34. package/dist/client.js +1 -1
  35. package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
  36. package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
  37. package/dist/{connector-v2M1zlZp.d.ts → connector-CkQD4MK3.d.ts} +20 -3
  38. package/dist/{connector-KEJnl6e5.js → connector-CptFKzRh.js} +158 -40
  39. package/dist/connector-CptFKzRh.js.map +1 -0
  40. package/dist/current-agent-CuMErtly.d.ts +283 -0
  41. package/dist/current-agent-DhoDkSnH.js +51 -0
  42. package/dist/current-agent-DhoDkSnH.js.map +1 -0
  43. package/dist/diagnostics-CaBjfz4J.js +41 -0
  44. package/dist/diagnostics-CaBjfz4J.js.map +1 -0
  45. package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
  46. package/dist/durable-object-lifecycle-D6nNQJJd.js +862 -0
  47. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +1 -0
  48. package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
  49. package/dist/email-send.d.ts +15 -0
  50. package/dist/email-send.js +32 -0
  51. package/dist/email-send.js.map +1 -0
  52. package/dist/email.d.ts +14 -10
  53. package/dist/email.js.map +1 -1
  54. package/dist/experimental/memory/session/index.d.ts +3 -2
  55. package/dist/experimental/memory/session/index.js +15 -10
  56. package/dist/experimental/memory/session/index.js.map +1 -1
  57. package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
  58. package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
  59. package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
  60. package/dist/{index-BRnybD6X.d.ts → index-DcSAZKsB.d.ts} +19 -31
  61. package/dist/index.d.ts +102 -91
  62. package/dist/index.js +12 -7235
  63. package/dist/internal_context-BlxFEWfn.d.ts +19 -0
  64. package/dist/internal_context.d.ts +10 -4
  65. package/dist/internal_context.js +1 -10
  66. package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
  67. package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
  68. package/dist/invoker-CHMnoxIA.js.map +1 -0
  69. package/dist/lifecycle/index.d.ts +48 -0
  70. package/dist/lifecycle/index.js +4 -0
  71. package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
  72. package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
  73. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
  74. package/dist/mcp/client/index.d.ts +42 -0
  75. package/dist/mcp/{client.js → client/index.js} +1 -1
  76. package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
  77. package/dist/mcp/{x402.js → client/x402.js} +2 -2
  78. package/dist/mcp/client/x402.js.map +1 -0
  79. package/dist/mcp/index.d.ts +36 -36
  80. package/dist/mcp/index.js +14 -16
  81. package/dist/mcp/index.js.map +1 -1
  82. package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
  83. package/dist/mcp/{server.js → server/index.js} +1 -1
  84. package/dist/observability/ai/index.d.ts +1 -130
  85. package/dist/observability/ai/index.js +88 -433
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/observability/index.d.ts +4 -4
  88. package/dist/observability/index.js +3 -50
  89. package/dist/observability/index.js.map +1 -1
  90. package/dist/{wire-types-CU9rLoeS.js → protocol-B0nh6KNf.js} +20 -66
  91. package/dist/protocol-B0nh6KNf.js.map +1 -0
  92. package/dist/react.d.ts +4 -4
  93. package/dist/react.js +6 -9
  94. package/dist/react.js.map +1 -1
  95. package/dist/schedule.d.ts +25 -94
  96. package/dist/schedule.js +1 -98
  97. package/dist/schedule.js.map +1 -1
  98. package/dist/scheduler-CR9RHGos.js +857 -0
  99. package/dist/scheduler-CR9RHGos.js.map +1 -0
  100. package/dist/scheduler-DQoTGoAW.d.ts +224 -0
  101. package/dist/schedules/index.d.ts +22 -0
  102. package/dist/schedules/index.js +2 -0
  103. package/dist/schedules/parser.d.ts +79 -0
  104. package/dist/schedules/parser.js +103 -0
  105. package/dist/schedules/parser.js.map +1 -0
  106. package/dist/serializable.d.ts +1 -1
  107. package/dist/sql-error-CPY-GXyI.d.ts +12 -0
  108. package/dist/sql-error.d.ts +2 -0
  109. package/dist/sql-error.js +16 -0
  110. package/dist/sql-error.js.map +1 -0
  111. package/dist/src-5W6JNKVb.js +6613 -0
  112. package/dist/src-5W6JNKVb.js.map +1 -0
  113. package/dist/sub-routing.d.ts +18 -6
  114. package/dist/sub-routing.js +92 -2
  115. package/dist/sub-routing.js.map +1 -1
  116. package/dist/wire-types-CnMt6_HR.js +47 -0
  117. package/dist/wire-types-CnMt6_HR.js.map +1 -0
  118. package/dist/workflow-types.d.ts +25 -25
  119. package/dist/workflow-types.js.map +1 -1
  120. package/dist/workflows.d.ts +22 -22
  121. package/dist/workflows.js +2 -1
  122. package/dist/workflows.js.map +1 -1
  123. package/dist/ws-chat-transport-UNRIS2xl.d.ts +184 -0
  124. package/dist/ws-chat-transport-rWwta645.js +698 -0
  125. package/dist/ws-chat-transport-rWwta645.js.map +1 -0
  126. package/docs/adding-to-existing-project.md +4 -2
  127. package/docs/agent-class.md +29 -87
  128. package/docs/agent-tools.md +29 -0
  129. package/docs/browse-the-web.md +16 -1
  130. package/docs/chat-agents.md +16 -13
  131. package/docs/client-sdk.md +12 -8
  132. package/docs/configuration.md +7 -1
  133. package/docs/cross-domain-authentication.md +7 -35
  134. package/docs/durable-execution.md +1 -1
  135. package/docs/email.md +2 -13
  136. package/docs/http-websockets.md +1 -11
  137. package/docs/human-in-the-loop.md +16 -13
  138. package/docs/index.md +1 -0
  139. package/docs/lifecycle.md +323 -0
  140. package/docs/long-running-agents.md +13 -15
  141. package/docs/mcp-client.md +55 -0
  142. package/docs/mcp-servers.md +6 -0
  143. package/docs/mcp-transports.md +18 -12
  144. package/docs/migration-to-ai-sdk-v5.md +2 -2
  145. package/docs/migration-to-ai-sdk-v6.md +5 -1
  146. package/docs/observability.md +33 -77
  147. package/docs/resumable-streaming.md +1 -1
  148. package/docs/routing.md +27 -0
  149. package/docs/scheduling.md +175 -15
  150. package/docs/server-driven-messages.md +1 -1
  151. package/docs/sub-agents.md +47 -2
  152. package/docs/webhooks.md +109 -136
  153. package/docs/workflows.md +9 -1
  154. package/package.json +41 -23
  155. package/dist/cli/index.d.ts +0 -1
  156. package/dist/cli/index.js +0 -26
  157. package/dist/cli/index.js.map +0 -1
  158. package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
  159. package/dist/client-zqKcsyFa.js.map +0 -1
  160. package/dist/connector-KEJnl6e5.js.map +0 -1
  161. package/dist/handler-stateless-CIkKPETH.js.map +0 -1
  162. package/dist/index.js.map +0 -1
  163. package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
  164. package/dist/internal_context.js.map +0 -1
  165. package/dist/mcp/client.d.ts +0 -42
  166. package/dist/mcp/do-oauth-client-provider.js.map +0 -1
  167. package/dist/mcp/x402.js.map +0 -1
  168. package/dist/wire-types-CU9rLoeS.js.map +0 -1
  169. package/dist/workflow-types-Baz_PO5v.d.ts +0 -280
@@ -1,11 +1,11 @@
1
1
  import {
2
- a as genericObservability,
3
- i as channels,
2
+ a as subscribe,
3
+ i as genericObservability,
4
4
  n as Observability,
5
- o as subscribe,
5
+ o as channels,
6
6
  r as ObservabilityEvent,
7
7
  t as ChannelEventMap
8
- } from "../index-BRnybD6X.js";
8
+ } from "../index-DcSAZKsB.js";
9
9
  export {
10
10
  ChannelEventMap,
11
11
  Observability,
@@ -1,67 +1,20 @@
1
- import { channel, subscribe as subscribe$1, unsubscribe } from "node:diagnostics_channel";
1
+ import { n as publishDiagnosticsEvent, t as channels } from "../diagnostics-CaBjfz4J.js";
2
+ import { subscribe as subscribe$1, unsubscribe } from "node:diagnostics_channel";
2
3
  //#region src/observability/index.ts
3
4
  /**
4
- * Diagnostics channels for agent observability.
5
- *
6
- * Events are published to named channels using the Node.js diagnostics_channel API.
7
- * By default, publishing to a channel with no subscribers is a no-op (zero overhead).
8
- *
9
- * To observe events, subscribe to the channels you care about:
10
- * ```ts
11
- * import { subscribe } from "node:diagnostics_channel";
12
- * subscribe("agents:rpc", (event) => console.log(event));
13
- * ```
14
- *
15
- * In production, all published messages are automatically forwarded to
16
- * Tail Workers via `event.diagnosticsChannelEvents` — no subscription needed.
17
- */
18
- const channels = {
19
- state: channel("agents:state"),
20
- rpc: channel("agents:rpc"),
21
- message: channel("agents:message"),
22
- chat: channel("agents:chat"),
23
- transcript: channel("agents:transcript"),
24
- fiber: channel("agents:fiber"),
25
- agentTool: channel("agents:agent_tool"),
26
- schedule: channel("agents:schedule"),
27
- lifecycle: channel("agents:lifecycle"),
28
- workflow: channel("agents:workflow"),
29
- mcp: channel("agents:mcp"),
30
- email: channel("agents:email"),
31
- channel: channel("agents:channel")
32
- };
33
- /**
34
5
  * Channel keys whose diagnostics channel name differs from `agents:${key}`.
35
6
  * Keep this in sync with {@link channels} for any camelCase key that maps to a
36
7
  * snake_case diagnostics channel.
37
8
  */
38
9
  const CHANNEL_DIAGNOSTIC_NAME_OVERRIDES = { agentTool: "agents:agent_tool" };
39
10
  /**
40
- * Map event type prefixes to their diagnostics channel.
41
- */
42
- function getChannel(type) {
43
- if (type.startsWith("mcp:")) return channels.mcp;
44
- if (type.startsWith("workflow:")) return channels.workflow;
45
- if (type.startsWith("fiber:")) return channels.fiber;
46
- if (type.startsWith("transcript:") || type.startsWith("chat:transcript:")) return channels.transcript;
47
- if (type.startsWith("chat:")) return channels.chat;
48
- if (type.startsWith("agent_tool:")) return channels.agentTool;
49
- if (type.startsWith("schedule:") || type.startsWith("queue:")) return channels.schedule;
50
- if (type.startsWith("message:") || type.startsWith("tool:") || type.startsWith("submission:") || type.startsWith("action:")) return channels.message;
51
- if (type === "rpc" || type.startsWith("rpc:")) return channels.rpc;
52
- if (type.startsWith("state:")) return channels.state;
53
- if (type.startsWith("email:")) return channels.email;
54
- if (type.startsWith("channel:") || type.startsWith("notice:")) return channels.channel;
55
- return channels.lifecycle;
56
- }
57
- /**
58
11
  * The default observability implementation.
59
12
  *
60
13
  * Publishes events to diagnostics_channel. Events are silent unless
61
14
  * a subscriber is registered or a Tail Worker is attached.
62
15
  */
63
16
  const genericObservability = { emit(event) {
64
- getChannel(event.type).publish(event);
17
+ publishDiagnosticsEvent(event);
65
18
  } };
66
19
  /**
67
20
  * Subscribe to a typed observability channel.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["dcUnsubscribe"],"sources":["../../src/observability/index.ts"],"sourcesContent":["import {\n channel,\n subscribe as dcSubscribe,\n unsubscribe as dcUnsubscribe,\n type Channel\n} from \"node:diagnostics_channel\";\nimport type { AgentObservabilityEvent } from \"./agent\";\nimport type { MCPObservabilityEvent } from \"./mcp\";\n\n/**\n * Union of all observability event types from different domains\n */\nexport type ObservabilityEvent =\n | AgentObservabilityEvent\n | MCPObservabilityEvent;\n\nexport interface Observability {\n /**\n * Emit an event for the Agent's observability implementation to handle.\n * @param event - The event to emit\n */\n emit(event: ObservabilityEvent): void;\n}\n\n/**\n * Diagnostics channels for agent observability.\n *\n * Events are published to named channels using the Node.js diagnostics_channel API.\n * By default, publishing to a channel with no subscribers is a no-op (zero overhead).\n *\n * To observe events, subscribe to the channels you care about:\n * ```ts\n * import { subscribe } from \"node:diagnostics_channel\";\n * subscribe(\"agents:rpc\", (event) => console.log(event));\n * ```\n *\n * In production, all published messages are automatically forwarded to\n * Tail Workers via `event.diagnosticsChannelEvents` — no subscription needed.\n */\nexport const channels = {\n state: channel(\"agents:state\"),\n rpc: channel(\"agents:rpc\"),\n message: channel(\"agents:message\"),\n chat: channel(\"agents:chat\"),\n transcript: channel(\"agents:transcript\"),\n fiber: channel(\"agents:fiber\"),\n agentTool: channel(\"agents:agent_tool\"),\n schedule: channel(\"agents:schedule\"),\n lifecycle: channel(\"agents:lifecycle\"),\n workflow: channel(\"agents:workflow\"),\n mcp: channel(\"agents:mcp\"),\n email: channel(\"agents:email\"),\n channel: channel(\"agents:channel\")\n} as const;\n\n/**\n * Channel keys whose diagnostics channel name differs from `agents:${key}`.\n * Keep this in sync with {@link channels} for any camelCase key that maps to a\n * snake_case diagnostics channel.\n */\nconst CHANNEL_DIAGNOSTIC_NAME_OVERRIDES: Partial<Record<string, string>> = {\n agentTool: \"agents:agent_tool\"\n};\n\n/**\n * Map event type prefixes to their diagnostics channel.\n */\nfunction getChannel(type: string): Channel {\n if (type.startsWith(\"mcp:\")) return channels.mcp;\n if (type.startsWith(\"workflow:\")) return channels.workflow;\n if (type.startsWith(\"fiber:\")) return channels.fiber;\n if (type.startsWith(\"transcript:\") || type.startsWith(\"chat:transcript:\"))\n return channels.transcript;\n if (type.startsWith(\"chat:\")) return channels.chat;\n if (type.startsWith(\"agent_tool:\")) return channels.agentTool;\n if (type.startsWith(\"schedule:\") || type.startsWith(\"queue:\"))\n return channels.schedule;\n if (\n type.startsWith(\"message:\") ||\n type.startsWith(\"tool:\") ||\n type.startsWith(\"submission:\") ||\n type.startsWith(\"action:\")\n )\n return channels.message;\n if (type === \"rpc\" || type.startsWith(\"rpc:\")) return channels.rpc;\n if (type.startsWith(\"state:\")) return channels.state;\n if (type.startsWith(\"email:\")) return channels.email;\n if (type.startsWith(\"channel:\") || type.startsWith(\"notice:\"))\n return channels.channel;\n // connect, disconnect, destroy\n return channels.lifecycle;\n}\n\n/**\n * The default observability implementation.\n *\n * Publishes events to diagnostics_channel. Events are silent unless\n * a subscriber is registered or a Tail Worker is attached.\n */\nexport const genericObservability: Observability = {\n emit(event) {\n getChannel(event.type).publish(event);\n }\n};\n\n/**\n * Maps each channel key to the observability events it carries.\n */\nexport type ChannelEventMap = {\n state: Extract<ObservabilityEvent, { type: `state:${string}` }>;\n rpc: Extract<ObservabilityEvent, { type: \"rpc\" | `rpc:${string}` }>;\n message: Extract<\n ObservabilityEvent,\n {\n type:\n | `message:${string}`\n | `tool:${string}`\n | `submission:${string}`\n | `action:${string}`;\n }\n >;\n chat: Exclude<\n Extract<ObservabilityEvent, { type: `chat:${string}` }>,\n { type: `chat:transcript:${string}` }\n >;\n transcript: Extract<\n ObservabilityEvent,\n { type: `transcript:${string}` | `chat:transcript:${string}` }\n >;\n fiber: Extract<ObservabilityEvent, { type: `fiber:${string}` }>;\n agentTool: Extract<ObservabilityEvent, { type: `agent_tool:${string}` }>;\n schedule: Extract<\n ObservabilityEvent,\n { type: `schedule:${string}` | `queue:${string}` }\n >;\n lifecycle: Extract<\n ObservabilityEvent,\n { type: \"connect\" | \"disconnect\" | \"destroy\" }\n >;\n workflow: Extract<ObservabilityEvent, { type: `workflow:${string}` }>;\n mcp: Extract<ObservabilityEvent, { type: `mcp:${string}` }>;\n email: Extract<ObservabilityEvent, { type: `email:${string}` }>;\n channel: Extract<\n ObservabilityEvent,\n { type: `channel:${string}` | `notice:${string}` }\n >;\n};\n\n/**\n * Subscribe to a typed observability channel.\n *\n * ```ts\n * import { subscribe } from \"agents/observability\";\n *\n * const unsub = subscribe(\"rpc\", (event) => {\n * console.log(event.payload.method); // fully typed\n * });\n * ```\n *\n * @returns A function that unsubscribes the callback.\n */\nexport function subscribe<K extends keyof ChannelEventMap>(\n channelKey: K,\n callback: (event: ChannelEventMap[K]) => void\n): () => void {\n const name =\n CHANNEL_DIAGNOSTIC_NAME_OVERRIDES[channelKey] ?? `agents:${channelKey}`;\n const handler = (message: unknown, _name: string | symbol) =>\n callback(message as ChannelEventMap[K]);\n dcSubscribe(name, handler);\n return () => dcUnsubscribe(name, handler);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,MAAa,WAAW;CACtB,OAAO,QAAQ,cAAc;CAC7B,KAAK,QAAQ,YAAY;CACzB,SAAS,QAAQ,gBAAgB;CACjC,MAAM,QAAQ,aAAa;CAC3B,YAAY,QAAQ,mBAAmB;CACvC,OAAO,QAAQ,cAAc;CAC7B,WAAW,QAAQ,mBAAmB;CACtC,UAAU,QAAQ,iBAAiB;CACnC,WAAW,QAAQ,kBAAkB;CACrC,UAAU,QAAQ,iBAAiB;CACnC,KAAK,QAAQ,YAAY;CACzB,OAAO,QAAQ,cAAc;CAC7B,SAAS,QAAQ,gBAAgB;AACnC;;;;;;AAOA,MAAM,oCAAqE,EACzE,WAAW,oBACb;;;;AAKA,SAAS,WAAW,MAAuB;CACzC,IAAI,KAAK,WAAW,MAAM,GAAG,OAAO,SAAS;CAC7C,IAAI,KAAK,WAAW,WAAW,GAAG,OAAO,SAAS;CAClD,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO,SAAS;CAC/C,IAAI,KAAK,WAAW,aAAa,KAAK,KAAK,WAAW,kBAAkB,GACtE,OAAO,SAAS;CAClB,IAAI,KAAK,WAAW,OAAO,GAAG,OAAO,SAAS;CAC9C,IAAI,KAAK,WAAW,aAAa,GAAG,OAAO,SAAS;CACpD,IAAI,KAAK,WAAW,WAAW,KAAK,KAAK,WAAW,QAAQ,GAC1D,OAAO,SAAS;CAClB,IACE,KAAK,WAAW,UAAU,KAC1B,KAAK,WAAW,OAAO,KACvB,KAAK,WAAW,aAAa,KAC7B,KAAK,WAAW,SAAS,GAEzB,OAAO,SAAS;CAClB,IAAI,SAAS,SAAS,KAAK,WAAW,MAAM,GAAG,OAAO,SAAS;CAC/D,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO,SAAS;CAC/C,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO,SAAS;CAC/C,IAAI,KAAK,WAAW,UAAU,KAAK,KAAK,WAAW,SAAS,GAC1D,OAAO,SAAS;CAElB,OAAO,SAAS;AAClB;;;;;;;AAQA,MAAa,uBAAsC,EACjD,KAAK,OAAO;CACV,WAAW,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK;AACtC,EACF;;;;;;;;;;;;;;AA0DA,SAAgB,UACd,YACA,UACY;CACZ,MAAM,OACJ,kCAAkC,eAAe,UAAU;CAC7D,MAAM,WAAW,SAAkB,UACjC,SAAS,OAA6B;CACxC,YAAY,MAAM,OAAO;CACzB,aAAaA,YAAc,MAAM,OAAO;AAC1C"}
1
+ {"version":3,"file":"index.js","names":["dcUnsubscribe"],"sources":["../../src/observability/index.ts"],"sourcesContent":["import {\n subscribe as dcSubscribe,\n unsubscribe as dcUnsubscribe\n} from \"node:diagnostics_channel\";\nimport type { AgentObservabilityEvent } from \"./agent\";\nimport type { MCPObservabilityEvent } from \"./mcp\";\nimport { publishDiagnosticsEvent } from \"./diagnostics\";\nexport { channels } from \"./diagnostics\";\n\n/**\n * Union of all observability event types from different domains\n */\nexport type ObservabilityEvent =\n | AgentObservabilityEvent\n | MCPObservabilityEvent;\n\nexport interface Observability {\n /**\n * Emit an event for the Agent's observability implementation to handle.\n * @param event - The event to emit\n */\n emit(event: ObservabilityEvent): void;\n}\n\n/**\n * Channel keys whose diagnostics channel name differs from `agents:${key}`.\n * Keep this in sync with {@link channels} for any camelCase key that maps to a\n * snake_case diagnostics channel.\n */\nconst CHANNEL_DIAGNOSTIC_NAME_OVERRIDES: Partial<Record<string, string>> = {\n agentTool: \"agents:agent_tool\"\n};\n\n/**\n * The default observability implementation.\n *\n * Publishes events to diagnostics_channel. Events are silent unless\n * a subscriber is registered or a Tail Worker is attached.\n */\nexport const genericObservability: Observability = {\n emit(event) {\n publishDiagnosticsEvent(event);\n }\n};\n\n/**\n * Maps each channel key to the observability events it carries.\n */\nexport type ChannelEventMap = {\n state: Extract<ObservabilityEvent, { type: `state:${string}` }>;\n rpc: Extract<ObservabilityEvent, { type: \"rpc\" | `rpc:${string}` }>;\n message: Extract<\n ObservabilityEvent,\n {\n type:\n | `message:${string}`\n | `tool:${string}`\n | `submission:${string}`\n | `action:${string}`;\n }\n >;\n chat: Exclude<\n Extract<ObservabilityEvent, { type: `chat:${string}` }>,\n { type: `chat:transcript:${string}` }\n >;\n transcript: Extract<\n ObservabilityEvent,\n { type: `transcript:${string}` | `chat:transcript:${string}` }\n >;\n fiber: Extract<ObservabilityEvent, { type: `fiber:${string}` }>;\n agentTool: Extract<ObservabilityEvent, { type: `agent_tool:${string}` }>;\n schedule: Extract<\n ObservabilityEvent,\n { type: `schedule:${string}` | `queue:${string}` }\n >;\n lifecycle: Extract<\n ObservabilityEvent,\n { type: \"connect\" | \"disconnect\" | \"destroy\" }\n >;\n workflow: Extract<ObservabilityEvent, { type: `workflow:${string}` }>;\n mcp: Extract<ObservabilityEvent, { type: `mcp:${string}` }>;\n email: Extract<ObservabilityEvent, { type: `email:${string}` }>;\n channel: Extract<\n ObservabilityEvent,\n { type: `channel:${string}` | `notice:${string}` }\n >;\n};\n\n/**\n * Subscribe to a typed observability channel.\n *\n * ```ts\n * import { subscribe } from \"agents/observability\";\n *\n * const unsub = subscribe(\"rpc\", (event) => {\n * console.log(event.payload.method); // fully typed\n * });\n * ```\n *\n * @returns A function that unsubscribes the callback.\n */\nexport function subscribe<K extends keyof ChannelEventMap>(\n channelKey: K,\n callback: (event: ChannelEventMap[K]) => void\n): () => void {\n const name =\n CHANNEL_DIAGNOSTIC_NAME_OVERRIDES[channelKey] ?? `agents:${channelKey}`;\n const handler = (message: unknown, _name: string | symbol) =>\n callback(message as ChannelEventMap[K]);\n dcSubscribe(name, handler);\n return () => dcUnsubscribe(name, handler);\n}\n"],"mappings":";;;;;;;;AA6BA,MAAM,oCAAqE,EACzE,WAAW,oBACb;;;;;;;AAQA,MAAa,uBAAsC,EACjD,KAAK,OAAO;CACV,wBAAwB,KAAK;AAC/B,EACF;;;;;;;;;;;;;;AA0DA,SAAgB,UACd,YACA,UACY;CACZ,MAAM,OACJ,kCAAkC,eAAe,UAAU;CAC7D,MAAM,WAAW,SAAkB,UACjC,SAAS,OAA6B;CACxC,YAAY,MAAM,OAAO;CACzB,aAAaA,YAAc,MAAM,OAAO;AAC1C"}
@@ -9,8 +9,10 @@ var StreamAccumulator = class {
9
9
  this._isContinuation = options.continuation ?? false;
10
10
  this.parts = options.existingParts ? [...options.existingParts] : [];
11
11
  this.metadata = options.existingMetadata ? { ...options.existingMetadata } : void 0;
12
+ this._pendingContinuationChunks = this._isContinuation && options.existingParts === void 0 && options.existingMetadata === void 0 ? [] : null;
12
13
  }
13
14
  applyChunk(chunk) {
15
+ this._pendingContinuationChunks?.push(chunk);
14
16
  const handled = applyChunkToParts(this.parts, chunk);
15
17
  if (chunk.type === "tool-approval-request" && chunk.toolCallId) return {
16
18
  handled,
@@ -101,7 +103,8 @@ var StreamAccumulator = class {
101
103
  /**
102
104
  * Merge this accumulator's message into an existing message array.
103
105
  * Handles continuation (walk backward for last assistant), replacement
104
- * (update existing by messageId), or append (new message).
106
+ * (update existing by messageId), or append (new message). An unseeded
107
+ * continuation adopts current parts here before applying its queued chunks.
105
108
  */
106
109
  mergeInto(messages) {
107
110
  let existingIdx = messages.findIndex((m) => m.id === this.messageId);
@@ -111,6 +114,15 @@ var StreamAccumulator = class {
111
114
  break;
112
115
  }
113
116
  }
117
+ if (this._pendingContinuationChunks !== null) {
118
+ const pendingChunks = this._pendingContinuationChunks;
119
+ this._pendingContinuationChunks = null;
120
+ this.parts.splice(0, this.parts.length, ...existingIdx >= 0 ? messages[existingIdx].parts : []);
121
+ const currentMetadata = existingIdx >= 0 ? asMetadata(messages[existingIdx].metadata) : void 0;
122
+ this.metadata = currentMetadata ? { ...currentMetadata } : void 0;
123
+ if (existingIdx >= 0) this.messageId = messages[existingIdx].id;
124
+ for (const chunk of pendingChunks) this.applyChunk(chunk);
125
+ }
114
126
  const partialMessage = {
115
127
  id: existingIdx >= 0 ? messages[existingIdx].id : this.messageId,
116
128
  role: "assistant",
@@ -147,25 +159,11 @@ function transition(state, event) {
147
159
  case "response": {
148
160
  let accumulator;
149
161
  const isReplayedStart = event.replay === true && event.chunkData?.type === "start";
150
- if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) {
151
- let messageId = event.messageId;
152
- let existingParts;
153
- let existingMetadata;
154
- if (event.continuation && event.currentMessages) {
155
- for (let i = event.currentMessages.length - 1; i >= 0; i--) if (event.currentMessages[i].role === "assistant") {
156
- messageId = event.currentMessages[i].id;
157
- existingParts = [...event.currentMessages[i].parts];
158
- if (event.currentMessages[i].metadata != null) existingMetadata = { ...event.currentMessages[i].metadata };
159
- break;
160
- }
161
- }
162
- accumulator = new StreamAccumulator({
163
- messageId,
164
- continuation: event.continuation,
165
- existingParts,
166
- existingMetadata
167
- });
168
- } else accumulator = state.accumulator;
162
+ if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) accumulator = new StreamAccumulator({
163
+ messageId: event.messageId,
164
+ continuation: event.continuation
165
+ });
166
+ else accumulator = state.accumulator;
169
167
  if (event.chunkData) accumulator.applyChunk(event.chunkData);
170
168
  let messagesUpdate;
171
169
  if (event.done) {
@@ -222,50 +220,6 @@ const CHAT_MESSAGE_TYPES = {
222
220
  CHAT_RECOVERING: "cf_agent_chat_recovering"
223
221
  };
224
222
  //#endregion
225
- //#region src/chat/wire-types.ts
226
- /**
227
- * Enum for message types to improve type safety and maintainability
228
- */
229
- let MessageType = /* @__PURE__ */ function(MessageType) {
230
- MessageType["CF_AGENT_CHAT_MESSAGES"] = "cf_agent_chat_messages";
231
- MessageType["CF_AGENT_USE_CHAT_REQUEST"] = "cf_agent_use_chat_request";
232
- MessageType["CF_AGENT_USE_CHAT_RESPONSE"] = "cf_agent_use_chat_response";
233
- MessageType["CF_AGENT_CHAT_CLEAR"] = "cf_agent_chat_clear";
234
- MessageType["CF_AGENT_CHAT_REQUEST_CANCEL"] = "cf_agent_chat_request_cancel";
235
- /** Sent by server when client connects and there's an active stream to resume */
236
- MessageType["CF_AGENT_STREAM_RESUMING"] = "cf_agent_stream_resuming";
237
- /** Sent by client to acknowledge stream resuming notification and request chunks */
238
- MessageType["CF_AGENT_STREAM_RESUME_ACK"] = "cf_agent_stream_resume_ack";
239
- /** Sent by client after message handler is ready, requesting stream resume check */
240
- MessageType["CF_AGENT_STREAM_RESUME_REQUEST"] = "cf_agent_stream_resume_request";
241
- /** Sent by server when client requests resume but no active stream exists */
242
- MessageType["CF_AGENT_STREAM_RESUME_NONE"] = "cf_agent_stream_resume_none";
243
- /**
244
- * Sent by server when a turn is accepted but its resumable stream has not
245
- * started yet (queued / debouncing / waiting on MCP / async setup). Tells a
246
- * reconnecting client to keep waiting rather than resolve its resume probe to
247
- * "no stream". Resolved by a later `CF_AGENT_STREAM_RESUMING` (stream started)
248
- * or `CF_AGENT_STREAM_RESUME_NONE` (settled without streaming). See #1784.
249
- */
250
- MessageType["CF_AGENT_STREAM_PENDING"] = "cf_agent_stream_pending";
251
- /** Client sends tool result to server (for client-side tools) */
252
- MessageType["CF_AGENT_TOOL_RESULT"] = "cf_agent_tool_result";
253
- /** Server notifies client that a message was updated (e.g., tool result applied) */
254
- MessageType["CF_AGENT_MESSAGE_UPDATED"] = "cf_agent_message_updated";
255
- /** Client sends tool approval response to server (for tools with needsApproval) */
256
- MessageType["CF_AGENT_TOOL_APPROVAL"] = "cf_agent_tool_approval";
257
- /**
258
- * Server→client progress hint: a durable chat turn is being recovered
259
- * (interrupted by a deploy/eviction or a stream-stall watchdog abort and now
260
- * resuming). Sent when a recovery continuation is scheduled and cleared on
261
- * every terminal outcome. (`@cloudflare/think` also replays it on connect;
262
- * `@cloudflare/ai-chat` broadcasts the live signal only — see #1645.)
263
- * Backward-compatible — clients that don't understand it ignore it. See #1620.
264
- */
265
- MessageType["CF_AGENT_CHAT_RECOVERING"] = "cf_agent_chat_recovering";
266
- return MessageType;
267
- }({});
268
- //#endregion
269
- export { StreamAccumulator as a, transition as i, CHAT_MESSAGE_TYPES as n, STREAM_RESUME_NONE_REASONS as r, MessageType as t };
223
+ export { StreamAccumulator as i, STREAM_RESUME_NONE_REASONS as n, transition as r, CHAT_MESSAGE_TYPES as t };
270
224
 
271
- //# sourceMappingURL=wire-types-CU9rLoeS.js.map
225
+ //# sourceMappingURL=protocol-B0nh6KNf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-B0nh6KNf.js","names":[],"sources":["../src/chat/stream-accumulator.ts","../src/chat/broadcast-state.ts","../src/chat/protocol.ts"],"sourcesContent":["/**\n * StreamAccumulator — unified chunk-to-message builder.\n *\n * Used by @cloudflare/ai-chat (server + client) and @cloudflare/think\n * to incrementally build a UIMessage from stream chunks. Wraps\n * applyChunkToParts and handles the metadata chunk types (start, finish,\n * message-metadata, error) that applyChunkToParts does not cover.\n *\n * The accumulator signals domain-specific concerns (early persistence,\n * cross-message tool updates) via ChunkAction returns — callers handle\n * these according to their context.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { applyChunkToParts, type StreamChunkData } from \"./message-builder\";\n\nfunction asMetadata(value: unknown): Record<string, unknown> | undefined {\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nexport interface StreamAccumulatorOptions {\n messageId: string;\n /** Whether chunks continue the current assistant message when merged. */\n continuation?: boolean;\n /** Eager seed; omitted continuations seed from the messages passed to `mergeInto`. */\n existingParts?: UIMessage[\"parts\"];\n existingMetadata?: Record<string, unknown>;\n}\n\nexport type ChunkAction =\n | {\n type: \"start\";\n messageId?: string;\n metadata?: Record<string, unknown>;\n }\n | {\n type: \"finish\";\n finishReason?: string;\n metadata?: Record<string, unknown>;\n }\n | { type: \"message-metadata\"; metadata: Record<string, unknown> }\n | { type: \"tool-approval-request\"; toolCallId: string }\n | {\n type: \"cross-message-tool-update\";\n updateType: \"output-available\" | \"output-error\";\n toolCallId: string;\n output?: unknown;\n errorText?: string;\n preliminary?: boolean;\n }\n | { type: \"error\"; error: string };\n\nexport interface ChunkResult {\n handled: boolean;\n action?: ChunkAction;\n}\n\nexport class StreamAccumulator {\n messageId: string;\n readonly parts: UIMessage[\"parts\"];\n metadata?: Record<string, unknown>;\n private _isContinuation: boolean;\n // Continuation chunks wait here until mergeInto can seed from current state.\n private _pendingContinuationChunks: StreamChunkData[] | null;\n\n constructor(options: StreamAccumulatorOptions) {\n this.messageId = options.messageId;\n this._isContinuation = options.continuation ?? false;\n this.parts = options.existingParts ? [...options.existingParts] : [];\n this.metadata = options.existingMetadata\n ? { ...options.existingMetadata }\n : undefined;\n this._pendingContinuationChunks =\n this._isContinuation &&\n options.existingParts === undefined &&\n options.existingMetadata === undefined\n ? []\n : null;\n }\n\n applyChunk(chunk: StreamChunkData): ChunkResult {\n this._pendingContinuationChunks?.push(chunk);\n const handled = applyChunkToParts(this.parts, chunk);\n\n // Detect tool-approval-request for early persistence signaling\n if (chunk.type === \"tool-approval-request\" && chunk.toolCallId) {\n return {\n handled,\n action: { type: \"tool-approval-request\", toolCallId: chunk.toolCallId }\n };\n }\n\n // Detect cross-message tool output/error: applyChunkToParts returns true\n // for recognized types but silently does nothing when the toolCallId\n // doesn't exist in the current parts array.\n if (\n (chunk.type === \"tool-output-available\" ||\n chunk.type === \"tool-output-error\") &&\n chunk.toolCallId\n ) {\n const foundInParts = this.parts.some(\n (p) => \"toolCallId\" in p && p.toolCallId === chunk.toolCallId\n );\n if (!foundInParts) {\n return {\n handled,\n action: {\n type: \"cross-message-tool-update\",\n updateType:\n chunk.type === \"tool-output-available\"\n ? \"output-available\"\n : \"output-error\",\n toolCallId: chunk.toolCallId,\n output: chunk.output,\n errorText: chunk.errorText,\n preliminary: chunk.preliminary\n }\n };\n }\n }\n\n if (!handled) {\n switch (chunk.type) {\n case \"start\": {\n if (chunk.messageId != null && !this._isContinuation) {\n this.messageId = chunk.messageId;\n }\n const startMeta = asMetadata(chunk.messageMetadata);\n if (startMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...startMeta }\n : { ...startMeta };\n }\n return {\n handled: true,\n action: {\n type: \"start\",\n messageId: chunk.messageId,\n metadata: startMeta\n }\n };\n }\n case \"finish\": {\n const finishMeta = asMetadata(chunk.messageMetadata);\n if (finishMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...finishMeta }\n : { ...finishMeta };\n }\n const finishReason =\n \"finishReason\" in chunk\n ? (chunk.finishReason as string)\n : undefined;\n return {\n handled: true,\n action: {\n type: \"finish\",\n finishReason,\n metadata: finishMeta\n }\n };\n }\n case \"message-metadata\": {\n const msgMeta = asMetadata(chunk.messageMetadata);\n if (msgMeta) {\n this.metadata = this.metadata\n ? { ...this.metadata, ...msgMeta }\n : { ...msgMeta };\n }\n return {\n handled: true,\n action: {\n type: \"message-metadata\",\n metadata: msgMeta ?? {}\n }\n };\n }\n case \"finish-step\": {\n return { handled: true };\n }\n case \"error\": {\n return {\n handled: true,\n action: {\n type: \"error\",\n error: chunk.errorText ?? JSON.stringify(chunk)\n }\n };\n }\n }\n }\n\n return { handled };\n }\n\n /** Snapshot the current state as a UIMessage. */\n toMessage(): UIMessage {\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n }\n\n /**\n * Merge this accumulator's message into an existing message array.\n * Handles continuation (walk backward for last assistant), replacement\n * (update existing by messageId), or append (new message). An unseeded\n * continuation adopts current parts here before applying its queued chunks.\n */\n mergeInto(messages: UIMessage[]): UIMessage[] {\n let existingIdx = messages.findIndex((m) => m.id === this.messageId);\n\n if (existingIdx < 0 && this._isContinuation) {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === \"assistant\") {\n existingIdx = i;\n break;\n }\n }\n }\n\n if (this._pendingContinuationChunks !== null) {\n const pendingChunks = this._pendingContinuationChunks;\n this._pendingContinuationChunks = null;\n this.parts.splice(\n 0,\n this.parts.length,\n ...(existingIdx >= 0 ? messages[existingIdx].parts : [])\n );\n const currentMetadata =\n existingIdx >= 0\n ? asMetadata(messages[existingIdx].metadata)\n : undefined;\n this.metadata = currentMetadata ? { ...currentMetadata } : undefined;\n if (existingIdx >= 0) {\n this.messageId = messages[existingIdx].id;\n }\n for (const chunk of pendingChunks) {\n this.applyChunk(chunk);\n }\n }\n\n const messageId =\n existingIdx >= 0 ? messages[existingIdx].id : this.messageId;\n\n const partialMessage: UIMessage = {\n id: messageId,\n role: \"assistant\",\n parts: [...this.parts],\n ...(this.metadata != null && { metadata: this.metadata })\n } as UIMessage;\n\n if (existingIdx >= 0) {\n const updated = [...messages];\n updated[existingIdx] = partialMessage;\n return updated;\n }\n return [...messages, partialMessage];\n }\n}\n","/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** @deprecated Continuations now seed from current messages in `messagesUpdate`. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n accumulator = new StreamAccumulator({\n messageId: event.messageId,\n continuation: event.continuation\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n if (event.done) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n return {\n state: { status: \"idle\" },\n messagesUpdate,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n","/**\n * Wire protocol message type constants for the cf_agent_chat_* protocol.\n *\n * These are the string values used on the wire between agent servers and\n * clients. Both @cloudflare/ai-chat (via its MessageType enum) and\n * @cloudflare/think use these values.\n */\nexport const STREAM_RESUME_NONE_REASONS = {\n /** No active, pending, or terminal stream exists for this agent. */\n IDLE: \"idle\",\n /** An active tool continuation is owned by another live connection. */\n CONTINUATION_OWNED: \"continuation-owned\"\n} as const;\n\nexport type StreamResumeNoneReason =\n (typeof STREAM_RESUME_NONE_REASONS)[keyof typeof STREAM_RESUME_NONE_REASONS];\n\nexport const CHAT_MESSAGE_TYPES = {\n CHAT_MESSAGES: \"cf_agent_chat_messages\",\n USE_CHAT_REQUEST: \"cf_agent_use_chat_request\",\n USE_CHAT_RESPONSE: \"cf_agent_use_chat_response\",\n CHAT_CLEAR: \"cf_agent_chat_clear\",\n CHAT_REQUEST_CANCEL: \"cf_agent_chat_request_cancel\",\n STREAM_RESUMING: \"cf_agent_stream_resuming\",\n STREAM_RESUME_ACK: \"cf_agent_stream_resume_ack\",\n STREAM_RESUME_REQUEST: \"cf_agent_stream_resume_request\",\n STREAM_RESUME_NONE: \"cf_agent_stream_resume_none\",\n // Server→client: a turn has been accepted but its resumable stream has not\n // started yet (queued, debouncing, waiting on MCP setup, or running async\n // work in `onChatMessage`). Sent in response to a resume request (or on\n // connect) so a reconnecting/re-mounting client keeps its expectation instead\n // of resolving its resume probe to \"no stream\" and then false-timing-out the\n // turn. Resolved by a later `STREAM_RESUMING` (stream started) or\n // `STREAM_RESUME_NONE` (turn settled without streaming). Backward-compatible —\n // clients that don't understand it ignore it. See issue #1784.\n STREAM_PENDING: \"cf_agent_stream_pending\",\n TOOL_RESULT: \"cf_agent_tool_result\",\n TOOL_APPROVAL: \"cf_agent_tool_approval\",\n MESSAGE_UPDATED: \"cf_agent_message_updated\",\n // Server→client: a durable chat turn is being recovered (interrupted by a\n // deploy/eviction or a stream-stall watchdog abort and now resuming). Sent\n // when a recovery continuation is scheduled and cleared on every terminal\n // outcome; `@cloudflare/think` also replays it on connect so a client that\n // joins mid-recovery learns it. Purely a progress hint — backward-compatible\n // (clients that don't understand it ignore it). See issue #1620.\n CHAT_RECOVERING: \"cf_agent_chat_recovering\"\n} as const;\n"],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
- Y as MCPServersState,
3
- _ as AgentToolRunState,
4
- g as AgentToolRunPart
5
- } from "./agent-tool-types-BC-WFlsz.js";
2
+ $ as AgentToolRunPart,
3
+ et as AgentToolRunState,
4
+ w as MCPServersState
5
+ } from "./agent-routing-CnkaHb-v.js";
6
6
  import { ClientParameters } from "./serializable.js";
7
7
  import {
8
8
  AgentConnectionError,
package/dist/react.js CHANGED
@@ -1,6 +1,7 @@
1
- import "./types.js";
2
1
  import { camelCaseToKebabCase } from "./utils.js";
2
+ import "./types.js";
3
3
  import { AgentConnectionError, createStubProxy, isTerminalCloseEvent } from "./client.js";
4
+ import { buildSubAgentPathUnchecked } from "./sub-routing.js";
4
5
  import { n as applyAgentToolEvent, r as createAgentToolEventState } from "./agent-tools-y7zLfw4Q.js";
5
6
  import { usePartySocket } from "partysocket/react";
6
7
  import { use, useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -27,14 +28,10 @@ function createCacheKey(agentNamespace, name, subChainOrDeps, deps) {
27
28
  }
28
29
  /** Build a URL path tail `/sub/{agent-kebab}/{name}/...` from a sub chain. */
29
30
  function buildSubPath(subChain, extraPath) {
30
- if (subChain.length === 0) return extraPath ?? "";
31
- const combined = subChain.flatMap((step) => [
32
- "sub",
33
- camelCaseToKebabCase(step.agent),
34
- encodeURIComponent(step.name)
35
- ]).join("/");
36
- if (extraPath) return `${combined}/${extraPath.startsWith("/") ? extraPath.slice(1) : extraPath}`;
37
- return combined;
31
+ return buildSubAgentPathUnchecked(subChain.map((step) => ({
32
+ className: step.agent,
33
+ name: step.name
34
+ })), extraPath);
38
35
  }
39
36
  function getCacheEntry(key) {
40
37
  const entry = queryCache.get(key);
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":["AgentConnectionErrorCtor"],"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 AgentConnectionError,\n AgentPromiseReturnType,\n AgentStub,\n CallOptions,\n OptionalAgentMethods,\n RequiredAgentMethods,\n StreamOptions,\n UntypedAgentStub\n} from \"./client\";\nimport type { ClientParameters } from \"./serializable\";\nimport {\n createStubProxy,\n DEFAULT_CALL_TIMEOUT_MS,\n AgentConnectionError as AgentConnectionErrorCtor,\n isTerminalCloseEvent\n} from \"./client\";\nimport { camelCaseToKebabCase } from \"./utils\";\nimport { MessageType } from \"./types\";\nimport {\n applyAgentToolEvent,\n createAgentToolEventState,\n type AgentToolEventMessage,\n type AgentToolEventState,\n type AgentToolRunPart,\n type AgentToolRunState\n} from \"./chat/agent-tools\";\n\ntype QueryObject = Record<string, string | null>;\ntype TerminalReconnectOptions = {\n shouldReconnectOnClose?: (event: CloseEvent) => boolean;\n};\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 TerminalReconnectOptions & {\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 * Default timeout (in milliseconds) applied to non-streaming `call()`s\n * that don't pass an explicit `timeout`. Acts as a backstop so calls\n * whose response is lost (e.g. the connection is replaced mid-flight)\n * reject instead of hanging forever.\n *\n * Defaults to 30 000 ms. Set to `0` to disable the default timeout.\n * Streaming calls never get a default timeout (long-lived streams are\n * legitimate); pass an explicit `timeout` to bound them.\n */\n defaultCallTimeout?: number;\n /** Called when the connection closes with a terminal code and will not reconnect. */\n onConnectionError?: (error: AgentConnectionError) => void;\n };\n\ntype OptionalArgsAgentMethodCall<AgentT> = <\n K extends keyof OptionalAgentMethods<AgentT>\n>(\n method: K,\n args?: ClientParameters<OptionalAgentMethods<AgentT>[K]>,\n options?: CallOptions | StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype RequiredArgsAgentMethodCall<AgentT> = <\n K extends keyof RequiredAgentMethods<AgentT>\n>(\n method: K,\n args: ClientParameters<RequiredAgentMethods<AgentT>[K]>,\n options?: CallOptions | 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 options?: CallOptions | 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 connectionError: AgentConnectionError | null;\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 connectionError: AgentConnectionError | null;\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 connectionError: AgentConnectionError | null;\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 defaultCallTimeout,\n onConnectionError,\n shouldReconnectOnClose,\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 //\n // Each entry is tagged with the socket the request was transmitted on\n // (`sentOn`). Requests are only handed to a socket once it's OPEN —\n // until then they stay queued here (`sentOn: null`). This matters\n // because `usePartySocket` *replaces* the socket object whenever\n // connection options change (async query refresh, path change, etc.):\n // anything buffered inside a replaced socket is lost forever, and a\n // call transmitted on a replaced socket can never receive its\n // response. Tagging lets us:\n // - flush still-queued requests on whichever socket connects next\n // (safe: they were never transmitted, so no double-execution risk)\n // - reject calls transmitted on a socket that closed or was replaced\n // (their response can never arrive)\n // - avoid rejecting calls in flight on the *new* socket when a stale\n // close event from an old socket trickles in\n const pendingCallsRef = useRef(\n new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n timeoutId?: ReturnType<typeof setTimeout>;\n /** Serialized RPC request, kept so it can be (re)transmitted */\n request: string;\n /** Socket the request was transmitted on; null while queued */\n sentOn: PartySocket | null;\n }\n >()\n );\n\n // Always points at the socket from the latest render. `call`,\n // `setState`, and the queue-flushing logic go through this ref so\n // that stale `agent` references held by old effect closures still\n // route their traffic to the live socket instead of a dead one.\n const socketRef = useRef<PartySocket | null>(null);\n\n const defaultCallTimeoutRef = useRef(\n defaultCallTimeout ?? DEFAULT_CALL_TIMEOUT_MS\n );\n defaultCallTimeoutRef.current = defaultCallTimeout ?? DEFAULT_CALL_TIMEOUT_MS;\n\n /** Reject (and remove) every pending call transmitted on `socket`. */\n const rejectCallsSentOn = (socket: PartySocket, reason: string) => {\n const error = new Error(reason);\n for (const [id, pending] of pendingCallsRef.current) {\n if (pending.sentOn === socket) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\n pendingCallsRef.current.delete(id);\n pending.reject(error);\n pending.stream?.onError?.(reason);\n }\n }\n };\n\n /** Transmit queued (never-sent) calls if the live socket is open. */\n const flushQueuedCalls = () => {\n const socket = socketRef.current;\n if (!socket || socket.readyState !== socket.OPEN) return;\n for (const pending of pendingCallsRef.current.values()) {\n if (pending.sentOn === null) {\n socket.send(pending.request);\n pending.sentOn = socket;\n }\n }\n };\n\n /** Reject (and remove) every still-queued (never transmitted) call. */\n const rejectQueuedCalls = (reason: string) => {\n const error = new Error(reason);\n for (const [id, pending] of pendingCallsRef.current) {\n if (pending.sentOn === null) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\n pendingCallsRef.current.delete(id);\n pending.reject(error);\n pending.stream?.onError?.(reason);\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 const [connectionError, setConnectionError] =\n useState<AgentConnectionError | null>(null);\n const connectionErrorRef = useRef<AgentConnectionError | null>(null);\n const connectionErrorAddressKeyRef = useRef<string | null>(null);\n const shouldReconnectOnCloseRef = useRef(shouldReconnectOnClose);\n shouldReconnectOnCloseRef.current = shouldReconnectOnClose;\n const classifyReconnect = useCallback(\n (event: CloseEvent) =>\n (shouldReconnectOnCloseRef.current?.(event) ?? true) &&\n !isTerminalCloseEvent(event),\n []\n );\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 const mutableAgentRef = useRef<{\n agent: string;\n name: string;\n identified: boolean;\n } | null>(null);\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 shouldReconnectOnClose: classifyReconnect\n }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions,\n shouldReconnectOnClose: classifyReconnect\n };\n\n const socketEnabled = !awaitingQueryRefresh && (restOptions.enabled ?? true);\n\n // Identifies *which agent instance* this hook is addressing. Queued\n // (never-transmitted) RPC calls are only safe to flush onto a later\n // socket if it still points at the same instance — a call composed for\n // agent \"alpha\" must not execute on agent \"beta\" just because the\n // `name` prop changed while the call was waiting for a connection.\n // Credentials (query params) are deliberately excluded: a token\n // refresh doesn't change where calls go.\n const addressKey = JSON.stringify([\n options.host ?? null,\n options.basePath ?? null,\n agentNamespace,\n options.name || \"default\",\n combinedPath || null\n ]);\n const visibleConnectionError =\n connectionErrorAddressKeyRef.current === addressKey\n ? connectionError\n : null;\n connectionErrorRef.current = visibleConnectionError;\n\n const agent = usePartySocket({\n ...socketOptions,\n enabled: socketEnabled,\n onOpen: (event: Event) => {\n connectionErrorAddressKeyRef.current = null;\n setConnectionError(null);\n // The socket is open: transmit any RPC requests that were issued\n // while disconnected (or while a previous socket was being\n // replaced). They were never handed to a socket before, so this\n // cannot double-execute anything server-side.\n flushQueuedCalls();\n options.onOpen?.(event);\n },\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 const currentAgent = mutableAgentRef.current;\n if (currentAgent) {\n currentAgent.name = newName;\n currentAgent.agent = newAgent;\n currentAgent.identified = true;\n }\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) {\n console.warn(\n `[useAgent] Discarded an RPC response with no matching pending call (id \"${response.id}\"). ` +\n \"The call likely timed out or was rejected when its connection closed before the response arrived.\"\n );\n return;\n }\n\n if (!response.success) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 // Identify which socket actually closed. Close events are\n // dispatched asynchronously, so a close from an old socket that\n // was just replaced can arrive while a new socket is already\n // connecting (or connected). `event.target` is the PartySocket\n // that dispatched the event; fall back to the live socket if the\n // environment doesn't populate it.\n const closedSocket =\n (event.target as PartySocket | null) ?? socketRef.current;\n const isCurrentSocket = closedSocket === socketRef.current;\n const terminalClose = isTerminalCloseEvent(event);\n\n // Calls transmitted on the closed socket can never receive their\n // response — reject them. Calls still queued (never transmitted)\n // stay pending and are flushed when a socket next opens; calls\n // in flight on a *different* (newer) socket are untouched.\n if (closedSocket) {\n rejectCallsSentOn(closedSocket, \"Connection closed\");\n if (isCurrentSocket && !closedSocket.shouldReconnect) {\n rejectQueuedCalls(\"Connection closed\");\n }\n }\n\n if (isCurrentSocket) {\n // Reset ready state for next connection\n resetReady();\n if (mutableAgentRef.current) {\n mutableAgentRef.current.identified = false;\n }\n setIdentity((prev) => ({ ...prev, identified: false }));\n\n if (closedSocket?.shouldReconnect) {\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\n if (!closedSocket?.shouldReconnect && terminalClose) {\n const error = new AgentConnectionErrorCtor(event);\n connectionErrorAddressKeyRef.current = addressKey;\n setConnectionError(error);\n onConnectionError?.(error);\n }\n }\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 connectionError: AgentConnectionError | null;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n };\n // Update the live-socket ref before anything below can use it.\n socketRef.current = agent;\n\n // When `usePartySocket` replaces the socket object (connection options\n // changed — async query refresh, path change, enabled toggle, ...) the\n // old socket's event listeners are detached at the same commit, so its\n // final close event may never be observed by our onClose handler.\n // Sweep here instead: anything transmitted on the old socket can never\n // get a response, and the identity it established no longer applies.\n // Queued (never-transmitted) calls survive and flush when the new\n // socket opens.\n const prevSocketRef = useRef<PartySocket | null>(null);\n const prevAddressKeyRef = useRef(addressKey);\n useEffect(() => {\n const prev = prevSocketRef.current;\n prevSocketRef.current = agent;\n const prevAddress = prevAddressKeyRef.current;\n prevAddressKeyRef.current = addressKey;\n\n // Destination guard: if the agent address changed (different agent,\n // name, or path — not just refreshed credentials), calls that are\n // still queued were composed for the *old* instance. Reject them\n // before anything can flush them onto the new instance.\n if (prevAddress !== addressKey) {\n connectionErrorAddressKeyRef.current = null;\n setConnectionError(null);\n rejectQueuedCalls(\n \"Call discarded: the agent address changed before the request could be sent\"\n );\n }\n\n if (prev && prev !== agent) {\n rejectCallsSentOn(prev, \"Connection closed\");\n resetReady();\n if (mutableAgentRef.current) {\n mutableAgentRef.current.identified = false;\n }\n setIdentity((current) =>\n current.identified ? { ...current, identified: false } : current\n );\n }\n // The helpers only touch refs; re-running on socket/address change is all we need.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, addressKey]);\n\n // Create the call method. Deliberately dependency-free: it routes\n // through refs, so even a stale `agent` reference captured by an old\n // effect closure issues calls against the live socket.\n const call = useCallback(\n <T = unknown,>(\n method: string,\n args: unknown[] = [],\n options?: CallOptions | StreamOptions\n ): Promise<T> => {\n return new Promise((resolve, reject) => {\n const socket = socketRef.current;\n if (\n socket &&\n connectionErrorRef.current &&\n socket.readyState === socket.CLOSED\n ) {\n reject(new Error(\"Connection closed\"));\n return;\n }\n\n const id = crypto.randomUUID();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Detect legacy format: { onChunk?, onDone?, onError? } vs new format: { timeout?, stream? }\n const isLegacyFormat =\n options &&\n (\"onChunk\" in options || \"onDone\" in options || \"onError\" in options);\n const streamOptions = isLegacyFormat\n ? (options as StreamOptions)\n : (options as CallOptions | undefined)?.stream;\n const timeout = isLegacyFormat\n ? undefined\n : (options as CallOptions | undefined)?.timeout;\n\n // Apply the default timeout as a backstop for non-streaming\n // calls so a lost response rejects instead of hanging forever.\n // An explicit `timeout` (including 0 = disabled) always wins.\n const effectiveTimeout =\n timeout !== undefined\n ? timeout\n : streamOptions\n ? undefined\n : defaultCallTimeoutRef.current;\n\n if (effectiveTimeout) {\n timeoutId = setTimeout(() => {\n const pending = pendingCallsRef.current.get(id);\n pendingCallsRef.current.delete(id);\n const errorMessage = `RPC call to ${method} timed out after ${effectiveTimeout}ms`;\n pending?.stream?.onError?.(errorMessage);\n reject(new Error(errorMessage));\n }, effectiveTimeout);\n }\n\n const rpcRequest: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n const request = JSON.stringify(rpcRequest);\n\n pendingCallsRef.current.set(id, {\n reject,\n resolve: resolve as (value: unknown) => void,\n stream: streamOptions,\n timeoutId,\n request,\n sentOn: null\n });\n\n // Transmit immediately if the live socket is open; otherwise the\n // request stays queued and is flushed on the next open event.\n // We never hand requests to a non-open socket: its internal\n // buffer is lost forever if the socket gets replaced.\n if (socket && socket.readyState === socket.OPEN) {\n socket.send(request);\n const pending = pendingCallsRef.current.get(id);\n if (pending) pending.sentOn = socket;\n }\n });\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n []\n );\n\n agent.setState = (newState: State) => {\n // Route through the live socket so stale `agent` references don't\n // write into a replaced socket's dead buffer.\n (socketRef.current ?? 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 agent.connectionError = visibleConnectionError;\n mutableAgentRef.current = agent;\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<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(options: {\n agent: AgentToolEventAgent;\n}): {\n runsById: Record<string, AgentToolRunState<Part>>;\n runsByToolCallId: Record<string, AgentToolRunState<Part>[]>;\n unboundRuns: AgentToolRunState<Part>[];\n getRunsForToolCall(toolCallId: string): AgentToolRunState<Part>[];\n resetLocalState(): void;\n} {\n const { agent } = options;\n const [state, setState] = useState<AgentToolEventState<Part>>(() =>\n createAgentToolEventState<Part>()\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<Part>(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<Part>());\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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"}
1
+ {"version":3,"file":"react.js","names":["AgentConnectionErrorCtor"],"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 AgentConnectionError,\n AgentPromiseReturnType,\n AgentStub,\n CallOptions,\n OptionalAgentMethods,\n RequiredAgentMethods,\n StreamOptions,\n UntypedAgentStub\n} from \"./client\";\nimport type { ClientParameters } from \"./serializable\";\nimport {\n createStubProxy,\n DEFAULT_CALL_TIMEOUT_MS,\n AgentConnectionError as AgentConnectionErrorCtor,\n isTerminalCloseEvent\n} from \"./client\";\nimport { buildSubAgentPathUnchecked } from \"./sub-routing\";\nimport { camelCaseToKebabCase } from \"./utils\";\nimport { MessageType } from \"./types\";\nimport {\n applyAgentToolEvent,\n createAgentToolEventState,\n type AgentToolEventMessage,\n type AgentToolEventState,\n type AgentToolRunPart,\n type AgentToolRunState\n} from \"./chat/agent-tools\";\n\ntype QueryObject = Record<string, string | null>;\ntype TerminalReconnectOptions = {\n shouldReconnectOnClose?: (event: CloseEvent) => boolean;\n};\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 return buildSubAgentPathUnchecked(\n subChain.map((step) => ({ className: step.agent, name: step.name })),\n extraPath\n );\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 TerminalReconnectOptions & {\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 * Default timeout (in milliseconds) applied to non-streaming `call()`s\n * that don't pass an explicit `timeout`. Acts as a backstop so calls\n * whose response is lost (e.g. the connection is replaced mid-flight)\n * reject instead of hanging forever.\n *\n * Defaults to 30 000 ms. Set to `0` to disable the default timeout.\n * Streaming calls never get a default timeout (long-lived streams are\n * legitimate); pass an explicit `timeout` to bound them.\n */\n defaultCallTimeout?: number;\n /** Called when the connection closes with a terminal code and will not reconnect. */\n onConnectionError?: (error: AgentConnectionError) => void;\n };\n\ntype OptionalArgsAgentMethodCall<AgentT> = <\n K extends keyof OptionalAgentMethods<AgentT>\n>(\n method: K,\n args?: ClientParameters<OptionalAgentMethods<AgentT>[K]>,\n options?: CallOptions | StreamOptions\n) => AgentPromiseReturnType<AgentT, K>;\n\ntype RequiredArgsAgentMethodCall<AgentT> = <\n K extends keyof RequiredAgentMethods<AgentT>\n>(\n method: K,\n args: ClientParameters<RequiredAgentMethods<AgentT>[K]>,\n options?: CallOptions | 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 options?: CallOptions | 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 connectionError: AgentConnectionError | null;\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 connectionError: AgentConnectionError | null;\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 connectionError: AgentConnectionError | null;\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 defaultCallTimeout,\n onConnectionError,\n shouldReconnectOnClose,\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 //\n // Each entry is tagged with the socket the request was transmitted on\n // (`sentOn`). Requests are only handed to a socket once it's OPEN —\n // until then they stay queued here (`sentOn: null`). This matters\n // because `usePartySocket` *replaces* the socket object whenever\n // connection options change (async query refresh, path change, etc.):\n // anything buffered inside a replaced socket is lost forever, and a\n // call transmitted on a replaced socket can never receive its\n // response. Tagging lets us:\n // - flush still-queued requests on whichever socket connects next\n // (safe: they were never transmitted, so no double-execution risk)\n // - reject calls transmitted on a socket that closed or was replaced\n // (their response can never arrive)\n // - avoid rejecting calls in flight on the *new* socket when a stale\n // close event from an old socket trickles in\n const pendingCallsRef = useRef(\n new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n timeoutId?: ReturnType<typeof setTimeout>;\n /** Serialized RPC request, kept so it can be (re)transmitted */\n request: string;\n /** Socket the request was transmitted on; null while queued */\n sentOn: PartySocket | null;\n }\n >()\n );\n\n // Always points at the socket from the latest render. `call`,\n // `setState`, and the queue-flushing logic go through this ref so\n // that stale `agent` references held by old effect closures still\n // route their traffic to the live socket instead of a dead one.\n const socketRef = useRef<PartySocket | null>(null);\n\n const defaultCallTimeoutRef = useRef(\n defaultCallTimeout ?? DEFAULT_CALL_TIMEOUT_MS\n );\n defaultCallTimeoutRef.current = defaultCallTimeout ?? DEFAULT_CALL_TIMEOUT_MS;\n\n /** Reject (and remove) every pending call transmitted on `socket`. */\n const rejectCallsSentOn = (socket: PartySocket, reason: string) => {\n const error = new Error(reason);\n for (const [id, pending] of pendingCallsRef.current) {\n if (pending.sentOn === socket) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\n pendingCallsRef.current.delete(id);\n pending.reject(error);\n pending.stream?.onError?.(reason);\n }\n }\n };\n\n /** Transmit queued (never-sent) calls if the live socket is open. */\n const flushQueuedCalls = () => {\n const socket = socketRef.current;\n if (!socket || socket.readyState !== socket.OPEN) return;\n for (const pending of pendingCallsRef.current.values()) {\n if (pending.sentOn === null) {\n socket.send(pending.request);\n pending.sentOn = socket;\n }\n }\n };\n\n /** Reject (and remove) every still-queued (never transmitted) call. */\n const rejectQueuedCalls = (reason: string) => {\n const error = new Error(reason);\n for (const [id, pending] of pendingCallsRef.current) {\n if (pending.sentOn === null) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\n pendingCallsRef.current.delete(id);\n pending.reject(error);\n pending.stream?.onError?.(reason);\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 const [connectionError, setConnectionError] =\n useState<AgentConnectionError | null>(null);\n const connectionErrorRef = useRef<AgentConnectionError | null>(null);\n const connectionErrorAddressKeyRef = useRef<string | null>(null);\n const shouldReconnectOnCloseRef = useRef(shouldReconnectOnClose);\n shouldReconnectOnCloseRef.current = shouldReconnectOnClose;\n const classifyReconnect = useCallback(\n (event: CloseEvent) =>\n (shouldReconnectOnCloseRef.current?.(event) ?? true) &&\n !isTerminalCloseEvent(event),\n []\n );\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 const mutableAgentRef = useRef<{\n agent: string;\n name: string;\n identified: boolean;\n } | null>(null);\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 shouldReconnectOnClose: classifyReconnect\n }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: combinedPath || undefined,\n query: resolvedQuery,\n ...restOptions,\n shouldReconnectOnClose: classifyReconnect\n };\n\n const socketEnabled = !awaitingQueryRefresh && (restOptions.enabled ?? true);\n\n // Identifies *which agent instance* this hook is addressing. Queued\n // (never-transmitted) RPC calls are only safe to flush onto a later\n // socket if it still points at the same instance — a call composed for\n // agent \"alpha\" must not execute on agent \"beta\" just because the\n // `name` prop changed while the call was waiting for a connection.\n // Credentials (query params) are deliberately excluded: a token\n // refresh doesn't change where calls go.\n const addressKey = JSON.stringify([\n options.host ?? null,\n options.basePath ?? null,\n agentNamespace,\n options.name || \"default\",\n combinedPath || null\n ]);\n const visibleConnectionError =\n connectionErrorAddressKeyRef.current === addressKey\n ? connectionError\n : null;\n connectionErrorRef.current = visibleConnectionError;\n\n const agent = usePartySocket({\n ...socketOptions,\n enabled: socketEnabled,\n onOpen: (event: Event) => {\n connectionErrorAddressKeyRef.current = null;\n setConnectionError(null);\n // The socket is open: transmit any RPC requests that were issued\n // while disconnected (or while a previous socket was being\n // replaced). They were never handed to a socket before, so this\n // cannot double-execute anything server-side.\n flushQueuedCalls();\n options.onOpen?.(event);\n },\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 const currentAgent = mutableAgentRef.current;\n if (currentAgent) {\n currentAgent.name = newName;\n currentAgent.agent = newAgent;\n currentAgent.identified = true;\n }\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) {\n console.warn(\n `[useAgent] Discarded an RPC response with no matching pending call (id \"${response.id}\"). ` +\n \"The call likely timed out or was rejected when its connection closed before the response arrived.\"\n );\n return;\n }\n\n if (!response.success) {\n if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 if (pending.timeoutId) clearTimeout(pending.timeoutId);\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 // Identify which socket actually closed. Close events are\n // dispatched asynchronously, so a close from an old socket that\n // was just replaced can arrive while a new socket is already\n // connecting (or connected). `event.target` is the PartySocket\n // that dispatched the event; fall back to the live socket if the\n // environment doesn't populate it.\n const closedSocket =\n (event.target as PartySocket | null) ?? socketRef.current;\n const isCurrentSocket = closedSocket === socketRef.current;\n const terminalClose = isTerminalCloseEvent(event);\n\n // Calls transmitted on the closed socket can never receive their\n // response — reject them. Calls still queued (never transmitted)\n // stay pending and are flushed when a socket next opens; calls\n // in flight on a *different* (newer) socket are untouched.\n if (closedSocket) {\n rejectCallsSentOn(closedSocket, \"Connection closed\");\n if (isCurrentSocket && !closedSocket.shouldReconnect) {\n rejectQueuedCalls(\"Connection closed\");\n }\n }\n\n if (isCurrentSocket) {\n // Reset ready state for next connection\n resetReady();\n if (mutableAgentRef.current) {\n mutableAgentRef.current.identified = false;\n }\n setIdentity((prev) => ({ ...prev, identified: false }));\n\n if (closedSocket?.shouldReconnect) {\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\n if (!closedSocket?.shouldReconnect && terminalClose) {\n const error = new AgentConnectionErrorCtor(event);\n connectionErrorAddressKeyRef.current = addressKey;\n setConnectionError(error);\n onConnectionError?.(error);\n }\n }\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 connectionError: AgentConnectionError | null;\n setState: (state: State) => void;\n call: UntypedAgentMethodCall;\n stub: UntypedAgentStub;\n getHttpUrl: () => string;\n };\n // Update the live-socket ref before anything below can use it.\n socketRef.current = agent;\n\n // When `usePartySocket` replaces the socket object (connection options\n // changed — async query refresh, path change, enabled toggle, ...) the\n // old socket's event listeners are detached at the same commit, so its\n // final close event may never be observed by our onClose handler.\n // Sweep here instead: anything transmitted on the old socket can never\n // get a response, and the identity it established no longer applies.\n // Queued (never-transmitted) calls survive and flush when the new\n // socket opens.\n const prevSocketRef = useRef<PartySocket | null>(null);\n const prevAddressKeyRef = useRef(addressKey);\n useEffect(() => {\n const prev = prevSocketRef.current;\n prevSocketRef.current = agent;\n const prevAddress = prevAddressKeyRef.current;\n prevAddressKeyRef.current = addressKey;\n\n // Destination guard: if the agent address changed (different agent,\n // name, or path — not just refreshed credentials), calls that are\n // still queued were composed for the *old* instance. Reject them\n // before anything can flush them onto the new instance.\n if (prevAddress !== addressKey) {\n connectionErrorAddressKeyRef.current = null;\n setConnectionError(null);\n rejectQueuedCalls(\n \"Call discarded: the agent address changed before the request could be sent\"\n );\n }\n\n if (prev && prev !== agent) {\n rejectCallsSentOn(prev, \"Connection closed\");\n resetReady();\n if (mutableAgentRef.current) {\n mutableAgentRef.current.identified = false;\n }\n setIdentity((current) =>\n current.identified ? { ...current, identified: false } : current\n );\n }\n // The helpers only touch refs; re-running on socket/address change is all we need.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, addressKey]);\n\n // Create the call method. Deliberately dependency-free: it routes\n // through refs, so even a stale `agent` reference captured by an old\n // effect closure issues calls against the live socket.\n const call = useCallback(\n <T = unknown,>(\n method: string,\n args: unknown[] = [],\n options?: CallOptions | StreamOptions\n ): Promise<T> => {\n return new Promise((resolve, reject) => {\n const socket = socketRef.current;\n if (\n socket &&\n connectionErrorRef.current &&\n socket.readyState === socket.CLOSED\n ) {\n reject(new Error(\"Connection closed\"));\n return;\n }\n\n const id = crypto.randomUUID();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Detect legacy format: { onChunk?, onDone?, onError? } vs new format: { timeout?, stream? }\n const isLegacyFormat =\n options &&\n (\"onChunk\" in options || \"onDone\" in options || \"onError\" in options);\n const streamOptions = isLegacyFormat\n ? (options as StreamOptions)\n : (options as CallOptions | undefined)?.stream;\n const timeout = isLegacyFormat\n ? undefined\n : (options as CallOptions | undefined)?.timeout;\n\n // Apply the default timeout as a backstop for non-streaming\n // calls so a lost response rejects instead of hanging forever.\n // An explicit `timeout` (including 0 = disabled) always wins.\n const effectiveTimeout =\n timeout !== undefined\n ? timeout\n : streamOptions\n ? undefined\n : defaultCallTimeoutRef.current;\n\n if (effectiveTimeout) {\n timeoutId = setTimeout(() => {\n const pending = pendingCallsRef.current.get(id);\n pendingCallsRef.current.delete(id);\n const errorMessage = `RPC call to ${method} timed out after ${effectiveTimeout}ms`;\n pending?.stream?.onError?.(errorMessage);\n reject(new Error(errorMessage));\n }, effectiveTimeout);\n }\n\n const rpcRequest: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n const request = JSON.stringify(rpcRequest);\n\n pendingCallsRef.current.set(id, {\n reject,\n resolve: resolve as (value: unknown) => void,\n stream: streamOptions,\n timeoutId,\n request,\n sentOn: null\n });\n\n // Transmit immediately if the live socket is open; otherwise the\n // request stays queued and is flushed on the next open event.\n // We never hand requests to a non-open socket: its internal\n // buffer is lost forever if the socket gets replaced.\n if (socket && socket.readyState === socket.OPEN) {\n socket.send(request);\n const pending = pendingCallsRef.current.get(id);\n if (pending) pending.sentOn = socket;\n }\n });\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n []\n );\n\n agent.setState = (newState: State) => {\n // Route through the live socket so stale `agent` references don't\n // write into a replaced socket's dead buffer.\n (socketRef.current ?? 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 agent.connectionError = visibleConnectionError;\n mutableAgentRef.current = agent;\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<\n Part extends AgentToolRunPart = AgentToolRunPart\n>(options: {\n agent: AgentToolEventAgent;\n}): {\n runsById: Record<string, AgentToolRunState<Part>>;\n runsByToolCallId: Record<string, AgentToolRunState<Part>[]>;\n unboundRuns: AgentToolRunState<Part>[];\n getRunsForToolCall(toolCallId: string): AgentToolRunState<Part>[];\n resetLocalState(): void;\n} {\n const { agent } = options;\n const [state, setState] = useState<AgentToolEventState<Part>>(() =>\n createAgentToolEventState<Part>()\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<Part>(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<Part>());\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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"}