@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.
@@ -0,0 +1,304 @@
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
+ ## Basic Setup
21
+
22
+ ### Native WebSocket
23
+
24
+ ```typescript
25
+ import { useMemo } from 'react';
26
+ import { useOctavusChat, createSocketTransport } from '@octavus/react';
27
+
28
+ function Chat({ sessionId }: { sessionId: string }) {
29
+ const transport = useMemo(
30
+ () =>
31
+ createSocketTransport({
32
+ connect: () =>
33
+ 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'));
39
+ }),
40
+ }),
41
+ [sessionId],
42
+ );
43
+
44
+ const { messages, status, send } = useOctavusChat({ transport });
45
+
46
+ // ... rest of your component
47
+ }
48
+ ```
49
+
50
+ ### SockJS
51
+
52
+ SockJS provides WebSocket-like functionality with fallbacks for older browsers:
53
+
54
+ ```typescript
55
+ import SockJS from 'sockjs-client';
56
+ import { createSocketTransport } from '@octavus/react';
57
+
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
+ }),
65
+ });
66
+ ```
67
+
68
+ ## Custom Events
69
+
70
+ The socket transport can handle custom events alongside Octavus stream events. Use the `onMessage` callback to process any message your server sends:
71
+
72
+ ```typescript
73
+ const transport = createSocketTransport({
74
+ connect: () => /* ... */,
75
+
76
+ onMessage: (data) => {
77
+ const msg = data as { type: string; [key: string]: unknown };
78
+
79
+ switch (msg.type) {
80
+ case 'typing-indicator':
81
+ setAgentTyping(msg.isTyping as boolean);
82
+ break;
83
+
84
+ case 'presence-update':
85
+ setOnlineUsers(msg.users as string[]);
86
+ break;
87
+
88
+ case 'notification':
89
+ showToast(msg.message as string);
90
+ break;
91
+
92
+ // Octavus events (text-delta, finish, etc.) are handled automatically
93
+ // No need to handle them here
94
+ }
95
+ },
96
+ });
97
+ ```
98
+
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
+ ## Connection Management
102
+
103
+ ### Handling Disconnections
104
+
105
+ Use the `onClose` callback for cleanup or reconnection logic:
106
+
107
+ ```typescript
108
+ const transport = createSocketTransport({
109
+ connect: () => /* ... */,
110
+
111
+ onClose: () => {
112
+ console.log('Socket disconnected');
113
+ setConnectionStatus('disconnected');
114
+
115
+ // Optional: trigger reconnection
116
+ // reconnect();
117
+ },
118
+ });
119
+ ```
120
+
121
+ ### Reconnection Pattern
122
+
123
+ For production apps, implement reconnection with exponential backoff:
124
+
125
+ ```typescript
126
+ import { useRef, useCallback, useMemo } from 'react';
127
+ import { createSocketTransport, type SocketLike } from '@octavus/react';
128
+
129
+ function useReconnectingTransport(sessionId: string) {
130
+ const reconnectAttempts = useRef(0);
131
+ const maxAttempts = 5;
132
+
133
+ const connect = useCallback((): Promise<SocketLike> => {
134
+ return new Promise((resolve, reject) => {
135
+ const ws = new WebSocket(`wss://your-server.com/octavus?sessionId=${sessionId}`);
136
+
137
+ ws.onopen = () => {
138
+ reconnectAttempts.current = 0; // Reset on success
139
+ resolve(ws);
140
+ };
141
+
142
+ ws.onerror = () => {
143
+ if (reconnectAttempts.current < maxAttempts) {
144
+ reconnectAttempts.current++;
145
+ const delay = Math.min(1000 * 2 ** reconnectAttempts.current, 30000);
146
+ console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current})`);
147
+ setTimeout(() => connect().then(resolve).catch(reject), delay);
148
+ } else {
149
+ reject(new Error('Max reconnection attempts reached'));
150
+ }
151
+ };
152
+ });
153
+ }, [sessionId]);
154
+
155
+ return useMemo(
156
+ () =>
157
+ createSocketTransport({
158
+ connect,
159
+ onClose: () => {
160
+ console.log('Connection closed');
161
+ },
162
+ }),
163
+ [connect],
164
+ );
165
+ }
166
+ ```
167
+
168
+ ## Server-Side Implementation
169
+
170
+ Your server needs to handle socket connections and forward events from the Octavus platform. Here's a basic pattern:
171
+
172
+ ### Express + ws (WebSocket)
173
+
174
+ ```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
+ }
217
+
218
+ if (message.type === 'stop') {
219
+ // Handle stop request
220
+ }
221
+ });
222
+ });
223
+ ```
224
+
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;
240
+
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
+ ```
279
+
280
+ ## Protocol Reference
281
+
282
+ ### Client → Server Messages
283
+
284
+ ```typescript
285
+ // Trigger an action
286
+ { type: 'trigger', triggerName: string, input?: Record<string, unknown> }
287
+
288
+ // Stop current stream
289
+ { type: 'stop' }
290
+ ```
291
+
292
+ ### Server → Client Messages
293
+
294
+ The server sends Octavus `StreamEvent` objects as JSON. See [Streaming Events](/docs/server-sdk/streaming#event-types) for the full list.
295
+
296
+ ```typescript
297
+ // Examples
298
+ { type: 'start', messageId: '...' }
299
+ { type: 'text-delta', id: '...', delta: 'Hello' }
300
+ { type: 'tool-input-start', toolCallId: '...', toolName: 'get-user' }
301
+ { type: 'finish', finishReason: 'stop' }
302
+ ```
303
+
304
+
@@ -32,7 +32,7 @@ triggers:
32
32
  request-human:
33
33
  description: User clicks "Talk to Human" button
34
34
  # No input needed - action is implicit
35
-
35
+
36
36
  submit-feedback:
37
37
  description: User submits feedback form
38
38
  input:
@@ -79,26 +79,46 @@ triggers:
79
79
 
80
80
  ### From Client SDK
81
81
 
82
- ```typescript
83
- const { send } = useOctavusChat({...});
84
-
85
- // User message trigger with UI message
86
- await send('user-message', { USER_MESSAGE: text }, {
87
- userMessage: { content: text },
88
- });
89
-
90
- // User action trigger (no input, no UI message)
91
- await send('request-human');
92
-
93
- // Action with input
94
- await send('submit-feedback', { RATING: 5, COMMENT: 'Great help!' });
82
+ ```tsx
83
+ import { useMemo } from 'react';
84
+ import { useOctavusChat, createHttpTransport } from '@octavus/react';
85
+
86
+ function Chat({ sessionId }: { sessionId: string }) {
87
+ const transport = useMemo(
88
+ () =>
89
+ createHttpTransport({
90
+ triggerRequest: (triggerName, input) =>
91
+ fetch('/api/trigger', {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ sessionId, triggerName, input }),
95
+ }),
96
+ }),
97
+ [sessionId],
98
+ );
99
+
100
+ const { send } = useOctavusChat({ transport });
101
+
102
+ // User message trigger with UI message
103
+ await send(
104
+ 'user-message',
105
+ { USER_MESSAGE: text },
106
+ { userMessage: { content: text } },
107
+ );
108
+
109
+ // User action trigger (no input, no UI message)
110
+ await send('request-human');
111
+
112
+ // Action with input
113
+ await send('submit-feedback', { RATING: 5, COMMENT: 'Great help!' });
114
+ }
95
115
  ```
96
116
 
97
117
  ### From Server SDK
98
118
 
99
119
  ```typescript
100
- const { stream } = session.trigger('user-message', {
101
- USER_MESSAGE: 'Help me with billing'
120
+ const { stream } = session.trigger('user-message', {
121
+ USER_MESSAGE: 'Help me with billing',
102
122
  });
103
123
 
104
124
  const { stream } = session.trigger('request-human');
@@ -125,7 +145,7 @@ handlers:
125
145
  role: user
126
146
  prompt: user-message
127
147
  input: [USER_MESSAGE]
128
-
148
+
129
149
  Respond:
130
150
  type: next-message
131
151
 
@@ -191,10 +211,10 @@ Each trigger should do one thing:
191
211
  triggers:
192
212
  send-message:
193
213
  input: { MESSAGE: { type: string } }
194
-
214
+
195
215
  upload-file:
196
216
  input: { FILE_URL: { type: string } }
197
-
217
+
198
218
  request-callback:
199
219
  input: { PHONE: { type: string } }
200
220
 
@@ -205,4 +225,3 @@ triggers:
205
225
  ACTION_TYPE: { type: string } # "message" | "file" | "callback"
206
226
  PAYLOAD: { type: unknown } # Different structure per type
207
227
  ```
208
-
@@ -59,17 +59,17 @@ tools:
59
59
  query:
60
60
  type: string
61
61
  description: Search query
62
-
62
+
63
63
  category:
64
64
  type: string
65
65
  description: Filter by category
66
66
  optional: true
67
-
67
+
68
68
  maxPrice:
69
69
  type: number
70
70
  description: Maximum price filter
71
71
  optional: true
72
-
72
+
73
73
  inStock:
74
74
  type: boolean
75
75
  description: Only show in-stock items
@@ -86,7 +86,7 @@ tools:
86
86
  description: Look up user account
87
87
  parameters:
88
88
  userId: { type: string }
89
-
89
+
90
90
  create-support-ticket:
91
91
  description: Create a support ticket
92
92
  parameters:
@@ -136,17 +136,30 @@ handlers:
136
136
 
137
137
  ### In Prompts
138
138
 
139
- Reference tool results in prompts:
139
+ Tool results are stored in variables. Reference the variable in prompts:
140
140
 
141
141
  ```markdown
142
142
  <!-- prompts/ticket-directive.md -->
143
143
  A support ticket has been created:
144
- - Ticket ID: {{TICKET.ticketId}}
145
- - Estimated Response: {{TICKET.estimatedResponse}}
144
+ {{TICKET}}
145
+
146
+ Let the user know their ticket has been created.
147
+ ```
148
+
149
+ When the `TICKET` variable contains an object, it's automatically serialized as JSON in the prompt:
150
+
151
+ ```
152
+ A support ticket has been created:
153
+ {
154
+ "ticketId": "TKT-123ABC",
155
+ "estimatedResponse": "24 hours"
156
+ }
146
157
 
147
158
  Let the user know their ticket has been created.
148
159
  ```
149
160
 
161
+ > **Note**: Variables use `{{VARIABLE_NAME}}` syntax with `UPPERCASE_SNAKE_CASE`. Dot notation (like `{{TICKET.ticketId}}`) is not supported. Objects are automatically JSON-serialized.
162
+
150
163
  ### In Variables
151
164
 
152
165
  Store tool results for later use:
@@ -160,15 +173,13 @@ handlers:
160
173
  input:
161
174
  userId: USER_ID
162
175
  output: ACCOUNT # Result stored here
163
-
176
+
164
177
  Create ticket:
165
178
  type: tool-call
166
179
  tool: create-support-ticket
167
180
  input:
168
181
  summary: SUMMARY
169
182
  priority: medium
170
- # Can reference ACCOUNT here
171
- email: ACCOUNT.email
172
183
  output: TICKET
173
184
  ```
174
185
 
@@ -182,7 +193,7 @@ const session = client.agentSessions.attach(sessionId, {
182
193
  'get-user-account': async (args) => {
183
194
  const userId = args.userId as string;
184
195
  const user = await db.users.findById(userId);
185
-
196
+
186
197
  return {
187
198
  name: user.name,
188
199
  email: user.email,
@@ -190,13 +201,13 @@ const session = client.agentSessions.attach(sessionId, {
190
201
  createdAt: user.createdAt.toISOString(),
191
202
  };
192
203
  },
193
-
204
+
194
205
  'create-support-ticket': async (args) => {
195
206
  const ticket = await ticketService.create({
196
207
  summary: args.summary as string,
197
208
  priority: args.priority as string,
198
209
  });
199
-
210
+
200
211
  return {
201
212
  ticketId: ticket.id,
202
213
  estimatedResponse: getEstimatedTime(args.priority),
@@ -218,7 +229,7 @@ tools:
218
229
  Retrieves the user's account information including name, email,
219
230
  subscription plan, and account creation date. Use this when the
220
231
  user asks about their account or you need to verify their identity.
221
-
232
+
222
233
  # Avoid - vague
223
234
  get-data:
224
235
  description: Gets some data
@@ -234,4 +245,3 @@ tools:
234
245
  type: string
235
246
  description: Ticket priority level (low, medium, high, urgent)
236
247
  ```
237
-