@octavus/docs 0.0.9 → 1.0.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 (177) hide show
  1. package/README.md +127 -0
  2. package/content/01-getting-started/01-introduction.md +3 -3
  3. package/content/01-getting-started/02-quickstart.md +40 -17
  4. package/content/02-server-sdk/01-overview.md +54 -11
  5. package/content/02-server-sdk/02-sessions.md +166 -15
  6. package/content/02-server-sdk/03-tools.md +21 -21
  7. package/content/02-server-sdk/04-streaming.md +50 -20
  8. package/content/02-server-sdk/05-cli.md +247 -0
  9. package/content/03-client-sdk/01-overview.md +65 -35
  10. package/content/03-client-sdk/02-messages.md +116 -8
  11. package/content/03-client-sdk/03-streaming.md +36 -17
  12. package/content/03-client-sdk/04-execution-blocks.md +8 -12
  13. package/content/03-client-sdk/05-socket-transport.md +161 -45
  14. package/content/03-client-sdk/06-http-transport.md +48 -24
  15. package/content/03-client-sdk/07-structured-output.md +412 -0
  16. package/content/03-client-sdk/08-file-uploads.md +473 -0
  17. package/content/03-client-sdk/09-error-handling.md +274 -0
  18. package/content/04-protocol/01-overview.md +24 -14
  19. package/content/04-protocol/02-input-resources.md +35 -35
  20. package/content/04-protocol/03-triggers.md +9 -11
  21. package/content/04-protocol/04-tools.md +63 -29
  22. package/content/04-protocol/05-skills.md +304 -0
  23. package/content/04-protocol/06-handlers.md +304 -0
  24. package/content/04-protocol/07-agent-config.md +334 -0
  25. package/content/04-protocol/{07-provider-options.md → 08-provider-options.md} +54 -35
  26. package/content/04-protocol/09-skills-advanced.md +439 -0
  27. package/content/04-protocol/10-types.md +719 -0
  28. package/content/05-api-reference/01-overview.md +20 -21
  29. package/content/05-api-reference/02-sessions.md +192 -37
  30. package/content/05-api-reference/03-agents.md +25 -37
  31. package/content/06-examples/01-overview.md +6 -4
  32. package/content/06-examples/02-nextjs-chat.md +28 -18
  33. package/content/06-examples/03-socket-chat.md +53 -30
  34. package/content/06-examples/_meta.md +0 -1
  35. package/dist/chunk-WJ2W3DUC.js +663 -0
  36. package/dist/chunk-WJ2W3DUC.js.map +1 -0
  37. package/dist/content.js +1 -1
  38. package/dist/docs.json +99 -36
  39. package/dist/index.js +1 -1
  40. package/dist/search-index.json +1 -1
  41. package/dist/search.js +1 -1
  42. package/dist/search.js.map +1 -1
  43. package/dist/sections.json +99 -36
  44. package/package.json +12 -2
  45. package/content/04-protocol/05-handlers.md +0 -251
  46. package/content/04-protocol/06-agent-config.md +0 -242
  47. package/dist/chunk-232K4EME.js +0 -439
  48. package/dist/chunk-232K4EME.js.map +0 -1
  49. package/dist/chunk-2JDZLMS3.js +0 -439
  50. package/dist/chunk-2JDZLMS3.js.map +0 -1
  51. package/dist/chunk-2YMRODFE.js +0 -421
  52. package/dist/chunk-2YMRODFE.js.map +0 -1
  53. package/dist/chunk-2ZBPX5QB.js +0 -421
  54. package/dist/chunk-2ZBPX5QB.js.map +0 -1
  55. package/dist/chunk-3PIIST4D.js +0 -421
  56. package/dist/chunk-3PIIST4D.js.map +0 -1
  57. package/dist/chunk-42JETGDO.js +0 -421
  58. package/dist/chunk-42JETGDO.js.map +0 -1
  59. package/dist/chunk-4WWUKU4V.js +0 -421
  60. package/dist/chunk-4WWUKU4V.js.map +0 -1
  61. package/dist/chunk-5M7DS4DF.js +0 -519
  62. package/dist/chunk-5M7DS4DF.js.map +0 -1
  63. package/dist/chunk-6JQ3OMGF.js +0 -421
  64. package/dist/chunk-6JQ3OMGF.js.map +0 -1
  65. package/dist/chunk-7AOWCJHW.js +0 -421
  66. package/dist/chunk-7AOWCJHW.js.map +0 -1
  67. package/dist/chunk-7AS4ST73.js +0 -421
  68. package/dist/chunk-7AS4ST73.js.map +0 -1
  69. package/dist/chunk-7F5WOCIL.js +0 -421
  70. package/dist/chunk-7F5WOCIL.js.map +0 -1
  71. package/dist/chunk-7FPUAWSX.js +0 -421
  72. package/dist/chunk-7FPUAWSX.js.map +0 -1
  73. package/dist/chunk-7KXF63FV.js +0 -537
  74. package/dist/chunk-7KXF63FV.js.map +0 -1
  75. package/dist/chunk-APASMJBS.js +0 -421
  76. package/dist/chunk-APASMJBS.js.map +0 -1
  77. package/dist/chunk-BCEV3WV2.js +0 -421
  78. package/dist/chunk-BCEV3WV2.js.map +0 -1
  79. package/dist/chunk-CHGY4G27.js +0 -421
  80. package/dist/chunk-CHGY4G27.js.map +0 -1
  81. package/dist/chunk-CI7JDWKU.js +0 -421
  82. package/dist/chunk-CI7JDWKU.js.map +0 -1
  83. package/dist/chunk-CVFWWRL7.js +0 -421
  84. package/dist/chunk-CVFWWRL7.js.map +0 -1
  85. package/dist/chunk-EPDM2NIJ.js +0 -421
  86. package/dist/chunk-EPDM2NIJ.js.map +0 -1
  87. package/dist/chunk-ESGSYVGK.js +0 -421
  88. package/dist/chunk-ESGSYVGK.js.map +0 -1
  89. package/dist/chunk-GDCTM2SV.js +0 -421
  90. package/dist/chunk-GDCTM2SV.js.map +0 -1
  91. package/dist/chunk-GJ6FMIPD.js +0 -421
  92. package/dist/chunk-GJ6FMIPD.js.map +0 -1
  93. package/dist/chunk-H6JGSSAJ.js +0 -519
  94. package/dist/chunk-H6JGSSAJ.js.map +0 -1
  95. package/dist/chunk-IKQHGGUZ.js +0 -421
  96. package/dist/chunk-IKQHGGUZ.js.map +0 -1
  97. package/dist/chunk-IUKE3XDN.js +0 -421
  98. package/dist/chunk-IUKE3XDN.js.map +0 -1
  99. package/dist/chunk-J26MLMLN.js +0 -421
  100. package/dist/chunk-J26MLMLN.js.map +0 -1
  101. package/dist/chunk-J7BMB3ZW.js +0 -421
  102. package/dist/chunk-J7BMB3ZW.js.map +0 -1
  103. package/dist/chunk-JCBQRD5N.js +0 -421
  104. package/dist/chunk-JCBQRD5N.js.map +0 -1
  105. package/dist/chunk-JOB6YWEF.js +0 -421
  106. package/dist/chunk-JOB6YWEF.js.map +0 -1
  107. package/dist/chunk-JZRABTHU.js +0 -519
  108. package/dist/chunk-JZRABTHU.js.map +0 -1
  109. package/dist/chunk-K3GFQUMC.js +0 -421
  110. package/dist/chunk-K3GFQUMC.js.map +0 -1
  111. package/dist/chunk-LWYMRXBF.js +0 -421
  112. package/dist/chunk-LWYMRXBF.js.map +0 -1
  113. package/dist/chunk-M2R2NDPR.js +0 -421
  114. package/dist/chunk-M2R2NDPR.js.map +0 -1
  115. package/dist/chunk-MA3P7WCA.js +0 -421
  116. package/dist/chunk-MA3P7WCA.js.map +0 -1
  117. package/dist/chunk-MDMRCS4W.mjs +0 -421
  118. package/dist/chunk-MDMRCS4W.mjs.map +0 -1
  119. package/dist/chunk-MJXTA2R6.js +0 -421
  120. package/dist/chunk-MJXTA2R6.js.map +0 -1
  121. package/dist/chunk-NFVJQNDP.js +0 -421
  122. package/dist/chunk-NFVJQNDP.js.map +0 -1
  123. package/dist/chunk-O5TLYMQP.js +0 -421
  124. package/dist/chunk-O5TLYMQP.js.map +0 -1
  125. package/dist/chunk-OECAPVSX.js +0 -439
  126. package/dist/chunk-OECAPVSX.js.map +0 -1
  127. package/dist/chunk-OL5QDJ42.js +0 -483
  128. package/dist/chunk-OL5QDJ42.js.map +0 -1
  129. package/dist/chunk-PMOVVTHO.js +0 -519
  130. package/dist/chunk-PMOVVTHO.js.map +0 -1
  131. package/dist/chunk-QCHDPR2D.js +0 -421
  132. package/dist/chunk-QCHDPR2D.js.map +0 -1
  133. package/dist/chunk-R5MTVABN.js +0 -439
  134. package/dist/chunk-R5MTVABN.js.map +0 -1
  135. package/dist/chunk-RJ4H4YVA.js +0 -519
  136. package/dist/chunk-RJ4H4YVA.js.map +0 -1
  137. package/dist/chunk-S5U4IWCR.js +0 -439
  138. package/dist/chunk-S5U4IWCR.js.map +0 -1
  139. package/dist/chunk-SCKIOGKI.js +0 -421
  140. package/dist/chunk-SCKIOGKI.js.map +0 -1
  141. package/dist/chunk-TGJSIJYP.js +0 -421
  142. package/dist/chunk-TGJSIJYP.js.map +0 -1
  143. package/dist/chunk-TQJG6EBM.js +0 -537
  144. package/dist/chunk-TQJG6EBM.js.map +0 -1
  145. package/dist/chunk-TQZRBMU7.js +0 -421
  146. package/dist/chunk-TQZRBMU7.js.map +0 -1
  147. package/dist/chunk-TRL4RSEO.js +0 -421
  148. package/dist/chunk-TRL4RSEO.js.map +0 -1
  149. package/dist/chunk-TWUMRHQ7.js +0 -421
  150. package/dist/chunk-TWUMRHQ7.js.map +0 -1
  151. package/dist/chunk-UCJE36LL.js +0 -519
  152. package/dist/chunk-UCJE36LL.js.map +0 -1
  153. package/dist/chunk-VCASA6KL.js +0 -421
  154. package/dist/chunk-VCASA6KL.js.map +0 -1
  155. package/dist/chunk-VWPQ6ORV.js +0 -421
  156. package/dist/chunk-VWPQ6ORV.js.map +0 -1
  157. package/dist/chunk-WPXKIHLT.js +0 -421
  158. package/dist/chunk-WPXKIHLT.js.map +0 -1
  159. package/dist/chunk-WUNFFJ32.js +0 -421
  160. package/dist/chunk-WUNFFJ32.js.map +0 -1
  161. package/dist/chunk-WW7TRC7S.js +0 -519
  162. package/dist/chunk-WW7TRC7S.js.map +0 -1
  163. package/dist/chunk-XVSMRXBJ.js +0 -421
  164. package/dist/chunk-XVSMRXBJ.js.map +0 -1
  165. package/dist/chunk-YPPXXV3I.js +0 -421
  166. package/dist/chunk-YPPXXV3I.js.map +0 -1
  167. package/dist/chunk-ZKZVV4OQ.js +0 -421
  168. package/dist/chunk-ZKZVV4OQ.js.map +0 -1
  169. package/dist/chunk-ZOFEX73I.js +0 -421
  170. package/dist/chunk-ZOFEX73I.js.map +0 -1
  171. package/dist/content.mjs +0 -17
  172. package/dist/content.mjs.map +0 -1
  173. package/dist/index.mjs +0 -11
  174. package/dist/index.mjs.map +0 -1
  175. package/dist/search.mjs +0 -30
  176. package/dist/search.mjs.map +0 -1
  177. package/dist/types-BltYGlWI.d.ts +0 -36
@@ -7,10 +7,10 @@ description: Introduction to the Octavus Client SDKs for building chat interface
7
7
 
8
8
  Octavus provides two packages for frontend integration:
9
9
 
10
- | Package | Purpose | Use When |
11
- |---------|---------|----------|
12
- | `@octavus/react` | React hooks and bindings | Building React applications |
13
- | `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |
10
+ | Package | Purpose | Use When |
11
+ | --------------------- | ------------------------ | ----------------------------------------------------- |
12
+ | `@octavus/react` | React hooks and bindings | Building React applications |
13
+ | `@octavus/client-sdk` | Framework-agnostic core | Using Vue, Svelte, vanilla JS, or custom integrations |
14
14
 
15
15
  **Most users should install `@octavus/react`** — it includes everything from `@octavus/client-sdk` plus React-specific hooks.
16
16
 
@@ -36,9 +36,9 @@ npm install @octavus/client-sdk
36
36
 
37
37
  The Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:
38
38
 
39
- | Transport | Use Case | Docs |
40
- |-----------|----------|------|
41
- | `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |
39
+ | Transport | Use Case | Docs |
40
+ | ----------------------- | -------------------------------------------- | ----------------------------------------------------- |
41
+ | `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) | [HTTP Transport](/docs/client-sdk/http-transport) |
42
42
  | `createSocketTransport` | WebSocket, SockJS, or other socket protocols | [Socket Transport](/docs/client-sdk/socket-transport) |
43
43
 
44
44
  When the transport changes (e.g., when `sessionId` changes), the `useOctavusChat` hook automatically reinitializes with the new transport.
@@ -71,11 +71,7 @@ function Chat({ sessionId }: { sessionId: string }) {
71
71
  const { messages, status, send } = useOctavusChat({ transport });
72
72
 
73
73
  const sendMessage = async (text: string) => {
74
- await send(
75
- 'user-message',
76
- { USER_MESSAGE: text },
77
- { userMessage: { content: text } },
78
- );
74
+ await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });
79
75
  };
80
76
 
81
77
  return (
@@ -127,11 +123,7 @@ const unsubscribe = chat.subscribe(() => {
127
123
  });
128
124
 
129
125
  // Send a message
130
- await chat.send(
131
- 'user-message',
132
- { USER_MESSAGE: 'Hello' },
133
- { userMessage: { content: 'Hello' } },
134
- );
126
+ await chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });
135
127
 
136
128
  // Cleanup when done
137
129
  unsubscribe();
@@ -147,11 +139,7 @@ The `send` function handles both user message display and agent triggering in on
147
139
  const { send } = useOctavusChat({ transport });
148
140
 
149
141
  // Add user message to UI and trigger agent
150
- await send(
151
- 'user-message',
152
- { USER_MESSAGE: text },
153
- { userMessage: { content: text } },
154
- );
142
+ await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });
155
143
 
156
144
  // Trigger without adding a user message (e.g., button click)
157
145
  await send('request-human');
@@ -167,10 +155,10 @@ const { messages } = useOctavusChat({ transport });
167
155
  // Each message has typed parts
168
156
  message.parts.map((part) => {
169
157
  switch (part.type) {
170
- case 'text': // Text content
171
- case 'reasoning': // Extended reasoning/thinking
172
- case 'tool-call': // Tool execution
173
- case 'operation': // Internal operations (set-resource, etc.)
158
+ case 'text': // Text content
159
+ case 'reasoning': // Extended reasoning/thinking
160
+ case 'tool-call': // Tool execution
161
+ case 'operation': // Internal operations (set-resource, etc.)
174
162
  }
175
163
  });
176
164
  ```
@@ -203,32 +191,53 @@ interface OctavusChatOptions {
203
191
  // Required: Transport for streaming events
204
192
  transport: Transport;
205
193
 
194
+ // Optional: Function to request upload URLs for file uploads
195
+ requestUploadUrls?: (
196
+ files: { filename: string; mediaType: string; size: number }[],
197
+ ) => Promise<UploadUrlsResponse>;
198
+
206
199
  // Optional: Pre-populate with existing messages (session restore)
207
200
  initialMessages?: UIMessage[];
208
201
 
209
202
  // Optional: Callbacks
210
- onError?: (error: Error) => void;
203
+ onError?: (error: OctavusError) => void; // Structured error with type, source, retryable
211
204
  onFinish?: () => void;
205
+ onStop?: () => void; // Called when user stops generation
212
206
  onResourceUpdate?: (name: string, value: unknown) => void;
213
207
  }
214
208
 
215
209
  interface UseOctavusChatReturn {
216
210
  // State
217
211
  messages: UIMessage[];
218
- status: ChatStatus; // 'idle' | 'streaming' | 'error'
219
- error: Error | null;
212
+ status: ChatStatus; // 'idle' | 'streaming' | 'error'
213
+ error: OctavusError | null; // Structured error with type, source, retryable
214
+
215
+ // Connection (socket transport only - undefined for HTTP)
216
+ connectionState: ConnectionState | undefined; // 'disconnected' | 'connecting' | 'connected' | 'error'
217
+ connectionError: Error | undefined;
220
218
 
221
219
  // Actions
222
220
  send: (
223
221
  triggerName: string,
224
222
  input?: Record<string, unknown>,
225
- options?: { userMessage?: UserMessageInput }
223
+ options?: { userMessage?: UserMessageInput },
226
224
  ) => Promise<void>;
227
225
  stop: () => void;
226
+
227
+ // Connection management (socket transport only - undefined for HTTP)
228
+ connect: (() => Promise<void>) | undefined;
229
+ disconnect: (() => void) | undefined;
230
+
231
+ // File uploads (requires requestUploadUrls)
232
+ uploadFiles: (
233
+ files: FileList | File[],
234
+ onProgress?: (fileIndex: number, progress: number) => void,
235
+ ) => Promise<FileReference[]>;
228
236
  }
229
237
 
230
238
  interface UserMessageInput {
231
- content: string;
239
+ content?: string;
240
+ files?: FileList | File[] | FileReference[];
232
241
  }
233
242
  ```
234
243
 
@@ -242,11 +251,12 @@ Creates an HTTP/SSE transport using native `fetch()`:
242
251
  import { createHttpTransport } from '@octavus/react';
243
252
 
244
253
  const transport = createHttpTransport({
245
- triggerRequest: (triggerName, input) =>
254
+ triggerRequest: (triggerName, input, options) =>
246
255
  fetch('/api/trigger', {
247
256
  method: 'POST',
248
257
  headers: { 'Content-Type': 'application/json' },
249
258
  body: JSON.stringify({ sessionId, triggerName, input }),
259
+ signal: options?.signal,
250
260
  }),
251
261
  });
252
262
  ```
@@ -268,6 +278,24 @@ const transport = createSocketTransport({
268
278
  });
269
279
  ```
270
280
 
281
+ Socket transport provides additional connection management:
282
+
283
+ ```typescript
284
+ // Access connection state directly
285
+ transport.connectionState; // 'disconnected' | 'connecting' | 'connected' | 'error'
286
+
287
+ // Subscribe to state changes
288
+ transport.onConnectionStateChange((state, error) => {
289
+ /* ... */
290
+ });
291
+
292
+ // Eager connection (instead of lazy on first send)
293
+ await transport.connect();
294
+
295
+ // Manual disconnect
296
+ transport.disconnect();
297
+ ```
298
+
271
299
  For detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).
272
300
 
273
301
  ## Class Reference (Framework-Agnostic)
@@ -281,18 +309,18 @@ class OctavusChat {
281
309
  // State (read-only)
282
310
  readonly messages: UIMessage[];
283
311
  readonly status: ChatStatus;
284
- readonly error: Error | null;
312
+ readonly error: OctavusError | null; // Structured error
285
313
 
286
314
  // Actions
287
315
  send(
288
316
  triggerName: string,
289
317
  input?: Record<string, unknown>,
290
- options?: { userMessage?: UserMessageInput }
318
+ options?: { userMessage?: UserMessageInput },
291
319
  ): Promise<void>;
292
320
  stop(): void;
293
321
 
294
322
  // Subscription
295
- subscribe(callback: () => void): () => void; // Returns unsubscribe function
323
+ subscribe(callback: () => void): () => void; // Returns unsubscribe function
296
324
  }
297
325
  ```
298
326
 
@@ -303,4 +331,6 @@ class OctavusChat {
303
331
  - [Messages](/docs/client-sdk/messages) — Working with message state
304
332
  - [Streaming](/docs/client-sdk/streaming) — Building streaming UIs
305
333
  - [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress
334
+ - [Error Handling](/docs/client-sdk/error-handling) — Handling errors with type guards
335
+ - [File Uploads](/docs/client-sdk/file-uploads) — Uploading images and documents
306
336
  - [Examples](/docs/examples/overview) — Complete working examples
@@ -28,14 +28,17 @@ type UIMessagePart =
28
28
  | UITextPart
29
29
  | UIReasoningPart
30
30
  | UIToolCallPart
31
- | UIOperationPart;
31
+ | UIOperationPart
32
+ | UISourcePart
33
+ | UIFilePart
34
+ | UIObjectPart;
32
35
 
33
36
  // Text content
34
37
  interface UITextPart {
35
38
  type: 'text';
36
39
  text: string;
37
40
  status: 'streaming' | 'done';
38
- thread?: string; // For named threads (e.g., "summary")
41
+ thread?: string; // For named threads (e.g., "summary")
39
42
  }
40
43
 
41
44
  // Extended reasoning/thinking
@@ -51,7 +54,7 @@ interface UIToolCallPart {
51
54
  type: 'tool-call';
52
55
  toolCallId: string;
53
56
  toolName: string;
54
- displayName?: string; // Human-readable name
57
+ displayName?: string; // Human-readable name
55
58
  args: Record<string, unknown>;
56
59
  result?: unknown;
57
60
  error?: string;
@@ -68,6 +71,42 @@ interface UIOperationPart {
68
71
  status: 'running' | 'done';
69
72
  thread?: string;
70
73
  }
74
+
75
+ // Source references (from web search, document processing)
76
+ interface UISourcePart {
77
+ type: 'source';
78
+ sourceType: 'url' | 'document';
79
+ id: string;
80
+ url?: string; // For URL sources
81
+ title?: string;
82
+ mediaType?: string; // For document sources
83
+ filename?: string;
84
+ thread?: string;
85
+ }
86
+
87
+ // Generated files (from image generation, skills, code execution)
88
+ interface UIFilePart {
89
+ type: 'file';
90
+ id: string;
91
+ mediaType: string; // MIME type (e.g., 'image/png', 'image/webp')
92
+ url: string; // Download/display URL (presigned S3 URL)
93
+ filename?: string;
94
+ size?: number;
95
+ toolCallId?: string; // Present if from a tool call
96
+ thread?: string;
97
+ }
98
+
99
+ // Structured output (when responseType is used)
100
+ interface UIObjectPart {
101
+ type: 'object';
102
+ id: string;
103
+ typeName: string; // Type name from protocol (e.g., "ChatResponse")
104
+ partial?: unknown; // Partial object while streaming
105
+ object?: unknown; // Final object when done
106
+ status: 'streaming' | 'done' | 'error';
107
+ error?: string;
108
+ thread?: string;
109
+ }
71
110
  ```
72
111
 
73
112
  ## Sending Messages
@@ -94,11 +133,7 @@ function Chat({ sessionId }: { sessionId: string }) {
94
133
 
95
134
  async function handleSend(text: string) {
96
135
  // Add user message to UI and trigger agent
97
- await send(
98
- 'user-message',
99
- { USER_MESSAGE: text },
100
- { userMessage: { content: text } },
101
- );
136
+ await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });
102
137
  }
103
138
 
104
139
  // ...
@@ -106,10 +141,56 @@ function Chat({ sessionId }: { sessionId: string }) {
106
141
  ```
107
142
 
108
143
  The `send` function:
144
+
109
145
  1. Adds the user message to the UI immediately (if `userMessage` is provided)
110
146
  2. Triggers the agent with the specified trigger name and input
111
147
  3. Streams the assistant's response back
112
148
 
149
+ ### Message Content Types
150
+
151
+ The `content` field in `userMessage` accepts both strings and objects:
152
+
153
+ ```tsx
154
+ // Text content → creates a text part
155
+ await send('user-message', { USER_MESSAGE: text }, { userMessage: { content: text } });
156
+
157
+ // Object content → creates an object part (uses `type` field as typeName)
158
+ const selection = { type: 'product_selection', productId: 'abc123', action: 'select' };
159
+ await send('user-message', { USER_INPUT: selection }, { userMessage: { content: selection } });
160
+ ```
161
+
162
+ When passing an object as `content`:
163
+
164
+ - The SDK creates a `UIObjectPart` instead of a `UITextPart`
165
+ - The object's `type` field is used as the `typeName` (defaults to `'object'` if not present)
166
+ - This is useful for rich UI interactions like product selections, quick replies, etc.
167
+
168
+ ### Sending with Files
169
+
170
+ Include file attachments with messages:
171
+
172
+ ```tsx
173
+ import type { FileReference } from '@octavus/react';
174
+
175
+ async function handleSend(text: string, files?: FileReference[]) {
176
+ await send(
177
+ 'user-message',
178
+ {
179
+ USER_MESSAGE: text,
180
+ FILES: files, // Array of FileReference
181
+ },
182
+ {
183
+ userMessage: {
184
+ content: text,
185
+ files: files, // Shows files in user message bubble
186
+ },
187
+ },
188
+ );
189
+ }
190
+ ```
191
+
192
+ See [File Uploads](/docs/client-sdk/file-uploads) for complete upload flow.
193
+
113
194
  ## Rendering Messages
114
195
 
115
196
  ### Basic Rendering
@@ -180,6 +261,33 @@ function PartRenderer({ part }: { part: UIMessagePart }) {
180
261
  </div>
181
262
  );
182
263
 
264
+ case 'source':
265
+ return (
266
+ <div className="text-blue-500 text-sm">📎 {part.title || part.url || part.filename}</div>
267
+ );
268
+
269
+ case 'file':
270
+ // Render images inline, other files as download links
271
+ if (part.mediaType.startsWith('image/')) {
272
+ return (
273
+ <img
274
+ src={part.url}
275
+ alt={part.filename || 'Generated image'}
276
+ className="max-w-full rounded-lg"
277
+ />
278
+ );
279
+ }
280
+ return (
281
+ <a href={part.url} className="text-blue-500 text-sm underline">
282
+ 📄 {part.filename || 'Download file'}
283
+ </a>
284
+ );
285
+
286
+ case 'object':
287
+ // For structured output, render custom UI based on typeName
288
+ // See Structured Output guide for more details
289
+ return <ObjectPartRenderer part={part} />;
290
+
183
291
  default:
184
292
  return null;
185
293
  }
@@ -85,9 +85,7 @@ function ReasoningPart({ part }: { part: UIReasoningPart }) {
85
85
  {expanded ? '▼' : '▶'}
86
86
  </button>
87
87
 
88
- {expanded && (
89
- <pre className="mt-2 text-sm text-gray-600">{part.text}</pre>
90
- )}
88
+ {expanded && <pre className="mt-2 text-sm text-gray-600">{part.text}</pre>}
91
89
  </div>
92
90
  );
93
91
  }
@@ -117,9 +115,10 @@ function ToolCallPart({ part }: { part: UIToolCallPart }) {
117
115
  )}
118
116
 
119
117
  {/* Show error if failed */}
120
- {part.status === 'error' && (
121
- <p className="mt-2 text-red-500 text-sm">{part.error}</p>
122
- )}
118
+ {part.status === 'error' && <p className="mt-2 text-red-500 text-sm">{part.error}</p>}
119
+
120
+ {/* Show cancelled state */}
121
+ {part.status === 'cancelled' && <p className="mt-2 text-amber-500 text-sm">Cancelled</p>}
123
122
  </div>
124
123
  );
125
124
  }
@@ -134,6 +133,8 @@ function StatusBadge({ status }: { status: UIToolCallPart['status'] }) {
134
133
  return <span className="text-green-500">✓</span>;
135
134
  case 'error':
136
135
  return <span className="text-red-500">✗</span>;
136
+ case 'cancelled':
137
+ return <span className="text-amber-500">◼</span>;
137
138
  }
138
139
  }
139
140
  ```
@@ -156,19 +157,34 @@ function StatusIndicator({ status }: { status: ChatStatus }) {
156
157
  ## Handling Completion
157
158
 
158
159
  ```tsx
160
+ import { isRateLimitError, type OctavusError } from '@octavus/react';
161
+
159
162
  useOctavusChat({
160
163
  transport,
161
164
  onFinish: () => {
162
- console.log('Stream completed');
165
+ console.log('Stream completed successfully');
163
166
  // Scroll to bottom, play sound, etc.
164
167
  },
165
- onError: (error) => {
166
- console.error('Stream error:', error);
167
- toast.error('Failed to get response');
168
+ onStop: () => {
169
+ console.log('User stopped generation');
170
+ // Handle stop - content is preserved
171
+ },
172
+ onError: (error: OctavusError) => {
173
+ console.error('Stream error:', error.errorType, error.message);
174
+
175
+ if (isRateLimitError(error)) {
176
+ toast.error(`Rate limited. Retry in ${error.retryAfter}s`);
177
+ } else {
178
+ toast.error('Failed to get response');
179
+ }
168
180
  },
169
181
  });
170
182
  ```
171
183
 
184
+ See [Error Handling](/docs/client-sdk/error-handling) for comprehensive error handling patterns.
185
+
186
+ ````
187
+
172
188
  ## Stop Function
173
189
 
174
190
  Stop the current stream and finalize any partial message:
@@ -182,12 +198,17 @@ const { status, stop } = useOctavusChat({ transport });
182
198
  Stop generating
183
199
  </button>
184
200
  )}
185
- ```
201
+ ````
186
202
 
187
203
  When `stop()` is called:
188
- 1. The current request is aborted
189
- 2. Any partial message is finalized with current content
190
- 3. Status changes to `'idle'`
204
+
205
+ 1. The HTTP request is aborted (requires `signal` in transport)
206
+ 2. Any partial text/reasoning is finalized with `done` status
207
+ 3. In-progress tool calls are marked as `cancelled`
208
+ 4. The `onStop` callback is invoked
209
+ 5. Status changes to `idle`
210
+
211
+ Partial content is preserved in the message, so users don't lose what was already generated.
191
212
 
192
213
  ## Named Thread Content
193
214
 
@@ -211,9 +232,7 @@ function MessageBubble({ message }: { message: UIMessage }) {
211
232
  {/* Named thread content (e.g., summarization) */}
212
233
  {otherParts.length > 0 && (
213
234
  <div className="bg-amber-50 p-3 rounded mt-4 border border-amber-200">
214
- <div className="text-amber-600 font-medium mb-2">
215
- Background processing
216
- </div>
235
+ <div className="text-amber-600 font-medium mb-2">Background processing</div>
217
236
  {otherParts.map((part, i) => (
218
237
  <PartRenderer key={i} part={part} />
219
238
  ))}
@@ -13,10 +13,10 @@ Operations represent internal agent activities like setting resources or seriali
13
13
  interface UIOperationPart {
14
14
  type: 'operation';
15
15
  operationId: string;
16
- name: string; // Human-readable name
17
- operationType: string; // e.g., 'set-resource', 'serialize-thread'
16
+ name: string; // Human-readable name
17
+ operationType: string; // e.g., 'set-resource', 'serialize-thread'
18
18
  status: 'running' | 'done';
19
- thread?: string; // For named threads
19
+ thread?: string; // For named threads
20
20
  }
21
21
  ```
22
22
 
@@ -76,9 +76,9 @@ function PartRenderer({ part }: { part: UIMessagePart }) {
76
76
 
77
77
  ## Common Operation Types
78
78
 
79
- | Type | Description |
80
- |------|-------------|
81
- | `set-resource` | Updating a resource value |
79
+ | Type | Description |
80
+ | ------------------ | ---------------------------------- |
81
+ | `set-resource` | Updating a resource value |
82
82
  | `serialize-thread` | Converting thread messages to text |
83
83
 
84
84
  ## Example: Progress During Escalation
@@ -87,9 +87,7 @@ When a user clicks "Talk to Human", multiple operations may occur:
87
87
 
88
88
  ```tsx
89
89
  function EscalationProgress({ message }: { message: UIMessage }) {
90
- const operations = message.parts.filter(
91
- (p): p is UIOperationPart => p.type === 'operation'
92
- );
90
+ const operations = message.parts.filter((p): p is UIOperationPart => p.type === 'operation');
93
91
 
94
92
  return (
95
93
  <div className="space-y-2">
@@ -123,9 +121,7 @@ Operations can belong to named threads. Use the `thread` property to identify th
123
121
  function OperationCard({ operation }: { operation: UIOperationPart }) {
124
122
  return (
125
123
  <div className="flex items-center gap-2 text-sm">
126
- {operation.thread && (
127
- <span className="text-amber-500">[{operation.thread}]</span>
128
- )}
124
+ {operation.thread && <span className="text-amber-500">[{operation.thread}]</span>}
129
125
  <span>{operation.name}</span>
130
126
  {operation.status === 'done' && <span className="text-green-500">✓</span>}
131
127
  </div>