agents 0.28.1 → 0.29.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 (112) hide show
  1. package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-ohoewW-B.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +1 -1
  6. package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
  7. package/dist/{ai-sdk-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
  8. package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
  9. package/dist/browser/ai.d.ts +1 -1
  10. package/dist/browser/ai.js +2 -2
  11. package/dist/browser/index.d.ts +8 -8
  12. package/dist/browser/index.js +3 -3
  13. package/dist/browser/tools/ai-sdk.d.ts +2 -2
  14. package/dist/browser/tools/ai-sdk.js +1 -1
  15. package/dist/browser/tools/pi.d.ts +1 -1
  16. package/dist/browser/tools/pi.js +3 -3
  17. package/dist/browser/tools/pi.js.map +1 -1
  18. package/dist/browser/tools/tanstack-ai.d.ts +2 -2
  19. package/dist/browser/tools/tanstack-ai.js +1 -1
  20. package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
  21. package/dist/browser-DccPw_wW.js.map +1 -0
  22. package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
  23. package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
  24. package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
  25. package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
  26. package/dist/chat/index.d.ts +143 -71
  27. package/dist/chat/index.js +269 -110
  28. package/dist/chat/index.js.map +1 -1
  29. package/dist/chat-sdk/index.d.ts +1 -1
  30. package/dist/chat-sdk/index.js +1 -1
  31. package/dist/cli.js +33 -33
  32. package/dist/{client-Bp-JwL3p.d.ts → client-C27CtRZr.d.ts} +2 -2
  33. package/dist/client.d.ts +1 -1
  34. package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
  35. package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
  36. package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
  37. package/dist/experimental/channels/web/index.js +1 -1
  38. package/dist/harness/container/index.js +1 -1
  39. package/dist/harness/container/index.js.map +1 -1
  40. package/dist/harness/opencode/index.js +1 -1
  41. package/dist/harness/pi/index.d.ts +13 -5
  42. package/dist/harness/pi/index.js +8 -214
  43. package/dist/harness/pi/index.js.map +1 -1
  44. package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
  45. package/dist/images-xaDv2Fqe.js.map +1 -0
  46. package/dist/index-DCxk3OSJ.d.ts +378 -0
  47. package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
  51. package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
  52. package/dist/lifecycle/index.js +1 -1
  53. package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
  54. package/dist/lifecycle-ClT4DmF2.js.map +1 -0
  55. package/dist/mcp/client/index.d.ts +1 -1
  56. package/dist/mcp/index.d.ts +1 -1
  57. package/dist/mcp/index.js +1 -1
  58. package/dist/models/ai-sdk/index.d.ts +29 -352
  59. package/dist/models/ai-sdk/index.js +1 -1
  60. package/dist/models/opencode/index.d.ts +25 -13
  61. package/dist/models/opencode/index.js +16 -11
  62. package/dist/models/opencode/index.js.map +1 -1
  63. package/dist/models/pi-ai/index.d.ts +13 -14
  64. package/dist/models/pi-ai/index.js +85 -32
  65. package/dist/models/pi-ai/index.js.map +1 -1
  66. package/dist/react.d.ts +2 -2
  67. package/dist/routing/index.d.ts +1 -1
  68. package/dist/serializable.d.ts +1 -1
  69. package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
  70. package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
  71. package/dist/session-store-yvwq1f9x.js.map +1 -0
  72. package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
  73. package/dist/settings-Dk2il2Hf.js.map +1 -0
  74. package/dist/skills/index.d.ts +22 -28
  75. package/dist/skills/index.js +183 -143
  76. package/dist/skills/index.js.map +1 -1
  77. package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
  78. package/dist/src-6lr7MtM_.js.map +1 -0
  79. package/dist/sub-routing.d.ts +1 -1
  80. package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
  81. package/dist/tool-DkT_DJBm.js.map +1 -0
  82. package/dist/transport-CPh7YGiw.d.ts +623 -0
  83. package/dist/voice/index.d.ts +1 -1
  84. package/dist/web/fetch/tools/pi.js +2 -2
  85. package/dist/web/fetch/tools/pi.js.map +1 -1
  86. package/dist/web/search/index.js +1 -1
  87. package/dist/web/search/tools/ai-sdk.js +2 -2
  88. package/dist/web/search/tools/pi.js +3 -3
  89. package/dist/web/search/tools/pi.js.map +1 -1
  90. package/dist/web/search/tools/tanstack-ai.js +2 -2
  91. package/dist/websockets/index.js +1 -1
  92. package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
  93. package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
  94. package/dist/workflow-types.d.ts +1 -1
  95. package/dist/workflows.d.ts +1 -1
  96. package/dist/workflows.js +1 -1
  97. package/docs/browse-the-web.md +1 -1
  98. package/docs/chat-agents.md +19 -18
  99. package/docs/harnesses/pi.md +47 -18
  100. package/docs/models-pi-ai.md +62 -20
  101. package/docs/models.md +197 -172
  102. package/package.json +7 -7
  103. package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
  104. package/dist/browser-CEdNlsaD.js.map +0 -1
  105. package/dist/images-C4hiIexM.js.map +0 -1
  106. package/dist/lifecycle-DGorpAmA.js.map +0 -1
  107. package/dist/session-store-Do4xkDcf.js.map +0 -1
  108. package/dist/settings-BjAiPd6Z.js.map +0 -1
  109. package/dist/settings-DnD__xuE.d.ts +0 -349
  110. package/dist/src-CQxJFv4l.js.map +0 -1
  111. package/dist/tool-VF0qiO7q.js.map +0 -1
  112. package/dist/transport-DCyarnwm.d.ts +0 -200
@@ -1,5 +1,5 @@
1
1
  import { n as browserScreenshotOutput, t as browserExecuteModelOutput } from "../../tool-helpers-O5BDVEAY.js";
2
- import { n as createBrowserToolCore } from "../../browser-tool-4iomavs_.js";
2
+ import { n as createBrowserToolCore } from "../../browser-tool-B1mhifin.js";
3
3
  import { Type } from "@earendil-works/pi-ai";
4
4
  //#region src/browser/tools/pi.ts
5
5
  const browserToolParameters = Type.Object({ code: Type.String({ description: "An async arrow function that drives the browser with `cdp`" }) });
@@ -30,7 +30,7 @@ function browserToolResult(output) {
30
30
  const isError = output.status === "error";
31
31
  const screenshot = browserScreenshotOutput(output);
32
32
  if (!screenshot) return {
33
- content: [text(browserExecuteModelOutput(output).value)],
33
+ output: [text(browserExecuteModelOutput(output).value)],
34
34
  details: details(output),
35
35
  ...isError ? { isError } : {}
36
36
  };
@@ -45,7 +45,7 @@ function browserToolResult(output) {
45
45
  mimeType: screenshot.mediaType
46
46
  };
47
47
  return {
48
- content: [text(summary), image],
48
+ output: [text(summary), image],
49
49
  details: details(output)
50
50
  };
51
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pi.js","names":[],"sources":["../../../src/browser/tools/pi.ts"],"sourcesContent":["import {\n Type,\n type ImageContent,\n type TextContent\n} from \"@earendil-works/pi-ai\";\nimport type {\n ToolExecutionApi,\n ToolExecutionResult,\n ToolRegistration\n} from \"@earendil-works/pi-durable\";\nimport {\n createBrowserToolCore,\n type BrowserToolOptions,\n type BrowserToolOutput\n} from \"../browser-tool\";\nimport type { BrowserNewTab } from \"../session-connector\";\nimport {\n browserExecuteModelOutput,\n browserScreenshotOutput\n} from \"../tool-helpers\";\n\nexport type {\n BrowserToolInput,\n BrowserToolOptions,\n BrowserToolOutput\n} from \"../browser-tool\";\nexport type { BrowserNewTab, BrowserSource } from \"../session-connector\";\n\nexport interface PiBrowserToolOptions<\n TName extends string = \"browser\"\n> extends Omit<BrowserToolOptions, \"scope\"> {\n /** The tool's name. Default `\"browser\"`. */\n name?: TName;\n /**\n * Which calls share an active tab: a string for every call, or a function\n * of the call. Defaults to `conversation:<id>` for the calling\n * conversation, so each\n * conversation (and each fork or subagent) on the Durable Object keeps its\n * own tab, while cookies and logins stay shared.\n */\n scope?: string | ((api: ToolExecutionApi) => string);\n}\n\nconst browserToolParameters = Type.Object({\n code: Type.String({\n description: \"An async arrow function that drives the browser with `cdp`\"\n })\n});\n\n/**\n * What a UI can show about a run without reading the content: the run's\n * outcome and what happened to the browser.\n */\nexport type PiBrowserToolDetails = {\n executionId: string;\n status: BrowserToolOutput[\"status\"];\n restarted?: true;\n newTabs?: { targetId: string; url?: string; title?: string }[];\n};\n\nexport type PiBrowserTool<TName extends string = \"browser\"> = ToolRegistration<\n typeof browserToolParameters,\n PiBrowserToolDetails\n> & {\n readonly name: TName;\n};\n\nfunction details(output: BrowserToolOutput): PiBrowserToolDetails {\n return {\n executionId: output.executionId,\n status: output.status,\n ...(output.restarted ? { restarted: true as const } : {}),\n ...(output.newTabs\n ? {\n newTabs: output.newTabs.map((tab: BrowserNewTab) => ({\n targetId: tab.targetId,\n ...(tab.url === undefined ? {} : { url: tab.url }),\n ...(tab.title === undefined ? {} : { title: tab.title })\n }))\n }\n : {})\n };\n}\n\nfunction text(value: unknown): TextContent {\n return {\n type: \"text\",\n text: typeof value === \"string\" ? value : JSON.stringify(value)\n };\n}\n\n/**\n * The tool result pi stores and sends: the model projection of the run as\n * JSON (no `calls` log, bounded `logs`), and a screenshot as an image part\n * after it. pi-ai only sends the image to models that accept images.\n */\nfunction browserToolResult(\n output: BrowserToolOutput\n): ToolExecutionResult<PiBrowserToolDetails> {\n const isError = output.status === \"error\";\n const screenshot = browserScreenshotOutput(output);\n if (!screenshot) {\n return {\n content: [text(browserExecuteModelOutput(output).value)],\n details: details(output),\n ...(isError ? { isError } : {})\n };\n }\n\n // No size check: a screenshot comes from one cdp.send result, which\n // codemode caps at 1 MB, well under what model providers accept.\n const bytes = Math.floor((screenshot.data.length * 3) / 4).toLocaleString();\n // Keep the rest of the result (status, restarted, notice, newTabs) around\n // the sentence that replaces the screenshot.\n const summary = browserExecuteModelOutput({\n ...output,\n result: `Screenshot attached as an image (${screenshot.mediaType}, approximately ${bytes} bytes). If you can't see it, read the page with Runtime.evaluate instead.`\n }).value;\n const image: ImageContent = {\n type: \"image\",\n data: screenshot.data,\n mimeType: screenshot.mediaType\n };\n return {\n content: [text(summary), image],\n details: details(output)\n };\n}\n\nfunction conversationScope(api: ToolExecutionApi): string {\n return `conversation:${api.conversationId}`;\n}\n\n/**\n * Create a pi-durable 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`. A returned screenshot comes back as an image part that\n * the model sees, when its model accepts images.\n *\n * Each conversation gets its own active tab (see\n * {@link PiBrowserToolOptions.scope}). A conversation's calls run one at a\n * time, since they share its tab, and the tool doesn't rerun after an\n * eviction (its code may have clicked or\n * submitted something): pi gives the model an interrupted result instead.\n * Pass `ctx` unless the tool is built inside an Agent.\n *\n * @example\n * ```ts\n * import { Browser, browserRun } from \"agents/browser\";\n * import { browserTool } from \"agents/browser/pi\";\n *\n * export class MyAgent extends DurableObject<Env> {\n * readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) });\n * readonly registry = createRegistry();\n * readonly harness = new PiHarness({\n * harness: ({ storage, context }) => {\n * this.registry.install({\n * name: \"browser\",\n * tools: [\n * browserTool({\n * ctx: this.ctx,\n * browser: this.browser,\n * loader: this.env.LOADER\n * })\n * ]\n * });\n * return Harness.open(storage, { models, registry: this.registry }, context);\n * }\n * });\n * readonly lifecycle = Lifecycle.install(this)\n * .use(this.browser)\n * .use(this.harness);\n * }\n * ```\n */\nexport function browserTool<TName extends string = \"browser\">(\n options: PiBrowserToolOptions<TName>\n): PiBrowserTool<TName> {\n const { scope = conversationScope, ...coreOptions } = options;\n const core = createBrowserToolCore(coreOptions, {\n screenshotHint:\n \"To see a screenshot, return { type: 'browser_screenshot', mediaType, data } with data from Page.captureScreenshot and mediaType 'image/png', or 'image/jpeg' if you captured with format: 'jpeg'. The image is attached to the result.\"\n });\n return {\n name: options.name ?? (\"browser\" as TName),\n description: core.description,\n parameters: browserToolParameters,\n // A conversation's calls share its tab, so they must not interleave.\n executionMode: \"sequential\",\n // The code may have clicked or submitted something; don't run it twice.\n replay: \"unsafe\",\n async execute(args, api) {\n const callScope = typeof scope === \"string\" ? scope : scope(api);\n return browserToolResult(\n await core.execute({ code: args.code }, callScope)\n );\n }\n };\n}\n"],"mappings":";;;;AA2CA,MAAM,wBAAwB,KAAK,OAAO,EACxC,MAAM,KAAK,OAAO,EAChB,aAAa,6DACf,CAAC,EACH,CAAC;AAoBD,SAAS,QAAQ,QAAiD;CAChE,OAAO;EACL,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,GAAI,OAAO,YAAY,EAAE,WAAW,KAAc,IAAI,CAAC;EACvD,GAAI,OAAO,UACP,EACE,SAAS,OAAO,QAAQ,KAAK,SAAwB;GACnD,UAAU,IAAI;GACd,GAAI,IAAI,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI;GAChD,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;EACxD,EAAE,EACJ,IACA,CAAC;CACP;AACF;AAEA,SAAS,KAAK,OAA6B;CACzC,OAAO;EACL,MAAM;EACN,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;CAChE;AACF;;;;;;AAOA,SAAS,kBACP,QAC2C;CAC3C,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,aAAa,wBAAwB,MAAM;CACjD,IAAI,CAAC,YACH,OAAO;EACL,SAAS,CAAC,KAAK,0BAA0B,MAAM,CAAC,CAAC,KAAK,CAAC;EACvD,SAAS,QAAQ,MAAM;EACvB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAC/B;CAKF,MAAM,QAAQ,KAAK,MAAO,WAAW,KAAK,SAAS,IAAK,CAAC,CAAC,CAAC,eAAe;CAG1E,MAAM,UAAU,0BAA0B;EACxC,GAAG;EACH,QAAQ,oCAAoC,WAAW,UAAU,kBAAkB,MAAM;CAC3F,CAAC,CAAC,CAAC;CACH,MAAM,QAAsB;EAC1B,MAAM;EACN,MAAM,WAAW;EACjB,UAAU,WAAW;CACvB;CACA,OAAO;EACL,SAAS,CAAC,KAAK,OAAO,GAAG,KAAK;EAC9B,SAAS,QAAQ,MAAM;CACzB;AACF;AAEA,SAAS,kBAAkB,KAA+B;CACxD,OAAO,gBAAgB,IAAI;AAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,YACd,SACsB;CACtB,MAAM,EAAE,QAAQ,mBAAmB,GAAG,gBAAgB;CACtD,MAAM,OAAO,sBAAsB,aAAa,EAC9C,gBACE,yOACJ,CAAC;CACD,OAAO;EACL,MAAM,QAAQ,QAAS;EACvB,aAAa,KAAK;EAClB,YAAY;EAEZ,eAAe;EAEf,QAAQ;EACR,MAAM,QAAQ,MAAM,KAAK;GACvB,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,MAAM,GAAG;GAC/D,OAAO,kBACL,MAAM,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CACnD;EACF;CACF;AACF"}
1
+ {"version":3,"file":"pi.js","names":[],"sources":["../../../src/browser/tools/pi.ts"],"sourcesContent":["import {\n Type,\n type ImageContent,\n type TextContent\n} from \"@earendil-works/pi-ai\";\nimport type {\n ToolExecutionApi,\n ToolExecutionResult,\n ToolRegistration\n} from \"@earendil-works/pi-durable\";\nimport {\n createBrowserToolCore,\n type BrowserToolOptions,\n type BrowserToolOutput\n} from \"../browser-tool\";\nimport type { BrowserNewTab } from \"../session-connector\";\nimport {\n browserExecuteModelOutput,\n browserScreenshotOutput\n} from \"../tool-helpers\";\n\nexport type {\n BrowserToolInput,\n BrowserToolOptions,\n BrowserToolOutput\n} from \"../browser-tool\";\nexport type { BrowserNewTab, BrowserSource } from \"../session-connector\";\n\nexport interface PiBrowserToolOptions<\n TName extends string = \"browser\"\n> extends Omit<BrowserToolOptions, \"scope\"> {\n /** The tool's name. Default `\"browser\"`. */\n name?: TName;\n /**\n * Which calls share an active tab: a string for every call, or a function\n * of the call. Defaults to `conversation:<id>` for the calling\n * conversation, so each\n * conversation (and each fork or subagent) on the Durable Object keeps its\n * own tab, while cookies and logins stay shared.\n */\n scope?: string | ((api: ToolExecutionApi) => string);\n}\n\nconst browserToolParameters = Type.Object({\n code: Type.String({\n description: \"An async arrow function that drives the browser with `cdp`\"\n })\n});\n\n/**\n * What a UI can show about a run without reading the content: the run's\n * outcome and what happened to the browser.\n */\nexport type PiBrowserToolDetails = {\n executionId: string;\n status: BrowserToolOutput[\"status\"];\n restarted?: true;\n newTabs?: { targetId: string; url?: string; title?: string }[];\n};\n\nexport type PiBrowserTool<TName extends string = \"browser\"> = ToolRegistration<\n typeof browserToolParameters,\n PiBrowserToolDetails\n> & {\n readonly name: TName;\n};\n\nfunction details(output: BrowserToolOutput): PiBrowserToolDetails {\n return {\n executionId: output.executionId,\n status: output.status,\n ...(output.restarted ? { restarted: true as const } : {}),\n ...(output.newTabs\n ? {\n newTabs: output.newTabs.map((tab: BrowserNewTab) => ({\n targetId: tab.targetId,\n ...(tab.url === undefined ? {} : { url: tab.url }),\n ...(tab.title === undefined ? {} : { title: tab.title })\n }))\n }\n : {})\n };\n}\n\nfunction text(value: unknown): TextContent {\n return {\n type: \"text\",\n text: typeof value === \"string\" ? value : JSON.stringify(value)\n };\n}\n\n/**\n * The tool result pi stores and sends: the model projection of the run as\n * JSON (no `calls` log, bounded `logs`), and a screenshot as an image part\n * after it. pi-ai only sends the image to models that accept images.\n */\nfunction browserToolResult(\n output: BrowserToolOutput\n): ToolExecutionResult<PiBrowserToolDetails> {\n const isError = output.status === \"error\";\n const screenshot = browserScreenshotOutput(output);\n if (!screenshot) {\n return {\n output: [text(browserExecuteModelOutput(output).value)],\n details: details(output),\n ...(isError ? { isError } : {})\n };\n }\n\n // No size check: a screenshot comes from one cdp.send result, which\n // codemode caps at 1 MB, well under what model providers accept.\n const bytes = Math.floor((screenshot.data.length * 3) / 4).toLocaleString();\n // Keep the rest of the result (status, restarted, notice, newTabs) around\n // the sentence that replaces the screenshot.\n const summary = browserExecuteModelOutput({\n ...output,\n result: `Screenshot attached as an image (${screenshot.mediaType}, approximately ${bytes} bytes). If you can't see it, read the page with Runtime.evaluate instead.`\n }).value;\n const image: ImageContent = {\n type: \"image\",\n data: screenshot.data,\n mimeType: screenshot.mediaType\n };\n return {\n output: [text(summary), image],\n details: details(output)\n };\n}\n\nfunction conversationScope(api: ToolExecutionApi): string {\n return `conversation:${api.conversationId}`;\n}\n\n/**\n * Create a pi-durable 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`. A returned screenshot comes back as an image part that\n * the model sees, when its model accepts images.\n *\n * Each conversation gets its own active tab (see\n * {@link PiBrowserToolOptions.scope}). A conversation's calls run one at a\n * time, since they share its tab, and the tool doesn't rerun after an\n * eviction (its code may have clicked or\n * submitted something): pi gives the model an interrupted result instead.\n * Pass `ctx` unless the tool is built inside an Agent.\n *\n * @example\n * ```ts\n * import { Browser, browserRun } from \"agents/browser\";\n * import { browserTool } from \"agents/browser/pi\";\n *\n * export class MyAgent extends DurableObject<Env> {\n * readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) });\n * readonly registry = createRegistry();\n * readonly harness = new PiHarness({\n * harness: ({ storage, context }) => {\n * this.registry.install({\n * name: \"browser\",\n * tools: [\n * browserTool({\n * ctx: this.ctx,\n * browser: this.browser,\n * loader: this.env.LOADER\n * })\n * ]\n * });\n * return Harness.open(storage, { models, registry: this.registry }, context);\n * }\n * });\n * readonly lifecycle = Lifecycle.install(this)\n * .use(this.browser)\n * .use(this.harness);\n * }\n * ```\n */\nexport function browserTool<TName extends string = \"browser\">(\n options: PiBrowserToolOptions<TName>\n): PiBrowserTool<TName> {\n const { scope = conversationScope, ...coreOptions } = options;\n const core = createBrowserToolCore(coreOptions, {\n screenshotHint:\n \"To see a screenshot, return { type: 'browser_screenshot', mediaType, data } with data from Page.captureScreenshot and mediaType 'image/png', or 'image/jpeg' if you captured with format: 'jpeg'. The image is attached to the result.\"\n });\n return {\n name: options.name ?? (\"browser\" as TName),\n description: core.description,\n parameters: browserToolParameters,\n // A conversation's calls share its tab, so they must not interleave.\n executionMode: \"sequential\",\n // The code may have clicked or submitted something; don't run it twice.\n replay: \"unsafe\",\n async execute(args, api) {\n const callScope = typeof scope === \"string\" ? scope : scope(api);\n return browserToolResult(\n await core.execute({ code: args.code }, callScope)\n );\n }\n };\n}\n"],"mappings":";;;;AA2CA,MAAM,wBAAwB,KAAK,OAAO,EACxC,MAAM,KAAK,OAAO,EAChB,aAAa,6DACf,CAAC,EACH,CAAC;AAoBD,SAAS,QAAQ,QAAiD;CAChE,OAAO;EACL,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,GAAI,OAAO,YAAY,EAAE,WAAW,KAAc,IAAI,CAAC;EACvD,GAAI,OAAO,UACP,EACE,SAAS,OAAO,QAAQ,KAAK,SAAwB;GACnD,UAAU,IAAI;GACd,GAAI,IAAI,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI;GAChD,GAAI,IAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,IAAI,MAAM;EACxD,EAAE,EACJ,IACA,CAAC;CACP;AACF;AAEA,SAAS,KAAK,OAA6B;CACzC,OAAO;EACL,MAAM;EACN,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;CAChE;AACF;;;;;;AAOA,SAAS,kBACP,QAC2C;CAC3C,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,aAAa,wBAAwB,MAAM;CACjD,IAAI,CAAC,YACH,OAAO;EACL,QAAQ,CAAC,KAAK,0BAA0B,MAAM,CAAC,CAAC,KAAK,CAAC;EACtD,SAAS,QAAQ,MAAM;EACvB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAC/B;CAKF,MAAM,QAAQ,KAAK,MAAO,WAAW,KAAK,SAAS,IAAK,CAAC,CAAC,CAAC,eAAe;CAG1E,MAAM,UAAU,0BAA0B;EACxC,GAAG;EACH,QAAQ,oCAAoC,WAAW,UAAU,kBAAkB,MAAM;CAC3F,CAAC,CAAC,CAAC;CACH,MAAM,QAAsB;EAC1B,MAAM;EACN,MAAM,WAAW;EACjB,UAAU,WAAW;CACvB;CACA,OAAO;EACL,QAAQ,CAAC,KAAK,OAAO,GAAG,KAAK;EAC7B,SAAS,QAAQ,MAAM;CACzB;AACF;AAEA,SAAS,kBAAkB,KAA+B;CACxD,OAAO,gBAAgB,IAAI;AAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,YACd,SACsB;CACtB,MAAM,EAAE,QAAQ,mBAAmB,GAAG,gBAAgB;CACtD,MAAM,OAAO,sBAAsB,aAAa,EAC9C,gBACE,yOACJ,CAAC;CACD,OAAO;EACL,MAAM,QAAQ,QAAS;EACvB,aAAa,KAAK;EAClB,YAAY;EAEZ,eAAe;EAEf,QAAQ;EACR,MAAM,QAAQ,MAAM,KAAK;GACvB,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,MAAM,GAAG;GAC/D,OAAO,kBACL,MAAM,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CACnD;EACF;CACF;AACF"}
@@ -1,11 +1,11 @@
1
- import { n as CreateBrowserToolsOptions } from "../../ai-ahruGw7e.js";
1
+ import { n as CreateBrowserToolsOptions } from "../../ai-iXw0uOGj.js";
2
2
  import {
3
3
  a as BrowserSource,
4
4
  i as BrowserNewTab,
5
5
  n as BrowserToolOptions,
6
6
  r as BrowserToolOutput,
7
7
  t as BrowserToolInput
8
- } from "../../browser-tool-vtrPOV8e.js";
8
+ } from "../../browser-tool-Cxjk5sgG.js";
9
9
  import { ServerTool } from "@tanstack/ai";
10
10
 
11
11
  //#region src/browser/tanstack-ai.d.ts
@@ -1,6 +1,6 @@
1
1
  import { n as browserScreenshotOutput, t as browserExecuteModelOutput } from "../../tool-helpers-O5BDVEAY.js";
2
2
  import { createBrowserRuntime } from "../ai.js";
3
- import { n as createBrowserToolCore } from "../../browser-tool-4iomavs_.js";
3
+ import { n as createBrowserToolCore } from "../../browser-tool-B1mhifin.js";
4
4
  import { z } from "zod";
5
5
  import { toolDefinition } from "@tanstack/ai";
6
6
  //#region src/browser/tanstack-ai.ts
@@ -1,7 +1,90 @@
1
1
  import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
2
2
  import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
3
3
  import { t as LifecycleCapability } from "./capability-DBAKc6UZ.js";
4
- import { d as createBrowserSession, f as deleteBrowserSession, h as listBrowserTargets, m as isMissingBrowserSession, n as DurableBrowserSessionStore, o as createLiveView, r as loadCdpSpec, u as connectBrowserSession } from "./session-store-Do4xkDcf.js";
4
+ import { _ as listBrowserTargets, f as connectBrowserSession, g as isMissingBrowserSession, l as acquireBrowserSession, n as DurableBrowserSessionStore, o as createLiveView, r as loadCdpSpec, u as closeBrowserSession } from "./session-store-yvwq1f9x.js";
5
+ //#region src/browser/providers/provider-adapter.ts
6
+ /**
7
+ * Thrown by {@link BrowserProviderAdapter.connect} when the session no longer
8
+ * exists upstream (closed, expired, or reclaimed). `Browser` treats it as
9
+ * "this browser is lost" and replaces it; any other error is passed on.
10
+ */
11
+ var BrowserSessionGoneError = class extends Error {
12
+ constructor(sessionId, options) {
13
+ super(`Browser session gone: ${sessionId}`, options);
14
+ this.sessionId = sessionId;
15
+ this.name = "BrowserSessionGoneError";
16
+ }
17
+ };
18
+ /**
19
+ * Run the browser on Cloudflare Browser Run.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * new Browser({ provider: browserRun(env.BROWSER, { recording: true }) });
24
+ * ```
25
+ */
26
+ function browserRun(binding, options = {}) {
27
+ return {
28
+ kind: "browser-run",
29
+ binding,
30
+ options
31
+ };
32
+ }
33
+ /** The code behind {@link browserRun}. */
34
+ function browserRunAdapter(provider) {
35
+ const { binding, options } = provider;
36
+ const keepAliveMs = () => options.keepAliveMs ?? 6e5;
37
+ return {
38
+ get keepAliveMs() {
39
+ return keepAliveMs();
40
+ },
41
+ async create() {
42
+ return acquireBrowserSession(binding, {
43
+ keepAliveMs: keepAliveMs(),
44
+ recording: options.recording,
45
+ guardrails: options.guardrails
46
+ });
47
+ },
48
+ async isAlive(sessionId) {
49
+ try {
50
+ await listBrowserTargets(binding, sessionId);
51
+ return true;
52
+ } catch (error) {
53
+ if (isMissingBrowserSession(error)) return false;
54
+ throw error;
55
+ }
56
+ },
57
+ async connect(sessionId, connectOptions) {
58
+ try {
59
+ return await connectBrowserSession(binding, sessionId, connectOptions);
60
+ } catch (error) {
61
+ if (isMissingBrowserSession(error)) throw new BrowserSessionGoneError(sessionId, { cause: error });
62
+ throw error;
63
+ }
64
+ },
65
+ async close(sessionId) {
66
+ try {
67
+ await closeBrowserSession(binding, sessionId);
68
+ } catch (error) {
69
+ if (!isMissingBrowserSession(error)) throw error;
70
+ }
71
+ },
72
+ spec(sessionId) {
73
+ return loadCdpSpec({
74
+ browser: binding,
75
+ sessionId
76
+ });
77
+ },
78
+ async liveView(sessionId, mode) {
79
+ try {
80
+ return createLiveView(sessionId, await listBrowserTargets(binding, sessionId), mode);
81
+ } catch (error) {
82
+ if (isMissingBrowserSession(error)) return void 0;
83
+ throw error;
84
+ }
85
+ }
86
+ };
87
+ }
5
88
  /**
6
89
  * The scope used when a caller doesn't pick one: every caller that leaves
7
90
  * `scope` out shares this one active tab. See {@link BrowserConnectOptions}.
@@ -36,23 +119,15 @@ function retiredBrowserSessionKey(name) {
36
119
  function browserScopeKeyPrefix(name) {
37
120
  return `browser:scope:${name.length}:${name}:`;
38
121
  }
39
- /**
40
- * Run the browser on Cloudflare Browser Run.
41
- *
42
- * @example
43
- * ```ts
44
- * new Browser({ provider: browserRun(env.BROWSER, { recording: true }) });
45
- * ```
46
- */
47
- function browserRun(binding, options = {}) {
48
- return {
49
- kind: "browser-run",
50
- binding,
51
- options
52
- };
122
+ /** The adapter behind a provider: one explicit case per provider kind. */
123
+ function providerAdapterFor(provider) {
124
+ switch (provider.kind) {
125
+ case "browser-run": return browserRunAdapter(provider);
126
+ }
53
127
  }
54
128
  var _options = /* @__PURE__ */ new WeakMap();
55
129
  var _key = /* @__PURE__ */ new WeakMap();
130
+ var _adapter = /* @__PURE__ */ new WeakMap();
56
131
  var _store = /* @__PURE__ */ new WeakMap();
57
132
  var _Browser_brand = /* @__PURE__ */ new WeakSet();
58
133
  /**
@@ -60,16 +135,16 @@ var _Browser_brand = /* @__PURE__ */ new WeakSet();
60
135
  *
61
136
  * Install it with `Lifecycle.use()` — on an Agent subclass or any Durable
62
137
  * Object that composes `Lifecycle` — and it stores its record in the
63
- * object's own storage. It schedules nothing: Browser Run reclaims idle
64
- * browsers itself once `keep_alive` elapses, and the next use replaces the
65
- * lost browser with `restarted: true`.
138
+ * object's own storage. It schedules nothing: the provider reclaims idle
139
+ * browsers itself (Browser Run once `keep_alive` elapses), and the next use
140
+ * replaces the lost browser with `restarted: true`.
66
141
  *
67
- * Everything here is host-side. Models never see browser names, Browser Run
68
- * ids, or Live View URLs through this surface.
142
+ * Everything here is host-side. Models never see browser names, provider
143
+ * session ids, or Live View URLs through this surface.
69
144
  *
70
145
  * Store discipline: locks wrap storage operations only — liveness probes and
71
- * Browser Run create/delete calls always happen outside any lock, with a
72
- * commit re-check to detect concurrent swaps.
146
+ * provider create/close calls always happen outside any lock, with a commit
147
+ * re-check to detect concurrent swaps.
73
148
  *
74
149
  * @experimental The API surface may change before stabilizing.
75
150
  */
@@ -80,11 +155,13 @@ var Browser = class extends LifecycleCapability {
80
155
  _classPrivateMethodInitSpec(this, _Browser_brand);
81
156
  _classPrivateFieldInitSpec(this, _options, void 0);
82
157
  _classPrivateFieldInitSpec(this, _key, void 0);
158
+ _classPrivateFieldInitSpec(this, _adapter, void 0);
83
159
  _classPrivateFieldInitSpec(this, _store, void 0);
84
160
  if (name.trim() === "") throw new Error("Browser names must be non-empty");
85
161
  this.name = name;
86
162
  _classPrivateFieldSet2(_options, this, options);
87
163
  _classPrivateFieldSet2(_key, this, namedBrowserSessionKey(name));
164
+ _classPrivateFieldSet2(_adapter, this, providerAdapterFor(options.provider));
88
165
  }
89
166
  /**
90
167
  * Reattach to the browser when it is still alive, otherwise create a fresh
@@ -103,7 +180,7 @@ var Browser = class extends LifecycleCapability {
103
180
  ...resolvedFields(session)
104
181
  };
105
182
  }
106
- const alive = await _assertClassBrand(_Browser_brand, this, _isAlive).call(this, existing);
183
+ const alive = await _classPrivateFieldGet2(_adapter, this).isAlive(existing.sessionId);
107
184
  const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(key);
108
185
  try {
109
186
  const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(key);
@@ -146,7 +223,7 @@ var Browser = class extends LifecycleCapability {
146
223
  try {
147
224
  cdp = await _assertClassBrand(_Browser_brand, this, _open).call(this, resolved);
148
225
  } catch (error) {
149
- if (!isMissingBrowserSession(error)) throw error;
226
+ if (!(error instanceof BrowserSessionGoneError)) throw error;
150
227
  await _assertClassBrand(_Browser_brand, this, _retireIfCurrent).call(this, resolved.sessionId);
151
228
  resolved = await this.resolve();
152
229
  cdp = await _assertClassBrand(_Browser_brand, this, _open).call(this, resolved);
@@ -161,12 +238,13 @@ var Browser = class extends LifecycleCapability {
161
238
  }
162
239
  /**
163
240
  * Close the browser: retire the record (so the next resolve reports
164
- * `restarted: true`) and delete its Browser Run session. Returns `false`
165
- * when there was nothing to close.
241
+ * `restarted: true`) and close its provider session. Returns `false` when
242
+ * there was nothing to close.
166
243
  *
167
- * The platform delete is best-effort: the retired record is the durable
168
- * outcome, and a browser whose delete failed is unreachable through this
169
- * store, so the pinned `keep_alive` (≤600s) reclaims it.
244
+ * The provider close is best-effort: the retired record is the durable
245
+ * outcome, and a browser whose close failed is unreachable through this
246
+ * store, so the provider reclaims it once idle (Browser Run: after
247
+ * `keepAliveMs`, at most 20 minutes).
170
248
  */
171
249
  async close() {
172
250
  let stored;
@@ -180,9 +258,9 @@ var Browser = class extends LifecycleCapability {
180
258
  await lock.release();
181
259
  }
182
260
  try {
183
- await deleteBrowserSession(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, stored.sessionId);
261
+ await _classPrivateFieldGet2(_adapter, this).close(stored.sessionId);
184
262
  } catch (error) {
185
- console.warn(`[agents/browser] Failed to delete closed Browser Run session ${stored.sessionId}`, error);
263
+ console.warn(`[agents/browser] Failed to close browser session ${stored.sessionId}`, error);
186
264
  }
187
265
  return true;
188
266
  }
@@ -196,23 +274,18 @@ var Browser = class extends LifecycleCapability {
196
274
  async liveView(options) {
197
275
  const entry = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(_classPrivateFieldGet2(_key, this));
198
276
  if (!entry) return void 0;
277
+ const view = await _classPrivateFieldGet2(_adapter, this).liveView(entry.sessionId, options?.mode);
278
+ if (!view) return void 0;
199
279
  try {
200
- const targets = await listBrowserTargets(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, entry.sessionId);
201
- try {
202
- if (!await _assertClassBrand(_Browser_brand, this, _touch).call(this, entry.sessionId)) return void 0;
203
- } catch (error) {
204
- console.warn(`[agents/browser] Failed to refresh activity for browser "${this.name}"`, error);
205
- }
206
- return createLiveView(entry.sessionId, targets, options?.mode);
280
+ if (!await _assertClassBrand(_Browser_brand, this, _touch).call(this, entry.sessionId)) return void 0;
207
281
  } catch (error) {
208
- if (isMissingBrowserSession(error)) return void 0;
209
- throw error;
282
+ console.warn(`[agents/browser] Failed to refresh activity for browser "${this.name}"`, error);
210
283
  }
284
+ return view;
211
285
  }
212
286
  };
213
287
  function _connection(resolved, scope, cdp, entered) {
214
288
  const { sessionId } = resolved;
215
- const { binding } = _get_provider.call(_assertClassBrand(_Browser_brand, this));
216
289
  return {
217
290
  name: resolved.name,
218
291
  sessionId,
@@ -222,16 +295,13 @@ function _connection(resolved, scope, cdp, entered) {
222
295
  cdp,
223
296
  setActiveTarget: (targetId) => _assertClassBrand(_Browser_brand, this, _updateScope).call(this, scope, sessionId, targetId),
224
297
  targetsInOtherScopes: async () => activeTargets(await _assertClassBrand(_Browser_brand, this, _listScopes).call(this), scope, sessionId),
225
- spec: () => loadCdpSpec({
226
- browser: binding,
227
- sessionId
228
- })
298
+ spec: () => _classPrivateFieldGet2(_adapter, this).spec(sessionId)
229
299
  };
230
300
  }
231
301
  async function _open(resolved) {
232
302
  let lastTouchAt = Date.now();
233
303
  let touchInFlight = false;
234
- return connectBrowserSession(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, resolved.sessionId, {
304
+ return _classPrivateFieldGet2(_adapter, this).connect(resolved.sessionId, {
235
305
  timeoutMs: _classPrivateFieldGet2(_options, this).timeoutMs,
236
306
  onActivity: () => {
237
307
  const now = Date.now();
@@ -246,20 +316,15 @@ async function _open(resolved) {
246
316
  }
247
317
  });
248
318
  }
249
- function _get_provider() {
250
- return _classPrivateFieldGet2(_options, this).provider;
251
- }
252
- /** The platform `keep_alive` every create applies. */
253
- function _get_keepAliveMs() {
254
- return _get_provider.call(_assertClassBrand(_Browser_brand, this)).options.keepAliveMs ?? 6e5;
255
- }
256
319
  /**
257
- * Capped at half the keep-alive window: a browser with continuous CDP
258
- * traffic must never look older than the window the platform uses to
259
- * reclaim it.
320
+ * Capped at half the provider's keep-alive window: a browser with
321
+ * continuous CDP traffic must never look older than the window the
322
+ * provider uses to reclaim it.
260
323
  */
261
324
  function _get_touchIntervalMs() {
262
- return Math.min(_classPrivateFieldGet2(_options, this).touchIntervalMs ?? 6e4, Math.floor(_get_keepAliveMs.call(_assertClassBrand(_Browser_brand, this)) / 2));
325
+ const interval = _classPrivateFieldGet2(_options, this).touchIntervalMs ?? 6e4;
326
+ const { keepAliveMs } = _classPrivateFieldGet2(_adapter, this);
327
+ return keepAliveMs === void 0 ? interval : Math.min(interval, Math.floor(keepAliveMs / 2));
263
328
  }
264
329
  /** Lazy: `lifecycle.storage` exists only once `Lifecycle.use()` ran. */
265
330
  function _get_sessionStore() {
@@ -389,10 +454,10 @@ async function _retire(current) {
389
454
  await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).delete(_classPrivateFieldGet2(_key, this));
390
455
  }
391
456
  /**
392
- * Create a Browser Run session (outside any lock) and commit it, applying
393
- * the provider's creation options. If a concurrent caller committed an
394
- * entry first, theirs wins and the redundant session is deleted
395
- * best-effort.
457
+ * Create a provider session (outside any lock) and commit it. The adapter
458
+ * applies the provider's creation options. If a concurrent caller
459
+ * committed an entry first, theirs wins and the redundant session is
460
+ * closed best-effort.
396
461
  *
397
462
  * `restarted` is read under the commit lock: a retired marker — left by
398
463
  * close() or a dead-browser recovery — is evidence a prior browser
@@ -401,12 +466,7 @@ async function _retire(current) {
401
466
  * was created and closed while this create was in flight.
402
467
  */
403
468
  async function _createAndCommit() {
404
- const { binding, options } = _get_provider.call(_assertClassBrand(_Browser_brand, this));
405
- const info = await createBrowserSession(binding, {
406
- keepAliveMs: _get_keepAliveMs.call(_assertClassBrand(_Browser_brand, this)),
407
- recording: options.recording,
408
- guardrails: options.guardrails
409
- });
469
+ const info = await _classPrivateFieldGet2(_adapter, this).create();
410
470
  const now = Date.now();
411
471
  const stored = {
412
472
  sessionId: info.sessionId,
@@ -426,9 +486,9 @@ async function _createAndCommit() {
426
486
  }
427
487
  if (winner) {
428
488
  try {
429
- await deleteBrowserSession(binding, stored.sessionId);
489
+ await _classPrivateFieldGet2(_adapter, this).close(stored.sessionId);
430
490
  } catch (error) {
431
- console.warn(`[agents/browser] Failed to delete redundant Browser Run session ${stored.sessionId}`, error);
491
+ console.warn(`[agents/browser] Failed to close redundant browser session ${stored.sessionId}`, error);
432
492
  }
433
493
  return {
434
494
  session: winner,
@@ -440,15 +500,6 @@ async function _createAndCommit() {
440
500
  restarted
441
501
  };
442
502
  }
443
- async function _isAlive(stored) {
444
- try {
445
- await listBrowserTargets(_get_provider.call(_assertClassBrand(_Browser_brand, this)).binding, stored.sessionId);
446
- return true;
447
- } catch (error) {
448
- if (isMissingBrowserSession(error)) return false;
449
- throw error;
450
- }
451
- }
452
503
  async function _readStored() {
453
504
  const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(_classPrivateFieldGet2(_key, this));
454
505
  try {
@@ -478,4 +529,4 @@ function activeTargets(scopes, scope, sessionId) {
478
529
  //#endregion
479
530
  export { browserRun as i, DEFAULT_BROWSER_SCOPE as n, MAX_BROWSER_SCOPES as r, Browser as t };
480
531
 
481
- //# sourceMappingURL=browser-CEdNlsaD.js.map
532
+ //# sourceMappingURL=browser-DccPw_wW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-DccPw_wW.js","names":[],"sources":["../src/browser/providers/provider-adapter.ts","../src/browser/providers/browser-run.ts","../src/browser/browser.ts"],"sourcesContent":["/**\n * The internal seam between {@link Browser} and the service that runs its\n * browser. A provider is the public, plain-data config a host passes\n * (`browserRun(env.BROWSER)`); its adapter is the code that does the work.\n * `Browser` keeps everything that doesn't depend on the provider — the\n * durable record, locks, restart detection, scopes, and activity touches —\n * and calls the adapter for the rest.\n *\n * Not exported from `agents/browser`: the provider set is closed, and this\n * interface may change as providers are added.\n */\n\nimport type { CdpConnection } from \"../cdp-connection\";\nimport type { BrowserLiveView, LiveViewMode } from \"../live-view\";\nimport type { SearchableCdpSpec } from \"../spec\";\n\n/**\n * Thrown by {@link BrowserProviderAdapter.connect} when the session no longer\n * exists upstream (closed, expired, or reclaimed). `Browser` treats it as\n * \"this browser is lost\" and replaces it; any other error is passed on.\n */\nexport class BrowserSessionGoneError extends Error {\n constructor(\n readonly sessionId: string,\n options?: { cause?: unknown }\n ) {\n super(`Browser session gone: ${sessionId}`, options);\n this.name = \"BrowserSessionGoneError\";\n }\n}\n\nexport interface BrowserProviderAdapter {\n /** Start a new browser session. */\n create(): Promise<{ sessionId: string }>;\n /**\n * Whether the session still exists. `true` means alive and `false` means\n * definitely gone. Throw when the answer is unknown (a 5xx, a network\n * error): `Browser` discards the browser on `false`, so a flaky probe must\n * never report it.\n */\n isAlive(sessionId: string): Promise<boolean>;\n /**\n * Open a CDP connection to the session. Throws\n * {@link BrowserSessionGoneError} when the session is gone.\n */\n connect(\n sessionId: string,\n options: { timeoutMs?: number; onActivity?: () => void }\n ): Promise<CdpConnection>;\n /** End the session. A session that's already gone counts as closed. */\n close(sessionId: string): Promise<void>;\n /** The Chrome DevTools Protocol description the session's browser serves. */\n spec(sessionId: string): Promise<SearchableCdpSpec>;\n /**\n * Live View URLs for the session's open tabs, or `undefined` when the\n * session is gone.\n */\n liveView(\n sessionId: string,\n mode?: LiveViewMode\n ): Promise<BrowserLiveView | undefined>;\n /**\n * How long the provider keeps an idle session before reclaiming it.\n * `Browser` refreshes the record's `updatedAt` at most every half window.\n * `undefined` when idle sessions are never reclaimed.\n */\n readonly keepAliveMs?: number;\n}\n","/**\n * The Browser Run provider: `browserRun(env.BROWSER, options)` and the\n * adapter that runs a {@link Browser}'s browser on Cloudflare Browser Run.\n */\n\nimport {\n acquireBrowserSession,\n type BrowserBinding,\n type BrowserSessionGuardrails,\n closeBrowserSession,\n connectBrowserSession,\n isMissingBrowserSession,\n listBrowserTargets\n} from \"../browser-run\";\nimport { createLiveView } from \"../live-view\";\nimport { loadCdpSpec } from \"../spec\";\nimport {\n BrowserSessionGoneError,\n type BrowserProviderAdapter\n} from \"./provider-adapter\";\n\n/**\n * Default `keep_alive` for a `Browser` on Browser Run: 10 minutes. Long\n * enough to survive quiet spells between agent turns, while an abandoned\n * browser bills for at most 10 idle minutes (a whole day of Workers Free\n * browser time).\n */\nexport const BROWSER_SESSION_KEEP_ALIVE_DEFAULT_MS = 600_000;\n\n/**\n * Browser Run's `keep_alive` maximum: 20 minutes. Browser Run rejects\n * anything longer.\n */\nexport const BROWSER_SESSION_KEEP_ALIVE_MAX_MS = 1_200_000;\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_DEFAULT_MS} (10 minutes).\n * Browser Run accepts up to {@link BROWSER_SESSION_KEEP_ALIVE_MAX_MS}\n * (20 minutes).\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/**\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\n/** The code behind {@link browserRun}. */\nexport function browserRunAdapter(\n provider: BrowserRunProvider\n): BrowserProviderAdapter {\n const { binding, options } = provider;\n // Options are read on every use, like `recording` and `guardrails` below.\n const keepAliveMs = () =>\n options.keepAliveMs ?? BROWSER_SESSION_KEEP_ALIVE_DEFAULT_MS;\n\n return {\n get keepAliveMs() {\n return keepAliveMs();\n },\n\n async create() {\n return acquireBrowserSession(binding, {\n keepAliveMs: keepAliveMs(),\n recording: options.recording,\n guardrails: options.guardrails\n });\n },\n\n async isAlive(sessionId) {\n try {\n await listBrowserTargets(binding, sessionId);\n return true;\n } catch (error) {\n if (isMissingBrowserSession(error)) return false;\n throw error;\n }\n },\n\n async connect(sessionId, connectOptions) {\n try {\n return await connectBrowserSession(binding, sessionId, connectOptions);\n } catch (error) {\n if (isMissingBrowserSession(error)) {\n throw new BrowserSessionGoneError(sessionId, { cause: error });\n }\n throw error;\n }\n },\n\n async close(sessionId) {\n try {\n await closeBrowserSession(binding, sessionId);\n } catch (error) {\n if (!isMissingBrowserSession(error)) throw error;\n }\n },\n\n spec(sessionId) {\n return loadCdpSpec({ browser: binding, sessionId });\n },\n\n async liveView(sessionId, mode) {\n try {\n const targets = await listBrowserTargets(binding, sessionId);\n return createLiveView(sessionId, targets, mode);\n } catch (error) {\n if (isMissingBrowserSession(error)) return undefined;\n throw error;\n }\n }\n };\n}\n","/**\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 * Where the browser runs is its provider: Browser Run is the only one today\n * (`browserRun` in ./providers/browser-run.ts). `Browser` holds everything\n * that doesn't depend on the provider and calls the provider's adapter for\n * the rest (see ./providers/provider-adapter.ts).\n */\n\nimport { LifecycleCapability } from \"../lifecycle/capability\";\nimport type { CdpConnection } from \"./cdp-connection\";\nimport type { BrowserLiveView, LiveViewMode } from \"./live-view\";\nimport {\n browserRunAdapter,\n type BrowserRunProvider\n} from \"./providers/browser-run\";\nimport {\n BrowserSessionGoneError,\n type BrowserProviderAdapter\n} from \"./providers/provider-adapter\";\nimport {\n DurableBrowserSessionStore,\n type ListableBrowserSessionStore,\n type StoredBrowserSession\n} from \"./session-store\";\nimport type { SearchableCdpSpec } from \"./spec\";\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\n/**\n * The scope used when a caller doesn't pick one: every caller that leaves\n * `scope` out shares this one active tab. See {@link BrowserConnectOptions}.\n */\nexport const DEFAULT_BROWSER_SCOPE = \"shared\";\n\n/**\n * Scope records kept per browser. Writing a new one past the cap deletes the\n * least recently used; a scope that comes back afterwards starts fresh.\n */\nexport const MAX_BROWSER_SCOPES = 100;\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 * Where a browser's scope records live. The name's length is part of the\n * prefix, so one browser's prefix never matches another name's (`a` with\n * scope `b:c` vs. `a:b` with scope `c`).\n */\nfunction browserScopeKeyPrefix(name: string): string {\n return `browser:scope:${name.length}:${name}:`;\n}\n\n/** Where a {@link Browser} gets its browser from. */\nexport type BrowserProvider = BrowserRunProvider;\n\n/** The adapter behind a provider: one explicit case per provider kind. */\nfunction providerAdapterFor(provider: BrowserProvider): BrowserProviderAdapter {\n switch (provider.kind) {\n case \"browser-run\":\n return browserRunAdapter(provider);\n }\n}\n\nexport interface BrowserOptions {\n /** Where the browser runs. Create one with `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. It must implement `list`.\n */\n store?: ListableBrowserSessionStore;\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 provider's 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\nexport interface BrowserConnectOptions {\n /**\n * Who is driving. Connections with the same scope share an active tab, and\n * each scope keeps its own, so two conversations on one browser don't\n * drive each other's page. Cookies, logins, and the tabs themselves are\n * shared by every scope: a scope splits \"which tab is mine\", not the\n * browser. Defaults to {@link DEFAULT_BROWSER_SCOPE}.\n */\n scope?: string;\n}\n\nexport interface BrowserConnection {\n name: string;\n sessionId: string;\n /** The scope this connection works in; see {@link BrowserConnectOptions}. */\n scope: string;\n /**\n * `true` when this scope worked in an earlier browser that has since been\n * replaced, whichever scope's connection replaced it. Its tabs and page\n * state are gone; surface this loudly to the model. `false` on a scope's\n * first connection, even one that replaced a lost browser: the scope had\n * nothing to lose. This is per scope, not a browser-wide signal.\n */\n restarted: boolean;\n /**\n * The tab this scope last worked in, when it recorded one in this\n * browser.\n */\n activeTargetId?: string;\n /** Closing this connection does NOT close the browser. */\n cdp: CdpConnection;\n /**\n * Record the tab this scope 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 /** The tabs other scopes are working in, in this browser. */\n targetsInOtherScopes(): Promise<Set<string>>;\n /**\n * The Chrome DevTools Protocol description this browser serves, read from\n * the browser itself (cached).\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: the provider reclaims idle\n * browsers itself (Browser Run once `keep_alive` elapses), and the next use\n * replaces the lost browser with `restarted: true`.\n *\n * Everything here is host-side. Models never see browser names, provider\n * session ids, or Live View URLs through this surface.\n *\n * Store discipline: locks wrap storage operations only — liveness probes and\n * provider create/close calls always happen outside any lock, with a commit\n * 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 readonly #adapter: BrowserProviderAdapter;\n #store?: ListableBrowserSessionStore;\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 this.#adapter = providerAdapterFor(options.provider);\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, ...resolvedFields(session) };\n }\n\n // Live entry on record — probe it outside any lock.\n const alive = await this.#adapter.isAlive(existing.sessionId);\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, ...resolvedFields(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 for one scope (see\n * {@link BrowserConnectOptions.scope}). Commands sent over the connection\n * 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 instead of an error.\n */\n async connect(\n options: BrowserConnectOptions = {}\n ): Promise<BrowserConnection> {\n const scope = options.scope ?? DEFAULT_BROWSER_SCOPE;\n if (scope.trim() === \"\") {\n throw new Error(\"Browser scopes must be non-empty\");\n }\n let resolved = await this.resolve();\n let cdp: CdpConnection;\n try {\n cdp = await this.#open(resolved);\n } catch (error) {\n if (!(error instanceof BrowserSessionGoneError)) throw error;\n await this.#retireIfCurrent(resolved.sessionId);\n resolved = await this.resolve();\n cdp = await this.#open(resolved);\n }\n try {\n const entered = await this.#enterScope(scope, resolved.sessionId);\n return this.#connection(resolved, scope, cdp, entered);\n } catch (error) {\n cdp.disconnect();\n throw error;\n }\n }\n\n #connection(\n resolved: ResolvedBrowser,\n scope: string,\n cdp: CdpConnection,\n entered: { restarted: boolean; activeTargetId?: string }\n ): BrowserConnection {\n const { sessionId } = resolved;\n return {\n name: resolved.name,\n sessionId,\n scope,\n restarted: entered.restarted,\n activeTargetId: entered.activeTargetId,\n cdp,\n setActiveTarget: (targetId) =>\n this.#updateScope(scope, sessionId, targetId),\n targetsInOtherScopes: async () =>\n activeTargets(await this.#listScopes(), scope, sessionId),\n spec: () => this.#adapter.spec(sessionId)\n };\n }\n\n async #open(resolved: ResolvedBrowser): Promise<CdpConnection> {\n let lastTouchAt = Date.now();\n let touchInFlight = false;\n return this.#adapter.connect(resolved.sessionId, {\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\n /**\n * Close the browser: retire the record (so the next resolve reports\n * `restarted: true`) and close its provider session. Returns `false` when\n * there was nothing to close.\n *\n * The provider close is best-effort: the retired record is the durable\n * outcome, and a browser whose close failed is unreachable through this\n * store, so the provider reclaims it once idle (Browser Run: after\n * `keepAliveMs`, at most 20 minutes).\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 this.#adapter.close(stored.sessionId);\n } catch (error) {\n console.warn(\n `[agents/browser] Failed to close browser 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 const view = await this.#adapter.liveView(entry.sessionId, options?.mode);\n if (!view) return undefined;\n // A human is about to look: minting counts as activity. Target listing\n // yields to the network, so a concurrent close or replacement can retire\n // the browser mid-mint — the touch and the retire serialize on the key\n // lock, and a lost touch means the listed targets are already dead:\n // report the browser gone rather than handing out doomed links. Touch\n // *errors* stay best effort — a store 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 view;\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n /**\n * Capped at half the provider's keep-alive window: a browser with\n * continuous CDP traffic must never look older than the window the\n * provider uses to reclaim it.\n */\n get #touchIntervalMs(): number {\n const interval = this.#options.touchIntervalMs ?? SESSION_TOUCH_INTERVAL_MS;\n const { keepAliveMs } = this.#adapter;\n return keepAliveMs === undefined\n ? interval\n : Math.min(interval, Math.floor(keepAliveMs / 2));\n }\n\n /** Lazy: `lifecycle.storage` exists only once `Lifecycle.use()` ran. */\n get #sessionStore(): ListableBrowserSessionStore {\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 // ── Scopes ───────────────────────────────────────────────────────────────\n //\n // Each scope has a small record next to the browser's: the browser it last\n // worked in (`sessionId`) and its tab there. All of a browser's scope\n // records share one lock, so claiming a tab can check the others.\n\n #scopeKey(scope: string): string {\n return `${browserScopeKeyPrefix(this.name)}${scope}`;\n }\n\n /** The lock every scope record of this browser is written under. */\n get #scopesLockKey(): string {\n return browserScopeKeyPrefix(this.name);\n }\n\n /** Every scope record of this browser, by scope. */\n async #listScopes(): Promise<Map<string, StoredBrowserSession>> {\n const prefix = browserScopeKeyPrefix(this.name);\n const entries = await this.#sessionStore.list(prefix);\n const scopes = new Map<string, StoredBrowserSession>();\n for (const [key, entry] of entries) {\n scopes.set(key.slice(prefix.length), entry);\n }\n return scopes;\n }\n\n /**\n * Start a connection's scope: a scope whose record names an earlier\n * browser was restarted, and its record moves to this one. A new record\n * past {@link MAX_BROWSER_SCOPES} evicts the least recently used.\n */\n async #enterScope(\n scope: string,\n sessionId: string\n ): Promise<{ restarted: boolean; activeTargetId?: string }> {\n const key = this.#scopeKey(scope);\n const lock = await this.#sessionStore.acquireLock(this.#scopesLockKey);\n try {\n const entry = await this.#sessionStore.get(key);\n const now = Date.now();\n if (entry?.sessionId === sessionId) {\n await this.#sessionStore.set(key, { ...entry, updatedAt: now });\n return { restarted: false, activeTargetId: entry.activeTargetId };\n }\n await this.#sessionStore.set(key, {\n sessionId,\n createdAt: now,\n updatedAt: now\n });\n if (!entry) await this.#evictScopes();\n return { restarted: entry !== undefined };\n } finally {\n await lock.release();\n }\n }\n\n /** Delete the least recently used scope records past the cap. */\n async #evictScopes(): Promise<void> {\n const scopes = await this.#listScopes();\n if (scopes.size <= MAX_BROWSER_SCOPES) return;\n const oldest = [...scopes]\n .sort(([, a], [, b]) => a.updatedAt - b.updatedAt)\n .slice(0, scopes.size - MAX_BROWSER_SCOPES);\n for (const [scope] of oldest) {\n await this.#sessionStore.delete(this.#scopeKey(scope));\n }\n }\n\n /**\n * Set a scope's tab (or clear it with `undefined`). Never resurrects:\n * does nothing (and returns `false`) once the browser this connection\n * resolved was closed or replaced.\n */\n async #updateScope(\n scope: string,\n sessionId: string,\n targetId: string | undefined\n ): Promise<boolean> {\n const key = this.#scopeKey(scope);\n const lock = await this.#sessionStore.acquireLock(this.#scopesLockKey);\n try {\n const current = await this.#sessionStore.get(this.#key);\n const entry = await this.#sessionStore.get(key);\n if (current?.sessionId !== sessionId || entry?.sessionId !== sessionId) {\n return false;\n }\n await this.#sessionStore.set(key, {\n ...entry,\n activeTargetId: targetId,\n updatedAt: Date.now()\n });\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 provider session (outside any lock) and commit it. The adapter\n * applies the provider's creation options. If a concurrent caller\n * committed an entry first, theirs wins and the redundant session is\n * closed 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 info = await this.#adapter.create();\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 this.#adapter.close(stored.sessionId);\n } catch (error) {\n console.warn(\n `[agents/browser] Failed to close redundant browser session ${stored.sessionId}`,\n error\n );\n }\n return { session: winner, restarted };\n }\n return { session: stored, restarted };\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\n/** A store record without fields that belong to scope records. */\nfunction resolvedFields(session: StoredBrowserSession) {\n const { sessionId, createdAt, updatedAt } = session;\n return { sessionId, createdAt, updatedAt };\n}\n\n/** The tabs scopes other than `scope` are working in, in `sessionId`. */\nfunction activeTargets(\n scopes: Map<string, StoredBrowserSession>,\n scope: string,\n sessionId: string\n): Set<string> {\n const targets = new Set<string>();\n for (const [other, entry] of scopes) {\n if (other === scope || entry.sessionId !== sessionId) continue;\n if (entry.activeTargetId) targets.add(entry.activeTargetId);\n }\n return targets;\n}\n"],"mappings":"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"}