agents 0.25.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/{harnesses → harness}/pi/index.d.ts +85 -88
- package/dist/{harnesses → harness}/pi/index.js +196 -77
- package/dist/harness/pi/index.js.map +1 -0
- 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 +23 -21
- package/dist/models/pi-ai/index.js +7 -6
- 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/pi.md +114 -32
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +4 -3
- package/docs/lifecycle.md +40 -0
- package/docs/models-pi-ai.md +5 -5
- package/docs/search-the-web.md +133 -0
- package/package.json +126 -47
- 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/harnesses/pi/index.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,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()`.
|
package/docs/index.md
CHANGED
|
@@ -58,7 +58,6 @@ The differentiator is not "we have durable state" — it is what happens when a
|
|
|
58
58
|
- [Push Notifications](./push-notifications.md) - Browser push notifications via Web Push API and scheduled delivery
|
|
59
59
|
- TODO: [SMS](./sms.md) - Text message integration (Twilio, etc.)
|
|
60
60
|
- [Voice Agents](./voice.md) - Build voice agents with real-time speech-to-text, text-to-speech, and conversation persistence
|
|
61
|
-
- [Channels](./channels.md) - Send and receive messages through Slack, Telegram, email, browser voice, or custom adapters
|
|
62
61
|
- [Chat SDK State](./chat-sdk.md) - Store Chat SDK subscriptions, locks, queues, and history in Agents sub-agents
|
|
63
62
|
|
|
64
63
|
## Background Processing
|
|
@@ -80,8 +79,9 @@ The differentiator is not "we have durable state" — it is what happens when a
|
|
|
80
79
|
- [Server-Driven Messages](./server-driven-messages.md) - Autonomous agent workflows: scheduled follow-ups, queue processing, webhooks, chained reasoning
|
|
81
80
|
- TODO: [Using AI Models](./using-ai-models.md) - OpenAI, Anthropic, Workers AI, and other providers
|
|
82
81
|
- [Models (Experimental)](./models.md) - `createAI` — one AI SDK provider for Workers AI and third-party catalog models, same string space
|
|
83
|
-
- [Models for pi-ai (
|
|
84
|
-
- [Pi harness (
|
|
82
|
+
- [Models for pi-ai (Beta)](./models-pi-ai.md) - `createAI` for pi-ai: Workers AI ids and third-party models through AI Gateway, as a pi-ai provider
|
|
83
|
+
- [Pi harness (Beta)](./harnesses/pi.md) - Host pi-durable sessions in a Durable Object with durable storage and lifecycle wakeups
|
|
84
|
+
- [Think harness (Experimental)](./harnesses/think.md) - Run Think's agent loop as a Lifecycle capability, with transcripts in Sessions, output in Streams, and turns that survive eviction
|
|
85
85
|
- TODO: [RAG (Retrieval Augmented Generation)](./rag.md) - Vector search with Vectorize
|
|
86
86
|
- [Sessions (Experimental)](./sessions.md) - Durable message trees, streamed history, compaction, search, and lossless attachment offload
|
|
87
87
|
- [Context (Experimental)](./context.md) - System-prompt blocks, frozen prompts, writable/searchable/loadable providers, and their tools
|
|
@@ -128,6 +128,7 @@ The differentiator is not "we have durable state" — it is what happens when a
|
|
|
128
128
|
## Compute Environments
|
|
129
129
|
|
|
130
130
|
- [Browse the Web (Experimental)](./browse-the-web.md) - Full CDP access for web inspection, scraping, and debugging
|
|
131
|
+
- [Search the Web (Beta)](./search-the-web.md) - A `websearch` tool over Cloudflare's Web Search API for the pi harness, the AI SDK, and TanStack AI
|
|
131
132
|
- TODO: [Cloudflare Sandboxes](./sandboxes.md) - Isolated environments for coding agents, ffmpeg, and heavy compute
|
|
132
133
|
|
|
133
134
|
## Advanced Topics
|
package/docs/lifecycle.md
CHANGED
|
@@ -156,6 +156,46 @@ Host-specific bindings, authentication, and protocol adapters remain explicit
|
|
|
156
156
|
constructor dependencies. Lifecycle never grants a capability the complete
|
|
157
157
|
host implicitly.
|
|
158
158
|
|
|
159
|
+
A capability's public operations can run before startup, because
|
|
160
|
+
[native RPC](#native-rpc) bypasses the automatic startup on fetch, alarm and
|
|
161
|
+
WebSocket events. If an operation starts work that `onStart` also awaits, such
|
|
162
|
+
as lazily opening a store, it must call `await this.lifecycle.ready()` first:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
class Store extends LifecycleCapability {
|
|
166
|
+
#opening: Promise<Database> | undefined;
|
|
167
|
+
|
|
168
|
+
constructor() {
|
|
169
|
+
super("store");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async onStart(): Promise<void> {
|
|
173
|
+
await this.#open();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async get(key: string): Promise<string | undefined> {
|
|
177
|
+
const db = await this.#open();
|
|
178
|
+
return db.get(key);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async #open(): Promise<Database> {
|
|
182
|
+
// Without this, an RPC that calls get() before startup begins the open
|
|
183
|
+
// outside startup, and onStart then waits on it behind a closed gate.
|
|
184
|
+
await this.lifecycle.ready();
|
|
185
|
+
this.#opening ??= openDatabase(this.lifecycle.storage);
|
|
186
|
+
return this.#opening;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Startup holds the input gate (`blockConcurrencyWhile`) while `onStart` runs, so
|
|
192
|
+
work begun outside startup has its timers and I/O held back and never finishes.
|
|
193
|
+
`onStart` waits on it until startup times out after 30 seconds and the object
|
|
194
|
+
is reset. Whether it happens depends on whether the RPC arrives first, so it
|
|
195
|
+
shows up as an intermittent timeout. `ready()` resolves immediately inside
|
|
196
|
+
startup, so `onStart` can call the same guarded method. `Tasks`, `Streams`,
|
|
197
|
+
`Scheduler`, `Queue` and `PiHarness` guard their operations this way.
|
|
198
|
+
|
|
159
199
|
Capability hooks run outside host context, but user callbacks run through
|
|
160
200
|
`this.lifecycle.runInHostContext(fn)` inside the host invocation context.
|
|
161
201
|
Scheduler and Queue dispatch their registered callbacks through this
|
package/docs/models-pi-ai.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Models for pi-ai (
|
|
1
|
+
# Models for pi-ai (Beta)
|
|
2
2
|
|
|
3
3
|
`agents/models/pi-ai` is the [pi-ai](https://github.com/earendil-works/pi/tree/main/packages/ai)
|
|
4
4
|
twin of [`agents/models/ai-sdk`](./models.md): the same `createAI` factory, the same options,
|
|
@@ -45,7 +45,7 @@ design: a model this package cannot name exactly is yours to construct.
|
|
|
45
45
|
store on the gateway (BYOK) — so nothing vendor-shaped needs a secret in your Worker.
|
|
46
46
|
4. **The binding is the only way in.** Workers AI goes through `env.AI.run`, vendor models
|
|
47
47
|
through `env.AI.gateway(id).run`; there is no HTTP transport and no API token to hold.
|
|
48
|
-
5. Everything here is
|
|
48
|
+
5. Everything here is in beta and may change in a minor release.
|
|
49
49
|
|
|
50
50
|
## Quick start
|
|
51
51
|
|
|
@@ -311,10 +311,10 @@ Image generation is Workers AI only. The knobs a model takes are the model's:
|
|
|
311
311
|
in a `cloudflare-compat` diagnostic on the result rather than sent and ignored.
|
|
312
312
|
Every other model in the catalog takes the full set.
|
|
313
313
|
|
|
314
|
-
##
|
|
314
|
+
## Beta
|
|
315
315
|
|
|
316
|
-
This subpath needs `@earendil-works/pi-ai@^0.
|
|
317
|
-
takes no vendor package as a dependency, optional or otherwise. The surface is
|
|
316
|
+
This subpath needs `@earendil-works/pi-ai@^1.0.0`, an optional peer of `agents`. `agents`
|
|
317
|
+
takes no vendor package as a dependency, optional or otherwise. The surface is in beta
|
|
318
318
|
and may change in a minor release. An unlisted `@cf/` id resolves with default metadata (a
|
|
319
319
|
128k context window, 8192 output tokens, zero cost); pass overrides when you know better.
|
|
320
320
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Search the Web (Beta)
|
|
2
|
+
|
|
3
|
+
`agents/websearch` gives a model a `web_search` tool over Cloudflare's [Web Search API](https://developers.cloudflare.com/web-search/), called through the `AI` binding and billed by the account's AI Gateway. The same tool is available for the pi harness, the AI SDK, and TanStack AI.
|
|
4
|
+
|
|
5
|
+
This page covers what the SDK adds on top of the API. For the binding, the providers, pricing, payment, and the error codes, see the [Web Search API docs](https://developers.cloudflare.com/web-search/).
|
|
6
|
+
|
|
7
|
+
> **Beta** — this feature may have breaking changes in future releases.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
You need:
|
|
12
|
+
|
|
13
|
+
- An `AI` binding: `"ai": { "binding": "AI" }` in `wrangler.jsonc`.
|
|
14
|
+
- workerd 1.20260924.1 or later, which is where `env.AI.websearch()` arrived. It ships with wrangler 4.141.0 and `@cloudflare/vite-plugin` 1.60.2.
|
|
15
|
+
- AI Gateway credits or a provider key on the gateway, in the account the Worker runs in. That account pays for every search. The `AI` binding always calls Cloudflare, so under `wrangler dev` searches run against, and bill, the account you are logged in to.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
Pi harness:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { webSearchTool } from "agents/websearch/pi";
|
|
23
|
+
|
|
24
|
+
this.registry.install({
|
|
25
|
+
name: "tools",
|
|
26
|
+
tools: [webSearchTool({ binding: this.env.AI, provider: "exa" })]
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
AI SDK:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { webSearchTool } from "agents/websearch/ai-sdk";
|
|
34
|
+
|
|
35
|
+
const result = streamText({
|
|
36
|
+
model,
|
|
37
|
+
tools: { web_search: webSearchTool({ binding: this.env.AI }) },
|
|
38
|
+
messages
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If you convert UI messages yourself, pass the same tools to `convertToModelMessages(messages, { tools })`. Without them, the AI SDK sends earlier search results back to the model as the full JSON response instead of the trimmed text. `AiSdkHarness` does this for you.
|
|
43
|
+
|
|
44
|
+
TanStack AI:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { webSearchTool } from "agents/websearch/tanstack-ai";
|
|
48
|
+
|
|
49
|
+
const tools = [webSearchTool({ binding: this.env.AI })];
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Options
|
|
53
|
+
|
|
54
|
+
The model's input is `{ query, limit? }` and nothing else. The host fixes everything that affects cost or data handling. An invalid `limit`, `maxDescriptionChars`, `timeoutMs`, or `byokAlias` throws a `RangeError` when the tool is created:
|
|
55
|
+
|
|
56
|
+
| Option | Default | Notes |
|
|
57
|
+
| --------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `binding` | — | The `AI` binding. Or pass `source` instead (see [Other sources](#other-sources)). |
|
|
59
|
+
| `gateway` | `"default"` | AI Gateway id. |
|
|
60
|
+
| `provider` | platform default | `"ceramic"`, `"exa"`, or `"linkup"`. The model cannot choose or change it. |
|
|
61
|
+
| `byokAlias` | — | Bill a provider key stored on the gateway. Passed through as the API defines it. |
|
|
62
|
+
| `limit` | `5` | Results per search when the model does not ask for a count, and the most it gets when it asks for more. The tool description tells the model this number. |
|
|
63
|
+
| `maxDescriptionChars` | `600` | Per-result description length in the model's view. `Infinity` passes descriptions through whole. |
|
|
64
|
+
| `description` | built-in description | Replaces the tool description the model sees. |
|
|
65
|
+
| `timeoutMs` | `30000` | Give up on a search after this long, as a retryable `web_search_timeout` failure. |
|
|
66
|
+
|
|
67
|
+
## Model Interface
|
|
68
|
+
|
|
69
|
+
The model gets text: a numbered list of title, URL, and description, with descriptions trimmed to `maxDescriptionChars`. Some providers return descriptions of several thousand characters per result, so the default keeps a five-result search to a few kilobytes of context.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
3 results for "cloudflare web search api":
|
|
73
|
+
|
|
74
|
+
1. Introducing the Web Search API
|
|
75
|
+
https://blog.cloudflare.com/introducing-web-search-api/
|
|
76
|
+
Today we are launching the Web Search API in open beta…
|
|
77
|
+
|
|
78
|
+
2. …
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The host gets the full API response, without trimming, as `WebSearchToolOutput`: `items` with their whole descriptions and every documented field, `metadata` with `requestId` and `latencyMs`, plus `provider`. Items without a URL are dropped, a missing title falls back to the URL, and fields the API does not document are left out:
|
|
82
|
+
|
|
83
|
+
- **Pi**: in the tool result's `details`, as `{ ok: true, output }`. The tool is `replay: "safe"`: if a search is interrupted mid-call, for example by an eviction, pi runs it again when the session recovers, and that is a second billed search. Completed results are stored and not searched again.
|
|
84
|
+
- **AI SDK**: as the return value of `execute`, so `onFinish`, UI message parts, and logs see the full response. `toModelOutput` renders the text for the model.
|
|
85
|
+
- **TanStack AI**: the server tool returns the rendered text, so the host gets the same text the model does.
|
|
86
|
+
|
|
87
|
+
`renderWebSearchResults(output, { maxDescriptionChars })` from `agents/websearch` is the renderer, if you want the same text elsewhere.
|
|
88
|
+
|
|
89
|
+
The API has no pagination. The tool description tells the model to search again with a rephrased query when it wants more or different results.
|
|
90
|
+
|
|
91
|
+
## Failures
|
|
92
|
+
|
|
93
|
+
A failed search becomes a `WebSearchError` with `status`, `code`, `retryable`, and `requestId` (AI Gateway's id for the request, for the gateway log). Every error has a `code`: the API's own when it sends one, for example `web_search_payment_required`, otherwise one derived from the HTTP status, such as `web_search_rate_limited` or `web_search_unavailable`. On a runtime older than the one above, the binding has no `websearch()` and the search fails with code `web_search_unsupported_runtime`.
|
|
94
|
+
|
|
95
|
+
The error's `message` is written for you, and can say to top up credits or configure a key. The model gets different text that tells it what to do next: fix the query, retry once, or carry on without search.
|
|
96
|
+
|
|
97
|
+
- **Pi**: the tool returns an error result with the model's text, and `details` is `{ ok: false, message, status, code, retryable, requestId }`.
|
|
98
|
+
- **AI SDK** and **TanStack AI**: the tool throws a `WebSearchError`, which is how those frameworks report tool errors. Its `message` is the model's text, and its `cause` is the original error with the API's detail.
|
|
99
|
+
|
|
100
|
+
Every adapter entry point re-exports `WebSearchError`, so `instanceof` checks do not need a second import.
|
|
101
|
+
|
|
102
|
+
If the harness cancels the call, the search is aborted and the abort propagates instead of becoming a failed result.
|
|
103
|
+
|
|
104
|
+
## Other sources
|
|
105
|
+
|
|
106
|
+
`createAIWebSearch` and `createHTTPWebSearch` from `agents/websearch` return a `WebSearchSource`: an object whose `search({ query, limit? }, { signal? })` returns the API response (`limit` defaults to 5), with no model involved. Use them from scheduled jobs, or outside Workers:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createHTTPWebSearch } from "agents/websearch";
|
|
110
|
+
|
|
111
|
+
const search = createHTTPWebSearch({
|
|
112
|
+
accountId: env.CF_ACCOUNT_ID,
|
|
113
|
+
apiToken: env.CF_API_TOKEN,
|
|
114
|
+
provider: "linkup"
|
|
115
|
+
});
|
|
116
|
+
const { items } = await search.search({
|
|
117
|
+
query: "cloudflare agents sdk",
|
|
118
|
+
limit: 3
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Any `WebSearchSource` 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. Spread the source you wrap so its `provider` is kept:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
webSearchTool({
|
|
126
|
+
source: {
|
|
127
|
+
search: async ({ query }) => ({
|
|
128
|
+
items: [{ url: "https://example.com", title: query }],
|
|
129
|
+
metadata: { query, requestId: "test", latencyMs: 0 }
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
```
|