@octavus/docs 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/01-getting-started/02-quickstart.md +4 -3
- package/content/02-server-sdk/01-overview.md +11 -5
- package/content/02-server-sdk/02-sessions.md +6 -3
- package/content/02-server-sdk/03-tools.md +4 -2
- package/content/02-server-sdk/04-streaming.md +12 -4
- package/content/03-client-sdk/01-overview.md +11 -5
- package/content/03-client-sdk/05-socket-transport.md +263 -153
- package/content/03-client-sdk/06-http-transport.md +280 -0
- package/content/04-protocol/01-overview.md +1 -0
- package/content/04-protocol/03-triggers.md +10 -2
- package/content/04-protocol/04-tools.md +12 -3
- package/content/04-protocol/06-agent-config.md +36 -3
- package/content/04-protocol/07-provider-options.md +275 -0
- package/content/05-api-reference/01-overview.md +3 -17
- package/content/06-examples/01-overview.md +27 -0
- package/content/06-examples/02-nextjs-chat.md +343 -0
- package/content/06-examples/03-socket-chat.md +392 -0
- package/content/06-examples/_meta.md +5 -0
- package/dist/chunk-5M7DS4DF.js +519 -0
- package/dist/chunk-5M7DS4DF.js.map +1 -0
- package/dist/chunk-7KXF63FV.js +537 -0
- package/dist/chunk-7KXF63FV.js.map +1 -0
- package/dist/chunk-H6JGSSAJ.js +519 -0
- package/dist/chunk-H6JGSSAJ.js.map +1 -0
- package/dist/chunk-JZRABTHU.js +519 -0
- package/dist/chunk-JZRABTHU.js.map +1 -0
- package/dist/chunk-OL5QDJ42.js +483 -0
- package/dist/chunk-OL5QDJ42.js.map +1 -0
- package/dist/chunk-PMOVVTHO.js +519 -0
- package/dist/chunk-PMOVVTHO.js.map +1 -0
- package/dist/chunk-R5MTVABN.js +439 -0
- package/dist/chunk-R5MTVABN.js.map +1 -0
- package/dist/chunk-RJ4H4YVA.js +519 -0
- package/dist/chunk-RJ4H4YVA.js.map +1 -0
- package/dist/chunk-S5U4IWCR.js +439 -0
- package/dist/chunk-S5U4IWCR.js.map +1 -0
- package/dist/chunk-TQJG6EBM.js +537 -0
- package/dist/chunk-TQJG6EBM.js.map +1 -0
- package/dist/chunk-UCJE36LL.js +519 -0
- package/dist/chunk-UCJE36LL.js.map +1 -0
- package/dist/chunk-WW7TRC7S.js +519 -0
- package/dist/chunk-WW7TRC7S.js.map +1 -0
- package/dist/content.js +1 -1
- package/dist/docs.json +60 -15
- package/dist/index.js +1 -1
- package/dist/search-index.json +1 -1
- package/dist/search.js +1 -1
- package/dist/search.js.map +1 -1
- package/dist/sections.json +68 -15
- 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
|
-
##
|
|
20
|
+
## Patterns Overview
|
|
21
21
|
|
|
22
|
-
|
|
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
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
176
|
+
When `sessionId` changes, the hook automatically reinitializes with the new transport.
|
|
177
|
+
|
|
178
|
+
### Server Handler with Init Message
|
|
51
179
|
|
|
52
|
-
|
|
180
|
+
When using client-provided sessionId, the server must handle an `init` message:
|
|
53
181
|
|
|
54
182
|
```typescript
|
|
55
|
-
|
|
56
|
-
|
|
183
|
+
sockServer.on('connection', (conn) => {
|
|
184
|
+
let session: AgentSession | null = null;
|
|
185
|
+
let abortController: AbortController | null = null;
|
|
57
186
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
334
|
+
const sock = new SockJS('/octavus');
|
|
136
335
|
|
|
137
|
-
|
|
138
|
-
reconnectAttempts.current = 0;
|
|
139
|
-
resolve(
|
|
336
|
+
sock.onopen = () => {
|
|
337
|
+
reconnectAttempts.current = 0;
|
|
338
|
+
resolve(sock);
|
|
140
339
|
};
|
|
141
340
|
|
|
142
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
##
|
|
361
|
+
## Framework Notes
|
|
169
362
|
|
|
170
|
-
|
|
363
|
+
### Meteor
|
|
171
364
|
|
|
172
|
-
|
|
365
|
+
Meteor's bundler may have issues with ES6 imports of `sockjs-client`. Use `require()` instead:
|
|
173
366
|
|
|
174
367
|
```typescript
|
|
175
|
-
|
|
176
|
-
import
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
});
|
|
371
|
+
// ✅ Works in Meteor
|
|
372
|
+
const SockJS: typeof import('sockjs-client') = require('sockjs-client');
|
|
223
373
|
```
|
|
224
374
|
|
|
225
|
-
###
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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).
|