@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,147 +0,0 @@
1
- /**
2
- * Server-side turn state management and lifecycle event publishing.
3
- *
4
- * Owns the authoritative turn lifecycle. Tracks active turns with their
5
- * AbortControllers and clientIds. Publishes turn-start and turn-end events
6
- * on the Ably channel so all clients can react to turn state changes.
7
- */
8
-
9
- import type * as Ably from 'ably';
10
-
11
- import {
12
- EVENT_TURN_END,
13
- EVENT_TURN_START,
14
- HEADER_TURN_CLIENT_ID,
15
- HEADER_TURN_ID,
16
- HEADER_TURN_REASON,
17
- } from '../../constants.js';
18
- import type { Logger } from '../../logger.js';
19
- import type { TurnEndReason } from './types.js';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Interface
23
- // ---------------------------------------------------------------------------
24
-
25
- /** Manages active turns and publishes turn lifecycle events on the channel. */
26
- export interface TurnManager {
27
- /** Register a new turn. Publishes turn-start on the channel. Returns AbortSignal. */
28
- startTurn(turnId: string, clientId?: string, controller?: AbortController): Promise<AbortSignal>;
29
- /** End a turn. Publishes turn-end on the channel. Cleans up internal state. */
30
- endTurn(turnId: string, reason: TurnEndReason): Promise<void>;
31
- /** Get the AbortSignal for a turn. */
32
- getSignal(turnId: string): AbortSignal | undefined;
33
- /** Get the clientId that owns a turn. */
34
- getClientId(turnId: string): string | undefined;
35
- /** Abort the signal for a turn. */
36
- abort(turnId: string): void;
37
- /** Get all active turn IDs. */
38
- getActiveTurnIds(): string[];
39
- /** Abort all active turns and clear state. */
40
- close(): void;
41
- }
42
-
43
- // ---------------------------------------------------------------------------
44
- // Internal state
45
- // ---------------------------------------------------------------------------
46
-
47
- interface TurnState {
48
- controller: AbortController;
49
- clientId: string;
50
- }
51
-
52
- // ---------------------------------------------------------------------------
53
- // Implementation
54
- // ---------------------------------------------------------------------------
55
-
56
- class DefaultTurnManager implements TurnManager {
57
- private readonly _channel: Ably.RealtimeChannel;
58
- private readonly _logger: Logger | undefined;
59
- private readonly _activeTurns = new Map<string, TurnState>();
60
-
61
- constructor(channel: Ably.RealtimeChannel, logger?: Logger) {
62
- this._channel = channel;
63
- this._logger = logger?.withContext({ component: 'TurnManager' });
64
- }
65
-
66
- async startTurn(turnId: string, clientId?: string, externalController?: AbortController): Promise<AbortSignal> {
67
- this._logger?.trace('DefaultTurnManager.startTurn();', { turnId, clientId });
68
-
69
- const controller = externalController ?? new AbortController();
70
- const resolvedClientId = clientId ?? '';
71
- this._activeTurns.set(turnId, { controller, clientId: resolvedClientId });
72
-
73
- await this._channel.publish({
74
- name: EVENT_TURN_START,
75
- extras: {
76
- headers: {
77
- [HEADER_TURN_ID]: turnId,
78
- [HEADER_TURN_CLIENT_ID]: resolvedClientId,
79
- },
80
- },
81
- });
82
-
83
- this._logger?.debug('DefaultTurnManager.startTurn(); turn started', { turnId });
84
- return controller.signal;
85
- }
86
-
87
- async endTurn(turnId: string, reason: TurnEndReason): Promise<void> {
88
- this._logger?.trace('DefaultTurnManager.endTurn();', { turnId, reason });
89
-
90
- const state = this._activeTurns.get(turnId);
91
- const resolvedClientId = state?.clientId ?? '';
92
-
93
- // Publish before deleting local state so that if publish fails,
94
- // the turn remains in the active set and can be retried or cleaned up.
95
- await this._channel.publish({
96
- name: EVENT_TURN_END,
97
- extras: {
98
- headers: {
99
- [HEADER_TURN_ID]: turnId,
100
- [HEADER_TURN_CLIENT_ID]: resolvedClientId,
101
- [HEADER_TURN_REASON]: reason,
102
- },
103
- },
104
- });
105
-
106
- this._activeTurns.delete(turnId);
107
- this._logger?.debug('DefaultTurnManager.endTurn(); turn ended', { turnId, reason });
108
- }
109
-
110
- getSignal(turnId: string): AbortSignal | undefined {
111
- return this._activeTurns.get(turnId)?.controller.signal;
112
- }
113
-
114
- getClientId(turnId: string): string | undefined {
115
- return this._activeTurns.get(turnId)?.clientId;
116
- }
117
-
118
- abort(turnId: string): void {
119
- this._logger?.debug('DefaultTurnManager.abort();', { turnId });
120
- this._activeTurns.get(turnId)?.controller.abort();
121
- }
122
-
123
- getActiveTurnIds(): string[] {
124
- return [...this._activeTurns.keys()];
125
- }
126
-
127
- close(): void {
128
- this._logger?.trace('DefaultTurnManager.close();', { activeTurns: this._activeTurns.size });
129
- for (const state of this._activeTurns.values()) {
130
- state.controller.abort();
131
- }
132
- this._activeTurns.clear();
133
- }
134
- }
135
-
136
- // ---------------------------------------------------------------------------
137
- // Factory
138
- // ---------------------------------------------------------------------------
139
-
140
- /**
141
- * Create a turn manager bound to the given channel.
142
- * @param channel - The Ably channel to publish lifecycle events on.
143
- * @param logger - Optional logger for diagnostic output.
144
- * @returns A new {@link TurnManager} instance.
145
- */
146
- export const createTurnManager = (channel: Ably.RealtimeChannel, logger?: Logger): TurnManager =>
147
- new DefaultTurnManager(channel, logger);
@@ -1,61 +0,0 @@
1
- /**
2
- * useActiveTurns: reactive view of active turns on the channel,
3
- * keyed by clientId.
4
- *
5
- * Subscribes to transport turn lifecycle events and maintains a
6
- * Map<clientId, Set<turnId>> that updates on every turn start/end.
7
- *
8
- * Generic — works with any codec, not tied to Vercel types.
9
- */
10
-
11
- import { useEffect, useState } from 'react';
12
-
13
- import { EVENT_TURN_START } from '../constants.js';
14
- import type { ClientTransport, TurnLifecycleEvent } from '../core/transport/types.js';
15
-
16
- /**
17
- * Returns a reactive Map of all active turns on the channel, keyed by clientId.
18
- * Updates when turns start or end.
19
- * @param transport - The client transport to observe, or null/undefined if not yet available.
20
- * @returns A Map where keys are clientIds and values are Sets of active turnIds.
21
- */
22
- export const useActiveTurns = <TEvent, TMessage>(
23
- transport: ClientTransport<TEvent, TMessage> | null | undefined,
24
- ): Map<string, Set<string>> => {
25
- const [turns, setTurns] = useState<Map<string, Set<string>>>(() => new Map());
26
-
27
- useEffect(() => {
28
- if (!transport) return;
29
-
30
- // Initialize from current state
31
- setTurns(transport.getActiveTurnIds());
32
-
33
- const unsubscribe = transport.on('turn', (event: TurnLifecycleEvent) => {
34
- setTurns((prev) => {
35
- const next = new Map(prev);
36
-
37
- if (event.type === EVENT_TURN_START) {
38
- const set = new Set(next.get(event.clientId) ?? []);
39
- set.add(event.turnId);
40
- next.set(event.clientId, set);
41
- } else {
42
- const set = next.get(event.clientId);
43
- if (set) {
44
- set.delete(event.turnId);
45
- if (set.size === 0) {
46
- next.delete(event.clientId);
47
- } else {
48
- next.set(event.clientId, new Set(set));
49
- }
50
- }
51
- }
52
-
53
- return next;
54
- });
55
- });
56
-
57
- return unsubscribe;
58
- }, [transport]);
59
-
60
- return turns;
61
- };
@@ -1,37 +0,0 @@
1
- /**
2
- * useClientTransport: creates and memoizes a core ClientTransport instance
3
- * across renders.
4
- *
5
- * Stores the instance in a ref so the same transport is returned on every render.
6
- * The transport manages its own Ably channel subscription in the constructor —
7
- * this hook adds no subscription logic.
8
- *
9
- * The hook does NOT auto-close the transport on unmount. Channel lifecycle is
10
- * managed by the Ably provider (useChannel), which detaches the channel and
11
- * clears all subscriptions. Auto-closing would break React Strict Mode
12
- * (double-mount calls close() on the first cleanup, leaving a dead transport
13
- * on the second mount). Call transport.close() explicitly if you need to tear
14
- * down the transport independently of the channel lifecycle.
15
- */
16
-
17
- import { useRef } from 'react';
18
-
19
- import { createClientTransport } from '../core/transport/client-transport.js';
20
- import type { ClientTransport, ClientTransportOptions } from '../core/transport/types.js';
21
-
22
- /**
23
- * Create and memoize a {@link ClientTransport} across renders.
24
- * @param options - Configuration for the client transport.
25
- * @returns The memoized transport instance.
26
- */
27
- export const useClientTransport = <TEvent, TMessage>(
28
- options: ClientTransportOptions<TEvent, TMessage>,
29
- ): ClientTransport<TEvent, TMessage> => {
30
- const transportRef = useRef<ClientTransport<TEvent, TMessage> | null>(null);
31
-
32
- if (transportRef.current === null) {
33
- transportRef.current = createClientTransport(options);
34
- }
35
-
36
- return transportRef.current;
37
- };
@@ -1,71 +0,0 @@
1
- /**
2
- * useConversationTree — reactive branch navigation for a ClientTransport.
3
- *
4
- * Subscribes to the transport's "message" notification and provides
5
- * branch navigation primitives (getSiblings, selectSibling, hasSiblings)
6
- * backed by the transport's ConversationTree.
7
- *
8
- * Branch selection state is local to the hook instance — each component
9
- * (or tab) can navigate branches independently.
10
- */
11
-
12
- import { useCallback, useEffect, useState } from 'react';
13
-
14
- import type { ClientTransport } from '../core/transport/types.js';
15
-
16
- /** Handle for navigating the branching conversation tree. */
17
- export interface ConversationTreeHandle<TMessage> {
18
- /** Linear message list for the currently selected branch. */
19
- messages: TMessage[];
20
- /** Get all sibling messages at a fork point. */
21
- getSiblings: (msgId: string) => TMessage[];
22
- /** Whether a message has siblings (should show navigation arrows). */
23
- hasSiblings: (msgId: string) => boolean;
24
- /** Index of the currently selected sibling. */
25
- getSelectedIndex: (msgId: string) => number;
26
- /** Navigate to a sibling. Triggers re-render with updated messages. */
27
- selectSibling: (msgId: string, index: number) => void;
28
- }
29
-
30
- /**
31
- * Subscribe to transport message updates and provide branch navigation primitives.
32
- * @param transport - The client transport whose conversation tree to navigate.
33
- * @returns A {@link ConversationTreeHandle} with the current messages and navigation methods.
34
- */
35
- export const useConversationTree = <TEvent, TMessage>(
36
- transport: ClientTransport<TEvent, TMessage>,
37
- ): ConversationTreeHandle<TMessage> => {
38
- const [messages, setMessages] = useState<TMessage[]>(() => transport.getMessages());
39
-
40
- useEffect(() => {
41
- setMessages(transport.getMessages());
42
-
43
- const unsub = transport.on('message', () => {
44
- setMessages(transport.getMessages());
45
- });
46
- return unsub;
47
- }, [transport]);
48
-
49
- const getSiblings = useCallback((msgId: string) => transport.getTree().getSiblings(msgId), [transport]);
50
-
51
- const hasSiblings = useCallback((msgId: string) => transport.getTree().hasSiblings(msgId), [transport]);
52
-
53
- const getSelectedIndex = useCallback((msgId: string) => transport.getTree().getSelectedIndex(msgId), [transport]);
54
-
55
- const selectSibling = useCallback(
56
- (msgId: string, index: number) => {
57
- transport.getTree().select(msgId, index);
58
- // flatten() returns a new array after select(), triggering re-render.
59
- setMessages(transport.getMessages());
60
- },
61
- [transport],
62
- );
63
-
64
- return {
65
- messages,
66
- getSiblings,
67
- hasSiblings,
68
- getSelectedIndex,
69
- selectSibling,
70
- };
71
- };
@@ -1,24 +0,0 @@
1
- /**
2
- * useEdit — stable callback for editing a user message.
3
- *
4
- * Delegates to `transport.edit()`, which automatically computes
5
- * `forkOf`, `parent`, and history from the conversation tree.
6
- */
7
-
8
- import { useCallback } from 'react';
9
-
10
- import type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';
11
-
12
- /**
13
- * Return a stable `edit` callback bound to the given transport.
14
- * @param transport - The client transport to edit through.
15
- * @returns A function that edits a user message and returns an {@link ActiveTurn} handle.
16
- */
17
- export const useEdit = <TEvent, TMessage>(
18
- transport: ClientTransport<TEvent, TMessage>,
19
- ): ((messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>
20
- useCallback(
21
- async (messageId: string, newMessages: TMessage | TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>> =>
22
- transport.edit(messageId, newMessages, options),
23
- [transport],
24
- );
@@ -1,111 +0,0 @@
1
- /**
2
- * useHistory — history pagination handle for a ClientTransport.
3
- *
4
- * Returns a `HistoryHandle` with `load()`, `next()`, `hasNext`, and
5
- * `loading` — mirroring the transport's `history()` and
6
- * `PaginatedMessages` API.
7
- *
8
- * The transport's `history()` is branch-aware: `limit` means "keep loading
9
- * until N new messages appear on the selected branch." Messages on
10
- * unselected branches are loaded into the tree but not counted toward the
11
- * limit. The returned `items` contain only the newly visible messages.
12
- *
13
- * When `options` are provided, auto-loads the first page on mount
14
- * (SWR-style: options present = enabled). When omitted or null,
15
- * no auto-load — call `load()` manually.
16
- *
17
- * Usage:
18
- * ```tsx
19
- * // Auto-load on mount
20
- * const history = useHistory(transport, { limit: 30 });
21
- *
22
- * // Manual load (e.g. on button press)
23
- * const history = useHistory(transport);
24
- * // ...later: await history.load({ limit: 30 });
25
- *
26
- * // Scroll-back
27
- * if (history.hasNext) await history.next();
28
- * ```
29
- */
30
-
31
- import { useCallback, useEffect, useRef, useState } from 'react';
32
-
33
- import type { ClientTransport, LoadHistoryOptions, PaginatedMessages } from '../core/transport/types.js';
34
-
35
- /** Handle for paginated history loading. */
36
- export interface HistoryHandle {
37
- /** Are there older pages available? False until `load()` has been called. */
38
- hasNext: boolean;
39
- /** Is a page being fetched? */
40
- loading: boolean;
41
- /** Load the first page (or re-load with different options). Inserts into the conversation tree. */
42
- load: (options?: LoadHistoryOptions) => Promise<void>;
43
- /** Fetch the next (older) page. No-op if loading or no more pages. Inserts into the conversation tree. */
44
- next: () => Promise<void>;
45
- }
46
-
47
- /**
48
- * Paginated history handle for a client transport.
49
- * @param transport - The client transport to load history from, or null/undefined if not yet available.
50
- * @param options - When provided, auto-loads the first page on mount. Omit or pass null for manual loading.
51
- * @returns A {@link HistoryHandle} for loading and paginating through history.
52
- */
53
- export const useHistory = <TEvent, TMessage>(
54
- transport: ClientTransport<TEvent, TMessage> | null | undefined,
55
- options?: LoadHistoryOptions | null,
56
- ): HistoryHandle => {
57
- const [hasNext, setHasNext] = useState(false);
58
- const [loading, setLoading] = useState(false);
59
- const loadingRef = useRef(false);
60
- const pageRef = useRef<PaginatedMessages<TMessage> | null>(null);
61
- const transportRef = useRef(transport);
62
- transportRef.current = transport;
63
-
64
- const load = useCallback(async (loadOptions?: LoadHistoryOptions) => {
65
- if (!transportRef.current || loadingRef.current) return;
66
- loadingRef.current = true;
67
- setLoading(true);
68
- try {
69
- const page = await transportRef.current.history(loadOptions);
70
- pageRef.current = page;
71
- setHasNext(page.hasNext());
72
- } finally {
73
- loadingRef.current = false;
74
- setLoading(false);
75
- }
76
- }, []);
77
-
78
- const next = useCallback(async () => {
79
- const page = pageRef.current;
80
- if (!page || !page.hasNext() || loadingRef.current || !transportRef.current) return;
81
-
82
- loadingRef.current = true;
83
- setLoading(true);
84
- try {
85
- const older = await page.next();
86
- if (older) {
87
- pageRef.current = older;
88
- setHasNext(older.hasNext());
89
- } else {
90
- setHasNext(false);
91
- }
92
- } finally {
93
- loadingRef.current = false;
94
- setLoading(false);
95
- }
96
- }, []);
97
-
98
- // Auto-load first page on mount when options are provided (SWR-style).
99
- const autoLoad = options !== undefined && options !== null;
100
- const autoLoadedRef = useRef(false);
101
- const optionsRef = useRef(options);
102
- optionsRef.current = options;
103
-
104
- useEffect(() => {
105
- if (!autoLoad || autoLoadedRef.current || !transportRef.current) return;
106
- autoLoadedRef.current = true;
107
- void load(optionsRef.current ?? undefined);
108
- }, [autoLoad, load]);
109
-
110
- return { hasNext, loading, load, next };
111
- };
@@ -1,32 +0,0 @@
1
- /**
2
- * useMessages — reactive message list from a ClientTransport.
3
- *
4
- * Subscribes to the transport's "message" notification and returns
5
- * the current message list as React state. Replaces the manual
6
- * useState + useEffect + on("message") + getMessages() pattern.
7
- */
8
-
9
- import { useEffect, useState } from 'react';
10
-
11
- import type { ClientTransport } from '../core/transport/types.js';
12
-
13
- /**
14
- * Subscribe to transport message updates and return the current message list.
15
- * @param transport - The client transport to observe.
16
- * @returns The current list of decoded messages.
17
- */
18
- export const useMessages = <TEvent, TMessage>(transport: ClientTransport<TEvent, TMessage>): TMessage[] => {
19
- const [messages, setMessages] = useState<TMessage[]>(() => transport.getMessages());
20
-
21
- useEffect(() => {
22
- // Sync initial state in case the transport already has messages
23
- setMessages(transport.getMessages());
24
-
25
- const unsub = transport.on('message', () => {
26
- setMessages(transport.getMessages());
27
- });
28
- return unsub;
29
- }, [transport]);
30
-
31
- return messages;
32
- };
@@ -1,24 +0,0 @@
1
- /**
2
- * useRegenerate — stable callback for regenerating an assistant message.
3
- *
4
- * Delegates to `transport.regenerate()`, which automatically computes
5
- * `forkOf`, `parent`, and truncated history from the conversation tree.
6
- */
7
-
8
- import { useCallback } from 'react';
9
-
10
- import type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';
11
-
12
- /**
13
- * Return a stable `regenerate` callback bound to the given transport.
14
- * @param transport - The client transport to regenerate through.
15
- * @returns A function that regenerates an assistant message and returns an {@link ActiveTurn} handle.
16
- */
17
- export const useRegenerate = <TEvent, TMessage>(
18
- transport: ClientTransport<TEvent, TMessage>,
19
- ): ((messageId: string, options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>
20
- useCallback(
21
- async (messageId: string, options?: SendOptions): Promise<ActiveTurn<TEvent>> =>
22
- transport.regenerate(messageId, options),
23
- [transport],
24
- );
@@ -1,25 +0,0 @@
1
- /**
2
- * useSend — stable callback for sending messages through a ClientTransport.
3
- *
4
- * Returns a `send` function that sends one or more messages in a single
5
- * turn via `transport.send()`. Callers construct the domain messages
6
- * themselves; the hook provides a stable reference suitable for React deps.
7
- */
8
-
9
- import { useCallback } from 'react';
10
-
11
- import type { ActiveTurn, ClientTransport, SendOptions } from '../core/transport/types.js';
12
-
13
- /**
14
- * Return a stable `send` callback bound to the given transport.
15
- * @param transport - The client transport to send through.
16
- * @returns A function that sends messages and returns an {@link ActiveTurn} handle.
17
- */
18
- export const useSend = <TEvent, TMessage>(
19
- transport: ClientTransport<TEvent, TMessage>,
20
- ): ((messages: TMessage[], options?: SendOptions) => Promise<ActiveTurn<TEvent>>) =>
21
- useCallback(
22
- async (messages: TMessage[], options?: SendOptions): Promise<ActiveTurn<TEvent>> =>
23
- transport.send(messages, options),
24
- [transport],
25
- );