agents 0.24.0 → 0.26.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 (170) hide show
  1. package/dist/{agent-routing-B2XLNMxq.d.ts → agent-routing-0DR0Tk2v.d.ts} +205 -132
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +28 -22
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-Dj5Mzlw7.d.ts → agent-tools-YrATZJu2.d.ts} +34 -20
  10. package/dist/agent-tools.d.ts +20 -18
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/capability-B4WbF81e.js.map +1 -1
  32. package/dist/{capability-runner-BUBa6Ake.d.ts → capability-runner-DBAnGr6L.d.ts} +7 -2
  33. package/dist/chat/index.d.ts +340 -234
  34. package/dist/chat/index.js +592 -66
  35. package/dist/chat/index.js.map +1 -1
  36. package/dist/chat/react.d.ts +42 -2
  37. package/dist/chat/react.js +191 -35
  38. package/dist/chat/react.js.map +1 -1
  39. package/dist/chat/transport.d.ts +1 -1
  40. package/dist/chat/transport.js +1 -1
  41. package/dist/chat-sdk/index.d.ts +17 -3
  42. package/dist/chat-sdk/index.js +28 -2
  43. package/dist/chat-sdk/index.js.map +1 -1
  44. package/dist/{client-C9DAjZIY.js → client-4OlMIJx-.js} +102 -29
  45. package/dist/client-4OlMIJx-.js.map +1 -0
  46. package/dist/{client-BxdkCuVy.d.ts → client-je3koD9Z.d.ts} +45 -10
  47. package/dist/client.d.ts +7 -5
  48. package/dist/client.js +2 -2
  49. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  50. package/dist/connector-58XDGvc3.js.map +1 -0
  51. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  52. package/dist/context/index.d.ts +29 -0
  53. package/dist/context/index.js +64 -17
  54. package/dist/context/index.js.map +1 -1
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{current-agent-C4w86kcN.d.ts → current-agent-vAk_5JMt.d.ts} +4 -4
  57. package/dist/{durable-object-lifecycle-ei7M7lqB.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +15 -7
  58. package/dist/harness/pi/index.d.ts +344 -0
  59. package/dist/harness/pi/index.js +873 -0
  60. package/dist/harness/pi/index.js.map +1 -0
  61. package/dist/images-iiTDSyRu.js +1631 -0
  62. package/dist/images-iiTDSyRu.js.map +1 -0
  63. package/dist/{index-Dlgtd3Mj.d.ts → index-BreL0YrQ.d.ts} +2 -2
  64. package/dist/index-kbSUIer3.d.ts +343 -0
  65. package/dist/index.d.ts +46 -44
  66. package/dist/index.js +1 -1
  67. package/dist/internal_context.d.ts +1 -1
  68. package/dist/lifecycle/index.d.ts +3 -3
  69. package/dist/lifecycle/index.js +1 -1
  70. package/dist/{lifecycle-Mm_jQh7r.js → lifecycle-CFu6OV03.js} +39 -10
  71. package/dist/{lifecycle-Mm_jQh7r.js.map → lifecycle-CFu6OV03.js.map} +1 -1
  72. package/dist/mcp/client/index.d.ts +20 -20
  73. package/dist/mcp/client/x402.js +21 -19
  74. package/dist/mcp/client/x402.js.map +1 -1
  75. package/dist/mcp/index.d.ts +35 -35
  76. package/dist/mcp/index.js +1 -1
  77. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  78. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  79. package/dist/models/ai-sdk/index.d.ts +353 -0
  80. package/dist/models/ai-sdk/index.js +2443 -0
  81. package/dist/models/ai-sdk/index.js.map +1 -0
  82. package/dist/models/pi-ai/index.d.ts +313 -0
  83. package/dist/models/pi-ai/index.js +1795 -0
  84. package/dist/models/pi-ai/index.js.map +1 -0
  85. package/dist/observability/ai/index.js +20 -4
  86. package/dist/observability/ai/index.js.map +1 -1
  87. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  88. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  89. package/dist/queue/index.d.ts +3 -3
  90. package/dist/queue/index.js +1 -1
  91. package/dist/{queue-BC9F5cUo.js → queue-BB-zfIYF.js} +2 -2
  92. package/dist/{queue-BC9F5cUo.js.map → queue-BB-zfIYF.js.map} +1 -1
  93. package/dist/react.d.ts +6 -6
  94. package/dist/react.js +160 -64
  95. package/dist/react.js.map +1 -1
  96. package/dist/{retries-D9B2UCq3.d.ts → retries-CV6AkXmQ.d.ts} +18 -9
  97. package/dist/retries.d.ts +11 -9
  98. package/dist/retries.js +11 -1
  99. package/dist/retries.js.map +1 -1
  100. package/dist/routing/index.d.ts +2 -2
  101. package/dist/routing/index.js +1 -1
  102. package/dist/{scheduler-D_KHqxBa.js → scheduler-DZ3xQfeK.js} +2 -2
  103. package/dist/{scheduler-D_KHqxBa.js.map → scheduler-DZ3xQfeK.js.map} +1 -1
  104. package/dist/{scheduler-BGq6M5Kd.d.ts → scheduler-RdACNXDP.d.ts} +3 -3
  105. package/dist/schedules/index.d.ts +1 -1
  106. package/dist/schedules/index.js +1 -1
  107. package/dist/serializable.d.ts +1 -1
  108. package/dist/sessions/index.d.ts +70 -3
  109. package/dist/sessions/index.js +276 -67
  110. package/dist/sessions/index.js.map +1 -1
  111. package/dist/skills/index.d.ts +29 -314
  112. package/dist/socket-address.d.ts +16 -0
  113. package/dist/socket-address.js +19 -0
  114. package/dist/socket-address.js.map +1 -0
  115. package/dist/{src-BNU3ZiJM.js → src-BZeu-SCD.js} +423 -115
  116. package/dist/src-BZeu-SCD.js.map +1 -0
  117. package/dist/state/index.d.ts +1 -1
  118. package/dist/state/index.js +1 -1
  119. package/dist/streams/index.d.ts +1 -1
  120. package/dist/streams/index.js +1 -1
  121. package/dist/{streams-BcE8JP2X.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  122. package/dist/{streams-CCPRV6dt.js → streams-DW-UCOUc.js} +9 -2
  123. package/dist/streams-DW-UCOUc.js.map +1 -0
  124. package/dist/sub-routing.d.ts +12 -12
  125. package/dist/tasks/index.d.ts +1 -1
  126. package/dist/tasks/index.js +1 -1
  127. package/dist/{tasks-D4nLqVSI.js → tasks-68DESxf0.js} +2 -2
  128. package/dist/{tasks-D4nLqVSI.js.map → tasks-68DESxf0.js.map} +1 -1
  129. package/dist/{tasks-V2lCT8ZV.d.ts → tasks-9YxYtByh.d.ts} +2 -2
  130. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  131. package/dist/{types-DCD6BNZH.d.ts → types-BA4anrjI.d.ts} +2 -2
  132. package/dist/voice/index.d.ts +2 -2
  133. package/dist/websockets/index.d.ts +23 -6
  134. package/dist/websockets/index.js +1 -1
  135. package/dist/{websockets-D7IzWbZb.js → websockets-Ci-LZFHH.js} +45 -20
  136. package/dist/{websockets-D7IzWbZb.js.map → websockets-Ci-LZFHH.js.map} +1 -1
  137. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  138. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  139. package/dist/workflow-types.d.ts +25 -25
  140. package/dist/workflows.d.ts +21 -21
  141. package/dist/workflows.js +1 -1
  142. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  143. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  144. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  145. package/docs/agent-tools.md +29 -4
  146. package/docs/browse-the-web.md +87 -5
  147. package/docs/chat-agents.md +124 -16
  148. package/docs/context.md +27 -0
  149. package/docs/durable-execution.md +2 -0
  150. package/docs/harnesses/pi.md +173 -0
  151. package/docs/human-in-the-loop.md +4 -0
  152. package/docs/index.md +3 -0
  153. package/docs/lifecycle.md +20 -4
  154. package/docs/models-pi-ai.md +325 -0
  155. package/docs/models.md +504 -0
  156. package/docs/resumable-streaming.md +2 -0
  157. package/docs/routing.md +13 -13
  158. package/docs/sessions.md +36 -1
  159. package/docs/state.md +1 -0
  160. package/docs/sub-agents.md +9 -0
  161. package/package.json +40 -2
  162. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  163. package/dist/browser/ai.js.map +0 -1
  164. package/dist/client-C9DAjZIY.js.map +0 -1
  165. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  166. package/dist/connector-CptFKzRh.js.map +0 -1
  167. package/dist/message-builder-BymO4N_D.js.map +0 -1
  168. package/dist/src-BNU3ZiJM.js.map +0 -1
  169. package/dist/streams-CCPRV6dt.js.map +0 -1
  170. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
@@ -1,3 +1,362 @@
1
- import { C as connectUrl, S as CdpSession, _ as connectBrowserSession, a as loadCdpSpec, b as getBrowserRecording, c as browserLinks, d as browserScrape, f as browserScreenshot, g as connectBrowser, h as BrowserRenderingError, i as DurableBrowserSessionStore, l as browserMarkdown, m as runQuickAction, n as DEFAULT_EXEC_SWEEP_IDLE_MS, o as browserContent, p as browserSnapshot, r as DEFAULT_SWEEP_IDLE_MS, s as browserExtract, t as BrowserConnector, u as browserPdf, v as createBrowserSession, x as listBrowserTargets, y as deleteBrowserSession } from "../connector-CptFKzRh.js";
1
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
2
+ import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
3
+ import { t as LifecycleCapability } from "../capability-B4WbF81e.js";
4
+ import { C as listBrowserTargets, D as CdpSession, O as connectUrl, S as isMissingBrowserSession, T as CdpConnection, _ as connectBrowser, a as loadCdpSpec, b as deleteBrowserSession, c as browserExtract, d as browserPdf, f as browserScrape, g as BrowserRenderingError, h as runQuickAction, i as DurableBrowserSessionStore, l as browserLinks, m as browserSnapshot, n as DEFAULT_EXEC_SWEEP_IDLE_MS, o as createLiveView, p as browserScreenshot, r as DEFAULT_SWEEP_IDLE_MS, s as browserContent, t as BrowserConnector, u as browserMarkdown, v as connectBrowserSession, x as getBrowserRecording, y as createBrowserSession } from "../connector-58XDGvc3.js";
2
5
  import { CodemodeRuntime } from "@cloudflare/codemode";
3
- export { BrowserConnector, BrowserRenderingError, CdpSession, CodemodeRuntime, DEFAULT_EXEC_SWEEP_IDLE_MS, DEFAULT_SWEEP_IDLE_MS, DurableBrowserSessionStore, browserContent, browserExtract, browserLinks, browserMarkdown, browserPdf, browserScrape, browserScreenshot, browserSnapshot, connectBrowser, connectBrowserSession, connectUrl, createBrowserSession, deleteBrowserSession, getBrowserRecording, listBrowserTargets, loadCdpSpec, runQuickAction };
6
+ const NAMED_SESSION_KEY_PREFIX = "browser:session:";
7
+ /** The store key holding the named browser's current record. */
8
+ function namedBrowserSessionKey(name) {
9
+ return `${NAMED_SESSION_KEY_PREFIX}${name}`;
10
+ }
11
+ /**
12
+ * Where a closed or lost browser's record moves: permanent evidence that the
13
+ * name once owned a browser, so a later resolve reports `restarted: true`.
14
+ * Kept outside {@link NAMED_SESSION_KEY_PREFIX} so listing that prefix
15
+ * yields only names that currently own a browser. Grows with the number of
16
+ * distinct names the host has ever used.
17
+ */
18
+ const RETIRED_SESSION_KEY_PREFIX = "browser:retired:";
19
+ function retiredBrowserSessionKey(name) {
20
+ return `${RETIRED_SESSION_KEY_PREFIX}${name}`;
21
+ }
22
+ /**
23
+ * Run the browser on Cloudflare Browser Run.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * new Browser({ provider: browserRun(env.BROWSER, { recording: true }) });
28
+ * ```
29
+ */
30
+ function browserRun(binding, options = {}) {
31
+ return {
32
+ kind: "browser-run",
33
+ binding,
34
+ options
35
+ };
36
+ }
37
+ var _options = /* @__PURE__ */ new WeakMap();
38
+ var _key = /* @__PURE__ */ new WeakMap();
39
+ var _store = /* @__PURE__ */ new WeakMap();
40
+ var _Browser_brand = /* @__PURE__ */ new WeakSet();
41
+ /**
42
+ * One named, persistent browser.
43
+ *
44
+ * Install it with `Lifecycle.use()` — on an Agent subclass or any Durable
45
+ * Object that composes `Lifecycle` — and it stores its record in the
46
+ * object's own storage. It schedules nothing: Browser Run reclaims idle
47
+ * browsers itself once `keep_alive` elapses, and the next use replaces the
48
+ * lost browser with `restarted: true`.
49
+ *
50
+ * Everything here is host-side. Models never see browser names, Browser Run
51
+ * ids, or Live View URLs through this surface.
52
+ *
53
+ * Store discipline: locks wrap storage operations only — liveness probes and
54
+ * Browser Run create/delete calls always happen outside any lock, with a
55
+ * commit re-check to detect concurrent swaps.
56
+ *
57
+ * @experimental The API surface may change before stabilizing.
58
+ */
59
+ var Browser = class extends LifecycleCapability {
60
+ constructor(options) {
61
+ const name = options.name ?? "default";
62
+ super(`browser:${name}`);
63
+ _classPrivateMethodInitSpec(this, _Browser_brand);
64
+ _classPrivateFieldInitSpec(this, _options, void 0);
65
+ _classPrivateFieldInitSpec(this, _key, void 0);
66
+ _classPrivateFieldInitSpec(this, _store, void 0);
67
+ if (name.trim() === "") throw new Error("Browser names must be non-empty");
68
+ this.name = name;
69
+ _classPrivateFieldSet2(_options, this, options);
70
+ _classPrivateFieldSet2(_key, this, namedBrowserSessionKey(name));
71
+ }
72
+ /**
73
+ * Reattach to the browser when it is still alive, otherwise create a fresh
74
+ * one. See {@link ResolvedBrowser.restarted} for the mortality signal.
75
+ */
76
+ async resolve() {
77
+ const { name } = this;
78
+ const key = _classPrivateFieldGet2(_key, this);
79
+ for (let attempt = 0; attempt < 4; attempt++) {
80
+ const existing = await _assertClassBrand(_Browser_brand, this, _readStored).call(this);
81
+ if (existing === void 0) {
82
+ const { session, restarted } = await _assertClassBrand(_Browser_brand, this, _createAndCommit).call(this);
83
+ return {
84
+ name,
85
+ restarted,
86
+ ...session
87
+ };
88
+ }
89
+ const alive = await _assertClassBrand(_Browser_brand, this, _isAlive).call(this, existing);
90
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(key);
91
+ try {
92
+ const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(key);
93
+ if (current?.sessionId !== existing.sessionId) continue;
94
+ if (alive) {
95
+ const refreshed = {
96
+ ...current,
97
+ updatedAt: Date.now()
98
+ };
99
+ await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).set(key, refreshed);
100
+ return {
101
+ name,
102
+ restarted: false,
103
+ ...refreshed
104
+ };
105
+ }
106
+ await _assertClassBrand(_Browser_brand, this, _retire).call(this, current);
107
+ } finally {
108
+ await lock.release();
109
+ }
110
+ }
111
+ throw new Error(`Browser "${name}" kept changing concurrently — retry`);
112
+ }
113
+ /**
114
+ * Resolve the browser and open a CDP connection to it. Commands sent over
115
+ * the connection refresh the record's `updatedAt` (throttled to
116
+ * {@link SESSION_TOUCH_INTERVAL_MS}), so hosts can tell an actively used
117
+ * browser from one the platform has likely reclaimed.
118
+ *
119
+ * If the browser expires between the liveness probe and the WebSocket
120
+ * upgrade, the record is retired and the browser resolved once more, so
121
+ * the caller gets a fresh browser with `restarted: true` instead of an
122
+ * error.
123
+ */
124
+ async connect() {
125
+ const resolved = await this.resolve();
126
+ try {
127
+ return await _assertClassBrand(_Browser_brand, this, _attach).call(this, resolved);
128
+ } catch (error) {
129
+ if (!isMissingBrowserSession(error)) throw error;
130
+ await _assertClassBrand(_Browser_brand, this, _retireIfCurrent).call(this, resolved.sessionId);
131
+ }
132
+ const replaced = await this.resolve();
133
+ return _assertClassBrand(_Browser_brand, this, _attach).call(this, {
134
+ ...replaced,
135
+ restarted: true
136
+ });
137
+ }
138
+ /**
139
+ * Close the browser: retire the record (so the next resolve reports
140
+ * `restarted: true`) and delete its Browser Run session. Returns `false`
141
+ * when there was nothing to close.
142
+ *
143
+ * The platform delete is best-effort: the retired record is the durable
144
+ * outcome, and a browser whose delete failed is unreachable through this
145
+ * store, so the pinned `keep_alive` (≤600s) reclaims it.
146
+ */
147
+ async close() {
148
+ let stored;
149
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
150
+ try {
151
+ const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
152
+ if (!current) return false;
153
+ stored = current;
154
+ await _assertClassBrand(_Browser_brand, this, _retire).call(this, current);
155
+ } finally {
156
+ await lock.release();
157
+ }
158
+ try {
159
+ await deleteBrowserSession(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, stored.sessionId);
160
+ } catch (error) {
161
+ console.warn(`[agents/browser] Failed to delete closed Browser Run session ${stored.sessionId}`, error);
162
+ }
163
+ return true;
164
+ }
165
+ /**
166
+ * Mint Live View URLs for the browser's open tabs, fresh from a target
167
+ * listing. `undefined` when the browser was never created, is closed, or
168
+ * is gone. The URLs are bearer credentials with a short connect window
169
+ * (~5 min) — hand them to a trusted human, never to the model, and never
170
+ * store them: re-mint instead.
171
+ */
172
+ async liveView(options) {
173
+ const entry = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
174
+ if (!entry) return void 0;
175
+ try {
176
+ const targets = await listBrowserTargets(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, entry.sessionId);
177
+ try {
178
+ if (!await _assertClassBrand(_Browser_brand, this, _touch).call(this, entry.sessionId)) return void 0;
179
+ } catch (error) {
180
+ console.warn(`[agents/browser] Failed to refresh activity for browser "${this.name}"`, error);
181
+ }
182
+ return createLiveView(entry.sessionId, targets, options?.mode);
183
+ } catch (error) {
184
+ if (isMissingBrowserSession(error)) return void 0;
185
+ throw error;
186
+ }
187
+ }
188
+ };
189
+ async function _attach(resolved) {
190
+ let lastTouchAt = Date.now();
191
+ let touchInFlight = false;
192
+ const cdp = await connectBrowserSession(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, resolved.sessionId, {
193
+ timeoutMs: _classPrivateFieldGet2(_options, this).timeoutMs,
194
+ onActivity: () => {
195
+ const now = Date.now();
196
+ if (touchInFlight || now - lastTouchAt < _get_touchIntervalMs.call(_assertClassBrand(_Browser_brand, this))) return;
197
+ touchInFlight = true;
198
+ lastTouchAt = now;
199
+ _assertClassBrand(_Browser_brand, this, _touch).call(this, resolved.sessionId).catch((error) => {
200
+ console.warn(`[agents/browser] Failed to refresh activity for browser "${this.name}"`, error);
201
+ }).finally(() => {
202
+ touchInFlight = false;
203
+ });
204
+ }
205
+ });
206
+ const { binding } = _get_provider.call(_assertClassBrand(_Browser_brand, this));
207
+ return {
208
+ name: resolved.name,
209
+ sessionId: resolved.sessionId,
210
+ restarted: resolved.restarted,
211
+ activeTargetId: resolved.activeTargetId,
212
+ cdp,
213
+ setActiveTarget: (targetId) => _assertClassBrand(_Browser_brand, this, _update).call(this, resolved.sessionId, (current) => ({
214
+ ...current,
215
+ activeTargetId: targetId,
216
+ updatedAt: Date.now()
217
+ })),
218
+ spec: () => loadCdpSpec({
219
+ browser: binding,
220
+ sessionId: resolved.sessionId
221
+ })
222
+ };
223
+ }
224
+ function _get_provider() {
225
+ return _classPrivateFieldGet2(_options, this).provider;
226
+ }
227
+ /** The platform `keep_alive` every create applies. */
228
+ function _get_keepAliveMs() {
229
+ return _get_provider.call(_assertClassBrand(_Browser_brand, this)).options.keepAliveMs ?? 6e5;
230
+ }
231
+ /**
232
+ * Capped at half the keep-alive window: a browser with continuous CDP
233
+ * traffic must never look older than the window the platform uses to
234
+ * reclaim it.
235
+ */
236
+ function _get_touchIntervalMs() {
237
+ return Math.min(_classPrivateFieldGet2(_options, this).touchIntervalMs ?? 6e4, Math.floor(_get_keepAliveMs.call(_assertClassBrand(_Browser_brand, this)) / 2));
238
+ }
239
+ /** Lazy: `lifecycle.storage` exists only once `Lifecycle.use()` ran. */
240
+ function _get_sessionStore() {
241
+ _classPrivateFieldGet2(_store, this) ?? _classPrivateFieldSet2(_store, this, _classPrivateFieldGet2(_options, this).store ?? new DurableBrowserSessionStore(this.lifecycle.storage));
242
+ return _classPrivateFieldGet2(_store, this);
243
+ }
244
+ /**
245
+ * Refresh `updatedAt` while the record still holds `sessionId`. Returns
246
+ * false when a concurrent close or replacement already retired it.
247
+ */
248
+ function _touch(sessionId) {
249
+ return _assertClassBrand(_Browser_brand, this, _update).call(this, sessionId, (current) => ({
250
+ ...current,
251
+ updatedAt: Date.now()
252
+ }));
253
+ }
254
+ /**
255
+ * Rewrite the record only while it still holds `sessionId`: a replaced or
256
+ * retired record is never resurrected.
257
+ */
258
+ async function _update(sessionId, change) {
259
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
260
+ try {
261
+ const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
262
+ if (current?.sessionId !== sessionId) return false;
263
+ await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).set(_classPrivateFieldGet2(_key, this), change(current));
264
+ return true;
265
+ } finally {
266
+ await lock.release();
267
+ }
268
+ }
269
+ /** Retire the record if it still holds `sessionId`. */
270
+ async function _retireIfCurrent(sessionId) {
271
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
272
+ try {
273
+ const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
274
+ if (current?.sessionId === sessionId) await _assertClassBrand(_Browser_brand, this, _retire).call(this, current);
275
+ } finally {
276
+ await lock.release();
277
+ }
278
+ }
279
+ /**
280
+ * Move the record to its retired marker. Callers hold the key lock, so a
281
+ * resolver always sees either the record or the marker.
282
+ */
283
+ async function _retire(current) {
284
+ await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).set(retiredBrowserSessionKey(this.name), {
285
+ ...current,
286
+ closedAt: Date.now()
287
+ });
288
+ await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).delete(_classPrivateFieldGet2(_key, this));
289
+ }
290
+ /**
291
+ * Create a Browser Run session (outside any lock) and commit it, applying
292
+ * the provider's creation options. If a concurrent caller committed an
293
+ * entry first, theirs wins and the redundant session is deleted
294
+ * best-effort.
295
+ *
296
+ * `restarted` is read under the commit lock: a retired marker — left by
297
+ * close() or a dead-browser recovery — is evidence a prior browser
298
+ * existed, and only first-ever use of the name is not a restart. Markers
299
+ * are permanent, so reading at commit time also catches a browser that
300
+ * was created and closed while this create was in flight.
301
+ */
302
+ async function _createAndCommit() {
303
+ const { binding, options } = _get_provider.call(_assertClassBrand(_Browser_brand, this));
304
+ const info = await createBrowserSession(binding, {
305
+ keepAliveMs: _get_keepAliveMs.call(_assertClassBrand(_Browser_brand, this)),
306
+ recording: options.recording,
307
+ guardrails: options.guardrails
308
+ });
309
+ const now = Date.now();
310
+ const stored = {
311
+ sessionId: info.sessionId,
312
+ createdAt: now,
313
+ updatedAt: now
314
+ };
315
+ let winner;
316
+ let restarted;
317
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
318
+ try {
319
+ restarted = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(retiredBrowserSessionKey(this.name)) !== void 0;
320
+ const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
321
+ if (current === void 0) await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).set(_classPrivateFieldGet2(_key, this), stored);
322
+ else winner = current;
323
+ } finally {
324
+ await lock.release();
325
+ }
326
+ if (winner) {
327
+ try {
328
+ await deleteBrowserSession(binding, stored.sessionId);
329
+ } catch (error) {
330
+ console.warn(`[agents/browser] Failed to delete redundant Browser Run session ${stored.sessionId}`, error);
331
+ }
332
+ return {
333
+ session: winner,
334
+ restarted
335
+ };
336
+ }
337
+ return {
338
+ session: stored,
339
+ restarted
340
+ };
341
+ }
342
+ async function _isAlive(stored) {
343
+ try {
344
+ await listBrowserTargets(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, stored.sessionId);
345
+ return true;
346
+ } catch (error) {
347
+ if (isMissingBrowserSession(error)) return false;
348
+ throw error;
349
+ }
350
+ }
351
+ async function _readStored() {
352
+ const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
353
+ try {
354
+ return await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
355
+ } finally {
356
+ await lock.release();
357
+ }
358
+ }
359
+ //#endregion
360
+ export { Browser, BrowserConnector, BrowserRenderingError, CdpConnection, CdpSession, CodemodeRuntime, DEFAULT_EXEC_SWEEP_IDLE_MS, DEFAULT_SWEEP_IDLE_MS, DurableBrowserSessionStore, browserContent, browserExtract, browserLinks, browserMarkdown, browserPdf, browserRun, browserScrape, browserScreenshot, browserSnapshot, connectBrowser, connectBrowserSession, connectUrl, createBrowserSession, deleteBrowserSession, getBrowserRecording, listBrowserTargets, loadCdpSpec, runQuickAction };
361
+
362
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/browser/browser.ts"],"sourcesContent":["/**\n * `Browser`: one named, persistent browser for an agent or Durable Object.\n *\n * Its tabs and logins survive between agent runs. When the browser is lost\n * (closed, idle past `keep_alive`, or crashed), the next use starts a fresh\n * one and reports `restarted: true` so the model knows its earlier state is\n * gone.\n *\n * Browser Run is the only provider today; see {@link browserRun}.\n */\n\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport type { CdpConnection } from \"./cdp-connection\";\nimport {\n type BrowserBinding,\n type BrowserSessionGuardrails,\n connectBrowserSession,\n createBrowserSession,\n deleteBrowserSession,\n isMissingBrowserSession,\n listBrowserTargets\n} from \"./browser-run\";\nimport {\n createLiveView,\n type BrowserLiveView,\n type LiveViewMode\n} from \"./live-view\";\nimport {\n DurableBrowserSessionStore,\n type BrowserSessionStore,\n type StoredBrowserSession\n} from \"./session-store\";\nimport { loadCdpSpec, type SearchableCdpSpec } from \"./spec\";\n\n/**\n * Browser Run's server-side `keep_alive` maximum (600 seconds). A `Browser`\n * pins keep-alive here by default so it survives quiet spells between agent\n * turns; the platform reclaims it only after this long idle.\n */\nexport const BROWSER_SESSION_KEEP_ALIVE_MAX_MS = 600_000;\n\n/**\n * Minimum interval between store `updatedAt` refreshes driven by CDP traffic\n * on connections returned by {@link Browser.connect}.\n */\nexport const SESSION_TOUCH_INTERVAL_MS = 60_000;\n\n/** The browser name used when a host doesn't pick one. */\nexport const DEFAULT_BROWSER_NAME = \"default\";\n\nconst NAMED_SESSION_KEY_PREFIX = \"browser:session:\";\n\n/** The store key holding the named browser's current record. */\nexport function namedBrowserSessionKey(name: string): string {\n return `${NAMED_SESSION_KEY_PREFIX}${name}`;\n}\n\n/**\n * Where a closed or lost browser's record moves: permanent evidence that the\n * name once owned a browser, so a later resolve reports `restarted: true`.\n * Kept outside {@link NAMED_SESSION_KEY_PREFIX} so listing that prefix\n * yields only names that currently own a browser. Grows with the number of\n * distinct names the host has ever used.\n */\nconst RETIRED_SESSION_KEY_PREFIX = \"browser:retired:\";\n\nfunction retiredBrowserSessionKey(name: string): string {\n return `${RETIRED_SESSION_KEY_PREFIX}${name}`;\n}\n\n/**\n * Browser Run settings applied every time a browser is created — including\n * when a lost browser is replaced — so options like guardrails survive a\n * restart.\n */\nexport interface BrowserRunOptions {\n /**\n * Platform `keep_alive` in milliseconds: how long an idle browser lives.\n * Defaults to {@link BROWSER_SESSION_KEEP_ALIVE_MAX_MS} (the platform\n * maximum).\n */\n keepAliveMs?: number;\n /** Opt into Browser Run session recording (rrweb capture). */\n recording?: boolean;\n /** Hostname guardrails, fixed at launch for every connection. */\n guardrails?: BrowserSessionGuardrails;\n}\n\n/** A {@link Browser} provider backed by Cloudflare Browser Run. */\nexport interface BrowserRunProvider {\n readonly kind: \"browser-run\";\n readonly binding: BrowserBinding;\n readonly options: BrowserRunOptions;\n}\n\n/** Where a {@link Browser} gets its browser from. */\nexport type BrowserProvider = BrowserRunProvider;\n\n/**\n * Run the browser on Cloudflare Browser Run.\n *\n * @example\n * ```ts\n * new Browser({ provider: browserRun(env.BROWSER, { recording: true }) });\n * ```\n */\nexport function browserRun(\n binding: BrowserBinding,\n options: BrowserRunOptions = {}\n): BrowserRunProvider {\n return { kind: \"browser-run\", binding, options };\n}\n\nexport interface BrowserOptions {\n /** Where the browser runs. Create one with {@link browserRun}. */\n provider: BrowserProvider;\n /**\n * Which browser this is. Two `Browser` objects on the same Durable Object\n * need different names. Defaults to {@link DEFAULT_BROWSER_NAME}.\n */\n name?: string;\n /**\n * Where the browser's record lives. Defaults to a\n * {@link DurableBrowserSessionStore} over the host object's storage, which\n * requires installing the `Browser` with `Lifecycle.use()`. Passing a store\n * lets the `Browser` work without Lifecycle.\n */\n store?: BrowserSessionStore;\n /** Default CDP command timeout for {@link Browser.connect}. */\n timeoutMs?: number;\n /**\n * Minimum interval between activity-driven `updatedAt` refreshes.\n * Defaults to {@link SESSION_TOUCH_INTERVAL_MS}, capped at half the\n * keep-alive window.\n *\n * @internal For tests.\n */\n touchIntervalMs?: number;\n}\n\nexport interface ResolvedBrowser {\n name: string;\n /** The Browser Run session id — host-side only, never model-visible. */\n sessionId: string;\n /**\n * `true` when this resolution had to create a fresh browser to replace one\n * that previously existed (was closed, or expired or died upstream). Page\n * state from the prior browser is gone; surface this loudly to the model.\n * `false` only on first-ever use of the name — nothing was lost.\n */\n restarted: boolean;\n createdAt: number;\n updatedAt: number;\n /**\n * The tab the agent last worked in, when one was recorded for this\n * browser. Absent on a browser this resolution created; a replacement a\n * concurrent caller already created and used may carry one.\n */\n activeTargetId?: string;\n}\n\nexport interface BrowserConnection {\n name: string;\n sessionId: string;\n /**\n * `true` when the browser this caller resolved is gone, even if a\n * concurrent caller already replaced it. See\n * {@link ResolvedBrowser.restarted}.\n */\n restarted: boolean;\n /** See {@link ResolvedBrowser.activeTargetId}. */\n activeTargetId?: string;\n /** Closing this connection does NOT close the browser. */\n cdp: CdpConnection;\n /**\n * Record the tab the agent is working in (or clear it with `undefined`).\n * Never resurrects: returns `false` when the browser was closed or\n * replaced since this connection resolved it.\n */\n setActiveTarget(targetId: string | undefined): Promise<boolean>;\n /**\n * The Chrome DevTools Protocol description this browser serves, read from\n * the browser itself (cached per binding).\n */\n spec(): Promise<SearchableCdpSpec>;\n}\n\n/**\n * One named, persistent browser.\n *\n * Install it with `Lifecycle.use()` — on an Agent subclass or any Durable\n * Object that composes `Lifecycle` — and it stores its record in the\n * object's own storage. It schedules nothing: Browser Run reclaims idle\n * browsers itself once `keep_alive` elapses, and the next use replaces the\n * lost browser with `restarted: true`.\n *\n * Everything here is host-side. Models never see browser names, Browser Run\n * ids, or Live View URLs through this surface.\n *\n * Store discipline: locks wrap storage operations only — liveness probes and\n * Browser Run create/delete calls always happen outside any lock, with a\n * commit re-check to detect concurrent swaps.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport class Browser extends LifecycleCapability {\n /** Which browser this is; see {@link BrowserOptions.name}. */\n readonly name: string;\n readonly #options: BrowserOptions;\n readonly #key: string;\n #store?: BrowserSessionStore;\n\n constructor(options: BrowserOptions) {\n const name = options.name ?? DEFAULT_BROWSER_NAME;\n super(`browser:${name}`);\n if (name.trim() === \"\") {\n throw new Error(\"Browser names must be non-empty\");\n }\n this.name = name;\n this.#options = options;\n this.#key = namedBrowserSessionKey(name);\n }\n\n /**\n * Reattach to the browser when it is still alive, otherwise create a fresh\n * one. See {@link ResolvedBrowser.restarted} for the mortality signal.\n */\n async resolve(): Promise<ResolvedBrowser> {\n const { name } = this;\n const key = this.#key;\n\n // Dead-browser recovery consumes two attempts (retire, then create).\n for (let attempt = 0; attempt < 4; attempt++) {\n const existing = await this.#readStored();\n\n if (existing === undefined) {\n // No browser on record: create one. The commit reports whether the\n // name was ever used before (see #createAndCommit).\n const { session, restarted } = await this.#createAndCommit();\n return { name, restarted, ...session };\n }\n\n // Live entry on record — probe it outside any lock.\n const alive = await this.#isAlive(existing);\n const lock = await this.#sessionStore.acquireLock(key);\n try {\n const current = await this.#sessionStore.get(key);\n if (current?.sessionId !== existing.sessionId) {\n continue; // swapped or retired while we probed — revalidate\n }\n if (alive) {\n const refreshed = { ...current, updatedAt: Date.now() };\n await this.#sessionStore.set(key, refreshed);\n return { name, restarted: false, ...refreshed };\n }\n // The browser died upstream (expired or reclaimed). Retire the\n // record under this lock, so any resolver that reads it during the\n // replacement window sees the marker and reports restarted: true\n // too.\n await this.#retire(current);\n } finally {\n await lock.release();\n }\n // Re-enter the loop: the next attempt takes the create path.\n }\n\n throw new Error(`Browser \"${name}\" kept changing concurrently — retry`);\n }\n\n /**\n * Resolve the browser and open a CDP connection to it. Commands sent over\n * the connection refresh the record's `updatedAt` (throttled to\n * {@link SESSION_TOUCH_INTERVAL_MS}), so hosts can tell an actively used\n * browser from one the platform has likely reclaimed.\n *\n * If the browser expires between the liveness probe and the WebSocket\n * upgrade, the record is retired and the browser resolved once more, so\n * the caller gets a fresh browser with `restarted: true` instead of an\n * error.\n */\n async connect(): Promise<BrowserConnection> {\n const resolved = await this.resolve();\n try {\n return await this.#attach(resolved);\n } catch (error) {\n if (!isMissingBrowserSession(error)) throw error;\n await this.#retireIfCurrent(resolved.sessionId);\n }\n // The browser this caller resolved is gone, so report a restart even\n // when a concurrent resolver already replaced it and this resolve\n // reattaches to that replacement.\n const replaced = await this.resolve();\n return this.#attach({ ...replaced, restarted: true });\n }\n\n async #attach(resolved: ResolvedBrowser): Promise<BrowserConnection> {\n let lastTouchAt = Date.now();\n let touchInFlight = false;\n const cdp = await connectBrowserSession(\n this.#provider.binding,\n resolved.sessionId,\n {\n timeoutMs: this.#options.timeoutMs,\n onActivity: () => {\n const now = Date.now();\n if (touchInFlight || now - lastTouchAt < this.#touchIntervalMs) {\n return;\n }\n touchInFlight = true;\n lastTouchAt = now;\n void this.#touch(resolved.sessionId)\n .catch((error: unknown) => {\n console.warn(\n `[agents/browser] Failed to refresh activity for browser \"${this.name}\"`,\n error\n );\n })\n .finally(() => {\n touchInFlight = false;\n });\n }\n }\n );\n const { binding } = this.#provider;\n return {\n name: resolved.name,\n sessionId: resolved.sessionId,\n restarted: resolved.restarted,\n activeTargetId: resolved.activeTargetId,\n cdp,\n setActiveTarget: (targetId) =>\n this.#update(resolved.sessionId, (current) => ({\n ...current,\n activeTargetId: targetId,\n updatedAt: Date.now()\n })),\n spec: () =>\n loadCdpSpec({ browser: binding, sessionId: resolved.sessionId })\n };\n }\n\n /**\n * Close the browser: retire the record (so the next resolve reports\n * `restarted: true`) and delete its Browser Run session. Returns `false`\n * when there was nothing to close.\n *\n * The platform delete is best-effort: the retired record is the durable\n * outcome, and a browser whose delete failed is unreachable through this\n * store, so the pinned `keep_alive` (≤600s) reclaims it.\n */\n async close(): Promise<boolean> {\n let stored: StoredBrowserSession | undefined;\n const lock = await this.#sessionStore.acquireLock(this.#key);\n try {\n const current = await this.#sessionStore.get(this.#key);\n if (!current) return false;\n stored = current;\n await this.#retire(current);\n } finally {\n await lock.release();\n }\n try {\n await deleteBrowserSession(this.#provider.binding, stored.sessionId);\n } catch (error) {\n console.warn(\n `[agents/browser] Failed to delete closed Browser Run session ${stored.sessionId}`,\n error\n );\n }\n return true;\n }\n\n /**\n * Mint Live View URLs for the browser's open tabs, fresh from a target\n * listing. `undefined` when the browser was never created, is closed, or\n * is gone. The URLs are bearer credentials with a short connect window\n * (~5 min) — hand them to a trusted human, never to the model, and never\n * store them: re-mint instead.\n */\n async liveView(options?: {\n mode?: LiveViewMode;\n }): Promise<BrowserLiveView | undefined> {\n const entry = await this.#sessionStore.get(this.#key);\n if (!entry) return undefined;\n try {\n const targets = await listBrowserTargets(\n this.#provider.binding,\n entry.sessionId\n );\n // A human is about to look: minting counts as activity. Target\n // listing yields to the network, so a concurrent close or replacement\n // can retire the browser mid-mint — the touch and the retire\n // serialize on the key lock, and a lost touch means the listed\n // targets are already dead: report the browser gone rather than\n // minting doomed links. Touch *errors* stay best effort — a store\n // blip must not break minting.\n try {\n if (!(await this.#touch(entry.sessionId))) return undefined;\n } catch (error) {\n console.warn(\n `[agents/browser] Failed to refresh activity for browser \"${this.name}\"`,\n error\n );\n }\n return createLiveView(entry.sessionId, targets, options?.mode);\n } catch (error) {\n if (isMissingBrowserSession(error)) return undefined;\n throw error;\n }\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n get #provider(): BrowserRunProvider {\n return this.#options.provider;\n }\n\n /** The platform `keep_alive` every create applies. */\n get #keepAliveMs(): number {\n return (\n this.#provider.options.keepAliveMs ?? BROWSER_SESSION_KEEP_ALIVE_MAX_MS\n );\n }\n\n /**\n * Capped at half the keep-alive window: a browser with continuous CDP\n * traffic must never look older than the window the platform uses to\n * reclaim it.\n */\n get #touchIntervalMs(): number {\n return Math.min(\n this.#options.touchIntervalMs ?? SESSION_TOUCH_INTERVAL_MS,\n Math.floor(this.#keepAliveMs / 2)\n );\n }\n\n /** Lazy: `lifecycle.storage` exists only once `Lifecycle.use()` ran. */\n get #sessionStore(): BrowserSessionStore {\n this.#store ??=\n this.#options.store ??\n new DurableBrowserSessionStore(this.lifecycle.storage);\n return this.#store;\n }\n\n /**\n * Refresh `updatedAt` while the record still holds `sessionId`. Returns\n * false when a concurrent close or replacement already retired it.\n */\n #touch(sessionId: string): Promise<boolean> {\n return this.#update(sessionId, (current) => ({\n ...current,\n updatedAt: Date.now()\n }));\n }\n\n /**\n * Rewrite the record only while it still holds `sessionId`: a replaced or\n * retired record is never resurrected.\n */\n async #update(\n sessionId: string,\n change: (current: StoredBrowserSession) => StoredBrowserSession\n ): Promise<boolean> {\n const lock = await this.#sessionStore.acquireLock(this.#key);\n try {\n const current = await this.#sessionStore.get(this.#key);\n if (current?.sessionId !== sessionId) {\n return false; // replaced or gone — the caller's view is stale\n }\n await this.#sessionStore.set(this.#key, change(current));\n return true;\n } finally {\n await lock.release();\n }\n }\n\n /** Retire the record if it still holds `sessionId`. */\n async #retireIfCurrent(sessionId: string): Promise<void> {\n const lock = await this.#sessionStore.acquireLock(this.#key);\n try {\n const current = await this.#sessionStore.get(this.#key);\n if (current?.sessionId === sessionId) await this.#retire(current);\n } finally {\n await lock.release();\n }\n }\n\n /**\n * Move the record to its retired marker. Callers hold the key lock, so a\n * resolver always sees either the record or the marker.\n */\n async #retire(current: StoredBrowserSession): Promise<void> {\n await this.#sessionStore.set(retiredBrowserSessionKey(this.name), {\n ...current,\n closedAt: Date.now()\n });\n await this.#sessionStore.delete(this.#key);\n }\n\n /**\n * Create a Browser Run session (outside any lock) and commit it, applying\n * the provider's creation options. If a concurrent caller committed an\n * entry first, theirs wins and the redundant session is deleted\n * best-effort.\n *\n * `restarted` is read under the commit lock: a retired marker — left by\n * close() or a dead-browser recovery — is evidence a prior browser\n * existed, and only first-ever use of the name is not a restart. Markers\n * are permanent, so reading at commit time also catches a browser that\n * was created and closed while this create was in flight.\n */\n async #createAndCommit(): Promise<{\n session: StoredBrowserSession;\n restarted: boolean;\n }> {\n const { binding, options } = this.#provider;\n const info = await createBrowserSession(binding, {\n keepAliveMs: this.#keepAliveMs,\n recording: options.recording,\n guardrails: options.guardrails\n });\n const now = Date.now();\n const stored: StoredBrowserSession = {\n sessionId: info.sessionId,\n createdAt: now,\n updatedAt: now\n };\n\n let winner: StoredBrowserSession | undefined;\n let restarted: boolean;\n const lock = await this.#sessionStore.acquireLock(this.#key);\n try {\n restarted =\n (await this.#sessionStore.get(retiredBrowserSessionKey(this.name))) !==\n undefined;\n const current = await this.#sessionStore.get(this.#key);\n if (current === undefined) {\n await this.#sessionStore.set(this.#key, stored);\n } else {\n winner = current;\n }\n } finally {\n await lock.release();\n }\n\n if (winner) {\n try {\n await deleteBrowserSession(binding, stored.sessionId);\n } catch (error) {\n console.warn(\n `[agents/browser] Failed to delete redundant Browser Run session ${stored.sessionId}`,\n error\n );\n }\n return { session: winner, restarted };\n }\n return { session: stored, restarted };\n }\n\n async #isAlive(stored: StoredBrowserSession): Promise<boolean> {\n try {\n await listBrowserTargets(this.#provider.binding, stored.sessionId);\n return true;\n } catch (error) {\n if (isMissingBrowserSession(error)) return false;\n throw error;\n }\n }\n\n async #readStored(): Promise<StoredBrowserSession | undefined> {\n const lock = await this.#sessionStore.acquireLock(this.#key);\n try {\n return await this.#sessionStore.get(this.#key);\n } finally {\n await lock.release();\n }\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,92 @@
1
+ import {
2
+ a as BrowserSource,
3
+ i as BrowserNewTab,
4
+ n as BrowserToolOptions,
5
+ r as BrowserToolOutput,
6
+ t as BrowserToolInput
7
+ } from "../browser-tool-DuNdlwZ9.js";
1
8
  import { CreateBrowserToolsOptions } from "./ai.js";
2
9
  import { ServerTool } from "@tanstack/ai";
3
10
 
4
11
  //#region src/browser/tanstack-ai.d.ts
12
+ interface TanStackBrowserToolOptions<
13
+ TName extends string = "browser"
14
+ > extends BrowserToolOptions {
15
+ /** The tool's name. TanStack AI tools carry it in the definition. */
16
+ name?: TName;
17
+ }
18
+ /**
19
+ * Create a TanStack AI tool that lets the model drive a persistent browser
20
+ * with JavaScript and the Chrome DevTools Protocol.
21
+ *
22
+ * Works like `browserTool` in `agents/browser/ai-sdk`: tabs, cookies, and
23
+ * logins carry over between runs, `sessionId: "active"` addresses the tab the
24
+ * model last worked in, and a replaced browser is reported as
25
+ * `restarted: true`. The tool is named `browser` unless you pass `name`.
26
+ *
27
+ * Unlike the AI SDK tool, the host gets the same output as the model, and
28
+ * screenshots aren't supported: a returned screenshot is replaced by a
29
+ * sentence saying it was left out.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { Browser, browserRun } from "agents/browser";
34
+ * import { browserTool } from "agents/browser/tanstack-ai";
35
+ * import { chat } from "@tanstack/ai";
36
+ *
37
+ * export class MyAgent extends Agent<Env> {
38
+ * browser = new Browser({ provider: browserRun(this.env.BROWSER) });
39
+ *
40
+ * constructor(ctx: AgentContext, env: Env) {
41
+ * super(ctx, env);
42
+ * this.lifecycle.use(this.browser);
43
+ * }
44
+ *
45
+ * async onChatMessage() {
46
+ * const stream = chat({
47
+ * adapter,
48
+ * tools: [browserTool({ browser: this.browser, loader: this.env.LOADER })],
49
+ * messages
50
+ * });
51
+ * }
52
+ * }
53
+ * ```
54
+ */
55
+ declare function browserTool<TName extends string = "browser">(
56
+ options: TanStackBrowserToolOptions<TName>
57
+ ): ServerTool<
58
+ {
59
+ readonly "~standard": {
60
+ readonly version: 1;
61
+ readonly vendor: "@cloudflare/codemode";
62
+ readonly types?: {
63
+ readonly input: import("@cloudflare/codemode").ProxyToolInput;
64
+ readonly output: import("@cloudflare/codemode").ProxyToolInput;
65
+ };
66
+ readonly validate: (value: unknown) =>
67
+ | {
68
+ readonly value: import("@cloudflare/codemode").ProxyToolInput;
69
+ }
70
+ | {
71
+ readonly issues: ReadonlyArray<{
72
+ readonly message: string;
73
+ readonly path?: ReadonlyArray<PropertyKey>;
74
+ }>;
75
+ };
76
+ readonly jsonSchema: {
77
+ readonly input: (options: {
78
+ readonly target: string;
79
+ }) => Record<string, unknown>;
80
+ readonly output: (options: {
81
+ readonly target: string;
82
+ }) => Record<string, unknown>;
83
+ };
84
+ };
85
+ },
86
+ import("@tanstack/ai").SchemaInput,
87
+ TName,
88
+ unknown
89
+ >;
5
90
  /**
6
91
  * Create TanStack AI tools for browser automation via CDP code mode.
7
92
  *
@@ -37,5 +122,15 @@ declare function createBrowserTools(
37
122
  options: CreateBrowserToolsOptions
38
123
  ): ServerTool[];
39
124
  //#endregion
40
- export { type CreateBrowserToolsOptions, createBrowserTools };
125
+ export {
126
+ type BrowserNewTab,
127
+ type BrowserSource,
128
+ type BrowserToolInput,
129
+ type BrowserToolOptions,
130
+ type BrowserToolOutput,
131
+ type CreateBrowserToolsOptions,
132
+ TanStackBrowserToolOptions,
133
+ browserTool,
134
+ createBrowserTools
135
+ };
41
136
  //# sourceMappingURL=tanstack-ai.d.ts.map
@@ -1,8 +1,68 @@
1
- import { createBrowserRuntime } from "./ai.js";
1
+ import { n as createBrowserToolCore } from "../browser-tool-CQ10e6Kj.js";
2
+ import { a as browserScreenshotOutput, i as browserExecuteModelOutput, t as createBrowserRuntime } from "../ai-fm7xx1BK.js";
2
3
  import { z } from "zod";
3
4
  import { toolDefinition } from "@tanstack/ai";
4
5
  //#region src/browser/tanstack-ai.ts
5
6
  /**
7
+ * What the model sees from one run. TanStack AI has one return channel, so
8
+ * the host gets this too: no `calls` log, bounded `logs`, and a screenshot
9
+ * replaced by a sentence saying it was left out.
10
+ */
11
+ function browserToolModelResult(output) {
12
+ const screenshot = browserScreenshotOutput(output);
13
+ if (!screenshot) return browserExecuteModelOutput(output).value;
14
+ const bytes = Math.floor(screenshot.data.length * 3 / 4);
15
+ return browserExecuteModelOutput({
16
+ ...output,
17
+ result: `Screenshot captured (${screenshot.mediaType}, approximately ${bytes.toLocaleString()} bytes), but this tool can't return images, so neither you nor the user can see it. Read the page with Runtime.evaluate instead.`
18
+ }).value;
19
+ }
20
+ /**
21
+ * Create a TanStack AI tool that lets the model drive a persistent browser
22
+ * with JavaScript and the Chrome DevTools Protocol.
23
+ *
24
+ * Works like `browserTool` in `agents/browser/ai-sdk`: tabs, cookies, and
25
+ * logins carry over between runs, `sessionId: "active"` addresses the tab the
26
+ * model last worked in, and a replaced browser is reported as
27
+ * `restarted: true`. The tool is named `browser` unless you pass `name`.
28
+ *
29
+ * Unlike the AI SDK tool, the host gets the same output as the model, and
30
+ * screenshots aren't supported: a returned screenshot is replaced by a
31
+ * sentence saying it was left out.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { Browser, browserRun } from "agents/browser";
36
+ * import { browserTool } from "agents/browser/tanstack-ai";
37
+ * import { chat } from "@tanstack/ai";
38
+ *
39
+ * export class MyAgent extends Agent<Env> {
40
+ * browser = new Browser({ provider: browserRun(this.env.BROWSER) });
41
+ *
42
+ * constructor(ctx: AgentContext, env: Env) {
43
+ * super(ctx, env);
44
+ * this.lifecycle.use(this.browser);
45
+ * }
46
+ *
47
+ * async onChatMessage() {
48
+ * const stream = chat({
49
+ * adapter,
50
+ * tools: [browserTool({ browser: this.browser, loader: this.env.LOADER })],
51
+ * messages
52
+ * });
53
+ * }
54
+ * }
55
+ * ```
56
+ */
57
+ function browserTool(options) {
58
+ const core = createBrowserToolCore(options, { screenshotHint: "This tool can't return images, so don't take screenshots; read the page with Runtime.evaluate (for example document.body.innerText) instead." });
59
+ return toolDefinition({
60
+ name: options.name ?? "browser",
61
+ description: core.description,
62
+ inputSchema: core.inputSchema
63
+ }).server(async (input) => browserToolModelResult(await core.execute(input)));
64
+ }
65
+ /**
6
66
  * Create TanStack AI tools for browser automation via CDP code mode.
7
67
  *
8
68
  * Returns an array with a single durable `browser_execute` `ServerTool`
@@ -65,6 +125,6 @@ function createBrowserTools(options) {
65
125
  })];
66
126
  }
67
127
  //#endregion
68
- export { createBrowserTools };
128
+ export { browserTool, createBrowserTools };
69
129
 
70
130
  //# sourceMappingURL=tanstack-ai.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-ai.js","names":[],"sources":["../../src/browser/tanstack-ai.ts"],"sourcesContent":["import { toolDefinition } from \"@tanstack/ai\";\nimport type { ServerTool } from \"@tanstack/ai\";\nimport type { ProxyToolOutput } from \"@cloudflare/codemode\";\nimport { z } from \"zod\";\nimport { createBrowserRuntime, type CreateBrowserToolsOptions } from \"./ai\";\n\nexport type { CreateBrowserToolsOptions } from \"./ai\";\n\n/**\n * Create TanStack AI tools for browser automation via CDP code mode.\n *\n * Returns an array with a single durable `browser_execute` `ServerTool`\n * backed by the same codemode runtime as `agents/browser/ai` — the model\n * writes TypeScript against the `cdp` connector and browser sessions\n * survive pauses.\n *\n * The stateless Quick Action tools are not surfaced through this TanStack\n * wrapper (it exposes only `browser_execute`); use `createQuickActionTools`\n * from `agents/browser/ai` if you want them.\n *\n * @example\n * ```ts\n * import { createBrowserTools } from \"agents/browser/tanstack-ai\";\n * import { chat } from \"@tanstack/ai\";\n *\n * // inside a Durable Object / Agent:\n * const browserTools = createBrowserTools({\n * ctx: this.ctx,\n * browser: this.env.BROWSER,\n * loader: this.env.LOADER,\n * });\n *\n * const stream = chat({\n * adapter: openaiText(\"gpt-4o\"),\n * tools: [...browserTools, ...otherTools],\n * messages,\n * });\n * ```\n */\nexport function createBrowserTools(\n options: CreateBrowserToolsOptions\n): ServerTool[] {\n // This wrapper only surfaces `browser_execute`, so don't build the default-on\n // Quick Action tools just to discard them.\n const { tools } = createBrowserRuntime({ ...options, quickActions: false });\n const executeTool = tools.browser_execute;\n\n const execute = toolDefinition({\n name: \"browser_execute\" as const,\n description:\n typeof executeTool.description === \"function\"\n ? \"\"\n : (executeTool.description ?? \"\"),\n inputSchema: z.object({\n code: z.string().meta({\n description:\n \"TypeScript async arrow function that uses the cdp connector\"\n })\n })\n }).server(async ({ code }) => {\n if (!executeTool.execute) {\n throw new Error(\"browser_execute tool is not executable\");\n }\n const result = (await executeTool.execute({ code }, {\n toolCallId: crypto.randomUUID(),\n messages: [],\n context: {}\n } as never)) as ProxyToolOutput;\n // TanStack has a single return channel, so what the host sees is what the\n // model sees: apply the AI SDK path's `toModelOutput` projection so a\n // screenshot's base64 cannot reach the model and the durable `calls` log\n // stays out of its context. The execution envelope (status, executionId)\n // is preserved either way.\n const modelOutput = await executeTool.toModelOutput?.({\n toolCallId: crypto.randomUUID(),\n input: { code },\n output: result\n });\n if (modelOutput?.type === \"error-text\") {\n throw new Error(modelOutput.value);\n }\n if (modelOutput?.type === \"text\") {\n return { ...result, result: modelOutput.value };\n }\n if (modelOutput?.type === \"json\") {\n return modelOutput.value;\n }\n return result;\n });\n\n return [execute];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,mBACd,SACc;CAGd,MAAM,EAAE,UAAU,qBAAqB;EAAE,GAAG;EAAS,cAAc;CAAM,CAAC;CAC1E,MAAM,cAAc,MAAM;CA6C1B,OAAO,CA3CS,eAAe;EAC7B,MAAM;EACN,aACE,OAAO,YAAY,gBAAgB,aAC/B,KACC,YAAY,eAAe;EAClC,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,EACpB,aACE,8DACJ,CAAC,EACH,CAAC;CACH,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,wCAAwC;EAE1D,MAAM,SAAU,MAAM,YAAY,QAAQ,EAAE,KAAK,GAAG;GAClD,YAAY,OAAO,WAAW;GAC9B,UAAU,CAAC;GACX,SAAS,CAAC;EACZ,CAAU;EAMV,MAAM,cAAc,MAAM,YAAY,gBAAgB;GACpD,YAAY,OAAO,WAAW;GAC9B,OAAO,EAAE,KAAK;GACd,QAAQ;EACV,CAAC;EACD,IAAI,aAAa,SAAS,cACxB,MAAM,IAAI,MAAM,YAAY,KAAK;EAEnC,IAAI,aAAa,SAAS,QACxB,OAAO;GAAE,GAAG;GAAQ,QAAQ,YAAY;EAAM;EAEhD,IAAI,aAAa,SAAS,QACxB,OAAO,YAAY;EAErB,OAAO;CACT,CAEc,CAAC;AACjB"}
1
+ {"version":3,"file":"tanstack-ai.js","names":[],"sources":["../../src/browser/tanstack-ai.ts"],"sourcesContent":["import { toolDefinition } from \"@tanstack/ai\";\nimport type { ServerTool } from \"@tanstack/ai\";\nimport type { ProxyToolOutput } from \"@cloudflare/codemode\";\nimport { z } from \"zod\";\nimport { createBrowserRuntime, type CreateBrowserToolsOptions } from \"./ai\";\nimport {\n createBrowserToolCore,\n type BrowserToolOptions,\n type BrowserToolOutput\n} from \"./browser-tool\";\nimport {\n browserExecuteModelOutput,\n browserScreenshotOutput\n} from \"./tool-helpers\";\n\nexport type { CreateBrowserToolsOptions } from \"./ai\";\nexport type {\n BrowserToolInput,\n BrowserToolOptions,\n BrowserToolOutput\n} from \"./browser-tool\";\nexport type { BrowserNewTab, BrowserSource } from \"./session-connector\";\n\nexport interface TanStackBrowserToolOptions<\n TName extends string = \"browser\"\n> extends BrowserToolOptions {\n /** The tool's name. TanStack AI tools carry it in the definition. */\n name?: TName;\n}\n\n/**\n * What the model sees from one run. TanStack AI has one return channel, so\n * the host gets this too: no `calls` log, bounded `logs`, and a screenshot\n * replaced by a sentence saying it was left out.\n */\nfunction browserToolModelResult(output: BrowserToolOutput): unknown {\n const screenshot = browserScreenshotOutput(output);\n if (!screenshot) return browserExecuteModelOutput(output).value;\n const bytes = Math.floor((screenshot.data.length * 3) / 4);\n // Keep the rest of the result (status, restarted, notice, newTabs).\n return browserExecuteModelOutput({\n ...output,\n result: `Screenshot captured (${screenshot.mediaType}, approximately ${bytes.toLocaleString()} bytes), but this tool can't return images, so neither you nor the user can see it. Read the page with Runtime.evaluate instead.`\n }).value;\n}\n\n/**\n * Create a TanStack AI tool that lets the model drive a persistent browser\n * with JavaScript and the Chrome DevTools Protocol.\n *\n * Works like `browserTool` in `agents/browser/ai-sdk`: tabs, cookies, and\n * logins carry over between runs, `sessionId: \"active\"` addresses the tab the\n * model last worked in, and a replaced browser is reported as\n * `restarted: true`. The tool is named `browser` unless you pass `name`.\n *\n * Unlike the AI SDK tool, the host gets the same output as the model, and\n * screenshots aren't supported: a returned screenshot is replaced by a\n * sentence saying it was left out.\n *\n * @example\n * ```ts\n * import { Browser, browserRun } from \"agents/browser\";\n * import { browserTool } from \"agents/browser/tanstack-ai\";\n * import { chat } from \"@tanstack/ai\";\n *\n * export class MyAgent extends Agent<Env> {\n * browser = new Browser({ provider: browserRun(this.env.BROWSER) });\n *\n * constructor(ctx: AgentContext, env: Env) {\n * super(ctx, env);\n * this.lifecycle.use(this.browser);\n * }\n *\n * async onChatMessage() {\n * const stream = chat({\n * adapter,\n * tools: [browserTool({ browser: this.browser, loader: this.env.LOADER })],\n * messages\n * });\n * }\n * }\n * ```\n */\nexport function browserTool<TName extends string = \"browser\">(\n options: TanStackBrowserToolOptions<TName>\n) {\n const core = createBrowserToolCore(options, {\n screenshotHint:\n \"This tool can't return images, so don't take screenshots; read the page with Runtime.evaluate (for example document.body.innerText) instead.\"\n });\n return toolDefinition({\n name: options.name ?? (\"browser\" as TName),\n description: core.description,\n inputSchema: core.inputSchema\n }).server(async (input) => browserToolModelResult(await core.execute(input)));\n}\n\n/**\n * Create TanStack AI tools for browser automation via CDP code mode.\n *\n * Returns an array with a single durable `browser_execute` `ServerTool`\n * backed by the same codemode runtime as `agents/browser/ai` — the model\n * writes TypeScript against the `cdp` connector and browser sessions\n * survive pauses.\n *\n * The stateless Quick Action tools are not surfaced through this TanStack\n * wrapper (it exposes only `browser_execute`); use `createQuickActionTools`\n * from `agents/browser/ai` if you want them.\n *\n * @example\n * ```ts\n * import { createBrowserTools } from \"agents/browser/tanstack-ai\";\n * import { chat } from \"@tanstack/ai\";\n *\n * // inside a Durable Object / Agent:\n * const browserTools = createBrowserTools({\n * ctx: this.ctx,\n * browser: this.env.BROWSER,\n * loader: this.env.LOADER,\n * });\n *\n * const stream = chat({\n * adapter: openaiText(\"gpt-4o\"),\n * tools: [...browserTools, ...otherTools],\n * messages,\n * });\n * ```\n */\nexport function createBrowserTools(\n options: CreateBrowserToolsOptions\n): ServerTool[] {\n // This wrapper only surfaces `browser_execute`, so don't build the default-on\n // Quick Action tools just to discard them.\n const { tools } = createBrowserRuntime({ ...options, quickActions: false });\n const executeTool = tools.browser_execute;\n\n const execute = toolDefinition({\n name: \"browser_execute\" as const,\n description:\n typeof executeTool.description === \"function\"\n ? \"\"\n : (executeTool.description ?? \"\"),\n inputSchema: z.object({\n code: z.string().meta({\n description:\n \"TypeScript async arrow function that uses the cdp connector\"\n })\n })\n }).server(async ({ code }) => {\n if (!executeTool.execute) {\n throw new Error(\"browser_execute tool is not executable\");\n }\n const result = (await executeTool.execute({ code }, {\n toolCallId: crypto.randomUUID(),\n messages: [],\n context: {}\n } as never)) as ProxyToolOutput;\n // TanStack has a single return channel, so what the host sees is what the\n // model sees: apply the AI SDK path's `toModelOutput` projection so a\n // screenshot's base64 cannot reach the model and the durable `calls` log\n // stays out of its context. The execution envelope (status, executionId)\n // is preserved either way.\n const modelOutput = await executeTool.toModelOutput?.({\n toolCallId: crypto.randomUUID(),\n input: { code },\n output: result\n });\n if (modelOutput?.type === \"error-text\") {\n throw new Error(modelOutput.value);\n }\n if (modelOutput?.type === \"text\") {\n return { ...result, result: modelOutput.value };\n }\n if (modelOutput?.type === \"json\") {\n return modelOutput.value;\n }\n return result;\n });\n\n return [execute];\n}\n"],"mappings":";;;;;;;;;;AAmCA,SAAS,uBAAuB,QAAoC;CAClE,MAAM,aAAa,wBAAwB,MAAM;CACjD,IAAI,CAAC,YAAY,OAAO,0BAA0B,MAAM,CAAC,CAAC;CAC1D,MAAM,QAAQ,KAAK,MAAO,WAAW,KAAK,SAAS,IAAK,CAAC;CAEzD,OAAO,0BAA0B;EAC/B,GAAG;EACH,QAAQ,wBAAwB,WAAW,UAAU,kBAAkB,MAAM,eAAe,EAAE;CAChG,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,YACd,SACA;CACA,MAAM,OAAO,sBAAsB,SAAS,EAC1C,gBACE,+IACJ,CAAC;CACD,OAAO,eAAe;EACpB,MAAM,QAAQ,QAAS;EACvB,aAAa,KAAK;EAClB,aAAa,KAAK;CACpB,CAAC,CAAC,CAAC,OAAO,OAAO,UAAU,uBAAuB,MAAM,KAAK,QAAQ,KAAK,CAAC,CAAC;AAC9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,mBACd,SACc;CAGd,MAAM,EAAE,UAAU,qBAAqB;EAAE,GAAG;EAAS,cAAc;CAAM,CAAC;CAC1E,MAAM,cAAc,MAAM;CA6C1B,OAAO,CA3CS,eAAe;EAC7B,MAAM;EACN,aACE,OAAO,YAAY,gBAAgB,aAC/B,KACC,YAAY,eAAe;EAClC,aAAa,EAAE,OAAO,EACpB,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,EACpB,aACE,8DACJ,CAAC,EACH,CAAC;CACH,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,WAAW;EAC5B,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,wCAAwC;EAE1D,MAAM,SAAU,MAAM,YAAY,QAAQ,EAAE,KAAK,GAAG;GAClD,YAAY,OAAO,WAAW;GAC9B,UAAU,CAAC;GACX,SAAS,CAAC;EACZ,CAAU;EAMV,MAAM,cAAc,MAAM,YAAY,gBAAgB;GACpD,YAAY,OAAO,WAAW;GAC9B,OAAO,EAAE,KAAK;GACd,QAAQ;EACV,CAAC;EACD,IAAI,aAAa,SAAS,cACxB,MAAM,IAAI,MAAM,YAAY,KAAK;EAEnC,IAAI,aAAa,SAAS,QACxB,OAAO;GAAE,GAAG;GAAQ,QAAQ,YAAY;EAAM;EAEhD,IAAI,aAAa,SAAS,QACxB,OAAO,YAAY;EAErB,OAAO;CACT,CAEc,CAAC;AACjB"}