agents 0.28.1 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-routing-BMvWTFEp.d.ts → agent-routing-CDbpVjYq.d.ts} +7 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-ohoewW-B.d.ts → agent-tools-C8DH8Yii.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/{ai-ahruGw7e.d.ts → ai-iXw0uOGj.d.ts} +3 -3
- package/dist/{ai-sdk-C2iTAwUd.js → ai-sdk-DRPh2rRa.js} +185 -125
- package/dist/ai-sdk-DRPh2rRa.js.map +1 -0
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +2 -2
- package/dist/browser/index.d.ts +8 -8
- package/dist/browser/index.js +3 -3
- package/dist/browser/tools/ai-sdk.d.ts +2 -2
- package/dist/browser/tools/ai-sdk.js +1 -1
- package/dist/browser/tools/pi.d.ts +1 -1
- package/dist/browser/tools/pi.js +3 -3
- package/dist/browser/tools/pi.js.map +1 -1
- package/dist/browser/tools/tanstack-ai.d.ts +2 -2
- package/dist/browser/tools/tanstack-ai.js +1 -1
- package/dist/{browser-CEdNlsaD.js → browser-DccPw_wW.js} +130 -79
- package/dist/browser-DccPw_wW.js.map +1 -0
- package/dist/{browser-D30VlpcA.d.ts → browser-DlXhSAdf.d.ts} +41 -37
- package/dist/{browser-tool-4iomavs_.js → browser-tool-B1mhifin.js} +3 -3
- package/dist/{browser-tool-4iomavs_.js.map → browser-tool-B1mhifin.js.map} +1 -1
- package/dist/{browser-tool-vtrPOV8e.d.ts → browser-tool-Cxjk5sgG.d.ts} +2 -2
- package/dist/chat/index.d.ts +143 -71
- package/dist/chat/index.js +269 -110
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +33 -33
- package/dist/{client-Bp-JwL3p.d.ts → client-C27CtRZr.d.ts} +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/{connector-DEXzVoaY.d.ts → connector-20DJ6p4k.d.ts} +2 -2
- package/dist/{connector-CtZylXdk.js → connector-DAXBOIIi.js} +2 -2
- package/dist/{connector-CtZylXdk.js.map → connector-DAXBOIIi.js.map} +1 -1
- package/dist/experimental/channels/web/index.js +1 -1
- package/dist/harness/container/index.js +1 -1
- package/dist/harness/container/index.js.map +1 -1
- package/dist/harness/opencode/index.js +1 -1
- package/dist/harness/pi/index.d.ts +13 -5
- package/dist/harness/pi/index.js +8 -214
- package/dist/harness/pi/index.js.map +1 -1
- package/dist/{images-C4hiIexM.js → images-xaDv2Fqe.js} +636 -168
- package/dist/images-xaDv2Fqe.js.map +1 -0
- package/dist/index-DCxk3OSJ.d.ts +378 -0
- package/dist/{index-kbSUIer3.d.ts → index-iv9FTXX9.d.ts} +80 -112
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{input-schema-xBofjBKC.js → input-schema-B0Etuo-o.js} +2 -2
- package/dist/{input-schema-xBofjBKC.js.map → input-schema-B0Etuo-o.js.map} +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/{lifecycle-DGorpAmA.js → lifecycle-ClT4DmF2.js} +11 -3
- package/dist/lifecycle-ClT4DmF2.js.map +1 -0
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/models/ai-sdk/index.d.ts +29 -352
- package/dist/models/ai-sdk/index.js +1 -1
- package/dist/models/opencode/index.d.ts +25 -13
- package/dist/models/opencode/index.js +16 -11
- package/dist/models/opencode/index.js.map +1 -1
- package/dist/models/pi-ai/index.d.ts +13 -14
- package/dist/models/pi-ai/index.js +85 -32
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{session-store-DoPY12xM.d.ts → session-store-_IK6dV-w.d.ts} +14 -2
- package/dist/{session-store-Do4xkDcf.js → session-store-yvwq1f9x.js} +55 -2
- package/dist/session-store-yvwq1f9x.js.map +1 -0
- package/dist/{settings-BjAiPd6Z.js → settings-Dk2il2Hf.js} +4 -1
- package/dist/settings-Dk2il2Hf.js.map +1 -0
- package/dist/skills/index.d.ts +22 -28
- package/dist/skills/index.js +183 -143
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-CQxJFv4l.js → src-6lr7MtM_.js} +3 -3
- package/dist/src-6lr7MtM_.js.map +1 -0
- package/dist/sub-routing.d.ts +1 -1
- package/dist/{tool-VF0qiO7q.js → tool-DkT_DJBm.js} +2 -2
- package/dist/tool-DkT_DJBm.js.map +1 -0
- package/dist/transport-CPh7YGiw.d.ts +623 -0
- package/dist/voice/index.d.ts +1 -1
- package/dist/web/fetch/tools/pi.js +2 -2
- package/dist/web/fetch/tools/pi.js.map +1 -1
- package/dist/web/search/index.js +1 -1
- package/dist/web/search/tools/ai-sdk.js +2 -2
- package/dist/web/search/tools/pi.js +3 -3
- package/dist/web/search/tools/pi.js.map +1 -1
- package/dist/web/search/tools/tanstack-ai.js +2 -2
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-C3zy8YR0.js → websockets-CqxnntmE.js} +2 -2
- package/dist/{websockets-C3zy8YR0.js.map → websockets-CqxnntmE.js.map} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +1 -1
- package/docs/chat-agents.md +19 -18
- package/docs/harnesses/pi.md +47 -18
- package/docs/models-pi-ai.md +62 -20
- package/docs/models.md +197 -172
- package/package.json +7 -7
- package/dist/ai-sdk-C2iTAwUd.js.map +0 -1
- package/dist/browser-CEdNlsaD.js.map +0 -1
- package/dist/images-C4hiIexM.js.map +0 -1
- package/dist/lifecycle-DGorpAmA.js.map +0 -1
- package/dist/session-store-Do4xkDcf.js.map +0 -1
- package/dist/settings-BjAiPd6Z.js.map +0 -1
- package/dist/settings-DnD__xuE.d.ts +0 -349
- package/dist/src-CQxJFv4l.js.map +0 -1
- package/dist/tool-VF0qiO7q.js.map +0 -1
- package/dist/transport-DCyarnwm.d.ts +0 -200
package/docs/chat-agents.md
CHANGED
|
@@ -652,7 +652,7 @@ Durable bookkeeping stays enabled even when automatic continuation is not approp
|
|
|
652
652
|
|
|
653
653
|
- **Retries or side effects are unsafe:** override `onChatRecovery()` and return `{ continue: false }`. Persist idempotency keys or completion records before external side effects so a recovered turn can tell whether work already happened.
|
|
654
654
|
- **Cancellation must survive eviction:** an `AbortSignal` only cancels the current in-memory turn. Also persist cancellation intent in agent state or SQL, read it in `onChatRecovery()`, and return `{ continue: false }` when cancellation was requested.
|
|
655
|
-
- **Cost must be bounded:** set `maxAttempts`, `noProgressTimeoutMs`, `maxRecoveryWork`, and `
|
|
655
|
+
- **Cost must be bounded:** set `maxAttempts`, `noProgressTimeoutMs`, `maxRecoveryWork`, and `maxInterruptedAttempts`. Use `shouldKeepRecovering` with durable spend data to stop later attempts. The predicate is not bound to the agent instance, so read spend from a store keyed by `ctx.recoveryRootRequestId`.
|
|
656
656
|
- **A provider can resume without a new model call:** use `this.stash()` to save its response ID, retrieve that response in `onChatRecovery()`, and return `{ persist: false, continue: false }`.
|
|
657
657
|
|
|
658
658
|
`chatRecovery` can be configured with budgets and terminal behavior:
|
|
@@ -669,10 +669,10 @@ override chatRecovery = {
|
|
|
669
669
|
// finite backstop (10000). Set a different value to tune when a turn that
|
|
670
670
|
// keeps emitting content but never converges is sealed.
|
|
671
671
|
maxRecoveryWork: 2000,
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
|
|
672
|
+
// How many of one turn's attempts may die before recovery gives up, for
|
|
673
|
+
// example to a memory-limit reset on every run. Defaults to 5, counting
|
|
674
|
+
// the original turn.
|
|
675
|
+
maxInterruptedAttempts: 5,
|
|
676
676
|
// Caller policy consulted from the second recovery attempt onward. Return
|
|
677
677
|
// false to stop recovery. This is where you enforce a token/cost budget.
|
|
678
678
|
// Note: this is called as `config.shouldKeepRecovering(ctx)`, so it is not
|
|
@@ -689,16 +689,16 @@ override chatRecovery = {
|
|
|
689
689
|
|
|
690
690
|
**`chatRecovery` configuration:**
|
|
691
691
|
|
|
692
|
-
| Field
|
|
693
|
-
|
|
|
694
|
-
| `maxAttempts`
|
|
695
|
-
| `stableTimeoutMs`
|
|
696
|
-
| `terminalMessage`
|
|
697
|
-
| `noProgressTimeoutMs`
|
|
698
|
-
| `maxRecoveryWork`
|
|
699
|
-
| `
|
|
700
|
-
| `shouldKeepRecovering`
|
|
701
|
-
| `onExhausted`
|
|
692
|
+
| Field | Default | Description |
|
|
693
|
+
| ------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
694
|
+
| `maxAttempts` | `10` | Attempt cap before terminal exhaustion. Resets on forward progress, so it catches a tight no-progress alarm loop, not a healthy long turn. |
|
|
695
|
+
| `stableTimeoutMs` | `10_000` | How long a recovery attempt waits for the isolate to reach stable state before rescheduling. |
|
|
696
|
+
| `terminalMessage` | generic message | The message shown to the user when recovery is given up on. |
|
|
697
|
+
| `noProgressTimeoutMs` | `300_000` (5 min) | Primary stuck-turn bound: how long an incident may go without forward progress before it is sealed (`no_progress_timeout`). **Resets on every progress-bearing attempt**, so a turn that keeps producing content survives unbounded interruption. |
|
|
698
|
+
| `maxRecoveryWork` | `10000` | Runaway-loop guard. Maximum recovery work since the incident began before a still-progressing turn is sealed (`work_budget_exceeded`), counted in durable stream segments (see [Recovery work units](#recovery-work-units)). A generous finite backstop so an agent that keeps emitting a little content but never converges (for example an isolate that runs out of memory mid-stream on every recovery) cannot loop forever. Work only accrues from the first interruption until the turn completes. Set a higher value, or `Infinity`, for a very long agentic turn that legitimately needs more. |
|
|
699
|
+
| `maxInterruptedAttempts` | `5` | How many of one turn's attempts may be interrupted before recovery gives up (`interrupted_attempts_exceeded`). An attempt is interrupted when its isolate dies before it finishes: a Durable Object memory-limit reset, a deploy, or any other reset. The original turn counts, so the default runs the model at most five times for a turn that dies every time. The count is taken when the next isolate picks the turn up, before it re-runs anything, because an isolate killed by a memory-limit reset cannot record anything. Progress never resets it. A memory-limit reset and a deploy look the same from the next isolate, so a turn interrupted by five deploys in a row is also sealed. Values below `1` are read as `1`; `Infinity` disables the bound. Replaces `maxOomRetries`, which is still read as `maxInterruptedAttempts: maxOomRetries + 1`. |
|
|
700
|
+
| `shouldKeepRecovering` | — | Caller policy consulted from the second recovery attempt onward (never on the first detection, never once a hard bound has sealed the incident). Return `false` to stop recovery. The hook point for a token/cost budget — `ctx.work` is a coarse segment count, not tokens, so track real spend yourself. |
|
|
701
|
+
| `onExhausted` | — | Called once when recovery is given up on, before the terminal message is delivered. Inspect `ctx.reason` for why. |
|
|
702
702
|
|
|
703
703
|
**`ChatRecoveryProgressContext`** (the `ctx` passed to `shouldKeepRecovering`):
|
|
704
704
|
|
|
@@ -713,12 +713,13 @@ override chatRecovery = {
|
|
|
713
713
|
| `work` | `number` | Monotonic count of durable stream segments produced since the incident opened (not tokens). |
|
|
714
714
|
| `ageMs` | `number` | Wall-clock ms since the incident's first interruption. |
|
|
715
715
|
|
|
716
|
-
A progressing turn survives
|
|
716
|
+
A progressing turn survives repeated interruption (for example a dense deploy window) as long as it keeps making forward progress, stays under the `maxRecoveryWork` backstop, and fewer than `maxInterruptedAttempts` of its attempts die. Recovery is sealed only by one of these `ctx.reason` values:
|
|
717
717
|
|
|
718
718
|
- `no_progress_timeout` — no forward progress within the no-progress window (a stuck turn).
|
|
719
719
|
- `max_attempts_exceeded` — the attempt cap was spent on a tight no-progress alarm loop.
|
|
720
720
|
- `work_budget_exceeded` — the turn kept producing content but exceeded `maxRecoveryWork` (a runaway loop).
|
|
721
|
-
- `
|
|
721
|
+
- `interrupted_attempts_exceeded` — the turn's attempts kept dying before they finished (for example a memory-limit reset on every run) until `maxInterruptedAttempts` of them had been interrupted.
|
|
722
|
+
- `out_of_memory` — the alarm memory-limit circuit breaker sealed the recovery after repeated memory-limit resets.
|
|
722
723
|
- `recovery_aborted` — your `shouldKeepRecovering` hook returned `false`.
|
|
723
724
|
- `stable_timeout` — recovery attempts kept timing out waiting for stable state until the budget drained (extreme churn).
|
|
724
725
|
|
|
@@ -736,7 +737,7 @@ The unit is only meaningful relative to another `ctx.work` reading, so to choose
|
|
|
736
737
|
|
|
737
738
|
> **Upgrading an explicit `maxRecoveryWork`?** Releases before `agents@0.23.0` (`@cloudflare/ai-chat@0.12.0`, `@cloudflare/think@0.18.0`) counted "credited chunks" (one per tool call, one per text or reasoning segment start, and one per five seconds of streamed deltas), and the default was `1000`. The unit changed to durable segments and the default moved to `10000`. An explicit value is not recalibrated automatically, and there is no fixed conversion factor between the two units: a delta-heavy turn produces far more segments than it used to produce credits, while a turn dominated by tool calls changes much less. Measure `ctx.work` for your own turns before carrying an old value forward.
|
|
738
739
|
|
|
739
|
-
> **Out-of-memory crash loops
|
|
740
|
+
> **Out-of-memory crash loops.** A turn that exceeds the Durable Object's 128 MB limit on every run is bounded by `maxInterruptedAttempts`: each run that dies is counted when the next isolate picks the turn up, before it re-runs anything, and the fifth seals the turn with `interrupted_attempts_exceeded`. The isolate that hit the limit cannot record anything itself, because every storage call after the reset fails. The alarm boundary also catches memory-limit resets so the platform does not retry the alarm in a tight loop, and emits an `alarm:memory_limit_reset` observability event. Its strike counter (`maxAlarmMemoryLimitStrikes`, a base `Agent` static option, default `3`) only advances while storage still accepts writes, for example when the reset came from another isolate such as D1. None of this shrinks the working set: a turn whose context no longer fits in 128 MB needs a smaller transcript or fewer or smaller tool results.
|
|
740
741
|
|
|
741
742
|
#### Turns waiting on a human are not sealed
|
|
742
743
|
|
package/docs/harnesses/pi.md
CHANGED
|
@@ -14,7 +14,7 @@ Pi owns everything about a run: the transcript, the inbox of steers and follow-u
|
|
|
14
14
|
npm install agents @earendil-works/pi-durable @earendil-works/pi-ai
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Both Pi packages are optional peer dependencies of `agents`, at
|
|
17
|
+
Both Pi packages are optional peer dependencies of `agents`, at `~1.3.0`. pi-durable is experimental, and its API can change in minor releases, so `agents` supports one Pi minor at a time. The examples use Workers AI through the `AI` binding. To configure the binding and choose other models, refer to [Models for pi-ai](../models-pi-ai.md).
|
|
18
18
|
|
|
19
19
|
## Create the harness
|
|
20
20
|
|
|
@@ -27,6 +27,9 @@ import { Agent } from "agents";
|
|
|
27
27
|
import { PiHarness } from "agents/harness/pi";
|
|
28
28
|
import { createAI } from "agents/models/pi-ai";
|
|
29
29
|
|
|
30
|
+
// Skip Pi's idle caches (refer to the note below).
|
|
31
|
+
const settings = { contextRetentionMs: 0, documentRetentionMs: 0 };
|
|
32
|
+
|
|
30
33
|
export class Assistant extends Agent<Env> {
|
|
31
34
|
ai = createAI({ binding: this.env.AI });
|
|
32
35
|
registry = createRegistry();
|
|
@@ -37,7 +40,7 @@ export class Assistant extends Agent<Env> {
|
|
|
37
40
|
models.setProvider(this.ai.provider);
|
|
38
41
|
return Harness.open(
|
|
39
42
|
storage,
|
|
40
|
-
{ models, registry: this.registry },
|
|
43
|
+
{ models, registry: this.registry, settings },
|
|
41
44
|
context
|
|
42
45
|
);
|
|
43
46
|
},
|
|
@@ -63,6 +66,8 @@ export class Assistant extends Agent<Env> {
|
|
|
63
66
|
| `harness` | Required. Receives `{ storage, context }` and returns Pi's `Harness`, usually from `Harness.open`. Models, the registry, `settings`, `env` and `onReport` are all yours to build here. |
|
|
64
67
|
| `defaults` | What a new session starts with: `model`, a pi-ai `Model` such as `ai("@cf/…")`, and `thinkingLevel`. Without a model, a session's prompts end unanswered (`no_model`) until one is set. |
|
|
65
68
|
|
|
69
|
+
Set Pi's `contextRetentionMs` and `documentRetentionMs` settings to `0`. Pi keeps idle caches behind timers that default to ten minutes, and a pending timer keeps a Durable Object in memory: it cannot hibernate or be evicted, and it is billed for that time. The object's SQLite is local, so the caches save little.
|
|
70
|
+
|
|
66
71
|
The factory runs as part of startup, once per isolate. If it throws the operation that triggered it fails and the next operation tries again.
|
|
67
72
|
|
|
68
73
|
A model in `defaults` or `session.setModel()` is stored by its provider and id only. Pi resolves it at each request against the `Models` the factory opened Pi with, so it must be a model those `Models` list. Options passed to `ai(id, options)` there, such as `fallback`, are not applied.
|
|
@@ -89,7 +94,7 @@ const wordCount: ToolRegistration<typeof WordCount> = {
|
|
|
89
94
|
// `text` is typed from `parameters` and is validated by Pi.
|
|
90
95
|
async execute({ text }) {
|
|
91
96
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
92
|
-
return {
|
|
97
|
+
return { output: [{ type: "text", text: String(words) }] };
|
|
93
98
|
}
|
|
94
99
|
};
|
|
95
100
|
|
|
@@ -119,7 +124,11 @@ import { skills } from "agents/harness/pi";
|
|
|
119
124
|
|
|
120
125
|
harness: async ({ storage, context }) => {
|
|
121
126
|
this.registry.install(await skills(sources));
|
|
122
|
-
return Harness.open(
|
|
127
|
+
return Harness.open(
|
|
128
|
+
storage,
|
|
129
|
+
{ models, registry: this.registry, settings },
|
|
130
|
+
context
|
|
131
|
+
);
|
|
123
132
|
};
|
|
124
133
|
```
|
|
125
134
|
|
|
@@ -142,7 +151,11 @@ harness: async ({ storage, context }) => {
|
|
|
142
151
|
browserTool({ ctx: this.ctx, browser: this.browser, loader: this.env.LOADER })
|
|
143
152
|
]
|
|
144
153
|
});
|
|
145
|
-
return Harness.open(
|
|
154
|
+
return Harness.open(
|
|
155
|
+
storage,
|
|
156
|
+
{ models, registry: this.registry, settings },
|
|
157
|
+
context
|
|
158
|
+
);
|
|
146
159
|
};
|
|
147
160
|
|
|
148
161
|
readonly lifecycle = Lifecycle.install(this).use(this.browser).use(this.harness);
|
|
@@ -169,24 +182,40 @@ const receipt = await session.submit("Summarize the latest report", {
|
|
|
169
182
|
const result = await session.wait(receipt.operationId);
|
|
170
183
|
```
|
|
171
184
|
|
|
172
|
-
| Call
|
|
173
|
-
|
|
|
174
|
-
| `prompt(input)`
|
|
175
|
-
| `submit(input, { operationId, whenBusy })` |
|
|
176
|
-
| `wait(operationId, signal?)`
|
|
177
|
-
| `steer(input)`
|
|
178
|
-
| `abort(operationId?)`
|
|
179
|
-
| `pending()`
|
|
180
|
-
| `messages()`
|
|
181
|
-
| `events()`
|
|
182
|
-
| `reset(handoff?)`
|
|
183
|
-
| `setModel(model)`
|
|
184
|
-
| `busy()`
|
|
185
|
+
| Call | What it does |
|
|
186
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
187
|
+
| `prompt(input)` | Submits and waits: the answer, and the transcript after it. |
|
|
188
|
+
| `submit(input, { operationId, whenBusy, entry })` | Hands the input to Pi and returns as soon as Pi has saved it, without waiting for the model. If the session is already working, the input waits until that work finishes. With `whenBusy: "steer"`, it joins the running work instead, after the current round of tool calls. `entry` records where the input came from (see below). |
|
|
189
|
+
| `wait(operationId, signal?)` | The operation's result once Pi settles it. Aborting `signal` stops only the wait, not the work. |
|
|
190
|
+
| `steer(input)` | `submit` with `whenBusy: "steer"`. |
|
|
191
|
+
| `abort(operationId?)` | Withdraws a queued operation, or with no id, stops everything running in the session. |
|
|
192
|
+
| `pending()` | Operations Pi has not settled yet: queued, or part of the running work. |
|
|
193
|
+
| `messages()` | Pi's transcript entries since the newest reset. |
|
|
194
|
+
| `events()` | Pi's agent events: a `snapshot`, then one batch per commit. |
|
|
195
|
+
| `reset(handoff?)` | Starts a new context, optionally from a handoff note. |
|
|
196
|
+
| `setModel(model)` | Changes this session's model. |
|
|
197
|
+
| `busy()` | Whether a run is going. |
|
|
185
198
|
|
|
186
199
|
`harness.prompt()`, `submit()`, `wait()`, `abort()`, `messages()` and `pending()` act on the root session, or on `{ session }`. `harness.sessions` has `create()`, `fork(from)`, `get(id)` and `list()`, which includes sessions subagents created. `ROOT_SESSION` is the root's id.
|
|
187
200
|
|
|
188
201
|
`messages()` and `events()` are Pi's own records, not a UI-ready chat transcript. The example below folds them into one.
|
|
189
202
|
|
|
203
|
+
### Label input the user did not type
|
|
204
|
+
|
|
205
|
+
Pi records every submitted input as a `pi.user` entry, as if the user typed it. Your code often submits input of its own, such as a prompt from a scheduled job, a webhook payload or the outcome of an approval. To record where that input came from, pass `entry` with a `kind` of your own and any `data` you want to keep with it:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
await session.submit("The deploy was approved. Run the migration.", {
|
|
209
|
+
entry: { kind: "app.approval", data: { approved: true, by: "ben" } }
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The model sees no difference: it still gets the text as a user message. Only the transcript changes. The input is stored with your `kind` and `data` instead of `pi.user`, and `messages()` and `events()` return both, so your UI can show it differently, for example as "Approved by ben" rather than as something the user said. Pi reserves kinds that start with `pi.`, so use a prefix of your own, such as `app.`.
|
|
214
|
+
|
|
215
|
+
The kind is a label, not a permission. It records what your code said the input was, and nothing checks it. The model reads the input as if the user wrote it, so do not put anything in it that you would not let the user send.
|
|
216
|
+
|
|
217
|
+
The example app folds the transcript into chat messages, ignores `kind` and `data`, and shows every input as a user message. To show labelled input differently, read them from the entry.
|
|
218
|
+
|
|
190
219
|
## How recovery works
|
|
191
220
|
|
|
192
221
|
The harness schedules one lifecycle job per session with work. The job waits on Pi's tasks, refreshes itself as a heartbeat while they run, and completes when they settle. If the object is evicted or crashes, the job is still due, so its alarm restarts the object. Pi reopens its stored state and resumes the session's work: a replay-safe tool call runs again, and an unsafe one is reported to the model as interrupted.
|
package/docs/models-pi-ai.md
CHANGED
|
@@ -43,8 +43,10 @@ design: a model this package cannot name exactly is yours to construct.
|
|
|
43
43
|
created on first use.
|
|
44
44
|
3. **No provider keys.** AI Gateway holds the credential — unified billing, or a key you
|
|
45
45
|
store on the gateway (BYOK) — so nothing vendor-shaped needs a secret in your Worker.
|
|
46
|
-
4. **The binding
|
|
47
|
-
|
|
46
|
+
4. **The binding, or an account id and token.** Workers AI and catalog models go through
|
|
47
|
+
`env.AI.run`, other model objects through the binding's `fetch` to AI Gateway's
|
|
48
|
+
provider-native path. With [an account id and API token](#account-id-and-api-token) the
|
|
49
|
+
same requests go over HTTP instead.
|
|
48
50
|
5. Everything here is in beta and may change in a minor release.
|
|
49
51
|
|
|
50
52
|
## Quick start
|
|
@@ -77,6 +79,26 @@ export default {
|
|
|
77
79
|
};
|
|
78
80
|
```
|
|
79
81
|
|
|
82
|
+
### Account id and API token
|
|
83
|
+
|
|
84
|
+
To use a gateway in another account, or to call from outside a Worker, pass `accountId` and
|
|
85
|
+
`apiToken` instead of `binding`. Everything else is the same, and the gateway still defaults to
|
|
86
|
+
`default`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const ai = createAI({
|
|
90
|
+
accountId: "27b146402af2103944379f33841b6234",
|
|
91
|
+
apiToken: env.CLOUDFLARE_API_TOKEN,
|
|
92
|
+
gateway: "project-gateway"
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Workers AI and catalog models go to AI Gateway's
|
|
97
|
+
[REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/), which runs them on
|
|
98
|
+
`/run` as `env.AI.run` does. Other model objects go to the provider-native path on
|
|
99
|
+
`gateway.ai.cloudflare.com`, with the token as `cf-aig-authorization`. Create the token with
|
|
100
|
+
**Workers AI Read** and **AI Gateway Run**; it is sent to those two hosts only.
|
|
101
|
+
|
|
80
102
|
## Where a model comes from
|
|
81
103
|
|
|
82
104
|
```ts
|
|
@@ -179,7 +201,7 @@ ai("anthropic/claude-opus-4.8", {
|
|
|
179
201
|
eventId: "turn-17",
|
|
180
202
|
requestTimeoutMs: 30_000,
|
|
181
203
|
retries: { maxAttempts: 3, backoff: "exponential", retryDelayMs: 500 },
|
|
182
|
-
|
|
204
|
+
byokAlias: "team-a", // which stored BYOK key, by alias
|
|
183
205
|
fallback: ["@cf/zai-org/glm-4.7-flash"], // ids or models, in any mix
|
|
184
206
|
headers: { "x-request-source": "docs" },
|
|
185
207
|
sessionAffinity: "chat-42", // Workers AI prefix-cache routing
|
|
@@ -211,20 +233,29 @@ then model, then `createAI` settings.
|
|
|
211
233
|
|
|
212
234
|
## How a request travels
|
|
213
235
|
|
|
214
|
-
| Model
|
|
215
|
-
|
|
|
216
|
-
| `@cf/…`
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
236
|
+
| Model | `api` | Through the binding | With an account id and token |
|
|
237
|
+
| --------------------------------------- | -------------------- | --------------------------------------------------------- | ------------------------------------------------------ |
|
|
238
|
+
| `@cf/…`, `dynamic/…` | `cloudflare-ai` | `env.AI.run` + Cloudflare's compat layer | the REST API + Cloudflare's compat layer |
|
|
239
|
+
| `anthropic/…` from pi's gateway catalog | `anthropic-messages` | `env.AI.run` → AI Gateway `/run`, Messages in and out | the REST API's `/ai/v1/messages` → AI Gateway `/run` |
|
|
240
|
+
| `openai/…` from pi's gateway catalog | `openai-responses` | `env.AI.run` → AI Gateway `/run`, Responses in and out | the REST API's `/ai/v1/responses` → AI Gateway `/run` |
|
|
241
|
+
| Any other model object | its own `api` | the binding's `fetch` → AI Gateway's provider-native path | `gateway.ai.cloudflare.com` → the provider-native path |
|
|
242
|
+
|
|
243
|
+
A model from pi's Cloudflare AI Gateway catalog runs on `env.AI.run` under the gateway's
|
|
244
|
+
catalog id (`anthropic/claude-opus-4.8`). pi-ai spells Claude ids as Anthropic does
|
|
245
|
+
(`claude-opus-4-8`), so `agents` lists and resolves them with the catalog's dot instead. The body is pi-ai's own,
|
|
246
|
+
built by pi-ai's converter for the model's `api`, and it is sent in that format: `/run`
|
|
247
|
+
converts nothing, so the answer, streams included, is the shape pi-ai's stream processors
|
|
248
|
+
read. `/run` picks the provider and bills through Unified Billing or a key stored on the
|
|
249
|
+
gateway.
|
|
250
|
+
|
|
251
|
+
Any other model object — one from pi's Anthropic, OpenAI or Groq registry, with the vendor's
|
|
252
|
+
own id — goes to AI Gateway's **provider-native path** through the binding's `fetch`, with the
|
|
253
|
+
provider slug taken from the model's `baseUrl` host. The gateway forwards it to the vendor with
|
|
254
|
+
the credential it holds and streams the vendor's own SSE back untouched.
|
|
255
|
+
|
|
256
|
+
Either way no vendor credential leaves your Worker, and the model's `api`, `provider` and
|
|
257
|
+
`id` are never rewritten, so thinking signatures and encrypted reasoning items replay on the
|
|
258
|
+
next turn.
|
|
228
259
|
|
|
229
260
|
The `api` markers in that table are the only ones this package routes, and the table is the
|
|
230
261
|
whole list: `cloudflare-ai`, `anthropic-messages`, `openai-responses`, `openai-completions`.
|
|
@@ -266,8 +297,10 @@ gateway, an unpaid account) become a `CloudflareAIError`, so fallback and retry
|
|
|
266
297
|
way they treat Workers AI failures. A model whose `api` is not one of the four routed markers
|
|
267
298
|
never gets that far: the stream ends with an `error` event naming the `api`.
|
|
268
299
|
|
|
269
|
-
`
|
|
270
|
-
|
|
300
|
+
`byokAlias` picks a stored key by alias when the gateway holds several for one provider. It is
|
|
301
|
+
sent as `cf-aig-byok-alias` on a catalog model and on any other model object. `zdr` turns Zero
|
|
302
|
+
Data Retention on or off for a Unified Billing request, and applies to model objects outside
|
|
303
|
+
pi's gateway catalog only: `/run` does not read it.
|
|
271
304
|
|
|
272
305
|
## Observability
|
|
273
306
|
|
|
@@ -287,6 +320,15 @@ A failed dispatch ends the stream with an `error` event whose message carries a
|
|
|
287
320
|
`cloudflare-error` diagnostic (`status`, `code`, `logId`), and `onResponse` fires with the raw
|
|
288
321
|
status and headers for every request.
|
|
289
322
|
|
|
323
|
+
## Stream resume
|
|
324
|
+
|
|
325
|
+
Through the binding, a model from pi's AI Gateway catalog streams from `/run`, which names the run in
|
|
326
|
+
`cf-aig-run-id` and keeps its events. If the connection drops, the provider reconnects through
|
|
327
|
+
the binding and replays from the last complete event, so the stream continues with nothing lost
|
|
328
|
+
or repeated. It gives up after five drops or once the gateway no longer holds the run, and an
|
|
329
|
+
abort never resumes. Workers AI streams and models on the provider-native path have no run id
|
|
330
|
+
and are not resumed. The run id is on the `cloudflare` diagnostic as `runId`.
|
|
331
|
+
|
|
290
332
|
## Fallback
|
|
291
333
|
|
|
292
334
|
```ts
|
|
@@ -324,7 +366,7 @@ Every other model in the catalog takes the full set.
|
|
|
324
366
|
|
|
325
367
|
## Beta
|
|
326
368
|
|
|
327
|
-
This subpath needs `@earendil-works/pi-ai
|
|
369
|
+
This subpath needs `@earendil-works/pi-ai@~1.3.0`, an optional peer of `agents`. `agents`
|
|
328
370
|
takes no vendor package as a dependency, optional or otherwise. The surface is in beta
|
|
329
371
|
and may change in a minor release. An unlisted `@cf/` id resolves with default metadata (a
|
|
330
372
|
128k context window, 8192 output tokens, zero cost); pass overrides when you know better.
|