agents 0.26.0 → 0.27.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 +8 -9
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-sdk-OoGkFy3s.js +2444 -0
- package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +1 -1
- package/dist/browser/ai-sdk.js +2 -2
- package/dist/browser/ai-sdk.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
- package/dist/browser-tool-CBqs6hqa.js.map +1 -0
- package/dist/capability-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.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 +144 -0
- package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
- package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
- package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/conversations-DH0ut_8n.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BDC07gYO.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +2 -1
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2529 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
- package/dist/index-Ib_4dwOo.d.ts +200 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-DNrpTu49.js +23 -0
- package/dist/input-schema-DNrpTu49.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-pwTNq9z6.d.ts +134 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
- package/dist/schedules/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-B9pNuxdT.js +2272 -0
- package/dist/sessions-B9pNuxdT.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
- package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/store-DLeqKuj3.d.ts +310 -0
- package/dist/store-vgcPYo8K.js +432 -0
- package/dist/store-vgcPYo8K.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
- package/dist/streams-CITClVer.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
- package/dist/tasks-CkTZNaV1.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
- package/dist/tool-3PA8xjB_.js +421 -0
- package/dist/tool-3PA8xjB_.js.map +1 -0
- package/dist/tool-BPJEbwaX.d.ts +278 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/websearch/index.d.ts +58 -0
- package/dist/websearch/index.js +2 -0
- package/dist/websearch/tools/ai-sdk.d.ts +77 -0
- package/dist/websearch/tools/ai-sdk.js +46 -0
- package/dist/websearch/tools/ai-sdk.js.map +1 -0
- package/dist/websearch/tools/pi.d.ts +82 -0
- package/dist/websearch/tools/pi.js +89 -0
- package/dist/websearch/tools/pi.js.map +1 -0
- package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
- package/dist/websearch/tools/tanstack-ai.js +46 -0
- package/dist/websearch/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
- package/dist/websockets-CmENGb-Z.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- 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 +3 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +2 -1
- package/docs/lifecycle.md +40 -0
- package/docs/search-the-web.md +133 -0
- package/package.json +117 -38
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { r as StreamAccumulator } from "./protocol-eXeJxhgu.js";
|
|
2
|
+
//#region src/chat/broadcast-state.ts
|
|
3
|
+
function textOf(parts) {
|
|
4
|
+
let text = "";
|
|
5
|
+
for (const part of parts) if (part.type === "text") text += part.text;
|
|
6
|
+
return text;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Whether `messages` already holds a copy of the observed message whose text
|
|
10
|
+
* the accumulator does not extend. A healthy live accumulator is always the
|
|
11
|
+
* stored copy plus more; one that diverges holds interleaved or duplicated
|
|
12
|
+
* chunks (#2166), so the stored copy must win or the corruption never heals.
|
|
13
|
+
*/
|
|
14
|
+
function observedDivergesFrom(accumulator, messages) {
|
|
15
|
+
const existing = messages.find((m) => m.id === accumulator.messageId);
|
|
16
|
+
if (!existing) return false;
|
|
17
|
+
return !textOf(accumulator.parts).startsWith(textOf(existing.parts));
|
|
18
|
+
}
|
|
19
|
+
function transition(state, event) {
|
|
20
|
+
switch (event.type) {
|
|
21
|
+
case "clear": return {
|
|
22
|
+
state: { status: "idle" },
|
|
23
|
+
isStreaming: false
|
|
24
|
+
};
|
|
25
|
+
case "resume-fallback": {
|
|
26
|
+
const accumulator = new StreamAccumulator({ messageId: event.messageId });
|
|
27
|
+
return {
|
|
28
|
+
state: {
|
|
29
|
+
status: "observing",
|
|
30
|
+
streamId: event.streamId,
|
|
31
|
+
accumulator
|
|
32
|
+
},
|
|
33
|
+
isStreaming: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
case "response": {
|
|
37
|
+
let accumulator;
|
|
38
|
+
const isReplayedStart = event.replay === true && event.chunkData?.type === "start";
|
|
39
|
+
if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) accumulator = new StreamAccumulator({
|
|
40
|
+
messageId: event.messageId,
|
|
41
|
+
continuation: event.continuation
|
|
42
|
+
});
|
|
43
|
+
else accumulator = state.accumulator;
|
|
44
|
+
if (event.chunkData) accumulator.applyChunk(event.chunkData);
|
|
45
|
+
let messagesUpdate;
|
|
46
|
+
const mergeUnlessDiverged = (prev) => observedDivergesFrom(accumulator, prev) ? prev : accumulator.mergeInto(prev);
|
|
47
|
+
if (event.done) return {
|
|
48
|
+
state: { status: "idle" },
|
|
49
|
+
messagesUpdate: mergeUnlessDiverged,
|
|
50
|
+
isStreaming: false
|
|
51
|
+
};
|
|
52
|
+
if (event.chunkData && !event.replay) messagesUpdate = mergeUnlessDiverged;
|
|
53
|
+
else if (event.replayComplete) messagesUpdate = (prev) => accumulator.mergeInto(prev);
|
|
54
|
+
return {
|
|
55
|
+
state: {
|
|
56
|
+
status: "observing",
|
|
57
|
+
streamId: event.streamId,
|
|
58
|
+
accumulator
|
|
59
|
+
},
|
|
60
|
+
messagesUpdate,
|
|
61
|
+
isStreaming: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { transition as n, observedDivergesFrom as t };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=broadcast-state-CEyI4uAK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast-state-CEyI4uAK.js","names":[],"sources":["../src/chat/broadcast-state.ts"],"sourcesContent":["/**\n * Broadcast stream state machine.\n *\n * Manages the lifecycle of a StreamAccumulator for broadcast/resume\n * streams — the path where this client is *observing* a stream owned\n * by another tab or resumed after reconnect, rather than the transport-\n * owned path that feeds directly into useChat.\n *\n * The transition function is pure (no React, no WebSocket, no side\n * effects). Callers dispatch events and apply the returned state +\n * messagesUpdate. Side effects (sending ACKs, calling onData) stay\n * in the caller.\n */\n\nimport type { UIMessage } from \"ai\";\nimport { StreamAccumulator } from \"./stream-accumulator\";\nimport type { StreamChunkData } from \"./message-builder\";\n\n// ── State ──────────────────────────────────────────────────────────\n\nexport type BroadcastStreamState =\n | { status: \"idle\" }\n | {\n status: \"observing\";\n streamId: string;\n accumulator: StreamAccumulator;\n };\n\n// ── Events ─────────────────────────────────────────────────────────\n\nexport type BroadcastStreamEvent =\n | {\n type: \"response\";\n streamId: string;\n /** Fallback message ID for a new accumulator (ignored if one exists for this stream). */\n messageId: string;\n chunkData?: unknown;\n done?: boolean;\n error?: boolean;\n replay?: boolean;\n replayComplete?: boolean;\n continuation?: boolean;\n /** @deprecated Continuations now seed from current messages in `messagesUpdate`. */\n currentMessages?: UIMessage[];\n }\n | {\n type: \"resume-fallback\";\n streamId: string;\n messageId: string;\n }\n | { type: \"clear\" };\n\n// ── Result ─────────────────────────────────────────────────────────\n\nexport interface TransitionResult {\n state: BroadcastStreamState;\n messagesUpdate?: (prev: UIMessage[]) => UIMessage[];\n isStreaming: boolean;\n}\n\n// ── Snapshot reconciliation ────────────────────────────────────────\n\nfunction textOf(parts: UIMessage[\"parts\"]): string {\n let text = \"\";\n for (const part of parts) {\n if (part.type === \"text\") text += part.text;\n }\n return text;\n}\n\n/**\n * Whether `messages` already holds a copy of the observed message whose text\n * the accumulator does not extend. A healthy live accumulator is always the\n * stored copy plus more; one that diverges holds interleaved or duplicated\n * chunks (#2166), so the stored copy must win or the corruption never heals.\n */\nexport function observedDivergesFrom(\n accumulator: StreamAccumulator,\n messages: UIMessage[]\n): boolean {\n const existing = messages.find((m) => m.id === accumulator.messageId);\n if (!existing) return false;\n return !textOf(accumulator.parts).startsWith(textOf(existing.parts));\n}\n\n// ── Transition ─────────────────────────────────────────────────────\n\nexport function transition(\n state: BroadcastStreamState,\n event: BroadcastStreamEvent\n): TransitionResult {\n switch (event.type) {\n case \"clear\":\n return { state: { status: \"idle\" }, isStreaming: false };\n\n case \"resume-fallback\": {\n const accumulator = new StreamAccumulator({\n messageId: event.messageId\n });\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n isStreaming: true\n };\n }\n\n case \"response\": {\n let accumulator: StreamAccumulator;\n\n // A replayed `start` chunk means the server is re-sending the stream\n // buffer from chunk 0 (resume replay). Re-initialize the accumulator\n // instead of appending into an existing one: replaying into an\n // accumulator that already holds this stream's parts would duplicate\n // them (a second `text-start` unconditionally opens a second text\n // part — #1733). Re-initializing makes replay idempotent under any\n // number of replays, including a second replay triggered by a\n // duplicate STREAM_RESUMING → ACK cycle or a reconnect.\n const isReplayedStart =\n event.replay === true &&\n (event.chunkData as { type?: string } | null | undefined)?.type ===\n \"start\";\n\n if (\n state.status === \"idle\" ||\n state.streamId !== event.streamId ||\n isReplayedStart\n ) {\n accumulator = new StreamAccumulator({\n messageId: event.messageId,\n continuation: event.continuation\n });\n } else {\n accumulator = state.accumulator;\n }\n\n if (event.chunkData) {\n accumulator.applyChunk(event.chunkData as StreamChunkData);\n }\n\n let messagesUpdate: ((prev: UIMessage[]) => UIMessage[]) | undefined;\n\n const mergeUnlessDiverged = (prev: UIMessage[]) =>\n observedDivergesFrom(accumulator, prev)\n ? prev\n : accumulator.mergeInto(prev);\n\n if (event.done) {\n return {\n state: { status: \"idle\" },\n messagesUpdate: mergeUnlessDiverged,\n isStreaming: false\n };\n }\n\n if (event.chunkData && !event.replay) {\n messagesUpdate = mergeUnlessDiverged;\n } else if (event.replayComplete) {\n messagesUpdate = (prev) => accumulator.mergeInto(prev);\n }\n\n return {\n state: {\n status: \"observing\",\n streamId: event.streamId,\n accumulator\n },\n messagesUpdate,\n isStreaming: true\n };\n }\n }\n}\n"],"mappings":";;AA8DA,SAAS,OAAO,OAAmC;CACjD,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,QAAQ,QAAQ,KAAK;CAEzC,OAAO;AACT;;;;;;;AAQA,SAAgB,qBACd,aACA,UACS;CACT,MAAM,WAAW,SAAS,MAAM,MAAM,EAAE,OAAO,YAAY,SAAS;CACpE,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,CAAC,OAAO,YAAY,KAAK,CAAC,CAAC,WAAW,OAAO,SAAS,KAAK,CAAC;AACrE;AAIA,SAAgB,WACd,OACA,OACkB;CAClB,QAAQ,MAAM,MAAd;EACE,KAAK,SACH,OAAO;GAAE,OAAO,EAAE,QAAQ,OAAO;GAAG,aAAa;EAAM;EAEzD,KAAK,mBAAmB;GACtB,MAAM,cAAc,IAAI,kBAAkB,EACxC,WAAW,MAAM,UACnB,CAAC;GACD,OAAO;IACL,OAAO;KACL,QAAQ;KACR,UAAU,MAAM;KAChB;IACF;IACA,aAAa;GACf;EACF;EAEA,KAAK,YAAY;GACf,IAAI;GAUJ,MAAM,kBACJ,MAAM,WAAW,QAChB,MAAM,WAAoD,SACzD;GAEJ,IACE,MAAM,WAAW,UACjB,MAAM,aAAa,MAAM,YACzB,iBAEA,cAAc,IAAI,kBAAkB;IAClC,WAAW,MAAM;IACjB,cAAc,MAAM;GACtB,CAAC;QAED,cAAc,MAAM;GAGtB,IAAI,MAAM,WACR,YAAY,WAAW,MAAM,SAA4B;GAG3D,IAAI;GAEJ,MAAM,uBAAuB,SAC3B,qBAAqB,aAAa,IAAI,IAClC,OACA,YAAY,UAAU,IAAI;GAEhC,IAAI,MAAM,MACR,OAAO;IACL,OAAO,EAAE,QAAQ,OAAO;IACxB,gBAAgB;IAChB,aAAa;GACf;GAGF,IAAI,MAAM,aAAa,CAAC,MAAM,QAC5B,iBAAiB;QACZ,IAAI,MAAM,gBACf,kBAAkB,SAAS,YAAY,UAAU,IAAI;GAGvD,OAAO;IACL,OAAO;KACL,QAAQ;KACR,UAAU,MAAM;KAChB;IACF;IACA;IACA,aAAa;GACf;EACF;CACF;AACF"}
|
package/dist/browser/ai-sdk.d.ts
CHANGED
package/dist/browser/ai-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createBrowserToolCore, t as browserReportNotes } from "../browser-tool-
|
|
1
|
+
import { n as createBrowserToolCore, t as browserReportNotes } from "../browser-tool-CBqs6hqa.js";
|
|
2
2
|
import { i as browserExecuteModelOutput, r as createQuickActionTools } from "../ai-fm7xx1BK.js";
|
|
3
3
|
//#region src/browser/ai-sdk.ts
|
|
4
4
|
/**
|
|
@@ -37,7 +37,7 @@ import { i as browserExecuteModelOutput, r as createQuickActionTools } from "../
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
function browserTool(options) {
|
|
40
|
-
const core = createBrowserToolCore(options, { screenshotHint: "
|
|
40
|
+
const core = createBrowserToolCore(options, { screenshotHint: "To show 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 user sees the image; you get a short text summary." });
|
|
41
41
|
return {
|
|
42
42
|
description: core.description,
|
|
43
43
|
inputSchema: core.inputSchema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sdk.js","names":[],"sources":["../../src/browser/ai-sdk.ts"],"sourcesContent":["/**\n * `agents/browser/ai-sdk` — the AI SDK tool for a persistent `Browser`.\n *\n * The older `createBrowserTools` stays in `agents/browser/ai`.\n */\nimport type { FlexibleSchema } from \"ai\";\nimport {\n browserReportNotes,\n createBrowserToolCore,\n type BrowserToolInput,\n type BrowserToolOptions,\n type BrowserToolOutput\n} from \"./browser-tool\";\nimport {\n browserExecuteModelOutput,\n type BrowserModelOutput\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\n// Stateless Quick Action tools, so one import covers both.\nexport {\n createQuickActionTools,\n type CreateQuickActionToolsOptions,\n type QuickActionToolName\n} from \"./ai\";\n\n/**\n * The AI SDK tool {@link browserTool} returns. Assignable to the AI SDK's\n * `Tool`, and `execute` / `toModelOutput` are always present.\n */\nexport interface BrowserTool {\n description: string;\n inputSchema: FlexibleSchema<BrowserToolInput>;\n execute(\n input: BrowserToolInput,\n options: unknown\n ): Promise<BrowserToolOutput>;\n toModelOutput(options: { output: BrowserToolOutput }): BrowserModelOutput;\n}\n\n/**\n * Create an AI SDK tool that lets the model drive a persistent browser with\n * JavaScript and the Chrome DevTools Protocol.\n *\n * The browser outlives each run: tabs, cookies, and logins carry over. The\n * model never starts, closes, or resets it, and `sessionId: \"active\"`\n * addresses the tab it last worked in. If the browser had to be replaced\n * (it expired or was closed), the code still runs in the new browser and the\n * result says `restarted: true`.\n *\n * Browser Run only (Chromium). Guardrails, `keepAliveMs`, and recording are\n * set on the `Browser`'s provider.\n *\n * @example\n * ```ts\n * import { Browser, browserRun } from \"agents/browser\";\n * import { browserTool } from \"agents/browser/ai-sdk\";\n *\n * export class MyAgent extends Agent<Env> {\n * browser = new Browser({ provider: browserRun(this.env.BROWSER) });\n *\n * constructor(ctx: AgentContext, env: Env) {\n * super(ctx, env);\n * this.lifecycle.use(this.browser);\n * }\n *\n * async onChatMessage() {\n * const tools = {\n * browser: browserTool({ browser: this.browser, loader: this.env.LOADER })\n * };\n * // …pass `tools` to streamText / generateText\n * }\n * }\n * ```\n */\nexport function browserTool(options: BrowserToolOptions): BrowserTool {\n const core = createBrowserToolCore(options, {\n screenshotHint:\n \"
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","names":[],"sources":["../../src/browser/ai-sdk.ts"],"sourcesContent":["/**\n * `agents/browser/ai-sdk` — the AI SDK tool for a persistent `Browser`.\n *\n * The older `createBrowserTools` stays in `agents/browser/ai`.\n */\nimport type { FlexibleSchema } from \"ai\";\nimport {\n browserReportNotes,\n createBrowserToolCore,\n type BrowserToolInput,\n type BrowserToolOptions,\n type BrowserToolOutput\n} from \"./browser-tool\";\nimport {\n browserExecuteModelOutput,\n type BrowserModelOutput\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\n// Stateless Quick Action tools, so one import covers both.\nexport {\n createQuickActionTools,\n type CreateQuickActionToolsOptions,\n type QuickActionToolName\n} from \"./ai\";\n\n/**\n * The AI SDK tool {@link browserTool} returns. Assignable to the AI SDK's\n * `Tool`, and `execute` / `toModelOutput` are always present.\n */\nexport interface BrowserTool {\n description: string;\n inputSchema: FlexibleSchema<BrowserToolInput>;\n execute(\n input: BrowserToolInput,\n options: unknown\n ): Promise<BrowserToolOutput>;\n toModelOutput(options: { output: BrowserToolOutput }): BrowserModelOutput;\n}\n\n/**\n * Create an AI SDK tool that lets the model drive a persistent browser with\n * JavaScript and the Chrome DevTools Protocol.\n *\n * The browser outlives each run: tabs, cookies, and logins carry over. The\n * model never starts, closes, or resets it, and `sessionId: \"active\"`\n * addresses the tab it last worked in. If the browser had to be replaced\n * (it expired or was closed), the code still runs in the new browser and the\n * result says `restarted: true`.\n *\n * Browser Run only (Chromium). Guardrails, `keepAliveMs`, and recording are\n * set on the `Browser`'s provider.\n *\n * @example\n * ```ts\n * import { Browser, browserRun } from \"agents/browser\";\n * import { browserTool } from \"agents/browser/ai-sdk\";\n *\n * export class MyAgent extends Agent<Env> {\n * browser = new Browser({ provider: browserRun(this.env.BROWSER) });\n *\n * constructor(ctx: AgentContext, env: Env) {\n * super(ctx, env);\n * this.lifecycle.use(this.browser);\n * }\n *\n * async onChatMessage() {\n * const tools = {\n * browser: browserTool({ browser: this.browser, loader: this.env.LOADER })\n * };\n * // …pass `tools` to streamText / generateText\n * }\n * }\n * ```\n */\nexport function browserTool(options: BrowserToolOptions): BrowserTool {\n const core = createBrowserToolCore(options, {\n screenshotHint:\n \"To show 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 user sees the image; you get a short text summary.\"\n });\n return {\n description: core.description,\n inputSchema: core.inputSchema,\n execute: core.execute,\n toModelOutput: ({ output }) =>\n browserExecuteModelOutput(output, browserReportNotes(output))\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFA,SAAgB,YAAY,SAA0C;CACpE,MAAM,OAAO,sBAAsB,SAAS,EAC1C,gBACE,4PACJ,CAAC;CACD,OAAO;EACL,aAAa,KAAK;EAClB,aAAa,KAAK;EAClB,SAAS,KAAK;EACd,gBAAgB,EAAE,aAChB,0BAA0B,QAAQ,mBAAmB,MAAM,CAAC;CAChE;AACF"}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
n as BrowserToolOptions,
|
|
5
5
|
r as BrowserToolOutput,
|
|
6
6
|
t as BrowserToolInput
|
|
7
|
-
} from "../browser-tool-
|
|
7
|
+
} from "../browser-tool-Bn-nzJQI.js";
|
|
8
8
|
import { CreateBrowserToolsOptions } from "./ai.js";
|
|
9
9
|
import { ServerTool } from "@tanstack/ai";
|
|
10
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createBrowserToolCore } from "../browser-tool-
|
|
1
|
+
import { n as createBrowserToolCore } from "../browser-tool-CBqs6hqa.js";
|
|
2
2
|
import { a as browserScreenshotOutput, i as browserExecuteModelOutput, t as createBrowserRuntime } from "../ai-fm7xx1BK.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { toolDefinition } from "@tanstack/ai";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as LifecycleCapability } from "./capability-runner-
|
|
1
|
+
import { s as LifecycleCapability } from "./capability-runner-O5VbQzqg.js";
|
|
2
2
|
import {
|
|
3
3
|
$ as CdpConnection,
|
|
4
4
|
F as LiveViewMode,
|
|
@@ -272,4 +272,4 @@ export {
|
|
|
272
272
|
Browser as t,
|
|
273
273
|
CdpItems as u
|
|
274
274
|
};
|
|
275
|
-
//# sourceMappingURL=browser-
|
|
275
|
+
//# sourceMappingURL=browser-tmkcAAI3.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as BrowserConnection } from "./browser-
|
|
1
|
+
import { n as BrowserConnection } from "./browser-tmkcAAI3.js";
|
|
2
2
|
import {
|
|
3
3
|
CodemodeConnector,
|
|
4
4
|
ConnectorTool,
|
|
@@ -37,7 +37,7 @@ interface BrowserToolOptions {
|
|
|
37
37
|
* Requires `"worker_loaders": [{ "binding": "LOADER" }]` in wrangler.jsonc.
|
|
38
38
|
*/
|
|
39
39
|
loader: WorkerLoader;
|
|
40
|
-
/** Sandbox execution timeout in milliseconds. Defaults to
|
|
40
|
+
/** Sandbox execution timeout in milliseconds. Defaults to 60000 (60s). */
|
|
41
41
|
timeoutMs?: number;
|
|
42
42
|
/**
|
|
43
43
|
* Durable Object state for the codemode runtime facet. Optional inside an
|
|
@@ -70,4 +70,4 @@ export {
|
|
|
70
70
|
BrowserToolOutput as r,
|
|
71
71
|
BrowserToolInput as t
|
|
72
72
|
};
|
|
73
|
-
//# sourceMappingURL=browser-tool-
|
|
73
|
+
//# sourceMappingURL=browser-tool-Bn-nzJQI.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _ass
|
|
|
2
2
|
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
3
|
import { E as CdpProtocolError, k as validateConnectorArgs } from "./connector-58XDGvc3.js";
|
|
4
4
|
import { o as resolveCtx, s as transformBrowserResult } from "./ai-fm7xx1BK.js";
|
|
5
|
-
import { CodemodeConnector, DynamicWorkerExecutor, createCodemodeRuntime } from "@cloudflare/codemode";
|
|
5
|
+
import { CodemodeConnector, DynamicWorkerExecutor, MAX_DURABLE_VALUE_BYTES, createCodemodeRuntime } from "@cloudflare/codemode";
|
|
6
6
|
/**
|
|
7
7
|
* `cdp.attachToTarget` returns `{ sessionId: "target:<targetId>" }` — a
|
|
8
8
|
* stable handle rather than a connection-scoped CDP session id, so replayed
|
|
@@ -11,6 +11,18 @@ import { CodemodeConnector, DynamicWorkerExecutor, createCodemodeRuntime } from
|
|
|
11
11
|
const ATTACH_HANDLE_PREFIX = "target:";
|
|
12
12
|
/** Chrome's JSON-RPC code for a CDP session id it doesn't know. */
|
|
13
13
|
const CDP_SESSION_NOT_FOUND = -32001;
|
|
14
|
+
/**
|
|
15
|
+
* Codemode records every `send` result for replay and fails the run when one
|
|
16
|
+
* is over {@link MAX_DURABLE_VALUE_BYTES}, telling the model to write to a
|
|
17
|
+
* file, which this sandbox can't. A full-page screenshot is the usual
|
|
18
|
+
* culprit, so say how to take a smaller one instead.
|
|
19
|
+
*/
|
|
20
|
+
function assertScreenshotFits(result) {
|
|
21
|
+
const size = JSON.stringify(result ?? null).length;
|
|
22
|
+
if (size <= MAX_DURABLE_VALUE_BYTES) return;
|
|
23
|
+
const megabytes = (size / 1e6).toFixed(1);
|
|
24
|
+
throw new Error(`The screenshot result is ${megabytes} MB, over the 1 MB limit on a cdp.send result. Capture just the viewport (no captureBeyondViewport or clip larger than the window), or pass format: "jpeg" with quality: 60.`);
|
|
25
|
+
}
|
|
14
26
|
/** {@link BrowserSessionConnector} `#liveParams`: nothing to detach. */
|
|
15
27
|
const DETACHED = Symbol("detached");
|
|
16
28
|
/** Reports awaiting their tool call; bounded in case a caller never reads. */
|
|
@@ -24,18 +36,21 @@ const BROWSER_LIFETIME_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
24
36
|
"Browser.crash",
|
|
25
37
|
"Browser.crashGpuProcess"
|
|
26
38
|
]);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"Page
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
+
/**
|
|
40
|
+
* How to use the `cdp` connector, one rule per line. `codemode.describe("cdp")`
|
|
41
|
+
* returns these, and `browserTool` puts them in its description. Each rule
|
|
42
|
+
* fixes a mistake models make with CDP, or states something about this
|
|
43
|
+
* browser they can't know; leave out what a model knows already.
|
|
44
|
+
*/
|
|
45
|
+
const BROWSER_INSTRUCTIONS = [
|
|
46
|
+
"The browser persists between runs: tabs, cookies, and logins are still there next time. You never start or close it.",
|
|
47
|
+
"If the browser was replaced (idle timeout, crash), the result says restarted: true and earlier tabs and logins are gone. Before an action that matters (submitting a form, a purchase), check you're on the page you expect.",
|
|
48
|
+
"Page-scoped commands (Page.*, Runtime.*, DOM.*, Input.*, Network.*, Emulation.*) need sessionId: \"active\", the tab you're working in, which is remembered between runs. Browser.* and Target.* commands take no sessionId.",
|
|
49
|
+
"Target.createTarget opens a tab and makes it active. To switch to another open tab, call cdp.attachToTarget({ targetId }); the sessionId it returns keeps working in later runs, while one from a raw Target.attachToTarget only works in the current run.",
|
|
50
|
+
"Tabs a page opens itself (popups, target=_blank links) don't become active; the result lists them as newTabs.",
|
|
51
|
+
"Runtime.evaluate: pass returnByValue: true, or objects come back as a remote reference with no value, and awaitPromise: true for async expressions. The value is at result.value. A thrown error doesn't reject: check exceptionDetails.",
|
|
52
|
+
"Page.navigate returns before the page loads and doesn't throw when it fails: check errorText, then poll document.readyState with Runtime.evaluate until it is 'complete'.",
|
|
53
|
+
"Pick out what you need inside Runtime.evaluate and return only that. Results over about 24,000 characters are cut short, and a single cdp.send result over 1 MB (a full-page screenshot, say) fails the run."
|
|
39
54
|
].join("\n");
|
|
40
55
|
var _browser = /* @__PURE__ */ new WeakMap();
|
|
41
56
|
var _states = /* @__PURE__ */ new WeakMap();
|
|
@@ -69,7 +84,7 @@ var BrowserSessionConnector = class extends CodemodeConnector {
|
|
|
69
84
|
return "cdp";
|
|
70
85
|
}
|
|
71
86
|
instructions() {
|
|
72
|
-
return
|
|
87
|
+
return BROWSER_INSTRUCTIONS;
|
|
73
88
|
}
|
|
74
89
|
tool(name, tool) {
|
|
75
90
|
return validateConnectorArgs(this.name(), name, tool);
|
|
@@ -117,6 +132,7 @@ var BrowserSessionConnector = class extends CodemodeConnector {
|
|
|
117
132
|
throw await _assertClassBrand(_BrowserSessionConnector_brand, this, _teach).call(this, state, error, method, sessionId);
|
|
118
133
|
}
|
|
119
134
|
_assertClassBrand(_BrowserSessionConnector_brand, this, _observe).call(this, state, method, liveParams, result);
|
|
135
|
+
if (method === "Page.captureScreenshot") assertScreenshotFits(result);
|
|
120
136
|
return result;
|
|
121
137
|
}
|
|
122
138
|
},
|
|
@@ -419,6 +435,7 @@ function targetOfHandle(handle) {
|
|
|
419
435
|
* (`agents/browser/ai-sdk`) and TanStack AI (`agents/browser/tanstack-ai`)
|
|
420
436
|
* adapters. Internal — not an entry point.
|
|
421
437
|
*/
|
|
438
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
422
439
|
const RESTARTED_NOTICE = "The browser was restarted before this run (it expired or was closed): earlier tabs, logins, and page state are gone. Your code ran in a fresh browser — navigate again before relying on page state.";
|
|
423
440
|
/**
|
|
424
441
|
* A codemode runtime name per browser. Browser names are host-chosen and
|
|
@@ -454,17 +471,40 @@ function browserReportNotes(output) {
|
|
|
454
471
|
function createBrowserToolCore(options, adapter) {
|
|
455
472
|
const ctx = resolveCtx(options);
|
|
456
473
|
if (!ctx) throw new Error("browserTool requires a Durable Object 'ctx' — pass it explicitly, or call from within an Agent so it can be resolved via getCurrentAgent()");
|
|
474
|
+
const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
457
475
|
const connector = new BrowserSessionConnector(ctx, { browser: options.browser });
|
|
458
|
-
const
|
|
476
|
+
const runtime = createCodemodeRuntime({
|
|
459
477
|
ctx,
|
|
460
478
|
executor: new DynamicWorkerExecutor({
|
|
461
479
|
loader: options.loader,
|
|
462
|
-
timeout
|
|
480
|
+
timeout
|
|
463
481
|
}),
|
|
464
482
|
connectors: [connector],
|
|
465
483
|
name: browserRuntimeName(options.browser.name),
|
|
466
484
|
transformResult: transformBrowserResult
|
|
467
|
-
})
|
|
485
|
+
});
|
|
486
|
+
const description = [
|
|
487
|
+
"Run JavaScript that drives a persistent Chrome browser over the Chrome DevTools Protocol (CDP).",
|
|
488
|
+
"",
|
|
489
|
+
"Write an async arrow function, `async () => { ... }`, and return a small JSON value. Use the `cdp` global to reach the browser. `fetch` is blocked and there is no file system or Node.js API.",
|
|
490
|
+
"",
|
|
491
|
+
"## cdp",
|
|
492
|
+
"",
|
|
493
|
+
"- `cdp.send({ method, params?, sessionId?, timeoutMs? })` sends a CDP command and returns its result without the JSON-RPC envelope: `Page.navigate` returns `{ frameId, errorText? }`, `Runtime.evaluate` returns `{ result: { value }, exceptionDetails? }`.",
|
|
494
|
+
"- `cdp.attachToTarget({ targetId })` makes an open tab active and returns `{ sessionId }`.",
|
|
495
|
+
"- `cdp.spec()` returns the whole protocol (about 650 KB) as `{ domains: [{ name, commands, events, types }] }`. Filter it in code, for example `spec.domains.find((d) => d.name === \"Page\").commands.find((c) => c.name === \"navigate\")`.",
|
|
496
|
+
"- `cdp.getDebugLog()` returns recent protocol traffic, for when a command fails or times out.",
|
|
497
|
+
"",
|
|
498
|
+
"## Rules",
|
|
499
|
+
"",
|
|
500
|
+
[
|
|
501
|
+
BROWSER_INSTRUCTIONS,
|
|
502
|
+
`Each run times out after ${Math.round(timeout / 1e3)}s, so split long jobs (crawling a site, say) across runs.`,
|
|
503
|
+
adapter.screenshotHint
|
|
504
|
+
].join("\n").split("\n").map((rule) => `- ${rule}`).join("\n"),
|
|
505
|
+
"- A `SyntaxError` from \"Failed to start Worker\" means your code didn't parse; its line number points into wrapper code, not yours. Check regex literals and escapes."
|
|
506
|
+
].join("\n");
|
|
507
|
+
const codemodeTool = runtime.tool({ description });
|
|
468
508
|
return {
|
|
469
509
|
description: codemodeTool.description,
|
|
470
510
|
inputSchema: codemodeTool.inputSchema,
|
|
@@ -477,4 +517,4 @@ function createBrowserToolCore(options, adapter) {
|
|
|
477
517
|
//#endregion
|
|
478
518
|
export { createBrowserToolCore as n, browserReportNotes as t };
|
|
479
519
|
|
|
480
|
-
//# sourceMappingURL=browser-tool-
|
|
520
|
+
//# sourceMappingURL=browser-tool-CBqs6hqa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-tool-CBqs6hqa.js","names":[],"sources":["../src/browser/session-connector.ts","../src/browser/browser-tool.ts"],"sourcesContent":["import {\n CodemodeConnector,\n MAX_DURABLE_VALUE_BYTES,\n type ConnectorTool,\n type ConnectorTools,\n type ToolExecuteContext\n} from \"@cloudflare/codemode\";\nimport { validateConnectorArgs } from \"./connector-validation\";\nimport {\n CDP_METHOD_NOT_FOUND,\n CdpProtocolError,\n type CdpConnection\n} from \"./cdp-connection\";\nimport type { BrowserConnection } from \"./browser\";\nimport type { SearchableCdpSpec } from \"./spec\";\n\n/**\n * Where a {@link BrowserSessionConnector} gets its browser. Usually a\n * `Browser` installed on the host's Lifecycle.\n */\nexport interface BrowserSource {\n /** The browser's name, for log messages. */\n readonly name: string;\n /** Reattach to the browser (or replace it) and open a CDP connection. */\n connect(): Promise<BrowserConnection>;\n}\n\nexport interface BrowserSessionConnectorOptions {\n /** The persistent browser this connector drives. */\n browser: BrowserSource;\n}\n\n/** A tab a page opened on its own during an execution. */\nexport interface BrowserNewTab {\n targetId: string;\n url?: string;\n title?: string;\n}\n\n/**\n * What happened to the browser during one execution, beyond the code's own\n * result — read by the tool after the run via\n * {@link BrowserSessionConnector.takeReport}.\n */\nexport interface BrowserExecutionReport {\n /** The browser was replaced before this run; earlier page state is gone. */\n restarted: boolean;\n /** Tabs the page opened itself (popups, `target=_blank`). */\n newTabs: BrowserNewTab[];\n}\n\n/** `sessionId` value that addresses the tab the agent is working in. */\nexport const ACTIVE_PAGE_SESSION = \"active\";\n\n/**\n * `cdp.attachToTarget` returns `{ sessionId: \"target:<targetId>\" }` — a\n * stable handle rather than a connection-scoped CDP session id, so replayed\n * code computes identical arguments. `send` resolves it on the live socket.\n */\nconst ATTACH_HANDLE_PREFIX = \"target:\";\n\n/** Chrome's JSON-RPC code for a CDP session id it doesn't know. */\nconst CDP_SESSION_NOT_FOUND = -32001;\n\n/**\n * Codemode records every `send` result for replay and fails the run when one\n * is over {@link MAX_DURABLE_VALUE_BYTES}, telling the model to write to a\n * file, which this sandbox can't. A full-page screenshot is the usual\n * culprit, so say how to take a smaller one instead.\n */\nfunction assertScreenshotFits(result: unknown): void {\n // Measure the result as codemode stores it (JSON), not just the data, so a\n // screenshot just under the limit can't slip past with its JSON framing.\n const size = JSON.stringify(result ?? null).length;\n if (size <= MAX_DURABLE_VALUE_BYTES) return;\n const megabytes = (size / 1_000_000).toFixed(1);\n throw new Error(\n `The screenshot result is ${megabytes} MB, over the 1 MB limit on a ` +\n `cdp.send result. Capture just the viewport (no captureBeyondViewport ` +\n `or clip larger than the window), or pass format: \"jpeg\" with ` +\n `quality: 60.`\n );\n}\n\n/** {@link BrowserSessionConnector} `#liveParams`: nothing to detach. */\nconst DETACHED = Symbol(\"detached\");\n\n/** Reports awaiting their tool call; bounded in case a caller never reads. */\nconst MAX_PENDING_REPORTS = 100;\n\n/**\n * Commands that would end the browser. The host owns its lifetime, so the\n * model can't close it out from under the agent (and lose its logins).\n */\nconst BROWSER_LIFETIME_COMMANDS = new Set([\n \"Browser.close\",\n \"Browser.crash\",\n \"Browser.crashGpuProcess\"\n]);\n\n/** A CDP target id — for this connector, a tab. */\ntype TargetId = string & { readonly __brand: \"TargetId\" };\n\n/** A CDP session id from `Target.attachToTarget`, valid on one socket only. */\ntype LiveSessionId = string & { readonly __brand: \"LiveSessionId\" };\n\n/** The stable `sessionId` handle `cdp.attachToTarget` gives the model. */\ntype AttachHandle = `${typeof ATTACH_HANDLE_PREFIX}${string}`;\n\ninterface TargetInfo {\n targetId: TargetId;\n type: string;\n url?: string;\n title?: string;\n /** The tab that opened this one (popups, `target=_blank` links). */\n openerId?: TargetId;\n}\n\n/** Per-execution state for one pass. Dropped when the pass ends. */\ninterface ExecutionState {\n connected: BrowserConnection;\n /** Live CDP session per target, valid on this socket only. */\n attached: Map<TargetId, LiveSessionId>;\n /** Page targets open when the pass connected. */\n initialPages: Set<TargetId>;\n /** The tab `\"active\"` resolves to — decided lazily on first use. */\n activeTargetId?: TargetId;\n /** Whether {@link activeTargetId} was decided (vs. never needed). */\n activeResolved: boolean;\n}\n\n/**\n * How to use the `cdp` connector, one rule per line. `codemode.describe(\"cdp\")`\n * returns these, and `browserTool` puts them in its description. Each rule\n * fixes a mistake models make with CDP, or states something about this\n * browser they can't know; leave out what a model knows already.\n */\nexport const BROWSER_INSTRUCTIONS = [\n \"The browser persists between runs: tabs, cookies, and logins are still there next time. You never start or close it.\",\n \"If the browser was replaced (idle timeout, crash), the result says restarted: true and earlier tabs and logins are gone. Before an action that matters (submitting a form, a purchase), check you're on the page you expect.\",\n 'Page-scoped commands (Page.*, Runtime.*, DOM.*, Input.*, Network.*, Emulation.*) need sessionId: \"active\", the tab you\\'re working in, which is remembered between runs. Browser.* and Target.* commands take no sessionId.',\n \"Target.createTarget opens a tab and makes it active. To switch to another open tab, call cdp.attachToTarget({ targetId }); the sessionId it returns keeps working in later runs, while one from a raw Target.attachToTarget only works in the current run.\",\n \"Tabs a page opens itself (popups, target=_blank links) don't become active; the result lists them as newTabs.\",\n \"Runtime.evaluate: pass returnByValue: true, or objects come back as a remote reference with no value, and awaitPromise: true for async expressions. The value is at result.value. A thrown error doesn't reject: check exceptionDetails.\",\n \"Page.navigate returns before the page loads and doesn't throw when it fails: check errorText, then poll document.readyState with Runtime.evaluate until it is 'complete'.\",\n \"Pick out what you need inside Runtime.evaluate and return only that. Results over about 24,000 characters are cut short, and a single cdp.send result over 1 MB (a full-page screenshot, say) fails the run.\"\n].join(\"\\n\");\n\n/**\n * Codemode connector exposing one host-named, persistent browser over the\n * Chrome DevTools Protocol as the `cdp` global.\n *\n * The model never manages sessions: every execution reattaches to the\n * browser (or gets a replacement, reported as `restarted`), and\n * `sessionId: \"active\"` addresses the tab it last worked in. The active tab\n * is stored on the browser's record, so it survives between executions for as\n * long as the tab does.\n *\n * The CDP socket is per pass: opened on the first call, closed when the pass\n * ends. The browser itself outlives the execution.\n */\nexport class BrowserSessionConnector extends CodemodeConnector {\n readonly #browser: BrowserSource;\n #states = new Map<string, ExecutionState>();\n #connecting = new Map<string, Promise<ExecutionState>>();\n /**\n * In memory, on this instance. An execution resumed after an approval\n * pause may run on a fresh connector, whose report starts over. Nothing\n * pauses today — the browser tool wires no approval tools — so reporting\n * across a resume is left for when approvals are added.\n *\n * The same goes for raw CDP session ids from `Target.attachToTarget` sent\n * through `send`: they belong to one pass's socket, so a replay after a\n * pause would reuse a dead id. The stable handles from `attachToTarget`\n * are the replay-safe path.\n */\n #reports = new Map<string, BrowserExecutionReport>();\n\n constructor(\n ctx: DurableObjectState | ExecutionContext,\n options: BrowserSessionConnectorOptions\n ) {\n super(ctx, {});\n this.#browser = options.browser;\n }\n\n name(): string {\n return \"cdp\";\n }\n\n protected instructions(): string {\n return BROWSER_INSTRUCTIONS;\n }\n\n protected override tool(name: string, tool: ConnectorTool): ConnectorTool {\n return validateConnectorArgs(this.name(), name, tool);\n }\n\n protected tools(): ConnectorTools {\n return {\n send: {\n description:\n 'Send a CDP command and return its method result directly. Pass sessionId: \"active\" for page-scoped commands; omit it for Browser/Target commands.',\n inputSchema: {\n type: \"object\",\n properties: {\n method: {\n type: \"string\",\n description: 'CDP method, e.g. \"Page.navigate\"'\n },\n params: {\n type: \"object\",\n description: \"CDP command parameters\"\n },\n sessionId: {\n type: \"string\",\n description:\n '\"active\" for the current tab, or a handle from attachToTarget. Omit for Browser/Target commands.'\n },\n timeoutMs: {\n type: \"number\",\n description: \"Per-command timeout override in milliseconds\"\n }\n },\n required: [\"method\"]\n },\n execute: async (args, ctx) => {\n const { method, params, sessionId, timeoutMs } = args as {\n method: string;\n params?: Record<string, unknown>;\n sessionId?: string;\n timeoutMs?: number;\n };\n if (BROWSER_LIFETIME_COMMANDS.has(method)) {\n throw new Error(\n `${method} is not available: this browser is managed for you ` +\n `and persists between runs. To close a tab, send ` +\n `Target.closeTarget with its targetId.`\n );\n }\n const state = await this.#state(this.#executionId(ctx));\n const live = await this.#resolveSessionId(state, sessionId);\n const liveParams = this.#liveParams(state, method, params);\n if (liveParams === DETACHED) return {};\n let result: unknown;\n try {\n result = await state.connected.cdp.send(method, liveParams, {\n sessionId: live,\n timeoutMs\n });\n } catch (error) {\n throw await this.#teach(state, error, method, sessionId);\n }\n this.#observe(state, method, liveParams, result);\n if (method === \"Page.captureScreenshot\") {\n assertScreenshotFits(result);\n }\n return result;\n }\n },\n\n attachToTarget: {\n description:\n \"Switch to an open tab: make it the active tab and return { sessionId } — a handle you can pass to page-scoped send calls for that tab.\",\n inputSchema: {\n type: \"object\",\n properties: {\n targetId: {\n type: \"string\",\n description: \"Target id from Target.getTargets/createTarget\"\n },\n timeoutMs: { type: \"number\" }\n },\n required: [\"targetId\"]\n },\n outputSchema: {\n type: \"object\",\n properties: {\n sessionId: {\n type: \"string\",\n description: \"Session handle for page-scoped send calls\"\n }\n },\n required: [\"sessionId\"]\n },\n execute: async (args, ctx) => {\n const { targetId, timeoutMs } = args as {\n targetId: TargetId;\n timeoutMs?: number;\n };\n const state = await this.#state(this.#executionId(ctx));\n await this.#attach(state, targetId, timeoutMs);\n this.#setActive(state, targetId);\n return { sessionId: attachHandle(targetId) };\n }\n },\n\n spec: {\n description:\n \"Return the searchable Chrome DevTools Protocol spec: domains with their commands, events, and types.\",\n replay: \"reexecute\",\n inputSchema: { type: \"object\", properties: {} },\n execute: async (_args, ctx): Promise<SearchableCdpSpec> => {\n const state = await this.#state(this.#executionId(ctx));\n return state.connected.spec();\n }\n },\n\n getDebugLog: {\n description:\n \"Return recent CDP protocol traffic (sends, receives, warnings) for this execution — useful to diagnose failures and timeouts.\",\n replay: \"reexecute\",\n inputSchema: {\n type: \"object\",\n properties: {\n limit: {\n type: \"number\",\n minimum: 1,\n description:\n \"Max entries to return, newest last (default 50; only the last 400 are kept)\"\n }\n }\n },\n execute: async (args, ctx) => {\n const { limit } = (args ?? {}) as { limit?: number };\n const state = await this.#state(this.#executionId(ctx));\n return state.connected.cdp.getDebugLog(limit);\n }\n },\n\n clearDebugLog: {\n description: \"Clear the CDP debug log for this execution.\",\n inputSchema: { type: \"object\", properties: {} },\n execute: async (_args, ctx) => {\n const state = await this.#state(this.#executionId(ctx));\n state.connected.cdp.clearDebugLog();\n return null;\n }\n }\n };\n }\n\n // ── Codemode execution hooks ─────────────────────────────────────────────\n\n /**\n * The pass is over: note tabs the page opened, save the active tab on the\n * browser's record, and drop the socket. The browser stays alive.\n */\n override async onPassEnd(executionId: string): Promise<void> {\n const state = this.#states.get(executionId);\n if (!state) return;\n this.#states.delete(executionId);\n try {\n const open = await this.#recordNewTabs(executionId, state);\n await this.#saveActiveTarget(state, open);\n } finally {\n state.connected.cdp.disconnect();\n }\n }\n\n /** Nothing per execution outlives the pass; the named browser persists. */\n override async disposeExecution(executionId: string): Promise<void> {\n const state = this.#states.get(executionId);\n if (!state) return;\n this.#states.delete(executionId);\n state.connected.cdp.disconnect();\n }\n\n /**\n * Take (and forget) what happened to the browser during an execution.\n * `undefined` when the execution never touched the browser. Covers the\n * passes this instance ran; see the note on `#reports`.\n */\n takeReport(executionId: string): BrowserExecutionReport | undefined {\n const report = this.#reports.get(executionId);\n this.#reports.delete(executionId);\n return report;\n }\n\n // ── Internals ────────────────────────────────────────────────────────────\n\n #executionId(ctx: ToolExecuteContext | undefined): string {\n if (!ctx?.executionId) {\n throw new Error(\"Browser tools must run inside a codemode execution\");\n }\n return ctx.executionId;\n }\n\n /**\n * Connect once per pass. Concurrent first calls (model code that uses\n * Promise.all despite the instructions) share one connect.\n */\n #state(executionId: string): Promise<ExecutionState> {\n const existing = this.#states.get(executionId);\n if (existing) return Promise.resolve(existing);\n const inFlight = this.#connecting.get(executionId);\n if (inFlight) return inFlight;\n const promise = this.#connect(executionId).finally(() => {\n this.#connecting.delete(executionId);\n });\n this.#connecting.set(executionId, promise);\n return promise;\n }\n\n async #connect(executionId: string): Promise<ExecutionState> {\n const connected = await this.#browser.connect();\n let initialPages: TargetInfo[];\n try {\n initialPages = await this.#pages(connected.cdp);\n } catch (error) {\n connected.cdp.disconnect();\n throw error;\n }\n const state: ExecutionState = {\n connected,\n attached: new Map(),\n initialPages: new Set(initialPages.map((page) => page.targetId)),\n activeResolved: false\n };\n this.#states.set(executionId, state);\n this.#report(executionId, connected.restarted);\n return state;\n }\n\n #report(executionId: string, restarted: boolean): BrowserExecutionReport {\n let report = this.#reports.get(executionId);\n if (!report) {\n report = { restarted, newTabs: [] };\n this.#reports.set(executionId, report);\n if (this.#reports.size > MAX_PENDING_REPORTS) {\n const oldest = this.#reports.keys().next().value;\n if (oldest !== undefined) this.#reports.delete(oldest);\n }\n }\n // A replacement on any pass means earlier page state is gone.\n report.restarted ||= restarted;\n return report;\n }\n\n /**\n * Add tabs the page opened this pass to the execution's report, and drop\n * earlier ones that have since closed: `newTabs` lists page-opened tabs\n * still open when the execution ends, across every pass. Returns the open\n * tabs, or `undefined` when the browser couldn't be asked.\n */\n async #recordNewTabs(\n executionId: string,\n state: ExecutionState\n ): Promise<Set<TargetId> | undefined> {\n let pages: TargetInfo[];\n try {\n pages = await this.#pages(state.connected.cdp);\n } catch (error) {\n this.#warn(\"list the open tabs\", error);\n return undefined;\n }\n const report = this.#report(executionId, state.connected.restarted);\n const opened = new Set<string>(report.newTabs.map((tab) => tab.targetId));\n for (const page of pages) {\n // Chrome sets openerId only on tabs a page opened, never on\n // Target.createTarget tabs — including ones another execution sharing\n // this browser created meanwhile.\n if (!state.initialPages.has(page.targetId) && page.openerId) {\n opened.add(page.targetId);\n }\n }\n report.newTabs = pages\n .filter((page) => opened.has(page.targetId))\n .map(({ targetId, url, title }) => ({ targetId, url, title }));\n return new Set(pages.map((page) => page.targetId));\n }\n\n /**\n * Save the tab this pass settled on, forgetting one that has since closed.\n * Without the list of open tabs, save the choice unchecked: the next\n * execution checks the stored tab is still open before using it.\n */\n async #saveActiveTarget(\n state: ExecutionState,\n open: Set<TargetId> | undefined\n ): Promise<void> {\n const stored = state.connected.activeTargetId as TargetId | undefined;\n const candidate = state.activeResolved ? state.activeTargetId : stored;\n const active =\n candidate && (!open || open.has(candidate)) ? candidate : undefined;\n if (active === stored) return;\n try {\n await state.connected.setActiveTarget(active);\n } catch (error) {\n this.#warn(\"save the active tab\", error);\n }\n }\n\n #warn(action: string, error: unknown): void {\n console.warn(\n `[agents/browser] Failed to ${action} for browser \"${this.#browser.name}\"`,\n error\n );\n }\n\n async #pages(cdp: CdpConnection): Promise<TargetInfo[]> {\n const result = (await cdp.send(\"Target.getTargets\")) as {\n targetInfos?: TargetInfo[];\n };\n return (result?.targetInfos ?? []).filter((info) => info.type === \"page\");\n }\n\n async #resolveSessionId(\n state: ExecutionState,\n sessionId: string | undefined\n ): Promise<LiveSessionId | undefined> {\n if (sessionId === ACTIVE_PAGE_SESSION) {\n return this.#attach(state, await this.#activeTarget(state));\n }\n if (isAttachHandle(sessionId)) {\n return this.#attach(state, targetOfHandle(sessionId));\n }\n // Omitted, or a raw CDP session id the model got from send().\n return sessionId as LiveSessionId | undefined;\n }\n\n /**\n * Decide which tab `\"active\"` means, once per pass: the stored tab if it\n * is still open, else the only open tab, else a new blank tab when none\n * are open, else the first listed tab.\n */\n async #activeTarget(state: ExecutionState): Promise<TargetId> {\n if (state.activeTargetId) return state.activeTargetId;\n const pages = await this.#pages(state.connected.cdp);\n const stored = state.connected.activeTargetId;\n let targetId = pages.find((page) => page.targetId === stored)?.targetId;\n if (!targetId && pages.length === 0) {\n const created = (await state.connected.cdp.send(\"Target.createTarget\", {\n url: \"about:blank\"\n })) as { targetId: TargetId };\n targetId = created.targetId;\n }\n targetId ??= pages[0].targetId;\n this.#setActive(state, targetId);\n return targetId;\n }\n\n #setActive(state: ExecutionState, targetId: TargetId | undefined): void {\n state.activeTargetId = targetId;\n state.activeResolved = true;\n }\n\n async #attach(\n state: ExecutionState,\n targetId: TargetId,\n timeoutMs?: number\n ): Promise<LiveSessionId> {\n const existing = state.attached.get(targetId);\n if (existing) return existing;\n const live = (await state.connected.cdp.attachToTarget(targetId, {\n timeoutMs\n })) as LiveSessionId;\n state.attached.set(targetId, live);\n return live;\n }\n\n /** Track tab changes the model makes through raw Target commands. */\n #observe(\n state: ExecutionState,\n method: string,\n params: Record<string, unknown> | undefined,\n result: unknown\n ): void {\n const targetId =\n typeof params?.targetId === \"string\"\n ? (params.targetId as TargetId)\n : undefined;\n if (method === \"Target.createTarget\") {\n const created = (result as { targetId?: unknown } | undefined)?.targetId;\n if (typeof created === \"string\") {\n this.#setActive(state, created as TargetId);\n }\n } else if (method === \"Target.attachToTarget\" && targetId) {\n this.#setActive(state, targetId);\n } else if (method === \"Target.closeTarget\" && targetId) {\n state.attached.delete(targetId);\n // The next \"active\" use picks a tab afresh.\n if (state.activeTargetId === targetId) state.activeTargetId = undefined;\n } else if (method === \"Target.detachFromTarget\") {\n // Forget a session the model detached, so the next use reattaches.\n for (const [target, live] of state.attached) {\n if (target === targetId || live === params?.sessionId) {\n state.attached.delete(target);\n }\n }\n }\n }\n\n /**\n * Swap a handle in `Target.detachFromTarget`'s `sessionId` param for the\n * live CDP session id. Returns {@link DETACHED} when the handle's tab isn't\n * attached on this socket, so there is nothing to detach.\n */\n #liveParams(\n state: ExecutionState,\n method: string,\n params: Record<string, unknown> | undefined\n ): Record<string, unknown> | undefined | typeof DETACHED {\n const handle = params?.sessionId;\n if (method !== \"Target.detachFromTarget\" || typeof handle !== \"string\") {\n return params;\n }\n if (!isAttachHandle(handle)) return params;\n const live = state.attached.get(targetOfHandle(handle));\n return live ? { ...params, sessionId: live } : DETACHED;\n }\n\n /**\n * Turn the common protocol mistakes into instructions: sending an\n * event as a command, and a page-scoped command without a session.\n */\n async #teach(\n state: ExecutionState,\n error: unknown,\n method: string,\n sessionId: string | undefined\n ): Promise<unknown> {\n if (!(error instanceof CdpProtocolError)) return error;\n if (\n error.code === CDP_SESSION_NOT_FOUND &&\n sessionId &&\n sessionId !== ACTIVE_PAGE_SESSION &&\n !isAttachHandle(sessionId)\n ) {\n return new Error(\n `${error.message}. CDP session ids don't carry over between runs. ` +\n `Use sessionId: \"active\" for the current tab, or the handle from ` +\n `cdp.attachToTarget({ targetId }) for another tab.`\n );\n }\n if (error.code !== CDP_METHOD_NOT_FOUND) return error;\n if (await this.#isEvent(state, method)) {\n return new Error(\n `${error.message}. '${method}' is a CDP *event*, not a command — it ` +\n `cannot be sent. To wait for page state, poll instead (e.g. ` +\n `Runtime.evaluate of document.readyState until \"complete\").`\n );\n }\n if (!sessionId) {\n return new Error(\n `${error.message}. '${method}' is page-scoped: pass sessionId: ` +\n `\"active\" to run it in the current tab — ` +\n `cdp.send({ method: \"${method}\", params, sessionId: \"active\" }).`\n );\n }\n return error;\n }\n\n async #isEvent(state: ExecutionState, method: string): Promise<boolean> {\n try {\n const spec = await state.connected.spec();\n const domain = method.split(\".\")[0];\n return spec.domains.some(\n (d) => d.name === domain && d.events.some((e) => e.event === method)\n );\n } catch {\n return false;\n }\n }\n}\n\nfunction attachHandle(targetId: TargetId): AttachHandle {\n return `${ATTACH_HANDLE_PREFIX}${targetId}`;\n}\n\nfunction isAttachHandle(\n sessionId: string | undefined\n): sessionId is AttachHandle {\n return sessionId?.startsWith(ATTACH_HANDLE_PREFIX) ?? false;\n}\n\nfunction targetOfHandle(handle: AttachHandle): TargetId {\n return handle.slice(ATTACH_HANDLE_PREFIX.length) as TargetId;\n}\n","/**\n * The harness-neutral core of `browserTool`, shared by the AI SDK\n * (`agents/browser/ai-sdk`) and TanStack AI (`agents/browser/tanstack-ai`)\n * adapters. Internal — not an entry point.\n */\nimport {\n createCodemodeRuntime,\n DynamicWorkerExecutor,\n type ProxyToolOutput\n} from \"@cloudflare/codemode\";\nimport {\n BROWSER_INSTRUCTIONS,\n BrowserSessionConnector,\n type BrowserExecutionReport,\n type BrowserNewTab,\n type BrowserSource\n} from \"./session-connector\";\nimport { resolveCtx, transformBrowserResult } from \"./tool-helpers\";\n\nexport interface BrowserToolOptions {\n /**\n * The browser the tool drives — usually a `Browser` installed on the\n * host's Lifecycle. Tools built from the same `Browser` share it.\n */\n browser: BrowserSource;\n\n /**\n * WorkerLoader binding for sandboxed code execution.\n *\n * Requires `\"worker_loaders\": [{ \"binding\": \"LOADER\" }]` in wrangler.jsonc.\n */\n loader: WorkerLoader;\n\n /** Sandbox execution timeout in milliseconds. Defaults to 60000 (60s). */\n timeoutMs?: number;\n\n /**\n * Durable Object state for the codemode runtime facet. Optional inside an\n * Agent (resolved via `getCurrentAgent()`); pass it explicitly elsewhere.\n *\n * The worker must export the `CodemodeRuntime` class (the\n * `@cloudflare/codemode/vite` plugin does this automatically, or add\n * `export { CodemodeRuntime } from \"@cloudflare/codemode\"` to your entry).\n */\n ctx?: DurableObjectState;\n}\n\n/** The model's input to `browserTool`: JavaScript to run. */\nexport interface BrowserToolInput {\n code: string;\n}\n\n/**\n * The result of one `browserTool` run: the codemode execution result,\n * plus what happened to the browser.\n */\nexport type BrowserToolOutput = ProxyToolOutput & {\n /** The browser was replaced before this run; earlier tabs are gone. */\n restarted?: true;\n /** A sentence telling the model what `restarted` means for it. */\n notice?: string;\n /** Tabs the page opened itself (popups, `target=_blank` links). */\n newTabs?: BrowserNewTab[];\n};\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\nconst RESTARTED_NOTICE =\n \"The browser was restarted before this run (it expired or was closed): earlier tabs, logins, and page state are gone. Your code ran in a fresh browser — navigate again before relying on page state.\";\n\n/**\n * A codemode runtime name per browser. Browser names are host-chosen and\n * unrestricted; runtime names allow only `[a-zA-Z0-9_.-]`, so anything else\n * (including `.`, the escape) is hex-escaped.\n */\nfunction browserRuntimeName(name: string): string {\n const escaped = name.replace(\n /[^a-zA-Z0-9_-]/g,\n (char) => `.${char.codePointAt(0)?.toString(16)}.`\n );\n return `browser-tool_${escaped}`;\n}\n\n/** Add what happened to the browser to the codemode result. */\nfunction withBrowserReport(\n output: ProxyToolOutput,\n report: BrowserExecutionReport | undefined\n): BrowserToolOutput {\n if (!report) return output;\n return {\n ...output,\n ...(report.restarted\n ? { restarted: true as const, notice: RESTARTED_NOTICE }\n : {}),\n ...(report.newTabs.length > 0 ? { newTabs: report.newTabs } : {})\n };\n}\n\n/** Report sentences that must survive a screenshot's text summary. */\nexport function browserReportNotes(output: BrowserToolOutput): string[] {\n const notes: string[] = [];\n if (output.notice) notes.push(output.notice);\n if (output.newTabs?.length) {\n notes.push(\n `New tabs opened by the page: ${JSON.stringify(output.newTabs)}`\n );\n }\n return notes;\n}\n\n/**\n * Build the codemode runtime and connector for one `browserTool`, and wrap\n * `execute` so each result carries the browser report.\n */\nexport function createBrowserToolCore(\n options: BrowserToolOptions,\n adapter: {\n /** What the adapter does with screenshots, told to the model. */\n screenshotHint: string;\n }\n) {\n const ctx = resolveCtx(options);\n if (!ctx) {\n throw new Error(\n \"browserTool requires a Durable Object 'ctx' — pass it explicitly, or call from within an Agent so it can be resolved via getCurrentAgent()\"\n );\n }\n\n const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const connector = new BrowserSessionConnector(ctx, {\n browser: options.browser\n });\n const runtime = createCodemodeRuntime({\n ctx,\n executor: new DynamicWorkerExecutor({\n loader: options.loader,\n timeout\n }),\n connectors: [connector],\n name: browserRuntimeName(options.browser.name),\n transformResult: transformBrowserResult\n });\n\n // Our own description, not codemode's generic one: that one tells the\n // model to search before every new connector (search doesn't cover CDP\n // commands), and describes approvals, snippets, and replay, which this\n // tool doesn't use.\n const rules = [\n BROWSER_INSTRUCTIONS,\n `Each run times out after ${Math.round(timeout / 1000)}s, so split long jobs (crawling a site, say) across runs.`,\n adapter.screenshotHint\n ]\n .join(\"\\n\")\n .split(\"\\n\")\n .map((rule) => `- ${rule}`)\n .join(\"\\n\");\n const description = [\n \"Run JavaScript that drives a persistent Chrome browser over the Chrome DevTools Protocol (CDP).\",\n \"\",\n \"Write an async arrow function, `async () => { ... }`, and return a small JSON value. Use the `cdp` global to reach the browser. `fetch` is blocked and there is no file system or Node.js API.\",\n \"\",\n \"## cdp\",\n \"\",\n \"- `cdp.send({ method, params?, sessionId?, timeoutMs? })` sends a CDP command and returns its result without the JSON-RPC envelope: `Page.navigate` returns `{ frameId, errorText? }`, `Runtime.evaluate` returns `{ result: { value }, exceptionDetails? }`.\",\n \"- `cdp.attachToTarget({ targetId })` makes an open tab active and returns `{ sessionId }`.\",\n '- `cdp.spec()` returns the whole protocol (about 650 KB) as `{ domains: [{ name, commands, events, types }] }`. Filter it in code, for example `spec.domains.find((d) => d.name === \"Page\").commands.find((c) => c.name === \"navigate\")`.',\n \"- `cdp.getDebugLog()` returns recent protocol traffic, for when a command fails or times out.\",\n \"\",\n \"## Rules\",\n \"\",\n rules,\n '- A `SyntaxError` from \"Failed to start Worker\" means your code didn\\'t parse; its line number points into wrapper code, not yours. Check regex literals and escapes.'\n ].join(\"\\n\");\n const codemodeTool = runtime.tool({ description });\n\n return {\n description: codemodeTool.description,\n inputSchema: codemodeTool.inputSchema,\n execute: async (\n input: BrowserToolInput,\n executeOptions?: unknown\n ): Promise<BrowserToolOutput> => {\n const output = await codemodeTool.execute(input, executeOptions);\n return withBrowserReport(\n output,\n connector.takeReport(output.executionId)\n );\n }\n };\n}\n"],"mappings":";;;;;;;;;;AA2DA,MAAM,uBAAuB;;AAG7B,MAAM,wBAAwB;;;;;;;AAQ9B,SAAS,qBAAqB,QAAuB;CAGnD,MAAM,OAAO,KAAK,UAAU,UAAU,IAAI,CAAC,CAAC;CAC5C,IAAI,QAAQ,yBAAyB;CACrC,MAAM,aAAa,OAAO,IAAA,CAAW,QAAQ,CAAC;CAC9C,MAAM,IAAI,MACR,4BAA4B,UAAU,6KAIxC;AACF;;AAGA,MAAM,WAAW,OAAO,UAAU;;AAGlC,MAAM,sBAAsB;;;;;AAM5B,MAAM,4CAA4B,IAAI,IAAI;CACxC;CACA;CACA;AACF,CAAC;;;;;;;AAuCD,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;;;;;;;;;;AAeX,IAAa,0BAAb,cAA6C,kBAAkB;CAiB7D,YACE,KACA,SACA;EACA,MAAM,KAAK,CAAC,CAAC;;;4DAnBL,IAAI,IAA4B,CAAA;gEAC5B,IAAI,IAAqC,CAAA;6DAY5C,IAAI,IAAoC,CAAA;EAOjD,uBAAA,UAAA,MAAgB,QAAQ,OAAA;CAC1B;CAEA,OAAe;EACb,OAAO;CACT;CAEA,eAAiC;EAC/B,OAAO;CACT;CAEA,KAAwB,MAAc,MAAoC;EACxE,OAAO,sBAAsB,KAAK,KAAK,GAAG,MAAM,IAAI;CACtD;CAEA,QAAkC;EAChC,OAAO;GACL,MAAM;IACJ,aACE;IACF,aAAa;KACX,MAAM;KACN,YAAY;MACV,QAAQ;OACN,MAAM;OACN,aAAa;MACf;MACA,QAAQ;OACN,MAAM;OACN,aAAa;MACf;MACA,WAAW;OACT,MAAM;OACN,aACE;MACJ;MACA,WAAW;OACT,MAAM;OACN,aAAa;MACf;KACF;KACA,UAAU,CAAC,QAAQ;IACrB;IACA,SAAS,OAAO,MAAM,QAAQ;KAC5B,MAAM,EAAE,QAAQ,QAAQ,WAAW,cAAc;KAMjD,IAAI,0BAA0B,IAAI,MAAM,GACtC,MAAM,IAAI,MACR,GAAG,OAAO,yIAGZ;KAEF,MAAM,QAAQ,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAA,kBAAA,gCAAY,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC;KACtD,MAAM,OAAO,MAAA,kBAAA,gCAAM,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,OAAO,SAAS;KAC1D,MAAM,aAAA,kBAAA,gCAAa,MAAA,WAAA,CAAA,CAAA,KAAA,MAAiB,OAAO,QAAQ,MAAM;KACzD,IAAI,eAAe,UAAU,OAAO,CAAC;KACrC,IAAI;KACJ,IAAI;MACF,SAAS,MAAM,MAAM,UAAU,IAAI,KAAK,QAAQ,YAAY;OAC1D,WAAW;OACX;MACF,CAAC;KACH,SAAS,OAAO;MACd,MAAM,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,OAAO,OAAO,QAAQ,SAAS;KACzD;KACA,kBAAA,gCAAA,MAAA,QAAA,CAAA,CAAA,KAAA,MAAc,OAAO,QAAQ,YAAY,MAAM;KAC/C,IAAI,WAAW,0BACb,qBAAqB,MAAM;KAE7B,OAAO;IACT;GACF;GAEA,gBAAgB;IACd,aACE;IACF,aAAa;KACX,MAAM;KACN,YAAY;MACV,UAAU;OACR,MAAM;OACN,aAAa;MACf;MACA,WAAW,EAAE,MAAM,SAAS;KAC9B;KACA,UAAU,CAAC,UAAU;IACvB;IACA,cAAc;KACZ,MAAM;KACN,YAAY,EACV,WAAW;MACT,MAAM;MACN,aAAa;KACf,EACF;KACA,UAAU,CAAC,WAAW;IACxB;IACA,SAAS,OAAO,MAAM,QAAQ;KAC5B,MAAM,EAAE,UAAU,cAAc;KAIhC,MAAM,QAAQ,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAA,kBAAA,gCAAY,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC;KACtD,MAAA,kBAAA,gCAAM,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,OAAO,UAAU,SAAS;KAC7C,kBAAA,gCAAA,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,OAAO,QAAQ;KAC/B,OAAO,EAAE,WAAW,aAAa,QAAQ,EAAE;IAC7C;GACF;GAEA,MAAM;IACJ,aACE;IACF,QAAQ;IACR,aAAa;KAAE,MAAM;KAAU,YAAY,CAAC;IAAE;IAC9C,SAAS,OAAO,OAAO,QAAoC;KAEzD,QAAO,MAAA,kBAAA,gCADa,MAAA,MAAA,CAAA,CAAA,KAAA,MAAA,kBAAA,gCAAY,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC,EAAA,CACzC,UAAU,KAAK;IAC9B;GACF;GAEA,aAAa;IACX,aACE;IACF,QAAQ;IACR,aAAa;KACX,MAAM;KACN,YAAY,EACV,OAAO;MACL,MAAM;MACN,SAAS;MACT,aACE;KACJ,EACF;IACF;IACA,SAAS,OAAO,MAAM,QAAQ;KAC5B,MAAM,EAAE,UAAW,QAAQ,CAAC;KAE5B,QAAO,MAAA,kBAAA,gCADa,MAAA,MAAA,CAAA,CAAA,KAAA,MAAA,kBAAA,gCAAY,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC,EAAA,CACzC,UAAU,IAAI,YAAY,KAAK;IAC9C;GACF;GAEA,eAAe;IACb,aAAa;IACb,aAAa;KAAE,MAAM;KAAU,YAAY,CAAC;IAAE;IAC9C,SAAS,OAAO,OAAO,QAAQ;KAE7B,CAAA,MAAA,kBAAA,gCADoB,MAAA,MAAA,CAAA,CAAA,KAAA,MAAA,kBAAA,gCAAY,MAAA,YAAA,CAAA,CAAA,KAAA,MAAkB,GAAG,CAAC,EAAA,CAChD,UAAU,IAAI,cAAc;KAClC,OAAO;IACT;GACF;EACF;CACF;;;;;CAQA,MAAe,UAAU,aAAoC;EAC3D,MAAM,QAAA,uBAAA,SAAQ,IAAA,CAAA,CAAa,IAAI,WAAW;EAC1C,IAAI,CAAC,OAAO;EACZ,uBAAA,SAAA,IAAA,CAAA,CAAa,OAAO,WAAW;EAC/B,IAAI;GACF,MAAM,OAAO,MAAA,kBAAA,gCAAM,MAAA,cAAA,CAAA,CAAA,KAAA,MAAoB,aAAa,KAAK;GACzD,MAAA,kBAAA,gCAAM,MAAA,iBAAA,CAAA,CAAA,KAAA,MAAuB,OAAO,IAAI;EAC1C,UAAU;GACR,MAAM,UAAU,IAAI,WAAW;EACjC;CACF;;CAGA,MAAe,iBAAiB,aAAoC;EAClE,MAAM,QAAA,uBAAA,SAAQ,IAAA,CAAA,CAAa,IAAI,WAAW;EAC1C,IAAI,CAAC,OAAO;EACZ,uBAAA,SAAA,IAAA,CAAA,CAAa,OAAO,WAAW;EAC/B,MAAM,UAAU,IAAI,WAAW;CACjC;;;;;;CAOA,WAAW,aAAyD;EAClE,MAAM,SAAA,uBAAA,UAAS,IAAA,CAAA,CAAc,IAAI,WAAW;EAC5C,uBAAA,UAAA,IAAA,CAAA,CAAc,OAAO,WAAW;EAChC,OAAO;CACT;AA+RF;AA3RE,SAAA,aAAa,KAA6C;CACxD,IAAI,CAAC,KAAK,aACR,MAAM,IAAI,MAAM,oDAAoD;CAEtE,OAAO,IAAI;AACb;;;;;AAMA,SAAA,OAAO,aAA8C;CACnD,MAAM,WAAA,uBAAA,SAAW,IAAA,CAAA,CAAa,IAAI,WAAW;CAC7C,IAAI,UAAU,OAAO,QAAQ,QAAQ,QAAQ;CAC7C,MAAM,WAAA,uBAAA,aAAW,IAAA,CAAA,CAAiB,IAAI,WAAW;CACjD,IAAI,UAAU,OAAO;CACrB,MAAM,UAAA,kBAAA,gCAAU,MAAA,QAAA,CAAA,CAAA,KAAA,MAAc,WAAW,CAAC,CAAC,cAAc;EACvD,uBAAA,aAAA,IAAA,CAAA,CAAiB,OAAO,WAAW;CACrC,CAAC;CACD,uBAAA,aAAA,IAAA,CAAA,CAAiB,IAAI,aAAa,OAAO;CACzC,OAAO;AACT;AAEA,eAAA,SAAe,aAA8C;CAC3D,MAAM,YAAY,MAAA,uBAAA,UAAM,IAAA,CAAA,CAAc,QAAQ;CAC9C,IAAI;CACJ,IAAI;EACF,eAAe,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,UAAU,GAAG;CAChD,SAAS,OAAO;EACd,UAAU,IAAI,WAAW;EACzB,MAAM;CACR;CACA,MAAM,QAAwB;EAC5B;EACA,0BAAU,IAAI,IAAI;EAClB,cAAc,IAAI,IAAI,aAAa,KAAK,SAAS,KAAK,QAAQ,CAAC;EAC/D,gBAAgB;CAClB;CACA,uBAAA,SAAA,IAAA,CAAA,CAAa,IAAI,aAAa,KAAK;CACnC,kBAAA,gCAAA,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,aAAa,UAAU,SAAS;CAC7C,OAAO;AACT;AAEA,SAAA,QAAQ,aAAqB,WAA4C;CACvE,IAAI,SAAA,uBAAA,UAAS,IAAA,CAAA,CAAc,IAAI,WAAW;CAC1C,IAAI,CAAC,QAAQ;EACX,SAAS;GAAE;GAAW,SAAS,CAAC;EAAE;EAClC,uBAAA,UAAA,IAAA,CAAA,CAAc,IAAI,aAAa,MAAM;EACrC,IAAA,uBAAA,UAAI,IAAA,CAAA,CAAc,OAAO,qBAAqB;GAC5C,MAAM,SAAA,uBAAA,UAAS,IAAA,CAAA,CAAc,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAC3C,IAAI,WAAW,KAAA,GAAW,uBAAA,UAAA,IAAA,CAAA,CAAc,OAAO,MAAM;EACvD;CACF;CAEA,OAAO,cAAc;CACrB,OAAO;AACT;;;;;;;AAQA,eAAA,eACE,aACA,OACoC;CACpC,IAAI;CACJ,IAAI;EACF,QAAQ,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,MAAM,UAAU,GAAG;CAC/C,SAAS,OAAO;EACd,kBAAA,gCAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,sBAAsB,KAAK;EACtC;CACF;CACA,MAAM,SAAA,kBAAA,gCAAS,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,aAAa,MAAM,UAAU,SAAS;CAClE,MAAM,SAAS,IAAI,IAAY,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC;CACxE,KAAK,MAAM,QAAQ,OAIjB,IAAI,CAAC,MAAM,aAAa,IAAI,KAAK,QAAQ,KAAK,KAAK,UACjD,OAAO,IAAI,KAAK,QAAQ;CAG5B,OAAO,UAAU,MACd,QAAQ,SAAS,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAC3C,KAAK,EAAE,UAAU,KAAK,aAAa;EAAE;EAAU;EAAK;CAAM,EAAE;CAC/D,OAAO,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC;AACnD;;;;;;AAOA,eAAA,kBACE,OACA,MACe;CACf,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,YAAY,MAAM,iBAAiB,MAAM,iBAAiB;CAChE,MAAM,SACJ,cAAc,CAAC,QAAQ,KAAK,IAAI,SAAS,KAAK,YAAY,KAAA;CAC5D,IAAI,WAAW,QAAQ;CACvB,IAAI;EACF,MAAM,MAAM,UAAU,gBAAgB,MAAM;CAC9C,SAAS,OAAO;EACd,kBAAA,gCAAA,MAAA,KAAA,CAAA,CAAA,KAAA,MAAW,uBAAuB,KAAK;CACzC;AACF;AAEA,SAAA,MAAM,QAAgB,OAAsB;CAC1C,QAAQ,KACN,8BAA8B,OAAO,gBAAA,uBAAA,UAAgB,IAAA,CAAA,CAAc,KAAK,IACxE,KACF;AACF;AAEA,eAAA,OAAa,KAA2C;CAItD,SAAQ,MAHc,IAAI,KAAK,mBAAmB,EAAA,EAGlC,eAAe,CAAC,EAAA,CAAG,QAAQ,SAAS,KAAK,SAAS,MAAM;AAC1E;AAEA,eAAA,kBACE,OACA,WACoC;CACpC,IAAI,cAAA,UACF,OAAA,kBAAA,gCAAO,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,OAAO,MAAA,kBAAA,gCAAM,MAAA,aAAA,CAAA,CAAA,KAAA,MAAmB,KAAK,CAAC;CAE5D,IAAI,eAAe,SAAS,GAC1B,OAAA,kBAAA,gCAAO,MAAA,OAAA,CAAA,CAAA,KAAA,MAAa,OAAO,eAAe,SAAS,CAAC;CAGtD,OAAO;AACT;;;;;;AAOA,eAAA,cAAoB,OAA0C;CAC5D,IAAI,MAAM,gBAAgB,OAAO,MAAM;CACvC,MAAM,QAAQ,MAAA,kBAAA,gCAAM,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,MAAM,UAAU,GAAG;CACnD,MAAM,SAAS,MAAM,UAAU;CAC/B,IAAI,WAAW,MAAM,MAAM,SAAS,KAAK,aAAa,MAAM,CAAC,EAAE;CAC/D,IAAI,CAAC,YAAY,MAAM,WAAW,GAIhC,YAAW,MAHY,MAAM,UAAU,IAAI,KAAK,uBAAuB,EACrE,KAAK,cACP,CAAC,EAAA,CACkB;CAErB,aAAa,MAAM,EAAE,CAAC;CACtB,kBAAA,gCAAA,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,OAAO,QAAQ;CAC/B,OAAO;AACT;AAEA,SAAA,WAAW,OAAuB,UAAsC;CACtE,MAAM,iBAAiB;CACvB,MAAM,iBAAiB;AACzB;AAEA,eAAA,QACE,OACA,UACA,WACwB;CACxB,MAAM,WAAW,MAAM,SAAS,IAAI,QAAQ;CAC5C,IAAI,UAAU,OAAO;CACrB,MAAM,OAAQ,MAAM,MAAM,UAAU,IAAI,eAAe,UAAU,EAC/D,UACF,CAAC;CACD,MAAM,SAAS,IAAI,UAAU,IAAI;CACjC,OAAO;AACT;;AAGA,SAAA,SACE,OACA,QACA,QACA,QACM;CACN,MAAM,WACJ,OAAO,QAAQ,aAAa,WACvB,OAAO,WACR,KAAA;CACN,IAAI,WAAW,uBAAuB;EACpC,MAAM,UAAW,QAA+C;EAChE,IAAI,OAAO,YAAY,UACrB,kBAAA,gCAAA,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,OAAO,OAAmB;CAE9C,OAAO,IAAI,WAAW,2BAA2B,UAC/C,kBAAA,gCAAA,MAAA,UAAA,CAAA,CAAA,KAAA,MAAgB,OAAO,QAAQ;MAC1B,IAAI,WAAW,wBAAwB,UAAU;EACtD,MAAM,SAAS,OAAO,QAAQ;EAE9B,IAAI,MAAM,mBAAmB,UAAU,MAAM,iBAAiB,KAAA;CAChE,OAAO,IAAI,WAAW;OAEf,MAAM,CAAC,QAAQ,SAAS,MAAM,UACjC,IAAI,WAAW,YAAY,SAAS,QAAQ,WAC1C,MAAM,SAAS,OAAO,MAAM;CAAA;AAIpC;;;;;;AAOA,SAAA,YACE,OACA,QACA,QACuD;CACvD,MAAM,SAAS,QAAQ;CACvB,IAAI,WAAW,6BAA6B,OAAO,WAAW,UAC5D,OAAO;CAET,IAAI,CAAC,eAAe,MAAM,GAAG,OAAO;CACpC,MAAM,OAAO,MAAM,SAAS,IAAI,eAAe,MAAM,CAAC;CACtD,OAAO,OAAO;EAAE,GAAG;EAAQ,WAAW;CAAK,IAAI;AACjD;;;;;AAMA,eAAA,OACE,OACA,OACA,QACA,WACkB;CAClB,IAAI,EAAE,iBAAiB,mBAAmB,OAAO;CACjD,IACE,MAAM,SAAS,yBACf,aACA,cAAA,YACA,CAAC,eAAe,SAAS,GAEzB,uBAAO,IAAI,MACT,GAAG,MAAM,QAAQ,mKAGnB;CAEF,IAAI,MAAM,SAAA,QAA+B,OAAO;CAChD,IAAI,MAAA,kBAAA,gCAAM,MAAA,QAAA,CAAA,CAAA,KAAA,MAAc,OAAO,MAAM,GACnC,uBAAO,IAAI,MACT,GAAG,MAAM,QAAQ,KAAK,OAAO,6JAG/B;CAEF,IAAI,CAAC,WACH,uBAAO,IAAI,MACT,GAAG,MAAM,QAAQ,KAAK,OAAO,gGAEJ,OAAO,mCAClC;CAEF,OAAO;AACT;AAEA,eAAA,SAAe,OAAuB,QAAkC;CACtE,IAAI;EACF,MAAM,OAAO,MAAM,MAAM,UAAU,KAAK;EACxC,MAAM,SAAS,OAAO,MAAM,GAAG,CAAC,CAAC;EACjC,OAAO,KAAK,QAAQ,MACjB,MAAM,EAAE,SAAS,UAAU,EAAE,OAAO,MAAM,MAAM,EAAE,UAAU,MAAM,CACrE;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAGF,SAAS,aAAa,UAAkC;CACtD,OAAO,GAAG,uBAAuB;AACnC;AAEA,SAAS,eACP,WAC2B;CAC3B,OAAO,WAAW,WAAW,oBAAoB,KAAK;AACxD;AAEA,SAAS,eAAe,QAAgC;CACtD,OAAO,OAAO,MAAM,CAA2B;AACjD;;;;;;;;ACrmBA,MAAM,qBAAqB;AAE3B,MAAM,mBACJ;;;;;;AAOF,SAAS,mBAAmB,MAAsB;CAKhD,OAAO,gBAJS,KAAK,QACnB,oBACC,SAAS,IAAI,KAAK,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAErB;AAC/B;;AAGA,SAAS,kBACP,QACA,QACmB;CACnB,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO;EACL,GAAG;EACH,GAAI,OAAO,YACP;GAAE,WAAW;GAAe,QAAQ;EAAiB,IACrD,CAAC;EACL,GAAI,OAAO,QAAQ,SAAS,IAAI,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CACjE;AACF;;AAGA,SAAgB,mBAAmB,QAAqC;CACtE,MAAM,QAAkB,CAAC;CACzB,IAAI,OAAO,QAAQ,MAAM,KAAK,OAAO,MAAM;CAC3C,IAAI,OAAO,SAAS,QAClB,MAAM,KACJ,gCAAgC,KAAK,UAAU,OAAO,OAAO,GAC/D;CAEF,OAAO;AACT;;;;;AAMA,SAAgB,sBACd,SACA,SAIA;CACA,MAAM,MAAM,WAAW,OAAO;CAC9B,IAAI,CAAC,KACH,MAAM,IAAI,MACR,4IACF;CAGF,MAAM,UAAU,QAAQ,aAAa;CACrC,MAAM,YAAY,IAAI,wBAAwB,KAAK,EACjD,SAAS,QAAQ,QACnB,CAAC;CACD,MAAM,UAAU,sBAAsB;EACpC;EACA,UAAU,IAAI,sBAAsB;GAClC,QAAQ,QAAQ;GAChB;EACF,CAAC;EACD,YAAY,CAAC,SAAS;EACtB,MAAM,mBAAmB,QAAQ,QAAQ,IAAI;EAC7C,iBAAiB;CACnB,CAAC;CAeD,MAAM,cAAc;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAtBY;GACZ;GACA,4BAA4B,KAAK,MAAM,UAAU,GAAI,EAAE;GACvD,QAAQ;EACV,CAAC,CACE,KAAK,IAAI,CAAC,CACV,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,KAAK,IAeF;EACJ;CACF,CAAC,CAAC,KAAK,IAAI;CACX,MAAM,eAAe,QAAQ,KAAK,EAAE,YAAY,CAAC;CAEjD,OAAO;EACL,aAAa,aAAa;EAC1B,aAAa,aAAa;EAC1B,SAAS,OACP,OACA,mBAC+B;GAC/B,MAAM,SAAS,MAAM,aAAa,QAAQ,OAAO,cAAc;GAC/D,OAAO,kBACL,QACA,UAAU,WAAW,OAAO,WAAW,CACzC;EACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability-B4WbF81e.js","names":[],"sources":["../src/lifecycle/capability.ts"],"sourcesContent":["import type {\n CapabilityStartContext,\n DurableObjectCapability\n} from \"./capability-runner\";\nimport type { Connection } from \"./types\";\nimport type { LifecycleJobs } from \"./job-queue\";\n\n/** Opaque address understood by a Lifecycle routing transport. */\nexport type LifecycleRouteAddress = {\n /** Stable equality and storage key. */\n readonly key: string;\n /** Transport-owned serialized address. */\n readonly data: string;\n};\n\n/** Context supplied with a routed capability message. */\nexport type LifecycleRouteContext = {\n /** Address of the sending Lifecycle, or undefined for an unrouted root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Capability-owned message payload. */\n readonly payload: unknown;\n};\n\n/** Startup state of a Lifecycle. */\nexport type LifecycleStatus = \"zero\" | \"starting\" | \"started\";\n\n/** Best-effort telemetry available to every Lifecycle capability. */\nexport type LifecycleEvents = {\n /** Publish an event under this capability's stable identity. */\n readonly emit: (type: string, payload: unknown) => void;\n};\n\n/** Routing available to every Lifecycle capability. */\nexport type LifecycleRoutes = {\n /** This Lifecycle's transport address, or undefined at the route root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Route a capability-owned message to the root Lifecycle. */\n readonly toRoot: (payload: unknown) => Promise<unknown>;\n /** Route a capability-owned message to another Lifecycle. */\n readonly to: (\n target: LifecycleRouteAddress,\n payload: unknown\n ) => Promise<unknown>;\n};\n\n/**\n * Ambient scope a capability supplies when entering host context on\n * behalf of a live connection or request.\n */\nexport type LifecycleHostContextScope = {\n /** The connection the callback runs on behalf of, when there is one. */\n readonly connection?: Connection;\n /** The request the callback runs on behalf of, when there is one. */\n readonly request?: Request;\n};\n\n/**\n * The platform's hibernatable-socket surface, exposed narrowly so a\n * capability that owns connections (e.g. WebSockets) can accept and\n * enumerate them without holding the whole `DurableObjectState`.\n * These are workerd API names, not Lifecycle modeling sockets.\n */\nexport type LifecycleSockets = {\n /** Accept a socket into hibernation under the given tags. */\n readonly accept: (ws: WebSocket, tags: string[]) => void;\n /** Every hibernated socket on the object, optionally by tag. */\n readonly get: (tag?: string) => WebSocket[];\n};\n\n/**\n * Standard services granted to every installed Lifecycle capability.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport type LifecycleServices = {\n /** The host's Durable Object name; see `Lifecycle.name`. */\n readonly name: string;\n /** The host class name, as exported from the Worker. */\n readonly className: string;\n readonly storage: DurableObjectStorage;\n readonly sockets: LifecycleSockets;\n /**\n * Resolve once Lifecycle startup has finished, starting it if needed, and\n * reject if that startup fails. Calls made from inside startup resolve\n * immediately.\n */\n readonly ready: () => Promise<void>;\n /**\n * Startup state: `\"zero\"` before startup, `\"starting\"` while capability\n * and host startup hooks run, `\"started\"` once `ready()` resolves without\n * waiting.\n */\n readonly status: () => LifecycleStatus;\n /**\n * This capability's scoped access to the Lifecycle-owned work queue.\n * Pushed items are dispatched to `onJob` when due; every queue mutation\n * re-arms the physical alarm automatically.\n */\n readonly jobs: LifecycleJobs;\n /**\n * Keep work this capability hands off at a bounded `onJob` return inside\n * the current alarm's memory-limit breaker domain (#1825). Returns false,\n * tracking nothing, outside an alarm invocation.\n */\n readonly trackAlarmWork: (work: Promise<unknown>) => boolean;\n /**\n * Run a capability-held user callback inside the host invocation context.\n * Capability hooks run outside host context; this is the one boundary for\n * entering it, and a host composition root may substitute its own wrapper\n * (Agent adds tracing span scope). Pass `scope` to make a live\n * connection or request ambient for the callback.\n */\n readonly runInHostContext: (\n fn: () => unknown,\n scope?: LifecycleHostContextScope\n ) => Promise<unknown>;\n readonly events: LifecycleEvents;\n readonly routes: LifecycleRoutes;\n};\n\nconst installedServices = new WeakMap<object, LifecycleServices>();\n\n/**\n * Base class for capabilities that consume standard Lifecycle services.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport abstract class LifecycleCapability<Props extends object = object> {\n readonly capabilityId: string;\n\n /** How this capability claims traffic; see {@link DurableObjectCapability.claims}. */\n readonly claims: \"selective\" | \"catch-all\" = \"selective\";\n\n protected constructor(capabilityId: string) {\n if (capabilityId.trim() === \"\") {\n throw new Error(\"Lifecycle capability IDs must be non-empty\");\n }\n this.capabilityId = capabilityId;\n }\n\n /** Default startup hook; capabilities override when they own startup work. */\n onStart(_context: CapabilityStartContext<Props>): void {}\n\n /** Standard services when installed, or undefined in isolated unit tests. */\n protected get lifecycleServices(): LifecycleServices | undefined {\n return installedServices.get(this);\n }\n\n /** Standard services supplied when Lifecycle installs this capability. */\n protected get lifecycle(): LifecycleServices {\n const services = this.lifecycleServices;\n if (!services) {\n throw new Error(\n `${this.constructor.name} must be installed with Lifecycle.use() before use`\n );\n }\n return services;\n }\n}\n\n/**\n * @internal Bind the standard service surface to one capability instance.\n * `Lifecycle.use()` calls this during installation. Test a capability by\n * installing it on a minimal Durable Object with a real Lifecycle rather\n * than binding fake services.\n */\nexport function bindLifecycleCapability(\n capability: LifecycleCapability,\n services: LifecycleServices\n): void {\n installedServices.set(capability, services);\n}\n\n/** @internal Read a capability ID without exposing installation internals. */\nexport function lifecycleCapabilityId(\n capability: DurableObjectCapability\n): string | undefined {\n return capability instanceof LifecycleCapability\n ? capability.capabilityId\n : undefined;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"capability-B4WbF81e.js","names":[],"sources":["../src/lifecycle/capability.ts"],"sourcesContent":["import type {\n CapabilityStartContext,\n DurableObjectCapability\n} from \"./capability-runner\";\nimport type { Connection } from \"./types\";\nimport type { LifecycleJobs } from \"./job-queue\";\n\n/** Opaque address understood by a Lifecycle routing transport. */\nexport type LifecycleRouteAddress = {\n /** Stable equality and storage key. */\n readonly key: string;\n /** Transport-owned serialized address. */\n readonly data: string;\n};\n\n/** Context supplied with a routed capability message. */\nexport type LifecycleRouteContext = {\n /** Address of the sending Lifecycle, or undefined for an unrouted root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Capability-owned message payload. */\n readonly payload: unknown;\n};\n\n/** Startup state of a Lifecycle. */\nexport type LifecycleStatus = \"zero\" | \"starting\" | \"started\";\n\n/** Best-effort telemetry available to every Lifecycle capability. */\nexport type LifecycleEvents = {\n /** Publish an event under this capability's stable identity. */\n readonly emit: (type: string, payload: unknown) => void;\n};\n\n/** Routing available to every Lifecycle capability. */\nexport type LifecycleRoutes = {\n /** This Lifecycle's transport address, or undefined at the route root. */\n readonly source: LifecycleRouteAddress | undefined;\n /** Route a capability-owned message to the root Lifecycle. */\n readonly toRoot: (payload: unknown) => Promise<unknown>;\n /** Route a capability-owned message to another Lifecycle. */\n readonly to: (\n target: LifecycleRouteAddress,\n payload: unknown\n ) => Promise<unknown>;\n};\n\n/**\n * Ambient scope a capability supplies when entering host context on\n * behalf of a live connection or request.\n */\nexport type LifecycleHostContextScope = {\n /** The connection the callback runs on behalf of, when there is one. */\n readonly connection?: Connection;\n /** The request the callback runs on behalf of, when there is one. */\n readonly request?: Request;\n};\n\n/**\n * The platform's hibernatable-socket surface, exposed narrowly so a\n * capability that owns connections (e.g. WebSockets) can accept and\n * enumerate them without holding the whole `DurableObjectState`.\n * These are workerd API names, not Lifecycle modeling sockets.\n */\nexport type LifecycleSockets = {\n /** Accept a socket into hibernation under the given tags. */\n readonly accept: (ws: WebSocket, tags: string[]) => void;\n /** Every hibernated socket on the object, optionally by tag. */\n readonly get: (tag?: string) => WebSocket[];\n};\n\n/**\n * Standard services granted to every installed Lifecycle capability.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport type LifecycleServices = {\n /** The host's Durable Object name; see `Lifecycle.name`. */\n readonly name: string;\n /** The host class name, as exported from the Worker. */\n readonly className: string;\n readonly storage: DurableObjectStorage;\n readonly sockets: LifecycleSockets;\n /**\n * Resolve once Lifecycle startup has finished, starting it if needed, and\n * reject if that startup fails. Calls made from inside startup resolve\n * immediately.\n *\n * Await it first in any operation that can run before startup and starts\n * work `onStart` also awaits, such as lazily opening a store. Native RPC\n * bypasses the automatic startup on fetch, alarm and WebSocket events, so\n * such an operation can begin that work outside startup. `onStart` then\n * awaits it while startup holds the input gate (`blockConcurrencyWhile`),\n * which holds back the work's timers and I/O, and startup times out after\n * 30 seconds and resets the object.\n */\n readonly ready: () => Promise<void>;\n /**\n * Startup state: `\"zero\"` before startup, `\"starting\"` while capability\n * and host startup hooks run, `\"started\"` once `ready()` resolves without\n * waiting.\n */\n readonly status: () => LifecycleStatus;\n /**\n * This capability's scoped access to the Lifecycle-owned work queue.\n * Pushed items are dispatched to `onJob` when due; every queue mutation\n * re-arms the physical alarm automatically.\n */\n readonly jobs: LifecycleJobs;\n /**\n * Keep work this capability hands off at a bounded `onJob` return inside\n * the current alarm's memory-limit breaker domain (#1825). Returns false,\n * tracking nothing, outside an alarm invocation.\n */\n readonly trackAlarmWork: (work: Promise<unknown>) => boolean;\n /**\n * Run a capability-held user callback inside the host invocation context.\n * Capability hooks run outside host context; this is the one boundary for\n * entering it, and a host composition root may substitute its own wrapper\n * (Agent adds tracing span scope). Pass `scope` to make a live\n * connection or request ambient for the callback.\n */\n readonly runInHostContext: (\n fn: () => unknown,\n scope?: LifecycleHostContextScope\n ) => Promise<unknown>;\n readonly events: LifecycleEvents;\n readonly routes: LifecycleRoutes;\n};\n\nconst installedServices = new WeakMap<object, LifecycleServices>();\n\n/**\n * Base class for capabilities that consume standard Lifecycle services.\n *\n * @experimental The API surface may change before stabilizing.\n */\nexport abstract class LifecycleCapability<Props extends object = object> {\n readonly capabilityId: string;\n\n /** How this capability claims traffic; see {@link DurableObjectCapability.claims}. */\n readonly claims: \"selective\" | \"catch-all\" = \"selective\";\n\n protected constructor(capabilityId: string) {\n if (capabilityId.trim() === \"\") {\n throw new Error(\"Lifecycle capability IDs must be non-empty\");\n }\n this.capabilityId = capabilityId;\n }\n\n /** Default startup hook; capabilities override when they own startup work. */\n onStart(_context: CapabilityStartContext<Props>): void {}\n\n /** Standard services when installed, or undefined in isolated unit tests. */\n protected get lifecycleServices(): LifecycleServices | undefined {\n return installedServices.get(this);\n }\n\n /** Standard services supplied when Lifecycle installs this capability. */\n protected get lifecycle(): LifecycleServices {\n const services = this.lifecycleServices;\n if (!services) {\n throw new Error(\n `${this.constructor.name} must be installed with Lifecycle.use() before use`\n );\n }\n return services;\n }\n}\n\n/**\n * @internal Bind the standard service surface to one capability instance.\n * `Lifecycle.use()` calls this during installation. Test a capability by\n * installing it on a minimal Durable Object with a real Lifecycle rather\n * than binding fake services.\n */\nexport function bindLifecycleCapability(\n capability: LifecycleCapability,\n services: LifecycleServices\n): void {\n installedServices.set(capability, services);\n}\n\n/** @internal Read a capability ID without exposing installation internals. */\nexport function lifecycleCapabilityId(\n capability: DurableObjectCapability\n): string | undefined {\n return capability instanceof LifecycleCapability\n ? capability.capabilityId\n : undefined;\n}\n"],"mappings":";AAgIA,MAAM,oCAAoB,IAAI,QAAmC;;;;;;AAOjE,IAAsB,sBAAtB,MAAyE;CAMvE,YAAsB,cAAsB;EAF5C,KAAS,SAAoC;EAG3C,IAAI,aAAa,KAAK,MAAM,IAC1B,MAAM,IAAI,MAAM,4CAA4C;EAE9D,KAAK,eAAe;CACtB;;CAGA,QAAQ,UAA+C,CAAC;;CAGxD,IAAc,oBAAmD;EAC/D,OAAO,kBAAkB,IAAI,IAAI;CACnC;;CAGA,IAAc,YAA+B;EAC3C,MAAM,WAAW,KAAK;EACtB,IAAI,CAAC,UACH,MAAM,IAAI,MACR,GAAG,KAAK,YAAY,KAAK,mDAC3B;EAEF,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,wBACd,YACA,UACM;CACN,kBAAkB,IAAI,YAAY,QAAQ;AAC5C;;AAGA,SAAgB,sBACd,YACoB;CACpB,OAAO,sBAAsB,sBACzB,WAAW,eACX,KAAA;AACN"}
|
|
@@ -231,6 +231,14 @@ type LifecycleServices = {
|
|
|
231
231
|
* Resolve once Lifecycle startup has finished, starting it if needed, and
|
|
232
232
|
* reject if that startup fails. Calls made from inside startup resolve
|
|
233
233
|
* immediately.
|
|
234
|
+
*
|
|
235
|
+
* Await it first in any operation that can run before startup and starts
|
|
236
|
+
* work `onStart` also awaits, such as lazily opening a store. Native RPC
|
|
237
|
+
* bypasses the automatic startup on fetch, alarm and WebSocket events, so
|
|
238
|
+
* such an operation can begin that work outside startup. `onStart` then
|
|
239
|
+
* awaits it while startup holds the input gate (`blockConcurrencyWhile`),
|
|
240
|
+
* which holds back the work's timers and I/O, and startup times out after
|
|
241
|
+
* 30 seconds and resets the object.
|
|
234
242
|
*/
|
|
235
243
|
readonly ready: () => Promise<void>;
|
|
236
244
|
/**
|
|
@@ -486,4 +494,4 @@ export {
|
|
|
486
494
|
Connection as x,
|
|
487
495
|
LifecycleJobPushOptions as y
|
|
488
496
|
};
|
|
489
|
-
//# sourceMappingURL=capability-runner-
|
|
497
|
+
//# sourceMappingURL=capability-runner-O5VbQzqg.d.ts.map
|