@octavus/docs 0.0.7 → 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 (42) hide show
  1. package/content/01-getting-started/02-quickstart.md +4 -3
  2. package/content/02-server-sdk/01-overview.md +11 -5
  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 +11 -5
  7. package/content/03-client-sdk/05-socket-transport.md +263 -153
  8. package/content/03-client-sdk/06-http-transport.md +280 -0
  9. package/content/04-protocol/03-triggers.md +10 -2
  10. package/content/05-api-reference/01-overview.md +3 -17
  11. package/content/06-examples/01-overview.md +27 -0
  12. package/content/06-examples/02-nextjs-chat.md +343 -0
  13. package/content/06-examples/03-socket-chat.md +392 -0
  14. package/content/06-examples/_meta.md +5 -0
  15. package/dist/chunk-5M7DS4DF.js +519 -0
  16. package/dist/chunk-5M7DS4DF.js.map +1 -0
  17. package/dist/chunk-H6JGSSAJ.js +519 -0
  18. package/dist/chunk-H6JGSSAJ.js.map +1 -0
  19. package/dist/chunk-JZRABTHU.js +519 -0
  20. package/dist/chunk-JZRABTHU.js.map +1 -0
  21. package/dist/chunk-OL5QDJ42.js +483 -0
  22. package/dist/chunk-OL5QDJ42.js.map +1 -0
  23. package/dist/chunk-PMOVVTHO.js +519 -0
  24. package/dist/chunk-PMOVVTHO.js.map +1 -0
  25. package/dist/chunk-R5MTVABN.js +439 -0
  26. package/dist/chunk-R5MTVABN.js.map +1 -0
  27. package/dist/chunk-RJ4H4YVA.js +519 -0
  28. package/dist/chunk-RJ4H4YVA.js.map +1 -0
  29. package/dist/chunk-S5U4IWCR.js +439 -0
  30. package/dist/chunk-S5U4IWCR.js.map +1 -0
  31. package/dist/chunk-UCJE36LL.js +519 -0
  32. package/dist/chunk-UCJE36LL.js.map +1 -0
  33. package/dist/chunk-WW7TRC7S.js +519 -0
  34. package/dist/chunk-WW7TRC7S.js.map +1 -0
  35. package/dist/content.js +1 -1
  36. package/dist/docs.json +46 -10
  37. package/dist/index.js +1 -1
  38. package/dist/search-index.json +1 -1
  39. package/dist/search.js +1 -1
  40. package/dist/search.js.map +1 -1
  41. package/dist/sections.json +54 -10
  42. package/package.json +1 -1
@@ -17,13 +17,138 @@ The socket transport enables real-time bidirectional communication using WebSock
17
17
  | Need for typing indicators, presence, etc. | Socket |
18
18
  | Meteor, Phoenix, or socket-based frameworks | Socket |
19
19
 
20
- ## Basic Setup
20
+ ## Patterns Overview
21
21
 
22
- ### Native WebSocket
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:
23
147
 
24
148
  ```typescript
25
149
  import { useMemo } from 'react';
26
- import { useOctavusChat, createSocketTransport } from '@octavus/react';
150
+ import SockJS from 'sockjs-client';
151
+ import { useOctavusChat, createSocketTransport, type SocketLike } from '@octavus/react';
27
152
 
28
153
  function Chat({ sessionId }: { sessionId: string }) {
29
154
  const transport = useMemo(
@@ -31,47 +156,131 @@ function Chat({ sessionId }: { sessionId: string }) {
31
156
  createSocketTransport({
32
157
  connect: () =>
33
158
  new Promise((resolve, reject) => {
34
- const ws = new WebSocket(
35
- `wss://your-server.com/octavus?sessionId=${sessionId}`
36
- );
37
- ws.onopen = () => resolve(ws);
38
- ws.onerror = () => reject(new Error('WebSocket connection failed'));
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'));
39
166
  }),
40
167
  }),
41
168
  [sessionId],
42
169
  );
43
170
 
44
171
  const { messages, status, send } = useOctavusChat({ transport });
45
-
46
- // ... rest of your component
172
+ // ... render chat
47
173
  }
48
174
  ```
49
175
 
50
- ### SockJS
176
+ When `sessionId` changes, the hook automatically reinitializes with the new transport.
177
+
178
+ ### Server Handler with Init Message
51
179
 
52
- SockJS provides WebSocket-like functionality with fallbacks for older browsers:
180
+ When using client-provided sessionId, the server must handle an `init` message:
53
181
 
54
182
  ```typescript
55
- import SockJS from 'sockjs-client';
56
- import { createSocketTransport } from '@octavus/react';
183
+ sockServer.on('connection', (conn) => {
184
+ let session: AgentSession | null = null;
185
+ let abortController: AbortController | null = null;
57
186
 
58
- const transport = createSocketTransport({
59
- connect: () =>
60
- new Promise((resolve, reject) => {
61
- const sock = new SockJS('/octavus-stream');
62
- sock.onopen = () => resolve(sock);
63
- sock.onerror = () => reject(new Error('SockJS connection failed'));
64
- }),
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
+ }
65
220
  });
66
221
  ```
67
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
+
68
277
  ## Custom Events
69
278
 
70
- The socket transport can handle custom events alongside Octavus stream events. Use the `onMessage` callback to process any message your server sends:
279
+ Handle custom events alongside Octavus stream events:
71
280
 
72
281
  ```typescript
73
282
  const transport = createSocketTransport({
74
- connect: () => /* ... */,
283
+ connect: connectSocket,
75
284
 
76
285
  onMessage: (data) => {
77
286
  const msg = data as { type: string; [key: string]: unknown };
@@ -90,198 +299,96 @@ const transport = createSocketTransport({
90
299
  break;
91
300
 
92
301
  // Octavus events (text-delta, finish, etc.) are handled automatically
93
- // No need to handle them here
94
302
  }
95
303
  },
96
304
  });
97
305
  ```
98
306
 
99
- > **Note**: Octavus stream events (`text-delta`, `finish`, `tool-input-start`, etc.) are automatically processed by the transport. The `onMessage` callback is for your custom events only.
100
-
101
307
  ## Connection Management
102
308
 
103
309
  ### Handling Disconnections
104
310
 
105
- Use the `onClose` callback for cleanup or reconnection logic:
106
-
107
311
  ```typescript
108
312
  const transport = createSocketTransport({
109
- connect: () => /* ... */,
313
+ connect: connectSocket,
110
314
 
111
315
  onClose: () => {
112
316
  console.log('Socket disconnected');
113
317
  setConnectionStatus('disconnected');
114
-
115
- // Optional: trigger reconnection
116
- // reconnect();
117
318
  },
118
319
  });
119
320
  ```
120
321
 
121
- ### Reconnection Pattern
122
-
123
- For production apps, implement reconnection with exponential backoff:
322
+ ### Reconnection with Exponential Backoff
124
323
 
125
324
  ```typescript
126
325
  import { useRef, useCallback, useMemo } from 'react';
127
326
  import { createSocketTransport, type SocketLike } from '@octavus/react';
128
327
 
129
- function useReconnectingTransport(sessionId: string) {
328
+ function useReconnectingTransport() {
130
329
  const reconnectAttempts = useRef(0);
131
330
  const maxAttempts = 5;
132
331
 
133
332
  const connect = useCallback((): Promise<SocketLike> => {
134
333
  return new Promise((resolve, reject) => {
135
- const ws = new WebSocket(`wss://your-server.com/octavus?sessionId=${sessionId}`);
334
+ const sock = new SockJS('/octavus');
136
335
 
137
- ws.onopen = () => {
138
- reconnectAttempts.current = 0; // Reset on success
139
- resolve(ws);
336
+ sock.onopen = () => {
337
+ reconnectAttempts.current = 0;
338
+ resolve(sock);
140
339
  };
141
340
 
142
- ws.onerror = () => {
341
+ sock.onerror = () => {
143
342
  if (reconnectAttempts.current < maxAttempts) {
144
343
  reconnectAttempts.current++;
145
344
  const delay = Math.min(1000 * 2 ** reconnectAttempts.current, 30000);
146
- console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current})`);
345
+ console.log(`Reconnecting in ${delay}ms...`);
147
346
  setTimeout(() => connect().then(resolve).catch(reject), delay);
148
347
  } else {
149
348
  reject(new Error('Max reconnection attempts reached'));
150
349
  }
151
350
  };
152
351
  });
153
- }, [sessionId]);
352
+ }, []);
154
353
 
155
354
  return useMemo(
156
- () =>
157
- createSocketTransport({
158
- connect,
159
- onClose: () => {
160
- console.log('Connection closed');
161
- },
162
- }),
355
+ () => createSocketTransport({ connect }),
163
356
  [connect],
164
357
  );
165
358
  }
166
359
  ```
167
360
 
168
- ## Server-Side Implementation
361
+ ## Framework Notes
169
362
 
170
- Your server needs to handle socket connections and forward events from the Octavus platform. Here's a basic pattern:
363
+ ### Meteor
171
364
 
172
- ### Express + ws (WebSocket)
365
+ Meteor's bundler may have issues with ES6 imports of `sockjs-client`. Use `require()` instead:
173
366
 
174
367
  ```typescript
175
- import { WebSocketServer } from 'ws';
176
- import { OctavusClient } from '@octavus/server-sdk';
177
-
178
- const octavus = new OctavusClient({
179
- baseUrl: process.env.OCTAVUS_API_URL!,
180
- apiKey: process.env.OCTAVUS_API_KEY!,
181
- });
182
-
183
- const wss = new WebSocketServer({ server });
184
-
185
- wss.on('connection', (ws, req) => {
186
- const sessionId = new URL(req.url!, 'http://localhost').searchParams.get('sessionId');
187
-
188
- ws.on('message', async (data) => {
189
- const message = JSON.parse(data.toString());
190
-
191
- if (message.type === 'trigger') {
192
- const session = octavus.agentSessions.attach(sessionId!, {
193
- tools: {
194
- // Your tool handlers
195
- },
196
- });
197
-
198
- const { stream } = session.trigger(message.triggerName, message.input);
199
-
200
- // Forward SSE stream to WebSocket
201
- const reader = stream.getReader();
202
- const decoder = new TextDecoder();
203
-
204
- while (true) {
205
- const { done, value } = await reader.read();
206
- if (done) break;
207
-
208
- const text = decoder.decode(value);
209
- // Parse SSE format and send as JSON
210
- for (const line of text.split('\n')) {
211
- if (line.startsWith('data: ') && line !== 'data: [DONE]') {
212
- ws.send(line.slice(6)); // Send JSON directly
213
- }
214
- }
215
- }
216
- }
368
+ // May fail in Meteor
369
+ import SockJS from 'sockjs-client';
217
370
 
218
- if (message.type === 'stop') {
219
- // Handle stop request
220
- }
221
- });
222
- });
371
+ // Works in Meteor
372
+ const SockJS: typeof import('sockjs-client') = require('sockjs-client');
223
373
  ```
224
374
 
225
- ### Express + SockJS
226
-
227
- ```typescript
228
- import sockjs from 'sockjs';
229
- import { OctavusClient } from '@octavus/server-sdk';
230
-
231
- const octavus = new OctavusClient({
232
- baseUrl: process.env.OCTAVUS_API_URL!,
233
- apiKey: process.env.OCTAVUS_API_KEY!,
234
- });
235
-
236
- const sockServer = sockjs.createServer();
237
-
238
- sockServer.on('connection', (conn) => {
239
- let sessionId: string | null = null;
375
+ ### SockJS vs WebSocket
240
376
 
241
- conn.on('data', async (data) => {
242
- const message = JSON.parse(data);
243
-
244
- if (message.type === 'init') {
245
- sessionId = message.sessionId;
246
- return;
247
- }
248
-
249
- if (message.type === 'trigger' && sessionId) {
250
- const session = octavus.agentSessions.attach(sessionId, {
251
- tools: {
252
- // Your tool handlers
253
- },
254
- });
255
-
256
- const { stream } = session.trigger(message.triggerName, message.input);
257
-
258
- // Forward stream events
259
- const reader = stream.getReader();
260
- const decoder = new TextDecoder();
261
-
262
- while (true) {
263
- const { done, value } = await reader.read();
264
- if (done) break;
265
-
266
- const text = decoder.decode(value);
267
- for (const line of text.split('\n')) {
268
- if (line.startsWith('data: ') && line !== 'data: [DONE]') {
269
- conn.write(line.slice(6));
270
- }
271
- }
272
- }
273
- }
274
- });
275
- });
276
-
277
- sockServer.installHandlers(server, { prefix: '/octavus-stream' });
278
- ```
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) |
279
383
 
280
384
  ## Protocol Reference
281
385
 
282
386
  ### Client → Server Messages
283
387
 
284
388
  ```typescript
389
+ // Initialize session (only for client-provided sessionId pattern)
390
+ { type: 'init', sessionId: string }
391
+
285
392
  // Trigger an action
286
393
  { type: 'trigger', triggerName: string, input?: Record<string, unknown> }
287
394
 
@@ -299,6 +406,9 @@ The server sends Octavus `StreamEvent` objects as JSON. See [Streaming Events](/
299
406
  { type: 'text-delta', id: '...', delta: 'Hello' }
300
407
  { type: 'tool-input-start', toolCallId: '...', toolName: 'get-user' }
301
408
  { type: 'finish', finishReason: 'stop' }
409
+ { type: 'error', errorText: 'Something went wrong' }
302
410
  ```
303
411
 
412
+ ## Full Example
304
413
 
414
+ For a complete walkthrough of building a chat interface with SockJS, see the [Socket Chat Example](/docs/examples/socket-chat).