@ably/ai-transport 0.2.0 → 0.3.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 +10 -19
- package/dist/ably-ai-transport.js +1790 -1091
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/core/agent.d.ts +20 -5
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +4 -1
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +2 -7
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -1
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +95 -36
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/decode-fold.d.ts +40 -32
- package/dist/core/transport/headers.d.ts +30 -1
- package/dist/core/transport/index.d.ts +1 -1
- package/dist/core/transport/invocation.d.ts +1 -1
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +21 -16
- package/dist/core/transport/run-manager.d.ts +9 -11
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +165 -15
- package/dist/core/transport/types/agent.d.ts +120 -98
- package/dist/core/transport/types/client.d.ts +45 -12
- package/dist/core/transport/types/tree.d.ts +52 -10
- package/dist/core/transport/types/view.d.ts +55 -28
- package/dist/core/transport/view.d.ts +176 -58
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +10 -4
- package/dist/index.d.ts +6 -5
- package/dist/react/ably-ai-transport-react.js +784 -415
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +2 -1
- package/dist/react/contexts/client-session-provider.d.ts +3 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/use-view.d.ts +3 -3
- package/dist/utils.d.ts +22 -54
- package/dist/vercel/ably-ai-transport-vercel.js +2297 -2026
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +1 -2
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +5 -30
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +20 -102
- package/dist/vercel/codec/tool-transitions.d.ts +0 -6
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +1 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2013 -9500
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -70
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +2 -1
- package/dist/vercel/run-end-reason.d.ts +66 -11
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +0 -2
- package/dist/vercel/transport/index.d.ts +1 -1
- package/dist/vercel/transport/run-output-stream.d.ts +6 -8
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/constants.ts +2 -2
- package/src/core/agent.ts +43 -19
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +145 -21
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +13 -54
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +43 -0
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +99 -36
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +330 -589
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +74 -69
- package/src/core/transport/decode-fold.ts +57 -47
- package/src/core/transport/headers.ts +57 -4
- package/src/core/transport/index.ts +2 -1
- package/src/core/transport/invocation.ts +1 -1
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +63 -61
- package/src/core/transport/pipe-stream.ts +10 -1
- package/src/core/transport/run-manager.ts +25 -31
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +414 -47
- package/src/core/transport/types/agent.ts +129 -102
- package/src/core/transport/types/client.ts +49 -13
- package/src/core/transport/types/tree.ts +61 -12
- package/src/core/transport/types/view.ts +57 -28
- package/src/core/transport/view.ts +520 -172
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +10 -3
- package/src/index.ts +44 -11
- package/src/react/contexts/client-session-context.ts +1 -1
- package/src/react/contexts/client-session-provider.tsx +38 -2
- package/src/react/index.ts +2 -1
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/use-client-session.ts +7 -30
- package/src/react/use-view.ts +3 -3
- package/src/utils.ts +31 -97
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +1 -3
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +23 -63
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +52 -838
- package/src/vercel/codec/tool-transitions.ts +1 -12
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +1 -0
- package/src/vercel/react/contexts/chat-transport-context.ts +1 -1
- package/src/vercel/react/use-chat-transport.ts +8 -28
- package/src/vercel/react/use-message-sync.ts +5 -10
- package/src/vercel/run-end-reason.ts +95 -16
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +10 -22
- package/src/vercel/transport/index.ts +1 -1
- package/src/vercel/transport/run-output-stream.ts +7 -8
- package/src/version.ts +1 -1
- package/dist/core/transport/branch-chain.d.ts +0 -43
- package/dist/core/transport/load-conversation.d.ts +0 -128
- package/dist/vercel/codec/decoder.d.ts +0 -9
- package/dist/vercel/codec/encoder.d.ts +0 -11
- package/src/core/transport/branch-chain.ts +0 -58
- package/src/core/transport/load-conversation.ts +0 -355
- package/src/vercel/codec/decoder.ts +0 -696
- package/src/vercel/codec/encoder.ts +0 -548
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-node event log.
|
|
3
|
+
*
|
|
4
|
+
* Each node retains the decoded events it was folded from, grouped by
|
|
5
|
+
* wire-message serial and ordered ascending by serial. The log captures
|
|
6
|
+
* canonical serial order regardless of delivery order, so a node's event
|
|
7
|
+
* sequence can be re-derived in that order even when wires arrive late
|
|
8
|
+
* (cross-publisher reordering) or out of order (history pages applying older
|
|
9
|
+
* messages after newer ones).
|
|
10
|
+
*
|
|
11
|
+
* Within one serial, deliveries are sequenced by `Message.version.serial`
|
|
12
|
+
* (lexicographically ordered per mutation — platform guarantee): each entry
|
|
13
|
+
* records the highest version decoded into it, so a delivery the entry has
|
|
14
|
+
* already incorporated — a whole-wire replay from a second hydration, a
|
|
15
|
+
* remount, or an agent re-walk — is recognised and dropped at the transport.
|
|
16
|
+
*
|
|
17
|
+
* {@link WireLog} encapsulates the entry list and all of its mutation: the
|
|
18
|
+
* caller hands it a wire and is told only how to fold (see {@link WireLogFold}).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** One wire message in a node's event log: a serial and its decoded events. */
|
|
22
|
+
interface WireLogEntry<TEvent> {
|
|
23
|
+
/** Ably channel serial of the wire message. */
|
|
24
|
+
serial: string;
|
|
25
|
+
/**
|
|
26
|
+
* The wire's codec-message-id — the reducer routing key the events were
|
|
27
|
+
* folded alongside; undefined when the wire carried none.
|
|
28
|
+
*/
|
|
29
|
+
messageId: string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The decoded events from this wire message's deliveries, in arrival order.
|
|
32
|
+
* Same-serial deliveries (the create plus each append/update) extend the
|
|
33
|
+
* entry, so the list accumulates across deliveries.
|
|
34
|
+
*/
|
|
35
|
+
events: TEvent[];
|
|
36
|
+
/**
|
|
37
|
+
* The highest `Message.version.serial` decoded into this entry. Versions
|
|
38
|
+
* are lexicographically comparable within one serial, so a delivery at or
|
|
39
|
+
* below this value is already incorporated and must not fold again. In
|
|
40
|
+
* practice every delivery carries a version (a never-mutated message's
|
|
41
|
+
* version serial equals its serial); the message serial is used as the floor
|
|
42
|
+
* only as a defensive fallback for the type-optional absent case.
|
|
43
|
+
*/
|
|
44
|
+
decodedThrough: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** How a {@link WireLog.record} call tells the caller to fold the wire's events. */
|
|
48
|
+
export type WireLogFold =
|
|
49
|
+
/**
|
|
50
|
+
* The version guard rejected a re-delivery the log already incorporated — a
|
|
51
|
+
* whole-wire replay, or a newer version of a non-streamed wire (an edited
|
|
52
|
+
* discrete). Nothing was recorded; fold nothing.
|
|
53
|
+
*/
|
|
54
|
+
| 'dropped'
|
|
55
|
+
/**
|
|
56
|
+
* The events extend the log tail (in-order delivery) or landed on a swept
|
|
57
|
+
* log; fold them onto the node's existing projection.
|
|
58
|
+
*/
|
|
59
|
+
| 'incremental'
|
|
60
|
+
/**
|
|
61
|
+
* An earlier-serial wire arrived late, so incremental folding would corrupt
|
|
62
|
+
* serial order; rebuild the projection from the whole log via {@link replay}.
|
|
63
|
+
*/
|
|
64
|
+
| 'refold';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A node's event log: one entry per wire-message serial, kept ascending by
|
|
68
|
+
* serial, each accumulating that serial's decoded events in arrival order.
|
|
69
|
+
*/
|
|
70
|
+
export class WireLog<TEvent> {
|
|
71
|
+
private readonly _entries: WireLogEntry<TEvent>[] = [];
|
|
72
|
+
private _swept = false;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether the retention sweep has dropped this log's decoded payloads. A
|
|
76
|
+
* swept log keeps each entry's replay key (serial + `decodedThrough`) so it
|
|
77
|
+
* still recognises whole-wire replays, but it can no longer be refolded.
|
|
78
|
+
* @returns True once {@link sweep} has run.
|
|
79
|
+
*/
|
|
80
|
+
get swept(): boolean {
|
|
81
|
+
return this._swept;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Record a wire message's decoded events and report how to fold them.
|
|
86
|
+
*
|
|
87
|
+
* Events for an already-logged serial are guarded by the entry's
|
|
88
|
+
* `decodedThrough` version before being recorded; a new serial is inserted
|
|
89
|
+
* at the position that keeps the log ascending by serial (Ably serials order
|
|
90
|
+
* lexicographically). The version guard fires only for deliveries carrying
|
|
91
|
+
* an explicit `version.serial`: in-contract mutations always do, while a
|
|
92
|
+
* version-less delivery records unguarded (and never advances
|
|
93
|
+
* `decodedThrough`), matching the decoder's convention.
|
|
94
|
+
*
|
|
95
|
+
* On a swept log the payload is not retained (only the replay key is), so
|
|
96
|
+
* the fold is never `refold` — a genuinely-new wire there is outside the
|
|
97
|
+
* reorder window and folds incrementally in arrival order.
|
|
98
|
+
* @param serial - The Ably channel serial of the wire message.
|
|
99
|
+
* @param messageId - The wire's codec-message-id, or undefined.
|
|
100
|
+
* @param events - The decoded events to record, in arrival order.
|
|
101
|
+
* @param version - The delivery's `Message.version.serial`, or undefined
|
|
102
|
+
* when the delivery carried none (guard disabled for this delivery).
|
|
103
|
+
* @param streamed - Whether the delivery is part of a streamed wire; a
|
|
104
|
+
* guarded newer delivery for a non-streamed wire is an edited discrete and
|
|
105
|
+
* is dropped.
|
|
106
|
+
* @returns How the caller should fold the events.
|
|
107
|
+
*/
|
|
108
|
+
record(
|
|
109
|
+
serial: string,
|
|
110
|
+
messageId: string | undefined,
|
|
111
|
+
events: TEvent[],
|
|
112
|
+
version: string | undefined,
|
|
113
|
+
streamed: boolean,
|
|
114
|
+
): WireLogFold {
|
|
115
|
+
// A swept log retains replay keys but not payloads: record an empty event
|
|
116
|
+
// list so the key advances while nothing is stored. The caller folds the
|
|
117
|
+
// events it already holds.
|
|
118
|
+
const index = this._recordEntry(serial, messageId, this._swept ? [] : events, version, streamed);
|
|
119
|
+
if (index === undefined) return 'dropped';
|
|
120
|
+
if (this._swept) return 'incremental';
|
|
121
|
+
return index === this._entries.length - 1 ? 'incremental' : 'refold';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Replay every recorded event in canonical order — wire messages ascending
|
|
126
|
+
* by serial, events within a wire in arrival order — each with its wire's
|
|
127
|
+
* routing metadata, for a refold.
|
|
128
|
+
* @param visit - Called once per event, in canonical order.
|
|
129
|
+
*/
|
|
130
|
+
replay(visit: (event: TEvent, serial: string, messageId: string | undefined) => void): void {
|
|
131
|
+
for (const entry of this._entries) {
|
|
132
|
+
for (const event of entry.events) visit(event, entry.serial, entry.messageId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Drop the decoded payloads (the unbounded cost) but keep each entry's
|
|
138
|
+
* replay key, so a post-sweep whole-wire replay is still recognised and
|
|
139
|
+
* dropped rather than re-folded. The log becomes {@link swept}; a refold can
|
|
140
|
+
* no longer rebuild the dropped events, which `swept` reflects.
|
|
141
|
+
*/
|
|
142
|
+
sweep(): void {
|
|
143
|
+
this._swept = true;
|
|
144
|
+
for (const entry of this._entries) entry.events.length = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Insert or extend the entry for `serial`, guarding replays by version.
|
|
149
|
+
* @param serial - The Ably channel serial of the wire message.
|
|
150
|
+
* @param messageId - The wire's codec-message-id, or undefined.
|
|
151
|
+
* @param events - The decoded events to store (empty on a swept log).
|
|
152
|
+
* @param version - The delivery's `Message.version.serial`, or undefined.
|
|
153
|
+
* @param streamed - Whether the delivery is part of a streamed wire.
|
|
154
|
+
* @returns The index of the entry the events landed in, or `undefined` when
|
|
155
|
+
* the version guard dropped the delivery.
|
|
156
|
+
*/
|
|
157
|
+
private _recordEntry(
|
|
158
|
+
serial: string,
|
|
159
|
+
messageId: string | undefined,
|
|
160
|
+
events: TEvent[],
|
|
161
|
+
version: string | undefined,
|
|
162
|
+
streamed: boolean,
|
|
163
|
+
): number | undefined {
|
|
164
|
+
// Scan from the tail: live delivery appends at (or extends) the end, so the
|
|
165
|
+
// match is almost always within the last entry or two.
|
|
166
|
+
for (let i = this._entries.length - 1; i >= 0; i--) {
|
|
167
|
+
const entry = this._entries[i];
|
|
168
|
+
if (!entry) break; // unreachable
|
|
169
|
+
if (entry.serial === serial) {
|
|
170
|
+
// Version guard: drop a re-delivery the entry already incorporated — a
|
|
171
|
+
// replay (version at or below the high-water-mark) or an edit to a
|
|
172
|
+
// discrete (a newer version of a non-streamed wire, not propagated).
|
|
173
|
+
if (version !== undefined && (version <= entry.decodedThrough || !streamed)) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
entry.events.push(...events);
|
|
177
|
+
if (version !== undefined) entry.decodedThrough = version;
|
|
178
|
+
return i;
|
|
179
|
+
}
|
|
180
|
+
if (entry.serial < serial) {
|
|
181
|
+
this._entries.splice(i + 1, 0, { serial, messageId, events: [...events], decodedThrough: version ?? serial });
|
|
182
|
+
return i + 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Lower than every logged serial (or the log is empty): insert at the head.
|
|
186
|
+
this._entries.unshift({ serial, messageId, events: [...events], decodedThrough: version ?? serial });
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -74,11 +74,18 @@ export enum ErrorCode {
|
|
|
74
74
|
StreamError = 104008,
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* The agent
|
|
78
|
-
*
|
|
79
|
-
* lapsed without seeing them.
|
|
77
|
+
* The agent waited for the input event(s) the invocation points at —
|
|
78
|
+
* across the bounded history scan and the live subscription — but
|
|
79
|
+
* `inputEventLookupTimeoutMs` lapsed without seeing them.
|
|
80
80
|
*/
|
|
81
81
|
InputEventNotFound = 104010,
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Channel history pagination failed after bounded retry — either the initial
|
|
85
|
+
* `channel.history()` call or a subsequent `page.next()` exhausted its
|
|
86
|
+
* retry budget. The original failure is preserved as `cause`.
|
|
87
|
+
*/
|
|
88
|
+
HistoryFetchFailed = 104011,
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/**
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ export type {
|
|
|
8
8
|
ClientSession,
|
|
9
9
|
ClientSessionOptions,
|
|
10
10
|
ConversationNode,
|
|
11
|
-
EventsNode,
|
|
12
11
|
InputNode,
|
|
13
12
|
InvocationData,
|
|
14
13
|
LoadConversationOptions,
|
|
@@ -16,10 +15,12 @@ export type {
|
|
|
16
15
|
OutputEvent,
|
|
17
16
|
PipeOptions,
|
|
18
17
|
Run,
|
|
18
|
+
RunEndParams,
|
|
19
19
|
RunEndReason,
|
|
20
20
|
RunInfo,
|
|
21
21
|
RunLifecycleEvent,
|
|
22
22
|
RunNode,
|
|
23
|
+
RunNodeState,
|
|
23
24
|
RunRuntime,
|
|
24
25
|
RunView,
|
|
25
26
|
SendOptions,
|
|
@@ -29,25 +30,54 @@ export type {
|
|
|
29
30
|
} from './core/transport/index.js';
|
|
30
31
|
export { buildTransportHeaders, createAgentSession, createClientSession, Invocation } from './core/transport/index.js';
|
|
31
32
|
|
|
33
|
+
// Channel modes
|
|
34
|
+
export { OBJECT_MODES } from './core/channel-options.js';
|
|
35
|
+
|
|
32
36
|
// Core codec
|
|
33
37
|
export type {
|
|
38
|
+
BatchAssembleContext,
|
|
39
|
+
BatchMessageHeaders,
|
|
40
|
+
BatchSpec,
|
|
34
41
|
ChannelWriter,
|
|
35
42
|
Codec,
|
|
43
|
+
CodecEvent,
|
|
36
44
|
CodecInputEvent,
|
|
37
45
|
CodecMessage,
|
|
38
46
|
CodecOutputEvent,
|
|
47
|
+
CodecReducer,
|
|
48
|
+
DataCodec,
|
|
39
49
|
DecodedMessage,
|
|
40
50
|
Decoder,
|
|
41
51
|
DecoderCore,
|
|
42
52
|
DecoderCoreHooks,
|
|
43
53
|
DecoderCoreOptions,
|
|
54
|
+
DefineCodecConfig,
|
|
55
|
+
DefinedCodec,
|
|
44
56
|
Encoder,
|
|
45
57
|
EncoderCore,
|
|
46
58
|
EncoderCoreOptions,
|
|
47
59
|
EncoderOptions,
|
|
60
|
+
EscapeHatchCore,
|
|
48
61
|
Extras,
|
|
62
|
+
FieldFor,
|
|
63
|
+
HeaderBuilder,
|
|
64
|
+
HeaderField,
|
|
65
|
+
InputBuilder,
|
|
66
|
+
InputDescriptor,
|
|
67
|
+
InputEventSpec,
|
|
68
|
+
LifecycleDiscreteContext,
|
|
69
|
+
LifecyclePolicy,
|
|
49
70
|
LifecycleTracker,
|
|
50
71
|
MessagePayload,
|
|
72
|
+
OutputBuilder,
|
|
73
|
+
OutputDecodeContext,
|
|
74
|
+
OutputDescriptor,
|
|
75
|
+
OutputEncodeHatchContext,
|
|
76
|
+
OutputEventSpec,
|
|
77
|
+
OutputStreamEndContext,
|
|
78
|
+
OutputStreamSpec,
|
|
79
|
+
PartBuilder,
|
|
80
|
+
PartSpec,
|
|
51
81
|
PhaseConfig,
|
|
52
82
|
Reducer,
|
|
53
83
|
ReducerMeta,
|
|
@@ -58,9 +88,19 @@ export type {
|
|
|
58
88
|
ToolResult,
|
|
59
89
|
ToolResultError,
|
|
60
90
|
UserMessage,
|
|
91
|
+
WellKnownInputFactories,
|
|
61
92
|
WriteOptions,
|
|
62
93
|
} from './core/codec/index.js';
|
|
63
|
-
export {
|
|
94
|
+
export {
|
|
95
|
+
boolField,
|
|
96
|
+
createDecoderCore,
|
|
97
|
+
createEncoderCore,
|
|
98
|
+
createLifecycleTracker,
|
|
99
|
+
defineCodec,
|
|
100
|
+
enumField,
|
|
101
|
+
jsonField,
|
|
102
|
+
strField,
|
|
103
|
+
} from './core/codec/index.js';
|
|
64
104
|
|
|
65
105
|
// Constants
|
|
66
106
|
export {
|
|
@@ -84,15 +124,8 @@ export {
|
|
|
84
124
|
} from './constants.js';
|
|
85
125
|
|
|
86
126
|
// Utilities
|
|
87
|
-
export type {
|
|
88
|
-
export {
|
|
89
|
-
getCodecHeaders,
|
|
90
|
-
getTransportHeaders,
|
|
91
|
-
headerReader,
|
|
92
|
-
headerWriter,
|
|
93
|
-
mergeHeaders,
|
|
94
|
-
stripUndefined,
|
|
95
|
-
} from './utils.js';
|
|
127
|
+
export type { Stripped } from './utils.js';
|
|
128
|
+
export { getCodecHeaders, getTransportHeaders, mergeHeaders, stripUndefined } from './utils.js';
|
|
96
129
|
|
|
97
130
|
// Event emitter
|
|
98
131
|
export { EventEmitter } from './event-emitter.js';
|
|
@@ -24,7 +24,7 @@ export interface ClientSessionSlot {
|
|
|
24
24
|
* `nearest` is the slot from the innermost enclosing {@link ClientSessionProvider}.
|
|
25
25
|
* `providers` is the full registry of all enclosing providers, keyed by channelName.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
interface ClientSessionContextValue {
|
|
28
28
|
/** The innermost {@link ClientSessionProvider}'s slot. `undefined` when no provider is present. */
|
|
29
29
|
nearest: ClientSessionSlot | undefined;
|
|
30
30
|
/** All registered session slots from enclosing providers, keyed by channelName. */
|
|
@@ -22,12 +22,20 @@
|
|
|
22
22
|
* Multiple ClientSessionProviders can be nested using distinct channelNames.
|
|
23
23
|
* Each provider merges its slot into the parent record so descendants
|
|
24
24
|
* can access all registered sessions via useClientSession(channelName).
|
|
25
|
+
*
|
|
26
|
+
* The provider also wraps its children in ably-js's `<ChannelProvider>` for the
|
|
27
|
+
* session's channel, so descendants can use ably-js channel hooks
|
|
28
|
+
* (`usePresence`, `useChannel`, etc.) against it without adding their own. It
|
|
29
|
+
* seeds the ChannelProvider's `options` with this SDK's channel agent so the
|
|
30
|
+
* hooks' agent is appended rather than overwriting it (ably-js >= 2.22).
|
|
25
31
|
*/
|
|
26
32
|
|
|
27
33
|
import * as Ably from 'ably';
|
|
28
|
-
import { useAbly } from 'ably/react';
|
|
34
|
+
import { ChannelProvider, useAbly } from 'ably/react';
|
|
29
35
|
import { type PropsWithChildren, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react';
|
|
30
36
|
|
|
37
|
+
import { channelAgent } from '../../core/agent.js';
|
|
38
|
+
import { resolveChannelModes } from '../../core/channel-options.js';
|
|
31
39
|
import type { CodecInputEvent, CodecOutputEvent } from '../../core/codec/types.js';
|
|
32
40
|
import { createClientSession } from '../../core/transport/client-session.js';
|
|
33
41
|
import type { ClientSession, ClientSessionOptions } from '../../core/transport/types.js';
|
|
@@ -86,6 +94,9 @@ export interface ClientSessionProviderProps<
|
|
|
86
94
|
* const { session: main } = useClientSession({ channelName: 'ai:main' });
|
|
87
95
|
* const { session: aux } = useClientSession({ channelName: 'ai:aux' });
|
|
88
96
|
* ```
|
|
97
|
+
* `channelModes` must stay constant for the provider's lifetime: the session is
|
|
98
|
+
* only recreated when `channelName` changes, and removing the modes after mount
|
|
99
|
+
* silently reverts the channel's mode set without a reattach.
|
|
89
100
|
* @param props - Provider configuration including `channelName`, `codec`, and all other {@link ClientSessionOptions} except `client`.
|
|
90
101
|
* @param props.children - Descendant components that consume the session via {@link useClientSession}.
|
|
91
102
|
* @returns A React element wrapping children with ClientSessionContext.
|
|
@@ -101,6 +112,22 @@ export const ClientSessionProvider = <
|
|
|
101
112
|
}: ClientSessionProviderProps<TInput, TOutput, TProjection, TMessage>): ReactNode => {
|
|
102
113
|
const client = useAbly();
|
|
103
114
|
const { channelName } = sessionOptions;
|
|
115
|
+
|
|
116
|
+
// Seed the ChannelProvider with this SDK's channel agent so ably-js's React
|
|
117
|
+
// hooks append their agent (`channelOptionsForReactHooks`) rather than
|
|
118
|
+
// overwriting it. Memoised on the codec, which determines the agent string.
|
|
119
|
+
//
|
|
120
|
+
// Spec: AIT-CT23 — resolve the channel modes through the same helper the
|
|
121
|
+
// session uses so the provider and the session request an identical,
|
|
122
|
+
// identically-ordered mode set. ably-js compares modes order- and
|
|
123
|
+
// duplicate-sensitively, so matching arrays mean the provider's setOptions
|
|
124
|
+
// never triggers a reattach and never silently reverts the session's modes.
|
|
125
|
+
const channelOptions = useMemo<Ably.ChannelOptions>(() => {
|
|
126
|
+
const options: Ably.ChannelOptions = { params: { agent: channelAgent(sessionOptions.codec) } };
|
|
127
|
+
const modes = resolveChannelModes(sessionOptions.channelModes);
|
|
128
|
+
if (modes) options.modes = modes;
|
|
129
|
+
return options;
|
|
130
|
+
}, [sessionOptions.codec, sessionOptions.channelModes]);
|
|
104
131
|
const sessionRef = useRef<ClientSession<TInput, TOutput, TProjection, TMessage> | undefined>(undefined);
|
|
105
132
|
const sessionChannelRef = useRef<string>(channelName);
|
|
106
133
|
const sessionsToDisposeRef = useRef<ClientSession<CodecInputEvent, CodecOutputEvent, unknown, unknown>[]>([]);
|
|
@@ -182,5 +209,14 @@ export const ClientSessionProvider = <
|
|
|
182
209
|
};
|
|
183
210
|
}, []);
|
|
184
211
|
|
|
185
|
-
return
|
|
212
|
+
return (
|
|
213
|
+
<ClientSessionContext.Provider value={contextValue}>
|
|
214
|
+
<ChannelProvider
|
|
215
|
+
channelName={channelName}
|
|
216
|
+
options={channelOptions}
|
|
217
|
+
>
|
|
218
|
+
{children}
|
|
219
|
+
</ChannelProvider>
|
|
220
|
+
</ClientSessionContext.Provider>
|
|
221
|
+
);
|
|
186
222
|
};
|
package/src/react/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
export { OBJECT_MODES } from '../core/channel-options.js';
|
|
1
2
|
export type { CodecMessage } from '../core/codec/types.js';
|
|
2
3
|
export type {
|
|
3
4
|
ActiveRun,
|
|
4
5
|
BranchSelection,
|
|
5
6
|
ClientSession,
|
|
6
7
|
ConversationNode,
|
|
7
|
-
EventsNode,
|
|
8
8
|
InputNode,
|
|
9
9
|
MessageNode,
|
|
10
10
|
RunInfo,
|
|
11
11
|
RunNode,
|
|
12
|
+
RunNodeState,
|
|
12
13
|
SendOptions,
|
|
13
14
|
} from '../core/transport/types.js';
|
|
14
15
|
export type { ClientSessionSlot } from './contexts/client-session-context.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stub {@link ClientSession} returned by the context-reader hooks when they
|
|
3
|
+
* are skipped, when no provider is found, or when session construction failed.
|
|
4
|
+
* Every member throws so a held-but-unusable session fails loudly rather than
|
|
5
|
+
* silently no-ops. Generic so each consumer gets a session typed to its own
|
|
6
|
+
* codec parameters without a cast — the throwing bodies satisfy any instantiation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as Ably from 'ably';
|
|
10
|
+
import type * as AblyObjects from 'ably/liveobjects';
|
|
11
|
+
|
|
12
|
+
import type { CodecInputEvent, CodecOutputEvent } from '../../core/codec/types.js';
|
|
13
|
+
import type { ClientSession, Tree, View } from '../../core/transport/types.js';
|
|
14
|
+
import { ErrorCode } from '../../errors.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build the `hook is skipped` error for a stub member.
|
|
18
|
+
* @param operation - The attempted operation, phrased for the `unable to <operation>; hook is skipped` message.
|
|
19
|
+
* @returns The skipped-hook error.
|
|
20
|
+
*/
|
|
21
|
+
const skipped = (operation: string): Ably.ErrorInfo =>
|
|
22
|
+
new Ably.ErrorInfo(`unable to ${operation}; hook is skipped`, ErrorCode.InvalidArgument, 400);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a throwing stub {@link ClientSession}. Held safely in state before a
|
|
26
|
+
* provider resolves; any access throws {@link Ably.ErrorInfo}.
|
|
27
|
+
* @returns A stub session whose every member throws.
|
|
28
|
+
*/
|
|
29
|
+
export const makeSkippedClientSession = <
|
|
30
|
+
TInput extends CodecInputEvent,
|
|
31
|
+
TOutput extends CodecOutputEvent,
|
|
32
|
+
TProjection,
|
|
33
|
+
TMessage,
|
|
34
|
+
>(): ClientSession<TInput, TOutput, TProjection, TMessage> => ({
|
|
35
|
+
get tree(): Tree<TOutput, TProjection> {
|
|
36
|
+
throw skipped('access tree');
|
|
37
|
+
},
|
|
38
|
+
get view(): View<TInput, TMessage> {
|
|
39
|
+
throw skipped('access view');
|
|
40
|
+
},
|
|
41
|
+
get presence(): Ably.RealtimePresence {
|
|
42
|
+
throw skipped('access presence');
|
|
43
|
+
},
|
|
44
|
+
get object(): AblyObjects.RealtimeObject {
|
|
45
|
+
throw skipped('access object');
|
|
46
|
+
},
|
|
47
|
+
connect: () => {
|
|
48
|
+
throw skipped('connect');
|
|
49
|
+
},
|
|
50
|
+
createView: (): View<TInput, TMessage> => {
|
|
51
|
+
throw skipped('create view');
|
|
52
|
+
},
|
|
53
|
+
cancel: () => {
|
|
54
|
+
throw skipped('cancel');
|
|
55
|
+
},
|
|
56
|
+
on: () => {
|
|
57
|
+
throw skipped('subscribe');
|
|
58
|
+
},
|
|
59
|
+
close: () => {
|
|
60
|
+
throw skipped('close');
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -23,33 +23,10 @@ import * as Ably from 'ably';
|
|
|
23
23
|
import { useContext, useEffect, useRef } from 'react';
|
|
24
24
|
|
|
25
25
|
import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
|
|
26
|
-
import type { ClientSession
|
|
26
|
+
import type { ClientSession } from '../core/transport/types.js';
|
|
27
27
|
import { ErrorCode } from '../errors.js';
|
|
28
28
|
import { ClientSessionContext } from './contexts/client-session-context.js';
|
|
29
|
-
|
|
30
|
-
const SKIPPED_SESSION: ClientSession<CodecInputEvent, CodecOutputEvent, unknown, unknown> = {
|
|
31
|
-
get tree(): Tree<CodecOutputEvent, unknown> {
|
|
32
|
-
throw new Ably.ErrorInfo('unable to access tree; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
33
|
-
},
|
|
34
|
-
get view(): View<CodecInputEvent, unknown> {
|
|
35
|
-
throw new Ably.ErrorInfo('unable to access view; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
36
|
-
},
|
|
37
|
-
connect: () => {
|
|
38
|
-
throw new Ably.ErrorInfo('unable to connect; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
39
|
-
},
|
|
40
|
-
createView: (): View<CodecInputEvent, unknown> => {
|
|
41
|
-
throw new Ably.ErrorInfo('unable to create view; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
42
|
-
},
|
|
43
|
-
cancel: () => {
|
|
44
|
-
throw new Ably.ErrorInfo('unable to cancel; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
45
|
-
},
|
|
46
|
-
on: () => {
|
|
47
|
-
throw new Ably.ErrorInfo('unable to subscribe; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
48
|
-
},
|
|
49
|
-
close: () => {
|
|
50
|
-
throw new Ably.ErrorInfo('unable to close; hook is skipped', ErrorCode.InvalidArgument, 400);
|
|
51
|
-
},
|
|
52
|
-
};
|
|
29
|
+
import { makeSkippedClientSession } from './internal/skipped-session.js';
|
|
53
30
|
|
|
54
31
|
/**
|
|
55
32
|
* Return value of {@link useClientSession}.
|
|
@@ -146,7 +123,7 @@ export const useClientSession = <
|
|
|
146
123
|
|
|
147
124
|
if (skip) {
|
|
148
125
|
return {
|
|
149
|
-
session:
|
|
126
|
+
session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
|
|
150
127
|
};
|
|
151
128
|
}
|
|
152
129
|
|
|
@@ -162,12 +139,12 @@ export const useClientSession = <
|
|
|
162
139
|
}
|
|
163
140
|
// Provider exists but construction failed.
|
|
164
141
|
return {
|
|
165
|
-
session:
|
|
142
|
+
session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
|
|
166
143
|
sessionError: slot.sessionError,
|
|
167
144
|
};
|
|
168
145
|
}
|
|
169
146
|
return {
|
|
170
|
-
session:
|
|
147
|
+
session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
|
|
171
148
|
sessionError: new Ably.ErrorInfo(
|
|
172
149
|
`unable to use session; no ClientSessionProvider found for channelName "${channelName}"`,
|
|
173
150
|
ErrorCode.BadRequest,
|
|
@@ -185,13 +162,13 @@ export const useClientSession = <
|
|
|
185
162
|
}
|
|
186
163
|
// Nearest provider exists but construction failed.
|
|
187
164
|
return {
|
|
188
|
-
session:
|
|
165
|
+
session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
|
|
189
166
|
sessionError: nearestSlot.sessionError,
|
|
190
167
|
};
|
|
191
168
|
}
|
|
192
169
|
|
|
193
170
|
return {
|
|
194
|
-
session:
|
|
171
|
+
session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
|
|
195
172
|
sessionError: new Ably.ErrorInfo(
|
|
196
173
|
'unable to use session; no ClientSessionProvider found in the tree',
|
|
197
174
|
ErrorCode.BadRequest,
|
package/src/react/use-view.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface UseViewOptions<
|
|
|
26
26
|
> extends BaseSessionOption<TInput, TOutput, TProjection, TMessage> {
|
|
27
27
|
/** A specific {@link View} to subscribe to directly. Takes priority over `session`. */
|
|
28
28
|
view?: View<TInput, TMessage> | null;
|
|
29
|
-
/**
|
|
29
|
+
/** Number of older codecMessages to reveal per page (exactly `limit`, fewer only at the end of history). When provided, auto-loads the first page on mount. */
|
|
30
30
|
limit?: number;
|
|
31
31
|
/** When `true`, skip all subscriptions and return an empty handle immediately. */
|
|
32
32
|
skip?: boolean;
|
|
@@ -46,7 +46,7 @@ export interface ViewHandle<TInput extends CodecInputEvent, TMessage> {
|
|
|
46
46
|
* identity the domain `message` may carry. See {@link View.getMessages}.
|
|
47
47
|
*/
|
|
48
48
|
messages: CodecMessage<TMessage>[];
|
|
49
|
-
/** Whether there are older
|
|
49
|
+
/** Whether there are older messages that can be revealed via `loadOlder`. */
|
|
50
50
|
hasOlder: boolean;
|
|
51
51
|
/** Whether a page load is currently in progress. */
|
|
52
52
|
loading: boolean;
|
|
@@ -128,7 +128,7 @@ const EMPTY_BRANCH_SELECTION: BranchSelection<never> = {
|
|
|
128
128
|
* @param props - Options for selecting the view source and configuring auto-load.
|
|
129
129
|
* @param props.session - Client session whose default view to subscribe to; defaults to the nearest provider.
|
|
130
130
|
* @param props.view - A specific {@link View} to subscribe to directly. Takes priority over `session`.
|
|
131
|
-
* @param props.limit -
|
|
131
|
+
* @param props.limit - Number of older codecMessages to reveal per page (exactly `limit`, fewer only at end of history); when provided, auto-loads the first page on mount.
|
|
132
132
|
* @param props.skip - When `true`, skip all subscriptions and return an empty handle.
|
|
133
133
|
* @returns A {@link ViewHandle} with messages, pagination state, navigation, write operations, and loadOlder.
|
|
134
134
|
*/
|