@ably/ai-transport 0.0.1 → 0.2.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.
Files changed (167) hide show
  1. package/README.md +114 -116
  2. package/dist/ably-ai-transport.js +1743 -961
  3. package/dist/ably-ai-transport.js.map +1 -1
  4. package/dist/ably-ai-transport.umd.cjs +1 -1
  5. package/dist/ably-ai-transport.umd.cjs.map +1 -1
  6. package/dist/constants.d.ts +117 -39
  7. package/dist/core/agent.d.ts +29 -0
  8. package/dist/core/codec/decoder.d.ts +20 -23
  9. package/dist/core/codec/encoder.d.ts +11 -8
  10. package/dist/core/codec/index.d.ts +1 -2
  11. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  12. package/dist/core/codec/types.d.ts +410 -101
  13. package/dist/core/transport/agent-session.d.ts +10 -0
  14. package/dist/core/transport/branch-chain.d.ts +43 -0
  15. package/dist/core/transport/client-session.d.ts +13 -0
  16. package/dist/core/transport/decode-fold.d.ts +47 -0
  17. package/dist/core/transport/headers.d.ts +97 -17
  18. package/dist/core/transport/index.d.ts +5 -3
  19. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  20. package/dist/core/transport/invocation.d.ts +74 -0
  21. package/dist/core/transport/load-conversation.d.ts +128 -0
  22. package/dist/core/transport/load-history.d.ts +39 -0
  23. package/dist/core/transport/pipe-stream.d.ts +9 -8
  24. package/dist/core/transport/run-manager.d.ts +78 -0
  25. package/dist/core/transport/tree.d.ts +435 -0
  26. package/dist/core/transport/types/agent.d.ts +353 -0
  27. package/dist/core/transport/types/client.d.ts +168 -0
  28. package/dist/core/transport/types/shared.d.ts +24 -0
  29. package/dist/core/transport/types/tree.d.ts +315 -0
  30. package/dist/core/transport/types/view.d.ts +222 -0
  31. package/dist/core/transport/types.d.ts +13 -402
  32. package/dist/core/transport/view.d.ts +354 -0
  33. package/dist/errors.d.ts +37 -9
  34. package/dist/index.d.ts +6 -6
  35. package/dist/logger.d.ts +12 -0
  36. package/dist/react/ably-ai-transport-react.js +1164 -645
  37. package/dist/react/ably-ai-transport-react.js.map +1 -1
  38. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  39. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  40. package/dist/react/contexts/client-session-context.d.ts +36 -0
  41. package/dist/react/contexts/client-session-provider.d.ts +53 -0
  42. package/dist/react/create-session-hooks.d.ts +116 -0
  43. package/dist/react/index.d.ts +16 -10
  44. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  45. package/dist/react/use-ably-messages.d.ts +20 -11
  46. package/dist/react/use-client-session.d.ts +81 -0
  47. package/dist/react/use-create-view.d.ts +23 -0
  48. package/dist/react/use-tree.d.ts +35 -0
  49. package/dist/react/use-view.d.ts +110 -0
  50. package/dist/utils.d.ts +32 -23
  51. package/dist/vercel/ably-ai-transport-vercel.js +2748 -1625
  52. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  53. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  54. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  55. package/dist/vercel/codec/decoder.d.ts +5 -18
  56. package/dist/vercel/codec/encoder.d.ts +6 -36
  57. package/dist/vercel/codec/events.d.ts +51 -0
  58. package/dist/vercel/codec/index.d.ts +24 -12
  59. package/dist/vercel/codec/reducer.d.ts +144 -0
  60. package/dist/vercel/codec/tool-transitions.d.ts +50 -0
  61. package/dist/vercel/index.d.ts +4 -2
  62. package/dist/vercel/react/ably-ai-transport-vercel-react.js +10298 -1410
  63. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  64. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +70 -1
  65. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  66. package/dist/vercel/react/contexts/chat-transport-context.d.ts +33 -0
  67. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +96 -0
  68. package/dist/vercel/react/index.d.ts +4 -0
  69. package/dist/vercel/react/use-chat-transport.d.ts +66 -21
  70. package/dist/vercel/react/use-message-sync.d.ts +31 -12
  71. package/dist/vercel/run-end-reason.d.ts +29 -0
  72. package/dist/vercel/transport/chat-transport.d.ts +71 -30
  73. package/dist/vercel/transport/index.d.ts +25 -18
  74. package/dist/vercel/transport/run-output-stream.d.ts +56 -0
  75. package/dist/version.d.ts +2 -0
  76. package/package.json +47 -34
  77. package/src/constants.ts +126 -47
  78. package/src/core/agent.ts +68 -0
  79. package/src/core/codec/decoder.ts +71 -98
  80. package/src/core/codec/encoder.ts +115 -58
  81. package/src/core/codec/index.ts +13 -6
  82. package/src/core/codec/lifecycle-tracker.ts +10 -9
  83. package/src/core/codec/types.ts +438 -106
  84. package/src/core/transport/agent-session.ts +1344 -0
  85. package/src/core/transport/branch-chain.ts +58 -0
  86. package/src/core/transport/client-session.ts +775 -0
  87. package/src/core/transport/decode-fold.ts +91 -0
  88. package/src/core/transport/headers.ts +182 -19
  89. package/src/core/transport/index.ts +29 -22
  90. package/src/core/transport/internal/bounded-map.ts +27 -0
  91. package/src/core/transport/invocation.ts +98 -0
  92. package/src/core/transport/load-conversation.ts +355 -0
  93. package/src/core/transport/load-history.ts +269 -0
  94. package/src/core/transport/pipe-stream.ts +58 -40
  95. package/src/core/transport/run-manager.ts +249 -0
  96. package/src/core/transport/tree.ts +1167 -0
  97. package/src/core/transport/types/agent.ts +407 -0
  98. package/src/core/transport/types/client.ts +211 -0
  99. package/src/core/transport/types/shared.ts +27 -0
  100. package/src/core/transport/types/tree.ts +344 -0
  101. package/src/core/transport/types/view.ts +259 -0
  102. package/src/core/transport/types.ts +13 -527
  103. package/src/core/transport/view.ts +1271 -0
  104. package/src/errors.ts +42 -9
  105. package/src/event-emitter.ts +3 -2
  106. package/src/index.ts +55 -39
  107. package/src/logger.ts +14 -1
  108. package/src/react/contexts/client-session-context.ts +41 -0
  109. package/src/react/contexts/client-session-provider.tsx +186 -0
  110. package/src/react/create-session-hooks.ts +141 -0
  111. package/src/react/index.ts +27 -10
  112. package/src/react/internal/use-resolved-session.ts +63 -0
  113. package/src/react/use-ably-messages.ts +47 -19
  114. package/src/react/use-client-session.ts +201 -0
  115. package/src/react/use-create-view.ts +72 -0
  116. package/src/react/use-tree.ts +84 -0
  117. package/src/react/use-view.ts +275 -0
  118. package/src/react/vite.config.ts +4 -1
  119. package/src/utils.ts +63 -45
  120. package/src/vercel/codec/decoder.ts +336 -255
  121. package/src/vercel/codec/encoder.ts +348 -196
  122. package/src/vercel/codec/events.ts +87 -0
  123. package/src/vercel/codec/index.ts +59 -14
  124. package/src/vercel/codec/reducer.ts +977 -0
  125. package/src/vercel/codec/tool-transitions.ts +122 -0
  126. package/src/vercel/index.ts +7 -3
  127. package/src/vercel/react/contexts/chat-transport-context.ts +41 -0
  128. package/src/vercel/react/contexts/chat-transport-provider.tsx +150 -0
  129. package/src/vercel/react/index.ts +13 -1
  130. package/src/vercel/react/use-chat-transport.ts +162 -42
  131. package/src/vercel/react/use-message-sync.ts +121 -22
  132. package/src/vercel/react/vite.config.ts +4 -2
  133. package/src/vercel/run-end-reason.ts +78 -0
  134. package/src/vercel/transport/chat-transport.ts +553 -113
  135. package/src/vercel/transport/index.ts +40 -28
  136. package/src/vercel/transport/run-output-stream.ts +170 -0
  137. package/src/version.ts +2 -0
  138. package/dist/core/transport/client-transport.d.ts +0 -10
  139. package/dist/core/transport/conversation-tree.d.ts +0 -9
  140. package/dist/core/transport/decode-history.d.ts +0 -41
  141. package/dist/core/transport/server-transport.d.ts +0 -7
  142. package/dist/core/transport/stream-router.d.ts +0 -19
  143. package/dist/core/transport/turn-manager.d.ts +0 -34
  144. package/dist/react/use-active-turns.d.ts +0 -8
  145. package/dist/react/use-client-transport.d.ts +0 -7
  146. package/dist/react/use-conversation-tree.d.ts +0 -20
  147. package/dist/react/use-edit.d.ts +0 -7
  148. package/dist/react/use-history.d.ts +0 -19
  149. package/dist/react/use-messages.d.ts +0 -7
  150. package/dist/react/use-regenerate.d.ts +0 -7
  151. package/dist/react/use-send.d.ts +0 -7
  152. package/dist/vercel/codec/accumulator.d.ts +0 -21
  153. package/src/core/transport/client-transport.ts +0 -959
  154. package/src/core/transport/conversation-tree.ts +0 -434
  155. package/src/core/transport/decode-history.ts +0 -337
  156. package/src/core/transport/server-transport.ts +0 -458
  157. package/src/core/transport/stream-router.ts +0 -118
  158. package/src/core/transport/turn-manager.ts +0 -147
  159. package/src/react/use-active-turns.ts +0 -61
  160. package/src/react/use-client-transport.ts +0 -37
  161. package/src/react/use-conversation-tree.ts +0 -71
  162. package/src/react/use-edit.ts +0 -24
  163. package/src/react/use-history.ts +0 -111
  164. package/src/react/use-messages.ts +0 -32
  165. package/src/react/use-regenerate.ts +0 -24
  166. package/src/react/use-send.ts +0 -25
  167. package/src/vercel/codec/accumulator.ts +0 -603
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Encoder core — message append lifecycle machinery.
3
3
  *
4
- * Provides Ably primitives (publish, append, close, abort, flush) that
4
+ * Provides Ably primitives (publish, append, close, cancel, flush) that
5
5
  * domain-specific encoders wire their event types to.
6
6
  *
7
7
  * Domain encoders call `createEncoderCore(writer, options)` and use the
@@ -11,7 +11,13 @@
11
11
 
12
12
  import * as Ably from 'ably';
13
13
 
14
- import { HEADER_MSG_ID, HEADER_STATUS, HEADER_STREAM, HEADER_STREAM_ID } from '../../constants.js';
14
+ import {
15
+ HEADER_CODEC_MESSAGE_ID,
16
+ HEADER_DISCRETE,
17
+ HEADER_STATUS,
18
+ HEADER_STREAM,
19
+ HEADER_STREAM_ID,
20
+ } from '../../constants.js';
15
21
  import { ErrorCode } from '../../errors.js';
16
22
  import type { Logger } from '../../logger.js';
17
23
  import { mergeHeaders } from '../../utils.js';
@@ -36,8 +42,20 @@ interface StreamState {
36
42
  name: string;
37
43
  streamId: string;
38
44
  accumulated: string;
39
- persistentHeaders: Record<string, string>;
40
- aborted: boolean;
45
+ /** Transport-tier headers repeated on every append (`extras.ai.transport`). */
46
+ persistentTransport: Record<string, string>;
47
+ /** Codec-tier headers repeated on every append (`extras.ai.codec`). */
48
+ persistentCodec: Record<string, string>;
49
+ cancelled: boolean;
50
+ }
51
+
52
+ /**
53
+ * The SDK's `extras.ai` namespace as written to the wire: a `transport` tier
54
+ * (always present on SDK-published messages) and an optional `codec` tier.
55
+ */
56
+ interface AiExtras {
57
+ transport: Record<string, string>;
58
+ codec?: Record<string, string>;
41
59
  }
42
60
 
43
61
  interface PendingAppend {
@@ -57,32 +75,35 @@ export interface EncoderCore {
57
75
  /** Publish multiple discrete messages atomically in a single channel publish. */
58
76
  publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult>;
59
77
 
60
- /** Start a streamed message with x-ably-status:streaming. */
78
+ /** Start a streamed message with status:streaming. */
61
79
  startStream(streamId: string, payload: StreamPayload, opts?: WriteOptions): Promise<void>;
62
80
 
63
81
  /**
64
82
  * Append data to an in-flight streamed message. Fire-and-forget: errors are
65
- * collected internally and surfaced by {@link closeStream} or {@link close}.
83
+ * collected internally and surfaced by {@link closeStream}, {@link cancelStream},
84
+ * {@link cancelAllStreams} or {@link close}.
85
+ * @throws {Ably.ErrorInfo} InvalidArgument if there is no active stream for `streamId` or the core is closed.
66
86
  */
67
87
  appendStream(streamId: string, data: string): void;
68
88
 
69
89
  /**
70
- * Close a streamed message with x-ably-status:finished. Flushes all pending
90
+ * Close a streamed message with status:complete. Flushes all pending
71
91
  * appends for recovery before returning. Repeats persistent and payload headers.
92
+ * @throws {Ably.ErrorInfo} InvalidArgument if there is no active stream for `streamId`, or the encoder has been closed; EncoderRecoveryFailed if a failed append cannot be recovered during the flush.
72
93
  */
73
94
  closeStream(streamId: string, payload: StreamPayload): Promise<void>;
74
95
 
75
96
  /**
76
- * Abort a single in-progress stream (x-ably-status:aborted) and flush all
97
+ * Cancel a single in-progress stream (status:cancelled) and flush all
77
98
  * pending appends for recovery before returning.
78
99
  */
79
- abortStream(streamId: string, opts?: WriteOptions): Promise<void>;
100
+ cancelStream(streamId: string, opts?: WriteOptions): Promise<void>;
80
101
 
81
102
  /**
82
- * Abort all in-progress streams (x-ably-status:aborted) and flush all
103
+ * Cancel all in-progress streams (status:cancelled) and flush all
83
104
  * pending appends for recovery before returning.
84
105
  */
85
- abortAllStreams(opts?: WriteOptions): Promise<void>;
106
+ cancelAllStreams(opts?: WriteOptions): Promise<void>;
86
107
 
87
108
  /** Flush + clear trackers. Idempotent. */
88
109
  close(): Promise<void>;
@@ -128,7 +149,7 @@ class DefaultEncoderCore implements EncoderCore {
128
149
  async publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult> {
129
150
  this._assertNotClosed();
130
151
  this._logger?.trace('DefaultEncoderCore.publishDiscreteBatch();', { count: payloads.length });
131
- const msgs = payloads.map((p) => this._buildDiscreteMessage(p, opts));
152
+ const msgs = payloads.map((p) => this._buildDiscreteMessage(p, opts, true));
132
153
  return this._writer.publish(msgs);
133
154
  }
134
155
 
@@ -137,16 +158,17 @@ class DefaultEncoderCore implements EncoderCore {
137
158
  this._assertNotClosed();
138
159
  this._logger?.trace('DefaultEncoderCore.startStream();', { name: payload.name, streamId });
139
160
 
140
- const allHeaders = this._buildHeaders(payload.headers ?? {}, opts);
141
- allHeaders[HEADER_STREAM] = 'true';
142
- allHeaders[HEADER_STATUS] = 'streaming';
143
- allHeaders[HEADER_STREAM_ID] = streamId;
161
+ const transport = this._buildTransport(payload.transportHeaders, opts);
162
+ transport[HEADER_STREAM] = 'true';
163
+ transport[HEADER_STATUS] = 'streaming';
164
+ transport[HEADER_STREAM_ID] = streamId;
165
+ const codec = payload.codecHeaders ?? {};
144
166
 
145
167
  const clientId = this._resolveClientId(opts);
146
168
  const msg: Ably.Message = {
147
169
  name: payload.name,
148
170
  data: payload.data,
149
- extras: { headers: allHeaders },
171
+ extras: { ai: this._aiExtras(transport, codec) },
150
172
  ...(clientId ? { clientId } : {}),
151
173
  };
152
174
 
@@ -154,6 +176,7 @@ class DefaultEncoderCore implements EncoderCore {
154
176
  const result = await this._writer.publish(msg);
155
177
  const serial = result.serials[0];
156
178
 
179
+ // Spec: AIT-CD2a
157
180
  if (!serial) {
158
181
  throw new Ably.ErrorInfo(
159
182
  `unable to start stream; no serial returned for stream '${payload.name}' (streamId: ${streamId})`,
@@ -167,8 +190,9 @@ class DefaultEncoderCore implements EncoderCore {
167
190
  name: payload.name,
168
191
  streamId,
169
192
  accumulated: payload.data,
170
- persistentHeaders: allHeaders,
171
- aborted: false,
193
+ persistentTransport: transport,
194
+ persistentCodec: codec,
195
+ cancelled: false,
172
196
  });
173
197
 
174
198
  this._logger?.debug('DefaultEncoderCore.startStream(); stream started', {
@@ -181,6 +205,7 @@ class DefaultEncoderCore implements EncoderCore {
181
205
  // Spec: AIT-CD3
182
206
  appendStream(streamId: string, data: string): void {
183
207
  this._assertNotClosed();
208
+ // Spec: AIT-CD3a
184
209
  const tracker = this._trackers.get(streamId);
185
210
  if (!tracker) {
186
211
  throw new Ably.ErrorInfo(
@@ -195,7 +220,7 @@ class DefaultEncoderCore implements EncoderCore {
195
220
  const appendMsg: Ably.Message = {
196
221
  serial: tracker.serial,
197
222
  data,
198
- extras: { headers: { ...tracker.persistentHeaders } },
223
+ extras: { ai: this._aiExtras({ ...tracker.persistentTransport }, { ...tracker.persistentCodec }) },
199
224
  };
200
225
 
201
226
  this._invokeOnMessage(appendMsg);
@@ -220,13 +245,13 @@ class DefaultEncoderCore implements EncoderCore {
220
245
  // Accumulate closing data so recovery has the full content
221
246
  tracker.accumulated += payload.data;
222
247
 
223
- const allHeaders = this._buildClosingHeaders(tracker, payload.headers ?? {});
224
- allHeaders[HEADER_STATUS] = 'finished';
248
+ const { transport, codec } = this._buildClosing(tracker, payload);
249
+ transport[HEADER_STATUS] = 'complete';
225
250
 
226
251
  const msg: Ably.Message = {
227
252
  serial: tracker.serial,
228
253
  data: payload.data,
229
- extras: { headers: allHeaders },
254
+ extras: { ai: this._aiExtras(transport, codec) },
230
255
  };
231
256
 
232
257
  this._invokeOnMessage(msg);
@@ -239,28 +264,28 @@ class DefaultEncoderCore implements EncoderCore {
239
264
  }
240
265
 
241
266
  // Spec: AIT-CD5, AIT-CD5b
242
- async abortStream(streamId: string, opts?: WriteOptions): Promise<void> {
267
+ async cancelStream(streamId: string, opts?: WriteOptions): Promise<void> {
243
268
  this._assertNotClosed();
244
- this._logger?.trace('DefaultEncoderCore.abortStream();', { streamId });
269
+ this._logger?.trace('DefaultEncoderCore.cancelStream();', { streamId });
245
270
 
246
271
  const tracker = this._trackers.get(streamId);
247
272
  if (!tracker) {
248
273
  throw new Ably.ErrorInfo(
249
- `unable to abort stream; no active stream for streamId '${streamId}'`,
274
+ `unable to cancel stream; no active stream for streamId '${streamId}'`,
250
275
  ErrorCode.InvalidArgument,
251
276
  400,
252
277
  );
253
278
  }
254
279
 
255
- tracker.aborted = true;
280
+ tracker.cancelled = true;
256
281
 
257
- const allHeaders = this._buildClosingHeaders(tracker, {}, opts);
258
- allHeaders[HEADER_STATUS] = 'aborted';
282
+ const { transport, codec } = this._buildClosing(tracker, undefined, opts);
283
+ transport[HEADER_STATUS] = 'cancelled';
259
284
 
260
285
  const msg: Ably.Message = {
261
286
  serial: tracker.serial,
262
287
  data: '',
263
- extras: { headers: allHeaders },
288
+ extras: { ai: this._aiExtras(transport, codec) },
264
289
  };
265
290
 
266
291
  this._invokeOnMessage(msg);
@@ -269,24 +294,24 @@ class DefaultEncoderCore implements EncoderCore {
269
294
 
270
295
  await this._flushPending();
271
296
 
272
- this._logger?.debug('DefaultEncoderCore.abortStream(); stream aborted', { streamId });
297
+ this._logger?.debug('DefaultEncoderCore.cancelStream(); stream cancelled', { streamId });
273
298
  }
274
299
 
275
300
  // Spec: AIT-CD5a
276
- async abortAllStreams(opts?: WriteOptions): Promise<void> {
301
+ async cancelAllStreams(opts?: WriteOptions): Promise<void> {
277
302
  this._assertNotClosed();
278
- this._logger?.trace('DefaultEncoderCore.abortAllStreams();', { streamCount: this._trackers.size });
303
+ this._logger?.trace('DefaultEncoderCore.cancelAllStreams();', { streamCount: this._trackers.size });
279
304
 
280
305
  for (const tracker of this._trackers.values()) {
281
- tracker.aborted = true;
306
+ tracker.cancelled = true;
282
307
 
283
- const allHeaders = this._buildClosingHeaders(tracker, {}, opts);
284
- allHeaders[HEADER_STATUS] = 'aborted';
308
+ const { transport, codec } = this._buildClosing(tracker, undefined, opts);
309
+ transport[HEADER_STATUS] = 'cancelled';
285
310
 
286
311
  const msg: Ably.Message = {
287
312
  serial: tracker.serial,
288
313
  data: '',
289
- extras: { headers: allHeaders },
314
+ extras: { ai: this._aiExtras(transport, codec) },
290
315
  };
291
316
 
292
317
  this._invokeOnMessage(msg);
@@ -345,11 +370,16 @@ class DefaultEncoderCore implements EncoderCore {
345
370
  const tracker = this._trackers.get(streamId);
346
371
  if (!tracker) continue;
347
372
 
348
- const recoveryStatus = tracker.aborted ? 'aborted' : 'finished';
373
+ const recoveryStatus = tracker.cancelled ? 'cancelled' : 'complete';
349
374
  const msg: Ably.Message = {
350
375
  serial: tracker.serial,
351
376
  data: tracker.accumulated,
352
- extras: { headers: { ...tracker.persistentHeaders, [HEADER_STATUS]: recoveryStatus } },
377
+ extras: {
378
+ ai: this._aiExtras(
379
+ { ...tracker.persistentTransport, [HEADER_STATUS]: recoveryStatus },
380
+ { ...tracker.persistentCodec },
381
+ ),
382
+ },
353
383
  };
354
384
 
355
385
  try {
@@ -406,25 +436,53 @@ class DefaultEncoderCore implements EncoderCore {
406
436
  return opts?.clientId ?? this._defaultClientId;
407
437
  }
408
438
 
409
- private _buildHeaders(codecHeaders: Record<string, string>, opts?: WriteOptions): Record<string, string> {
439
+ /**
440
+ * Build the transport-tier header record for a message: caller-configured
441
+ * transport headers (default extras + per-write overrides) layered with any
442
+ * transport headers the codec payload stamps directly, plus the message-id.
443
+ * @param payloadTransport - Transport headers carried on the codec payload.
444
+ * @param opts - Optional per-write overrides.
445
+ * @returns The transport-tier headers record (`extras.ai.transport`).
446
+ */
447
+ private _buildTransport(
448
+ payloadTransport: Record<string, string> | undefined,
449
+ opts?: WriteOptions,
450
+ ): Record<string, string> {
410
451
  const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);
411
- const merged = { ...callerHeaders, ...codecHeaders };
452
+ const transport = { ...callerHeaders, ...payloadTransport };
412
453
  if (opts?.messageId !== undefined) {
413
- merged[HEADER_MSG_ID] = opts.messageId;
454
+ transport[HEADER_CODEC_MESSAGE_ID] = opts.messageId;
414
455
  }
415
- return merged;
456
+ return transport;
416
457
  }
417
458
 
418
- private _buildDiscreteMessage(payload: MessagePayload, opts?: WriteOptions): Ably.Message {
419
- const headers = this._buildHeaders(payload.headers ?? {}, opts);
420
- headers[HEADER_STREAM] = 'false';
459
+ /**
460
+ * Assemble the `extras.ai` namespace from its two tiers, omitting the codec
461
+ * tier when empty.
462
+ * @param transport - Transport-tier headers (always present on SDK messages).
463
+ * @param codec - Codec-tier headers; omitted from the wire when empty.
464
+ * @returns The `extras.ai` object.
465
+ */
466
+ private _aiExtras(transport: Record<string, string>, codec: Record<string, string>): AiExtras {
467
+ return Object.keys(codec).length > 0 ? { transport, codec } : { transport };
468
+ }
469
+
470
+ private _buildDiscreteMessage(payload: MessagePayload, opts?: WriteOptions, discrete = false): Ably.Message {
471
+ const transport = this._buildTransport(payload.transportHeaders, opts);
472
+ transport[HEADER_STREAM] = 'false';
473
+ if (discrete) {
474
+ // Mark batch-published payloads as discrete message parts (from writeMessages).
475
+ // The decoder relies on this header to distinguish message parts from lifecycle
476
+ // events that also happen to be discrete (stream: false).
477
+ transport[HEADER_DISCRETE] = 'true';
478
+ }
421
479
  const clientId = this._resolveClientId(opts);
422
480
 
423
481
  const msg: Ably.Message = {
424
482
  name: payload.name,
425
483
  data: payload.data,
426
484
  extras: {
427
- headers,
485
+ ai: this._aiExtras(transport, payload.codecHeaders ?? {}),
428
486
  ...(payload.ephemeral ? { ephemeral: true } : {}),
429
487
  },
430
488
  ...(clientId ? { clientId } : {}),
@@ -435,24 +493,23 @@ class DefaultEncoderCore implements EncoderCore {
435
493
  }
436
494
 
437
495
  /**
438
- * Build headers for a closing append. Closing appends must repeat ALL
439
- * persistent headers (Ably replaces the entire extras object on append).
496
+ * Build both header tiers for a closing append. Closing appends must repeat
497
+ * ALL persistent headers (Ably replaces the entire extras object on append).
440
498
  * Then layer caller and codec overrides.
441
499
  * @param tracker - The stream tracker with persistent headers.
442
- * @param codecHeaders - Codec-layer headers to merge.
500
+ * @param payload - The closing stream payload (codec + transport headers).
443
501
  * @param opts - Optional per-write overrides.
444
- * @returns Merged headers for the closing append.
502
+ * @returns The two tiers for the closing append.
445
503
  */
446
- private _buildClosingHeaders(
504
+ private _buildClosing(
447
505
  tracker: StreamState,
448
- codecHeaders: Record<string, string>,
506
+ payload: StreamPayload | undefined,
449
507
  opts?: WriteOptions,
450
- ): Record<string, string> {
451
- const h = { ...tracker.persistentHeaders };
508
+ ): { transport: Record<string, string>; codec: Record<string, string> } {
452
509
  const callerHeaders = mergeHeaders(this._defaultExtras?.headers, opts?.extras?.headers);
453
- Object.assign(h, callerHeaders);
454
- Object.assign(h, codecHeaders);
455
- return h;
510
+ const transport = { ...tracker.persistentTransport, ...callerHeaders, ...payload?.transportHeaders };
511
+ const codec = { ...tracker.persistentCodec, ...payload?.codecHeaders };
512
+ return { transport, codec };
456
513
  }
457
514
  }
458
515
 
@@ -1,17 +1,24 @@
1
- export { eventOutput } from './decoder.js';
2
1
  export type {
3
2
  ChannelWriter,
4
3
  Codec,
5
- DecoderOutput,
6
- DiscreteEncoder,
4
+ CodecInputEvent,
5
+ CodecMessage,
6
+ CodecOutputEvent,
7
+ DecodedMessage,
8
+ Decoder,
9
+ Encoder,
7
10
  EncoderOptions,
8
11
  Extras,
9
- MessageAccumulator,
10
12
  MessagePayload,
11
- StreamDecoder,
12
- StreamEncoder,
13
+ Reducer,
14
+ ReducerMeta,
15
+ Regenerate,
13
16
  StreamPayload,
14
17
  StreamTrackerState,
18
+ ToolApprovalResponse,
19
+ ToolResult,
20
+ ToolResultError,
21
+ UserMessage,
15
22
  WriteOptions,
16
23
  } from './types.js';
17
24
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Generic lifecycle tracker for codec decoders.
3
3
  *
4
- * Manages per-scope (typically per-turn) tracking of lifecycle phases that
4
+ * Manages per-scope (typically per-run) tracking of lifecycle phases that
5
5
  * must be emitted before content events. When a phase has not been emitted
6
6
  * (e.g. mid-stream join), the tracker synthesizes the missing events using
7
7
  * codec-provided build functions.
@@ -40,15 +40,16 @@ export interface PhaseConfig<TEvent> {
40
40
  * Per-scope lifecycle tracker that ensures required phases are emitted
41
41
  * before content events, synthesizing missing ones for mid-stream joins.
42
42
  *
43
- * Scoped by an arbitrary string key (typically a turn ID). Each scope
43
+ * Scoped by an arbitrary string key (typically a run ID). Each scope
44
44
  * tracks independently which phases have been emitted.
45
45
  */
46
46
  export interface LifecycleTracker<TEvent> {
47
47
  /**
48
48
  * Ensure all configured phases have been emitted for the given scope.
49
- * Returns synthetic events for any phases not yet marked as emitted,
50
- * then marks them. Returns an empty array if all phases are current.
51
- * @param scopeId - The scope to check (e.g. turn ID).
49
+ * Synthesizes and returns the events for any phases not yet marked as
50
+ * emitted, marking each as emitted so it is not synthesized again.
51
+ * Returns an empty array if all phases are already emitted.
52
+ * @param scopeId - The scope to check (e.g. run ID).
52
53
  * @param context - Key-value pairs passed through to phase build functions.
53
54
  * @returns Synthetic events for missing phases, in configuration order.
54
55
  */
@@ -57,7 +58,7 @@ export interface LifecycleTracker<TEvent> {
57
58
  /**
58
59
  * Mark a phase as emitted from the wire (not synthetic). Call this
59
60
  * when the real event arrives so the tracker does not re-synthesize it.
60
- * @param scopeId - The scope (e.g. turn ID).
61
+ * @param scopeId - The scope (e.g. run ID).
61
62
  * @param phaseKey - The phase key to mark.
62
63
  */
63
64
  markEmitted(scopeId: string, phaseKey: string): void;
@@ -66,14 +67,14 @@ export interface LifecycleTracker<TEvent> {
66
67
  * Reset a phase so it will be re-synthesized on the next
67
68
  * {@link ensurePhases} call. Used for repeating phases (e.g. "start-step"
68
69
  * resets after "finish-step").
69
- * @param scopeId - The scope (e.g. turn ID).
70
+ * @param scopeId - The scope (e.g. run ID).
70
71
  * @param phaseKey - The phase key to reset.
71
72
  */
72
73
  resetPhase(scopeId: string, phaseKey: string): void;
73
74
 
74
75
  /**
75
- * Remove all tracking state for a scope. Call on turn completion
76
- * (finish, abort) to free memory.
76
+ * Remove all tracking state for a scope. Call on run completion
77
+ * (finish, cancel) to free memory.
77
78
  * @param scopeId - The scope to clear.
78
79
  */
79
80
  clearScope(scopeId: string): void;