@octavus/docs 0.0.6 → 0.0.8

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 (53) hide show
  1. package/content/01-getting-started/02-quickstart.md +28 -19
  2. package/content/02-server-sdk/01-overview.md +34 -17
  3. package/content/02-server-sdk/02-sessions.md +6 -3
  4. package/content/02-server-sdk/03-tools.md +4 -2
  5. package/content/02-server-sdk/04-streaming.md +12 -4
  6. package/content/03-client-sdk/01-overview.md +107 -42
  7. package/content/03-client-sdk/02-messages.md +71 -19
  8. package/content/03-client-sdk/03-streaming.md +38 -28
  9. package/content/03-client-sdk/05-socket-transport.md +414 -0
  10. package/content/03-client-sdk/06-http-transport.md +280 -0
  11. package/content/04-protocol/03-triggers.md +48 -21
  12. package/content/04-protocol/04-tools.md +25 -15
  13. package/content/05-api-reference/01-overview.md +3 -17
  14. package/content/06-examples/01-overview.md +27 -0
  15. package/content/06-examples/02-nextjs-chat.md +343 -0
  16. package/content/06-examples/03-socket-chat.md +392 -0
  17. package/content/06-examples/_meta.md +5 -0
  18. package/dist/chunk-232K4EME.js +439 -0
  19. package/dist/chunk-232K4EME.js.map +1 -0
  20. package/dist/chunk-2JDZLMS3.js +439 -0
  21. package/dist/chunk-2JDZLMS3.js.map +1 -0
  22. package/dist/chunk-5M7DS4DF.js +519 -0
  23. package/dist/chunk-5M7DS4DF.js.map +1 -0
  24. package/dist/chunk-7AS4ST73.js +421 -0
  25. package/dist/chunk-7AS4ST73.js.map +1 -0
  26. package/dist/chunk-H6JGSSAJ.js +519 -0
  27. package/dist/chunk-H6JGSSAJ.js.map +1 -0
  28. package/dist/chunk-JZRABTHU.js +519 -0
  29. package/dist/chunk-JZRABTHU.js.map +1 -0
  30. package/dist/chunk-OECAPVSX.js +439 -0
  31. package/dist/chunk-OECAPVSX.js.map +1 -0
  32. package/dist/chunk-OL5QDJ42.js +483 -0
  33. package/dist/chunk-OL5QDJ42.js.map +1 -0
  34. package/dist/chunk-PMOVVTHO.js +519 -0
  35. package/dist/chunk-PMOVVTHO.js.map +1 -0
  36. package/dist/chunk-R5MTVABN.js +439 -0
  37. package/dist/chunk-R5MTVABN.js.map +1 -0
  38. package/dist/chunk-RJ4H4YVA.js +519 -0
  39. package/dist/chunk-RJ4H4YVA.js.map +1 -0
  40. package/dist/chunk-S5U4IWCR.js +439 -0
  41. package/dist/chunk-S5U4IWCR.js.map +1 -0
  42. package/dist/chunk-UCJE36LL.js +519 -0
  43. package/dist/chunk-UCJE36LL.js.map +1 -0
  44. package/dist/chunk-WW7TRC7S.js +519 -0
  45. package/dist/chunk-WW7TRC7S.js.map +1 -0
  46. package/dist/content.js +1 -1
  47. package/dist/docs.json +57 -12
  48. package/dist/index.js +1 -1
  49. package/dist/search-index.json +1 -1
  50. package/dist/search.js +1 -1
  51. package/dist/search.js.map +1 -1
  52. package/dist/sections.json +65 -12
  53. package/package.json +2 -2
@@ -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>
@@ -0,0 +1,414 @@
1
+ ---
2
+ title: Socket Transport
3
+ description: Using WebSocket or SockJS for real-time streaming with Octavus.
4
+ ---
5
+
6
+ # Socket Transport
7
+
8
+ The socket transport enables real-time bidirectional communication using WebSocket or SockJS. Use this when you need persistent connections, custom server events, or when HTTP/SSE isn't suitable for your infrastructure.
9
+
10
+ ## When to Use Socket Transport
11
+
12
+ | Use Case | Recommended Transport |
13
+ |----------|----------------------|
14
+ | Standard web apps (Next.js, etc.) | HTTP (`createHttpTransport`) |
15
+ | Real-time apps with custom events | Socket (`createSocketTransport`) |
16
+ | Apps behind proxies that don't support SSE | Socket |
17
+ | Need for typing indicators, presence, etc. | Socket |
18
+ | Meteor, Phoenix, or socket-based frameworks | Socket |
19
+
20
+ ## Patterns Overview
21
+
22
+ There are two main patterns for socket-based integrations:
23
+
24
+ | Pattern | When to Use |
25
+ |---------|-------------|
26
+ | [Server-Managed Sessions](#server-managed-sessions-recommended) | **Recommended.** Server creates sessions lazily. Client doesn't need sessionId. |
27
+ | [Client-Provided Session ID](#client-provided-session-id) | When client must control session creation or pass sessionId from URL. |
28
+
29
+ ## Server-Managed Sessions (Recommended)
30
+
31
+ The cleanest pattern is to have the server manage session lifecycle. The client never needs to know about `sessionId` — the server creates it lazily on first message.
32
+
33
+ ### Client Setup
34
+
35
+ ```typescript
36
+ import { useMemo } from 'react';
37
+ import SockJS from 'sockjs-client';
38
+ import { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';
39
+
40
+ function connectSocket(): Promise<SocketLike> {
41
+ return new Promise((resolve, reject) => {
42
+ const sock = new SockJS('/octavus');
43
+ sock.onopen = () => resolve(sock);
44
+ sock.onerror = () => reject(new Error('Connection failed'));
45
+ });
46
+ }
47
+
48
+ function Chat() {
49
+ // Transport is stable — no dependencies on sessionId
50
+ const transport = useMemo(
51
+ () => createSocketTransport({ connect: connectSocket }),
52
+ [],
53
+ );
54
+
55
+ const { messages, status, send } = useOctavusChat({ transport });
56
+
57
+ const sendMessage = async (text: string) => {
58
+ await send(
59
+ 'user-message',
60
+ { USER_MESSAGE: text },
61
+ { userMessage: { content: text } },
62
+ );
63
+ };
64
+
65
+ // ... render messages
66
+ }
67
+ ```
68
+
69
+ ### Server Setup (Express + SockJS)
70
+
71
+ The server creates a session on first trigger message:
72
+
73
+ ```typescript
74
+ import sockjs from 'sockjs';
75
+ import { OctavusClient, type AgentSession } from '@octavus/server-sdk';
76
+
77
+ const client = new OctavusClient({
78
+ baseUrl: process.env.OCTAVUS_API_URL!,
79
+ apiKey: process.env.OCTAVUS_API_KEY!,
80
+ });
81
+
82
+ function createSocketHandler() {
83
+ return (conn: sockjs.Connection) => {
84
+ let session: AgentSession | null = null;
85
+ let abortController: AbortController | null = null;
86
+
87
+ conn.on('data', (rawData: string) => {
88
+ void handleMessage(rawData);
89
+ });
90
+
91
+ async function handleMessage(rawData: string) {
92
+ const msg = JSON.parse(rawData);
93
+
94
+ if (msg.type === 'stop') {
95
+ abortController?.abort();
96
+ return;
97
+ }
98
+
99
+ if (msg.type === 'trigger') {
100
+ // Create session lazily on first trigger
101
+ if (!session) {
102
+ const sessionId = await client.agentSessions.create('your-agent-id', {
103
+ // Initial input variables
104
+ COMPANY_NAME: 'Acme Corp',
105
+ });
106
+ session = client.agentSessions.attach(sessionId, {
107
+ tools: {
108
+ // Your tool handlers
109
+ },
110
+ });
111
+ }
112
+
113
+ abortController = new AbortController();
114
+
115
+ // Iterate events directly — no SSE parsing needed
116
+ const events = session.trigger(msg.triggerName, msg.input);
117
+
118
+ try {
119
+ for await (const event of events) {
120
+ if (abortController.signal.aborted) break;
121
+ conn.write(JSON.stringify(event));
122
+ }
123
+ } catch {
124
+ // Handle errors
125
+ }
126
+ }
127
+ }
128
+
129
+ conn.on('close', () => abortController?.abort());
130
+ };
131
+ }
132
+
133
+ const sockServer = sockjs.createServer({ prefix: '/octavus' });
134
+ sockServer.on('connection', createSocketHandler());
135
+ sockServer.installHandlers(httpServer);
136
+ ```
137
+
138
+ **Benefits of this pattern:**
139
+ - Client code is simple — no sessionId management
140
+ - No transport caching issues
141
+ - Session is created only when needed
142
+ - Server controls session configuration
143
+
144
+ ## Client-Provided Session ID
145
+
146
+ If you need the client to control the session (e.g., resuming a specific session from URL), pass the sessionId in an init message after connecting:
147
+
148
+ ```typescript
149
+ import { useMemo } from 'react';
150
+ import SockJS from 'sockjs-client';
151
+ import { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';
152
+
153
+ function Chat({ sessionId }: { sessionId: string }) {
154
+ const transport = useMemo(
155
+ () =>
156
+ createSocketTransport({
157
+ connect: () =>
158
+ new Promise((resolve, reject) => {
159
+ const sock = new SockJS('/octavus');
160
+ sock.onopen = () => {
161
+ // Send init message with sessionId
162
+ sock.send(JSON.stringify({ type: 'init', sessionId }));
163
+ resolve(sock);
164
+ };
165
+ sock.onerror = () => reject(new Error('Connection failed'));
166
+ }),
167
+ }),
168
+ [sessionId],
169
+ );
170
+
171
+ const { messages, status, send } = useOctavusChat({ transport });
172
+ // ... render chat
173
+ }
174
+ ```
175
+
176
+ When `sessionId` changes, the hook automatically reinitializes with the new transport.
177
+
178
+ ### Server Handler with Init Message
179
+
180
+ When using client-provided sessionId, the server must handle an `init` message:
181
+
182
+ ```typescript
183
+ sockServer.on('connection', (conn) => {
184
+ let session: AgentSession | null = null;
185
+ let abortController: AbortController | null = null;
186
+
187
+ conn.on('data', (rawData: string) => {
188
+ void handleMessage(rawData);
189
+ });
190
+
191
+ async function handleMessage(rawData: string) {
192
+ const msg = JSON.parse(rawData);
193
+
194
+ // Handle session initialization
195
+ if (msg.type === 'init') {
196
+ session = client.agentSessions.attach(msg.sessionId, {
197
+ tools: { /* ... */ },
198
+ });
199
+ return;
200
+ }
201
+
202
+ if (msg.type === 'stop') {
203
+ abortController?.abort();
204
+ return;
205
+ }
206
+
207
+ // All other messages require initialized session
208
+ if (!session) {
209
+ conn.write(JSON.stringify({
210
+ type: 'error',
211
+ errorText: 'Session not initialized. Send init message first.',
212
+ }));
213
+ return;
214
+ }
215
+
216
+ if (msg.type === 'trigger') {
217
+ // ... handle trigger (same as server-managed pattern)
218
+ }
219
+ }
220
+ });
221
+ ```
222
+
223
+ ## Async Session ID
224
+
225
+ When the session ID is fetched asynchronously (e.g., from an API), you have two options:
226
+
227
+ ### Option 1: Conditionally Render (Recommended)
228
+
229
+ Don't render the chat component until `sessionId` is available:
230
+
231
+ ```tsx
232
+ function ChatPage() {
233
+ const [sessionId, setSessionId] = useState<string | null>(null);
234
+
235
+ useEffect(() => {
236
+ api.createSession().then(res => setSessionId(res.sessionId));
237
+ }, []);
238
+
239
+ // Don't render until sessionId is ready
240
+ if (!sessionId) {
241
+ return <LoadingSpinner />;
242
+ }
243
+
244
+ return <Chat sessionId={sessionId} />;
245
+ }
246
+ ```
247
+
248
+ This is the cleanest approach — the `Chat` component always receives a valid `sessionId`.
249
+
250
+ ### Option 2: Server-Managed Sessions
251
+
252
+ Use the [server-managed sessions pattern](#server-managed-sessions-recommended) where the server creates the session lazily. The client never needs to know about `sessionId`.
253
+
254
+ ## Native WebSocket
255
+
256
+ If you're using native WebSocket instead of SockJS, you can pass sessionId via URL:
257
+
258
+ ```typescript
259
+ const transport = useMemo(
260
+ () =>
261
+ createSocketTransport({
262
+ connect: () =>
263
+ new Promise((resolve, reject) => {
264
+ const ws = new WebSocket(
265
+ `wss://your-server.com/octavus?sessionId=${sessionId}`
266
+ );
267
+ ws.onopen = () => resolve(ws);
268
+ ws.onerror = () => reject(new Error('WebSocket connection failed'));
269
+ }),
270
+ }),
271
+ [sessionId],
272
+ );
273
+ ```
274
+
275
+ When `sessionId` changes, the hook automatically reinitializes with the new transport.
276
+
277
+ ## Custom Events
278
+
279
+ Handle custom events alongside Octavus stream events:
280
+
281
+ ```typescript
282
+ const transport = createSocketTransport({
283
+ connect: connectSocket,
284
+
285
+ onMessage: (data) => {
286
+ const msg = data as { type: string; [key: string]: unknown };
287
+
288
+ switch (msg.type) {
289
+ case 'typing-indicator':
290
+ setAgentTyping(msg.isTyping as boolean);
291
+ break;
292
+
293
+ case 'presence-update':
294
+ setOnlineUsers(msg.users as string[]);
295
+ break;
296
+
297
+ case 'notification':
298
+ showToast(msg.message as string);
299
+ break;
300
+
301
+ // Octavus events (text-delta, finish, etc.) are handled automatically
302
+ }
303
+ },
304
+ });
305
+ ```
306
+
307
+ ## Connection Management
308
+
309
+ ### Handling Disconnections
310
+
311
+ ```typescript
312
+ const transport = createSocketTransport({
313
+ connect: connectSocket,
314
+
315
+ onClose: () => {
316
+ console.log('Socket disconnected');
317
+ setConnectionStatus('disconnected');
318
+ },
319
+ });
320
+ ```
321
+
322
+ ### Reconnection with Exponential Backoff
323
+
324
+ ```typescript
325
+ import { useRef, useCallback, useMemo } from 'react';
326
+ import { createSocketTransport, type SocketLike } from '@octavus/react';
327
+
328
+ function useReconnectingTransport() {
329
+ const reconnectAttempts = useRef(0);
330
+ const maxAttempts = 5;
331
+
332
+ const connect = useCallback((): Promise<SocketLike> => {
333
+ return new Promise((resolve, reject) => {
334
+ const sock = new SockJS('/octavus');
335
+
336
+ sock.onopen = () => {
337
+ reconnectAttempts.current = 0;
338
+ resolve(sock);
339
+ };
340
+
341
+ sock.onerror = () => {
342
+ if (reconnectAttempts.current < maxAttempts) {
343
+ reconnectAttempts.current++;
344
+ const delay = Math.min(1000 * 2 ** reconnectAttempts.current, 30000);
345
+ console.log(`Reconnecting in ${delay}ms...`);
346
+ setTimeout(() => connect().then(resolve).catch(reject), delay);
347
+ } else {
348
+ reject(new Error('Max reconnection attempts reached'));
349
+ }
350
+ };
351
+ });
352
+ }, []);
353
+
354
+ return useMemo(
355
+ () => createSocketTransport({ connect }),
356
+ [connect],
357
+ );
358
+ }
359
+ ```
360
+
361
+ ## Framework Notes
362
+
363
+ ### Meteor
364
+
365
+ Meteor's bundler may have issues with ES6 imports of `sockjs-client`. Use `require()` instead:
366
+
367
+ ```typescript
368
+ // ❌ May fail in Meteor
369
+ import SockJS from 'sockjs-client';
370
+
371
+ // ✅ Works in Meteor
372
+ const SockJS: typeof import('sockjs-client') = require('sockjs-client');
373
+ ```
374
+
375
+ ### SockJS vs WebSocket
376
+
377
+ | Feature | WebSocket | SockJS |
378
+ |---------|-----------|--------|
379
+ | Browser support | Modern browsers | All browsers (with fallbacks) |
380
+ | Session ID | Via URL query params | Via init message |
381
+ | Proxy compatibility | Varies | Excellent (polling fallback) |
382
+ | Setup complexity | Lower | Higher (requires server library) |
383
+
384
+ ## Protocol Reference
385
+
386
+ ### Client → Server Messages
387
+
388
+ ```typescript
389
+ // Initialize session (only for client-provided sessionId pattern)
390
+ { type: 'init', sessionId: string }
391
+
392
+ // Trigger an action
393
+ { type: 'trigger', triggerName: string, input?: Record<string, unknown> }
394
+
395
+ // Stop current stream
396
+ { type: 'stop' }
397
+ ```
398
+
399
+ ### Server → Client Messages
400
+
401
+ The server sends Octavus `StreamEvent` objects as JSON. See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list.
402
+
403
+ ```typescript
404
+ // Examples
405
+ { type: 'start', messageId: '...' }
406
+ { type: 'text-delta', id: '...', delta: 'Hello' }
407
+ { type: 'tool-input-start', toolCallId: '...', toolName: 'get-user' }
408
+ { type: 'finish', finishReason: 'stop' }
409
+ { type: 'error', errorText: 'Something went wrong' }
410
+ ```
411
+
412
+ ## Full Example
413
+
414
+ For a complete walkthrough of building a chat interface with SockJS, see the [Socket Chat Example](/docs/examples/socket-chat).