@axiom-lattice/gateway 1.0.46 → 2.0.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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +13 -0
- package/RESUME_STREAM_CONTENT_BASED.md +326 -0
- package/RESUME_STREAM_README.md +388 -0
- package/dist/index.js +93 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/controllers/run.ts +60 -148
- package/src/routes/index.ts +7 -1
- package/src/schemas/index.ts +33 -0
- package/src/services/agent_service.ts +79 -14
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# Resume Stream Feature
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `resume_stream` feature allows you to continue receiving streaming chunks from a known position. This is particularly useful for scenarios where the client connection is interrupted (e.g., page refresh, network issues) and you want to seamlessly continue from where you left off.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
### ChunkBuffer Integration
|
|
10
|
+
|
|
11
|
+
The feature leverages the `ChunkBuffer` module from `@axiom-lattice/core` to:
|
|
12
|
+
|
|
13
|
+
- Store streaming chunks in memory
|
|
14
|
+
- Track thread status (active/completed/aborted)
|
|
15
|
+
- Provide TTL-based automatic cleanup
|
|
16
|
+
- Calculate and return only new chunks since a known position
|
|
17
|
+
|
|
18
|
+
### How It Works
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────┐
|
|
22
|
+
│ Original │
|
|
23
|
+
│ Streaming │ ──► Chunks stored in ChunkBuffer
|
|
24
|
+
│ (agent_stream) │ (thread_id, message_id, content)
|
|
25
|
+
└─────────────────┘
|
|
26
|
+
│
|
|
27
|
+
│ Connection lost / Page refresh
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
┌─────────────────┐
|
|
31
|
+
│ Resume Stream │
|
|
32
|
+
│ (from known │ ──► Polls for new chunks
|
|
33
|
+
│ position) │ Returns only new content
|
|
34
|
+
└─────────────────┘ Ends when thread completes
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Reference
|
|
38
|
+
|
|
39
|
+
### `resume_stream(options)`
|
|
40
|
+
|
|
41
|
+
Creates an async iterator that yields new chunks as they arrive.
|
|
42
|
+
|
|
43
|
+
#### Parameters
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
{
|
|
47
|
+
thread_id: string; // Thread identifier
|
|
48
|
+
message_id: string; // Message identifier (usually run_id)
|
|
49
|
+
known_content: string; // Content already received (used to find resume position)
|
|
50
|
+
poll_interval?: number; // Polling interval in ms (default: 100)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Returns
|
|
55
|
+
|
|
56
|
+
An async iterable object with `Symbol.asyncIterator`:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
{
|
|
60
|
+
content: string; // Chunk content
|
|
61
|
+
timestamp: number; // When chunk was added
|
|
62
|
+
messageId: string; // Message identifier
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Behavior
|
|
67
|
+
|
|
68
|
+
- **Polling**: Checks for new chunks every `poll_interval` milliseconds
|
|
69
|
+
- **Status Check**: Monitors thread status (active/completed/aborted)
|
|
70
|
+
- **Timeout**: Automatically stops after 30 seconds of no new data
|
|
71
|
+
- **Completion**: Exits when thread is no longer active
|
|
72
|
+
|
|
73
|
+
## Usage Examples
|
|
74
|
+
|
|
75
|
+
### Example 1: Basic Resume After Page Refresh
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import {
|
|
79
|
+
resume_stream,
|
|
80
|
+
get_accumulated_content,
|
|
81
|
+
} from "./services/agent_service";
|
|
82
|
+
|
|
83
|
+
// Client already received content before refresh (e.g., from localStorage or state)
|
|
84
|
+
const knownContent = "Hello world! This is content I already received...";
|
|
85
|
+
|
|
86
|
+
const stream = await resume_stream({
|
|
87
|
+
thread_id: "thread-123",
|
|
88
|
+
message_id: "msg-abc-456",
|
|
89
|
+
known_content: knownContent,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Consume new chunks
|
|
93
|
+
for await (const chunk of stream) {
|
|
94
|
+
console.log("New content:", chunk.content);
|
|
95
|
+
displayInUI(chunk.content); // Update your UI
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log("Stream completed!");
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Example 2: Check Status Before Resuming
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { get_thread_status, resume_stream } from "./services/agent_service";
|
|
105
|
+
|
|
106
|
+
// Check thread status first
|
|
107
|
+
const status = await get_thread_status("thread-123");
|
|
108
|
+
|
|
109
|
+
if (!status.exists) {
|
|
110
|
+
console.log("Thread not found or expired");
|
|
111
|
+
} else if (status.status === "completed") {
|
|
112
|
+
// Thread already finished, get full content
|
|
113
|
+
const content = await get_accumulated_content("thread-123");
|
|
114
|
+
displayFullContent(content);
|
|
115
|
+
} else if (status.status === "active") {
|
|
116
|
+
// Thread still streaming, resume from current position
|
|
117
|
+
const currentContent = await get_accumulated_content("thread-123");
|
|
118
|
+
|
|
119
|
+
const stream = await resume_stream({
|
|
120
|
+
thread_id: "thread-123",
|
|
121
|
+
message_id: "msg-123",
|
|
122
|
+
known_content: currentContent,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
for await (const chunk of stream) {
|
|
126
|
+
appendToUI(chunk.content);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Example 3: Express/Fastify Endpoint (SSE)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import express from "express";
|
|
135
|
+
import { resume_stream } from "./services/agent_service";
|
|
136
|
+
|
|
137
|
+
const app = express();
|
|
138
|
+
|
|
139
|
+
app.post("/api/resume-stream", async (req, res) => {
|
|
140
|
+
const { thread_id, message_id, known_content } = req.body;
|
|
141
|
+
|
|
142
|
+
// Set up Server-Sent Events
|
|
143
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
144
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
145
|
+
res.setHeader("Connection", "keep-alive");
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const stream = await resume_stream({
|
|
149
|
+
thread_id,
|
|
150
|
+
message_id,
|
|
151
|
+
known_content,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
for await (const chunk of stream) {
|
|
155
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
res.write("event: complete\ndata: {}\n\n");
|
|
159
|
+
res.end();
|
|
160
|
+
} catch (error) {
|
|
161
|
+
res.write(
|
|
162
|
+
`event: error\ndata: ${JSON.stringify({ error: error.message })}\n\n`
|
|
163
|
+
);
|
|
164
|
+
res.end();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Example 4: React Hook
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { useState, useEffect } from 'react';
|
|
173
|
+
|
|
174
|
+
function useResumeStream(threadId: string, messageId: string, knownContent: string) {
|
|
175
|
+
const [content, setContent] = useState('');
|
|
176
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
177
|
+
const [error, setError] = useState<Error | null>(null);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
let isCancelled = false;
|
|
181
|
+
|
|
182
|
+
async function startStream() {
|
|
183
|
+
setIsStreaming(true);
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const stream = await resume_stream({
|
|
187
|
+
thread_id: threadId,
|
|
188
|
+
message_id: messageId,
|
|
189
|
+
known_content: knownContent,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
for await (const chunk of stream) {
|
|
193
|
+
if (isCancelled) break;
|
|
194
|
+
setContent(prev => prev + chunk.content);
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (!isCancelled) {
|
|
198
|
+
setError(err as Error);
|
|
199
|
+
}
|
|
200
|
+
} finally {
|
|
201
|
+
if (!isCancelled) {
|
|
202
|
+
setIsStreaming(false);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
startStream();
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
isCancelled = true;
|
|
211
|
+
};
|
|
212
|
+
}, [threadId, messageId, knownContent]);
|
|
213
|
+
|
|
214
|
+
return { content, isStreaming, error };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Usage in component
|
|
218
|
+
function ChatMessage({ threadId, messageId, initialContent }) {
|
|
219
|
+
const { content, isStreaming } = useResumeStream(
|
|
220
|
+
threadId,
|
|
221
|
+
messageId,
|
|
222
|
+
initialContent
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div>
|
|
227
|
+
{initialContent + content}
|
|
228
|
+
{isStreaming && <span className="cursor">▋</span>}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Related Functions
|
|
235
|
+
|
|
236
|
+
### `get_accumulated_content(thread_id)`
|
|
237
|
+
|
|
238
|
+
Get all accumulated content for a thread.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const content = await get_accumulated_content("thread-123");
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `get_thread_status(thread_id)`
|
|
245
|
+
|
|
246
|
+
Get thread status and metadata.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const status = await get_thread_status("thread-123");
|
|
250
|
+
// {
|
|
251
|
+
// exists: true,
|
|
252
|
+
// status: 'active' | 'completed' | 'aborted',
|
|
253
|
+
// chunkCount: 42,
|
|
254
|
+
// createdAt: 1234567890,
|
|
255
|
+
// updatedAt: 1234567900
|
|
256
|
+
// }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `get_active_threads()`
|
|
260
|
+
|
|
261
|
+
Get all currently active threads.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const activeThreads = await get_active_threads();
|
|
265
|
+
// ['thread-1', 'thread-2', 'thread-3']
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `clear_thread_buffer(thread_id)`
|
|
269
|
+
|
|
270
|
+
Manually clear a thread's buffer.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
await clear_thread_buffer("thread-123");
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Configuration
|
|
277
|
+
|
|
278
|
+
### TTL (Time-To-Live)
|
|
279
|
+
|
|
280
|
+
Threads are automatically cleaned up after 1 hour of inactivity:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// In agent_service.ts
|
|
284
|
+
const buffer = new InMemoryChunkBuffer({
|
|
285
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
286
|
+
cleanupInterval: 5 * 60 * 1000, // Clean every 5 minutes
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Polling Interval
|
|
291
|
+
|
|
292
|
+
Adjust polling frequency based on your needs:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// Fast polling for real-time updates
|
|
296
|
+
const stream = await resume_stream({
|
|
297
|
+
thread_id: "thread-123",
|
|
298
|
+
message_id: "msg-123",
|
|
299
|
+
known_content: knownContent,
|
|
300
|
+
poll_interval: 50, // Check every 50ms
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Slower polling to reduce server load
|
|
304
|
+
const stream = await resume_stream({
|
|
305
|
+
thread_id: "thread-123",
|
|
306
|
+
message_id: "msg-123",
|
|
307
|
+
known_content: knownContent,
|
|
308
|
+
poll_interval: 500, // Check every 500ms
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Timeout
|
|
313
|
+
|
|
314
|
+
The resume stream automatically times out after 30 seconds of no new data:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// In agent_service.ts - resume_stream function
|
|
318
|
+
const maxIdleTime = 30000; // 30 seconds
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Error Handling
|
|
322
|
+
|
|
323
|
+
### Thread Not Found
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
const stream = await resume_stream({
|
|
327
|
+
thread_id: "non-existent",
|
|
328
|
+
message_id: "msg-123",
|
|
329
|
+
known_content: "",
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Stream will exit immediately if thread doesn't exist
|
|
333
|
+
for await (const chunk of stream) {
|
|
334
|
+
// Won't execute
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Network Errors
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
try {
|
|
342
|
+
const stream = await resume_stream({
|
|
343
|
+
thread_id: "thread-123",
|
|
344
|
+
message_id: "msg-123",
|
|
345
|
+
known_content: previousContent,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
for await (const chunk of stream) {
|
|
349
|
+
displayChunk(chunk);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error("Stream error:", error);
|
|
353
|
+
showErrorToUser("Failed to resume stream");
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Best Practices
|
|
358
|
+
|
|
359
|
+
1. **Always Check Status First**: Check thread status before attempting to resume
|
|
360
|
+
2. **Store Message ID**: Keep track of the `message_id` (usually `run_id`) with your content
|
|
361
|
+
3. **Preserve Content Exactly**: Store the exact content as received (no formatting/processing)
|
|
362
|
+
4. **Handle Completion**: Be prepared for the stream to end at any time
|
|
363
|
+
5. **Implement Retry Logic**: Add retry mechanism for transient failures
|
|
364
|
+
6. **Set Appropriate Poll Interval**: Balance between responsiveness and server load
|
|
365
|
+
7. **Content Matching**: The algorithm matches content intelligently - exact match, prefix, or suffix
|
|
366
|
+
|
|
367
|
+
## Performance Considerations
|
|
368
|
+
|
|
369
|
+
- **Memory Usage**: Chunks are stored in memory (consider TTL for long-running applications)
|
|
370
|
+
- **Polling Overhead**: Lower `poll_interval` increases server load but improves responsiveness
|
|
371
|
+
- **Concurrent Streams**: Multiple resume streams can run concurrently
|
|
372
|
+
- **Cleanup**: Old threads are automatically cleaned up based on TTL
|
|
373
|
+
|
|
374
|
+
## Limitations
|
|
375
|
+
|
|
376
|
+
- **In-Memory Only**: Current implementation stores chunks in memory (lost on server restart)
|
|
377
|
+
- **Single Message Per Thread**: Designed for one active message per thread
|
|
378
|
+
- **Content Matching**: Relies on exact content matching (whitespace, encoding must match)
|
|
379
|
+
- **No Persistence**: Chunks are not persisted to disk or database
|
|
380
|
+
- **Performance**: Content matching is O(n) where n is number of chunks
|
|
381
|
+
|
|
382
|
+
## Future Enhancements
|
|
383
|
+
|
|
384
|
+
- **Persistent Storage**: Add Redis or database backend for persistence across restarts
|
|
385
|
+
- **WebSocket Support**: Direct WebSocket integration for lower latency
|
|
386
|
+
- **Event-Based Notifications**: Use event emitters instead of polling
|
|
387
|
+
- **Compression**: Compress stored chunks to reduce memory usage
|
|
388
|
+
- **Multi-Message Support**: Handle multiple concurrent messages per thread
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,18 @@ var import_messages = require("@langchain/core/messages");
|
|
|
42
42
|
var import_langgraph = require("@langchain/langgraph");
|
|
43
43
|
var import_uuid = require("uuid");
|
|
44
44
|
var import_core = require("@axiom-lattice/core");
|
|
45
|
+
function getOrCreateChunkBuffer() {
|
|
46
|
+
if (!(0, import_core.hasChunkBuffer)("default")) {
|
|
47
|
+
const buffer = new import_core.InMemoryChunkBuffer({
|
|
48
|
+
ttl: 60 * 60 * 1e3,
|
|
49
|
+
// 1 hour TTL
|
|
50
|
+
cleanupInterval: 5 * 60 * 1e3
|
|
51
|
+
// Clean every 5 minutes
|
|
52
|
+
});
|
|
53
|
+
(0, import_core.registerChunkBuffer)("default", buffer);
|
|
54
|
+
}
|
|
55
|
+
return (0, import_core.getChunkBuffer)("default");
|
|
56
|
+
}
|
|
45
57
|
async function agent_invoke({
|
|
46
58
|
input,
|
|
47
59
|
thread_id,
|
|
@@ -64,11 +76,11 @@ async function agent_invoke({
|
|
|
64
76
|
configurable: {
|
|
65
77
|
thread_id,
|
|
66
78
|
run_id: run_id || (0, import_uuid.v4)(),
|
|
67
|
-
recursionLimit: 200,
|
|
68
79
|
"x-tenant-id": tenant_id,
|
|
69
80
|
"x-request-id": run_id,
|
|
70
81
|
"x-thread-id": thread_id
|
|
71
|
-
}
|
|
82
|
+
},
|
|
83
|
+
recursionLimit: 200
|
|
72
84
|
}
|
|
73
85
|
);
|
|
74
86
|
const data = result.messages.map((message2) => {
|
|
@@ -96,6 +108,7 @@ async function agent_stream({
|
|
|
96
108
|
humanMessage.additional_kwargs = { files };
|
|
97
109
|
messages = [humanMessage];
|
|
98
110
|
}
|
|
111
|
+
const chunkBuffer = getOrCreateChunkBuffer();
|
|
99
112
|
try {
|
|
100
113
|
if (!runnable_agent) {
|
|
101
114
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
@@ -115,7 +128,8 @@ async function agent_stream({
|
|
|
115
128
|
"x-thread-id": thread_id
|
|
116
129
|
},
|
|
117
130
|
streamMode: ["updates", "messages"],
|
|
118
|
-
subgraphs: false
|
|
131
|
+
subgraphs: false,
|
|
132
|
+
recursionLimit: 200
|
|
119
133
|
}
|
|
120
134
|
);
|
|
121
135
|
return {
|
|
@@ -123,6 +137,7 @@ async function agent_stream({
|
|
|
123
137
|
try {
|
|
124
138
|
for await (const chunk of agentStream) {
|
|
125
139
|
let data;
|
|
140
|
+
let chunkContent = "";
|
|
126
141
|
if (chunk[0] === "updates") {
|
|
127
142
|
const update = chunk[1];
|
|
128
143
|
const values = Object.values(update);
|
|
@@ -141,16 +156,20 @@ async function agent_stream({
|
|
|
141
156
|
};
|
|
142
157
|
}
|
|
143
158
|
if (data) {
|
|
159
|
+
await chunkBuffer.addChunk(thread_id, data);
|
|
144
160
|
yield data;
|
|
145
161
|
}
|
|
146
162
|
}
|
|
163
|
+
await chunkBuffer.completeThread(thread_id);
|
|
147
164
|
} catch (error) {
|
|
148
165
|
console.error("Stream error:", error);
|
|
166
|
+
await chunkBuffer.abortThread(thread_id);
|
|
149
167
|
throw error;
|
|
150
168
|
}
|
|
151
169
|
}
|
|
152
170
|
};
|
|
153
171
|
} catch (error) {
|
|
172
|
+
await chunkBuffer.abortThread(thread_id);
|
|
154
173
|
throw error;
|
|
155
174
|
}
|
|
156
175
|
}
|
|
@@ -212,6 +231,32 @@ async function draw_graph(assistant_id) {
|
|
|
212
231
|
const image = await drawableGraph.drawMermaid();
|
|
213
232
|
return image;
|
|
214
233
|
}
|
|
234
|
+
async function resume_stream({
|
|
235
|
+
thread_id,
|
|
236
|
+
message_id,
|
|
237
|
+
known_content,
|
|
238
|
+
poll_interval = 100
|
|
239
|
+
}) {
|
|
240
|
+
const chunkBuffer = getOrCreateChunkBuffer();
|
|
241
|
+
const stream = await chunkBuffer.getNewChunksSinceContentIterator(
|
|
242
|
+
thread_id,
|
|
243
|
+
message_id,
|
|
244
|
+
known_content
|
|
245
|
+
);
|
|
246
|
+
return {
|
|
247
|
+
[Symbol.asyncIterator]: async function* () {
|
|
248
|
+
try {
|
|
249
|
+
for await (const chunk of stream) {
|
|
250
|
+
console.log(chunk.data?.content);
|
|
251
|
+
yield chunk;
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error("Resume stream error:", error);
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
215
260
|
|
|
216
261
|
// src/controllers/run.ts
|
|
217
262
|
var import_uuid2 = require("uuid");
|
|
@@ -243,6 +288,7 @@ var createRun = async (request, reply) => {
|
|
|
243
288
|
tenant_id,
|
|
244
289
|
run_id: x_request_id
|
|
245
290
|
});
|
|
291
|
+
reply.hijack();
|
|
246
292
|
reply.raw.writeHead(200, {
|
|
247
293
|
"Content-Type": "text/event-stream",
|
|
248
294
|
"Cache-Control": "no-cache",
|
|
@@ -256,9 +302,9 @@ var createRun = async (request, reply) => {
|
|
|
256
302
|
`);
|
|
257
303
|
}
|
|
258
304
|
} catch (error) {
|
|
305
|
+
console.error("Stream processing error:", error);
|
|
259
306
|
} finally {
|
|
260
307
|
reply.raw.end();
|
|
261
|
-
return reply.hijack();
|
|
262
308
|
}
|
|
263
309
|
} else {
|
|
264
310
|
const result = await agent_invoke({
|
|
@@ -278,6 +324,47 @@ var createRun = async (request, reply) => {
|
|
|
278
324
|
});
|
|
279
325
|
}
|
|
280
326
|
};
|
|
327
|
+
var resumeStream = async (request, reply) => {
|
|
328
|
+
try {
|
|
329
|
+
const { thread_id, message_id, known_content, poll_interval } = request.body;
|
|
330
|
+
if (!thread_id || !message_id || known_content === void 0) {
|
|
331
|
+
reply.status(400).send({
|
|
332
|
+
success: false,
|
|
333
|
+
error: "thread_id, message_id, and known_content are required"
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
reply.hijack();
|
|
338
|
+
reply.raw.writeHead(200, {
|
|
339
|
+
"Content-Type": "text/event-stream",
|
|
340
|
+
"Cache-Control": "no-cache",
|
|
341
|
+
Connection: "keep-alive",
|
|
342
|
+
"Access-Control-Allow-Origin": "*"
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
const stream = await resume_stream({
|
|
346
|
+
thread_id,
|
|
347
|
+
message_id,
|
|
348
|
+
known_content: "",
|
|
349
|
+
poll_interval: poll_interval || 100
|
|
350
|
+
});
|
|
351
|
+
for await (const chunk of stream) {
|
|
352
|
+
reply.raw.write(`data: ${JSON.stringify(chunk)}
|
|
353
|
+
|
|
354
|
+
`);
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error("Resume stream processing error:", error);
|
|
358
|
+
} finally {
|
|
359
|
+
reply.raw.end();
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
reply.status(500).send({
|
|
363
|
+
success: false,
|
|
364
|
+
error: `Error resuming stream: ${error.message}`
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
281
368
|
|
|
282
369
|
// src/controllers/memory.ts
|
|
283
370
|
var setMemoryItem = async (request, reply) => {
|
|
@@ -455,39 +542,6 @@ var getAgentGraph = async (request, reply) => {
|
|
|
455
542
|
};
|
|
456
543
|
|
|
457
544
|
// src/schemas/index.ts
|
|
458
|
-
var createRunSchema = {
|
|
459
|
-
description: "Create a new agent run",
|
|
460
|
-
tags: ["Runs"],
|
|
461
|
-
summary: "Create Agent Run",
|
|
462
|
-
body: {
|
|
463
|
-
type: "object",
|
|
464
|
-
properties: {
|
|
465
|
-
thread_id: { type: "string", description: "Thread ID" },
|
|
466
|
-
assistant_id: { type: "string", description: "Assistant ID" },
|
|
467
|
-
message: { type: "string", description: "Message data for the run" },
|
|
468
|
-
command: {
|
|
469
|
-
type: "object",
|
|
470
|
-
description: "Command data for the run",
|
|
471
|
-
nullable: true
|
|
472
|
-
},
|
|
473
|
-
streaming: {
|
|
474
|
-
type: "boolean",
|
|
475
|
-
description: "Whether to stream the response",
|
|
476
|
-
nullable: true
|
|
477
|
-
},
|
|
478
|
-
background: {
|
|
479
|
-
type: "boolean",
|
|
480
|
-
description: "Whether to run in background",
|
|
481
|
-
nullable: true
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
required: ["thread_id", "assistant_id", "message"]
|
|
485
|
-
},
|
|
486
|
-
response: {
|
|
487
|
-
200: {},
|
|
488
|
-
400: {}
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
545
|
var getAllMemoryItemsSchema = {
|
|
492
546
|
description: "Get all memory items for an assistant thread",
|
|
493
547
|
tags: ["Memory"],
|
|
@@ -607,7 +661,8 @@ var getAgentGraphSchema = {
|
|
|
607
661
|
|
|
608
662
|
// src/routes/index.ts
|
|
609
663
|
var registerLatticeRoutes = (app2) => {
|
|
610
|
-
app2.post("/api/runs",
|
|
664
|
+
app2.post("/api/runs", createRun);
|
|
665
|
+
app2.post("/api/resume_stream", resumeStream);
|
|
611
666
|
app2.get(
|
|
612
667
|
"/api/assistants/:assistantId/:thread_id/memory",
|
|
613
668
|
{ schema: getAllMemoryItemsSchema },
|