@octavus/client-sdk 1.0.0 → 2.1.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.
package/README.md CHANGED
@@ -21,11 +21,11 @@ import { OctavusChat, createHttpTransport } from '@octavus/client-sdk';
21
21
 
22
22
  // Create transport
23
23
  const transport = createHttpTransport({
24
- triggerRequest: (triggerName, input, options) =>
25
- fetch('/api/octavus', {
24
+ request: (payload, options) =>
25
+ fetch('/api/trigger', {
26
26
  method: 'POST',
27
27
  headers: { 'Content-Type': 'application/json' },
28
- body: JSON.stringify({ sessionId, triggerName, input }),
28
+ body: JSON.stringify({ sessionId, ...payload }),
29
29
  signal: options?.signal,
30
30
  }),
31
31
  });
@@ -56,11 +56,11 @@ Best for Next.js, Express, and HTTP-based applications:
56
56
  import { createHttpTransport } from '@octavus/client-sdk';
57
57
 
58
58
  const transport = createHttpTransport({
59
- triggerRequest: (triggerName, input, options) =>
60
- fetch('/api/octavus', {
59
+ request: (payload, options) =>
60
+ fetch('/api/trigger', {
61
61
  method: 'POST',
62
62
  headers: { 'Content-Type': 'application/json' },
63
- body: JSON.stringify({ sessionId, triggerName, input }),
63
+ body: JSON.stringify({ sessionId, ...payload }),
64
64
  signal: options?.signal,
65
65
  }),
66
66
  });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { StreamEvent, FileReference, UIMessage, OctavusError } from '@octavus/core';
1
+ import { StreamEvent, ToolResult, FileReference, UIMessage, OctavusError } from '@octavus/core';
2
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';
3
4
 
4
5
  /**
5
6
  * Transport interface for delivering events from server to client.
@@ -15,6 +16,13 @@ interface Transport {
15
16
  * @param input - Input parameters for variable substitution
16
17
  */
17
18
  trigger(triggerName: string, input?: Record<string, unknown>): AsyncIterable<StreamEvent>;
19
+ /**
20
+ * Continue execution with tool results after client-side tool handling.
21
+ *
22
+ * @param executionId - The execution ID from the client-tool-request event
23
+ * @param results - All tool results (server + client) to send
24
+ */
25
+ continueWithToolResults(executionId: string, results: ToolResult[]): AsyncIterable<StreamEvent>;
18
26
  /** Stop the current stream. Safe to call when no stream is active. */
19
27
  stop(): void;
20
28
  }
@@ -190,7 +198,51 @@ interface UploadFilesOptions {
190
198
  */
191
199
  declare function uploadFiles(files: FileList | File[], options: UploadFilesOptions): Promise<FileReference[]>;
192
200
 
193
- type ChatStatus = 'idle' | 'streaming' | 'error';
201
+ type ChatStatus = 'idle' | 'streaming' | 'error' | 'awaiting-input';
202
+ /**
203
+ * Context provided to client tool handlers.
204
+ */
205
+ interface ClientToolContext {
206
+ /** Unique identifier for this tool call */
207
+ toolCallId: string;
208
+ /** Name of the tool being called */
209
+ toolName: string;
210
+ /** Signal for cancellation if user stops generation */
211
+ signal: AbortSignal;
212
+ }
213
+ /**
214
+ * Handler function for client-side tool execution.
215
+ * Can be:
216
+ * - An async function that executes automatically and returns a result
217
+ * - The string 'interactive' to indicate the tool requires user interaction
218
+ */
219
+ type ClientToolHandler = ((args: Record<string, unknown>, ctx: ClientToolContext) => Promise<unknown>) | 'interactive';
220
+ /**
221
+ * Interactive tool call awaiting user interaction.
222
+ * The `submit` and `cancel` methods are pre-bound to this tool call's ID.
223
+ */
224
+ interface InteractiveTool {
225
+ /** Unique identifier for this tool call */
226
+ toolCallId: string;
227
+ /** Name of the tool being called */
228
+ toolName: string;
229
+ /** Arguments passed to the tool */
230
+ args: Record<string, unknown>;
231
+ /**
232
+ * Submit a result for this tool call.
233
+ * Call this when the user has provided input.
234
+ *
235
+ * @param result - The result from user interaction
236
+ */
237
+ submit: (result: unknown) => void;
238
+ /**
239
+ * Cancel this tool call with an optional reason.
240
+ * Call this when the user dismisses the UI without providing input.
241
+ *
242
+ * @param reason - Optional reason for cancellation (default: 'User cancelled')
243
+ */
244
+ cancel: (reason?: string) => void;
245
+ }
194
246
  /**
195
247
  * Input for creating a user message.
196
248
  * Supports text content, structured object content, and file attachments.
@@ -233,6 +285,35 @@ interface OctavusChatOptions {
233
285
  * ```
234
286
  */
235
287
  requestUploadUrls?: UploadFilesOptions['requestUploadUrls'];
288
+ /**
289
+ * Client-side tool handlers.
290
+ * Register handlers for tools that should execute in the browser.
291
+ *
292
+ * - If a tool has a handler function: executes automatically
293
+ * - If a tool is marked as 'interactive': appears in `pendingClientTools` with bound `submit()`/`cancel()`
294
+ *
295
+ * @example Automatic client tool
296
+ * ```typescript
297
+ * clientTools: {
298
+ * 'get-browser-location': async () => {
299
+ * const pos = await new Promise((resolve, reject) => {
300
+ * navigator.geolocation.getCurrentPosition(resolve, reject);
301
+ * });
302
+ * return { lat: pos.coords.latitude, lng: pos.coords.longitude };
303
+ * },
304
+ * }
305
+ * ```
306
+ *
307
+ * @example Interactive client tool (user input required)
308
+ * ```typescript
309
+ * clientTools: {
310
+ * 'request-feedback': 'interactive',
311
+ * }
312
+ * // Then render UI based on pendingClientTools['request-feedback']
313
+ * // and call tool.submit(result) or tool.cancel()
314
+ * ```
315
+ */
316
+ clientTools?: Record<string, ClientToolHandler>;
236
317
  /** Initial messages (for session refresh) */
237
318
  initialMessages?: UIMessage[];
238
319
  /**
@@ -275,10 +356,12 @@ type Listener = () => void;
275
356
  *
276
357
  * const chat = new OctavusChat({
277
358
  * transport: createHttpTransport({
278
- * triggerRequest: (triggerName, input) =>
359
+ * request: (payload, options) =>
279
360
  * fetch('/api/trigger', {
280
361
  * method: 'POST',
281
- * body: JSON.stringify({ sessionId, triggerName, input }),
362
+ * headers: { 'Content-Type': 'application/json' },
363
+ * body: JSON.stringify({ sessionId, ...payload }),
364
+ * signal: options?.signal,
282
365
  * }),
283
366
  * }),
284
367
  * });
@@ -306,6 +389,14 @@ declare class OctavusChat {
306
389
  private options;
307
390
  private transport;
308
391
  private streamingState;
392
+ private _pendingToolsByName;
393
+ private _pendingToolsByCallId;
394
+ private _pendingClientToolsCache;
395
+ private _completedToolResults;
396
+ private _clientToolAbortController;
397
+ private _serverToolResults;
398
+ private _pendingExecutionId;
399
+ private _readyToContinue;
309
400
  private listeners;
310
401
  constructor(options: OctavusChatOptions);
311
402
  get messages(): UIMessage[];
@@ -315,6 +406,25 @@ declare class OctavusChat {
315
406
  * Contains structured error information including type, source, and retryability.
316
407
  */
317
408
  get error(): OctavusError | null;
409
+ /**
410
+ * Pending interactive tool calls keyed by tool name.
411
+ * Each tool has bound `submit()` and `cancel()` methods.
412
+ *
413
+ * @example
414
+ * ```tsx
415
+ * const feedbackTools = pendingClientTools['request-feedback'] ?? [];
416
+ *
417
+ * {feedbackTools.map(tool => (
418
+ * <FeedbackModal
419
+ * key={tool.toolCallId}
420
+ * {...tool.args}
421
+ * onSubmit={(result) => tool.submit(result)}
422
+ * onCancel={() => tool.cancel()}
423
+ * />
424
+ * ))}
425
+ * ```
426
+ */
427
+ get pendingClientTools(): Record<string, InteractiveTool[]>;
318
428
  /**
319
429
  * Subscribe to state changes. The callback is called whenever messages, status, or error changes.
320
430
  * @returns Unsubscribe function
@@ -324,6 +434,7 @@ declare class OctavusChat {
324
434
  private setMessages;
325
435
  private setStatus;
326
436
  private setError;
437
+ private updatePendingClientToolsCache;
327
438
  /**
328
439
  * Trigger the agent and optionally add a user message to the chat.
329
440
  *
@@ -368,10 +479,34 @@ declare class OctavusChat {
368
479
  * ```
369
480
  */
370
481
  uploadFiles(files: FileList | File[], onProgress?: (fileIndex: number, progress: number) => void): Promise<FileReference[]>;
482
+ /**
483
+ * Internal: Submit a result for a pending tool.
484
+ * Called by bound submit/cancel methods on InteractiveTool.
485
+ */
486
+ private submitToolResult;
371
487
  /** Stop the current streaming and finalize any partial message */
372
488
  stop(): void;
373
489
  private handleStreamEvent;
374
490
  private updateStreamingMessage;
491
+ /**
492
+ * Emit a tool-output-available event for a client tool result.
493
+ */
494
+ private emitToolOutputAvailable;
495
+ /**
496
+ * Emit a tool-output-error event for a client tool result.
497
+ */
498
+ private emitToolOutputError;
499
+ /**
500
+ * Continue execution with collected client tool results.
501
+ */
502
+ private continueWithClientToolResults;
503
+ /**
504
+ * Handle client tool request event.
505
+ *
506
+ * IMPORTANT: Interactive tools must be registered synchronously (before any await)
507
+ * to avoid a race condition where the finish event is processed before tools are added.
508
+ */
509
+ private handleClientToolRequest;
375
510
  }
376
511
 
377
512
  /**
@@ -382,10 +517,22 @@ declare class OctavusChat {
382
517
  */
383
518
  declare function parseSSEStream(response: Response, signal?: AbortSignal): AsyncGenerator<StreamEvent, void, unknown>;
384
519
 
385
- /**
386
- * Request options passed to the triggerRequest function.
387
- */
388
- interface TriggerRequestOptions {
520
+ /** Start a new trigger execution */
521
+ interface TriggerRequest {
522
+ type: 'trigger';
523
+ triggerName: string;
524
+ input?: Record<string, unknown>;
525
+ }
526
+ /** Continue execution after client-side tool handling */
527
+ interface ContinueRequest {
528
+ type: 'continue';
529
+ executionId: string;
530
+ toolResults: ToolResult[];
531
+ }
532
+ /** All request types supported by the HTTP transport */
533
+ type HttpRequest = TriggerRequest | ContinueRequest;
534
+ /** Request options passed to the request callback */
535
+ interface HttpRequestOptions {
389
536
  /** Abort signal to cancel the request */
390
537
  signal?: AbortSignal;
391
538
  }
@@ -394,26 +541,25 @@ interface TriggerRequestOptions {
394
541
  */
395
542
  interface HttpTransportOptions {
396
543
  /**
397
- * Function to make the trigger request.
398
- * Called each time `send()` is invoked on the chat.
544
+ * Function to make requests to your backend.
545
+ * Receives a discriminated union with `type` to identify the request kind.
399
546
  *
400
- * @param triggerName - The trigger name (e.g., 'user-message')
401
- * @param input - Input parameters for the trigger
402
- * @param options - Optional request options including abort signal
547
+ * @param request - The request payload (check `request.type` for the kind)
548
+ * @param options - Request options including abort signal
403
549
  * @returns Response with SSE stream body
404
550
  *
405
551
  * @example
406
552
  * ```typescript
407
- * triggerRequest: (triggerName, input, options) =>
408
- * fetch('/api/octavus', {
553
+ * request: (payload, options) =>
554
+ * fetch('/api/trigger', {
409
555
  * method: 'POST',
410
556
  * headers: { 'Content-Type': 'application/json' },
411
- * body: JSON.stringify({ sessionId, triggerName, input }),
557
+ * body: JSON.stringify({ sessionId, ...payload }),
412
558
  * signal: options?.signal,
413
- * }),
559
+ * })
414
560
  * ```
415
561
  */
416
- triggerRequest: (triggerName: string, input?: Record<string, unknown>, options?: TriggerRequestOptions) => Promise<Response>;
562
+ request: (request: HttpRequest, options?: HttpRequestOptions) => Promise<Response>;
417
563
  }
418
564
  /**
419
565
  * Create an HTTP transport using native fetch() and SSE parsing.
@@ -422,11 +568,11 @@ interface HttpTransportOptions {
422
568
  * @example
423
569
  * ```typescript
424
570
  * const transport = createHttpTransport({
425
- * triggerRequest: (triggerName, input, options) =>
426
- * fetch('/api/octavus', {
571
+ * request: (payload, options) =>
572
+ * fetch('/api/trigger', {
427
573
  * method: 'POST',
428
574
  * headers: { 'Content-Type': 'application/json' },
429
- * body: JSON.stringify({ sessionId, triggerName, input }),
575
+ * body: JSON.stringify({ sessionId, ...payload }),
430
576
  * signal: options?.signal,
431
577
  * }),
432
578
  * });
@@ -552,4 +698,4 @@ interface SocketTransportOptions {
552
698
  */
553
699
  declare function createSocketTransport(options: SocketTransportOptions): SocketTransport;
554
700
 
555
- export { type ChatStatus, type ConnectionState, type ConnectionStateListener, type HttpTransportOptions, OctavusChat, type OctavusChatOptions, type SocketLike, type SocketTransport, type SocketTransportOptions, type Transport, type TriggerRequestOptions, type UploadFilesOptions, type UploadUrlsResponse, type UserMessageInput, createHttpTransport, createSocketTransport, isSocketTransport, parseSSEStream, uploadFiles };
701
+ 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 SocketLike, type SocketTransport, type SocketTransportOptions, type Transport, type TriggerRequest, type UploadFilesOptions, type UploadUrlsResponse, type UserMessageInput, createHttpTransport, createSocketTransport, isSocketTransport, parseSSEStream, uploadFiles };