agents 0.30.0 → 0.31.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/README.md +4 -2
- package/dist/{agent-routing-CDbpVjYq.d.ts → agent-routing-wS1TimZ_.d.ts} +64 -603
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-C8DH8Yii.d.ts → agent-tools-Bamxxb6Y.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/{ai-iXw0uOGj.d.ts → ai-CDn3Abax.d.ts} +30 -4
- package/dist/{ai-sdk-DRPh2rRa.js → ai-sdk-Kon40i0q.js} +2 -36
- package/dist/ai-sdk-Kon40i0q.js.map +1 -0
- package/dist/ai-sdk-hW3vntnk.d.ts +72 -0
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +16 -3
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/index.d.ts +64 -16
- package/dist/browser/index.js +4 -4
- package/dist/browser/tools/ai-sdk.d.ts +7 -70
- package/dist/browser/tools/ai-sdk.js +4 -4
- package/dist/browser/tools/ai-sdk.js.map +1 -1
- package/dist/browser/tools/pi.d.ts +9 -15
- package/dist/browser/tools/pi.js +6 -14
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/browser/tools/tanstack-ai.d.ts +46 -23
- package/dist/browser/tools/tanstack-ai.js +8 -4
- package/dist/browser/tools/tanstack-ai.js.map +1 -1
- package/dist/{browser-DccPw_wW.js → browser-1eUbPvC2.js} +413 -14
- package/dist/browser-1eUbPvC2.js.map +1 -0
- package/dist/{browser-DlXhSAdf.d.ts → browser-D9RNpJuT.d.ts} +254 -20
- package/dist/browser-tool-BhjCcNVE.d.ts +71 -0
- package/dist/browser-tool-lGnvEBLJ.js +497 -0
- package/dist/browser-tool-lGnvEBLJ.js.map +1 -0
- package/dist/capability-id-B3J8Nok_.js +13 -0
- package/dist/capability-id-B3J8Nok_.js.map +1 -0
- package/dist/chat/index.d.ts +2 -2
- package/dist/chat-sdk/index.d.ts +3 -3
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client-4OlMIJx-.js.map +1 -1
- package/dist/{client-BnYbNT3z.js → client-DMWujQJ1.js} +68 -244
- package/dist/client-DMWujQJ1.js.map +1 -0
- package/dist/{client-C27CtRZr.d.ts → client-wn4-yqGz.d.ts} +9 -7
- package/dist/client.d.ts +1 -1
- package/dist/{connector-DAXBOIIi.js → connector-CM-uuxKM.js} +96 -8
- package/dist/connector-CM-uuxKM.js.map +1 -0
- package/dist/{connector-20DJ6p4k.d.ts → connector-DZ71EjE-.d.ts} +109 -9
- package/dist/experimental/webmcp.js +1 -1
- package/dist/{handler-stateless-Bly_nSH3.js → handler-stateless-B5hU3dxK.js} +35 -2
- package/dist/handler-stateless-B5hU3dxK.js.map +1 -0
- package/dist/{handler-stateless-DxYpJ_XF.d.ts → handler-stateless-C90kD5_R.d.ts} +3 -47
- package/dist/harness/container/index.d.ts +15 -15
- package/dist/harness/opencode/index.js.map +1 -1
- package/dist/harness/pi/index.d.ts +7 -3
- package/dist/harness/pi/index.js +46 -15
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/harness/think/index.d.ts +2 -1
- package/dist/{images-xaDv2Fqe.js → images-CfuRCjwq.js} +1 -5
- package/dist/images-CfuRCjwq.js.map +1 -0
- package/dist/{index-DCxk3OSJ.d.ts → index-8k4qsX1T.d.ts} +2 -28
- package/dist/index.d.ts +14 -14
- package/dist/index.js +2 -2
- package/dist/internal-hosts-Kfp5A392.d.ts +230 -0
- package/dist/internal-kmw70xwR.js.map +1 -1
- package/dist/invoker-BC1SZ3q1.js +26 -0
- package/dist/invoker-BC1SZ3q1.js.map +1 -0
- package/dist/invoker-DoLFPJZV.d.ts +21 -0
- package/dist/mcp/client/index.d.ts +15 -1
- package/dist/mcp/client/index.js +2 -2
- package/dist/mcp/client/x402.d.ts +7 -5
- package/dist/mcp/client/x402.js +34 -1
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +561 -27
- package/dist/mcp/index.js +180 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server/index.d.ts +1 -1
- package/dist/mcp/server/index.js +1 -1
- package/dist/models/ai-sdk/index.d.ts +2 -2
- package/dist/models/ai-sdk/index.js +1 -1
- package/dist/models/opencode/index.d.ts +2 -2
- package/dist/models/opencode/index.js +1 -1
- package/dist/models/pi-ai/index.d.ts +3 -28
- package/dist/models/pi-ai/index.js +31 -16
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/protocol-BEgrJYC5.d.ts +29 -29
- 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-yvwq1f9x.js → session-7CDgVE0z.js} +455 -33
- package/dist/session-7CDgVE0z.js.map +1 -0
- package/dist/{session-store-_IK6dV-w.d.ts → session-store-B7Ca-u1t.d.ts} +8 -1
- package/dist/settings-Dk2il2Hf.js.map +1 -1
- package/dist/{src-DfLzdwlK.js → src-BGolXG_H.js} +18 -2
- package/dist/src-BGolXG_H.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/tool-CTp72Jb_.d.ts +10 -10
- package/dist/tool-DoVberpN.d.ts +22 -22
- package/dist/tool-helpers-BodWi4gd.js +256 -0
- package/dist/tool-helpers-BodWi4gd.js.map +1 -0
- package/dist/{transport-CPh7YGiw.d.ts → transport-5moD2pW5.d.ts} +1 -43
- package/dist/types-D2naO3rA.d.ts +49 -0
- package/dist/voice/index.d.ts +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 +247 -413
- package/docs/get-current-agent.md +4 -7
- package/docs/harnesses/pi.md +28 -28
- package/docs/human-in-the-loop.md +3 -7
- package/docs/index.md +6 -6
- package/docs/mcp-client.md +4 -28
- package/docs/mcp-servers.md +137 -411
- package/docs/mcp-transports.md +30 -289
- package/docs/models-pi-ai.md +9 -11
- package/docs/models.md +1 -8
- package/docs/routing.md +0 -2
- package/docs/securing-mcp-servers.md +120 -304
- package/package.json +8 -3
- package/dist/ai-sdk-DRPh2rRa.js.map +0 -1
- package/dist/browser-DccPw_wW.js.map +0 -1
- package/dist/browser-tool-B1mhifin.js +0 -616
- package/dist/browser-tool-B1mhifin.js.map +0 -1
- package/dist/browser-tool-Cxjk5sgG.d.ts +0 -92
- package/dist/client-BnYbNT3z.js.map +0 -1
- package/dist/connector-DAXBOIIi.js.map +0 -1
- package/dist/handler-stateless-Bly_nSH3.js.map +0 -1
- package/dist/images-xaDv2Fqe.js.map +0 -1
- package/dist/invoker-CG0_p_Wq.d.ts +0 -20
- package/dist/invoker-CHMnoxIA.js +0 -57
- package/dist/invoker-CHMnoxIA.js.map +0 -1
- package/dist/session-store-yvwq1f9x.js.map +0 -1
- package/dist/src-DfLzdwlK.js.map +0 -1
- package/dist/tool-helpers-O5BDVEAY.js +0 -58
- package/dist/tool-helpers-O5BDVEAY.js.map +0 -1
|
@@ -1,7 +1,227 @@
|
|
|
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 { t as browserCapabilityId } from "./capability-id-B3J8Nok_.js";
|
|
5
|
+
import { b as listBrowserTargets, f as acquireBrowserSession, h as connectBrowserSession, n as BrowserSession, o as DurableBrowserSessionStore, p as closeBrowserSession, s as loadCdpSpec, u as createLiveView, y as isMissingBrowserSession } from "./session-7CDgVE0z.js";
|
|
6
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
7
|
+
import { Validator } from "@cfworker/json-schema";
|
|
8
|
+
//#region src/browser/internal-hosts.ts
|
|
9
|
+
/**
|
|
10
|
+
* Internal hostnames: sites only this agent's browser can open.
|
|
11
|
+
*
|
|
12
|
+
* A host maps a hostname such as `preview.internal` to code in your agent
|
|
13
|
+
* (`fetch`) or to a deployed Worker (`worker`). Browser Run sends the
|
|
14
|
+
* browser's requests for that hostname to it instead of the internet, so a
|
|
15
|
+
* page can be served from a sandbox port or a workspace folder, without exposing anything publicly.
|
|
16
|
+
*
|
|
17
|
+
* How a request travels: Browser Run accepts only entrypoints of your own
|
|
18
|
+
* Worker as routes, and keeps them for the whole browser session (observed
|
|
19
|
+
* across redeploys and Durable Object eviction; Browser Run doesn't document
|
|
20
|
+
* this yet). So every `fetch` host routes to {@link InternalHostRouter}, which
|
|
21
|
+
* you export from your main module. Its props say which agent and browser
|
|
22
|
+
* own the host, and nothing else. It calls the agent's `fetchInternalHost()`
|
|
23
|
+
* over RPC, and the agent's `Browser` runs the host's `fetch`. `Agent` has
|
|
24
|
+
* that method built in; a plain Durable Object adds it (see
|
|
25
|
+
* {@link InternalHostMessage}). A `worker` host goes straight to the Worker
|
|
26
|
+
* you gave it.
|
|
27
|
+
*/
|
|
28
|
+
/** The most hostnames one browser can have. A Browser Run limit. */
|
|
29
|
+
const MAX_INTERNAL_HOSTS = 8;
|
|
30
|
+
const HOSTNAME = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/;
|
|
31
|
+
/**
|
|
32
|
+
* Suffixes reserved so they never resolve on the internet. A real domain
|
|
33
|
+
* would be served by the host over http but reach the real site over https
|
|
34
|
+
* (and Chrome tries https first), so internal hostnames must use one.
|
|
35
|
+
* Single labels like `preview` would work, but Browser Run doesn't route
|
|
36
|
+
* labels that are real TLDs (`docs`, `app`) and gives no error for them, so
|
|
37
|
+
* they're rejected rather than checked against a TLD list.
|
|
38
|
+
*/
|
|
39
|
+
const RESERVED_SUFFIXES = [
|
|
40
|
+
".internal",
|
|
41
|
+
".test",
|
|
42
|
+
".example",
|
|
43
|
+
".invalid"
|
|
44
|
+
];
|
|
45
|
+
/** Why `host` can't be an internal hostname, or undefined when it can. */
|
|
46
|
+
function hostnameProblem(host) {
|
|
47
|
+
if (host.includes("://")) return "drop the scheme";
|
|
48
|
+
if (host.includes("/")) return "drop the path";
|
|
49
|
+
if (host.includes(":")) return "drop the port (only port 80 is routed)";
|
|
50
|
+
if (host !== host.toLowerCase()) return "use lowercase";
|
|
51
|
+
if (/^\d+(\.\d+){3}$/.test(host)) return "use a name, not an IP address";
|
|
52
|
+
if (host === "localhost" || host.endsWith(".localhost")) return "Chrome sends localhost to the browser's own machine";
|
|
53
|
+
if (!HOSTNAME.test(host)) return "not a valid hostname";
|
|
54
|
+
if (!RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix))) return `use a name that can't be a real site: end it in ${RESERVED_SUFFIXES.join(", ")}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check `internalHosts` when the provider is created, so a mistake fails at
|
|
58
|
+
* startup instead of at the first browser launch.
|
|
59
|
+
*/
|
|
60
|
+
function validateInternalHosts(options) {
|
|
61
|
+
const hosts = Object.entries(options.hosts);
|
|
62
|
+
if (hosts.length === 0) throw new Error("internalHosts.hosts needs at least one hostname");
|
|
63
|
+
if (hosts.length > 8) throw new Error(`internalHosts has ${hosts.length} hostnames; Browser Run allows at most 8 per browser`);
|
|
64
|
+
for (const [host, config] of hosts) {
|
|
65
|
+
const problem = hostnameProblem(host);
|
|
66
|
+
if (problem) throw new Error(`Internal hostname "${host}": ${problem}`);
|
|
67
|
+
const hasFetch = typeof config.fetch === "function";
|
|
68
|
+
const hasWorker = config.worker !== void 0;
|
|
69
|
+
if (hasFetch === hasWorker) throw new Error(`Internal hostname "${host}" needs exactly one of fetch or worker`);
|
|
70
|
+
if (hasWorker && config.target !== void 0) throw new Error(`Internal hostname "${host}": target needs fetch; a worker host can't be assigned`);
|
|
71
|
+
if (hasFetch && typeof options.router !== "function") throw new Error(`Internal hostname "${host}" uses fetch, which needs internalHosts.router: this.ctx.exports.InternalHostRouter (and \`export { InternalHostRouter } from "agents/browser"\` in your main module)`);
|
|
72
|
+
if (config.target !== void 0) targetJsonSchema(host, config.target);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Identifies the set of hostnames and how each is routed. A browser started
|
|
77
|
+
* with a different set is replaced: Browser Run fixes routes at launch.
|
|
78
|
+
*/
|
|
79
|
+
function internalHostsSignature(options) {
|
|
80
|
+
if (!options) return void 0;
|
|
81
|
+
return Object.entries(options.hosts).map(([host, config]) => `${host}=${config.worker ? "worker" : "fetch"}`).sort().join(",");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The route Browser Run gets for a `worker` host. A `ctx.exports`
|
|
85
|
+
* entrypoint is a loopback stub that Browser Run can't take as is, so it's
|
|
86
|
+
* called (with empty props) to get a Fetcher it can.
|
|
87
|
+
*/
|
|
88
|
+
function workerRoute(worker) {
|
|
89
|
+
if (typeof worker !== "function") return worker;
|
|
90
|
+
return worker({ props: {} });
|
|
91
|
+
}
|
|
92
|
+
function isStandardSchema(schema) {
|
|
93
|
+
return typeof schema === "object" && schema !== null && "~standard" in schema;
|
|
94
|
+
}
|
|
95
|
+
/** A host's target schema as JSON Schema, for the model and for plain validation. */
|
|
96
|
+
function targetJsonSchema(host, schema) {
|
|
97
|
+
if (!isStandardSchema(schema)) {
|
|
98
|
+
if (typeof schema !== "object" || schema === null) throw new Error(`Internal hostname "${host}": target must be a schema`);
|
|
99
|
+
return schema;
|
|
100
|
+
}
|
|
101
|
+
const { jsonSchema } = schema["~standard"];
|
|
102
|
+
if (!jsonSchema?.input) throw new Error(`Internal hostname "${host}": target needs a schema that can describe itself as JSON Schema (Standard JSON Schema, such as zod 4.2+), or a plain JSON Schema object`);
|
|
103
|
+
try {
|
|
104
|
+
return jsonSchema.input({ target: "draft-07" });
|
|
105
|
+
} catch {
|
|
106
|
+
return jsonSchema.input({ target: "draft-2020-12" });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const plainValidators = /* @__PURE__ */ new WeakMap();
|
|
110
|
+
function formatIssues(issues) {
|
|
111
|
+
return issues.map((issue) => issue.path ? `${issue.path}: ${issue.message}` : issue.message).join("; ");
|
|
112
|
+
}
|
|
113
|
+
function pathString(path) {
|
|
114
|
+
if (!path?.length) return void 0;
|
|
115
|
+
return path.map((part) => String(typeof part === "object" ? part.key : part)).join(".");
|
|
116
|
+
}
|
|
117
|
+
function plainIssues(errors) {
|
|
118
|
+
const specific = errors.filter((error) => error.keyword !== "properties");
|
|
119
|
+
return (specific.length > 0 ? specific : errors).map((error) => ({
|
|
120
|
+
message: error.error,
|
|
121
|
+
path: error.instanceLocation.replace(/^#\/?/, "").replaceAll("/", ".") || void 0
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validate a target against a host's schema. Returns the validated value
|
|
126
|
+
* (a Standard Schema's output, which may differ from its input), or a
|
|
127
|
+
* message saying what's wrong.
|
|
128
|
+
*/
|
|
129
|
+
async function validateTarget(schema, value) {
|
|
130
|
+
if (isStandardSchema(schema)) {
|
|
131
|
+
const result = await schema["~standard"].validate(value);
|
|
132
|
+
if (result.issues) return {
|
|
133
|
+
ok: false,
|
|
134
|
+
error: formatIssues(result.issues.map((issue) => ({
|
|
135
|
+
message: issue.message,
|
|
136
|
+
path: pathString(issue.path)
|
|
137
|
+
})))
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
value: result.value
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
let validator = plainValidators.get(schema);
|
|
145
|
+
if (!validator) {
|
|
146
|
+
validator = new Validator(schema, "7", false);
|
|
147
|
+
plainValidators.set(schema, validator);
|
|
148
|
+
}
|
|
149
|
+
const result = validator.validate(value);
|
|
150
|
+
return result.valid ? {
|
|
151
|
+
ok: true,
|
|
152
|
+
value
|
|
153
|
+
} : {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: formatIssues(plainIssues(result.errors))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function isInternalHostMessage(value) {
|
|
159
|
+
if (typeof value !== "object" || value === null) return false;
|
|
160
|
+
const message = value;
|
|
161
|
+
return typeof message.browser === "string" && typeof message.host === "string" && message.request instanceof Request;
|
|
162
|
+
}
|
|
163
|
+
/** A plain-text response the browser shows for a failed internal request. */
|
|
164
|
+
function internalHostError(status, message) {
|
|
165
|
+
return new Response(message, {
|
|
166
|
+
status,
|
|
167
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether an RPC call failed because the agent has no `fetchInternalHost()`.
|
|
172
|
+
* workerd says: `The RPC receiver does not implement the method
|
|
173
|
+
* "fetchInternalHost".` If that wording changes, the router falls back to its
|
|
174
|
+
* generic 502, which names the method too.
|
|
175
|
+
*/
|
|
176
|
+
function isMissingMethod(error) {
|
|
177
|
+
return error instanceof Error && error.message.includes("fetchInternalHost") && /does not implement|is not a function/.test(error.message);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Carries a browser's requests for an internal hostname to the agent that
|
|
181
|
+
* owns it. Export it from your Worker's main module, so
|
|
182
|
+
* `this.ctx.exports.InternalHostRouter` exists:
|
|
183
|
+
*
|
|
184
|
+
* ```ts
|
|
185
|
+
* export { InternalHostRouter } from "agents/browser";
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* It trusts its props: Browser Run sets them from your routes, but another
|
|
189
|
+
* Worker in your account with a service binding to this entrypoint could
|
|
190
|
+
* set its own and reach any agent's hosts. That Worker could already call
|
|
191
|
+
* your agents directly, so this grants nothing new.
|
|
192
|
+
*
|
|
193
|
+
* It holds no logic of its own: it calls the agent's `fetchInternalHost()`
|
|
194
|
+
* with an {@link InternalHostMessage}, and the agent's `Browser` runs the
|
|
195
|
+
* host's `fetch`. `Agent` has `fetchInternalHost()` built in; a plain
|
|
196
|
+
* Durable Object adds it (see {@link InternalHostMessage}).
|
|
197
|
+
*
|
|
198
|
+
* @experimental The API may change before it stabilizes.
|
|
199
|
+
*/
|
|
200
|
+
var InternalHostRouter = class extends WorkerEntrypoint {
|
|
201
|
+
async fetch(request) {
|
|
202
|
+
const props = this.ctx.props;
|
|
203
|
+
if (!isRouterProps(props)) return internalHostError(502, "InternalHostRouter was called without route props. Only Browser Run calls it, with the routes browserRun({ internalHosts }) gives it.");
|
|
204
|
+
const namespace = this.ctx.exports?.[props.className];
|
|
205
|
+
if (!namespace?.getByName) return internalHostError(502, `${props.host}: the agent class "${props.className}" isn't exported from this Worker`);
|
|
206
|
+
try {
|
|
207
|
+
const response = await namespace.getByName(props.name).fetchInternalHost({
|
|
208
|
+
browser: props.browser,
|
|
209
|
+
host: props.host,
|
|
210
|
+
request
|
|
211
|
+
});
|
|
212
|
+
if (response instanceof Response) return response;
|
|
213
|
+
return internalHostError(502, `${props.host}: the agent didn't answer with a Response`);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (isMissingMethod(error)) return internalHostError(502, `${props.host}: ${props.className} has no fetchInternalHost() method. Agent has it built in; on a plain Durable Object add: fetchInternalHost(message) { return this.browser.fetchInternalHost(message); }`);
|
|
216
|
+
console.error(`[agents/browser] ${props.className} failed to serve ${props.host}`, error);
|
|
217
|
+
return internalHostError(502, `${props.host}: the agent failed to serve this request`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function isRouterProps(props) {
|
|
222
|
+
return props?.v === 1 && typeof props.className === "string" && typeof props.name === "string" && typeof props.browser === "string" && typeof props.host === "string";
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
5
225
|
//#region src/browser/providers/provider-adapter.ts
|
|
6
226
|
/**
|
|
7
227
|
* Thrown by {@link BrowserProviderAdapter.connect} when the session no longer
|
|
@@ -24,12 +244,34 @@ var BrowserSessionGoneError = class extends Error {
|
|
|
24
244
|
* ```
|
|
25
245
|
*/
|
|
26
246
|
function browserRun(binding, options = {}) {
|
|
247
|
+
const erased = options;
|
|
248
|
+
if (erased.internalHosts) validateInternalHosts(erased.internalHosts);
|
|
27
249
|
return {
|
|
28
250
|
kind: "browser-run",
|
|
29
251
|
binding,
|
|
30
|
-
options
|
|
252
|
+
options: erased
|
|
31
253
|
};
|
|
32
254
|
}
|
|
255
|
+
function isDataCloneError(error) {
|
|
256
|
+
return error instanceof Error && error.name === "DataCloneError";
|
|
257
|
+
}
|
|
258
|
+
/** One `outboundByHost` route per internal hostname. */
|
|
259
|
+
function internalHostRoutes(internalHosts, owner) {
|
|
260
|
+
const routes = {};
|
|
261
|
+
const hosts = Object.entries(internalHosts.hosts);
|
|
262
|
+
for (const [host, config] of hosts) {
|
|
263
|
+
if (config.worker) {
|
|
264
|
+
routes[host] = workerRoute(config.worker);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
routes[host] = internalHosts.router({ props: {
|
|
268
|
+
v: 1,
|
|
269
|
+
...owner,
|
|
270
|
+
host
|
|
271
|
+
} });
|
|
272
|
+
}
|
|
273
|
+
return routes;
|
|
274
|
+
}
|
|
33
275
|
/** The code behind {@link browserRun}. */
|
|
34
276
|
function browserRunAdapter(provider) {
|
|
35
277
|
const { binding, options } = provider;
|
|
@@ -38,12 +280,22 @@ function browserRunAdapter(provider) {
|
|
|
38
280
|
get keepAliveMs() {
|
|
39
281
|
return keepAliveMs();
|
|
40
282
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
283
|
+
internalHosts: options.internalHosts,
|
|
284
|
+
internalHostsSignature: internalHostsSignature(options.internalHosts),
|
|
285
|
+
async create(context) {
|
|
286
|
+
const { internalHosts } = options;
|
|
287
|
+
if (internalHosts && !context?.owner) throw new Error("Internal hostnames need the Browser installed with Lifecycle.use() on a top-level agent");
|
|
288
|
+
try {
|
|
289
|
+
return await acquireBrowserSession(binding, {
|
|
290
|
+
keepAliveMs: keepAliveMs(),
|
|
291
|
+
recording: options.recording,
|
|
292
|
+
guardrails: options.guardrails,
|
|
293
|
+
...internalHosts && context?.owner ? { outboundByHost: internalHostRoutes(internalHosts, context.owner) } : {}
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (internalHosts && isDataCloneError(error)) throw new Error("Browser Run couldn't take the internal hostname routes: a worker host must be a service binding or an entrypoint of this Worker (this.ctx.exports.Name), not a Dynamic Worker or another object it can't hold", { cause: error });
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
47
299
|
},
|
|
48
300
|
async isAlive(sessionId) {
|
|
49
301
|
try {
|
|
@@ -119,6 +371,15 @@ function retiredBrowserSessionKey(name) {
|
|
|
119
371
|
function browserScopeKeyPrefix(name) {
|
|
120
372
|
return `browser:scope:${name.length}:${name}:`;
|
|
121
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Where a browser's internal-host targets live: one key per host, under a
|
|
376
|
+
* prefix per browser (length-prefixed like scope records, so one browser's
|
|
377
|
+
* prefix never matches another's). Kept apart from the session record, so
|
|
378
|
+
* targets survive the browser being replaced.
|
|
379
|
+
*/
|
|
380
|
+
function internalHostTargetsPrefix(name) {
|
|
381
|
+
return `browser:hosts:${name.length}:${name}:`;
|
|
382
|
+
}
|
|
122
383
|
/** The adapter behind a provider: one explicit case per provider kind. */
|
|
123
384
|
function providerAdapterFor(provider) {
|
|
124
385
|
switch (provider.kind) {
|
|
@@ -151,7 +412,7 @@ var _Browser_brand = /* @__PURE__ */ new WeakSet();
|
|
|
151
412
|
var Browser = class extends LifecycleCapability {
|
|
152
413
|
constructor(options) {
|
|
153
414
|
const name = options.name ?? "default";
|
|
154
|
-
super(
|
|
415
|
+
super(browserCapabilityId(name));
|
|
155
416
|
_classPrivateMethodInitSpec(this, _Browser_brand);
|
|
156
417
|
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
157
418
|
_classPrivateFieldInitSpec(this, _key, void 0);
|
|
@@ -180,7 +441,8 @@ var Browser = class extends LifecycleCapability {
|
|
|
180
441
|
...resolvedFields(session)
|
|
181
442
|
};
|
|
182
443
|
}
|
|
183
|
-
const
|
|
444
|
+
const routesMatch = existing.internalHosts === _classPrivateFieldGet2(_adapter, this).internalHostsSignature;
|
|
445
|
+
const alive = routesMatch && await _classPrivateFieldGet2(_adapter, this).isAlive(existing.sessionId);
|
|
184
446
|
const lock = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).acquireLock(key);
|
|
185
447
|
try {
|
|
186
448
|
const current = await _get_sessionStore.call(_assertClassBrand(_Browser_brand, this)).get(key);
|
|
@@ -201,6 +463,7 @@ var Browser = class extends LifecycleCapability {
|
|
|
201
463
|
} finally {
|
|
202
464
|
await lock.release();
|
|
203
465
|
}
|
|
466
|
+
if (!routesMatch) await _assertClassBrand(_Browser_brand, this, _closeQuietly).call(this, existing.sessionId);
|
|
204
467
|
}
|
|
205
468
|
throw new Error(`Browser "${name}" kept changing concurrently — retry`);
|
|
206
469
|
}
|
|
@@ -237,6 +500,15 @@ var Browser = class extends LifecycleCapability {
|
|
|
237
500
|
}
|
|
238
501
|
}
|
|
239
502
|
/**
|
|
503
|
+
* Start a session on this browser for one run: connects on its first
|
|
504
|
+
* call, resolves `sessionId: "active"` to the scope's tab, and reports
|
|
505
|
+
* what happened to the browser when you close it. `browserTool` opens
|
|
506
|
+
* one per run.
|
|
507
|
+
*/
|
|
508
|
+
session(options) {
|
|
509
|
+
return new BrowserSession(this, options);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
240
512
|
* Close the browser: retire the record (so the next resolve reports
|
|
241
513
|
* `restarted: true`) and close its provider session. Returns `false` when
|
|
242
514
|
* there was nothing to close.
|
|
@@ -283,6 +555,83 @@ var Browser = class extends LifecycleCapability {
|
|
|
283
555
|
}
|
|
284
556
|
return view;
|
|
285
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* The browser's internal hostnames, or `undefined` when its provider has
|
|
560
|
+
* none. See {@link BrowserInternalHosts}.
|
|
561
|
+
*/
|
|
562
|
+
get internalHosts() {
|
|
563
|
+
const config = _classPrivateFieldGet2(_adapter, this).internalHosts;
|
|
564
|
+
if (!config) return void 0;
|
|
565
|
+
const hosts = config.hosts;
|
|
566
|
+
const describe = () => Object.entries(hosts).map(([host, entry]) => ({
|
|
567
|
+
host,
|
|
568
|
+
url: `http://${host}/`,
|
|
569
|
+
...entry.description ? { description: entry.description } : {},
|
|
570
|
+
assignable: entry.target !== void 0,
|
|
571
|
+
...entry.target !== void 0 ? { targetSchema: targetJsonSchema(host, entry.target) } : {}
|
|
572
|
+
}));
|
|
573
|
+
const assignable = (host) => {
|
|
574
|
+
const entry = hosts[host];
|
|
575
|
+
if (!entry || !Object.hasOwn(hosts, host)) throw new Error(`"${host}" isn't an internal hostname. The hostnames are: ${Object.keys(hosts).join(", ")}`);
|
|
576
|
+
if (entry.target === void 0) throw new Error(`"${host}" always serves the same thing; it has no target to assign`);
|
|
577
|
+
return entry.target;
|
|
578
|
+
};
|
|
579
|
+
return {
|
|
580
|
+
describe,
|
|
581
|
+
list: async () => {
|
|
582
|
+
const prefix = internalHostTargetsPrefix(this.name);
|
|
583
|
+
const stored = await this.lifecycle.storage.list({ prefix });
|
|
584
|
+
return describe().map((info) => {
|
|
585
|
+
const key = prefix + info.host;
|
|
586
|
+
return stored.has(key) ? {
|
|
587
|
+
...info,
|
|
588
|
+
target: stored.get(key)
|
|
589
|
+
} : info;
|
|
590
|
+
});
|
|
591
|
+
},
|
|
592
|
+
assign: async (host, target) => {
|
|
593
|
+
const schema = assignable(host);
|
|
594
|
+
if (target === void 0) throw new Error(`browser.assignHost needs a target for ${host}; use browser.clearHost to unassign it`);
|
|
595
|
+
const json = JSON.parse(JSON.stringify(target));
|
|
596
|
+
const checked = await validateTarget(schema, json);
|
|
597
|
+
if (!checked.ok) throw new Error(`Invalid target for ${host}: ${checked.error}`);
|
|
598
|
+
await this.lifecycle.storage.put(_assertClassBrand(_Browser_brand, this, _targetKey).call(this, host), json);
|
|
599
|
+
return { url: `http://${host}/` };
|
|
600
|
+
},
|
|
601
|
+
clear: async (host) => {
|
|
602
|
+
assignable(host);
|
|
603
|
+
return this.lifecycle.storage.delete(_assertClassBrand(_Browser_brand, this, _targetKey).call(this, host));
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Serve one browser request to an internal hostname. `InternalHostRouter`
|
|
609
|
+
* calls the agent's `fetchInternalHost()` with the message; `Agent` passes
|
|
610
|
+
* it here on its own. A plain Durable Object passes it on itself:
|
|
611
|
+
*
|
|
612
|
+
* ```ts
|
|
613
|
+
* fetchInternalHost(message: InternalHostMessage) {
|
|
614
|
+
* return this.browser.fetchInternalHost(message);
|
|
615
|
+
* }
|
|
616
|
+
* ```
|
|
617
|
+
*
|
|
618
|
+
* Answers 502 with the reason when this browser doesn't serve the host.
|
|
619
|
+
*/
|
|
620
|
+
async fetchInternalHost(message) {
|
|
621
|
+
if (message.browser !== this.name) return internalHostError(502, `${message.host} belongs to browser "${message.browser}", but was passed to browser "${this.name}"`);
|
|
622
|
+
return _assertClassBrand(_Browser_brand, this, _serveInternalHost).call(this, message.host, message.request);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* `Agent.fetchInternalHost()` routes here through Lifecycle, so `Agent`
|
|
626
|
+
* needn't import the browser.
|
|
627
|
+
*
|
|
628
|
+
* @internal Called by Lifecycle; not part of the host API.
|
|
629
|
+
*/
|
|
630
|
+
async onRoute(context) {
|
|
631
|
+
const { payload } = context;
|
|
632
|
+
if (!isInternalHostMessage(payload)) throw new Error(`Browser "${this.name}" got an unknown routed message`);
|
|
633
|
+
return this.fetchInternalHost(payload);
|
|
634
|
+
}
|
|
286
635
|
};
|
|
287
636
|
function _connection(resolved, scope, cdp, entered) {
|
|
288
637
|
const { sessionId } = resolved;
|
|
@@ -316,6 +665,54 @@ async function _open(resolved) {
|
|
|
316
665
|
}
|
|
317
666
|
});
|
|
318
667
|
}
|
|
668
|
+
async function _serveInternalHost(host, request) {
|
|
669
|
+
const hosts = _classPrivateFieldGet2(_adapter, this).internalHosts?.hosts;
|
|
670
|
+
const entry = hosts && Object.hasOwn(hosts, host) ? hosts[host] : void 0;
|
|
671
|
+
if (!entry?.fetch) return internalHostError(502, `${host} is no longer an internal hostname of browser "${this.name}"`);
|
|
672
|
+
let target;
|
|
673
|
+
if (entry.target !== void 0) {
|
|
674
|
+
const assigned = await this.lifecycle.storage.get(_assertClassBrand(_Browser_brand, this, _targetKey).call(this, host));
|
|
675
|
+
if (assigned === void 0) return internalHostError(502, `${host} isn't pointed anywhere yet. Call browser.assignHost({ host: "${host}", target }) first.`);
|
|
676
|
+
const checked = await validateTarget(entry.target, assigned);
|
|
677
|
+
if (!checked.ok) return internalHostError(502, `${host}'s target no longer matches its schema (${checked.error}). Call browser.assignHost again.`);
|
|
678
|
+
target = checked.value;
|
|
679
|
+
}
|
|
680
|
+
const fetch = entry.fetch;
|
|
681
|
+
try {
|
|
682
|
+
const response = await this.lifecycle.runInHostContext(() => fetch(request, target), { request });
|
|
683
|
+
if (response instanceof Response) return response;
|
|
684
|
+
return internalHostError(502, `${host}: its fetch didn't return a Response`);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error(`[agents/browser] ${host}: its fetch threw`, error);
|
|
687
|
+
return internalHostError(502, `${host}: its fetch failed`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Which agent and browser the session's routes lead back to. `undefined`
|
|
692
|
+
* without internal hostnames.
|
|
693
|
+
*/
|
|
694
|
+
function _internalHostOwner() {
|
|
695
|
+
if (!_classPrivateFieldGet2(_adapter, this).internalHosts) return void 0;
|
|
696
|
+
const services = this.lifecycleServices;
|
|
697
|
+
if (!services) throw new Error(`Browser "${this.name}" has internal hostnames, which need it installed with Lifecycle.use()`);
|
|
698
|
+
if (services.routes.source !== void 0) throw new Error(`Browser "${this.name}" has internal hostnames, which sub-agents don't support yet`);
|
|
699
|
+
return {
|
|
700
|
+
className: services.className,
|
|
701
|
+
name: services.name,
|
|
702
|
+
browser: this.name
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
/** The storage key of one host's target. */
|
|
706
|
+
function _targetKey(host) {
|
|
707
|
+
return internalHostTargetsPrefix(this.name) + host;
|
|
708
|
+
}
|
|
709
|
+
async function _closeQuietly(sessionId) {
|
|
710
|
+
try {
|
|
711
|
+
await _classPrivateFieldGet2(_adapter, this).close(sessionId);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
console.warn(`[agents/browser] Failed to close browser session ${sessionId}`, error);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
319
716
|
/**
|
|
320
717
|
* Capped at half the provider's keep-alive window: a browser with
|
|
321
718
|
* continuous CDP traffic must never look older than the window the
|
|
@@ -466,12 +863,14 @@ async function _retire(current) {
|
|
|
466
863
|
* was created and closed while this create was in flight.
|
|
467
864
|
*/
|
|
468
865
|
async function _createAndCommit() {
|
|
469
|
-
const info = await _classPrivateFieldGet2(_adapter, this).create();
|
|
866
|
+
const info = await _classPrivateFieldGet2(_adapter, this).create({ owner: _assertClassBrand(_Browser_brand, this, _internalHostOwner).call(this) });
|
|
470
867
|
const now = Date.now();
|
|
868
|
+
const signature = _classPrivateFieldGet2(_adapter, this).internalHostsSignature;
|
|
471
869
|
const stored = {
|
|
472
870
|
sessionId: info.sessionId,
|
|
473
871
|
createdAt: now,
|
|
474
|
-
updatedAt: now
|
|
872
|
+
updatedAt: now,
|
|
873
|
+
...signature !== void 0 ? { internalHosts: signature } : {}
|
|
475
874
|
};
|
|
476
875
|
let winner;
|
|
477
876
|
let restarted;
|
|
@@ -527,6 +926,6 @@ function activeTargets(scopes, scope, sessionId) {
|
|
|
527
926
|
return targets;
|
|
528
927
|
}
|
|
529
928
|
//#endregion
|
|
530
|
-
export { browserRun as i, DEFAULT_BROWSER_SCOPE as n, MAX_BROWSER_SCOPES as r, Browser as t };
|
|
929
|
+
export { InternalHostRouter as a, browserRun as i, DEFAULT_BROWSER_SCOPE as n, MAX_INTERNAL_HOSTS as o, MAX_BROWSER_SCOPES as r, Browser as t };
|
|
531
930
|
|
|
532
|
-
//# sourceMappingURL=browser-
|
|
931
|
+
//# sourceMappingURL=browser-1eUbPvC2.js.map
|