@octavus/docs 0.0.6 → 0.0.7

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.
@@ -118,14 +118,14 @@ export async function POST(request: Request) {
118
118
 
119
119
  ### 1. Create a Chat Component
120
120
 
121
- Use the `useOctavusChat` hook to build a chat interface:
121
+ Use the `useOctavusChat` hook with the HTTP transport:
122
122
 
123
123
  ```tsx
124
124
  // components/chat.tsx
125
125
  'use client';
126
126
 
127
- import { useOctavusChat, type UIMessage } from '@octavus/react';
128
- import { useState } from 'react';
127
+ import { useState, useMemo } from 'react';
128
+ import { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';
129
129
 
130
130
  interface ChatProps {
131
131
  sessionId: string;
@@ -134,15 +134,21 @@ interface ChatProps {
134
134
  export function Chat({ sessionId }: ChatProps) {
135
135
  const [inputValue, setInputValue] = useState('');
136
136
 
137
- const { messages, status, error, send } = useOctavusChat({
138
- onTrigger: async (triggerName, input) => {
139
- return fetch('/api/trigger', {
140
- method: 'POST',
141
- headers: { 'Content-Type': 'application/json' },
142
- body: JSON.stringify({ sessionId, triggerName, input }),
143
- });
144
- },
145
- });
137
+ // Create a stable transport instance
138
+ const transport = useMemo(
139
+ () =>
140
+ createHttpTransport({
141
+ triggerRequest: (triggerName, input) =>
142
+ fetch('/api/trigger', {
143
+ method: 'POST',
144
+ headers: { 'Content-Type': 'application/json' },
145
+ body: JSON.stringify({ sessionId, triggerName, input }),
146
+ }),
147
+ }),
148
+ [sessionId],
149
+ );
150
+
151
+ const { messages, status, error, send } = useOctavusChat({ transport });
146
152
 
147
153
  const isStreaming = status === 'streaming';
148
154
 
@@ -154,9 +160,11 @@ export function Chat({ sessionId }: ChatProps) {
154
160
  setInputValue('');
155
161
 
156
162
  // Add user message and trigger in one call
157
- await send('user-message', { USER_MESSAGE: message }, {
158
- userMessage: { content: message },
159
- });
163
+ await send(
164
+ 'user-message',
165
+ { USER_MESSAGE: message },
166
+ { userMessage: { content: message } },
167
+ );
160
168
  };
161
169
 
162
170
  return (
@@ -208,7 +216,7 @@ function MessageBubble({ message }: { message: UIMessage }) {
208
216
  }
209
217
  return null;
210
218
  })}
211
-
219
+
212
220
  {/* Streaming indicator */}
213
221
  {message.status === 'streaming' && (
214
222
  <span className="inline-block w-2 h-4 bg-gray-400 animate-pulse ml-1" />
@@ -34,13 +34,13 @@ Create and manage agent sessions:
34
34
 
35
35
  ```typescript
36
36
  // Create a new session
37
- const sessionId = await client.agentSessions.create('support-chat', {
37
+ const sessionId = await client.agentSessions.create('agent-id', {
38
38
  COMPANY_NAME: 'Acme Corp',
39
39
  PRODUCT_NAME: 'Widget Pro',
40
40
  });
41
41
 
42
- // Get session state
43
- const state = await client.agentSessions.get(sessionId);
42
+ // Get UI-ready session messages (for session restore)
43
+ const session = await client.agentSessions.getMessages(sessionId);
44
44
  ```
45
45
 
46
46
  ### Tool Handlers
@@ -63,8 +63,8 @@ const session = client.agentSessions.attach(sessionId, {
63
63
  All responses stream in real-time:
64
64
 
65
65
  ```typescript
66
- const { stream } = session.trigger('user-message', {
67
- USER_MESSAGE: 'Hello!'
66
+ const { stream } = session.trigger('user-message', {
67
+ USER_MESSAGE: 'Hello!',
68
68
  });
69
69
 
70
70
  // Use as a streaming response
@@ -88,7 +88,7 @@ interface OctavusClientConfig {
88
88
  class OctavusClient {
89
89
  readonly agents: AgentsApi;
90
90
  readonly agentSessions: AgentSessionsApi;
91
-
91
+
92
92
  constructor(config: OctavusClientConfig);
93
93
  }
94
94
  ```
@@ -101,24 +101,35 @@ Manages agent sessions.
101
101
  class AgentSessionsApi {
102
102
  // Create a new session
103
103
  async create(agentId: string, input?: Record<string, unknown>): Promise<string>;
104
-
105
- // Get session state (includes UI-ready messages)
106
- async get(sessionId: string): Promise<UISessionState>;
107
-
104
+
105
+ // Get full session state (for debugging/internal use)
106
+ async get(sessionId: string): Promise<SessionState>;
107
+
108
+ // Get UI-ready messages (for client display)
109
+ async getMessages(sessionId: string): Promise<UISessionState>;
110
+
108
111
  // Attach to a session for triggering
109
112
  attach(sessionId: string, options?: SessionAttachOptions): AgentSession;
110
113
  }
111
114
 
112
- interface UISessionState {
115
+ // Full session state (internal format)
116
+ interface SessionState {
113
117
  id: string;
114
118
  agentId: string;
115
119
  input: Record<string, unknown>;
116
120
  variables: Record<string, unknown>;
117
121
  resources: Record<string, unknown>;
118
- messages: UIMessage[]; // UI-ready messages for session restore
122
+ messages: ChatMessage[]; // Internal message format
119
123
  createdAt: string;
120
124
  updatedAt: string;
121
125
  }
126
+
127
+ // UI-ready session state
128
+ interface UISessionState {
129
+ sessionId: string;
130
+ agentId: string;
131
+ messages: UIMessage[]; // UI-ready messages for frontend
132
+ }
122
133
  ```
123
134
 
124
135
  ### AgentSession
@@ -132,7 +143,7 @@ class AgentSession {
132
143
  triggerName: string,
133
144
  input?: Record<string, unknown>
134
145
  ): { stream: ReadableStream<Uint8Array> };
135
-
146
+
136
147
  // Get the session ID
137
148
  getSessionId(): string;
138
149
  }
@@ -32,27 +32,46 @@ npm install @octavus/client-sdk
32
32
 
33
33
  **Current version:** `{{VERSION:@octavus/client-sdk}}`
34
34
 
35
+ ## Transport Pattern
36
+
37
+ The Client SDK uses a **transport abstraction** to handle communication with your backend. This gives you flexibility in how events are delivered:
38
+
39
+ | Transport | Use Case |
40
+ |-----------|----------|
41
+ | `createHttpTransport` | HTTP/SSE (Next.js, Express, etc.) |
42
+ | `createSocketTransport` | WebSocket, SockJS, or other socket protocols |
43
+
35
44
  ## React Usage
36
45
 
37
46
  The `useOctavusChat` hook provides state management and streaming for React applications:
38
47
 
39
48
  ```tsx
40
- import { useOctavusChat, type UIMessage } from '@octavus/react';
49
+ import { useMemo } from 'react';
50
+ import { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';
41
51
 
42
52
  function Chat({ sessionId }: { sessionId: string }) {
43
- const { messages, status, send } = useOctavusChat({
44
- onTrigger: async (triggerName, input) => {
45
- return fetch(`/api/trigger`, {
46
- method: 'POST',
47
- body: JSON.stringify({ sessionId, triggerName, input }),
48
- });
49
- },
50
- });
53
+ // Create a stable transport instance (memoized on sessionId)
54
+ const transport = useMemo(
55
+ () =>
56
+ createHttpTransport({
57
+ triggerRequest: (triggerName, input) =>
58
+ fetch('/api/trigger', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ sessionId, triggerName, input }),
62
+ }),
63
+ }),
64
+ [sessionId],
65
+ );
66
+
67
+ const { messages, status, send } = useOctavusChat({ transport });
51
68
 
52
69
  const sendMessage = async (text: string) => {
53
- await send('user-message', { USER_MESSAGE: text }, {
54
- userMessage: { content: text },
55
- });
70
+ await send(
71
+ 'user-message',
72
+ { USER_MESSAGE: text },
73
+ { userMessage: { content: text } },
74
+ );
56
75
  };
57
76
 
58
77
  return (
@@ -83,17 +102,19 @@ function MessageBubble({ message }: { message: UIMessage }) {
83
102
  The `OctavusChat` class can be used with any framework or vanilla JavaScript:
84
103
 
85
104
  ```typescript
86
- import { OctavusChat } from '@octavus/client-sdk';
105
+ import { OctavusChat, createHttpTransport } from '@octavus/client-sdk';
87
106
 
88
- const chat = new OctavusChat({
89
- onTrigger: async (triggerName, input) => {
90
- return fetch('/api/trigger', {
107
+ const transport = createHttpTransport({
108
+ triggerRequest: (triggerName, input) =>
109
+ fetch('/api/trigger', {
91
110
  method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
92
112
  body: JSON.stringify({ sessionId, triggerName, input }),
93
- });
94
- },
113
+ }),
95
114
  });
96
115
 
116
+ const chat = new OctavusChat({ transport });
117
+
97
118
  // Subscribe to state changes
98
119
  const unsubscribe = chat.subscribe(() => {
99
120
  console.log('Messages:', chat.messages);
@@ -102,9 +123,11 @@ const unsubscribe = chat.subscribe(() => {
102
123
  });
103
124
 
104
125
  // Send a message
105
- await chat.send('user-message', { USER_MESSAGE: 'Hello' }, {
106
- userMessage: { content: 'Hello' },
107
- });
126
+ await chat.send(
127
+ 'user-message',
128
+ { USER_MESSAGE: 'Hello' },
129
+ { userMessage: { content: 'Hello' } },
130
+ );
108
131
 
109
132
  // Cleanup when done
110
133
  unsubscribe();
@@ -117,12 +140,14 @@ unsubscribe();
117
140
  The `send` function handles both user message display and agent triggering in one call:
118
141
 
119
142
  ```tsx
120
- const { send } = useOctavusChat({...});
143
+ const { send } = useOctavusChat({ transport });
121
144
 
122
145
  // Add user message to UI and trigger agent
123
- await send('user-message', { USER_MESSAGE: text }, {
124
- userMessage: { content: text },
125
- });
146
+ await send(
147
+ 'user-message',
148
+ { USER_MESSAGE: text },
149
+ { userMessage: { content: text } },
150
+ );
126
151
 
127
152
  // Trigger without adding a user message (e.g., button click)
128
153
  await send('request-human');
@@ -133,7 +158,7 @@ await send('request-human');
133
158
  Messages contain ordered `parts` for rich content:
134
159
 
135
160
  ```tsx
136
- const { messages } = useOctavusChat({...});
161
+ const { messages } = useOctavusChat({ transport });
137
162
 
138
163
  // Each message has typed parts
139
164
  message.parts.map((part) => {
@@ -149,7 +174,7 @@ message.parts.map((part) => {
149
174
  ### Status Tracking
150
175
 
151
176
  ```tsx
152
- const { status } = useOctavusChat({...});
177
+ const { status } = useOctavusChat({ transport });
153
178
 
154
179
  // status: 'idle' | 'streaming' | 'error'
155
180
  ```
@@ -157,7 +182,7 @@ const { status } = useOctavusChat({...});
157
182
  ### Stop Streaming
158
183
 
159
184
  ```tsx
160
- const { stop } = useOctavusChat({...});
185
+ const { stop } = useOctavusChat({ transport });
161
186
 
162
187
  // Stop current stream and finalize message
163
188
  stop();
@@ -171,12 +196,12 @@ stop();
171
196
  function useOctavusChat(options: OctavusChatOptions): UseOctavusChatReturn;
172
197
 
173
198
  interface OctavusChatOptions {
174
- // Required: Function that calls your backend trigger endpoint
175
- onTrigger: TriggerFunction;
176
-
199
+ // Required: Transport for streaming events
200
+ transport: Transport;
201
+
177
202
  // Optional: Pre-populate with existing messages (session restore)
178
203
  initialMessages?: UIMessage[];
179
-
204
+
180
205
  // Optional: Callbacks
181
206
  onError?: (error: Error) => void;
182
207
  onFinish?: () => void;
@@ -188,7 +213,7 @@ interface UseOctavusChatReturn {
188
213
  messages: UIMessage[];
189
214
  status: ChatStatus; // 'idle' | 'streaming' | 'error'
190
215
  error: Error | null;
191
-
216
+
192
217
  // Actions
193
218
  send: (
194
219
  triggerName: string,
@@ -198,16 +223,49 @@ interface UseOctavusChatReturn {
198
223
  stop: () => void;
199
224
  }
200
225
 
201
- type TriggerFunction = (
202
- triggerName: string,
203
- input?: Record<string, unknown>,
204
- ) => Promise<Response>;
205
-
206
226
  interface UserMessageInput {
207
227
  content: string;
208
228
  }
209
229
  ```
210
230
 
231
+ ## Transport Reference
232
+
233
+ ### createHttpTransport
234
+
235
+ Creates an HTTP/SSE transport using native `fetch()`:
236
+
237
+ ```typescript
238
+ import { createHttpTransport } from '@octavus/react';
239
+
240
+ const transport = createHttpTransport({
241
+ triggerRequest: (triggerName, input) =>
242
+ fetch('/api/trigger', {
243
+ method: 'POST',
244
+ headers: { 'Content-Type': 'application/json' },
245
+ body: JSON.stringify({ sessionId, triggerName, input }),
246
+ }),
247
+ });
248
+ ```
249
+
250
+ ### createSocketTransport
251
+
252
+ Creates a WebSocket/SockJS transport for real-time connections:
253
+
254
+ ```typescript
255
+ import { createSocketTransport } from '@octavus/react';
256
+
257
+ const transport = createSocketTransport({
258
+ connect: () =>
259
+ new Promise((resolve, reject) => {
260
+ const ws = new WebSocket(`wss://api.example.com/stream?sessionId=${sessionId}`);
261
+ ws.onopen = () => resolve(ws);
262
+ ws.onerror = () => reject(new Error('Connection failed'));
263
+ }),
264
+ });
265
+ ```
266
+
267
+ For detailed WebSocket/SockJS usage including custom events, reconnection patterns, and server-side implementation, see [Socket Transport](/docs/client-sdk/socket-transport).
268
+
211
269
  ## Class Reference (Framework-Agnostic)
212
270
 
213
271
  ### OctavusChat
@@ -215,12 +273,12 @@ interface UserMessageInput {
215
273
  ```typescript
216
274
  class OctavusChat {
217
275
  constructor(options: OctavusChatOptions);
218
-
276
+
219
277
  // State (read-only)
220
278
  readonly messages: UIMessage[];
221
279
  readonly status: ChatStatus;
222
280
  readonly error: Error | null;
223
-
281
+
224
282
  // Actions
225
283
  send(
226
284
  triggerName: string,
@@ -228,7 +286,7 @@ class OctavusChat {
228
286
  options?: { userMessage?: UserMessageInput }
229
287
  ): Promise<void>;
230
288
  stop(): void;
231
-
289
+
232
290
  // Subscription
233
291
  subscribe(callback: () => void): () => void; // Returns unsubscribe function
234
292
  }
@@ -238,4 +296,5 @@ class OctavusChat {
238
296
 
239
297
  - [Messages](/docs/client-sdk/messages) — Working with message state
240
298
  - [Streaming](/docs/client-sdk/streaming) — Building streaming UIs
241
- - [Execution Blocks](/docs/client-sdk/execution-blocks) — Showing agent progress
299
+ - [Operations](/docs/client-sdk/execution-blocks) — Showing agent progress
300
+ - [Socket Transport](/docs/client-sdk/socket-transport) — WebSocket and SockJS integration
@@ -24,10 +24,10 @@ interface UIMessage {
24
24
  Messages contain ordered `parts` that preserve content ordering:
25
25
 
26
26
  ```typescript
27
- type UIMessagePart =
28
- | UITextPart
29
- | UIReasoningPart
30
- | UIToolCallPart
27
+ type UIMessagePart =
28
+ | UITextPart
29
+ | UIReasoningPart
30
+ | UIToolCallPart
31
31
  | UIOperationPart;
32
32
 
33
33
  // Text content
@@ -73,13 +73,35 @@ interface UIOperationPart {
73
73
  ## Sending Messages
74
74
 
75
75
  ```tsx
76
- const { send } = useOctavusChat({...});
76
+ import { useMemo } from 'react';
77
+ import { useOctavusChat, createHttpTransport } from '@octavus/react';
78
+
79
+ function Chat({ sessionId }: { sessionId: string }) {
80
+ const transport = useMemo(
81
+ () =>
82
+ createHttpTransport({
83
+ triggerRequest: (triggerName, input) =>
84
+ fetch('/api/trigger', {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({ sessionId, triggerName, input }),
88
+ }),
89
+ }),
90
+ [sessionId],
91
+ );
77
92
 
78
- async function handleSend(text: string) {
79
- // Add user message to UI and trigger agent
80
- await send('user-message', { USER_MESSAGE: text }, {
81
- userMessage: { content: text },
82
- });
93
+ const { send } = useOctavusChat({ transport });
94
+
95
+ async function handleSend(text: string) {
96
+ // Add user message to UI and trigger agent
97
+ await send(
98
+ 'user-message',
99
+ { USER_MESSAGE: text },
100
+ { userMessage: { content: text } },
101
+ );
102
+ }
103
+
104
+ // ...
83
105
  }
84
106
  ```
85
107
 
@@ -201,24 +223,54 @@ function PartRenderer({ part }: { part: UIMessagePart }) {
201
223
 
202
224
  ## Session Restore
203
225
 
204
- When restoring a session, pass existing messages:
226
+ When restoring a session, fetch messages from your backend and pass them to the hook:
205
227
 
206
228
  ```tsx
207
- // Fetch session state from your backend
208
- const sessionState = await fetchSession(sessionId);
229
+ import { useMemo } from 'react';
230
+ import { useOctavusChat, createHttpTransport, type UIMessage } from '@octavus/react';
209
231
 
210
- // Pass to hook
211
- const { messages } = useOctavusChat({
212
- initialMessages: sessionState.messages,
213
- onTrigger: ...
214
- });
232
+ interface ChatProps {
233
+ sessionId: string;
234
+ initialMessages: UIMessage[];
235
+ }
236
+
237
+ function Chat({ sessionId, initialMessages }: ChatProps) {
238
+ const transport = useMemo(
239
+ () =>
240
+ createHttpTransport({
241
+ triggerRequest: (triggerName, input) =>
242
+ fetch('/api/trigger', {
243
+ method: 'POST',
244
+ headers: { 'Content-Type': 'application/json' },
245
+ body: JSON.stringify({ sessionId, triggerName, input }),
246
+ }),
247
+ }),
248
+ [sessionId],
249
+ );
250
+
251
+ // Pass existing messages to restore the conversation
252
+ const { messages } = useOctavusChat({
253
+ transport,
254
+ initialMessages,
255
+ });
256
+
257
+ // ...
258
+ }
259
+ ```
260
+
261
+ On your backend, use `agentSessions.getMessages()` to fetch UI-ready messages:
262
+
263
+ ```typescript
264
+ // Server-side
265
+ const session = await client.agentSessions.getMessages(sessionId);
266
+ // session.messages is UIMessage[] ready for the client
215
267
  ```
216
268
 
217
269
  ## Callbacks
218
270
 
219
271
  ```tsx
220
272
  useOctavusChat({
221
- onTrigger: ...,
273
+ transport,
222
274
  onFinish: () => {
223
275
  console.log('Stream completed');
224
276
  // Scroll to bottom, play sound, etc.
@@ -10,7 +10,7 @@ The Client SDK provides real-time access to streaming content through the messag
10
10
  ## Streaming State
11
11
 
12
12
  ```tsx
13
- const { messages, status, error } = useOctavusChat({...});
13
+ const { messages, status, error } = useOctavusChat({ transport });
14
14
 
15
15
  // status: 'idle' | 'streaming' | 'error'
16
16
  // Each message has status: 'streaming' | 'done'
@@ -20,8 +20,24 @@ const { messages, status, error } = useOctavusChat({...});
20
20
  ## Building a Streaming UI
21
21
 
22
22
  ```tsx
23
- function Chat() {
24
- const { messages, status, error, send, stop } = useOctavusChat({...});
23
+ import { useMemo } from 'react';
24
+ import { useOctavusChat, createHttpTransport } from '@octavus/react';
25
+
26
+ function Chat({ sessionId }: { sessionId: string }) {
27
+ const transport = useMemo(
28
+ () =>
29
+ createHttpTransport({
30
+ triggerRequest: (triggerName, input) =>
31
+ fetch('/api/trigger', {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ sessionId, triggerName, input }),
35
+ }),
36
+ }),
37
+ [sessionId],
38
+ );
39
+
40
+ const { messages, status, error, send, stop } = useOctavusChat({ transport });
25
41
 
26
42
  return (
27
43
  <div>
@@ -31,14 +47,10 @@ function Chat() {
31
47
  ))}
32
48
 
33
49
  {/* Error state */}
34
- {error && (
35
- <div className="text-red-500">{error.message}</div>
36
- )}
50
+ {error && <div className="text-red-500">{error.message}</div>}
37
51
 
38
52
  {/* Stop button during streaming */}
39
- {status === 'streaming' && (
40
- <button onClick={stop}>Stop</button>
41
- )}
53
+ {status === 'streaming' && <button onClick={stop}>Stop</button>}
42
54
  </div>
43
55
  );
44
56
  }
@@ -72,11 +84,9 @@ function ReasoningPart({ part }: { part: UIReasoningPart }) {
72
84
  {part.status === 'streaming' ? '💭 Thinking...' : '💭 Thought process'}
73
85
  {expanded ? '▼' : '▶'}
74
86
  </button>
75
-
87
+
76
88
  {expanded && (
77
- <pre className="mt-2 text-sm text-gray-600">
78
- {part.text}
79
- </pre>
89
+ <pre className="mt-2 text-sm text-gray-600">{part.text}</pre>
80
90
  )}
81
91
  </div>
82
92
  );
@@ -95,19 +105,17 @@ function ToolCallPart({ part }: { part: UIToolCallPart }) {
95
105
  <div className="border rounded p-3">
96
106
  <div className="flex items-center gap-2">
97
107
  <span className="text-lg">🔧</span>
98
- <span className="font-medium">
99
- {part.displayName || part.toolName}
100
- </span>
108
+ <span className="font-medium">{part.displayName || part.toolName}</span>
101
109
  <StatusBadge status={part.status} />
102
110
  </div>
103
-
111
+
104
112
  {/* Show result when done */}
105
113
  {part.status === 'done' && part.result && (
106
114
  <pre className="mt-2 text-xs bg-gray-50 p-2 rounded">
107
115
  {JSON.stringify(part.result, null, 2)}
108
116
  </pre>
109
117
  )}
110
-
118
+
111
119
  {/* Show error if failed */}
112
120
  {part.status === 'error' && (
113
121
  <p className="mt-2 text-red-500 text-sm">{part.error}</p>
@@ -133,9 +141,7 @@ function StatusBadge({ status }: { status: UIToolCallPart['status'] }) {
133
141
  ## Status Indicator
134
142
 
135
143
  ```tsx
136
- function StatusIndicator() {
137
- const { status } = useOctavusChat({...});
138
-
144
+ function StatusIndicator({ status }: { status: ChatStatus }) {
139
145
  switch (status) {
140
146
  case 'idle':
141
147
  return null;
@@ -151,7 +157,7 @@ function StatusIndicator() {
151
157
 
152
158
  ```tsx
153
159
  useOctavusChat({
154
- onTrigger: ...,
160
+ transport,
155
161
  onFinish: () => {
156
162
  console.log('Stream completed');
157
163
  // Scroll to bottom, play sound, etc.
@@ -168,7 +174,7 @@ useOctavusChat({
168
174
  Stop the current stream and finalize any partial message:
169
175
 
170
176
  ```tsx
171
- const { status, stop } = useOctavusChat({...});
177
+ const { status, stop } = useOctavusChat({ transport });
172
178
 
173
179
  // Stop button
174
180
  {status === 'streaming' && (
@@ -188,17 +194,19 @@ When `stop()` is called:
188
194
  Content from named threads (like "summary") streams separately and is identified by the `thread` property:
189
195
 
190
196
  ```tsx
191
- import { isOtherThread } from '@octavus/react';
197
+ import { isOtherThread, type UIMessage } from '@octavus/react';
192
198
 
193
199
  function MessageBubble({ message }: { message: UIMessage }) {
194
200
  // Separate main thread from named threads
195
- const mainParts = message.parts.filter(p => !isOtherThread(p));
196
- const otherParts = message.parts.filter(p => isOtherThread(p));
201
+ const mainParts = message.parts.filter((p) => !isOtherThread(p));
202
+ const otherParts = message.parts.filter((p) => isOtherThread(p));
197
203
 
198
204
  return (
199
205
  <div>
200
206
  {/* Main conversation */}
201
- {mainParts.map((part, i) => <PartRenderer key={i} part={part} />)}
207
+ {mainParts.map((part, i) => (
208
+ <PartRenderer key={i} part={part} />
209
+ ))}
202
210
 
203
211
  {/* Named thread content (e.g., summarization) */}
204
212
  {otherParts.length > 0 && (
@@ -206,7 +214,9 @@ function MessageBubble({ message }: { message: UIMessage }) {
206
214
  <div className="text-amber-600 font-medium mb-2">
207
215
  Background processing
208
216
  </div>
209
- {otherParts.map((part, i) => <PartRenderer key={i} part={part} />)}
217
+ {otherParts.map((part, i) => (
218
+ <PartRenderer key={i} part={part} />
219
+ ))}
210
220
  </div>
211
221
  )}
212
222
  </div>