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.
- package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-ohoewW-B.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
- package/dist/{ai-sdk-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
- package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +2 -2
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +3 -3
- package/dist/browser/tools/ai-sdk.d.ts +2 -2
- package/dist/browser/tools/ai-sdk.js +1 -1
- package/dist/browser/tools/pi.d.ts +1 -1
- package/dist/browser/tools/pi.js +3 -3
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/browser/tools/tanstack-ai.d.ts +2 -2
- package/dist/browser/tools/tanstack-ai.js +1 -1
- package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
- package/dist/browser-DccPw_wW.js.map +1 -0
- package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
- package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
- package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
- package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
- package/dist/chat/index.d.ts +143 -71
- package/dist/chat/index.js +269 -110
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +33 -33
- package/dist/{client-Bp-JwL3p.d.ts → client-C27CtRZr.d.ts} +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
- package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
- package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/container/index.js.map +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +13 -5
- package/dist/harness/pi/index.js +8 -214
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
- package/dist/images-xaDv2Fqe.js.map +1 -0
- package/dist/index-DCxk3OSJ.d.ts +378 -0
- package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
- package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
- package/dist/lifecycle-ClT4DmF2.js.map +1 -0
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/models/ai-sdk/index.d.ts +29 -352
- package/dist/models/ai-sdk/index.js +1 -1
- package/dist/models/opencode/index.d.ts +25 -13
- package/dist/models/opencode/index.js +16 -11
- package/dist/models/opencode/index.js.map +1 -1
- package/dist/models/pi-ai/index.d.ts +13 -14
- package/dist/models/pi-ai/index.js +85 -32
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
- package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
- package/dist/session-store-yvwq1f9x.js.map +1 -0
- package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
- package/dist/settings-Dk2il2Hf.js.map +1 -0
- package/dist/skills/index.d.ts +22 -28
- package/dist/skills/index.js +183 -143
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
- package/dist/src-6lr7MtM_.js.map +1 -0
- package/dist/sub-routing.d.ts +1 -1
- package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
- package/dist/tool-DkT_DJBm.js.map +1 -0
- package/dist/transport-CPh7YGiw.d.ts +623 -0
- package/dist/voice/index.d.ts +1 -1
- package/dist/web/fetch/tools/pi.js +2 -2
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/index.js +1 -1
- package/dist/web/search/tools/ai-sdk.js +2 -2
- package/dist/web/search/tools/pi.js +3 -3
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/web/search/tools/tanstack-ai.js +2 -2
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
- package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +1 -1
- package/docs/chat-agents.md +19 -18
- package/docs/harnesses/pi.md +47 -18
- package/docs/models-pi-ai.md +62 -20
- package/docs/models.md +197 -172
- package/package.json +7 -7
- package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
- package/dist/browser-CEdNlsaD.js.map +0 -1
- package/dist/images-C4hiIexM.js.map +0 -1
- package/dist/lifecycle-DGorpAmA.js.map +0 -1
- package/dist/session-store-Do4xkDcf.js.map +0 -1
- package/dist/settings-BjAiPd6Z.js.map +0 -1
- package/dist/settings-DnD__xuE.d.ts +0 -349
- package/dist/src-CQxJFv4l.js.map +0 -1
- package/dist/tool-VF0qiO7q.js.map +0 -1
- package/dist/transport-DCyarnwm.d.ts +0 -200
package/dist/browser/tools/pi.js
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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:
|
|
64
|
-
* browsers itself once `keep_alive` elapses, and the next use
|
|
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,
|
|
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
|
-
*
|
|
72
|
-
*
|
|
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
|
|
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 (!
|
|
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
|
|
165
|
-
*
|
|
241
|
+
* `restarted: true`) and close its provider session. Returns `false` when
|
|
242
|
+
* there was nothing to close.
|
|
166
243
|
*
|
|
167
|
-
* The
|
|
168
|
-
* outcome, and a browser whose
|
|
169
|
-
* store, so the
|
|
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
|
|
261
|
+
await _classPrivateFieldGet2(_adapter, this).close(stored.sessionId);
|
|
184
262
|
} catch (error) {
|
|
185
|
-
console.warn(`[agents/browser] Failed to
|
|
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
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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
|
|
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
|
|
258
|
-
* traffic must never look older than the window the
|
|
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
|
-
|
|
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
|
|
393
|
-
* the provider's creation options. If a concurrent caller
|
|
394
|
-
* entry first, theirs wins and the redundant session is
|
|
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
|
|
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
|
|
489
|
+
await _classPrivateFieldGet2(_adapter, this).close(stored.sessionId);
|
|
430
490
|
} catch (error) {
|
|
431
|
-
console.warn(`[agents/browser] Failed to
|
|
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-
|
|
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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"}
|