@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.
- package/content/01-getting-started/01-introduction.md +1 -1
- package/content/01-getting-started/02-quickstart.md +26 -18
- package/content/02-server-sdk/01-overview.md +24 -13
- package/content/03-client-sdk/01-overview.md +173 -37
- package/content/03-client-sdk/02-messages.md +73 -21
- package/content/03-client-sdk/03-streaming.md +40 -30
- package/content/03-client-sdk/04-execution-blocks.md +2 -2
- package/content/03-client-sdk/05-socket-transport.md +304 -0
- package/content/03-client-sdk/_meta.md +1 -1
- package/content/04-protocol/02-input-resources.md +2 -0
- package/content/04-protocol/03-triggers.md +39 -20
- package/content/04-protocol/04-tools.md +25 -15
- package/dist/chunk-232K4EME.js +439 -0
- package/dist/chunk-232K4EME.js.map +1 -0
- package/dist/chunk-2JDZLMS3.js +439 -0
- package/dist/chunk-2JDZLMS3.js.map +1 -0
- package/dist/chunk-4WWUKU4V.js +421 -0
- package/dist/chunk-4WWUKU4V.js.map +1 -0
- package/dist/chunk-7AS4ST73.js +421 -0
- package/dist/chunk-7AS4ST73.js.map +1 -0
- package/dist/chunk-IUKE3XDN.js +421 -0
- package/dist/chunk-IUKE3XDN.js.map +1 -0
- package/dist/chunk-JOB6YWEF.js +421 -0
- package/dist/chunk-JOB6YWEF.js.map +1 -0
- package/dist/chunk-OECAPVSX.js +439 -0
- package/dist/chunk-OECAPVSX.js.map +1 -0
- package/dist/content.js +1 -1
- package/dist/docs.json +21 -12
- 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 +22 -13
- package/package.json +2 -2
|
@@ -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
|
-
|
|
24
|
-
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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) =>
|
|
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) =>
|
|
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/
|
|
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/
|
|
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
|
+
|
|
@@ -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
|
-
```
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|