agents 0.21.0 → 0.23.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 +21 -1
- package/dist/{agent-tool-types-CzGGB-20.d.ts → agent-routing-DE5zmCQ8.d.ts} +1844 -1336
- package/dist/agent-routing.d.ts +14 -0
- package/dist/agent-routing.js +187 -0
- package/dist/agent-routing.js.map +1 -0
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-zR2d5uij.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/agent-tools.js +2 -1
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.js +6 -2
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js +42 -0
- package/dist/capability-BjSKYpzg.js.map +1 -0
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2328 -2015
- package/dist/chat/index.js +891 -521
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +14 -1
- package/dist/chat/react.js +82 -52
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.js +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
- package/dist/client-jagG8a9_.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
- package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/current-agent-Da_C9a3b.d.ts +266 -0
- package/dist/current-agent-DhoDkSnH.js +51 -0
- package/dist/current-agent-DhoDkSnH.js.map +1 -0
- package/dist/diagnostics-BzvaX2UT.js +45 -0
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
- package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
- package/dist/email-send.d.ts +15 -0
- package/dist/email-send.js +32 -0
- package/dist/email-send.js.map +1 -0
- package/dist/email.d.ts +14 -10
- package/dist/email.js.map +1 -1
- package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
- package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
- package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-BRnybD6X.d.ts → index-YSKgfgg9.d.ts} +21 -31
- package/dist/index.d.ts +113 -101
- package/dist/index.js +11 -7234
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context-BlxFEWfn.d.ts +19 -0
- package/dist/internal_context.d.ts +10 -4
- package/dist/internal_context.js +1 -10
- package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
- package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
- package/dist/invoker-CHMnoxIA.js.map +1 -0
- package/dist/lifecycle/index.d.ts +66 -0
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
- package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
- package/dist/mcp/client/index.d.ts +42 -0
- package/dist/mcp/{client.js → client/index.js} +1 -1
- package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
- package/dist/mcp/{x402.js → client/x402.js} +2 -2
- package/dist/mcp/client/x402.js.map +1 -0
- package/dist/mcp/index.d.ts +36 -36
- package/dist/mcp/index.js +14 -16
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
- package/dist/mcp/{server.js → server/index.js} +1 -1
- package/dist/observability/ai/index.js +50 -35
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.js +3 -50
- package/dist/observability/index.js.map +1 -1
- package/dist/{protocol-Dqc2MQxo.js → protocol-B0nh6KNf.js} +19 -21
- package/dist/protocol-B0nh6KNf.js.map +1 -0
- package/dist/react.d.ts +4 -4
- package/dist/react.js +1 -1
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/schedule.d.ts +25 -94
- package/dist/schedule.js +1 -98
- package/dist/schedule.js.map +1 -1
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/scheduler-Dwh85ZGl.d.ts +223 -0
- package/dist/schedules/index.d.ts +22 -0
- package/dist/schedules/index.js +2 -0
- package/dist/schedules/parser.d.ts +79 -0
- package/dist/schedules/parser.js +103 -0
- package/dist/schedules/parser.js.map +1 -0
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sql-error-CPY-GXyI.d.ts +12 -0
- package/dist/sql-error.d.ts +2 -0
- package/dist/sql-error.js +16 -0
- package/dist/sql-error.js.map +1 -0
- package/dist/src-DlSHshb2.js +6963 -0
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +22 -22
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/dist/{ws-chat-transport-CIoOBbO7.js → ws-chat-transport-rWwta645.js} +152 -15
- package/dist/ws-chat-transport-rWwta645.js.map +1 -0
- package/docs/agent-class.md +29 -87
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +19 -25
- package/docs/context.md +131 -0
- package/docs/durable-execution.md +1 -1
- package/docs/http-websockets.md +1 -11
- package/docs/human-in-the-loop.md +1 -1
- package/docs/index.md +16 -12
- package/docs/lifecycle.md +370 -0
- package/docs/long-running-agents.md +4 -6
- package/docs/mcp-client.md +55 -0
- package/docs/mcp-servers.md +5 -1
- package/docs/observability.md +11 -11
- package/docs/resumable-streaming.md +2 -2
- package/docs/routing.md +105 -0
- package/docs/scheduling.md +175 -15
- package/docs/server-driven-messages.md +1 -1
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +185 -125
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +144 -33
- package/dist/cli/index.js +0 -26
- package/dist/cli/index.js.map +0 -1
- package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
- package/dist/client-zqKcsyFa.js.map +0 -1
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/experimental/memory/session/index.d.ts +0 -670
- package/dist/experimental/memory/session/index.js +0 -2374
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/handler-stateless-CIkKPETH.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
- package/dist/internal_context.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -42
- package/dist/mcp/do-oauth-client-provider.js.map +0 -1
- package/dist/mcp/x402.js.map +0 -1
- package/dist/protocol-Dqc2MQxo.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
- package/dist/ws-chat-transport-CIoOBbO7.js.map +0 -1
- /package/dist/{cli/index.d.ts → index-BVVgDSdq.d.ts} +0 -0
package/docs/streams.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Streams
|
|
2
|
+
|
|
3
|
+
> **Experimental.** Everything exported from `agents/streams` may change
|
|
4
|
+
> between releases while the durable output surface stabilizes.
|
|
5
|
+
|
|
6
|
+
`agents/streams` adds durable incremental output to a [Lifecycle
|
|
7
|
+
Object](./lifecycle.md): an ordered, durable chunk log per stream with a
|
|
8
|
+
monotonic cursor, replay-then-tail reads, and terminal status. A consumer
|
|
9
|
+
that reconnects replays from its cursor; a producer that dies mid-stream
|
|
10
|
+
leaves exactly the chunks it durably appended, ready for a replayed
|
|
11
|
+
producer to resume from. The capability needs no alarm, so it also works
|
|
12
|
+
on facets.
|
|
13
|
+
|
|
14
|
+
## Install and use
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { DurableObject } from "cloudflare:workers";
|
|
18
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
19
|
+
import { Streams } from "agents/streams";
|
|
20
|
+
|
|
21
|
+
export class ReportObject extends DurableObject<Env> {
|
|
22
|
+
readonly streams = new Streams();
|
|
23
|
+
readonly lifecycle = Lifecycle.install(this).use(this.streams);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
On an `Agent`, install it onto the composition root in the constructor —
|
|
28
|
+
the pattern for adding any extra capability to an Agent:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export class ReportAgent extends Agent<Env> {
|
|
32
|
+
readonly streams = new Streams();
|
|
33
|
+
|
|
34
|
+
constructor(ctx: AgentContext, env: Env) {
|
|
35
|
+
super(ctx, env);
|
|
36
|
+
this.lifecycle.use(this.streams);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Producing
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const stream = await this.streams.open("reply:123", { metadata });
|
|
45
|
+
stream.append(chunk); // synchronous durable write; wakes live readers
|
|
46
|
+
stream.close(); // or stream.error(reason)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Chunks are JSON values (1 MiB default ceiling, configurable via
|
|
50
|
+
`maxChunkBytes`); each append assigns the next monotonic sequence number —
|
|
51
|
+
the stream's **cursor**. `open()` is idempotent on the id: reopening a live
|
|
52
|
+
stream returns a writer at its cursor, reopening a settled stream throws
|
|
53
|
+
`StreamClosedError`, and settling twice is a no-op so recovery callers stay
|
|
54
|
+
idempotent.
|
|
55
|
+
|
|
56
|
+
## Consuming
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
for await (const chunk of this.streams.read("reply:123", { from, signal })) {
|
|
60
|
+
// replays persisted chunks from `from`, then tails live appends,
|
|
61
|
+
// ends when the stream settles
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const status = await this.streams.status("reply:123");
|
|
65
|
+
// { state: "streaming" | "completed" | "errored", cursor, ... } | null
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Reads are independent of producer liveness. `list()` filters by state and
|
|
69
|
+
by `tag`, and `delete()` removes a settled stream and its chunk log (a live
|
|
70
|
+
stream must be settled first).
|
|
71
|
+
|
|
72
|
+
**Tags** are the lookup side of the id: `open(id, { tag })` stamps a stream
|
|
73
|
+
with an indexed application key — a request id, a session — that is
|
|
74
|
+
deliberately _not_ unique. An operation that produces successive streams (a
|
|
75
|
+
retried turn, a regenerated reply) tags each one, and
|
|
76
|
+
`list({ tag, limit: 1 })` finds the latest (results are newest-first). The
|
|
77
|
+
tag is fixed at creation; reopening a live stream with a different tag
|
|
78
|
+
throws. Use the id alone until one operation can own more than one stream —
|
|
79
|
+
that's the moment tags exist for.
|
|
80
|
+
|
|
81
|
+
`readBatches` also accepts `onUpToDate`, invoked once when the reader first
|
|
82
|
+
reaches the durable tail. Caught-up is distinct from ended: a live stream is
|
|
83
|
+
up to date while tailing — use it to flush replayed UI or flip on a "live"
|
|
84
|
+
indicator.
|
|
85
|
+
|
|
86
|
+
When the consumer pays per write — an SSE flush, an RPC hop, a history
|
|
87
|
+
append — read in batches instead of chunk by chunk:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
for await (const batch of this.streams.readBatches("reply:123", {
|
|
91
|
+
from,
|
|
92
|
+
batchSize: 50 // per-array ceiling during replay; default 100
|
|
93
|
+
})) {
|
|
94
|
+
flush(batch); // StreamChunk[] — one write per backlog, not per chunk
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`readBatches()` has the same lifecycle as `read()` (replay, then tail, end
|
|
99
|
+
on settlement); the difference is granularity: replay yields up to
|
|
100
|
+
`batchSize` chunks per array, and a live tail yields everything that
|
|
101
|
+
accumulated since the last wakeup as one array.
|
|
102
|
+
|
|
103
|
+
## Composing with Tasks
|
|
104
|
+
|
|
105
|
+
The contract [Tasks](./tasks.md) replay was designed around: a task step
|
|
106
|
+
appends to a stream it does not own, and because the producer starts its
|
|
107
|
+
loop at the stream's own durable cursor, a replay after interruption is a
|
|
108
|
+
resume — the stream is the recovery evidence.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
readonly tasks = new Tasks({
|
|
112
|
+
definitions: {
|
|
113
|
+
"generate@v1": async (input: GenerateInput, step: TaskStep) => {
|
|
114
|
+
return step.do("stream", async () => {
|
|
115
|
+
const stream = await this.streams.open(input.streamId);
|
|
116
|
+
// Resuming producers start from the stream's own cursor, so a
|
|
117
|
+
// replay after interruption never duplicates a chunk.
|
|
118
|
+
for (let i = stream.cursor; i < input.total; i++) {
|
|
119
|
+
stream.append(await this.produce(i));
|
|
120
|
+
}
|
|
121
|
+
stream.close();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Neither capability imports the other. The composition survives a real
|
|
129
|
+
process kill: the chunks appended before death are exactly what `status()`
|
|
130
|
+
reports afterward (proven by the SIGKILL e2e suite).
|
|
131
|
+
|
|
132
|
+
## Serving
|
|
133
|
+
|
|
134
|
+
For SSE, one call serves the whole lifecycle:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { sseResponse } from "agents/streams";
|
|
138
|
+
|
|
139
|
+
async onRequest(request: Request) {
|
|
140
|
+
return sseResponse(this.streams, "reply:123", { request });
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Each chunk's sequence number rides the SSE `id:` field, so resume is native
|
|
145
|
+
to the protocol: a reconnecting `EventSource` sends `Last-Event-ID`
|
|
146
|
+
automatically and the helper continues from the next chunk — cursor
|
|
147
|
+
persistence with zero client code (`?from=` works too). The response
|
|
148
|
+
replays, emits an `up-to-date` control event at the tail, tails live
|
|
149
|
+
appends (with periodic heartbeat comments to survive idle proxies), and
|
|
150
|
+
finishes with `done` or `error` (carrying the recorded reason). The
|
|
151
|
+
request's signal aborts the tail when the client disconnects.
|
|
152
|
+
`examples/next/streams` is the end-to-end demo. For other transports,
|
|
153
|
+
`read()`/`readBatches()` remain the raw async iterables to pipe yourself.
|
|
154
|
+
|
|
155
|
+
## Storage: blocks, and the cutover to a message
|
|
156
|
+
|
|
157
|
+
Chunks are stored as **rollover blocks**: one row per stream holds chunks
|
|
158
|
+
until it reaches 256 KB, then the next append opens a new row. An append
|
|
159
|
+
is one billed row either way (an UPDATE that grows the block, or the INSERT
|
|
160
|
+
of the next one), the same as a row-per-chunk log, but a stream of
|
|
161
|
+
thousands of chunks is a handful of rows, so deleting it is a handful of
|
|
162
|
+
writes instead of thousands. Replay parses one block at a time.
|
|
163
|
+
|
|
164
|
+
A stream is temporary: once its content has become something else (a
|
|
165
|
+
session message, a report), its rows are dead weight. The **cutover** ends
|
|
166
|
+
the stream, runs your own synchronous writes, and deletes its rows in one
|
|
167
|
+
SQLite transaction:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
stream.close({
|
|
171
|
+
commit: () => sessionSync.upsert(message), // synchronous writes only
|
|
172
|
+
discard: true // delete the stream's rows in the same transaction
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Either the message exists and the stream is gone, or `commit` threw, the
|
|
177
|
+
settle rolled back and the stream is still live. Nothing is left for a
|
|
178
|
+
retention sweep. `error(reason, { commit, discard })` is the same for a
|
|
179
|
+
failed producer. `commit` must not await; a Session handle's
|
|
180
|
+
`__DO_NOT_USE_WILL_BREAK__sync().upsert()` is the matching synchronous
|
|
181
|
+
message write, and returns a `notify()` to dispatch the change feed after
|
|
182
|
+
the transaction commits.
|
|
183
|
+
|
|
184
|
+
Measured on a real Durable Object (400-chunk chat turn, 10 chunks per
|
|
185
|
+
write): the old log paid 42 rows to write and another 42 to sweep; blocks
|
|
186
|
+
pay 42 to write and 3 to cut over.
|
|
187
|
+
|
|
188
|
+
## Chat runs on this
|
|
189
|
+
|
|
190
|
+
`AIChatAgent` and `Think` store their in-flight turn output here:
|
|
191
|
+
`ResumableStream` (from `agents/chat`) is a thin adapter over Streams that
|
|
192
|
+
packs ~10 wire chunks into one stored segment for write economy, and ends
|
|
193
|
+
every turn with the cutover: the assistant message, the stream's
|
|
194
|
+
settlement and the deletion of its rows commit in one transaction. Nothing
|
|
195
|
+
is swept on an alarm any more. A stream a crash left behind is either
|
|
196
|
+
still `streaming` (recovery rebuilds the message from it) or reclaimed by
|
|
197
|
+
the next stream start, together with in-flight rows abandoned for over an
|
|
198
|
+
hour. Existing `cf_ai_chat_stream_*` tables migrate onto the capability
|
|
199
|
+
automatically. The packing pattern is worth copying for any
|
|
200
|
+
high-frequency producer: buffer what you already hold synchronously, append
|
|
201
|
+
one packed chunk, and unpack on read — durability is unchanged (nothing is
|
|
202
|
+
held across an await at settlement) and rows written drop by ~an order of
|
|
203
|
+
magnitude versus per-token appends.
|
|
204
|
+
|
|
205
|
+
## Current limits
|
|
206
|
+
|
|
207
|
+
Live fanout is in-isolate (sufficient: a Durable Object executes in one
|
|
208
|
+
isolate at a time; reconnecting readers replay from their cursor). Retention
|
|
209
|
+
is explicit: `delete()`, or the cutover's `discard`; age-based sweeping in
|
|
210
|
+
the capability itself, producer-generation fencing on `open()`,
|
|
211
|
+
and transport helpers extracted from chat's resume protocol are future work.
|
|
212
|
+
The design record is
|
|
213
|
+
[`design/rfc-streams.md`](https://github.com/cloudflare/agents/blob/main/design/rfc-streams.md).
|