@octavus/client-sdk 2.19.0 → 2.20.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.
@@ -0,0 +1,884 @@
1
+ import { StreamEvent, ToolResult, FileReference, UIMessage, OctavusError } from '@octavus/core';
2
+ export * from '@octavus/core';
3
+ export { AppError, ConflictError, ForbiddenError, MAIN_THREAD, NotFoundError, OCTAVUS_SKILL_TOOLS, OctavusError, ValidationError, createApiErrorEvent, createErrorEvent, createInternalErrorEvent, errorToStreamEvent, generateId, getSkillSlugFromToolCall, isAbortError, isAuthenticationError, isFileReference, isFileReferenceArray, isMainThread, isOctavusSkillTool, isOtherThread, isProviderError, isRateLimitError, isRetryableError, isToolError, isValidationError, resolveThread, safeParseStreamEvent, safeParseUIMessage, safeParseUIMessages, threadForPart } from '@octavus/core';
4
+
5
+ /**
6
+ * Options for trigger execution (e.g., retry with rollback).
7
+ */
8
+ interface TriggerOptions {
9
+ /**
10
+ * ID of the last message to keep in session state.
11
+ * Messages after this are removed before execution.
12
+ * Use `null` to truncate all messages (retry from empty history).
13
+ */
14
+ rollbackAfterMessageId?: string | null;
15
+ }
16
+ /**
17
+ * Transport interface for delivering events from server to client.
18
+ *
19
+ * Abstracts the connection mechanism (HTTP/SSE or WebSocket) behind a unified
20
+ * async iterator interface. Use `createHttpTransport` or `createSocketTransport`
21
+ * to create an implementation.
22
+ */
23
+ interface Transport {
24
+ /**
25
+ * Trigger the agent and stream events.
26
+ * @param triggerName - The trigger name defined in the agent's protocol
27
+ * @param input - Input parameters for variable substitution
28
+ * @param options - Optional trigger options (e.g., rollback for retry)
29
+ */
30
+ trigger(triggerName: string, input?: Record<string, unknown>, options?: TriggerOptions): AsyncIterable<StreamEvent>;
31
+ /**
32
+ * Continue execution with tool results after client-side tool handling.
33
+ *
34
+ * @param executionId - The execution ID from the client-tool-request event
35
+ * @param results - All tool results (server + client) to send
36
+ */
37
+ continueWithToolResults(executionId: string, results: ToolResult[]): AsyncIterable<StreamEvent>;
38
+ /**
39
+ * Observe an already-active execution without triggering a new one.
40
+ * Returns events from the current execution.
41
+ *
42
+ * Only applicable to transports where execution happens independently of the
43
+ * client connection (e.g., polling). HTTP and WebSocket transports do not need
44
+ * to implement this.
45
+ */
46
+ observe?(): AsyncIterable<StreamEvent>;
47
+ /** Stop the current stream. Safe to call when no stream is active. */
48
+ stop(): void;
49
+ }
50
+ /**
51
+ * Connection states for socket transport.
52
+ *
53
+ * - `disconnected`: Not connected (initial state or after disconnect)
54
+ * - `connecting`: Connection attempt in progress
55
+ * - `connected`: Successfully connected and ready
56
+ * - `error`: Connection failed (check error in listener callback)
57
+ */
58
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
59
+ /**
60
+ * Callback for connection state changes.
61
+ */
62
+ type ConnectionStateListener = (state: ConnectionState, error?: Error) => void;
63
+ /**
64
+ * Socket transport with connection management capabilities.
65
+ *
66
+ * Extends the base Transport interface with methods for managing persistent
67
+ * WebSocket/SockJS connections. Use this when you need:
68
+ * - Eager connection (connect before first message)
69
+ * - Connection status UI indicators
70
+ * - Manual connection lifecycle control
71
+ *
72
+ * Created via `createSocketTransport()`.
73
+ */
74
+ interface SocketTransport extends Transport {
75
+ /**
76
+ * Current connection state.
77
+ *
78
+ * - `disconnected`: Not connected (initial state)
79
+ * - `connecting`: Connection in progress
80
+ * - `connected`: Ready to send/receive
81
+ * - `error`: Connection failed
82
+ */
83
+ readonly connectionState: ConnectionState;
84
+ /**
85
+ * Subscribe to connection state changes.
86
+ *
87
+ * The listener is called immediately with the current state, then again
88
+ * whenever the state changes.
89
+ *
90
+ * @param listener - Callback invoked on state changes
91
+ * @returns Unsubscribe function
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const unsubscribe = transport.onConnectionStateChange((state, error) => {
96
+ * setConnectionState(state);
97
+ * if (error) setConnectionError(error);
98
+ * });
99
+ * ```
100
+ */
101
+ onConnectionStateChange(listener: ConnectionStateListener): () => void;
102
+ /**
103
+ * Eagerly establish the connection.
104
+ *
105
+ * By default, socket transport connects lazily on first `trigger()`. Call
106
+ * this method to establish the connection early (e.g., on component mount):
107
+ * - Faster first message response
108
+ * - Show accurate connection status in UI
109
+ * - Handle connection errors before user interaction
110
+ *
111
+ * Safe to call multiple times - resolves immediately if already connected.
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * useEffect(() => {
116
+ * transport.connect()
117
+ * .then(() => console.log('Connected'))
118
+ * .catch((err) => console.error('Failed:', err));
119
+ *
120
+ * return () => transport.disconnect();
121
+ * }, [transport]);
122
+ * ```
123
+ */
124
+ connect(): Promise<void>;
125
+ /**
126
+ * Close the connection and clean up resources.
127
+ *
128
+ * The transport will reconnect automatically on next `trigger()` call.
129
+ */
130
+ disconnect(): void;
131
+ }
132
+ /**
133
+ * Check if a transport is a SocketTransport with connection management.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * if (isSocketTransport(transport)) {
138
+ * transport.connect(); // TypeScript knows this is available
139
+ * }
140
+ * ```
141
+ */
142
+ declare function isSocketTransport(transport: Transport): transport is SocketTransport;
143
+
144
+ /**
145
+ * Response from the upload URLs endpoint
146
+ */
147
+ interface UploadUrlsResponse {
148
+ files: {
149
+ id: string;
150
+ uploadUrl: string;
151
+ downloadUrl: string;
152
+ }[];
153
+ }
154
+ /**
155
+ * Options for uploading files
156
+ */
157
+ interface UploadFilesOptions {
158
+ /**
159
+ * Function to request upload URLs from the platform.
160
+ * Consumer apps must implement this to authenticate with the platform.
161
+ *
162
+ * @param files - Array of file metadata to request URLs for
163
+ * @returns Response with presigned upload and download URLs
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * requestUploadUrls: async (files) => {
168
+ * const response = await fetch('/api/upload-urls', {
169
+ * method: 'POST',
170
+ * headers: { 'Content-Type': 'application/json' },
171
+ * body: JSON.stringify({ sessionId, files }),
172
+ * });
173
+ * return response.json();
174
+ * }
175
+ * ```
176
+ */
177
+ requestUploadUrls: (files: {
178
+ filename: string;
179
+ mediaType: string;
180
+ size: number;
181
+ }[]) => Promise<UploadUrlsResponse>;
182
+ /**
183
+ * Callback for upload progress (0-100 per file).
184
+ * Called multiple times during upload with real-time progress.
185
+ *
186
+ * @param fileIndex - Index of the file being uploaded
187
+ * @param progress - Progress percentage (0-100)
188
+ */
189
+ onProgress?: (fileIndex: number, progress: number) => void;
190
+ /** Upload timeout per file in milliseconds. Default: 60000 (60s). Set to 0 to disable. */
191
+ timeoutMs?: number;
192
+ /** Max retry attempts per file after initial failure. Default: 2. Set to 0 to disable retries. */
193
+ maxRetries?: number;
194
+ /** Delay between retries in milliseconds. Default: 1000 (1s). */
195
+ retryDelayMs?: number;
196
+ }
197
+ /**
198
+ * Upload files to the Octavus platform.
199
+ *
200
+ * This function:
201
+ * 1. Requests presigned upload URLs from the platform
202
+ * 2. Uploads each file directly to S3 with progress tracking
203
+ * 3. Returns file references that can be used in trigger input
204
+ *
205
+ * Uploads include automatic timeout (default 60s) and retry (default 2 retries)
206
+ * for transient failures like network errors or server issues.
207
+ *
208
+ * @param files - Files to upload (from file input or drag/drop)
209
+ * @param options - Upload configuration
210
+ * @returns Array of file references with download URLs
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * const fileRefs = await uploadFiles(fileInputRef.current.files, {
215
+ * requestUploadUrls: async (files) => {
216
+ * const response = await fetch('/api/upload-urls', {
217
+ * method: 'POST',
218
+ * headers: { 'Content-Type': 'application/json' },
219
+ * body: JSON.stringify({ sessionId, files }),
220
+ * });
221
+ * return response.json();
222
+ * },
223
+ * onProgress: (fileIndex, progress) => {
224
+ * console.log(`File ${fileIndex}: ${progress}%`);
225
+ * },
226
+ * });
227
+ * ```
228
+ */
229
+ declare function uploadFiles(files: FileList | File[], options: UploadFilesOptions): Promise<FileReference[]>;
230
+
231
+ type ChatStatus = 'idle' | 'streaming' | 'error' | 'awaiting-input';
232
+ /**
233
+ * Context provided to client tool handlers.
234
+ */
235
+ interface ClientToolContext {
236
+ /** Unique identifier for this tool call */
237
+ toolCallId: string;
238
+ /** Name of the tool being called */
239
+ toolName: string;
240
+ /** Signal for cancellation if user stops generation */
241
+ signal: AbortSignal;
242
+ /**
243
+ * Register a file produced by this tool (e.g., a screenshot).
244
+ * Files are sent to the platform alongside the tool result so the LLM
245
+ * can see them as visual content rather than just a JSON URL.
246
+ */
247
+ addFile: (file: FileReference) => void;
248
+ }
249
+ /**
250
+ * Handler function for client-side tool execution.
251
+ * Can be:
252
+ * - An async function that executes automatically and returns a result
253
+ * - The string 'interactive' to indicate the tool requires user interaction
254
+ */
255
+ type ClientToolHandler = ((args: Record<string, unknown>, ctx: ClientToolContext) => Promise<unknown>) | 'interactive';
256
+ /**
257
+ * Interactive tool call awaiting user interaction.
258
+ * The `submit` and `cancel` methods are pre-bound to this tool call's ID.
259
+ */
260
+ interface InteractiveTool {
261
+ /** Unique identifier for this tool call */
262
+ toolCallId: string;
263
+ /** Name of the tool being called */
264
+ toolName: string;
265
+ /** Arguments passed to the tool */
266
+ args: Record<string, unknown>;
267
+ /**
268
+ * Submit a result for this tool call.
269
+ * Call this when the user has provided input.
270
+ *
271
+ * @param result - The result from user interaction
272
+ */
273
+ submit: (result: unknown) => void;
274
+ /**
275
+ * Cancel this tool call with an optional reason.
276
+ * Call this when the user dismisses the UI without providing input.
277
+ *
278
+ * @param reason - Optional reason for cancellation (default: 'User cancelled')
279
+ */
280
+ cancel: (reason?: string) => void;
281
+ }
282
+ /**
283
+ * Input for creating a user message.
284
+ * Supports text content, structured object content, and file attachments.
285
+ */
286
+ interface UserMessageInput {
287
+ /**
288
+ * Content of the message. Can be:
289
+ * - string: Creates a text part
290
+ * - object: Creates an object part (uses `type` field as typeName if present)
291
+ */
292
+ content?: string | Record<string, unknown>;
293
+ /**
294
+ * File attachments (shorthand). Can be:
295
+ * - FileList: From file input element (will be uploaded via uploadFiles)
296
+ * - File[]: Array of File objects (will be uploaded via uploadFiles)
297
+ * - FileReference[]: Already uploaded files (used directly)
298
+ */
299
+ files?: FileList | File[] | FileReference[];
300
+ }
301
+ interface OctavusChatOptions {
302
+ /**
303
+ * Transport for streaming events.
304
+ * Use `createHttpTransport` for HTTP/SSE or `createSocketTransport` for WebSocket/SockJS.
305
+ */
306
+ transport: Transport;
307
+ /**
308
+ * Function to request upload URLs from the platform.
309
+ * Required if you want to use file uploads with FileList/File[].
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * requestUploadUrls: async (files) => {
314
+ * const response = await fetch('/api/upload-urls', {
315
+ * method: 'POST',
316
+ * headers: { 'Content-Type': 'application/json' },
317
+ * body: JSON.stringify({ sessionId, files }),
318
+ * });
319
+ * return response.json();
320
+ * }
321
+ * ```
322
+ */
323
+ requestUploadUrls?: UploadFilesOptions['requestUploadUrls'];
324
+ /** Upload timeout and retry configuration. Defaults: 60s timeout, 2 retries, 1s delay. */
325
+ uploadOptions?: Pick<UploadFilesOptions, 'timeoutMs' | 'maxRetries' | 'retryDelayMs'>;
326
+ /**
327
+ * Client-side tool handlers.
328
+ * Register handlers for tools that should execute in the browser.
329
+ *
330
+ * - If a tool has a handler function: executes automatically
331
+ * - If a tool is marked as 'interactive': appears in `pendingClientTools` with bound `submit()`/`cancel()`
332
+ *
333
+ * @example Automatic client tool
334
+ * ```typescript
335
+ * clientTools: {
336
+ * 'get-browser-location': async () => {
337
+ * const pos = await new Promise((resolve, reject) => {
338
+ * navigator.geolocation.getCurrentPosition(resolve, reject);
339
+ * });
340
+ * return { lat: pos.coords.latitude, lng: pos.coords.longitude };
341
+ * },
342
+ * }
343
+ * ```
344
+ *
345
+ * @example Interactive client tool (user input required)
346
+ * ```typescript
347
+ * clientTools: {
348
+ * 'request-feedback': 'interactive',
349
+ * }
350
+ * // Then render UI based on pendingClientTools['request-feedback']
351
+ * // and call tool.submit(result) or tool.cancel()
352
+ * ```
353
+ */
354
+ clientTools?: Record<string, ClientToolHandler>;
355
+ /** Initial messages (for session refresh) */
356
+ initialMessages?: UIMessage[];
357
+ /**
358
+ * Callback when an error occurs.
359
+ * Receives an OctavusError with structured error information.
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * onError: (error) => {
364
+ * console.error('Chat error:', {
365
+ * type: error.errorType,
366
+ * message: error.message,
367
+ * retryable: error.retryable,
368
+ * provider: error.provider,
369
+ * });
370
+ *
371
+ * // Handle specific error types
372
+ * if (isRateLimitError(error)) {
373
+ * showRetryButton(error.retryAfter);
374
+ * }
375
+ * }
376
+ * ```
377
+ */
378
+ onError?: (error: OctavusError) => void;
379
+ /** Callback when streaming finishes successfully */
380
+ onFinish?: () => void;
381
+ /** Callback when streaming is stopped by user */
382
+ onStop?: () => void;
383
+ /** Callback when a resource is updated */
384
+ onResourceUpdate?: (name: string, value: unknown) => void;
385
+ /**
386
+ * Callback when execution starts with the session/execution ID.
387
+ * Useful for tracking the current execution for activity logs.
388
+ *
389
+ * @example
390
+ * ```typescript
391
+ * onStart: (sessionId) => {
392
+ * setCurrentSessionId(sessionId);
393
+ * }
394
+ * ```
395
+ */
396
+ onStart?: (sessionId: string) => void;
397
+ }
398
+ type Listener = () => void;
399
+ /**
400
+ * Framework-agnostic chat client for Octavus agents.
401
+ * Manages chat state and streaming, allowing reactive frameworks to subscribe to updates.
402
+ *
403
+ * @example HTTP transport (Next.js, etc.)
404
+ * ```typescript
405
+ * import { OctavusChat, createHttpTransport } from '@octavus/client-sdk';
406
+ *
407
+ * const chat = new OctavusChat({
408
+ * transport: createHttpTransport({
409
+ * request: (payload, options) =>
410
+ * fetch('/api/trigger', {
411
+ * method: 'POST',
412
+ * headers: { 'Content-Type': 'application/json' },
413
+ * body: JSON.stringify({ sessionId, ...payload }),
414
+ * signal: options?.signal,
415
+ * }),
416
+ * }),
417
+ * });
418
+ * ```
419
+ *
420
+ * @example Socket transport (WebSocket, SockJS, Meteor)
421
+ * ```typescript
422
+ * import { OctavusChat, createSocketTransport } from '@octavus/client-sdk';
423
+ *
424
+ * const chat = new OctavusChat({
425
+ * transport: createSocketTransport({
426
+ * connect: () => new Promise((resolve, reject) => {
427
+ * const ws = new WebSocket(`wss://api.octavus.ai/stream?sessionId=${sessionId}`);
428
+ * ws.onopen = () => resolve(ws);
429
+ * ws.onerror = () => reject(new Error('Connection failed'));
430
+ * }),
431
+ * }),
432
+ * });
433
+ * ```
434
+ */
435
+ declare class OctavusChat {
436
+ private _messages;
437
+ private _status;
438
+ private _error;
439
+ private options;
440
+ private transport;
441
+ private streamingState;
442
+ private _pendingToolsByName;
443
+ private _pendingToolsByCallId;
444
+ private _pendingClientToolsCache;
445
+ private _completedToolResults;
446
+ private _clientToolAbortController;
447
+ private _serverToolResults;
448
+ private _pendingExecutionId;
449
+ private _readyToContinue;
450
+ private _finishEventReceived;
451
+ private _rollbackSynced;
452
+ private _lastTrigger;
453
+ private listeners;
454
+ constructor(options: OctavusChatOptions);
455
+ /**
456
+ * Update mutable options (callbacks and tool handlers) without recreating the instance.
457
+ * Used by the React hook to keep options fresh across renders, but can also be
458
+ * called directly by non-React consumers.
459
+ *
460
+ * `transport` and `initialMessages` are excluded since they're only consumed at construction time.
461
+ */
462
+ updateOptions(updates: Partial<Omit<OctavusChatOptions, 'transport' | 'initialMessages'>>): void;
463
+ get messages(): UIMessage[];
464
+ get status(): ChatStatus;
465
+ /**
466
+ * The current error, if any.
467
+ * Contains structured error information including type, source, and retryability.
468
+ */
469
+ get error(): OctavusError | null;
470
+ /**
471
+ * Pending interactive tool calls keyed by tool name.
472
+ * Each tool has bound `submit()` and `cancel()` methods.
473
+ *
474
+ * @example
475
+ * ```tsx
476
+ * const feedbackTools = pendingClientTools['request-feedback'] ?? [];
477
+ *
478
+ * {feedbackTools.map(tool => (
479
+ * <FeedbackModal
480
+ * key={tool.toolCallId}
481
+ * {...tool.args}
482
+ * onSubmit={(result) => tool.submit(result)}
483
+ * onCancel={() => tool.cancel()}
484
+ * />
485
+ * ))}
486
+ * ```
487
+ */
488
+ get pendingClientTools(): Record<string, InteractiveTool[]>;
489
+ /**
490
+ * Whether `retry()` can be called.
491
+ * True when a trigger has been sent and the chat is not currently streaming or awaiting input.
492
+ */
493
+ get canRetry(): boolean;
494
+ /**
495
+ * Replace the message list with externally-provided messages.
496
+ *
497
+ * Use this to sync with server-authoritative state (e.g., after an execution
498
+ * completes or when an observer detects new messages from another client).
499
+ *
500
+ * Must NOT be called while streaming — only when status is `idle` or `error`.
501
+ */
502
+ replaceMessages(messages: UIMessage[]): void;
503
+ /**
504
+ * Subscribe to state changes. The callback is called whenever messages, status, or error changes.
505
+ * @returns Unsubscribe function
506
+ */
507
+ subscribe(listener: Listener): () => void;
508
+ private notifyListeners;
509
+ private setMessages;
510
+ private setStatus;
511
+ private setError;
512
+ private updatePendingClientToolsCache;
513
+ /**
514
+ * Trigger the agent and optionally add a user message to the chat.
515
+ *
516
+ * @param triggerName - The trigger name defined in the agent's protocol.yaml
517
+ * @param input - Input parameters for the trigger (variable substitutions)
518
+ * @param options.userMessage - If provided, adds a user message to the chat before triggering
519
+ *
520
+ * @example Send a text message
521
+ * ```typescript
522
+ * await chat.send('user-message',
523
+ * { USER_MESSAGE: message },
524
+ * { userMessage: { content: message } }
525
+ * );
526
+ * ```
527
+ *
528
+ * @example Send a message with file attachments
529
+ * ```typescript
530
+ * await chat.send('user-message',
531
+ * { USER_MESSAGE: message, FILES: fileRefs },
532
+ * { userMessage: { content: message, files: fileRefs } }
533
+ * );
534
+ * ```
535
+ */
536
+ send(triggerName: string, input?: Record<string, unknown>, sendOptions?: {
537
+ userMessage?: UserMessageInput;
538
+ }): Promise<void>;
539
+ /**
540
+ * Retry the last trigger from the same starting point.
541
+ * Rolls back messages to the state before the last trigger, re-adds the user message
542
+ * (if any), and re-executes. Files are not re-uploaded.
543
+ *
544
+ * No-op if no trigger has been sent yet.
545
+ *
546
+ * @example
547
+ * ```typescript
548
+ * // After an error or unsatisfactory result
549
+ * if (chat.canRetry) {
550
+ * await chat.retry();
551
+ * }
552
+ * ```
553
+ */
554
+ retry(): Promise<void>;
555
+ /**
556
+ * Observe an already-active execution without triggering a new one.
557
+ *
558
+ * Only supported by transports that implement `observe()` (e.g., polling transport).
559
+ * Use this when the page loads and the session is already streaming — the transport
560
+ * will start consuming events without dispatching a new trigger.
561
+ *
562
+ * When using with `initialMessages`, exclude any in-progress assistant message
563
+ * from the initial messages to avoid duplication — the event stream will rebuild it.
564
+ */
565
+ observe(): Promise<void>;
566
+ private _executeTrigger;
567
+ /**
568
+ * Shared streaming logic for `send()`, `retry()`, and `observe()`.
569
+ * Sets up streaming state, consumes an event stream, and handles errors.
570
+ */
571
+ private _consumeStream;
572
+ /**
573
+ * Upload files directly without sending a message.
574
+ * Useful for showing upload progress before sending.
575
+ *
576
+ * @param files - Files to upload
577
+ * @param onProgress - Optional progress callback
578
+ * @returns Array of file references
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * const fileRefs = await chat.uploadFiles(fileInput.files, (i, progress) => {
583
+ * console.log(`File ${i}: ${progress}%`);
584
+ * });
585
+ * // Later...
586
+ * await chat.send('user-message', { FILES: fileRefs }, { userMessage: { files: fileRefs } });
587
+ * ```
588
+ */
589
+ uploadFiles(files: FileList | File[], onProgress?: (fileIndex: number, progress: number) => void): Promise<FileReference[]>;
590
+ /**
591
+ * Internal: Submit a result for a pending tool.
592
+ * Called by bound submit/cancel methods on InteractiveTool.
593
+ */
594
+ private submitToolResult;
595
+ /** Stop the current streaming and finalize any partial message */
596
+ stop(): void;
597
+ /**
598
+ * IMMUTABILITY RULES — all event handlers must follow these patterns:
599
+ *
600
+ * 1. Never mutate an existing part/message object. Some environments (e.g.
601
+ * React Native with Reanimated) freeze objects between renders, so
602
+ * mutations silently fail or throw.
603
+ *
604
+ * 2. Always create a new object via spread and assign it back:
605
+ * GOOD: state.parts[i] = { ...part, text: part.text + delta };
606
+ * BAD: part.text += delta; state.parts[i] = { ...part };
607
+ *
608
+ * 3. For nested worker parts, copy the parts array too:
609
+ * const updatedParts = [...workerPart.parts];
610
+ * updatedParts[i] = { ...part, status: 'done' };
611
+ * state.parts[wi] = { ...workerPart, parts: updatedParts };
612
+ *
613
+ * 4. For the messages array, copy before mutating:
614
+ * const messages = [...this._messages];
615
+ * messages[i] = newMessage; // or messages.pop()
616
+ * this.setMessages(messages);
617
+ */
618
+ private handleStreamEvent;
619
+ private updateStreamingMessage;
620
+ /**
621
+ * Emit a tool-output-available event for a client tool result.
622
+ */
623
+ private emitToolOutputAvailable;
624
+ /**
625
+ * Emit a tool-output-error event for a client tool result.
626
+ */
627
+ private emitToolOutputError;
628
+ /**
629
+ * Continue execution with collected client tool results.
630
+ */
631
+ private continueWithClientToolResults;
632
+ /**
633
+ * Handle client tool request event.
634
+ *
635
+ * IMPORTANT: Interactive tools must be registered synchronously (before any await)
636
+ * to avoid a race condition where the finish event is processed before tools are added.
637
+ */
638
+ private handleClientToolRequest;
639
+ }
640
+
641
+ /**
642
+ * Parse SSE stream events.
643
+ *
644
+ * @param response - The HTTP response with SSE body
645
+ * @param signal - Optional abort signal to cancel reading
646
+ */
647
+ declare function parseSSEStream(response: Response, signal?: AbortSignal): AsyncGenerator<StreamEvent, void, unknown>;
648
+
649
+ /** Start a new trigger execution */
650
+ interface TriggerRequest {
651
+ type: 'trigger';
652
+ triggerName: string;
653
+ input?: Record<string, unknown>;
654
+ rollbackAfterMessageId?: string | null;
655
+ }
656
+ /** Continue execution after client-side tool handling */
657
+ interface ContinueRequest {
658
+ type: 'continue';
659
+ executionId: string;
660
+ toolResults: ToolResult[];
661
+ }
662
+ /** All request types supported by the HTTP transport */
663
+ type HttpRequest = TriggerRequest | ContinueRequest;
664
+ /** Request options passed to the request callback */
665
+ interface HttpRequestOptions {
666
+ /** Abort signal to cancel the request */
667
+ signal?: AbortSignal;
668
+ }
669
+ /**
670
+ * Options for creating an HTTP transport.
671
+ */
672
+ interface HttpTransportOptions {
673
+ /**
674
+ * Function to make requests to your backend.
675
+ * Receives a discriminated union with `type` to identify the request kind.
676
+ *
677
+ * @param request - The request payload (check `request.type` for the kind)
678
+ * @param options - Request options including abort signal
679
+ * @returns Response with SSE stream body
680
+ *
681
+ * @example
682
+ * ```typescript
683
+ * request: (payload, options) =>
684
+ * fetch('/api/trigger', {
685
+ * method: 'POST',
686
+ * headers: { 'Content-Type': 'application/json' },
687
+ * body: JSON.stringify({ sessionId, ...payload }),
688
+ * signal: options?.signal,
689
+ * })
690
+ * ```
691
+ */
692
+ request: (request: HttpRequest, options?: HttpRequestOptions) => Promise<Response>;
693
+ }
694
+ /**
695
+ * Create an HTTP transport using native fetch() and SSE parsing.
696
+ * This is the default transport for Next.js and other HTTP-based applications.
697
+ *
698
+ * @example
699
+ * ```typescript
700
+ * const transport = createHttpTransport({
701
+ * request: (payload, options) =>
702
+ * fetch('/api/trigger', {
703
+ * method: 'POST',
704
+ * headers: { 'Content-Type': 'application/json' },
705
+ * body: JSON.stringify({ sessionId, ...payload }),
706
+ * signal: options?.signal,
707
+ * }),
708
+ * });
709
+ * ```
710
+ */
711
+ declare function createHttpTransport(options: HttpTransportOptions): Transport;
712
+
713
+ /**
714
+ * Socket interface compatible with both WebSocket and SockJS.
715
+ *
716
+ * Uses MessageEvent for the message handler, which both WebSocket and SockJS support.
717
+ * The `| undefined` union accommodates SockJS's optional property typing.
718
+ */
719
+ interface SocketLike {
720
+ send(data: string): void;
721
+ close(): void;
722
+ readyState: number;
723
+ onmessage: ((event: MessageEvent) => void) | null | undefined;
724
+ onclose: ((event: CloseEvent) => void) | null | undefined;
725
+ }
726
+ /**
727
+ * Options for creating a socket transport.
728
+ */
729
+ interface SocketTransportOptions {
730
+ /**
731
+ * Function to create and connect the socket.
732
+ * Works directly with WebSocket and SockJS - no wrappers needed.
733
+ *
734
+ * @example Native WebSocket
735
+ * ```typescript
736
+ * connect: () => new Promise((resolve, reject) => {
737
+ * const ws = new WebSocket('wss://api.example.com/stream?sessionId=xxx');
738
+ * ws.onopen = () => resolve(ws);
739
+ * ws.onerror = () => reject(new Error('Connection failed'));
740
+ * })
741
+ * ```
742
+ *
743
+ * @example SockJS
744
+ * ```typescript
745
+ * connect: () => new Promise((resolve, reject) => {
746
+ * const sock = new SockJS('/chat-service');
747
+ * sock.onopen = () => resolve(sock);
748
+ * sock.onerror = () => reject(new Error('Connection failed'));
749
+ * })
750
+ * ```
751
+ */
752
+ connect: () => Promise<SocketLike>;
753
+ /**
754
+ * Called for every message received (parsed as JSON).
755
+ * Use this to handle custom (non-Octavus) events.
756
+ * Octavus StreamEvents are handled automatically by the transport.
757
+ *
758
+ * @example
759
+ * ```typescript
760
+ * onMessage: (data) => {
761
+ * const msg = data as { type: string };
762
+ * if (msg.type === 'typing-indicator') {
763
+ * setIsTyping(true);
764
+ * }
765
+ * if (msg.type === 'presence-update') {
766
+ * updatePresence(msg.users);
767
+ * }
768
+ * }
769
+ * ```
770
+ */
771
+ onMessage?: (data: unknown) => void;
772
+ /**
773
+ * Called when the socket connection closes.
774
+ * Use this for cleanup or reconnection logic.
775
+ */
776
+ onClose?: () => void;
777
+ }
778
+ /**
779
+ * Create a socket transport that works with any WebSocket-like connection.
780
+ * Supports native WebSocket, SockJS, or any compatible socket implementation.
781
+ *
782
+ * The server should send StreamEvent format (same as SSE) over the socket.
783
+ * Unknown events are safely ignored using Zod validation.
784
+ *
785
+ * ## Connection Lifecycle
786
+ *
787
+ * By default, the socket connects **lazily** on the first `send()` call.
788
+ * Use `connect()` to establish the connection eagerly (e.g., on component mount):
789
+ *
790
+ * ```typescript
791
+ * // Eager connection for UI status indicators
792
+ * useEffect(() => {
793
+ * transport.connect()
794
+ * .then(() => console.log('Connected'))
795
+ * .catch((err) => console.error('Failed:', err));
796
+ *
797
+ * return () => transport.disconnect();
798
+ * }, [transport]);
799
+ * ```
800
+ *
801
+ * @example Basic usage with WebSocket
802
+ * ```typescript
803
+ * const transport = createSocketTransport({
804
+ * connect: () => new Promise((resolve, reject) => {
805
+ * const ws = new WebSocket(`wss://api.octavus.ai/stream?sessionId=${sessionId}`);
806
+ * ws.onopen = () => resolve(ws);
807
+ * ws.onerror = () => reject(new Error('Connection failed'));
808
+ * }),
809
+ * });
810
+ * ```
811
+ *
812
+ * @example With SockJS and connection state
813
+ * ```typescript
814
+ * const transport = createSocketTransport({
815
+ * connect: () => new Promise((resolve, reject) => {
816
+ * const sock = new SockJS('/octavus-stream');
817
+ * sock.onopen = () => resolve(sock);
818
+ * sock.onerror = () => reject(new Error('Connection failed'));
819
+ * }),
820
+ * });
821
+ *
822
+ * // Subscribe to connection state changes
823
+ * transport.onConnectionStateChange((state, error) => {
824
+ * setConnectionState(state);
825
+ * if (error) setConnectionError(error);
826
+ * });
827
+ * ```
828
+ */
829
+ declare function createSocketTransport(options: SocketTransportOptions): SocketTransport;
830
+
831
+ interface PollResult {
832
+ events: unknown[];
833
+ cursor: number;
834
+ status: 'idle' | 'streaming' | 'error';
835
+ }
836
+ /**
837
+ * Options for creating a polling transport.
838
+ */
839
+ interface PollingTransportOptions {
840
+ /**
841
+ * Dispatch a trigger to the backend.
842
+ * Called when `send()` initiates a new execution.
843
+ * Return `{ error }` to surface the error to the chat.
844
+ */
845
+ onTrigger: (triggerName: string, input?: Record<string, unknown>) => Promise<{
846
+ error?: string;
847
+ }>;
848
+ /**
849
+ * Poll for execution events.
850
+ * Called repeatedly during streaming at `pollIntervalMs` intervals.
851
+ * The cursor tracks read position — pass 0 on first call.
852
+ */
853
+ onPoll: (cursor: number) => Promise<PollResult>;
854
+ /** Called when the user stops the execution. */
855
+ onStop: () => void;
856
+ /** Milliseconds between poll calls. Defaults to 500. */
857
+ pollIntervalMs?: number;
858
+ }
859
+ /**
860
+ * Create a polling transport for backends that use a poll-based event delivery
861
+ * model instead of SSE or WebSocket streaming.
862
+ *
863
+ * The transport dispatches a trigger via `onTrigger`, then polls `onPoll` at
864
+ * a fixed interval, yielding events as they arrive. From `OctavusChat`'s
865
+ * perspective this looks identical to an SSE stream.
866
+ *
867
+ * Supports `observe()` for resuming observation of an already-active execution
868
+ * (e.g., after a page refresh while the agent is streaming). `observe()` skips
869
+ * the trigger and goes straight to polling.
870
+ *
871
+ * @example
872
+ * ```typescript
873
+ * const transport = createPollingTransport({
874
+ * onTrigger: (triggerName, input) => triggerServerAction(input?.USER_MESSAGE),
875
+ * onPoll: (cursor) => pollServerAction(cursor),
876
+ * onStop: () => stopServerAction(),
877
+ * });
878
+ *
879
+ * const { send, messages } = useOctavusChat({ transport });
880
+ * ```
881
+ */
882
+ declare function createPollingTransport(options: PollingTransportOptions): Transport;
883
+
884
+ export { type ChatStatus, type ClientToolContext, type ClientToolHandler, type ConnectionState, type ConnectionStateListener, type ContinueRequest, type HttpRequest, type HttpRequestOptions, type HttpTransportOptions, type InteractiveTool, OctavusChat, type OctavusChatOptions, type PollResult, type PollingTransportOptions, type SocketLike, type SocketTransport, type SocketTransportOptions, type Transport, type TriggerOptions, type TriggerRequest, type UploadFilesOptions, type UploadUrlsResponse, type UserMessageInput, createHttpTransport, createPollingTransport, createSocketTransport, isSocketTransport, parseSSEStream, uploadFiles };