@futurity/chat-react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @futurity/chat-react
2
+
3
+ React hooks and utilities for embedding Futurity chat in any React application.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @futurity/chat-react @futurity/chat-protocol react zod
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { useStreamChat } from "@futurity/chat-react";
15
+
16
+ function Chat({ chatId, apiUrl }: { chatId: string; apiUrl: string }) {
17
+ const {
18
+ messages,
19
+ setMessages,
20
+ sendMessage,
21
+ status,
22
+ stop,
23
+ isConnected,
24
+ pendingClarify,
25
+ sendClarifyResponse,
26
+ } = useStreamChat({
27
+ chatId,
28
+ wsUrl: `${apiUrl}/api/v2/chat/${chatId}`,
29
+ onStart: (id) => {
30
+ // A new assistant message is about to stream
31
+ setMessages((prev) => [
32
+ ...prev,
33
+ { id, role: "assistant", parts: [{ type: "text", text: "" }] },
34
+ ]);
35
+ },
36
+ onDelta: (id, delta, consolidated) => {
37
+ // Append or replace the latest part
38
+ setMessages((prev) =>
39
+ prev.map((m) => {
40
+ if (m.id !== id) return m;
41
+ const parts = m.parts ?? [];
42
+ return {
43
+ ...m,
44
+ parts: consolidated
45
+ ? [...parts.slice(0, -1), delta]
46
+ : [...parts, delta],
47
+ };
48
+ }),
49
+ );
50
+ },
51
+ onResume: (id, parts) => {
52
+ setMessages((prev) =>
53
+ prev.map((m) => (m.id !== id ? m : { ...m, parts })),
54
+ );
55
+ },
56
+ onHistory: (history) => {
57
+ setMessages(history.initialPath);
58
+ },
59
+ onError: (error) => {
60
+ console.error("Chat error:", error.message ?? error.error);
61
+ },
62
+ });
63
+
64
+ return (
65
+ <div>
66
+ {messages.map((msg) => (
67
+ <div key={msg.id}>
68
+ <strong>{msg.role}:</strong>
69
+ {msg.parts
70
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
71
+ .map((p, i) => (
72
+ <span key={i}>{p.text}</span>
73
+ ))}
74
+ </div>
75
+ ))}
76
+
77
+ {status === "streaming" && <button onClick={stop}>Stop</button>}
78
+
79
+ <form
80
+ onSubmit={async (e) => {
81
+ e.preventDefault();
82
+ const input = e.currentTarget.elements.namedItem("msg") as HTMLInputElement;
83
+ await sendMessage({ parts: [{ type: "text", text: input.value }] });
84
+ input.value = "";
85
+ }}
86
+ >
87
+ <input name="msg" placeholder="Type a message..." />
88
+ <button type="submit" disabled={status === "streaming"}>
89
+ Send
90
+ </button>
91
+ </form>
92
+ </div>
93
+ );
94
+ }
95
+ ```
96
+
97
+ ## API
98
+
99
+ ### `useStreamChat(options)`
100
+
101
+ The main hook for connecting to a Futurity chat WebSocket.
102
+
103
+ **Options:**
104
+
105
+ | Option | Type | Description |
106
+ | ---------- | ---------- | --------------------------------------------------------- |
107
+ | `chatId` | `string` | The chat ID to connect to |
108
+ | `wsUrl` | `string` | Full WebSocket URL (e.g. `wss://api.example.com/api/v2/chat/<id>`) |
109
+ | `onStart` | `function` | Called when a new assistant message starts |
110
+ | `onDelta` | `function` | Called on each stream delta |
111
+ | `onResume` | `function` | Called when a stream resumes with accumulated parts |
112
+ | `onFinish` | `function` | Called when streaming finishes |
113
+ | `onError` | `function` | Called on protocol errors |
114
+ | `onHistory`| `function` | Called when chat history is received |
115
+
116
+ **Returns:**
117
+
118
+ | Property | Type | Description |
119
+ | -------------------- | ----------------------- | -------------------------------------- |
120
+ | `messages` | `ChatMessage[]` | Current messages in the active branch |
121
+ | `setMessages` | `SetState<ChatMessage[]>` | Replace the message list |
122
+ | `sendMessage` | `(payload) => Promise` | Send a new user message |
123
+ | `injectMessage` | `(text) => Promise` | Inject text into an active stream |
124
+ | `status` | `ChatStatus` | `"ready"` / `"submitted"` / `"streaming"` / `"error"` |
125
+ | `stop` | `() => Promise` | Cancel the current generation |
126
+ | `reset` | `() => void` | Reset all chat state |
127
+ | `isConnected` | `boolean` | WebSocket connection status |
128
+ | `pendingClarify` | `object \| null` | Pending clarification request |
129
+ | `sendClarifyResponse`| `(answers) => void` | Submit clarification answers |
130
+
131
+ ### `useReconnectingWebSocket(options)`
132
+
133
+ A generic WebSocket hook with automatic reconnection, heartbeat, and message queuing. Used internally by `useStreamChat`, but also available for custom WebSocket connections.
134
+
135
+ ### Tree Utilities
136
+
137
+ - `buildTree(messages, byId)` - Build a navigable message tree from a flat array
138
+ - `findLatestPath(tree, byId)` - Find the latest conversation path in a tree
139
+ - `MessageNode` - Tree node class with `getChildren()`, `parent_id`, and `message`
140
+
141
+ ### Protocol Types
142
+
143
+ All chat WebSocket protocol types are re-exported from `@futurity/chat-protocol`:
144
+
145
+ ```ts
146
+ import type {
147
+ WsClientCommand,
148
+ WsServerMessage,
149
+ WsStreamMessage,
150
+ WsClarifyQuestion,
151
+ WsVaultItem,
152
+ } from "@futurity/chat-react";
153
+ ```
@@ -0,0 +1,414 @@
1
+ import { WsServerMessage, MessagePart, WsClarifyQuestion, WsVaultItem, WsStreamMessage, WsClarifyRequestMessage } from '@futurity/chat-protocol';
2
+ export { WsClientCommand as ClientCommand, MessagePart, WsClarifyQuestion, WsClientCommand, WsErrorMessage, WsServerMessage, WsStreamMessage, WsVaultItem } from '@futurity/chat-protocol';
3
+ import { z } from 'zod';
4
+
5
+ /**
6
+ * Chat WebSocket protocol helpers.
7
+ *
8
+ * All protocol schemas live in @futurity/chat-protocol;
9
+ * this module provides a lightweight parse helper for the SDK.
10
+ */
11
+
12
+ declare function parseServerMessage(data: unknown): WsServerMessage | null;
13
+
14
+ declare const messageMetadataSchema: z.ZodObject<{
15
+ parent_id: z.ZodNullable<z.ZodUUID>;
16
+ }, z.core.$strip>;
17
+ type MessageMetadata = z.infer<typeof messageMetadataSchema>;
18
+ /** A chat message suitable for rendering in a UI. */
19
+ type ChatMessage = {
20
+ id: string;
21
+ role: "user" | "assistant" | "system";
22
+ parts: MessagePart[];
23
+ createdAt?: Date;
24
+ metadata?: MessageMetadata;
25
+ };
26
+ declare const Z_ChatMessage: z.ZodObject<{
27
+ id: z.ZodString;
28
+ role: z.ZodEnum<{
29
+ user: "user";
30
+ assistant: "assistant";
31
+ system: "system";
32
+ }>;
33
+ parts: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
34
+ type: z.ZodLiteral<"text">;
35
+ text: z.ZodString;
36
+ state: z.ZodOptional<z.ZodEnum<{
37
+ streaming: "streaming";
38
+ done: "done";
39
+ }>>;
40
+ providerMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ type: z.ZodLiteral<"reasoning">;
43
+ text: z.ZodString;
44
+ state: z.ZodOptional<z.ZodEnum<{
45
+ streaming: "streaming";
46
+ done: "done";
47
+ }>>;
48
+ providerMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
49
+ }, z.core.$strip>, z.ZodObject<{
50
+ type: z.ZodLiteral<"source-url">;
51
+ sourceId: z.ZodString;
52
+ url: z.ZodString;
53
+ title: z.ZodOptional<z.ZodString>;
54
+ providerMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
55
+ }, z.core.$strip>, z.ZodObject<{
56
+ type: z.ZodLiteral<"source-document">;
57
+ sourceId: z.ZodString;
58
+ mediaType: z.ZodString;
59
+ title: z.ZodString;
60
+ filename: z.ZodOptional<z.ZodString>;
61
+ providerMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
62
+ }, z.core.$strip>, z.ZodObject<{
63
+ type: z.ZodLiteral<"file">;
64
+ mediaType: z.ZodString;
65
+ filename: z.ZodOptional<z.ZodString>;
66
+ url: z.ZodString;
67
+ providerMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
68
+ }, z.core.$strip>, z.ZodObject<{
69
+ type: z.ZodLiteral<"step-start">;
70
+ }, z.core.$strip>, z.ZodObject<{
71
+ type: z.ZodCustom<`data-${string}`, `data-${string}`>;
72
+ id: z.ZodOptional<z.ZodString>;
73
+ data: z.ZodUnknown;
74
+ }, z.core.$strip>, z.ZodIntersection<z.ZodObject<{
75
+ type: z.ZodCustom<`tool-${string}`, `tool-${string}`>;
76
+ }, z.core.$strip>, z.ZodDiscriminatedUnion<[z.ZodObject<{
77
+ toolCallId: z.ZodString;
78
+ title: z.ZodOptional<z.ZodString>;
79
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
80
+ state: z.ZodLiteral<"input-streaming">;
81
+ input: z.ZodUnknown;
82
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
83
+ }, z.core.$strip>, z.ZodObject<{
84
+ toolCallId: z.ZodString;
85
+ title: z.ZodOptional<z.ZodString>;
86
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
87
+ state: z.ZodLiteral<"input-available">;
88
+ input: z.ZodUnknown;
89
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ toolCallId: z.ZodString;
92
+ title: z.ZodOptional<z.ZodString>;
93
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
94
+ state: z.ZodLiteral<"approval-requested">;
95
+ input: z.ZodUnknown;
96
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
97
+ approval: z.ZodObject<{
98
+ id: z.ZodString;
99
+ }, z.core.$strip>;
100
+ }, z.core.$strip>, z.ZodObject<{
101
+ toolCallId: z.ZodString;
102
+ title: z.ZodOptional<z.ZodString>;
103
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
104
+ state: z.ZodLiteral<"approval-responded">;
105
+ input: z.ZodUnknown;
106
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
107
+ approval: z.ZodObject<{
108
+ id: z.ZodString;
109
+ approved: z.ZodBoolean;
110
+ reason: z.ZodOptional<z.ZodString>;
111
+ }, z.core.$strip>;
112
+ }, z.core.$strip>, z.ZodObject<{
113
+ toolCallId: z.ZodString;
114
+ title: z.ZodOptional<z.ZodString>;
115
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
116
+ state: z.ZodLiteral<"output-available">;
117
+ input: z.ZodUnknown;
118
+ output: z.ZodUnknown;
119
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
120
+ preliminary: z.ZodOptional<z.ZodBoolean>;
121
+ approval: z.ZodOptional<z.ZodObject<{
122
+ id: z.ZodString;
123
+ approved: z.ZodLiteral<true>;
124
+ reason: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strip>>;
126
+ }, z.core.$strip>, z.ZodObject<{
127
+ toolCallId: z.ZodString;
128
+ title: z.ZodOptional<z.ZodString>;
129
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
130
+ state: z.ZodLiteral<"output-error">;
131
+ input: z.ZodUnknown;
132
+ rawInput: z.ZodOptional<z.ZodUnknown>;
133
+ errorText: z.ZodString;
134
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
135
+ approval: z.ZodOptional<z.ZodObject<{
136
+ id: z.ZodString;
137
+ approved: z.ZodLiteral<true>;
138
+ reason: z.ZodOptional<z.ZodString>;
139
+ }, z.core.$strip>>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ toolCallId: z.ZodString;
142
+ title: z.ZodOptional<z.ZodString>;
143
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
144
+ state: z.ZodLiteral<"output-denied">;
145
+ input: z.ZodUnknown;
146
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
147
+ approval: z.ZodObject<{
148
+ id: z.ZodString;
149
+ approved: z.ZodLiteral<false>;
150
+ reason: z.ZodOptional<z.ZodString>;
151
+ }, z.core.$strip>;
152
+ }, z.core.$strip>], "state">>, z.ZodIntersection<z.ZodObject<{
153
+ type: z.ZodLiteral<"dynamic-tool">;
154
+ toolName: z.ZodString;
155
+ toolCallId: z.ZodString;
156
+ title: z.ZodOptional<z.ZodString>;
157
+ providerExecuted: z.ZodOptional<z.ZodBoolean>;
158
+ }, z.core.$strip>, z.ZodDiscriminatedUnion<[z.ZodObject<{
159
+ state: z.ZodLiteral<"input-streaming">;
160
+ input: z.ZodUnknown;
161
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ state: z.ZodLiteral<"input-available">;
164
+ input: z.ZodUnknown;
165
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
166
+ }, z.core.$strip>, z.ZodObject<{
167
+ state: z.ZodLiteral<"approval-requested">;
168
+ input: z.ZodUnknown;
169
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
170
+ approval: z.ZodObject<{
171
+ id: z.ZodString;
172
+ }, z.core.$strip>;
173
+ }, z.core.$strip>, z.ZodObject<{
174
+ state: z.ZodLiteral<"approval-responded">;
175
+ input: z.ZodUnknown;
176
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
177
+ approval: z.ZodObject<{
178
+ id: z.ZodString;
179
+ approved: z.ZodBoolean;
180
+ reason: z.ZodOptional<z.ZodString>;
181
+ }, z.core.$strip>;
182
+ }, z.core.$strip>, z.ZodObject<{
183
+ state: z.ZodLiteral<"output-available">;
184
+ input: z.ZodUnknown;
185
+ output: z.ZodUnknown;
186
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
187
+ preliminary: z.ZodOptional<z.ZodBoolean>;
188
+ approval: z.ZodOptional<z.ZodObject<{
189
+ id: z.ZodString;
190
+ approved: z.ZodLiteral<true>;
191
+ reason: z.ZodOptional<z.ZodString>;
192
+ }, z.core.$strip>>;
193
+ }, z.core.$strip>, z.ZodObject<{
194
+ state: z.ZodLiteral<"output-error">;
195
+ input: z.ZodUnknown;
196
+ errorText: z.ZodString;
197
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
198
+ approval: z.ZodOptional<z.ZodObject<{
199
+ id: z.ZodString;
200
+ approved: z.ZodLiteral<true>;
201
+ reason: z.ZodOptional<z.ZodString>;
202
+ }, z.core.$strip>>;
203
+ }, z.core.$strip>, z.ZodObject<{
204
+ state: z.ZodLiteral<"output-denied">;
205
+ input: z.ZodUnknown;
206
+ callProviderMetadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
207
+ approval: z.ZodObject<{
208
+ id: z.ZodString;
209
+ approved: z.ZodLiteral<false>;
210
+ reason: z.ZodOptional<z.ZodString>;
211
+ }, z.core.$strip>;
212
+ }, z.core.$strip>], "state">>]>>;
213
+ createdAt: z.ZodPipe<z.ZodTransform<Date | undefined, unknown>, z.ZodOptional<z.ZodDate>>;
214
+ metadata: z.ZodOptional<z.ZodObject<{
215
+ parent_id: z.ZodNullable<z.ZodUUID>;
216
+ }, z.core.$strip>>;
217
+ }, z.core.$loose>;
218
+ type SendMessagePayload = {
219
+ parts: MessagePart[];
220
+ metadata?: MessageMetadata;
221
+ /** Optional vault items to attach to the message. */
222
+ vaultItems?: WsVaultItem[];
223
+ };
224
+ type SendMessageFn = (payload: SendMessagePayload) => Promise<void>;
225
+ type StreamDelta = WsStreamMessage["delta"];
226
+ type ChatStatus = "ready" | "submitted" | "streaming" | "error";
227
+ type ClarifyData = WsClarifyQuestion[];
228
+
229
+ declare class MessageNode {
230
+ readonly id: string;
231
+ readonly parent_id: string | null;
232
+ readonly message: ChatMessage;
233
+ private children;
234
+ constructor(message: ChatMessage);
235
+ addChild(child: MessageNode): void;
236
+ getChildren(): readonly MessageNode[];
237
+ }
238
+ /**
239
+ * Builds a tree of MessageNodes from a flat list of messages using a two-pass approach.
240
+ * This ensures that children are connected even if the parent appears later in the array.
241
+ * The input messages SHOULD BE SORTED by `created_at` ASC for optimal performance.
242
+ * @param messages - An array of ChatMessage objects.
243
+ * @param byId - An empty Map that will be populated with all nodes by their ID.
244
+ * @returns An array of root MessageNode(s).
245
+ */
246
+ declare function buildTree(messages: ChatMessage[], byId: Map<string, MessageNode>): MessageNode[];
247
+ /**
248
+ * Finds the latest path in the conversation tree by following
249
+ * the leaf with the most recent `createdAt` timestamp.
250
+ */
251
+ declare function findLatestPath(tree: MessageNode[], byId: Map<string, MessageNode>): ChatMessage[];
252
+
253
+ type ConnectionState = "disconnected" | "connecting" | "connected";
254
+ type WebSocketConnectionOptions = {
255
+ /** WebSocket URL (can be relative, will be converted to ws/wss) */
256
+ url: string;
257
+ /** Called when a parsed message is received */
258
+ onMessage: (message: unknown) => void;
259
+ /** Called when connection state changes */
260
+ onConnectionChange?: (state: ConnectionState) => void;
261
+ /** Called when an error occurs */
262
+ onError?: (error: Event) => void;
263
+ /** Heartbeat interval in ms (default: 5000) */
264
+ heartbeatInterval?: number;
265
+ /** How long to wait for pong before considering connection dead (default: 10000) */
266
+ heartbeatTimeout?: number;
267
+ /** Initial reconnection delay in ms (default: 1000) */
268
+ initialReconnectDelay?: number;
269
+ /** Maximum reconnection delay in ms (default: 30000) */
270
+ maxReconnectDelay?: number;
271
+ /** Log prefix for debugging */
272
+ debugPrefix?: string;
273
+ };
274
+ /**
275
+ * Framework-agnostic WebSocket connection with automatic reconnection and heartbeat.
276
+ *
277
+ * Handles:
278
+ * - Connection lifecycle (connect / disconnect / ensureConnected)
279
+ * - Reconnection with exponential backoff
280
+ * - Heartbeat ping/pong to detect stale connections
281
+ * - Message queue while disconnected
282
+ * - JSON serialization on send, JSON parsing on receive
283
+ */
284
+ declare class WebSocketConnection {
285
+ private ws;
286
+ private reconnectTimeout;
287
+ private heartbeatIntervalTimer;
288
+ private heartbeatTimeoutTimer;
289
+ private reconnectDelay;
290
+ private pendingMessages;
291
+ private intentionalClose;
292
+ private awaitingPong;
293
+ private connectPromise;
294
+ private _state;
295
+ private readonly url;
296
+ private readonly heartbeatInterval;
297
+ private readonly heartbeatTimeout;
298
+ private readonly initialReconnectDelay;
299
+ private readonly maxReconnectDelay;
300
+ private readonly debugPrefix;
301
+ onMessage: (message: unknown) => void;
302
+ onConnectionChange?: (state: ConnectionState) => void;
303
+ onError?: (error: Event) => void;
304
+ get state(): ConnectionState;
305
+ get isConnected(): boolean;
306
+ constructor(options: WebSocketConnectionOptions);
307
+ private updateState;
308
+ private clearTimers;
309
+ private sendPing;
310
+ private startHeartbeat;
311
+ connect(): void;
312
+ disconnect(): void;
313
+ send(message: unknown): void;
314
+ ensureConnected(): Promise<boolean>;
315
+ reconnect(): void;
316
+ }
317
+
318
+ type WebSocketOptions<TServerMessage> = {
319
+ /** WebSocket URL (can be relative, will be converted to ws/wss) */
320
+ url: string;
321
+ /** Called when a message is received */
322
+ onMessage: (message: TServerMessage) => void;
323
+ /** Called when connection state changes */
324
+ onConnectionChange?: (state: ConnectionState) => void;
325
+ /** Called when an error occurs */
326
+ onError?: (error: Event) => void;
327
+ /** Whether the WebSocket should be enabled (default: true) */
328
+ enabled?: boolean;
329
+ /** Heartbeat interval in ms (default: 5000 = 5s) */
330
+ heartbeatInterval?: number;
331
+ /** How long to wait for pong before considering connection dead (default: 10000 = 10s) */
332
+ heartbeatTimeout?: number;
333
+ /** Initial reconnection delay in ms (default: 1000) */
334
+ initialReconnectDelay?: number;
335
+ /** Maximum reconnection delay in ms (default: 30000) */
336
+ maxReconnectDelay?: number;
337
+ /** Log prefix for debugging */
338
+ debugPrefix?: string;
339
+ };
340
+ type WebSocketResult<TClientCommand> = {
341
+ /** Current connection state */
342
+ connectionState: ConnectionState;
343
+ /** Whether the WebSocket is currently connected */
344
+ isConnected: boolean;
345
+ /** Send a message, auto-reconnecting if needed */
346
+ send: (message: TClientCommand) => void;
347
+ /** Ensure connection before performing an action */
348
+ ensureConnected: () => Promise<boolean>;
349
+ /** Force reconnection */
350
+ reconnect: () => void;
351
+ };
352
+ /**
353
+ * A React hook wrapping {@link WebSocketConnection} with lifecycle management.
354
+ *
355
+ * - Instantiates a `WebSocketConnection` in a ref
356
+ * - Manages connect/disconnect on mount/unmount and when `enabled` changes
357
+ * - Bridges connection events to React state and callback props
358
+ * - Exposes `send`, `ensureConnected`, `reconnect` with stable references
359
+ */
360
+ declare function useReconnectingWebSocket<TServerMessage, TClientCommand>({ url, onMessage, onConnectionChange, onError, enabled, heartbeatInterval, heartbeatTimeout, initialReconnectDelay, maxReconnectDelay, debugPrefix, }: WebSocketOptions<TServerMessage>): WebSocketResult<TClientCommand>;
361
+
362
+ type TransformedHistory = {
363
+ messages: ChatMessage[];
364
+ tree: MessageNode[];
365
+ byId: Map<string, MessageNode>;
366
+ initialPath: ChatMessage[];
367
+ activeMessageId?: string;
368
+ };
369
+ type UseStreamChatOptions = {
370
+ /** The chat ID to connect to. */
371
+ chatId: string;
372
+ /** Full WebSocket URL for the chat endpoint (e.g. `wss://api.example.com/api/v2/chat/<id>`). */
373
+ wsUrl: string;
374
+ /** Called when a new assistant message starts streaming. */
375
+ onStart?: (id: string) => void;
376
+ /** Called on each stream delta. */
377
+ onDelta?: (id: string, delta: StreamDelta, consolidated: boolean) => void;
378
+ /** Called when a stream resumes with accumulated parts. */
379
+ onResume?: (id: string, parts: MessagePart[]) => void;
380
+ /** Called when streaming finishes. */
381
+ onFinish?: () => void;
382
+ /** Called on a protocol error. */
383
+ onError?: (error: {
384
+ error?: string;
385
+ message?: string;
386
+ }) => void;
387
+ /** Called when chat history is received from the server. */
388
+ onHistory?: (history: TransformedHistory) => void;
389
+ };
390
+ type UseStreamChatReturn = {
391
+ /** Current list of messages in the active branch. */
392
+ messages: ChatMessage[];
393
+ /** Replace the messages list (e.g. for branch switching). */
394
+ setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
395
+ /** Send a new user message. */
396
+ sendMessage: (payload: SendMessagePayload) => Promise<void>;
397
+ /** Inject a user message into an active stream. */
398
+ injectMessage: (text: string) => Promise<void>;
399
+ /** Current chat status. */
400
+ status: ChatStatus;
401
+ /** Stop the current generation. */
402
+ stop: () => Promise<void>;
403
+ /** Reset the chat state. */
404
+ reset: () => void;
405
+ /** Whether the WebSocket is connected. */
406
+ isConnected: boolean;
407
+ /** Pending clarification request, if any. */
408
+ pendingClarify: WsClarifyRequestMessage["data"] | null;
409
+ /** Submit answers to a clarification request. */
410
+ sendClarifyResponse: (answers: Record<string, string>) => void;
411
+ };
412
+ declare function useStreamChat({ chatId, wsUrl, onStart, onDelta, onResume, onFinish, onError, onHistory, }: UseStreamChatOptions): UseStreamChatReturn;
413
+
414
+ export { type ChatMessage, type ChatStatus, type ClarifyData, type ConnectionState, type MessageMetadata, MessageNode, type SendMessageFn, type SendMessagePayload, type StreamDelta, type UseStreamChatOptions, type UseStreamChatReturn, WebSocketConnection, type WebSocketConnectionOptions, type WebSocketOptions, type WebSocketResult, Z_ChatMessage, buildTree, findLatestPath, messageMetadataSchema, parseServerMessage, useReconnectingWebSocket, useStreamChat };