@tanstack/ai 0.42.0 → 0.43.1
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 +15 -1
- package/dist/esm/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -21
- package/dist/esm/activities/chat/index.js +2100 -1813
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +24 -6
- package/dist/esm/index.js +30 -98
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.d.ts +3 -1
- package/dist/esm/middlewares/otel.js +599 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +321 -42
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +98 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -61
- package/src/activities/chat/agent-loop-strategies.ts +5 -39
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1091 -200
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -1
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +23 -5
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +405 -45
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { StreamChunk } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A pluggable delivery-durability backend.
|
|
4
|
+
*
|
|
5
|
+
* Offsets are owned by the adapter and opaque to the transport. The generic
|
|
6
|
+
* parameter lets an adapter retain a branded string type across append, read,
|
|
7
|
+
* and resume without requiring core to understand its cursor format.
|
|
8
|
+
*/
|
|
9
|
+
export interface StreamDurability<TOffset extends string = string> {
|
|
10
|
+
/** Return the adapter offset captured from the request, or null for a producer. */
|
|
11
|
+
resumeFrom: () => TOffset | null;
|
|
12
|
+
/**
|
|
13
|
+
* Persist a batch before it is delivered and return exactly one resumable
|
|
14
|
+
* offset for each chunk, in the same order.
|
|
15
|
+
*/
|
|
16
|
+
append: (chunks: Array<StreamChunk>) => Promise<Array<TOffset>>;
|
|
17
|
+
/** Replay chunks strictly after the supplied adapter-owned offset. */
|
|
18
|
+
read: (offset: TOffset, signal?: AbortSignal) => AsyncIterable<{
|
|
19
|
+
offset: TOffset;
|
|
20
|
+
chunk: StreamChunk;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Terminalize the producer log and unblock live readers. Core awaits this
|
|
24
|
+
* for every producer exit, including completion, cancellation, and failure.
|
|
25
|
+
*/
|
|
26
|
+
close: () => Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Everything stored for this run **at the moment of the call**, in append
|
|
29
|
+
* order, then resolve.
|
|
30
|
+
*
|
|
31
|
+
* This is the bounded counterpart to {@link StreamDurability.read}. `read`
|
|
32
|
+
* tails: it parks until the log is terminalized or the caller aborts, so it
|
|
33
|
+
* cannot be used to inspect a log whose producer died without calling
|
|
34
|
+
* `close` — that log stays open forever and a `for await` over it never
|
|
35
|
+
* finishes. `snapshot` exists for exactly that case: a producer resuming a
|
|
36
|
+
* run needs to see the prefix a previous host already stored so it can line
|
|
37
|
+
* its own output up against it, and it needs that read to *return*.
|
|
38
|
+
*
|
|
39
|
+
* Implementations MUST:
|
|
40
|
+
*
|
|
41
|
+
* - never wait for more entries — resolve with what is stored, including
|
|
42
|
+
* while the log is still open and still being appended to;
|
|
43
|
+
* - resolve to an empty array for a run with nothing stored, rather than
|
|
44
|
+
* throwing. In particular an implementation must not reuse the
|
|
45
|
+
* unknown-run failure path a from-start `read` join takes (`read('-1')` on
|
|
46
|
+
* an empty log is allowed to fail; `snapshot()` is not). A backend over a
|
|
47
|
+
* network may of course still reject on a transport, protocol, or
|
|
48
|
+
* authorization failure — that is a failed call, not an empty run;
|
|
49
|
+
* - return a fresh array the caller can keep or mutate without reaching the
|
|
50
|
+
* stored log through it.
|
|
51
|
+
*
|
|
52
|
+
* The result is a point-in-time view and carries no lock: a concurrent
|
|
53
|
+
* `append` may land immediately after the snapshot is taken, so a caller
|
|
54
|
+
* must not treat the last returned offset as the permanent tail.
|
|
55
|
+
*/
|
|
56
|
+
snapshot: () => Promise<Array<{
|
|
57
|
+
offset: TOffset;
|
|
58
|
+
chunk: StreamChunk;
|
|
59
|
+
}>>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A {@link StreamDurability} that can re-persist an already-stored range
|
|
63
|
+
* idempotently.
|
|
64
|
+
*
|
|
65
|
+
* A run driver resuming after a crash re-derives the same offsets from its
|
|
66
|
+
* source position, so replaying an overlapping range must be a no-op rather
|
|
67
|
+
* than producing duplicates. That capability is deliberately a **separate,
|
|
68
|
+
* optional method** instead of an optional parameter on `append`:
|
|
69
|
+
*
|
|
70
|
+
* - Only adapters that actually support it return this type, so a consumer
|
|
71
|
+
* requiring the capability asks for `UpsertableStreamDurability` and a
|
|
72
|
+
* mismatch is a compile error rather than a runtime failure buried in a
|
|
73
|
+
* run log.
|
|
74
|
+
* - Pairing each chunk with its offset structurally makes a length mismatch
|
|
75
|
+
* and an unpaired chunk unrepresentable. A sparse hole is still
|
|
76
|
+
* representable, so implementations must reject one explicitly.
|
|
77
|
+
*
|
|
78
|
+
* Implementations MUST validate the entire batch before mutating any stored
|
|
79
|
+
* state (so a rejected call never partially applies), MUST reject an offset
|
|
80
|
+
* they did not mint themselves (every accepted offset is resumable by
|
|
81
|
+
* definition), MUST reject an offset repeated within one batch, and MUST
|
|
82
|
+
* reject a hole in the entries array.
|
|
83
|
+
*/
|
|
84
|
+
export interface UpsertableStreamDurability<TOffset extends string = string> extends StreamDurability<TOffset> {
|
|
85
|
+
/**
|
|
86
|
+
* Persist a batch at caller-supplied offsets, replacing any entry already
|
|
87
|
+
* stored at the same offset. Returns the offsets in the order supplied.
|
|
88
|
+
*/
|
|
89
|
+
upsert: (entries: Array<{
|
|
90
|
+
chunk: StreamChunk;
|
|
91
|
+
offset: TOffset;
|
|
92
|
+
}>) => Promise<Array<TOffset>>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The run id a request names: `X-Run-Id` header first, then `?runId`.
|
|
96
|
+
*
|
|
97
|
+
* The single implementation of that precedence, shared by the durability
|
|
98
|
+
* adapters below and by the resume response helpers' run driver
|
|
99
|
+
* (`stream-to-response.ts`), so the helper and the adapter can never disagree
|
|
100
|
+
* about which run a request is talking about.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resolveResumeRunId(request: Request): string | null;
|
|
103
|
+
/** Options for the in-process delivery-durability backend. */
|
|
104
|
+
export interface MemoryStreamOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Milliseconds a from-start join waits for the run's first chunk before
|
|
107
|
+
* throwing. Defaults to {@link DEFAULT_FIRST_CHUNK_DEADLINE_MS} (100ms) —
|
|
108
|
+
* raise it if a producer can legitimately start long after a joiner attaches.
|
|
109
|
+
*/
|
|
110
|
+
firstChunkDeadlineMs?: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Explicit construction for {@link memoryStream}, for callers that don't have
|
|
114
|
+
* the incoming `Request` — e.g. a TanStack Start server function implementing
|
|
115
|
+
* a `joinRun` replay for a run id it received as call data:
|
|
116
|
+
*
|
|
117
|
+
* ```ts
|
|
118
|
+
* const durability = memoryStream({ runId })
|
|
119
|
+
* for await (const chunk of replayRunStream(durability)) yield chunk
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export interface MemoryStreamInit {
|
|
123
|
+
/** The run this durability adapter attaches to. */
|
|
124
|
+
runId: string;
|
|
125
|
+
/**
|
|
126
|
+
* Resume offset captured by the consumer (`resumeFrom()` returns it).
|
|
127
|
+
* Defaults to `null` (a producer / from-start reader).
|
|
128
|
+
*/
|
|
129
|
+
offset?: string | null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The zero-infrastructure delivery-durability backend. Its versioned cursor is
|
|
133
|
+
* deliberately private: callers and core only pass the returned string back.
|
|
134
|
+
*
|
|
135
|
+
* Construct from the incoming `Request` (HTTP transports) or from an explicit
|
|
136
|
+
* {@link MemoryStreamInit} (server functions / direct calls that already know
|
|
137
|
+
* the run id).
|
|
138
|
+
*
|
|
139
|
+
* Logs live in a process-global map, so this backend is for development, tests,
|
|
140
|
+
* and single-process deployments only. Completed runs are evicted after a grace
|
|
141
|
+
* window (see {@link COMPLETED_LOG_TTL_MS}); a resume of an evicted or unknown
|
|
142
|
+
* run fails loudly rather than hanging.
|
|
143
|
+
*/
|
|
144
|
+
export declare function memoryStream(source: Request | MemoryStreamInit, options?: MemoryStreamOptions): UpsertableStreamDurability;
|
|
145
|
+
/**
|
|
146
|
+
* Replay a run's delivery-durability log as a bare stream of chunks, for
|
|
147
|
+
* callers that serve a `joinRun` handler without an HTTP `Response` — e.g. a
|
|
148
|
+
* TanStack Start server function returning an async iterable:
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* async function* joinImageRun({ data: runId }: { data: string }) {
|
|
152
|
+
* yield* replayRunStream(memoryStream({ runId }))
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* // Serve it from a server function whose handler is the generator above
|
|
156
|
+
* // (`createServerFn({ method: 'GET' }).inputValidator(...)`).
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* NOTE: the example deliberately declares the generator separately instead of
|
|
160
|
+
* inlining it into the server-fn builder chain. TanStack Start's server-fn
|
|
161
|
+
* Vite plugin decides whether a module needs compiling by regex-matching the
|
|
162
|
+
* SOURCE for a dotted `handler(` call, and JSDoc survives into `dist` — an
|
|
163
|
+
* inlined chain here would make every Start app treat this package as a
|
|
164
|
+
* server-fn module and try to resolve its framework's `@tanstack/*-start`
|
|
165
|
+
* package, failing the build wherever that framework is not the one installed.
|
|
166
|
+
*
|
|
167
|
+
* Reads from `offset` (default `'-1'` — from the start) and tails until the
|
|
168
|
+
* producer closes the log or `signal` aborts, exactly like the HTTP
|
|
169
|
+
* `resumeServerSentEventsResponse` path.
|
|
170
|
+
*/
|
|
171
|
+
export declare function replayRunStream<TOffset extends string>(durability: StreamDurability<TOffset>, offset?: TOffset, signal?: AbortSignal): AsyncGenerator<StreamChunk>;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
//#region src/stream-durability.ts
|
|
2
|
+
var MEMORY_OFFSET_PREFIX = "memory:v1:";
|
|
3
|
+
function encodeMemoryOffset(runId, seq) {
|
|
4
|
+
return `${MEMORY_OFFSET_PREFIX}${encodeURIComponent(runId)}:${seq}`;
|
|
5
|
+
}
|
|
6
|
+
function decodeMemoryOffset(offset) {
|
|
7
|
+
if (!offset.startsWith(MEMORY_OFFSET_PREFIX)) throw new Error(`Invalid memory stream offset: ${offset}`);
|
|
8
|
+
const encoded = offset.slice(10);
|
|
9
|
+
const separator = encoded.lastIndexOf(":");
|
|
10
|
+
if (separator === -1) throw new Error(`Invalid memory stream offset: ${offset}`);
|
|
11
|
+
const runId = decodeURIComponent(encoded.slice(0, separator));
|
|
12
|
+
const seq = Number(encoded.slice(separator + 1));
|
|
13
|
+
if (!Number.isSafeInteger(seq) || seq < 1) throw new Error(`Invalid memory stream offset: ${offset}`);
|
|
14
|
+
return {
|
|
15
|
+
runId,
|
|
16
|
+
seq
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function readResumeOffset(request) {
|
|
20
|
+
const header = request.headers.get("Last-Event-ID");
|
|
21
|
+
if (header) return header;
|
|
22
|
+
try {
|
|
23
|
+
return new URL(request.url).searchParams.get("offset");
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The run id a request names: `X-Run-Id` header first, then `?runId`.
|
|
30
|
+
*
|
|
31
|
+
* The single implementation of that precedence, shared by the durability
|
|
32
|
+
* adapters below and by the resume response helpers' run driver
|
|
33
|
+
* (`stream-to-response.ts`), so the helper and the adapter can never disagree
|
|
34
|
+
* about which run a request is talking about.
|
|
35
|
+
*/
|
|
36
|
+
function resolveResumeRunId(request) {
|
|
37
|
+
const header = request.headers.get("X-Run-Id");
|
|
38
|
+
if (header) return header;
|
|
39
|
+
try {
|
|
40
|
+
return new URL(request.url).searchParams.get("runId");
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function assertValidRunId(runId) {
|
|
46
|
+
if (runId.length === 0 || /[\r\n]/.test(runId)) throw new Error(`Invalid runId (must be non-empty and contain no CR/LF): ${JSON.stringify(runId)}`);
|
|
47
|
+
return runId;
|
|
48
|
+
}
|
|
49
|
+
function resolveMemoryRunId(request, resumeOffset) {
|
|
50
|
+
if (resumeOffset !== null && resumeOffset !== "-1" && resumeOffset !== "now") return assertValidRunId(decodeMemoryOffset(resumeOffset).runId);
|
|
51
|
+
const requestedRunId = resolveResumeRunId(request);
|
|
52
|
+
return requestedRunId === null ? crypto.randomUUID() : assertValidRunId(requestedRunId);
|
|
53
|
+
}
|
|
54
|
+
function memoryThreshold(offset, runId, tail) {
|
|
55
|
+
if (offset === "-1") return -1;
|
|
56
|
+
if (offset === "now") return tail;
|
|
57
|
+
const decoded = decodeMemoryOffset(offset);
|
|
58
|
+
if (decoded.runId !== runId) throw new Error(`Memory stream offset belongs to run ${JSON.stringify(decoded.runId)}, not ${JSON.stringify(runId)}`);
|
|
59
|
+
return decoded.seq;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Bounds for the in-process log store. `memoryStream` is the dev/single-process
|
|
63
|
+
* backend; without eviction its module-global Map would grow without bound on a
|
|
64
|
+
* long-lived server (one retained chunk buffer per run, forever). Completed logs
|
|
65
|
+
* are swept after a grace window — late resumers/joiners still work briefly —
|
|
66
|
+
* and a hard cap drops the oldest completed logs under pressure. Active
|
|
67
|
+
* (incomplete) logs are never evicted, so an in-flight run is never dropped.
|
|
68
|
+
*/
|
|
69
|
+
var MAX_MEMORY_RUNS = 1024;
|
|
70
|
+
var COMPLETED_LOG_TTL_MS = 5 * 6e4;
|
|
71
|
+
/**
|
|
72
|
+
* How long a from-start join (`-1` / `now`) waits for a run's first chunk before
|
|
73
|
+
* failing. Bounds the "joined a run that never produces" case so a consumer
|
|
74
|
+
* gets a surfaced error instead of an indefinitely-open, event-less connection.
|
|
75
|
+
*
|
|
76
|
+
* Defaults short: the common from-start join is a reload rejoining a run whose
|
|
77
|
+
* producer ran in a PRIOR request, so an in-flight run's log already holds
|
|
78
|
+
* chunks (it streams immediately, deadline never applies) and an empty log means
|
|
79
|
+
* the run is gone — failing fast lets the client re-enable input near-instantly
|
|
80
|
+
* instead of hanging. Raise `firstChunkDeadlineMs` for backends where a producer
|
|
81
|
+
* legitimately starts well after a joiner attaches (a queued/deferred job).
|
|
82
|
+
*/
|
|
83
|
+
var DEFAULT_FIRST_CHUNK_DEADLINE_MS = 100;
|
|
84
|
+
var memoryLogs = /* @__PURE__ */ new Map();
|
|
85
|
+
/**
|
|
86
|
+
* Evict completed logs past their grace window, then, if still over the cap,
|
|
87
|
+
* drop the oldest completed logs (the Map preserves insertion order) until back
|
|
88
|
+
* under the cap. Never touches an incomplete (in-flight) log.
|
|
89
|
+
*/
|
|
90
|
+
function sweepMemoryLogs(now) {
|
|
91
|
+
for (const [id, log] of memoryLogs) if (log.complete && log.completedAt !== void 0 && now - log.completedAt > COMPLETED_LOG_TTL_MS) memoryLogs.delete(id);
|
|
92
|
+
if (memoryLogs.size <= MAX_MEMORY_RUNS) return;
|
|
93
|
+
for (const [id, log] of memoryLogs) {
|
|
94
|
+
if (memoryLogs.size <= MAX_MEMORY_RUNS) break;
|
|
95
|
+
if (log.complete) memoryLogs.delete(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function getOrCreateLog(id) {
|
|
99
|
+
let log = memoryLogs.get(id);
|
|
100
|
+
if (!log) {
|
|
101
|
+
sweepMemoryLogs(Date.now());
|
|
102
|
+
log = {
|
|
103
|
+
entries: [],
|
|
104
|
+
complete: false,
|
|
105
|
+
completedAt: void 0,
|
|
106
|
+
waiters: []
|
|
107
|
+
};
|
|
108
|
+
memoryLogs.set(id, log);
|
|
109
|
+
}
|
|
110
|
+
return log;
|
|
111
|
+
}
|
|
112
|
+
function markComplete(log) {
|
|
113
|
+
if (!log.complete) {
|
|
114
|
+
log.complete = true;
|
|
115
|
+
log.completedAt = Date.now();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function wakeWaiters(log) {
|
|
119
|
+
const waiters = log.waiters;
|
|
120
|
+
log.waiters = [];
|
|
121
|
+
for (const wake of waiters) wake();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The zero-infrastructure delivery-durability backend. Its versioned cursor is
|
|
125
|
+
* deliberately private: callers and core only pass the returned string back.
|
|
126
|
+
*
|
|
127
|
+
* Construct from the incoming `Request` (HTTP transports) or from an explicit
|
|
128
|
+
* {@link MemoryStreamInit} (server functions / direct calls that already know
|
|
129
|
+
* the run id).
|
|
130
|
+
*
|
|
131
|
+
* Logs live in a process-global map, so this backend is for development, tests,
|
|
132
|
+
* and single-process deployments only. Completed runs are evicted after a grace
|
|
133
|
+
* window (see {@link COMPLETED_LOG_TTL_MS}); a resume of an evicted or unknown
|
|
134
|
+
* run fails loudly rather than hanging.
|
|
135
|
+
*/
|
|
136
|
+
function memoryStream(source, options = {}) {
|
|
137
|
+
const resumeOffset = source instanceof Request ? readResumeOffset(source) : source.offset ?? null;
|
|
138
|
+
const runId = source instanceof Request ? resolveMemoryRunId(source, resumeOffset) : assertValidRunId(source.runId);
|
|
139
|
+
const firstChunkDeadlineMs = options.firstChunkDeadlineMs ?? DEFAULT_FIRST_CHUNK_DEADLINE_MS;
|
|
140
|
+
return {
|
|
141
|
+
resumeFrom: () => resumeOffset,
|
|
142
|
+
append: async (chunks) => {
|
|
143
|
+
const log = getOrCreateLog(runId);
|
|
144
|
+
const firstSeq = (log.entries.at(-1)?.seq ?? 0) + 1;
|
|
145
|
+
const offsets = chunks.map((chunk, index) => {
|
|
146
|
+
const seq = firstSeq + index;
|
|
147
|
+
const offset = encodeMemoryOffset(runId, seq);
|
|
148
|
+
log.entries.push({
|
|
149
|
+
seq,
|
|
150
|
+
offset,
|
|
151
|
+
chunk
|
|
152
|
+
});
|
|
153
|
+
return offset;
|
|
154
|
+
});
|
|
155
|
+
wakeWaiters(log);
|
|
156
|
+
return offsets;
|
|
157
|
+
},
|
|
158
|
+
upsert: async (entries) => {
|
|
159
|
+
const log = getOrCreateLog(runId);
|
|
160
|
+
const tailSeq = log.entries.at(-1)?.seq ?? 0;
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
let plannedTailSeq = tailSeq;
|
|
163
|
+
const plan = Array.from(entries, (entry, index) => {
|
|
164
|
+
if (entry === void 0) throw new Error(`memoryStream: entries[${index}] is missing; entries must be dense`);
|
|
165
|
+
const { chunk, offset } = entry;
|
|
166
|
+
let decoded;
|
|
167
|
+
try {
|
|
168
|
+
decoded = decodeMemoryOffset(offset);
|
|
169
|
+
} catch (cause) {
|
|
170
|
+
throw new Error(`memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is not a resumable memory stream offset: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
171
|
+
}
|
|
172
|
+
if (decoded.runId !== runId) throw new Error(`memoryStream: entries[${index}].offset ${JSON.stringify(offset)} belongs to run ${JSON.stringify(decoded.runId)}, not ${JSON.stringify(runId)}`);
|
|
173
|
+
const seq = decoded.seq;
|
|
174
|
+
if (seen.has(offset)) throw new Error(`memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is repeated within the batch; each offset may appear at most once`);
|
|
175
|
+
seen.add(offset);
|
|
176
|
+
const existing = log.entries.find((stored) => stored.offset === offset);
|
|
177
|
+
if (existing) return {
|
|
178
|
+
kind: "replace",
|
|
179
|
+
existing,
|
|
180
|
+
chunk
|
|
181
|
+
};
|
|
182
|
+
if (seq <= plannedTailSeq) throw new Error(`memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is not stored yet but claims position ${seq}, at or before the tail ${plannedTailSeq}; a new offset must come after every stored and preceding entry`);
|
|
183
|
+
plannedTailSeq = seq;
|
|
184
|
+
return {
|
|
185
|
+
kind: "push",
|
|
186
|
+
seq,
|
|
187
|
+
offset,
|
|
188
|
+
chunk
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
for (const step of plan) if (step.kind === "replace") step.existing.chunk = step.chunk;
|
|
192
|
+
else log.entries.push({
|
|
193
|
+
seq: step.seq,
|
|
194
|
+
offset: step.offset,
|
|
195
|
+
chunk: step.chunk
|
|
196
|
+
});
|
|
197
|
+
wakeWaiters(log);
|
|
198
|
+
return plan.map((step) => step.kind === "replace" ? step.existing.offset : step.offset);
|
|
199
|
+
},
|
|
200
|
+
snapshot: () => {
|
|
201
|
+
const log = memoryLogs.get(runId);
|
|
202
|
+
if (log === void 0) return Promise.resolve([]);
|
|
203
|
+
return Promise.resolve(log.entries.map((entry) => ({
|
|
204
|
+
offset: entry.offset,
|
|
205
|
+
chunk: entry.chunk
|
|
206
|
+
})));
|
|
207
|
+
},
|
|
208
|
+
close: () => {
|
|
209
|
+
const log = getOrCreateLog(runId);
|
|
210
|
+
markComplete(log);
|
|
211
|
+
wakeWaiters(log);
|
|
212
|
+
return Promise.resolve();
|
|
213
|
+
},
|
|
214
|
+
read: async function* (offset, signal) {
|
|
215
|
+
const isFromStartJoin = offset === "-1" || offset === "now";
|
|
216
|
+
let log = memoryLogs.get(runId);
|
|
217
|
+
if (log === void 0 || log.entries.length === 0 && !log.complete) {
|
|
218
|
+
if (!isFromStartJoin) throw new Error(`Unknown or expired memory stream run: ${JSON.stringify(runId)}`);
|
|
219
|
+
log = getOrCreateLog(runId);
|
|
220
|
+
}
|
|
221
|
+
const threshold = memoryThreshold(offset, runId, log.entries.at(-1)?.seq ?? 0);
|
|
222
|
+
let index = 0;
|
|
223
|
+
for (;;) {
|
|
224
|
+
while (index < log.entries.length) {
|
|
225
|
+
const entry = log.entries[index];
|
|
226
|
+
index += 1;
|
|
227
|
+
if (entry && entry.seq > threshold) yield {
|
|
228
|
+
offset: entry.offset,
|
|
229
|
+
chunk: entry.chunk
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (log.complete || signal?.aborted) return;
|
|
233
|
+
const deadlineForFirstChunk = log.entries.length === 0 ? firstChunkDeadlineMs : void 0;
|
|
234
|
+
await new Promise((resolve, reject) => {
|
|
235
|
+
let timer;
|
|
236
|
+
const cleanup = () => {
|
|
237
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
238
|
+
signal?.removeEventListener("abort", onAbort);
|
|
239
|
+
const waiterIndex = log.waiters.indexOf(wake);
|
|
240
|
+
if (waiterIndex !== -1) log.waiters.splice(waiterIndex, 1);
|
|
241
|
+
};
|
|
242
|
+
const onAbort = () => {
|
|
243
|
+
cleanup();
|
|
244
|
+
resolve();
|
|
245
|
+
};
|
|
246
|
+
const wake = () => {
|
|
247
|
+
cleanup();
|
|
248
|
+
resolve();
|
|
249
|
+
};
|
|
250
|
+
log.waiters.push(wake);
|
|
251
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
252
|
+
if (deadlineForFirstChunk !== void 0) timer = setTimeout(() => {
|
|
253
|
+
cleanup();
|
|
254
|
+
if (log.entries.length === 0 && !log.complete && memoryLogs.get(runId) === log) memoryLogs.delete(runId);
|
|
255
|
+
reject(/* @__PURE__ */ new Error(`Memory stream run produced no data within ${deadlineForFirstChunk}ms: ${JSON.stringify(runId)}`));
|
|
256
|
+
}, deadlineForFirstChunk);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Replay a run's delivery-durability log as a bare stream of chunks, for
|
|
264
|
+
* callers that serve a `joinRun` handler without an HTTP `Response` — e.g. a
|
|
265
|
+
* TanStack Start server function returning an async iterable:
|
|
266
|
+
*
|
|
267
|
+
* ```ts
|
|
268
|
+
* async function* joinImageRun({ data: runId }: { data: string }) {
|
|
269
|
+
* yield* replayRunStream(memoryStream({ runId }))
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* // Serve it from a server function whose handler is the generator above
|
|
273
|
+
* // (`createServerFn({ method: 'GET' }).inputValidator(...)`).
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* NOTE: the example deliberately declares the generator separately instead of
|
|
277
|
+
* inlining it into the server-fn builder chain. TanStack Start's server-fn
|
|
278
|
+
* Vite plugin decides whether a module needs compiling by regex-matching the
|
|
279
|
+
* SOURCE for a dotted `handler(` call, and JSDoc survives into `dist` — an
|
|
280
|
+
* inlined chain here would make every Start app treat this package as a
|
|
281
|
+
* server-fn module and try to resolve its framework's `@tanstack/*-start`
|
|
282
|
+
* package, failing the build wherever that framework is not the one installed.
|
|
283
|
+
*
|
|
284
|
+
* Reads from `offset` (default `'-1'` — from the start) and tails until the
|
|
285
|
+
* producer closes the log or `signal` aborts, exactly like the HTTP
|
|
286
|
+
* `resumeServerSentEventsResponse` path.
|
|
287
|
+
*/
|
|
288
|
+
async function* replayRunStream(durability, offset, signal) {
|
|
289
|
+
const from = offset ?? "-1";
|
|
290
|
+
for await (const { chunk } of durability.read(from, signal)) yield chunk;
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
293
|
+
export { memoryStream, replayRunStream, resolveResumeRunId };
|
|
294
|
+
|
|
295
|
+
//# sourceMappingURL=stream-durability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-durability.js","names":[],"sources":["../../src/stream-durability.ts"],"sourcesContent":["import type { StreamChunk } from './types'\n\n/**\n * A pluggable delivery-durability backend.\n *\n * Offsets are owned by the adapter and opaque to the transport. The generic\n * parameter lets an adapter retain a branded string type across append, read,\n * and resume without requiring core to understand its cursor format.\n */\nexport interface StreamDurability<TOffset extends string = string> {\n /** Return the adapter offset captured from the request, or null for a producer. */\n resumeFrom: () => TOffset | null\n /**\n * Persist a batch before it is delivered and return exactly one resumable\n * offset for each chunk, in the same order.\n */\n append: (chunks: Array<StreamChunk>) => Promise<Array<TOffset>>\n /** Replay chunks strictly after the supplied adapter-owned offset. */\n read: (\n offset: TOffset,\n signal?: AbortSignal,\n ) => AsyncIterable<{ offset: TOffset; chunk: StreamChunk }>\n /**\n * Terminalize the producer log and unblock live readers. Core awaits this\n * for every producer exit, including completion, cancellation, and failure.\n */\n close: () => Promise<void>\n /**\n * Everything stored for this run **at the moment of the call**, in append\n * order, then resolve.\n *\n * This is the bounded counterpart to {@link StreamDurability.read}. `read`\n * tails: it parks until the log is terminalized or the caller aborts, so it\n * cannot be used to inspect a log whose producer died without calling\n * `close` — that log stays open forever and a `for await` over it never\n * finishes. `snapshot` exists for exactly that case: a producer resuming a\n * run needs to see the prefix a previous host already stored so it can line\n * its own output up against it, and it needs that read to *return*.\n *\n * Implementations MUST:\n *\n * - never wait for more entries — resolve with what is stored, including\n * while the log is still open and still being appended to;\n * - resolve to an empty array for a run with nothing stored, rather than\n * throwing. In particular an implementation must not reuse the\n * unknown-run failure path a from-start `read` join takes (`read('-1')` on\n * an empty log is allowed to fail; `snapshot()` is not). A backend over a\n * network may of course still reject on a transport, protocol, or\n * authorization failure — that is a failed call, not an empty run;\n * - return a fresh array the caller can keep or mutate without reaching the\n * stored log through it.\n *\n * The result is a point-in-time view and carries no lock: a concurrent\n * `append` may land immediately after the snapshot is taken, so a caller\n * must not treat the last returned offset as the permanent tail.\n */\n snapshot: () => Promise<Array<{ offset: TOffset; chunk: StreamChunk }>>\n}\n\n/**\n * A {@link StreamDurability} that can re-persist an already-stored range\n * idempotently.\n *\n * A run driver resuming after a crash re-derives the same offsets from its\n * source position, so replaying an overlapping range must be a no-op rather\n * than producing duplicates. That capability is deliberately a **separate,\n * optional method** instead of an optional parameter on `append`:\n *\n * - Only adapters that actually support it return this type, so a consumer\n * requiring the capability asks for `UpsertableStreamDurability` and a\n * mismatch is a compile error rather than a runtime failure buried in a\n * run log.\n * - Pairing each chunk with its offset structurally makes a length mismatch\n * and an unpaired chunk unrepresentable. A sparse hole is still\n * representable, so implementations must reject one explicitly.\n *\n * Implementations MUST validate the entire batch before mutating any stored\n * state (so a rejected call never partially applies), MUST reject an offset\n * they did not mint themselves (every accepted offset is resumable by\n * definition), MUST reject an offset repeated within one batch, and MUST\n * reject a hole in the entries array.\n */\nexport interface UpsertableStreamDurability<\n TOffset extends string = string,\n> extends StreamDurability<TOffset> {\n /**\n * Persist a batch at caller-supplied offsets, replacing any entry already\n * stored at the same offset. Returns the offsets in the order supplied.\n */\n upsert: (\n entries: Array<{ chunk: StreamChunk; offset: TOffset }>,\n ) => Promise<Array<TOffset>>\n}\n\nconst MEMORY_OFFSET_PREFIX = 'memory:v1:'\n\ninterface MemoryOffset {\n runId: string\n seq: number\n}\n\nfunction encodeMemoryOffset(runId: string, seq: number): string {\n return `${MEMORY_OFFSET_PREFIX}${encodeURIComponent(runId)}:${seq}`\n}\n\nfunction decodeMemoryOffset(offset: string): MemoryOffset {\n if (!offset.startsWith(MEMORY_OFFSET_PREFIX)) {\n throw new Error(`Invalid memory stream offset: ${offset}`)\n }\n const encoded = offset.slice(MEMORY_OFFSET_PREFIX.length)\n const separator = encoded.lastIndexOf(':')\n if (separator === -1) {\n throw new Error(`Invalid memory stream offset: ${offset}`)\n }\n const runId = decodeURIComponent(encoded.slice(0, separator))\n const seq = Number(encoded.slice(separator + 1))\n if (!Number.isSafeInteger(seq) || seq < 1) {\n throw new Error(`Invalid memory stream offset: ${offset}`)\n }\n return { runId, seq }\n}\n\nfunction readResumeOffset(request: Request): string | null {\n const header = request.headers.get('Last-Event-ID')\n if (header) return header\n try {\n return new URL(request.url).searchParams.get('offset')\n } catch {\n return null\n }\n}\n\n/**\n * The run id a request names: `X-Run-Id` header first, then `?runId`.\n *\n * The single implementation of that precedence, shared by the durability\n * adapters below and by the resume response helpers' run driver\n * (`stream-to-response.ts`), so the helper and the adapter can never disagree\n * about which run a request is talking about.\n */\nexport function resolveResumeRunId(request: Request): string | null {\n // A POST producer carries its client-chosen run id in the X-Run-Id header so\n // the request URL stays byte-identical to a plain, non-durable request; the\n // GET join path carries it in the ?runId query instead. Prefer the header,\n // fall back to the query.\n const header = request.headers.get('X-Run-Id')\n if (header) return header\n try {\n return new URL(request.url).searchParams.get('runId')\n } catch {\n return null\n }\n}\n\nfunction assertValidRunId(runId: string): string {\n if (runId.length === 0 || /[\\r\\n]/.test(runId)) {\n throw new Error(\n `Invalid runId (must be non-empty and contain no CR/LF): ${JSON.stringify(runId)}`,\n )\n }\n return runId\n}\n\nfunction resolveMemoryRunId(\n request: Request,\n resumeOffset: string | null,\n): string {\n if (\n resumeOffset !== null &&\n resumeOffset !== '-1' &&\n resumeOffset !== 'now'\n ) {\n return assertValidRunId(decodeMemoryOffset(resumeOffset).runId)\n }\n const requestedRunId = resolveResumeRunId(request)\n return requestedRunId === null\n ? crypto.randomUUID()\n : assertValidRunId(requestedRunId)\n}\n\nfunction memoryThreshold(offset: string, runId: string, tail: number): number {\n if (offset === '-1') return -1\n if (offset === 'now') return tail\n const decoded = decodeMemoryOffset(offset)\n if (decoded.runId !== runId) {\n throw new Error(\n `Memory stream offset belongs to run ${JSON.stringify(decoded.runId)}, not ${JSON.stringify(runId)}`,\n )\n }\n return decoded.seq\n}\n\ninterface MemoryEntry {\n seq: number\n offset: string\n chunk: StreamChunk\n}\n\n/**\n * One validated action from an `upsert` batch. Building the whole plan before\n * applying any of it is what keeps a rejected `upsert` from partially mutating\n * the log.\n */\ntype UpsertStep =\n | { kind: 'replace'; existing: MemoryEntry; chunk: StreamChunk }\n | { kind: 'push'; seq: number; offset: string; chunk: StreamChunk }\n\ninterface MemoryLog {\n entries: Array<MemoryEntry>\n complete: boolean\n /** Epoch ms when the log was terminalized; undefined while still producing. */\n completedAt: number | undefined\n waiters: Array<() => void>\n}\n\n/**\n * Bounds for the in-process log store. `memoryStream` is the dev/single-process\n * backend; without eviction its module-global Map would grow without bound on a\n * long-lived server (one retained chunk buffer per run, forever). Completed logs\n * are swept after a grace window — late resumers/joiners still work briefly —\n * and a hard cap drops the oldest completed logs under pressure. Active\n * (incomplete) logs are never evicted, so an in-flight run is never dropped.\n */\nconst MAX_MEMORY_RUNS = 1024\nconst COMPLETED_LOG_TTL_MS = 5 * 60_000\n\n/**\n * How long a from-start join (`-1` / `now`) waits for a run's first chunk before\n * failing. Bounds the \"joined a run that never produces\" case so a consumer\n * gets a surfaced error instead of an indefinitely-open, event-less connection.\n *\n * Defaults short: the common from-start join is a reload rejoining a run whose\n * producer ran in a PRIOR request, so an in-flight run's log already holds\n * chunks (it streams immediately, deadline never applies) and an empty log means\n * the run is gone — failing fast lets the client re-enable input near-instantly\n * instead of hanging. Raise `firstChunkDeadlineMs` for backends where a producer\n * legitimately starts well after a joiner attaches (a queued/deferred job).\n */\nconst DEFAULT_FIRST_CHUNK_DEADLINE_MS = 100\n\n/** Options for the in-process delivery-durability backend. */\nexport interface MemoryStreamOptions {\n /**\n * Milliseconds a from-start join waits for the run's first chunk before\n * throwing. Defaults to {@link DEFAULT_FIRST_CHUNK_DEADLINE_MS} (100ms) —\n * raise it if a producer can legitimately start long after a joiner attaches.\n */\n firstChunkDeadlineMs?: number\n}\n\nconst memoryLogs = new Map<string, MemoryLog>()\n\n/**\n * Evict completed logs past their grace window, then, if still over the cap,\n * drop the oldest completed logs (the Map preserves insertion order) until back\n * under the cap. Never touches an incomplete (in-flight) log.\n */\nfunction sweepMemoryLogs(now: number): void {\n for (const [id, log] of memoryLogs) {\n if (\n log.complete &&\n log.completedAt !== undefined &&\n now - log.completedAt > COMPLETED_LOG_TTL_MS\n ) {\n memoryLogs.delete(id)\n }\n }\n if (memoryLogs.size <= MAX_MEMORY_RUNS) return\n for (const [id, log] of memoryLogs) {\n if (memoryLogs.size <= MAX_MEMORY_RUNS) break\n if (log.complete) memoryLogs.delete(id)\n }\n}\n\nfunction getOrCreateLog(id: string): MemoryLog {\n let log = memoryLogs.get(id)\n if (!log) {\n sweepMemoryLogs(Date.now())\n log = { entries: [], complete: false, completedAt: undefined, waiters: [] }\n memoryLogs.set(id, log)\n }\n return log\n}\n\nfunction markComplete(log: MemoryLog): void {\n if (!log.complete) {\n log.complete = true\n log.completedAt = Date.now()\n }\n}\n\nfunction wakeWaiters(log: MemoryLog): void {\n const waiters = log.waiters\n log.waiters = []\n for (const wake of waiters) wake()\n}\n\n/**\n * Explicit construction for {@link memoryStream}, for callers that don't have\n * the incoming `Request` — e.g. a TanStack Start server function implementing\n * a `joinRun` replay for a run id it received as call data:\n *\n * ```ts\n * const durability = memoryStream({ runId })\n * for await (const chunk of replayRunStream(durability)) yield chunk\n * ```\n */\nexport interface MemoryStreamInit {\n /** The run this durability adapter attaches to. */\n runId: string\n /**\n * Resume offset captured by the consumer (`resumeFrom()` returns it).\n * Defaults to `null` (a producer / from-start reader).\n */\n offset?: string | null\n}\n\n/**\n * The zero-infrastructure delivery-durability backend. Its versioned cursor is\n * deliberately private: callers and core only pass the returned string back.\n *\n * Construct from the incoming `Request` (HTTP transports) or from an explicit\n * {@link MemoryStreamInit} (server functions / direct calls that already know\n * the run id).\n *\n * Logs live in a process-global map, so this backend is for development, tests,\n * and single-process deployments only. Completed runs are evicted after a grace\n * window (see {@link COMPLETED_LOG_TTL_MS}); a resume of an evicted or unknown\n * run fails loudly rather than hanging.\n */\nexport function memoryStream(\n source: Request | MemoryStreamInit,\n options: MemoryStreamOptions = {},\n): UpsertableStreamDurability {\n const resumeOffset =\n source instanceof Request\n ? readResumeOffset(source)\n : (source.offset ?? null)\n const runId =\n source instanceof Request\n ? resolveMemoryRunId(source, resumeOffset)\n : assertValidRunId(source.runId)\n const firstChunkDeadlineMs =\n options.firstChunkDeadlineMs ?? DEFAULT_FIRST_CHUNK_DEADLINE_MS\n\n return {\n resumeFrom: () => resumeOffset,\n // `async` so every failure surfaces as a rejected promise rather than a\n // synchronous throw at the call site — `append` is declared to return a\n // Promise, so callers must be able to `.catch()` every failure mode.\n append: async (chunks) => {\n const log = getOrCreateLog(runId)\n const firstSeq = (log.entries.at(-1)?.seq ?? 0) + 1\n const offsets = chunks.map((chunk, index) => {\n const seq = firstSeq + index\n const offset = encodeMemoryOffset(runId, seq)\n log.entries.push({ seq, offset, chunk })\n return offset\n })\n wakeWaiters(log)\n return offsets\n },\n // `async` for the same reason as `append`: every validation failure below\n // must be observable via `.catch()`, never as a synchronous throw.\n upsert: async (entries) => {\n const log = getOrCreateLog(runId)\n const tailSeq = log.entries.at(-1)?.seq ?? 0\n\n // Validate the WHOLE batch before touching `log.entries`, so a rejected\n // upsert never partially applies and a caller that catches and retries\n // can be sure no prefix landed.\n const seen = new Set<string>()\n // Tail as it will stand once every push planned so far has been applied,\n // so intra-batch ordering is validated up front too.\n let plannedTailSeq = tailSeq\n // `Array.from` rather than `entries.map`: `map` SKIPS holes in a sparse\n // array, which would leave the plan short and make the apply loop below\n // read `undefined` partway through, after earlier steps had already\n // mutated the log. `Array.from` invokes this callback for every index,\n // so a hole is rejected here, before anything is touched.\n const plan = Array.from(entries, (entry, index): UpsertStep => {\n if (entry === undefined) {\n throw new Error(\n `memoryStream: entries[${index}] is missing; entries must be dense`,\n )\n }\n const { chunk, offset } = entry\n let decoded: MemoryOffset\n try {\n decoded = decodeMemoryOffset(offset)\n } catch (cause) {\n throw new Error(\n `memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is not a resumable memory stream offset: ${cause instanceof Error ? cause.message : String(cause)}`,\n )\n }\n if (decoded.runId !== runId) {\n throw new Error(\n `memoryStream: entries[${index}].offset ${JSON.stringify(offset)} belongs to run ${JSON.stringify(decoded.runId)}, not ${JSON.stringify(runId)}`,\n )\n }\n const seq = decoded.seq\n if (seen.has(offset)) {\n throw new Error(\n `memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is repeated within the batch; each offset may appear at most once`,\n )\n }\n seen.add(offset)\n const existing = log.entries.find((stored) => stored.offset === offset)\n if (existing) return { kind: 'replace', existing, chunk }\n // A not-yet-stored offset must sit strictly after the current tail.\n // `read()` walks `entries` in array order and filters `seq > threshold`,\n // so a pushed entry has to keep the seqs monotonically increasing;\n // reusing the offset's own decoded seq also keeps a returned offset's\n // threshold exactly consistent with the entry it names. Gaps are fine —\n // nothing depends on seqs being contiguous, only on them increasing —\n // so do NOT \"fix\" this to renumber densely.\n if (seq <= plannedTailSeq) {\n throw new Error(\n `memoryStream: entries[${index}].offset ${JSON.stringify(offset)} is not stored yet but claims position ${seq}, at or before the tail ${plannedTailSeq}; a new offset must come after every stored and preceding entry`,\n )\n }\n plannedTailSeq = seq\n return { kind: 'push', seq, offset, chunk }\n })\n\n // Validation passed for every entry — mutation below cannot fail.\n for (const step of plan) {\n if (step.kind === 'replace') {\n step.existing.chunk = step.chunk\n } else {\n log.entries.push({\n seq: step.seq,\n offset: step.offset,\n chunk: step.chunk,\n })\n }\n }\n wakeWaiters(log)\n return plan.map((step) =>\n step.kind === 'replace' ? step.existing.offset : step.offset,\n )\n },\n snapshot: () => {\n // Peek, never getOrCreateLog: an unknown run must resolve to `[]`, and\n // inserting an empty, never-completed log here would leave a permanent\n // entry the sweep cannot reclaim (it only reclaims complete logs).\n const log = memoryLogs.get(runId)\n if (log === undefined) return Promise.resolve([])\n // Fresh outer array AND fresh pair objects, so a caller that mutates the\n // result cannot reach `log.entries` or the stored entries through it.\n // Never touches `log.waiters` — a snapshot is a point-in-time read and\n // returns even while the log is open and still being appended to.\n return Promise.resolve(\n log.entries.map((entry) => ({\n offset: entry.offset,\n chunk: entry.chunk,\n })),\n )\n },\n close: () => {\n const log = getOrCreateLog(runId)\n markComplete(log)\n wakeWaiters(log)\n return Promise.resolve()\n },\n read: async function* (offset, signal) {\n const isFromStartJoin = offset === '-1' || offset === 'now'\n\n // Peek, never getOrCreateLog. A concrete resume offset for an absent run\n // means the run was evicted (or never lived in this process) and will not\n // reappear — fail WITHOUT inserting a log. Inserting here would leave a\n // permanent empty, never-completed log (sweep only reclaims complete\n // ones), so client-supplied offsets could grow the map without bound and\n // defeat the eviction this backend relies on.\n let log = memoryLogs.get(runId)\n if (log === undefined || (log.entries.length === 0 && !log.complete)) {\n if (!isFromStartJoin) {\n throw new Error(\n `Unknown or expired memory stream run: ${JSON.stringify(runId)}`,\n )\n }\n // A from-start join may legitimately attach before the producer creates\n // the log (second-tab race); create it so a later append reuses the\n // same entry. If no producer ever arrives, the first-chunk deadline\n // below deletes this phantom before rejecting.\n log = getOrCreateLog(runId)\n }\n\n const threshold = memoryThreshold(\n offset,\n runId,\n log.entries.at(-1)?.seq ?? 0,\n )\n let index = 0\n\n for (;;) {\n while (index < log.entries.length) {\n const entry = log.entries[index]\n index += 1\n if (entry && entry.seq > threshold) {\n yield { offset: entry.offset, chunk: entry.chunk }\n }\n }\n // A terminal chunk (RUN_FINISHED / RUN_ERROR) does NOT end the read: an\n // agent-loop run emits one per iteration (finishReason \"tool_calls\" then\n // \"stop\"), so stopping on the first would truncate a tool-calling run at\n // its first tool call. The producer signals true completion by calling\n // `close()` (it does so on every exit — see StreamDurability.close), which\n // sets `log.complete`. Read tails until then, or until the caller aborts.\n if (log.complete || signal?.aborted) return\n\n // Bound only the wait for the very first chunk: once a run has produced\n // anything, its producer owns termination and a caught-up reader may\n // legitimately park indefinitely between chunks.\n const deadlineForFirstChunk =\n log.entries.length === 0 ? firstChunkDeadlineMs : undefined\n\n await new Promise<void>((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const cleanup = () => {\n if (timer !== undefined) clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n const waiterIndex = log.waiters.indexOf(wake)\n if (waiterIndex !== -1) log.waiters.splice(waiterIndex, 1)\n }\n const onAbort = () => {\n cleanup()\n resolve()\n }\n const wake = () => {\n cleanup()\n resolve()\n }\n log.waiters.push(wake)\n signal?.addEventListener('abort', onAbort, { once: true })\n if (deadlineForFirstChunk !== undefined) {\n timer = setTimeout(() => {\n cleanup()\n // No producer ever created data for this joined run. Drop the\n // phantom log we created above so it does not linger uncollected\n // (it is empty and will never be marked complete).\n if (\n log.entries.length === 0 &&\n !log.complete &&\n memoryLogs.get(runId) === log\n ) {\n memoryLogs.delete(runId)\n }\n reject(\n new Error(\n `Memory stream run produced no data within ${deadlineForFirstChunk}ms: ${JSON.stringify(runId)}`,\n ),\n )\n }, deadlineForFirstChunk)\n }\n })\n }\n },\n }\n}\n\n/**\n * Replay a run's delivery-durability log as a bare stream of chunks, for\n * callers that serve a `joinRun` handler without an HTTP `Response` — e.g. a\n * TanStack Start server function returning an async iterable:\n *\n * ```ts\n * async function* joinImageRun({ data: runId }: { data: string }) {\n * yield* replayRunStream(memoryStream({ runId }))\n * }\n *\n * // Serve it from a server function whose handler is the generator above\n * // (`createServerFn({ method: 'GET' }).inputValidator(...)`).\n * ```\n *\n * NOTE: the example deliberately declares the generator separately instead of\n * inlining it into the server-fn builder chain. TanStack Start's server-fn\n * Vite plugin decides whether a module needs compiling by regex-matching the\n * SOURCE for a dotted `handler(` call, and JSDoc survives into `dist` — an\n * inlined chain here would make every Start app treat this package as a\n * server-fn module and try to resolve its framework's `@tanstack/*-start`\n * package, failing the build wherever that framework is not the one installed.\n *\n * Reads from `offset` (default `'-1'` — from the start) and tails until the\n * producer closes the log or `signal` aborts, exactly like the HTTP\n * `resumeServerSentEventsResponse` path.\n */\nexport async function* replayRunStream<TOffset extends string>(\n durability: StreamDurability<TOffset>,\n offset?: TOffset,\n signal?: AbortSignal,\n): AsyncGenerator<StreamChunk> {\n // '-1' is the from-start replay sentinel every shipped backend honors.\n const from = offset ?? ('-1' as TOffset)\n for await (const { chunk } of durability.read(from, signal)) {\n yield chunk\n }\n}\n"],"mappings":"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"}
|