agents 0.26.0 → 0.28.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/abortable-C1SpfVoi.js +21 -0
- package/dist/abortable-C1SpfVoi.js.map +1 -0
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-QDcchcQP.d.ts} +16 -16
- 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-DHL2XI7u.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-ahruGw7e.d.ts +239 -0
- package/dist/ai-sdk-Bdsf6mSA.js +2444 -0
- package/dist/ai-sdk-Bdsf6mSA.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.d.ts +8 -227
- package/dist/browser/ai.js +259 -1
- package/dist/browser/ai.js.map +1 -0
- package/dist/browser/index.d.ts +56 -46
- package/dist/browser/index.js +4 -361
- package/dist/browser/{ai-sdk.d.ts → tools/ai-sdk.d.ts} +7 -7
- package/dist/browser/{ai-sdk.js → tools/ai-sdk.js} +6 -5
- package/dist/browser/tools/ai-sdk.js.map +1 -0
- package/dist/browser/tools/pi.d.ts +111 -0
- package/dist/browser/tools/pi.js +119 -0
- package/dist/browser/tools/pi.js.map +1 -0
- package/dist/browser/{tanstack-ai.d.ts → tools/tanstack-ai.d.ts} +39 -37
- package/dist/browser/{tanstack-ai.js → tools/tanstack-ai.js} +73 -59
- package/dist/browser/tools/tanstack-ai.js.map +1 -0
- package/dist/browser-CEdNlsaD.js +481 -0
- package/dist/browser-CEdNlsaD.js.map +1 -0
- package/dist/{browser-DE3d-RLk.d.ts → browser-D30VlpcA.d.ts} +63 -37
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-4iomavs_.js} +198 -62
- package/dist/browser-tool-4iomavs_.js.map +1 -0
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-vtrPOV8e.d.ts} +25 -6
- package/dist/{capability-B4WbF81e.js → capability-DBAKc6UZ.js} +1 -3
- package/dist/capability-DBAKc6UZ.js.map +1 -0
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-BiVUtjqC.d.ts} +9 -7
- 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-je3koD9Z.d.ts → client-BGIQKJhX.d.ts} +2 -2
- package/dist/{client-fyQrLGkD.js → client-BnYbNT3z.js} +29 -5
- package/dist/client-BnYbNT3z.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/{connector-58XDGvc3.js → connector-CtZylXdk.js} +3 -603
- package/dist/connector-CtZylXdk.js.map +1 -0
- package/dist/{connector-Cl-VqPmA.d.ts → connector-DEXzVoaY.d.ts} +9 -374
- package/dist/conversations-FjHDRZyD.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-CKgT5d1O.d.ts} +3 -5
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- 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-DOI_bzCH.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-BFO6vRDn.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 +3 -2
- package/dist/harness/pi/index.js +3 -3
- 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 +2721 -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-BEL3ZV-s.d.ts} +2 -6
- package/dist/index-CBt8oRJt.d.ts +198 -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-BQykTEp_.js +20 -0
- package/dist/input-schema-BQykTEp_.js.map +1 -0
- package/dist/input-schema-D2QAtHZH.js +23 -0
- package/dist/input-schema-D2QAtHZH.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/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CFu6OV03.js → lifecycle-DGorpAmA.js} +2 -4
- package/dist/lifecycle-DGorpAmA.js.map +1 -0
- 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-CiIsJHXR.d.ts +130 -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/queue/index.js +1 -1
- package/dist/{queue-BB-zfIYF.js → queue-CcIjVYTE.js} +2 -2
- package/dist/{queue-BB-zfIYF.js.map → queue-CcIjVYTE.js.map} +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-BCLESHSC.d.ts} +2 -12
- package/dist/{scheduler-DZ3xQfeK.js → scheduler-FIHv2-kJ.js} +2 -4
- package/dist/scheduler-FIHv2-kJ.js.map +1 -0
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/session-store-Do4xkDcf.js +606 -0
- package/dist/session-store-Do4xkDcf.js.map +1 -0
- package/dist/session-store-DoPY12xM.d.ts +383 -0
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-_vN5BEZX.js +2272 -0
- package/dist/sessions-_vN5BEZX.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-CQxJFv4l.js} +8 -8
- package/dist/src-CQxJFv4l.js.map +1 -0
- 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/state/index.js +1 -3
- package/dist/state/index.js.map +1 -1
- package/dist/store-BKM4Xm9M.d.ts +332 -0
- package/dist/store-CslVzfN7.js +458 -0
- package/dist/store-CslVzfN7.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-lsHMJhRW.js} +17 -4
- package/dist/streams-lsHMJhRW.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-vrCtbUIn.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-BN_5AWqI.js} +3 -2
- package/dist/tasks-BN_5AWqI.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-E89KdwkS.d.ts} +2 -2
- package/dist/tool-CTp72Jb_.d.ts +278 -0
- package/dist/tool-CxKsvxjm.js +1126 -0
- package/dist/tool-CxKsvxjm.js.map +1 -0
- package/dist/tool-DoVberpN.d.ts +301 -0
- package/dist/tool-cQRkwSZZ.js +410 -0
- package/dist/tool-cQRkwSZZ.js.map +1 -0
- package/dist/tool-helpers-O5BDVEAY.js +58 -0
- package/dist/tool-helpers-O5BDVEAY.js.map +1 -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/web/fetch/index.d.ts +89 -0
- package/dist/web/fetch/index.js +20 -0
- package/dist/web/fetch/index.js.map +1 -0
- package/dist/web/fetch/tools/ai-sdk.d.ts +80 -0
- package/dist/web/fetch/tools/ai-sdk.js +47 -0
- package/dist/web/fetch/tools/ai-sdk.js.map +1 -0
- package/dist/web/fetch/tools/pi.d.ts +84 -0
- package/dist/web/fetch/tools/pi.js +88 -0
- package/dist/web/fetch/tools/pi.js.map +1 -0
- package/dist/web/fetch/tools/tanstack-ai.d.ts +78 -0
- package/dist/web/fetch/tools/tanstack-ai.js +45 -0
- package/dist/web/fetch/tools/tanstack-ai.js.map +1 -0
- package/dist/web/search/index.d.ts +58 -0
- package/dist/web/search/index.js +2 -0
- package/dist/web/search/tools/ai-sdk.d.ts +77 -0
- package/dist/web/search/tools/ai-sdk.js +46 -0
- package/dist/web/search/tools/ai-sdk.js.map +1 -0
- package/dist/web/search/tools/pi.d.ts +82 -0
- package/dist/web/search/tools/pi.js +89 -0
- package/dist/web/search/tools/pi.js.map +1 -0
- package/dist/web/search/tools/tanstack-ai.d.ts +71 -0
- package/dist/web/search/tools/tanstack-ai.js +46 -0
- package/dist/web/search/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-C3zy8YR0.js} +20 -8
- package/dist/websockets-C3zy8YR0.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 +29 -1
- package/docs/fetch-the-web.md +276 -0
- package/docs/harnesses/pi.md +25 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +99 -75
- package/docs/lifecycle.md +42 -3
- package/docs/queue.md +2 -3
- package/docs/scheduling.md +2 -6
- package/docs/search-the-web.md +135 -0
- package/package.json +148 -44
- package/dist/ai-fm7xx1BK.js +0 -312
- package/dist/ai-fm7xx1BK.js.map +0 -1
- package/dist/browser/ai-sdk.js.map +0 -1
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/tanstack-ai.js.map +0 -1
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/capability-B4WbF81e.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/client-fyQrLGkD.js.map +0 -1
- package/dist/connector-58XDGvc3.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/lifecycle-CFu6OV03.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/scheduler-DZ3xQfeK.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/src-BZeu-SCD.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,276 @@
|
|
|
1
|
+
# Fetch the Web (Beta)
|
|
2
|
+
|
|
3
|
+
`agents/webfetch` gives a model a `web_fetch` tool that reads one URL and returns the most useful text form of it: Markdown for HTML pages, PDFs, and Office documents, pretty-printed JSON, or plain text as served. Long content is windowed, so the model reads a page in parts. The same tool is available for the pi harness, the AI SDK, and TanStack AI.
|
|
4
|
+
|
|
5
|
+
The host decides which URLs can be fetched, how much is read, and how long a fetch may take. The model only chooses the URL, where to continue reading, and whether to get the body converted or raw.
|
|
6
|
+
|
|
7
|
+
> **Beta** — this feature may have breaking changes in future releases.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
You need an `AI` binding in `wrangler.jsonc`. The tool fetches pages with the Worker's own `fetch` and uses `env.AI.toMarkdown()` from [Workers AI Markdown Conversion](https://developers.cloudflare.com/workers-ai/features/markdown-conversion/) to turn HTML and documents into Markdown. There is no API token to set up.
|
|
12
|
+
|
|
13
|
+
```jsonc
|
|
14
|
+
{
|
|
15
|
+
// No local implementation: under `wrangler dev` or `vite dev`, conversions
|
|
16
|
+
// run against the account you are logged in to.
|
|
17
|
+
"ai": { "binding": "AI", "remote": true }
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Pages that are already Markdown, JSON, or plain text never reach the binding. The tool asks for `text/markdown` first, so sites with [Markdown for Agents](https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/) enabled answer in Markdown and skip conversion.
|
|
22
|
+
|
|
23
|
+
Then add the tool to your harness. Every adapter takes the same options; the TanStack AI adapter also takes `name`, because TanStack AI tools carry their name in the definition.
|
|
24
|
+
|
|
25
|
+
Pi harness:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { webFetchTool } from "agents/webfetch/pi";
|
|
29
|
+
|
|
30
|
+
this.registry.install({
|
|
31
|
+
name: "tools",
|
|
32
|
+
tools: [webFetchTool({ binding: this.env.AI })]
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
AI SDK:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { webFetchTool } from "agents/webfetch/ai-sdk";
|
|
40
|
+
|
|
41
|
+
const result = streamText({
|
|
42
|
+
model,
|
|
43
|
+
tools: { web_fetch: webFetchTool({ binding: this.env.AI }) },
|
|
44
|
+
messages
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you convert UI messages yourself, pass the same tools to `convertToModelMessages(messages, { tools })`. Without them, the AI SDK sends earlier fetches back to the model as the JSON output: the same window, but without the header line or the `<untrusted_web_content>` wrapper. `AiSdkHarness` does this for you.
|
|
49
|
+
|
|
50
|
+
TanStack AI:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { webFetchTool } from "agents/webfetch/tanstack-ai";
|
|
54
|
+
|
|
55
|
+
const tools = [webFetchTool({ binding: this.env.AI })];
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
An invalid value throws a `RangeError` when the tool is created.
|
|
61
|
+
|
|
62
|
+
| Option | Default | Notes |
|
|
63
|
+
| ------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `binding` | — | The `AI` binding, for converting HTML and documents. Or pass `source` instead (see [Other sources](#other-sources)). |
|
|
65
|
+
| `pageChars` | `20000` | Characters the model reads per call. It continues with `offset`. |
|
|
66
|
+
| `maxBytes` | `5000000` | Stop reading a response body past this many bytes, as a `web_fetch_too_large` failure. Bodies are streamed, so a large one is never buffered. |
|
|
67
|
+
| `timeoutMs` | `30000` | Give up on a fetch after this long, redirects and conversion included, as a retryable `web_fetch_timeout` failure. |
|
|
68
|
+
| `maxRedirects` | `5` | Redirects to follow, from 0 to 20. |
|
|
69
|
+
| `allowedHosts` | any public host | Host patterns URLs must match: `example.com`, `*.example.com` (subdomains, not the apex), or `*`. |
|
|
70
|
+
| `blockedHosts` | — | Host patterns never fetched, in the same syntax. Checked first. |
|
|
71
|
+
| `allowPrivateHosts` | `false` | Allow loopback, private, and local-only hosts. For local development against `localhost`; see [URL policy](#url-policy). |
|
|
72
|
+
| `userAgent` | `Cloudflare-Agents (…)` | The `User-Agent` header. |
|
|
73
|
+
| `description` | built-in description | Replaces the tool description the model sees. |
|
|
74
|
+
|
|
75
|
+
With `source`, only `pageChars`, `timeoutMs`, and `description` apply: the URL policy and limits belong to the source.
|
|
76
|
+
|
|
77
|
+
## Model Interface
|
|
78
|
+
|
|
79
|
+
The model sends:
|
|
80
|
+
|
|
81
|
+
| Field | Notes |
|
|
82
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
83
|
+
| `url` | The http(s) URL to read, at most 2,000 characters. |
|
|
84
|
+
| `offset` | Character offset to continue a long page from, as the previous result says. Default `0`. |
|
|
85
|
+
| `format` | `"auto"` (default) converts to the most readable form; `"raw"` returns a textual body exactly as the server sent it. |
|
|
86
|
+
|
|
87
|
+
The model gets text: a header line, then one window of the content inside `<untrusted_web_content>`:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
web_fetch: https://developers.cloudflare.com/agents/ · 200 · text/html → markdown · chars 0–19873 of 81200 · continue with offset=19873
|
|
91
|
+
<untrusted_web_content>
|
|
92
|
+
# Agents
|
|
93
|
+
…
|
|
94
|
+
</untrusted_web_content>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The header line names the final URL after redirects, the status, the content type and how it was read, and where the window sits. A window ends on a line break when one is close to its end, so `offset` values are rarely round numbers; the model continues from the offset the header gives. When the window reaches the end, the header has no `continue with`. An error status reads `404 Not Found` rather than `404`, so the model cannot miss it.
|
|
98
|
+
|
|
99
|
+
The wrapper and the tool description tell the model to treat the content as data, not instructions. A page cannot close the wrapper early: a closing tag inside the content is escaped.
|
|
100
|
+
|
|
101
|
+
`renderWebFetchPage(output)` from `agents/webfetch` is the renderer, if you want the same text elsewhere. It formats a stored output as is, so re-rendering an old result gives the text the model read.
|
|
102
|
+
|
|
103
|
+
## Host Output
|
|
104
|
+
|
|
105
|
+
The host gets `WebFetchToolOutput`: the window the model read and the page's metadata. The rest of the content is not stored, so a long page does not bloat the conversation history:
|
|
106
|
+
|
|
107
|
+
| Field | Notes |
|
|
108
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| `url`, `finalUrl` | The URL as requested, and after redirects. |
|
|
110
|
+
| `status` | The final response's HTTP status. |
|
|
111
|
+
| `contentType` | The media type, lowercased, without parameters. |
|
|
112
|
+
| `via` | How the content was produced; see [Conversion](#conversion). |
|
|
113
|
+
| `content`, `totalChars` | The window of converted content the model read, and the whole page's length. |
|
|
114
|
+
| `offset`, `nextOffset` | The window the model read, and where the next one starts (`null` at the end). |
|
|
115
|
+
| `title` | The page title, from `<title>` for HTML or the first `# ` heading for Markdown, when there is one. |
|
|
116
|
+
| `headers` | `content-type`, `content-length`, `last-modified`, `etag`, `x-markdown-tokens`, and `cf-mitigated`, when sent. |
|
|
117
|
+
| `bytes` | Body bytes read. |
|
|
118
|
+
| `redirects` | The URLs that redirected, in order; `finalUrl` is not included. |
|
|
119
|
+
|
|
120
|
+
Where it arrives depends on the harness:
|
|
121
|
+
|
|
122
|
+
- **Pi**: in the tool result's `details`, as `{ ok: true, output }`. The tool is `replay: "safe"`: if a fetch is interrupted mid-call, for example by an eviction, pi runs it again when the session recovers. Completed results are stored and not fetched again.
|
|
123
|
+
- **AI SDK**: as the return value of `execute`, so `onFinish`, UI message parts, and logs see it. `toModelOutput` renders it for the model.
|
|
124
|
+
- **TanStack AI**: the server tool returns the rendered window, so the host gets the same text the model does.
|
|
125
|
+
|
|
126
|
+
Each call fetches the page again; there is no cache between calls, so reading a long page in five windows fetches it five times.
|
|
127
|
+
|
|
128
|
+
## Conversion
|
|
129
|
+
|
|
130
|
+
With `format: "auto"`, the content type decides what the model reads:
|
|
131
|
+
|
|
132
|
+
| Content type | Handling | `via` |
|
|
133
|
+
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------- |
|
|
134
|
+
| `text/markdown` | Passed through | `markdown-negotiated` |
|
|
135
|
+
| `text/html`, `application/xhtml+xml` | Converted with `env.AI.toMarkdown()` | `converted` |
|
|
136
|
+
| `application/pdf`, Word (`.docx`), Excel (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`), OpenDocument (`.odt`, `.ods`), Numbers | Converted with `env.AI.toMarkdown()` | `converted` |
|
|
137
|
+
| `application/json`, `*+json` | Pretty-printed; invalid JSON is passed through as text | `json` |
|
|
138
|
+
| Other `text/*`, `application/xml`, `*+xml`, JavaScript, YAML, TOML, NDJSON, SQL, GraphQL, shell | Passed through | `text` |
|
|
139
|
+
| Images, audio, video, fonts, archives, binary `application/octet-stream`, anything else | Rejected as `web_fetch_unsupported_content_type` | — |
|
|
140
|
+
|
|
141
|
+
Bodies are decoded with the charset from a byte-order mark, the `Content-Type` header, or an HTML `<meta charset>`, falling back to UTF-8. A response without a `Content-Type`, or sent as `application/octet-stream` (common for `.md`, `.yaml`, and source files), is sniffed as HTML, PDF, binary, or plain text.
|
|
142
|
+
|
|
143
|
+
With `format: "raw"`, textual bodies, HTML included, are returned exactly as served with `via: "raw"`. Documents and binary bodies are rejected, because their raw bytes are not text.
|
|
144
|
+
|
|
145
|
+
A 4xx or 5xx response with a readable body is returned like any page, with its status in the header line: error pages often explain what went wrong. Only an empty or non-textual error body becomes a `web_fetch_http_error` failure. A bot-management challenge page (sent with `cf-mitigated: challenge`) is reported as `web_fetch_blocked` rather than returned as content, because a plain fetch cannot pass it; the model is told to use a browser tool or another source.
|
|
146
|
+
|
|
147
|
+
## URL Policy
|
|
148
|
+
|
|
149
|
+
Every URL is checked before it is fetched, and every redirect target is checked again before it is followed. A redirect from `https:` down to `http:` is refused. Requests are GET only, with no cookies, credentials, or host-supplied headers.
|
|
150
|
+
|
|
151
|
+
Always rejected:
|
|
152
|
+
|
|
153
|
+
- Schemes other than `http:` and `https:`.
|
|
154
|
+
- URLs with a username or password.
|
|
155
|
+
- URLs longer than 2,000 characters.
|
|
156
|
+
|
|
157
|
+
Rejected unless `allowPrivateHosts` is `true`:
|
|
158
|
+
|
|
159
|
+
- Loopback, private, CGNAT, link-local (including cloud metadata at `169.254.169.254`), multicast, and reserved addresses, in IPv4 or IPv6, including IPv4-mapped, NAT64, and 6to4 forms. The list is a subset of the IANA special-purpose address registries ([RFC 6890](https://www.rfc-editor.org/rfc/rfc6890)).
|
|
160
|
+
- `localhost` and names ending in `.localhost` or `.internal`.
|
|
161
|
+
|
|
162
|
+
Hosts are checked after the URL parser has canonicalised them, so a private address cannot hide behind an unusual spelling such as `2130706433`, `0x7f.1`, `0177.0.0.1`, or `[::ffff:127.0.0.1]`. Names are not resolved: a public name that points at a private address, such as `127.0.0.1.nip.io`, passes the policy. In production, Workers egress cannot reach private ranges anyway; the policy is for local development, other runtimes, and defence in depth. The fragment is dropped from every URL.
|
|
163
|
+
|
|
164
|
+
`blockedHosts` is checked first, then `allowedHosts`, then the private-host rule. To keep the model on your documentation:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
webFetchTool({
|
|
168
|
+
binding: this.env.AI,
|
|
169
|
+
allowedHosts: ["developers.cloudflare.com", "*.cloudflare.com"]
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
No DNS lookup is done: a public name that resolves to a private address is the runtime's to refuse. Deployed Workers cannot reach private networks. Under `wrangler dev` they can, which is why the private-host rule exists, and why `allowPrivateHosts` is the switch for testing against a server on `localhost`:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
webFetchTool({
|
|
177
|
+
binding: this.env.AI,
|
|
178
|
+
// Local development only: lets the model reach http://localhost:8787.
|
|
179
|
+
allowPrivateHosts: true
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Failures
|
|
184
|
+
|
|
185
|
+
A failed fetch becomes a `WebFetchError` with `status`, `code`, `retryable`, and, where there is one, `url`, `contentType`, and (for a timeout) `timeoutMs`. Every error has a `code`:
|
|
186
|
+
|
|
187
|
+
| Code | Retryable | When |
|
|
188
|
+
| ------------------------------------ | ---------------------- | ------------------------------------------------------------------------ |
|
|
189
|
+
| `invalid_web_fetch_input` | No | The URL, `offset`, or `format` is malformed, or the URL is too long. |
|
|
190
|
+
| `web_fetch_disallowed_url` | No | The URL's scheme, credentials, or host is not allowed by the URL policy. |
|
|
191
|
+
| `web_fetch_disallowed_redirect` | No | A redirect pointed at a URL the policy does not allow, or https to http. |
|
|
192
|
+
| `web_fetch_too_many_redirects` | No | More than `maxRedirects` redirects, or a redirect loop. |
|
|
193
|
+
| `web_fetch_timeout` | Yes | The fetch took longer than `timeoutMs`. |
|
|
194
|
+
| `web_fetch_too_large` | No | The body is larger than `maxBytes`. |
|
|
195
|
+
| `web_fetch_unsupported_content_type` | No | The body is binary, or a document requested with `format: "raw"`. |
|
|
196
|
+
| `web_fetch_http_error` | 5xx, 408, and 429 only | A 4xx or 5xx response with an empty or non-textual body. |
|
|
197
|
+
| `web_fetch_blocked` | No | The site answered with a bot challenge (`cf-mitigated: challenge`). |
|
|
198
|
+
| `web_fetch_conversion_failed` | Yes | Workers AI could not convert the body to Markdown. |
|
|
199
|
+
| `web_fetch_unavailable` | Yes | The network failed: DNS, TLS, the connection, or reading the body. |
|
|
200
|
+
|
|
201
|
+
The error's `message` is written for you. The model gets different text that tells it what to do next: fix the input, retry once, retry with `format: "raw"` after a failed HTML conversion (a document that fails to convert is not retried), or move on.
|
|
202
|
+
|
|
203
|
+
- **Pi**: the tool returns an error result with the model's text, and `details` is `{ ok: false, message, status, code, retryable, url?, contentType? }`.
|
|
204
|
+
- **AI SDK** and **TanStack AI**: the tool throws a `WebFetchError`, which is how those frameworks report tool errors. Its `message` is the model's text, and its `cause` is the original error with the detail.
|
|
205
|
+
|
|
206
|
+
Every adapter entry point re-exports `WebFetchError`, so `instanceof` checks do not need a second import.
|
|
207
|
+
|
|
208
|
+
If the harness cancels the call, the fetch is aborted and the abort propagates instead of becoming a failed result.
|
|
209
|
+
|
|
210
|
+
## With Web Search
|
|
211
|
+
|
|
212
|
+
`web_fetch` pairs with [`web_search`](./search-the-web.md): search finds pages, fetch reads them. Each tool's description names the other, so a model with both searches, then fetches the results it wants to read.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { webFetchTool } from "agents/webfetch/ai-sdk";
|
|
216
|
+
import { webSearchTool } from "agents/websearch/ai-sdk";
|
|
217
|
+
|
|
218
|
+
const tools = {
|
|
219
|
+
web_search: webSearchTool({ binding: this.env.AI }),
|
|
220
|
+
web_fetch: webFetchTool({ binding: this.env.AI })
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## What It Is Not
|
|
225
|
+
|
|
226
|
+
- **Not an HTTP client.** There is no POST, no request body, and no custom headers, cookies, or credentials. For calls to your own APIs, write a tool for them.
|
|
227
|
+
- **Not a browser.** Pages are not rendered, so content that only appears after JavaScript runs is missing. For rendered pages, screenshots, or interaction, see [Browse the Web](./browse-the-web.md).
|
|
228
|
+
- **Not a summariser.** The model reads the page itself, a window at a time.
|
|
229
|
+
|
|
230
|
+
## Other Sources
|
|
231
|
+
|
|
232
|
+
`fetchWeb` from `agents/webfetch` fetches one URL with no model involved: the same pipeline, URL policy, limits, and `timeoutMs`. It returns the whole page as a `WebFetchPage`: the fields of `WebFetchToolOutput` with all of `content`, and no `offset` or `nextOffset`. Use it from scheduled jobs, callables, or tests:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { WebFetchError, fetchWeb } from "agents/webfetch";
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const page = await fetchWeb(
|
|
239
|
+
{ url: "https://developers.cloudflare.com/agents/" },
|
|
240
|
+
{ binding: this.env.AI, maxBytes: 1_000_000 }
|
|
241
|
+
);
|
|
242
|
+
console.log(page.title, page.via, page.totalChars);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error instanceof WebFetchError) console.log(error.code, error.retryable);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
To show a page a window at a time without a model, for example in a UI that pages through what the model would read, cut it with `windowWebFetchPage` and render each window with `renderWebFetchPage`. This is exactly what the tool does, and it does not fetch again:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { renderWebFetchPage, windowWebFetchPage } from "agents/webfetch";
|
|
252
|
+
|
|
253
|
+
const first = windowWebFetchPage(page, { offset: 0, pageChars: 20_000 });
|
|
254
|
+
console.log(renderWebFetchPage(first));
|
|
255
|
+
// Next window: windowWebFetchPage(page, { offset: first.nextOffset, … }),
|
|
256
|
+
// until nextOffset is null.
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`createDirectWebFetch` from `agents/webfetch` returns a `WebFetchSource`: an object whose `fetch({ url, format? }, { signal? })` returns a `WebFetchPage`. A source validates `url` and `format` itself (the tool checks only `offset`), throwing `invalid_web_fetch_input` for a malformed request. Any `WebFetchSource` can be passed to a tool as `source` instead of `binding`. That is how tests substitute a fake, and how you wrap a source with caching or logging:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { createDirectWebFetch } from "agents/webfetch";
|
|
263
|
+
import { webFetchTool } from "agents/webfetch/ai-sdk";
|
|
264
|
+
|
|
265
|
+
const direct = createDirectWebFetch({ binding: this.env.AI });
|
|
266
|
+
|
|
267
|
+
webFetchTool({
|
|
268
|
+
source: {
|
|
269
|
+
async fetch(request, options) {
|
|
270
|
+
const page = await direct.fetch(request, options);
|
|
271
|
+
console.log(request.url, page.status, page.bytes);
|
|
272
|
+
return page;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
```
|
package/docs/harnesses/pi.md
CHANGED
|
@@ -125,6 +125,31 @@ harness: async ({ storage, context }) => {
|
|
|
125
125
|
|
|
126
126
|
The existing `@cloudflare/think` skills work out-of-the-box.
|
|
127
127
|
|
|
128
|
+
### Browser
|
|
129
|
+
|
|
130
|
+
`browserTool` from `agents/browser/pi` gives the model a persistent browser it drives with the Chrome DevTools Protocol. Put a `Browser` on the object's Lifecycle and install the tool in an extension:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { Browser, browserRun } from "agents/browser";
|
|
134
|
+
import { browserTool } from "agents/browser/pi";
|
|
135
|
+
|
|
136
|
+
readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) });
|
|
137
|
+
|
|
138
|
+
harness: async ({ storage, context }) => {
|
|
139
|
+
this.registry.install({
|
|
140
|
+
name: "browser",
|
|
141
|
+
tools: [
|
|
142
|
+
browserTool({ ctx: this.ctx, browser: this.browser, loader: this.env.LOADER })
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
return Harness.open(storage, { models, registry: this.registry }, context);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
readonly lifecycle = Lifecycle.install(this).use(this.browser).use(this.harness);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It needs a `browser` binding, a `LOADER` Worker Loader binding, and `export { CodemodeRuntime } from "@cloudflare/codemode"` from the Worker entry. Screenshots come back as images, and each conversation works in its own tab. Refer to [Persistent browser](../browse-the-web.md#persistent-browser) for how it behaves.
|
|
152
|
+
|
|
128
153
|
## Work with sessions
|
|
129
154
|
|
|
130
155
|
A session is a Pi conversation. Each has its own transcript, inbox, model and run, and they can run at the same time.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Think harness (Experimental)
|
|
3
|
+
pcx_content_type: reference
|
|
4
|
+
description: Run Think's agent loop in a Durable Object with the experimental ThinkHarness lifecycle capability. Durable transcripts and turns that survive eviction.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
`ThinkHarness` runs Think's agent loop as a Lifecycle capability. It has the same shape as the [Pi harness](./pi.md): `harness.prompt()`, `harness.sessions` and `harness.session(id)`. The engine is the AI SDK. The harness owns its storage, so you install only the harness:
|
|
8
|
+
|
|
9
|
+
- Each session's transcript is kept as AI SDK `UIMessage`s, in the same tables the `Sessions` capability uses.
|
|
10
|
+
- The output of a model call is kept while it streams, in the same tables the `Streams` capability uses.
|
|
11
|
+
- One Lifecycle job per session wakes the object after an eviction.
|
|
12
|
+
|
|
13
|
+
It is experimental. The API may change in any release.
|
|
14
|
+
|
|
15
|
+
## Create the harness
|
|
16
|
+
|
|
17
|
+
Install the harness on the Lifecycle:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { DurableObject } from "cloudflare:workers";
|
|
21
|
+
import { tool } from "ai";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
import { ThinkHarness } from "agents/harness/think";
|
|
24
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
25
|
+
import { createAI } from "agents/models/ai-sdk";
|
|
26
|
+
|
|
27
|
+
export class Assistant extends DurableObject<Env> {
|
|
28
|
+
ai = createAI({ binding: this.env.AI });
|
|
29
|
+
|
|
30
|
+
harness = new ThinkHarness({
|
|
31
|
+
model: this.ai("@cf/moonshotai/kimi-k2.7-code"),
|
|
32
|
+
system: "You are a helpful assistant.",
|
|
33
|
+
tools: {
|
|
34
|
+
weather: {
|
|
35
|
+
...tool({
|
|
36
|
+
description: "Get the weather for a city",
|
|
37
|
+
inputSchema: z.object({ city: z.string() }),
|
|
38
|
+
execute: async ({ city }) => `Sunny in ${city}`
|
|
39
|
+
}),
|
|
40
|
+
// Safe to run again if an eviction cuts a call short.
|
|
41
|
+
recovery: "rerun"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
lifecycle = Lifecycle.install(this).use(this.harness);
|
|
47
|
+
|
|
48
|
+
async ask(prompt: string) {
|
|
49
|
+
const result = await this.harness.prompt(prompt);
|
|
50
|
+
return result.status === "done" ? result.text : result.reason;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
On an `Agent`, call `this.lifecycle.use(this.harness)` in the constructor. Do not also install a `Sessions` capability that writes the harness's sessions: the harness caches what it wrote.
|
|
56
|
+
|
|
57
|
+
| Option | What it is |
|
|
58
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `model` | Required. An AI SDK `LanguageModel`, or a function of `{ session }` that returns one. |
|
|
60
|
+
| `system` | The system prompt, or a function of `{ session }`. |
|
|
61
|
+
| `tools` | An AI SDK `ToolSet`, or a function of `{ session }`. Tools with `execute` run on the server. Tools without it run on a client. A server tool may carry `recovery: "rerun"`. |
|
|
62
|
+
| `maxSteps` | Most model calls one operation makes. Default 10. |
|
|
63
|
+
| `toolApproval` | Decide per call whether a tool needs approval. A tool's own `needsApproval` also applies. |
|
|
64
|
+
| `recovery` | The budget for recovering interrupted work: `maxAttempts`, `backoffMs`, `stallTimeoutMs`. Refer to [Recovery](#recovery). |
|
|
65
|
+
| `reservedMetadataKeys` | Message-metadata keys only the server may write; stripped from input submitted with `source: "client"`. |
|
|
66
|
+
| `configureSession` | Called with each session's `Session` handle the first time the harness uses it. Set compaction here with `onCompaction()` and `compactAfter()`. |
|
|
67
|
+
| `hooks` | Callbacks into the turn loop. Refer to [Hooks](#hooks). |
|
|
68
|
+
|
|
69
|
+
## Submit and wait
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const session = this.harness.session(); // the root session, id ""
|
|
73
|
+
|
|
74
|
+
// Durable before the model runs. The same operationId twice returns the same receipt.
|
|
75
|
+
const receipt = await session.submit("Summarize the README", {
|
|
76
|
+
operationId: requestId
|
|
77
|
+
});
|
|
78
|
+
const result = await session.wait(receipt.operationId);
|
|
79
|
+
|
|
80
|
+
// Or both in one call, with the transcript after it.
|
|
81
|
+
const { text, messages } = await session.prompt("Hello");
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`submit()` accepts a string, a `UIMessage` or an array of them, the shared harness input `{ parts }`, or a tool answer. A submission made while the session is busy waits its turn. Steering the running turn is not supported: `whenBusy: "steer"` throws `SteerNotSupportedError`.
|
|
85
|
+
|
|
86
|
+
| Method | What it does |
|
|
87
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `submit(input, options)` | Durably queue input. Returns `{ operationId, session, accepted }`. |
|
|
89
|
+
| `prompt(input, options)` | `submit`, then `wait`, then the transcript. |
|
|
90
|
+
| `wait(operationId, signal?)` | Resolve when the operation settles: `done` with `text`, or `unanswered` with a `reason`. |
|
|
91
|
+
| `inspect(operationId)` | The operation's status, or `undefined`. |
|
|
92
|
+
| `chat(input, callback)` | Stream the answer to `onStart`, `onEvent(json)`, then `onDone` or `onError`, like Think's `chat()`. |
|
|
93
|
+
| `abort(operationId?)` | Withdraw a queued operation or abort a running one. With no id, everything open in the session. |
|
|
94
|
+
| `regenerate(messageId?)` | Answer a user message again, as a new branch. `Session.getBranches()` still reads the earlier answers. |
|
|
95
|
+
| `continue()` | Call the model again to continue the latest message. |
|
|
96
|
+
| `reset(handoff?)` | Abort everything and clear the transcript, optionally leaving a system note. |
|
|
97
|
+
| `messages()` | The active transcript. |
|
|
98
|
+
| `pending()` | Operations not settled yet, oldest first. |
|
|
99
|
+
| `subscribe(listener)` | Live events in the AI SDK's vocabulary: chunks, persisted messages, operation status, run start and end, reset, and `transcript` (deleted messages or a compaction; re-read the transcript). |
|
|
100
|
+
| `inFlight()` | The running operation and its model call's chunks so far, including chunks an eviction left in storage. |
|
|
101
|
+
| `watch()` | The shared harness interface's watch, so a Channels host can serve the session. |
|
|
102
|
+
|
|
103
|
+
`harness.sessions` has `create()`, `fork(from)` and `list()`. A fork copies the source session's active path.
|
|
104
|
+
|
|
105
|
+
A session also reads and shapes its transcript:
|
|
106
|
+
|
|
107
|
+
| Method | What it does |
|
|
108
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| `branches(messageId)` | The answers to a message, its children in the tree. A regenerated answer is a branch beside the others. |
|
|
110
|
+
| `search(query, { limit })` | Full-text search over the session's messages. |
|
|
111
|
+
| `compact()` | Summarize older messages with the compaction function set in `configureSession`. |
|
|
112
|
+
|
|
113
|
+
`configureSession` receives each session's `Session` handle from `agents/sessions`, for compaction settings and for writing messages without starting a turn. Those writes still reach `subscribe()` listeners and `ThinkChat` clients: an append or update as a `message` event, a deletion or compaction as a `transcript` event.
|
|
114
|
+
|
|
115
|
+
## Tools, approvals and client tools
|
|
116
|
+
|
|
117
|
+
The harness runs every server tool call itself. The model only sees tool definitions, so each model call ends at its tool calls. The harness then runs those calls in parallel and calls the model again with their results. All of this stays in one assistant message.
|
|
118
|
+
|
|
119
|
+
A call that needs approval is recorded as `approval-requested` and the operation settles. Submit the answer to continue:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
await session.submit({ type: "approval", approvalId, approved: true });
|
|
123
|
+
await session.submit({
|
|
124
|
+
type: "tool-result",
|
|
125
|
+
toolCallId,
|
|
126
|
+
result: { ok: true, output: "42" }
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A client tool call works the same way: the operation settles with the call `input-available`, and the client's result continues the turn. Pass `autoContinue: false` with an answer to record it without continuing. Tool schemas a browser sends are kept with the session through `submit(..., { clientTools })`.
|
|
131
|
+
|
|
132
|
+
Input submitted with `source: "client"` is untrusted:
|
|
133
|
+
|
|
134
|
+
- It may only add `user` messages. Any other role ends the operation `unanswered` with reason `client_role`.
|
|
135
|
+
- A message whose id is already stored, on any branch, is dropped rather than rewritten.
|
|
136
|
+
- A client tool never replaces a server tool of the same name.
|
|
137
|
+
- A tool result answers only a client tool. A result for a server tool call ends `unanswered` with reason `not_client_tool`, so a server tool's result always comes from running it under its approval policy.
|
|
138
|
+
|
|
139
|
+
## Recovery
|
|
140
|
+
|
|
141
|
+
Every step of a turn reads only durable state, so a restarted object continues where the last write left off.
|
|
142
|
+
|
|
143
|
+
- **During a model call.** Chunks are written to a stream as they arrive. When the call ends, the harness persists the message and deletes the stream in one SQLite transaction. After an eviction, the harness rebuilds the partial message from the stream, keeps it, and calls the model again to continue the same message. Tool calls whose input never finished streaming are dropped.
|
|
144
|
+
- **During a tool call.** The harness records each call before it runs. After an eviction, a call with a record and no result was cut short. A tool that carries `recovery: "rerun"` runs again. Any other tool (`"report"`, the default) has the call recorded as failed with an "interrupted" error and the model decides what to do. Use `rerun` only for tools that are safe to repeat.
|
|
145
|
+
- **Budget.** `recovery.maxAttempts` (default 10) counts interruptions without progress. A finished model call or tool call resets the count. Past the budget the operation settles `unanswered` with reason `interrupted`. Retries after the first back off from `recovery.backoffMs` (default 1000), doubling up to a minute.
|
|
146
|
+
- **Stalls.** A model stream that sends nothing for `recovery.stallTimeoutMs` (default 120000) is treated as interrupted.
|
|
147
|
+
- **Memory limits.** The wake jobs are flagged for the Lifecycle's alarm memory-limit breaker, so a turn that keeps running out of memory is backed off. When the breaker seals, the running operation is settled `unanswered` with reason `out_of_memory` on the next start, keeping any partial answer, rather than run again. A restart keeps a wake already set for later, so backoff survives a deploy.
|
|
148
|
+
|
|
149
|
+
## Hooks
|
|
150
|
+
|
|
151
|
+
| Hook | When |
|
|
152
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
153
|
+
| `beforeTurn` | Before every model call. Return `model`, `system`, `messages`, `activeTools`, `toolChoice`, `providerOptions`, `maxOutputTokens` or `temperature`. |
|
|
154
|
+
| `beforeToolCall` | Before each server tool call. Return `{ action: "block", reason }`, `{ action: "substitute", output }`, or `{ action: "run", input }`. |
|
|
155
|
+
| `afterToolCall` | After each server tool call, with its result. |
|
|
156
|
+
| `onStepFinish` | After each model call's message is persisted. |
|
|
157
|
+
| `onChunk` | For each UI chunk the model streams. |
|
|
158
|
+
| `onTurnEnd` | After an operation settles. |
|
|
159
|
+
| `onError` | Observe a model call's error. |
|
|
160
|
+
| `classifyError` | Return `fail`, `retry`, or `context-overflow`. `classifyContextOverflow` matches the common providers' overflow errors. |
|
|
161
|
+
| `onRecovery` | Return `continue` or `abandon` for an interrupted model call. |
|
|
162
|
+
|
|
163
|
+
`beforeTurn` runs before every model call because a turn makes several, and after an eviction the harness has no memory of what an earlier call returned. On `context-overflow` the harness compacts the session with its compaction function and calls the model again, once.
|
|
164
|
+
|
|
165
|
+
## Serve useAgentChat
|
|
166
|
+
|
|
167
|
+
`ThinkChat` speaks Think's chat WebSocket protocol for one session, so `useAgentChat` works unchanged. It also answers `GET …/get-messages`:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { ThinkChat } from "agents/harness/think";
|
|
171
|
+
import { WebSockets } from "agents/websockets";
|
|
172
|
+
|
|
173
|
+
webSockets = new WebSockets();
|
|
174
|
+
chat = new ThinkChat({ harness: this.harness, webSockets: this.webSockets });
|
|
175
|
+
|
|
176
|
+
lifecycle = Lifecycle.install(this)
|
|
177
|
+
.use(this.harness)
|
|
178
|
+
.use(this.webSockets)
|
|
179
|
+
.use(this.chat);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It handles chat requests (`submit-message` and `regenerate-message`), cancel, clear, client tool results, approvals, and stream resume after a reconnect or an eviction.
|
|
183
|
+
|
|
184
|
+
## Compared with Think
|
|
185
|
+
|
|
186
|
+
`ThinkHarness` is meant to replace the engine inside `@cloudflare/think`. It reads and writes the same Sessions and Streams tables Think uses, and the root session is Think's conversation. Work in flight when an agent moves over is not carried across.
|
|
187
|
+
|
|
188
|
+
| Think | ThinkHarness |
|
|
189
|
+
| -------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
190
|
+
| `getModel()`, `getSystemPrompt()`, `getTools()` | `model`, `system`, `tools` options |
|
|
191
|
+
| `beforeTurn`, `beforeToolCall`, `afterToolCall`, `onStepFinish`, `onChunk` | Hooks of the same names |
|
|
192
|
+
| `beforeStep` | `beforeTurn`, which runs before every model call |
|
|
193
|
+
| `onChatResponse`, `onChatError`, `classifyChatError` | `onTurnEnd`, `onError`, `classifyError` |
|
|
194
|
+
| `onChatRecovery`, `chatRecovery` | `onRecovery`, `recovery` |
|
|
195
|
+
| `submitMessages`, `waitForSubmission`, `inspectSubmission` | `submit`, `wait`, `inspect` |
|
|
196
|
+
| `saveMessages`, `runTurn` | `prompt` |
|
|
197
|
+
| `chat(message, callback)` | `session.chat(input, callback)` |
|
|
198
|
+
| `cancelChat`, `cancelSubmission`, `cancelAllChats` | `abort(operationId)`, `abort()` |
|
|
199
|
+
| `continueLastTurn`, regeneration | `continue()`, `regenerate()` |
|
|
200
|
+
| `clearMessages`, `getMessages`, `messages` | `reset()`, `messages()` |
|
|
201
|
+
| `configureSession`, compaction | `configureSession` |
|
|
202
|
+
| `useAgentChat` protocol | `ThinkChat` |
|
|
203
|
+
| One conversation per Durable Object | Many sessions per object, with `create`, `fork` and `list` |
|
|
204
|
+
| Tools run inside `streamText`; interrupted calls are repaired | The harness runs each call; a tool can opt into `recovery: "rerun"` |
|
|
205
|
+
|
|
206
|
+
Not supported yet:
|
|
207
|
+
|
|
208
|
+
- Steering a running turn. `submit()` throws `SteerNotSupportedError` for `whenBusy: "steer"` rather than queue the input as a follow-up.
|
|
209
|
+
- `messageConcurrency` strategies other than queueing (`latest`, `merge`, `drop`, debounce).
|
|
210
|
+
- Context blocks from `configureContext()`. Compute the system prompt in `system` or `beforeTurn`.
|
|
211
|
+
- Read-time truncation of older tool results and media eviction.
|
|
212
|
+
- Workspace tools, `getActions()` with its ledger, agent tools, skills, extensions, MCP tools, messengers, channels policy, scheduled tasks, and workflows. Pass the tools these produce in `tools`; the higher-level features are not ported.
|
|
213
|
+
- `listSubmissions` and `deleteSubmissions`. `pending()` lists open operations.
|
|
214
|
+
- Client tool ownership by participant, and the `cf_agent_chat_recovering` frame.
|
|
215
|
+
- `configure()` and `getConfig()`.
|