@octavus/docs 0.0.5 → 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.
@@ -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
  }
@@ -49,7 +61,7 @@ function Chat() {
49
61
  Parts update in real-time during streaming. Use the part's `status` to show appropriate UI:
50
62
 
51
63
  ```tsx
52
- import type { UITextPart, UIReasoningPart } from '@octavus/client-sdk';
64
+ import type { UITextPart, UIReasoningPart } from '@octavus/react';
53
65
 
54
66
  function TextPart({ part }: { part: UITextPart }) {
55
67
  return (
@@ -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
  );
@@ -88,26 +98,24 @@ function ReasoningPart({ part }: { part: UIReasoningPart }) {
88
98
  Tool calls progress through multiple states:
89
99
 
90
100
  ```tsx
91
- import type { UIToolCallPart } from '@octavus/client-sdk';
101
+ import type { UIToolCallPart } from '@octavus/react';
92
102
 
93
103
  function ToolCallPart({ part }: { part: UIToolCallPart }) {
94
104
  return (
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/client-sdk';
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>
@@ -25,7 +25,7 @@ interface UIOperationPart {
25
25
  Operations are typically shown as compact status indicators:
26
26
 
27
27
  ```tsx
28
- import type { UIOperationPart } from '@octavus/client-sdk';
28
+ import type { UIOperationPart } from '@octavus/react';
29
29
 
30
30
  function OperationCard({ operation }: { operation: UIOperationPart }) {
31
31
  return (
@@ -46,7 +46,7 @@ function OperationCard({ operation }: { operation: UIOperationPart }) {
46
46
  Operations appear alongside text, reasoning, and tool calls in the message's `parts` array:
47
47
 
48
48
  ```tsx
49
- import type { UIMessage, UIMessagePart } from '@octavus/client-sdk';
49
+ import type { UIMessage, UIMessagePart } from '@octavus/react';
50
50
 
51
51
  function MessageBubble({ message }: { message: UIMessage }) {
52
52
  return (
@@ -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
+
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  title: Client SDK
3
- description: Frontend integration with @octavus/client-sdk for React applications.
3
+ description: Frontend integration with @octavus/react for React applications and @octavus/client-sdk for other frameworks.
4
4
  ---
@@ -65,6 +65,8 @@ In prompts, reference with `{{INPUT_NAME}}`:
65
65
  You are a support agent for {{COMPANY_NAME}}.
66
66
  ```
67
67
 
68
+ > **Note:** Variables must be `UPPER_SNAKE_CASE`. Nested properties (dot notation like `{{VAR.property}}`) are not supported. Objects are serialized as JSON when interpolated.
69
+
68
70
  ## Resources
69
71
 
70
72
  Resources are persistent state that:
@@ -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
-