@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,178 @@
1
+ /**
2
+ * useClientSession — read a ClientSession from the nearest ClientSessionProvider.
3
+ *
4
+ * The session is created by {@link ClientSessionProvider}, which reads the Ably
5
+ * Realtime client from the surrounding `<AblyProvider>`. This hook is a thin
6
+ * context reader — it does not create or manage session state.
7
+ *
8
+ * **Provider lookup**
9
+ * - Omit `channelName` to use the innermost `ClientSessionProvider` in the tree.
10
+ * - Pass `channelName` to look up a specific provider by name.
11
+ * - Pass `skip: true` to receive a stub session that throws on any access —
12
+ * safe to hold in state before auth or other conditions are ready.
13
+ *
14
+ * **Error handling**
15
+ * - When no matching provider is found, or when the provider's `createClientSession`
16
+ * call threw, `sessionError` is set on the returned object instead of throwing.
17
+ * The component can render an error state without an error boundary.
18
+ * - Pass `onError` to receive post-construction session errors (e.g. send failures,
19
+ * channel continuity loss) without wiring `session.on('error', ...)` manually.
20
+ */
21
+
22
+ import * as Ably from 'ably';
23
+ import { useContext, useEffect, useRef } from 'react';
24
+
25
+ import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
26
+ import type { ClientSession } from '../core/transport/types.js';
27
+ import { ErrorCode } from '../errors.js';
28
+ import { ClientSessionContext } from './contexts/client-session-context.js';
29
+ import { makeSkippedClientSession } from './internal/skipped-session.js';
30
+
31
+ /**
32
+ * Return value of {@link useClientSession}.
33
+ *
34
+ * `session` is always a valid object. When `skip` is `true`, when no provider was
35
+ * found, or when the provider's session construction failed, `session` is a stub
36
+ * that throws {@link Ably.ErrorInfo} on every access.
37
+ * Check `sessionError` before using `session` to avoid those throws.
38
+ */
39
+ export interface ClientSessionHandle<
40
+ TInput extends CodecInputEvent,
41
+ TOutput extends CodecOutputEvent,
42
+ TProjection,
43
+ TMessage,
44
+ > {
45
+ /**
46
+ * The resolved session.
47
+ *
48
+ * A throwing stub when `skip` is `true`, when no matching {@link ClientSessionProvider}
49
+ * was found in the tree, or when session construction failed.
50
+ */
51
+ session: ClientSession<TInput, TOutput, TProjection, TMessage>;
52
+ /**
53
+ * Set when no matching {@link ClientSessionProvider} was found, when session
54
+ * construction failed, and `skip` is `false`.
55
+ * `undefined` when the session resolved successfully or when `skip` is `true`.
56
+ */
57
+ sessionError?: Ably.ErrorInfo | undefined;
58
+ }
59
+
60
+ /**
61
+ * Read a {@link ClientSession} from the nearest {@link ClientSessionProvider}.
62
+ *
63
+ * Returns `{ session, sessionError }`. When no provider is found or session
64
+ * construction failed, `sessionError` is set and `session` is a stub that throws
65
+ * on access — the hook never throws during render.
66
+ *
67
+ * Pass `onError` to subscribe to post-construction session errors
68
+ * (e.g. {@link ErrorCode.SessionSendFailed}, {@link ErrorCode.ChannelContinuityLost})
69
+ * without calling `session.on('error', …)` manually. The subscription is
70
+ * created when the session resolves and removed on unmount.
71
+ * @param props - Hook options.
72
+ * @param props.channelName - Look up a specific provider by channel name; omit for the nearest.
73
+ * @param props.skip - When `true`, return the stub session immediately without resolving a provider slot.
74
+ * @param props.onError - Called whenever the resolved session emits an error event.
75
+ * @returns `{ session, sessionError }`.
76
+ */
77
+ export const useClientSession = <
78
+ TInput extends CodecInputEvent,
79
+ TOutput extends CodecOutputEvent,
80
+ TProjection,
81
+ TMessage,
82
+ >({
83
+ channelName,
84
+ skip,
85
+ onError,
86
+ }: {
87
+ /**
88
+ * Channel name passed to the enclosing {@link ClientSessionProvider}.
89
+ * Omit to use the nearest provider in the tree.
90
+ */
91
+ channelName?: string;
92
+ /**
93
+ * When `true`, skip context lookup and return a stub session that throws on
94
+ * any access. Use when a condition (auth, feature flag) is not yet resolved.
95
+ */
96
+ skip?: boolean;
97
+ /**
98
+ * Called whenever the resolved session emits an error event.
99
+ * The subscription is established once the session resolves and
100
+ * automatically removed on unmount or when the session changes.
101
+ */
102
+ onError?: (error: Ably.ErrorInfo) => void;
103
+ } = {}): ClientSessionHandle<TInput, TOutput, TProjection, TMessage> => {
104
+ const { nearest: nearestSlot, providers } = useContext(ClientSessionContext);
105
+ const errorCallbackRef = useRef(onError);
106
+ errorCallbackRef.current = onError;
107
+
108
+ // Compute the session for the onError subscription *before* any conditional
109
+ // returns to satisfy React's rules of hooks (no hooks in branches).
110
+ // Erased generics — this local is only used in the useEffect below.
111
+ const resolvedForEffect: ClientSession<CodecInputEvent, CodecOutputEvent, unknown, unknown> | undefined = skip
112
+ ? undefined
113
+ : channelName === undefined
114
+ ? nearestSlot?.session
115
+ : providers[channelName]?.session;
116
+
117
+ useEffect(() => {
118
+ if (!resolvedForEffect) return;
119
+ return resolvedForEffect.on('error', (errorInfo: Ably.ErrorInfo) => {
120
+ errorCallbackRef.current?.(errorInfo);
121
+ });
122
+ }, [resolvedForEffect]);
123
+
124
+ if (skip) {
125
+ return {
126
+ session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
127
+ };
128
+ }
129
+
130
+ if (channelName !== undefined) {
131
+ const slot = providers[channelName];
132
+ if (slot) {
133
+ if (slot.session) {
134
+ // CAST: ClientSessionContext stores sessions with erased generics.
135
+ // The caller is responsible for using type parameters matching those of the ClientSessionProvider.
136
+ return {
137
+ session: slot.session as unknown as ClientSession<TInput, TOutput, TProjection, TMessage>,
138
+ };
139
+ }
140
+ // Provider exists but construction failed.
141
+ return {
142
+ session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
143
+ sessionError: slot.sessionError,
144
+ };
145
+ }
146
+ return {
147
+ session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
148
+ sessionError: new Ably.ErrorInfo(
149
+ `unable to use session; no ClientSessionProvider found for channelName "${channelName}"`,
150
+ ErrorCode.BadRequest,
151
+ 400,
152
+ ),
153
+ };
154
+ }
155
+
156
+ if (nearestSlot) {
157
+ if (nearestSlot.session) {
158
+ // CAST: ClientSessionContext stores session with erased generics; types fixed at call site.
159
+ return {
160
+ session: nearestSlot.session as unknown as ClientSession<TInput, TOutput, TProjection, TMessage>,
161
+ };
162
+ }
163
+ // Nearest provider exists but construction failed.
164
+ return {
165
+ session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
166
+ sessionError: nearestSlot.sessionError,
167
+ };
168
+ }
169
+
170
+ return {
171
+ session: makeSkippedClientSession<TInput, TOutput, TProjection, TMessage>(),
172
+ sessionError: new Ably.ErrorInfo(
173
+ 'unable to use session; no ClientSessionProvider found in the tree',
174
+ ErrorCode.BadRequest,
175
+ 400,
176
+ ),
177
+ };
178
+ };
@@ -1,56 +1,60 @@
1
1
  /**
2
2
  * useCreateView — create an independent view with the same API as useView.
3
3
  *
4
- * Calls {@link ClientTransport.createView} to create an independent view over
4
+ * Calls {@link ClientSession.createView} to create an independent view over
5
5
  * the same conversation tree, then subscribes to it exactly like
6
6
  * {@link useView}. The view is closed automatically on unmount or when the
7
- * transport reference changes.
7
+ * session reference changes.
8
8
  *
9
- * Pass `null` or omit `transport` to defer creation (e.g. when a split pane is
10
- * collapsed). The returned handle has empty state until a transport is provided.
11
- * When `transport` is omitted entirely, defaults to the nearest
12
- * {@link TransportProvider}'s transport via context.
9
+ * Pass `null` or omit `session` to defer creation (e.g. when a split pane is
10
+ * collapsed). The returned handle has empty state until a session is provided.
11
+ * When `session` is omitted entirely, defaults to the nearest
12
+ * {@link ClientSessionProvider}'s session via context.
13
13
  * Pass `skip: true` to bypass all context reads and view creation entirely.
14
14
  */
15
15
 
16
- import { useContext, useEffect, useState } from 'react';
16
+ import { useEffect, useState } from 'react';
17
17
 
18
- import type { ClientTransport, View } from '../core/transport/types.js';
19
- import { NearestTransportContext } from './contexts/transport-context.js';
18
+ import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
19
+ import type { View } from '../core/transport/types.js';
20
+ import type { BaseSessionOption } from './internal/use-resolved-session.js';
21
+ import { useResolvedSession } from './internal/use-resolved-session.js';
20
22
  import type { ViewHandle } from './use-view.js';
21
23
  import { useView } from './use-view.js';
22
24
 
25
+ /** Options for {@link useCreateView}. */
26
+ export interface UseCreateViewOptions<
27
+ TInput extends CodecInputEvent,
28
+ TOutput extends CodecOutputEvent,
29
+ TProjection,
30
+ TMessage,
31
+ > extends BaseSessionOption<TInput, TOutput, TProjection, TMessage> {
32
+ /** When provided, auto-loads the first page on mount. Omit for manual load. */
33
+ limit?: number;
34
+ /** When `true`, skip view creation and return an empty handle immediately. */
35
+ skip?: boolean;
36
+ }
37
+
23
38
  /**
24
39
  * Create an independent {@link View} and subscribe to it.
25
40
  * Returns the same {@link ViewHandle} as {@link useView}, but backed by a
26
41
  * newly created view with its own branch selections and pagination state.
27
- * The view is closed on unmount or when the transport changes.
28
- * When `transport` is omitted, uses the nearest {@link TransportProvider}'s transport via context.
29
- * @param props - Options including optional `transport`, `limit` for auto-load, and `skip`.
30
- * @param props.transport - Transport to create a view from; defaults to the nearest provider.
42
+ * The view is closed on unmount or when the session changes.
43
+ * When `session` is omitted, uses the nearest {@link ClientSessionProvider}'s session via context.
44
+ * @param props - Options including optional `session`, `limit` for auto-load, and `skip`.
45
+ * @param props.session - Session to create a view from; defaults to the nearest provider.
31
46
  * @param props.limit - Max older messages per page; when provided, auto-loads on mount.
32
47
  * @param props.skip - When `true`, skip view creation and return an empty handle.
33
48
  * @returns A {@link ViewHandle} with nodes, pagination, navigation, and write operations.
34
49
  */
35
- export const useCreateView = <TEvent, TMessage>({
36
- transport,
50
+ export const useCreateView = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>({
51
+ session,
37
52
  limit,
38
53
  skip,
39
- }: {
40
- /** The transport to create a view from, or null/undefined to use the nearest provider. */
41
- transport?: ClientTransport<TEvent, TMessage> | null;
42
- /** When provided, auto-loads the first page on mount. Omit for manual load. */
43
- limit?: number;
44
- /** When `true`, skip view creation and return an empty handle immediately. */
45
- skip?: boolean;
46
- } = {}): ViewHandle<TEvent, TMessage> => {
47
- const nearestSlot = useContext(NearestTransportContext);
48
- // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.
49
- const resolved = skip
50
- ? undefined
51
- : ((transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | null | undefined);
54
+ }: UseCreateViewOptions<TInput, TOutput, TProjection, TMessage> = {}): ViewHandle<TInput, TMessage> => {
55
+ const resolved = useResolvedSession({ session, skip });
52
56
 
53
- const [view, setView] = useState<View<TEvent, TMessage> | undefined>();
57
+ const [view, setView] = useState<View<TInput, TMessage> | undefined>();
54
58
 
55
59
  useEffect(() => {
56
60
  if (!resolved) {
@@ -1,53 +1,84 @@
1
1
  /**
2
- * useTree — stable structural query callbacks for a ClientTransport's tree.
2
+ * useTree — stable structural query callbacks for a ClientSession's tree.
3
3
  *
4
4
  * Returns a {@link TreeHandle} with methods to inspect the tree structure.
5
- * These are thin `useCallback` wrappers around `transport.tree` — no local
5
+ * These are thin `useCallback` wrappers around `session.tree` — no local
6
6
  * state or subscriptions. Branch navigation (select, getSelectedIndex) is
7
7
  * on {@link ViewHandle} from {@link useView}.
8
8
  *
9
- * When `transport` is omitted, defaults to the nearest
10
- * {@link TransportProvider}'s transport via context.
9
+ * When `session` is omitted, defaults to the nearest
10
+ * {@link ClientSessionProvider}'s session via context.
11
11
  */
12
12
 
13
- import { useCallback, useContext } from 'react';
13
+ import { useCallback } from 'react';
14
14
 
15
- import type { ClientTransport, MessageNode } from '../core/transport/types.js';
16
- import { NearestTransportContext } from './contexts/transport-context.js';
15
+ import type { CodecInputEvent, CodecOutputEvent } from '../core/codec/types.js';
16
+ import type { ConversationNode, RunNode } from '../core/transport/types.js';
17
+ import type { BaseSessionOption } from './internal/use-resolved-session.js';
18
+ import { useResolvedSession } from './internal/use-resolved-session.js';
17
19
 
18
20
  /** Handle for querying the conversation tree structure. */
19
- export interface TreeHandle<TMessage> {
20
- /** Get all sibling messages at a fork point, ordered chronologically by serial. */
21
- getSiblings: (msgId: string) => TMessage[];
22
- /** Whether a message has sibling alternatives (i.e., show navigation arrows). */
23
- hasSiblings: (msgId: string) => boolean;
24
- /** Get a node by msgId, or undefined if not found. */
25
- getNode: (msgId: string) => MessageNode<TMessage> | undefined;
21
+ export interface TreeHandle<TProjection> {
22
+ /**
23
+ * Get the RunNode for `runId`, or undefined if no node is keyed by `runId`
24
+ * (or the keyed node is an input node, not a reply run).
25
+ */
26
+ getRunNode: (runId: string) => RunNode<TProjection> | undefined;
27
+ /**
28
+ * Get the node that owns a given codec-message-id, or undefined if not
29
+ * observed. Returns a {@link ConversationNode} union — narrow on `kind`
30
+ * (`'input'` vs `'run'`) before reading kind-specific fields.
31
+ */
32
+ getNodeByCodecMessageId: (codecMessageId: string) => ConversationNode<TProjection> | undefined;
33
+ /**
34
+ * Get the sibling group (both kinds) the node keyed by `key` belongs to —
35
+ * edit versions for an input node, regenerate runs for a reply run — ordered
36
+ * oldest-first. A single-element array when the node has no siblings; empty
37
+ * when `key` is unknown. `key` is a {@link RunNode.runId} or an
38
+ * {@link InputNode.codecMessageId}.
39
+ */
40
+ getSiblingNodes: (key: string) => ConversationNode<TProjection>[];
26
41
  }
27
42
 
43
+ /** Options for {@link useTree}. */
44
+ export type UseTreeOptions<
45
+ TInput extends CodecInputEvent,
46
+ TOutput extends CodecOutputEvent,
47
+ TProjection,
48
+ TMessage,
49
+ > = BaseSessionOption<TInput, TOutput, TProjection, TMessage>;
50
+
28
51
  /**
29
- * Provide stable structural query callbacks backed by the transport's tree.
30
- * When `transport` is omitted, uses the nearest {@link TransportProvider}'s transport via context.
31
- * @param props - Options including optional `transport`.
32
- * @param props.transport - Transport to read tree structure from; defaults to the nearest provider.
52
+ * Provide stable structural query callbacks backed by the session's tree.
53
+ * When `session` is omitted, uses the nearest {@link ClientSessionProvider}'s session via context.
54
+ * @param props - Options including optional `session`.
55
+ * @param props.session - Session to read tree structure from; defaults to the nearest provider.
33
56
  * @returns A {@link TreeHandle} with structural query methods.
34
57
  */
35
- export const useTree = <TEvent, TMessage>({
36
- transport,
37
- }: { transport?: ClientTransport<TEvent, TMessage> } = {}): TreeHandle<TMessage> => {
38
- const nearestSlot = useContext(NearestTransportContext);
39
- // CAST: NearestTransportContext stores transport with erased generics; types fixed at call site.
40
- const resolved = (transport ?? nearestSlot?.transport) as ClientTransport<TEvent, TMessage> | undefined;
58
+ export const useTree = <TInput extends CodecInputEvent, TOutput extends CodecOutputEvent, TProjection, TMessage>({
59
+ session,
60
+ }: UseTreeOptions<TInput, TOutput, TProjection, TMessage> = {}): TreeHandle<TProjection> => {
61
+ const resolved = useResolvedSession({ session });
41
62
 
42
- const getSiblings = useCallback((msgId: string) => resolved?.tree.getSiblings(msgId) ?? [], [resolved]);
63
+ const getRunNode = useCallback(
64
+ (runId: string): RunNode<TProjection> | undefined => resolved?.tree.getRunNode(runId),
65
+ [resolved],
66
+ );
43
67
 
44
- const hasSiblings = useCallback((msgId: string) => resolved?.tree.hasSiblings(msgId) ?? false, [resolved]);
68
+ const getNodeByCodecMessageId = useCallback(
69
+ (codecMessageId: string): ConversationNode<TProjection> | undefined =>
70
+ resolved?.tree.getNodeByCodecMessageId(codecMessageId),
71
+ [resolved],
72
+ );
45
73
 
46
- const getNode = useCallback((msgId: string) => resolved?.tree.getNode(msgId), [resolved]);
74
+ const getSiblingNodes = useCallback(
75
+ (key: string): ConversationNode<TProjection>[] => resolved?.tree.getSiblingNodes(key) ?? [],
76
+ [resolved],
77
+ );
47
78
 
48
79
  return {
49
- getSiblings,
50
- hasSiblings,
51
- getNode,
80
+ getRunNode,
81
+ getNodeByCodecMessageId,
82
+ getSiblingNodes,
52
83
  };
53
84
  };