@ably/ai-transport 0.1.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.
Files changed (221) hide show
  1. package/README.md +93 -111
  2. package/dist/ably-ai-transport.js +2401 -1387
  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 +116 -42
  7. package/dist/core/agent.d.ts +44 -0
  8. package/dist/core/channel-options.d.ts +57 -0
  9. package/dist/core/codec/codec-event.d.ts +9 -0
  10. package/dist/core/codec/decoder.d.ts +24 -24
  11. package/dist/core/codec/define-codec.d.ts +100 -0
  12. package/dist/core/codec/encoder.d.ts +10 -12
  13. package/dist/core/codec/field-bag.d.ts +85 -0
  14. package/dist/core/codec/fields.d.ts +141 -0
  15. package/dist/core/codec/index.d.ts +8 -2
  16. package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
  17. package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
  18. package/dist/core/codec/input-descriptors.d.ts +281 -0
  19. package/dist/core/codec/lifecycle-tracker.d.ts +10 -9
  20. package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
  21. package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
  22. package/dist/core/codec/output-descriptors.d.ts +237 -0
  23. package/dist/core/codec/types.d.ts +470 -119
  24. package/dist/core/codec/well-known-inputs.d.ts +52 -0
  25. package/dist/core/transport/agent-session.d.ts +10 -0
  26. package/dist/core/transport/agent-view.d.ts +296 -0
  27. package/dist/core/transport/client-session.d.ts +13 -0
  28. package/dist/core/transport/decode-fold.d.ts +55 -0
  29. package/dist/core/transport/headers.d.ts +121 -14
  30. package/dist/core/transport/index.d.ts +5 -6
  31. package/dist/core/transport/internal/bounded-map.d.ts +20 -0
  32. package/dist/core/transport/invocation.d.ts +74 -0
  33. package/dist/core/transport/load-history-pages.d.ts +71 -0
  34. package/dist/core/transport/load-history.d.ts +44 -0
  35. package/dist/core/transport/pipe-stream.d.ts +9 -9
  36. package/dist/core/transport/run-manager.d.ts +76 -0
  37. package/dist/core/transport/session-support.d.ts +55 -0
  38. package/dist/core/transport/tree.d.ts +523 -109
  39. package/dist/core/transport/types/agent.d.ts +375 -0
  40. package/dist/core/transport/types/client.d.ts +201 -0
  41. package/dist/core/transport/types/shared.d.ts +24 -0
  42. package/dist/core/transport/types/tree.d.ts +357 -0
  43. package/dist/core/transport/types/view.d.ts +249 -0
  44. package/dist/core/transport/types.d.ts +13 -553
  45. package/dist/core/transport/view.d.ts +390 -84
  46. package/dist/core/transport/wire-log.d.ts +102 -0
  47. package/dist/errors.d.ts +27 -10
  48. package/dist/index.d.ts +8 -9
  49. package/dist/logger.d.ts +12 -0
  50. package/dist/react/ably-ai-transport-react.js +1365 -1010
  51. package/dist/react/ably-ai-transport-react.js.map +1 -1
  52. package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
  53. package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
  54. package/dist/react/contexts/client-session-context.d.ts +37 -0
  55. package/dist/react/contexts/client-session-provider.d.ts +56 -0
  56. package/dist/react/create-session-hooks.d.ts +116 -0
  57. package/dist/react/index.d.ts +13 -12
  58. package/dist/react/internal/skipped-session.d.ts +8 -0
  59. package/dist/react/internal/use-resolved-session.d.ts +36 -0
  60. package/dist/react/use-ably-messages.d.ts +17 -14
  61. package/dist/react/use-client-session.d.ts +81 -0
  62. package/dist/react/use-create-view.d.ts +14 -13
  63. package/dist/react/use-tree.d.ts +30 -15
  64. package/dist/react/use-view.d.ts +81 -50
  65. package/dist/utils.d.ts +48 -71
  66. package/dist/vercel/ably-ai-transport-vercel.js +3257 -2499
  67. package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
  68. package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
  69. package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
  70. package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
  71. package/dist/vercel/codec/events.d.ts +50 -0
  72. package/dist/vercel/codec/fields.d.ts +44 -0
  73. package/dist/vercel/codec/fold-content.d.ts +16 -0
  74. package/dist/vercel/codec/fold-data.d.ts +16 -0
  75. package/dist/vercel/codec/fold-input.d.ts +67 -0
  76. package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
  77. package/dist/vercel/codec/fold-text.d.ts +16 -0
  78. package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
  79. package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
  80. package/dist/vercel/codec/index.d.ts +7 -20
  81. package/dist/vercel/codec/inputs.d.ts +11 -0
  82. package/dist/vercel/codec/outputs.d.ts +11 -0
  83. package/dist/vercel/codec/reducer-state.d.ts +121 -0
  84. package/dist/vercel/codec/reducer.d.ts +62 -0
  85. package/dist/vercel/codec/tool-transitions.d.ts +2 -8
  86. package/dist/vercel/codec/wire-data.d.ts +34 -0
  87. package/dist/vercel/index.d.ts +5 -5
  88. package/dist/vercel/react/ably-ai-transport-vercel-react.js +2859 -9705
  89. package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
  90. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -45
  91. package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
  92. package/dist/vercel/react/contexts/chat-transport-context.d.ts +9 -7
  93. package/dist/vercel/react/contexts/chat-transport-provider.d.ts +53 -41
  94. package/dist/vercel/react/index.d.ts +1 -2
  95. package/dist/vercel/react/use-chat-transport.d.ts +30 -26
  96. package/dist/vercel/react/use-message-sync.d.ts +17 -30
  97. package/dist/vercel/run-end-reason.d.ts +84 -0
  98. package/dist/vercel/tool-part.d.ts +21 -0
  99. package/dist/vercel/transport/chat-transport.d.ts +41 -24
  100. package/dist/vercel/transport/index.d.ts +24 -20
  101. package/dist/vercel/transport/run-output-stream.d.ts +54 -0
  102. package/dist/version.d.ts +2 -0
  103. package/package.json +31 -24
  104. package/src/constants.ts +124 -51
  105. package/src/core/agent.ts +92 -0
  106. package/src/core/channel-options.ts +89 -0
  107. package/src/core/codec/codec-event.ts +27 -0
  108. package/src/core/codec/decoder.ts +202 -105
  109. package/src/core/codec/define-codec.ts +432 -0
  110. package/src/core/codec/encoder.ts +114 -107
  111. package/src/core/codec/field-bag.ts +142 -0
  112. package/src/core/codec/fields.ts +193 -0
  113. package/src/core/codec/index.ts +56 -6
  114. package/src/core/codec/input-descriptor-decoder.ts +97 -0
  115. package/src/core/codec/input-descriptor-encoder.ts +150 -0
  116. package/src/core/codec/input-descriptors.ts +373 -0
  117. package/src/core/codec/lifecycle-tracker.ts +10 -9
  118. package/src/core/codec/output-descriptor-decoder.ts +139 -0
  119. package/src/core/codec/output-descriptor-encoder.ts +101 -0
  120. package/src/core/codec/output-descriptors.ts +307 -0
  121. package/src/core/codec/types.ts +505 -126
  122. package/src/core/codec/well-known-inputs.ts +96 -0
  123. package/src/core/transport/agent-session.ts +1085 -0
  124. package/src/core/transport/agent-view.ts +738 -0
  125. package/src/core/transport/client-session.ts +780 -0
  126. package/src/core/transport/decode-fold.ts +101 -0
  127. package/src/core/transport/headers.ts +234 -22
  128. package/src/core/transport/index.ts +27 -27
  129. package/src/core/transport/internal/bounded-map.ts +27 -0
  130. package/src/core/transport/invocation.ts +98 -0
  131. package/src/core/transport/load-history-pages.ts +220 -0
  132. package/src/core/transport/load-history.ts +271 -0
  133. package/src/core/transport/pipe-stream.ts +63 -39
  134. package/src/core/transport/run-manager.ts +243 -0
  135. package/src/core/transport/session-support.ts +96 -0
  136. package/src/core/transport/tree.ts +1293 -308
  137. package/src/core/transport/types/agent.ts +434 -0
  138. package/src/core/transport/types/client.ts +247 -0
  139. package/src/core/transport/types/shared.ts +27 -0
  140. package/src/core/transport/types/tree.ts +393 -0
  141. package/src/core/transport/types/view.ts +288 -0
  142. package/src/core/transport/types.ts +13 -706
  143. package/src/core/transport/view.ts +1229 -450
  144. package/src/core/transport/wire-log.ts +189 -0
  145. package/src/errors.ts +29 -9
  146. package/src/event-emitter.ts +3 -2
  147. package/src/index.ts +86 -42
  148. package/src/logger.ts +14 -1
  149. package/src/react/contexts/client-session-context.ts +41 -0
  150. package/src/react/contexts/client-session-provider.tsx +222 -0
  151. package/src/react/create-session-hooks.ts +141 -0
  152. package/src/react/index.ts +24 -13
  153. package/src/react/internal/skipped-session.ts +62 -0
  154. package/src/react/internal/use-resolved-session.ts +63 -0
  155. package/src/react/use-ably-messages.ts +32 -22
  156. package/src/react/use-client-session.ts +178 -0
  157. package/src/react/use-create-view.ts +33 -29
  158. package/src/react/use-tree.ts +61 -30
  159. package/src/react/use-view.ts +138 -96
  160. package/src/utils.ts +83 -131
  161. package/src/vercel/codec/decode-lifecycle.ts +70 -0
  162. package/src/vercel/codec/events.ts +85 -0
  163. package/src/vercel/codec/fields.ts +58 -0
  164. package/src/vercel/codec/fold-content.ts +54 -0
  165. package/src/vercel/codec/fold-data.ts +46 -0
  166. package/src/vercel/codec/fold-input.ts +255 -0
  167. package/src/vercel/codec/fold-lifecycle.ts +85 -0
  168. package/src/vercel/codec/fold-text.ts +55 -0
  169. package/src/vercel/codec/fold-tool-input.ts +86 -0
  170. package/src/vercel/codec/fold-tool-output.ts +79 -0
  171. package/src/vercel/codec/index.ts +28 -21
  172. package/src/vercel/codec/inputs.ts +116 -0
  173. package/src/vercel/codec/outputs.ts +207 -0
  174. package/src/vercel/codec/reducer-state.ts +169 -0
  175. package/src/vercel/codec/reducer.ts +191 -0
  176. package/src/vercel/codec/tool-transitions.ts +3 -14
  177. package/src/vercel/codec/wire-data.ts +64 -0
  178. package/src/vercel/index.ts +7 -19
  179. package/src/vercel/react/contexts/chat-transport-context.ts +8 -7
  180. package/src/vercel/react/contexts/chat-transport-provider.tsx +87 -59
  181. package/src/vercel/react/index.ts +3 -5
  182. package/src/vercel/react/use-chat-transport.ts +44 -66
  183. package/src/vercel/react/use-message-sync.ts +75 -39
  184. package/src/vercel/run-end-reason.ts +157 -0
  185. package/src/vercel/tool-part.ts +25 -0
  186. package/src/vercel/transport/chat-transport.ts +380 -98
  187. package/src/vercel/transport/index.ts +38 -37
  188. package/src/vercel/transport/run-output-stream.ts +169 -0
  189. package/src/version.ts +2 -0
  190. package/dist/core/transport/client-transport.d.ts +0 -10
  191. package/dist/core/transport/decode-history.d.ts +0 -43
  192. package/dist/core/transport/server-transport.d.ts +0 -7
  193. package/dist/core/transport/stream-router.d.ts +0 -29
  194. package/dist/core/transport/turn-manager.d.ts +0 -37
  195. package/dist/react/contexts/transport-context.d.ts +0 -31
  196. package/dist/react/contexts/transport-provider.d.ts +0 -49
  197. package/dist/react/create-transport-hooks.d.ts +0 -124
  198. package/dist/react/use-active-turns.d.ts +0 -12
  199. package/dist/react/use-client-transport.d.ts +0 -80
  200. package/dist/vercel/codec/accumulator.d.ts +0 -21
  201. package/dist/vercel/codec/decoder.d.ts +0 -22
  202. package/dist/vercel/codec/encoder.d.ts +0 -41
  203. package/dist/vercel/react/use-staged-add-tool-approval-response.d.ts +0 -30
  204. package/dist/vercel/tool-approvals.d.ts +0 -124
  205. package/dist/vercel/tool-events.d.ts +0 -26
  206. package/src/core/transport/client-transport.ts +0 -977
  207. package/src/core/transport/decode-history.ts +0 -485
  208. package/src/core/transport/server-transport.ts +0 -612
  209. package/src/core/transport/stream-router.ts +0 -136
  210. package/src/core/transport/turn-manager.ts +0 -165
  211. package/src/react/contexts/transport-context.ts +0 -37
  212. package/src/react/contexts/transport-provider.tsx +0 -164
  213. package/src/react/create-transport-hooks.ts +0 -144
  214. package/src/react/use-active-turns.ts +0 -72
  215. package/src/react/use-client-transport.ts +0 -197
  216. package/src/vercel/codec/accumulator.ts +0 -588
  217. package/src/vercel/codec/decoder.ts +0 -618
  218. package/src/vercel/codec/encoder.ts +0 -410
  219. package/src/vercel/react/use-staged-add-tool-approval-response.ts +0 -87
  220. package/src/vercel/tool-approvals.ts +0 -380
  221. package/src/vercel/tool-events.ts +0 -53
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Typed header-field bindings.
3
+ *
4
+ * A {@link HeaderField} binds a codec header key to its value type **once**,
5
+ * and exposes a symmetric {@link HeaderField.read | read} / {@link
6
+ * HeaderField.write | write} pair over a raw `Record<string, string>` headers
7
+ * record. Because a single binding drives both the encode and decode side, the
8
+ * header key and its value type stay in lockstep across directions — a key
9
+ * cannot be misspelled on one side and silently read as absent on the other.
10
+ *
11
+ * This is deliberately **not** a schema library: it is a thin bidirectional
12
+ * string (de)serializer over the headers record. The SDK ships no hard runtime
13
+ * dependencies, and a schema approach would force re-declaring peer-SDK-owned
14
+ * types. The four constructors cover every header value shape the codecs use:
15
+ *
16
+ * - {@link strField} — string values, optional default.
17
+ * - {@link boolField} — `"true"`/`"false"` booleans, optional default.
18
+ * - {@link jsonField} — JSON-serialized structured values.
19
+ * - {@link enumField} — string values validated against an allow-list with a
20
+ * fallback (e.g. a finish reason).
21
+ *
22
+ * Passing a default to `strField`/`boolField` makes the field **total**: its
23
+ * `read` returns `V` rather than `V | undefined`, for required headers that
24
+ * should always decode to a concrete value.
25
+ */
26
+
27
+ import { parseBool, parseJson } from '../../utils.js';
28
+
29
+ /**
30
+ * A header key bound to its value type, with symmetric read/write over a raw
31
+ * headers record. Created via {@link strField}, {@link boolField}, {@link
32
+ * jsonField}, or {@link enumField}.
33
+ *
34
+ * The `key` plays a dual role in descriptor `fields` tables: it is the wire
35
+ * header key AND the property name the drivers read off the source object on
36
+ * encode and write back into the rebuilt object on decode. {@link FieldFor}
37
+ * enforces this — a declared field's key must name a real property of the
38
+ * member it lenses onto.
39
+ * @template V - The decoded value type this field reads and writes.
40
+ * @template K - The header key literal (preserved so {@link FieldFor} can match it against the member's property names).
41
+ */
42
+ export interface HeaderField<V, K extends string = string> {
43
+ /** The raw header key this field reads from and writes to — also the source/rebuilt property name in descriptor tables. */
44
+ readonly key: K;
45
+ /**
46
+ * Read and decode this field's value from a headers record.
47
+ * @param headers - The raw codec headers record to read from.
48
+ * @returns The decoded value. For defaulted/validated fields this is total
49
+ * (the default/fallback is returned when the header is absent or invalid);
50
+ * otherwise `undefined` when the header is absent.
51
+ */
52
+ read(headers: Record<string, string>): V;
53
+ /**
54
+ * Encode and write this field's value into a headers record, mutating it in
55
+ * place. `undefined` (and `null`, for JSON values), and values whose runtime
56
+ * type doesn't match the field, are skipped — the key is left unset rather
57
+ * than written. The parameter is `unknown` (not `V`) so a field keeps `V` in
58
+ * a single covariant position (`read`); this lets heterogeneous fields share a
59
+ * `HeaderField<unknown>[]` array, which the descriptor drivers rely on. At a
60
+ * typed call site the caller still passes a `V`.
61
+ * @param headers - The headers record to mutate.
62
+ * @param value - The value to encode and set.
63
+ */
64
+ write(headers: Record<string, string>, value: unknown): void;
65
+ }
66
+
67
+ /**
68
+ * Symmetric codec for a descriptor's wire `data`. Many wire payloads are object
69
+ * envelopes a decode reads several chunk props out of (e.g. `{ errorText, input }`),
70
+ * so a single field can't model them. `encode` produces the wire data from the
71
+ * chunk; `decode` returns the chunk props the envelope contributes, merged into
72
+ * the rebuilt chunk by the driver.
73
+ * @template C - The narrowed chunk member.
74
+ */
75
+ export interface DataCodec<C> {
76
+ /** Produce the wire `data` from the chunk. */
77
+ encode: (chunk: C) => unknown;
78
+ /**
79
+ * Extract the chunk props this envelope contributes from the wire `data`.
80
+ * Undefined-valued props are stripped when the driver rebuilds the object —
81
+ * every rebuild seam (output chunk, input payload, batch part) applies the
82
+ * same rule, since absent and undefined are indistinguishable on the wire.
83
+ */
84
+ decode: (data: unknown) => Partial<C>;
85
+ }
86
+
87
+ /**
88
+ * The header fields a descriptor may declare against member `C`. For each
89
+ * string-keyed property of `C`, a field is acceptable when its key IS that
90
+ * property name and its value type can hold the property. A mistyped key or a
91
+ * wrong-typed field (e.g. a `boolField` on a string property) is a compile
92
+ * error instead of a silently absent header.
93
+ * @template C - The member (chunk, payload, or part) the fields lens onto.
94
+ */
95
+ export type FieldFor<C> = {
96
+ [K in keyof C & string]-?: HeaderField<C[K] | undefined, K>;
97
+ }[keyof C & string];
98
+
99
+ /**
100
+ * Bind a string-valued header field.
101
+ * @param key - The header key (and source property name in descriptor tables).
102
+ * @returns A field whose `read` yields `string | undefined` (absent → `undefined`).
103
+ */
104
+ export function strField<K extends string>(key: K): HeaderField<string | undefined, K>;
105
+ /**
106
+ * Bind a string-valued header field with a default, making it total.
107
+ * @param key - The header key (and source property name in descriptor tables).
108
+ * @param fallback - Value returned by `read` when the header is absent.
109
+ * @returns A field whose `read` yields `string` (absent → `fallback`).
110
+ */
111
+ export function strField<K extends string>(key: K, fallback: string): HeaderField<string, K>;
112
+ export function strField<K extends string>(key: K, fallback?: string): HeaderField<string | undefined, K> {
113
+ return {
114
+ key,
115
+ read: (headers) => headers[key] ?? fallback,
116
+ write: (headers, value) => {
117
+ if (typeof value === 'string') headers[key] = value;
118
+ },
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Bind a boolean-valued header field, serialized as `"true"`/`"false"`.
124
+ * @param key - The header key (and source property name in descriptor tables).
125
+ * @returns A field whose `read` yields `boolean | undefined` (absent → `undefined`).
126
+ */
127
+ export function boolField<K extends string>(key: K): HeaderField<boolean | undefined, K>;
128
+ /**
129
+ * Bind a boolean-valued header field with a default, making it total.
130
+ * @param key - The header key (and source property name in descriptor tables).
131
+ * @param fallback - Value returned by `read` when the header is absent.
132
+ * @returns A field whose `read` yields `boolean` (absent → `fallback`).
133
+ */
134
+ export function boolField<K extends string>(key: K, fallback: boolean): HeaderField<boolean, K>;
135
+ export function boolField<K extends string>(key: K, fallback?: boolean): HeaderField<boolean | undefined, K> {
136
+ return {
137
+ key,
138
+ read: (headers) => parseBool(headers[key]) ?? fallback,
139
+ write: (headers, value) => {
140
+ if (typeof value === 'boolean') headers[key] = String(value);
141
+ },
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Bind a JSON-serialized header field. The value is written with
147
+ * `JSON.stringify` and read back with `JSON.parse`; malformed JSON reads as
148
+ * `undefined`. The decoded shape is a trust boundary — the caller asserts it
149
+ * via the `V` type parameter.
150
+ * @template V - The expected decoded shape of the JSON value.
151
+ * @template K - The header key literal. Inferred when `V` is omitted; pass it explicitly alongside `V` when the field participates in a typed descriptor `fields` table.
152
+ * @param key - The header key (and source property name in descriptor tables).
153
+ * @returns A field whose `read` yields `V | undefined` (absent or malformed → `undefined`).
154
+ */
155
+ export const jsonField = <V, K extends string = string>(key: K): HeaderField<V | undefined, K> => ({
156
+ key,
157
+ // CAST: header values are wire data parsed via JSON.parse — a trust
158
+ // boundary. The caller declares the expected shape through `V`; malformed
159
+ // JSON reads back as `undefined` (parseJson swallows the parse error).
160
+ read: (headers) => parseJson(headers[key]) as V | undefined,
161
+ write: (headers, value) => {
162
+ // Skip undefined and null so an absent value leaves the key unset rather
163
+ // than serializing to "null".
164
+ if (value !== undefined && value !== null) headers[key] = JSON.stringify(value);
165
+ },
166
+ });
167
+
168
+ /**
169
+ * Bind a string-valued header field validated against a fixed allow-list,
170
+ * falling back to a given value when the header is absent or unrecognized. Use
171
+ * for headers with a small closed set of valid values (e.g. a finish reason).
172
+ * @template T - The union of allowed string literals, inferred from `allowed`.
173
+ * @template K - The header key literal, inferred from `key`.
174
+ * @param key - The header key (and source property name in descriptor tables).
175
+ * @param allowed - The exhaustive list of valid values.
176
+ * @param fallback - Value returned by `read` when the header is absent or not in `allowed`.
177
+ * @returns A total field whose `read` yields one of the allowed literals.
178
+ */
179
+ export const enumField = <const T extends string, K extends string>(
180
+ key: K,
181
+ allowed: readonly T[],
182
+ fallback: NoInfer<T>,
183
+ ): HeaderField<T, K> => ({
184
+ key,
185
+ read: (headers) => {
186
+ const raw = headers[key];
187
+ // find returns the matched literal (typed T) or undefined — no cast needed.
188
+ return allowed.find((candidate) => candidate === raw) ?? fallback;
189
+ },
190
+ write: (headers, value) => {
191
+ if (typeof value === 'string') headers[key] = value;
192
+ },
193
+ });
@@ -1,17 +1,25 @@
1
- export { eventOutput } from './decoder.js';
2
1
  export type {
3
2
  ChannelWriter,
4
3
  Codec,
5
- DecoderOutput,
6
- DiscreteEncoder,
4
+ CodecEvent,
5
+ CodecInputEvent,
6
+ CodecMessage,
7
+ CodecOutputEvent,
8
+ DecodedMessage,
9
+ Decoder,
10
+ Encoder,
7
11
  EncoderOptions,
8
12
  Extras,
9
- MessageAccumulator,
10
13
  MessagePayload,
11
- StreamDecoder,
12
- StreamEncoder,
14
+ Reducer,
15
+ ReducerMeta,
16
+ Regenerate,
13
17
  StreamPayload,
14
18
  StreamTrackerState,
19
+ ToolApprovalResponse,
20
+ ToolResult,
21
+ ToolResultError,
22
+ UserMessage,
15
23
  WriteOptions,
16
24
  } from './types.js';
17
25
 
@@ -26,3 +34,45 @@ export { createDecoderCore } from './decoder.js';
26
34
  // Lifecycle tracker
27
35
  export type { LifecycleTracker, PhaseConfig } from './lifecycle-tracker.js';
28
36
  export { createLifecycleTracker } from './lifecycle-tracker.js';
37
+
38
+ // Typed header-field bindings
39
+ export type { DataCodec, FieldFor, HeaderField } from './fields.js';
40
+ export { boolField, enumField, jsonField, strField } from './fields.js';
41
+
42
+ // Well-known input factories (merged into every codec by defineCodec)
43
+ export type { WellKnownInputFactories } from './well-known-inputs.js';
44
+
45
+ // Output descriptor authoring surface
46
+ export type {
47
+ EscapeHatchCore,
48
+ HeaderBuilder,
49
+ OutputDecodeContext,
50
+ OutputDescriptor,
51
+ OutputEncodeHatchContext,
52
+ OutputEventSpec,
53
+ OutputStreamEndContext,
54
+ OutputStreamSpec,
55
+ } from './output-descriptors.js';
56
+
57
+ // Input descriptor authoring surface
58
+ export type {
59
+ BatchAssembleContext,
60
+ BatchMessageHeaders,
61
+ BatchSpec,
62
+ InputDescriptor,
63
+ InputEventSpec,
64
+ PartBuilder,
65
+ PartSpec,
66
+ } from './input-descriptors.js';
67
+
68
+ // Codec composition factory
69
+ export type {
70
+ CodecReducer,
71
+ DefineCodecConfig,
72
+ DefinedCodec,
73
+ InputBuilder,
74
+ LifecycleDiscreteContext,
75
+ LifecyclePolicy,
76
+ OutputBuilder,
77
+ } from './define-codec.js';
78
+ export { defineCodec } from './define-codec.js';
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Generic input decode driver over an input descriptor set — the input-side
3
+ * sibling of {@link import('./output-descriptor-decoder.js')}.
4
+ *
5
+ * Rebuilds inputs from one inbound `ai-input` message, dispatching on the codec
6
+ * `kind` header. A single `event` rebuilds its field bag (and `data`) and wraps
7
+ * it into the `{ kind, codecMessageId, payload }` envelope; `wireOnly` events
8
+ * decode to `[]`. A
9
+ * `batch` reads the `partType` sub-discriminator, rebuilds the part via its
10
+ * sub-table, `assemble`s it into a one-part input, and the driver stamps the
11
+ * `kind` plus the codec-message-id reconstructed from the transport header.
12
+ *
13
+ * Returns bare `TInput[]`, never `CodecEvent[]` — direction tagging is
14
+ * core-owned, downstream at the decode→fold seam.
15
+ */
16
+
17
+ import { HEADER_CODEC_MESSAGE_ID } from '../../constants.js';
18
+ import { stripUndefined } from '../../utils.js';
19
+ import { PART_TYPE_HEADER, partFor, readFields } from './field-bag.js';
20
+ import type {
21
+ BatchDescriptor,
22
+ InputDecodeContext,
23
+ InputDescriptor,
24
+ InputEventDescriptor,
25
+ } from './input-descriptors.js';
26
+
27
+ /** Decodes inbound `ai-input` messages of union `U` from an input descriptor set. */
28
+ export interface InputDescriptorDecoder<U> {
29
+ /**
30
+ * Rebuild zero or more inputs from one inbound `ai-input` message.
31
+ * @param ctx - The inbound message context (codec kind, data, header tiers).
32
+ * @returns The decoded inputs (empty when no descriptor matches or the input is wire-only).
33
+ */
34
+ decode(ctx: InputDecodeContext): U[];
35
+ }
36
+
37
+ /**
38
+ * Build an input decode driver for an input descriptor set.
39
+ * @template U - The codec's input union.
40
+ * @param descriptors - The input descriptor set (events + batches).
41
+ * @returns An {@link InputDescriptorDecoder} that reconstructs inputs from the wire.
42
+ */
43
+ export const createInputDescriptorDecoder = <U extends { kind: string }>(
44
+ descriptors: readonly InputDescriptor<U>[],
45
+ ): InputDescriptorDecoder<U> => {
46
+ const byKind = new Map<string, InputDescriptor<U>>();
47
+ for (const descriptor of descriptors) byKind.set(descriptor.kind, descriptor);
48
+
49
+ const decodeEvent = (descriptor: InputEventDescriptor, ctx: InputDecodeContext): U[] => {
50
+ if (descriptor.wireOnly) return [];
51
+
52
+ const bag = readFields(descriptor.fields, ctx.codecHeaders);
53
+ if (descriptor.data) Object.assign(bag, descriptor.data.decode(ctx.data));
54
+
55
+ const codecMessageId = ctx.transportHeaders[HEADER_CODEC_MESSAGE_ID] ?? '';
56
+ // The payload bag is stripped of undefined-valued props — the same rule
57
+ // every rebuild seam applies to its innermost bag (absent and undefined
58
+ // are indistinguishable on the wire). The envelope keys are always defined.
59
+ // CAST: the rebuild seam — `bag` is assembled from the descriptor's declared
60
+ // fields and data codec onto the payload, so the `{ kind, codecMessageId, payload }`
61
+ // envelope conforms to the matched member by construction.
62
+ return [{ kind: descriptor.kind, codecMessageId, payload: stripUndefined(bag) } as unknown as U];
63
+ };
64
+
65
+ const decodeBatch = (descriptor: BatchDescriptor<U>, ctx: InputDecodeContext): U[] => {
66
+ const partType = ctx.codecHeaders[PART_TYPE_HEADER] ?? '';
67
+ const partDesc = partFor(descriptor.parts, partType);
68
+ if (!partDesc) return [];
69
+
70
+ const bag = readFields(partDesc.fields, ctx.codecHeaders);
71
+ if (partDesc.data) Object.assign(bag, partDesc.data.decode(ctx.data));
72
+ bag.type = partType;
73
+
74
+ // `assemble` takes the erased part (`unknown`); `bag` is the part rebuilt from
75
+ // its declared fields/data plus the `partType` written to the domain `type`
76
+ // field. The header tiers carry the per-message metadata (id, role, …) the
77
+ // batch stamped on every part, so `assemble` can rebuild the message envelope.
78
+ const partial = descriptor.assemble(stripUndefined(bag), {
79
+ codecHeaders: ctx.codecHeaders,
80
+ transportHeaders: ctx.transportHeaders,
81
+ });
82
+ // CAST: the driver stamps the shared `kind` onto the assembled one-part input; together
83
+ // they complete the matched member. A batch creates a new message (not addressed by a
84
+ // codec-message-id, unlike single `event`s), so none is stamped — the per-message
85
+ // identity rides the transport header and is recovered by `assemble` when needed.
86
+ return [{ kind: descriptor.kind, ...partial } as unknown as U];
87
+ };
88
+
89
+ return {
90
+ decode: (ctx) => {
91
+ const descriptor = byKind.get(ctx.codecKind);
92
+ if (!descriptor) return [];
93
+ if (descriptor.construct === 'event') return decodeEvent(descriptor, ctx);
94
+ return decodeBatch(descriptor, ctx);
95
+ },
96
+ };
97
+ };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Generic input encode driver over an input descriptor set — the input-side
3
+ * sibling of {@link import('./output-descriptor-encoder.js')}.
4
+ *
5
+ * Builds a `kind`→descriptor registry once, then routes each input: a single
6
+ * `event` publishes one discrete message (fields/data lensed onto the member's
7
+ * `payload`, or kind-only when `wireOnly`); a `batch` explodes the domain
8
+ * message into one wire event per part and publishes them atomically, with a
9
+ * built-in ≥1-event guarantee so the codec-message-id and role survive an empty
10
+ * decomposition. Headers are always built through the descriptor's declared
11
+ * fields ({@link writeFields}), so the imperative paths can't drift.
12
+ */
13
+
14
+ import * as Ably from 'ably';
15
+
16
+ import { ErrorCode } from '../../errors.js';
17
+ import { KIND_HEADER, PART_TYPE_HEADER, partFor, prop, writeFields } from './field-bag.js';
18
+ import type {
19
+ BatchDescriptor,
20
+ BatchMessageHeaders,
21
+ InputDescriptor,
22
+ InputEncodeContext,
23
+ InputEncoderCore,
24
+ InputEventDescriptor,
25
+ } from './input-descriptors.js';
26
+ import type { MessagePayload } from './types.js';
27
+
28
+ /** Encodes inputs of union `U` to channel operations via an input descriptor set. */
29
+ export interface InputDescriptorEncoder<U> {
30
+ /**
31
+ * Encode one input through its descriptor.
32
+ * @param input - The input to encode.
33
+ * @param core - The input encoder core to publish through.
34
+ * @param ctx - Per-write context (write options, carrying the codec-message-id).
35
+ * @returns A promise resolving when the publish operation completes.
36
+ */
37
+ encode(input: U, core: InputEncoderCore, ctx: InputEncodeContext): Promise<void>;
38
+ }
39
+
40
+ // Layer the batch's per-message transport headers onto a part payload, if any.
41
+ const withMessageTransport = (payload: MessagePayload, message: BatchMessageHeaders | undefined): MessagePayload =>
42
+ message?.transportHeaders === undefined
43
+ ? payload
44
+ : { ...payload, transportHeaders: { ...payload.transportHeaders, ...message.transportHeaders } };
45
+
46
+ /**
47
+ * Build an input encode driver for an input descriptor set bound to a wire name.
48
+ * @template U - The codec's input union.
49
+ * @param descriptors - The input descriptor set (events + batches).
50
+ * @param wireName - The Ably message name for the input direction (`ai-input`).
51
+ * @returns An {@link InputDescriptorEncoder} routing each input through its descriptor.
52
+ */
53
+ export const createInputDescriptorEncoder = <U extends { kind: string }>(
54
+ descriptors: readonly InputDescriptor<U>[],
55
+ wireName: string,
56
+ ): InputDescriptorEncoder<U> => {
57
+ const byKind = new Map<string, InputDescriptor<U>>();
58
+ for (const descriptor of descriptors) byKind.set(descriptor.kind, descriptor);
59
+
60
+ const encodeEvent = async (
61
+ descriptor: InputEventDescriptor,
62
+ input: U,
63
+ core: InputEncoderCore,
64
+ ctx: InputEncodeContext,
65
+ ): Promise<void> => {
66
+ if (descriptor.wireOnly) {
67
+ // Kind only: no fields, no data — the parent/target ride transport headers.
68
+ await core.publishDiscrete(
69
+ { name: wireName, data: '', codecHeaders: { [KIND_HEADER]: descriptor.kind } },
70
+ ctx.opts,
71
+ );
72
+ return;
73
+ }
74
+ // A non-wireOnly input nests its domain data under `payload` — fields and
75
+ // data are authored against it. Fail fast on a payload-less member instead
76
+ // of silently publishing empty data the decoder can't rebuild from.
77
+ const source = prop(input, 'payload');
78
+ if (typeof source !== 'object' || source === null) {
79
+ throw new Ably.ErrorInfo(
80
+ `unable to encode input; event '${descriptor.kind}' carries no payload object — declare it wireOnly or use an encode escape hatch`,
81
+ ErrorCode.InvalidArgument,
82
+ 400,
83
+ );
84
+ }
85
+ const codecHeaders = writeFields(descriptor.fields, descriptor.kind, source);
86
+ const data = descriptor.data ? descriptor.data.encode(source) : '';
87
+ await core.publishDiscrete({ name: wireName, data, codecHeaders }, ctx.opts);
88
+ };
89
+
90
+ const encodeBatch = async (
91
+ descriptor: BatchDescriptor<U>,
92
+ input: U,
93
+ core: InputEncoderCore,
94
+ ctx: InputEncodeContext,
95
+ ): Promise<void> => {
96
+ // Per-message headers (e.g. message id, role) are stamped on every part so
97
+ // the decode side can reconstruct the shared message envelope from any one.
98
+ const message = descriptor.messageHeaders?.(input);
99
+ const payloads: MessagePayload[] = [];
100
+ for (const part of descriptor.explode(input)) {
101
+ const partType = descriptor.partTypeOf(part);
102
+ const partDesc = partFor(descriptor.parts, partType);
103
+ if (!partDesc) continue;
104
+ // CAST: a part is indexed by its declared fields; the part descriptor only
105
+ // runs against the part its predicate/literal matched, so the source has the
106
+ // field's type at runtime. The wire `partType` is the resolved part type.
107
+ const source = part as object;
108
+ const codecHeaders = {
109
+ ...writeFields(partDesc.fields, descriptor.kind, source),
110
+ ...message?.codecHeaders,
111
+ [PART_TYPE_HEADER]: partType,
112
+ };
113
+ const data = partDesc.data ? partDesc.data.encode(part) : '';
114
+ payloads.push(withMessageTransport({ name: wireName, data, codecHeaders }, message));
115
+ }
116
+
117
+ if (payloads.length === 0) {
118
+ // ≥1-event guarantee: emit one bare part so the per-message headers (e.g.
119
+ // the message id and role) reach the wire even when no exploded part
120
+ // matched a descriptor. This fallback carries no partType, so the batch
121
+ // decode path yields no input for it — a codec that needs an empty
122
+ // message to round-trip must guarantee ≥1 encodable part in `explode`
123
+ // (e.g. by substituting a canonical empty part).
124
+ payloads.push(
125
+ withMessageTransport(
126
+ { name: wireName, data: '', codecHeaders: { [KIND_HEADER]: descriptor.kind, ...message?.codecHeaders } },
127
+ message,
128
+ ),
129
+ );
130
+ }
131
+
132
+ await core.publishDiscreteBatch(payloads, ctx.opts);
133
+ };
134
+
135
+ return {
136
+ encode: async (input, core, ctx) => {
137
+ const descriptor = byKind.get(input.kind);
138
+ if (!descriptor) {
139
+ throw new Ably.ErrorInfo(
140
+ `unable to publish; unsupported input kind '${input.kind}'`,
141
+ ErrorCode.InvalidArgument,
142
+ 400,
143
+ );
144
+ }
145
+ await (descriptor.construct === 'event'
146
+ ? encodeEvent(descriptor, input, core, ctx)
147
+ : encodeBatch(descriptor, input, core, ctx));
148
+ },
149
+ };
150
+ };