@octavus/docs 2.0.0 → 2.1.0

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.
@@ -162,6 +162,35 @@ const events = session.execute(request, {
162
162
 
163
163
  When the client aborts the request, the signal propagates through to the LLM provider, stopping generation immediately. Any partial content is preserved.
164
164
 
165
+ ## WebSocket Handling
166
+
167
+ For WebSocket integrations, use `handleSocketMessage()` which manages abort controller lifecycle internally:
168
+
169
+ ```typescript
170
+ import type { SocketMessage } from '@octavus/server-sdk';
171
+
172
+ // In your socket handler
173
+ conn.on('data', async (rawData: string) => {
174
+ const msg = JSON.parse(rawData);
175
+
176
+ if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {
177
+ await session.handleSocketMessage(msg as SocketMessage, {
178
+ onEvent: (event) => conn.write(JSON.stringify(event)),
179
+ onFinish: () => sendMessagesUpdate(), // Optional callback after streaming
180
+ });
181
+ }
182
+ });
183
+ ```
184
+
185
+ The `handleSocketMessage()` method:
186
+
187
+ - Handles `trigger`, `continue`, and `stop` messages
188
+ - Automatically aborts previous requests when a new one arrives
189
+ - Streams events via the `onEvent` callback
190
+ - Calls `onFinish` after streaming completes (not called if aborted)
191
+
192
+ See [Socket Chat Example](/docs/examples/socket-chat) for a complete implementation.
193
+
165
194
  ## Session Lifecycle
166
195
 
167
196
  ```mermaid
@@ -131,7 +131,7 @@ The server creates a session on first trigger message:
131
131
 
132
132
  ```typescript
133
133
  import sockjs from 'sockjs';
134
- import { OctavusClient, type AgentSession } from '@octavus/server-sdk';
134
+ import { OctavusClient, type AgentSession, type SocketMessage } from '@octavus/server-sdk';
135
135
 
136
136
  const client = new OctavusClient({
137
137
  baseUrl: process.env.OCTAVUS_API_URL!,
@@ -141,7 +141,8 @@ const client = new OctavusClient({
141
141
  function createSocketHandler() {
142
142
  return (conn: sockjs.Connection) => {
143
143
  let session: AgentSession | null = null;
144
- let abortController: AbortController | null = null;
144
+
145
+ const send = (data: unknown) => conn.write(JSON.stringify(data));
145
146
 
146
147
  conn.on('data', (rawData: string) => {
147
148
  void handleMessage(rawData);
@@ -150,17 +151,10 @@ function createSocketHandler() {
150
151
  async function handleMessage(rawData: string) {
151
152
  const msg = JSON.parse(rawData);
152
153
 
153
- if (msg.type === 'stop') {
154
- abortController?.abort();
155
- return;
156
- }
157
-
158
- // Handle both trigger and continue messages
159
- if (msg.type === 'trigger' || msg.type === 'continue') {
154
+ if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {
160
155
  // Create session lazily on first trigger
161
156
  if (!session && msg.type === 'trigger') {
162
157
  const sessionId = await client.agentSessions.create('your-agent-id', {
163
- // Initial input variables
164
158
  COMPANY_NAME: 'Acme Corp',
165
159
  });
166
160
  session = client.agentSessions.attach(sessionId, {
@@ -173,24 +167,14 @@ function createSocketHandler() {
173
167
 
174
168
  if (!session) return;
175
169
 
176
- abortController = new AbortController();
177
-
178
- // execute() handles both triggers and continuations
179
- const events = session.execute(msg, {
180
- signal: abortController.signal,
170
+ // handleSocketMessage manages abort controller internally
171
+ await session.handleSocketMessage(msg as SocketMessage, {
172
+ onEvent: send,
181
173
  });
182
-
183
- try {
184
- for await (const event of events) {
185
- conn.write(JSON.stringify(event));
186
- }
187
- } catch {
188
- // Handle errors
189
- }
190
174
  }
191
175
  }
192
176
 
193
- conn.on('close', () => abortController?.abort());
177
+ conn.on('close', () => {});
194
178
  };
195
179
  }
196
180
 
@@ -245,9 +229,12 @@ When `sessionId` changes, the hook automatically reinitializes with the new tran
245
229
  When using client-provided sessionId, the server must handle an `init` message:
246
230
 
247
231
  ```typescript
232
+ import type { SocketMessage } from '@octavus/server-sdk';
233
+
248
234
  sockServer.on('connection', (conn) => {
249
235
  let session: AgentSession | null = null;
250
- let abortController: AbortController | null = null;
236
+
237
+ const send = (data: unknown) => conn.write(JSON.stringify(data));
251
238
 
252
239
  conn.on('data', (rawData: string) => {
253
240
  void handleMessage(rawData);
@@ -266,35 +253,23 @@ sockServer.on('connection', (conn) => {
266
253
  return;
267
254
  }
268
255
 
269
- if (msg.type === 'stop') {
270
- abortController?.abort();
271
- return;
272
- }
273
-
274
256
  // All other messages require initialized session
275
257
  if (!session) {
276
- conn.write(
277
- JSON.stringify({
278
- type: 'error',
279
- errorType: 'validation_error',
280
- message: 'Session not initialized. Send init message first.',
281
- source: 'platform',
282
- retryable: false,
283
- }),
284
- );
258
+ send({
259
+ type: 'error',
260
+ errorType: 'validation_error',
261
+ message: 'Session not initialized. Send init message first.',
262
+ source: 'platform',
263
+ retryable: false,
264
+ });
285
265
  return;
286
266
  }
287
267
 
288
- // Handle both trigger and continue messages
289
- if (msg.type === 'trigger' || msg.type === 'continue') {
290
- abortController = new AbortController();
291
-
292
- // execute() handles both triggers and continuations
293
- const events = session.execute(msg, { signal: abortController.signal });
294
-
295
- for await (const event of events) {
296
- conn.write(JSON.stringify(event));
297
- }
268
+ // handleSocketMessage handles trigger, continue, and stop
269
+ if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {
270
+ await session.handleSocketMessage(msg as SocketMessage, {
271
+ onEvent: send,
272
+ });
298
273
  }
299
274
  }
300
275
  });
@@ -79,7 +79,7 @@ This is the core of socket integration. Each connection gets its own session:
79
79
  ```typescript
80
80
  // server/octavus/socket-handler.ts
81
81
  import type { Connection } from 'sockjs';
82
- import { OctavusClient, type AgentSession } from '@octavus/server-sdk';
82
+ import { OctavusClient, type AgentSession, type SocketMessage } from '@octavus/server-sdk';
83
83
 
84
84
  const octavus = new OctavusClient({
85
85
  baseUrl: process.env.OCTAVUS_API_URL!,
@@ -91,7 +91,8 @@ const AGENT_ID = process.env.OCTAVUS_AGENT_ID!;
91
91
  export function createSocketHandler() {
92
92
  return (conn: Connection) => {
93
93
  let session: AgentSession | null = null;
94
- let abortController: AbortController | null = null;
94
+
95
+ const send = (data: unknown) => conn.write(JSON.stringify(data));
95
96
 
96
97
  conn.on('data', (rawData: string) => {
97
98
  void handleMessage(rawData);
@@ -100,57 +101,36 @@ export function createSocketHandler() {
100
101
  async function handleMessage(rawData: string) {
101
102
  const msg = JSON.parse(rawData);
102
103
 
103
- // Handle stop request
104
- if (msg.type === 'stop') {
105
- abortController?.abort();
106
- return;
107
- }
108
-
109
- // Handle trigger and continue messages
110
- if (msg.type === 'trigger' || msg.type === 'continue') {
104
+ // Handle trigger, continue, and stop messages
105
+ if (msg.type === 'trigger' || msg.type === 'continue' || msg.type === 'stop') {
111
106
  // Create session lazily on first trigger
112
107
  if (!session && msg.type === 'trigger') {
113
108
  const sessionId = await octavus.agentSessions.create(AGENT_ID, {
114
- // Initial input variables from your protocol
115
109
  COMPANY_NAME: 'Acme Corp',
116
110
  });
117
111
 
118
112
  session = octavus.agentSessions.attach(sessionId, {
119
113
  tools: {
120
- // Server-side tool handlers
121
114
  'get-user-account': async () => {
122
- // Fetch from your database
123
115
  return { name: 'Demo User', plan: 'pro' };
124
116
  },
125
117
  'create-support-ticket': async () => {
126
118
  return { ticketId: 'TKT-123', estimatedResponse: '24h' };
127
119
  },
128
- // Tools without handlers are forwarded to the client
129
120
  },
130
121
  });
131
122
  }
132
123
 
133
124
  if (!session) return;
134
125
 
135
- abortController = new AbortController();
136
-
137
- // execute() handles both triggers and continuations
138
- const events = session.execute(msg, { signal: abortController.signal });
139
-
140
- try {
141
- for await (const event of events) {
142
- if (abortController.signal.aborted) break;
143
- conn.write(JSON.stringify(event));
144
- }
145
- } catch {
146
- // Handle errors
147
- }
126
+ // handleSocketMessage manages abort controller internally
127
+ await session.handleSocketMessage(msg as SocketMessage, {
128
+ onEvent: send,
129
+ });
148
130
  }
149
131
  }
150
132
 
151
- conn.on('close', () => {
152
- abortController?.abort();
153
- });
133
+ conn.on('close', () => {});
154
134
  };
155
135
  }
156
136
  ```
@@ -373,13 +353,13 @@ The socket handler receives messages and forwards them to Octavus:
373
353
  // Client sends continuation (after client tool handling):
374
354
  { type: 'continue', executionId: '...', toolResults: [...] }
375
355
 
376
- // Server handles both:
377
- if (msg.type === 'trigger' || msg.type === 'continue') {
378
- const events = session.execute(msg);
379
- for await (const event of events) {
380
- conn.write(JSON.stringify(event));
381
- }
382
- }
356
+ // Client sends stop:
357
+ { type: 'stop' }
358
+
359
+ // Server handles all three with handleSocketMessage:
360
+ await session.handleSocketMessage(msg, {
361
+ onEvent: (event) => conn.write(JSON.stringify(event)),
362
+ });
383
363
  ```
384
364
 
385
365
  ### Tools