@axiom-lattice/gateway 3.0.2 → 3.0.3
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/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
package/src/controllers/run.ts
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
-
import { CreateRunRequest } from "../types";
|
|
3
|
-
import { v4 } from "uuid";
|
|
4
|
-
import {
|
|
5
|
-
Agent,
|
|
6
|
-
ThreadStatus,
|
|
7
|
-
agentLatticeManager,
|
|
8
|
-
agentInstanceManager,
|
|
9
|
-
QueueMode,
|
|
10
|
-
} from "@axiom-lattice/core";
|
|
11
|
-
import { MessageChunkTypes } from "@axiom-lattice/protocols";
|
|
12
|
-
|
|
13
|
-
function getUserId(request: FastifyRequest): string | undefined {
|
|
14
|
-
const authUser = (request as any).user;
|
|
15
|
-
if (authUser?.id) return authUser.id;
|
|
16
|
-
return (request.headers["x-user-id"] as string) || undefined;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ResumeStreamRequest {
|
|
20
|
-
thread_id: string;
|
|
21
|
-
assistant_id: string
|
|
22
|
-
message_id: string;
|
|
23
|
-
known_content: string;
|
|
24
|
-
poll_interval?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Create run
|
|
28
|
-
export const createRun = async (
|
|
29
|
-
request: FastifyRequest,
|
|
30
|
-
reply: FastifyReply
|
|
31
|
-
): Promise<void> => {
|
|
32
|
-
try {
|
|
33
|
-
const {
|
|
34
|
-
assistant_id,
|
|
35
|
-
thread_id,
|
|
36
|
-
message_id,
|
|
37
|
-
command,
|
|
38
|
-
streaming,
|
|
39
|
-
background,
|
|
40
|
-
custom_run_config,
|
|
41
|
-
mode,
|
|
42
|
-
...input
|
|
43
|
-
} = request.body as CreateRunRequest;
|
|
44
|
-
|
|
45
|
-
const tenant_id = request.headers["x-tenant-id"] as string;
|
|
46
|
-
const workspace_id = request.headers["x-workspace-id"] as string;
|
|
47
|
-
const project_id = request.headers["x-project-id"] as string;
|
|
48
|
-
const x_request_id = (request.headers["x-request-id"] as string) || v4();
|
|
49
|
-
const user_id = getUserId(request);
|
|
50
|
-
|
|
51
|
-
// Merge user_id, project_id, workspace_id into custom_run_config for downstream propagation
|
|
52
|
-
const mergedConfig = {
|
|
53
|
-
...custom_run_config,
|
|
54
|
-
...(user_id && { user_id }),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Validate request
|
|
58
|
-
if (!assistant_id) {
|
|
59
|
-
reply.status(400).send({
|
|
60
|
-
success: false,
|
|
61
|
-
error: "助手ID是必需的",
|
|
62
|
-
});
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Get or create Agent instance (ensures single instance per thread)
|
|
67
|
-
const agent = agentInstanceManager.getAgent({
|
|
68
|
-
assistant_id,
|
|
69
|
-
thread_id,
|
|
70
|
-
tenant_id,
|
|
71
|
-
workspace_id,
|
|
72
|
-
project_id,
|
|
73
|
-
custom_run_config: mergedConfig,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// ADR-026: background mode — enqueue only, returns 202.
|
|
78
|
-
// Chunk delivery is handled by the thread-level resume_stream SSE.
|
|
79
|
-
if (background) {
|
|
80
|
-
const messageInput = message_id
|
|
81
|
-
? { ...input, id: message_id }
|
|
82
|
-
: input;
|
|
83
|
-
|
|
84
|
-
const result = await agent.addMessage({
|
|
85
|
-
input: messageInput,
|
|
86
|
-
command,
|
|
87
|
-
custom_run_config: mergedConfig,
|
|
88
|
-
}, mode as QueueMode);
|
|
89
|
-
|
|
90
|
-
reply.status(202).send({
|
|
91
|
-
success: true,
|
|
92
|
-
messageId: result.messageId,
|
|
93
|
-
queued: true,
|
|
94
|
-
});
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Handle streaming requests
|
|
99
|
-
if (streaming) {
|
|
100
|
-
// Setup SSE
|
|
101
|
-
reply.hijack();
|
|
102
|
-
reply.raw.writeHead(200, {
|
|
103
|
-
"Content-Type": "text/event-stream",
|
|
104
|
-
"Cache-Control": "no-cache",
|
|
105
|
-
Connection: "keep-alive",
|
|
106
|
-
"Access-Control-Allow-Origin": "*",
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
|
|
111
|
-
// Execute agent with streaming
|
|
112
|
-
// Only pass id if message_id is provided (for backward compatibility)
|
|
113
|
-
const messageInput = message_id
|
|
114
|
-
? { ...input, id: message_id }
|
|
115
|
-
: input;
|
|
116
|
-
|
|
117
|
-
const result = await agent.addMessage({
|
|
118
|
-
input: messageInput,
|
|
119
|
-
command,
|
|
120
|
-
custom_run_config: mergedConfig,
|
|
121
|
-
}, mode as QueueMode);
|
|
122
|
-
|
|
123
|
-
// const agentStatus = await agent.getRunStatus()
|
|
124
|
-
// console.log(agentStatus)
|
|
125
|
-
// if (agentStatus === "busy") {
|
|
126
|
-
// reply.status(200).send({
|
|
127
|
-
// success: true,
|
|
128
|
-
|
|
129
|
-
// });
|
|
130
|
-
// return
|
|
131
|
-
// }
|
|
132
|
-
|
|
133
|
-
const stream = agent.chunkStream((result).messageId, [MessageChunkTypes.MESSAGE_COMPLETED, MessageChunkTypes.INTERRUPT, MessageChunkTypes.MESSAGE_FAILED])
|
|
134
|
-
|
|
135
|
-
// Forward all chunks to SSE
|
|
136
|
-
for await (const chunk of stream) {
|
|
137
|
-
const success = reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
138
|
-
if (!success) {
|
|
139
|
-
await new Promise(resolve => reply.raw.once('drain', resolve));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} catch (error: any) {
|
|
143
|
-
// Send error as SSE event
|
|
144
|
-
const errorEvent = {
|
|
145
|
-
type: 'error',
|
|
146
|
-
data: {
|
|
147
|
-
id: v4(),
|
|
148
|
-
content: error.message || 'Stream processing error',
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
reply.raw.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
|
|
152
|
-
} finally {
|
|
153
|
-
reply.raw.end();
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
// Non-streaming: use agentExecutor
|
|
157
|
-
const { message: msg, ...restInputNonStream } = input;
|
|
158
|
-
const result = await agent.invoke({
|
|
159
|
-
input: { message: msg, ...restInputNonStream },
|
|
160
|
-
command,
|
|
161
|
-
custom_run_config: mergedConfig,
|
|
162
|
-
});
|
|
163
|
-
reply.status(200).send({
|
|
164
|
-
success: true,
|
|
165
|
-
...result,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
} catch (error: any) {
|
|
169
|
-
reply.status(500).send({
|
|
170
|
-
success: false,
|
|
171
|
-
error: `创建运行时发生错误: ${error.message}`,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Resume stream from known position
|
|
177
|
-
export const resumeStream = async (
|
|
178
|
-
request: FastifyRequest,
|
|
179
|
-
reply: FastifyReply
|
|
180
|
-
): Promise<void> => {
|
|
181
|
-
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const { thread_id, message_id, assistant_id, known_content, poll_interval } =
|
|
187
|
-
request.body as ResumeStreamRequest;
|
|
188
|
-
const tenant_id = request.headers["x-tenant-id"] as string;
|
|
189
|
-
const workspace_id = request.headers["x-workspace-id"] as string;
|
|
190
|
-
const project_id = request.headers["x-project-id"] as string;
|
|
191
|
-
|
|
192
|
-
// Validate request data
|
|
193
|
-
if (!thread_id || !message_id || !assistant_id || known_content === undefined) {
|
|
194
|
-
reply.status(400).send({
|
|
195
|
-
success: false,
|
|
196
|
-
error: "thread_id, message_id, assistant_id, and known_content are required",
|
|
197
|
-
});
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Notify Fastify that we will manually handle the response
|
|
202
|
-
reply.hijack();
|
|
203
|
-
|
|
204
|
-
// Set SSE response headers
|
|
205
|
-
reply.raw.writeHead(200, {
|
|
206
|
-
"Content-Type": "text/event-stream",
|
|
207
|
-
"Cache-Control": "no-cache",
|
|
208
|
-
Connection: "keep-alive",
|
|
209
|
-
"Access-Control-Allow-Origin": "*",
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// Create Agent instance for accessing chunk buffer
|
|
214
|
-
// Get or create Agent instance (ensures single instance per thread)
|
|
215
|
-
const agent = agentInstanceManager.getAgent({
|
|
216
|
-
assistant_id,
|
|
217
|
-
thread_id,
|
|
218
|
-
tenant_id,
|
|
219
|
-
workspace_id,
|
|
220
|
-
project_id,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// ADR-026: Thread-level SSE, lives until client disconnect.
|
|
224
|
-
// Empty stopTypes means the iterator never stops on its own —
|
|
225
|
-
// MESSAGE_COMPLETED, THREAD_IDLE etc. pass through as data.
|
|
226
|
-
// Client disconnect (close event) or client abort (reply.raw.destroyed)
|
|
227
|
-
// terminate the loop.
|
|
228
|
-
const stream = agent.chunkStream(message_id, []);
|
|
229
|
-
let closed = false;
|
|
230
|
-
request.raw.on('close', () => { closed = true; });
|
|
231
|
-
|
|
232
|
-
for await (const chunk of stream) {
|
|
233
|
-
if (closed || reply.raw.destroyed) break;
|
|
234
|
-
reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
235
|
-
}
|
|
236
|
-
} catch (error: any) {
|
|
237
|
-
// Send error as SSE event before closing (following MessageChunk format)
|
|
238
|
-
const errorEvent = {
|
|
239
|
-
type: 'error',
|
|
240
|
-
data: {
|
|
241
|
-
id: v4(),
|
|
242
|
-
content: error.message || 'Resume stream processing error'
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
reply.raw.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
|
|
246
|
-
} finally {
|
|
247
|
-
reply.raw.end();
|
|
248
|
-
}
|
|
249
|
-
} catch (error: any) {
|
|
250
|
-
reply.status(500).send({
|
|
251
|
-
success: false,
|
|
252
|
-
error: `Error resuming stream: ${error.message}`,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// Abort agent execution for a thread
|
|
258
|
-
export const abortRun = async (
|
|
259
|
-
request: FastifyRequest,
|
|
260
|
-
reply: FastifyReply
|
|
261
|
-
): Promise<void> => {
|
|
262
|
-
try {
|
|
263
|
-
const { assistantId, threadId } = request.params as {
|
|
264
|
-
assistantId: string;
|
|
265
|
-
threadId: string;
|
|
266
|
-
};
|
|
267
|
-
const tenant_id = request.headers["x-tenant-id"] as string;
|
|
268
|
-
const workspace_id = (request.headers["x-workspace-id"] as string) || "default";
|
|
269
|
-
const project_id = (request.headers["x-project-id"] as string) || "default";
|
|
270
|
-
|
|
271
|
-
const agent = agentInstanceManager.getAgent({
|
|
272
|
-
assistant_id: assistantId,
|
|
273
|
-
thread_id: threadId,
|
|
274
|
-
tenant_id,
|
|
275
|
-
workspace_id,
|
|
276
|
-
project_id,
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
await agent.abort();
|
|
280
|
-
|
|
281
|
-
reply.status(200).send({
|
|
282
|
-
success: true,
|
|
283
|
-
message: "Agent aborted",
|
|
284
|
-
});
|
|
285
|
-
} catch (error: any) {
|
|
286
|
-
reply.status(500).send({
|
|
287
|
-
success: false,
|
|
288
|
-
error: `Abort failed: ${error.message}`,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Recover agent execution after a crash or manual abort.
|
|
295
|
-
*
|
|
296
|
-
* Resumes the LangGraph from its last checkpoint if the graph was
|
|
297
|
-
* mid-execution (BUSY). Otherwise cleans up stuck queue messages
|
|
298
|
-
* and restarts the queue processor.
|
|
299
|
-
*
|
|
300
|
-
* Pairs with {@link abortRun} — use abort to stop, recover to continue.
|
|
301
|
-
*/
|
|
302
|
-
export const recoverRun = async (
|
|
303
|
-
request: FastifyRequest,
|
|
304
|
-
reply: FastifyReply
|
|
305
|
-
): Promise<void> => {
|
|
306
|
-
try {
|
|
307
|
-
const { assistantId, threadId } = request.params as {
|
|
308
|
-
assistantId: string;
|
|
309
|
-
threadId: string;
|
|
310
|
-
};
|
|
311
|
-
const tenant_id = request.headers["x-tenant-id"] as string;
|
|
312
|
-
const workspace_id = (request.headers["x-workspace-id"] as string) || "default";
|
|
313
|
-
const project_id = (request.headers["x-project-id"] as string) || "default";
|
|
314
|
-
|
|
315
|
-
const agent = agentInstanceManager.getAgent({
|
|
316
|
-
assistant_id: assistantId,
|
|
317
|
-
thread_id: threadId,
|
|
318
|
-
tenant_id,
|
|
319
|
-
workspace_id,
|
|
320
|
-
project_id,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
await agent.resumeTask();
|
|
324
|
-
|
|
325
|
-
const status = await agent.getRunStatus();
|
|
326
|
-
|
|
327
|
-
reply.status(200).send({
|
|
328
|
-
success: true,
|
|
329
|
-
threadId,
|
|
330
|
-
status,
|
|
331
|
-
});
|
|
332
|
-
} catch (error: any) {
|
|
333
|
-
reply.status(500).send({
|
|
334
|
-
success: false,
|
|
335
|
-
error: `Recover failed: ${error.message}`,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
};
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from "fastify";
|
|
2
|
-
import { sandboxService } from "../services/sandbox_service";
|
|
3
|
-
import { getSandBoxManager } from "@axiom-lattice/core";
|
|
4
|
-
import { getContentTypeFromFilename, getFilenameFromPath } from "../utils/mime";
|
|
5
|
-
|
|
6
|
-
interface SandboxParams {
|
|
7
|
-
assistantId: string;
|
|
8
|
-
threadId: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function registerSandboxProxyRoutes(app: FastifyInstance): void {
|
|
12
|
-
// Register uploadfile route FIRST before wildcard routes to ensure it matches
|
|
13
|
-
app.post<{ Params: SandboxParams }>(
|
|
14
|
-
"/api/assistants/:assistantId/threads/:threadId/sandbox/uploadfile",
|
|
15
|
-
async (request, reply) => {
|
|
16
|
-
console.log("[Sandbox Upload] Route matched:", request.url);
|
|
17
|
-
const { assistantId, threadId } = request.params;
|
|
18
|
-
const tenantId = (request.headers["x-tenant-id"] as string) || "default";
|
|
19
|
-
|
|
20
|
-
const vmIsolation = sandboxService.getFilesystemVmIsolation(tenantId, assistantId);
|
|
21
|
-
if (!vmIsolation) {
|
|
22
|
-
return reply.status(500).send({ error: "Assistant sandbox config not found" });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const workspaceId = request.headers["x-workspace-id"] as string;
|
|
26
|
-
const projectId = request.headers["x-project-id"] as string;
|
|
27
|
-
|
|
28
|
-
const sandboxManager = getSandBoxManager();
|
|
29
|
-
const sandbox = await sandboxManager.getSandboxFromConfig({
|
|
30
|
-
assistant_id: assistantId,
|
|
31
|
-
thread_id: threadId,
|
|
32
|
-
tenantId,
|
|
33
|
-
workspaceId,
|
|
34
|
-
projectId,
|
|
35
|
-
vmIsolation,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const data = await request.file();
|
|
40
|
-
if (!data) {
|
|
41
|
-
return reply.status(400).send({ error: "No file in request" });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const buffer = await data.toBuffer();
|
|
45
|
-
const pathEntry = data.fields?.path;
|
|
46
|
-
const pathValue =
|
|
47
|
-
pathEntry && typeof pathEntry === "object" && "value" in pathEntry
|
|
48
|
-
? String((pathEntry as { value: unknown }).value)
|
|
49
|
-
: typeof pathEntry === "string"
|
|
50
|
-
? pathEntry
|
|
51
|
-
: undefined;
|
|
52
|
-
|
|
53
|
-
const filePath = `/uploads/${pathValue ? pathValue : ""}${data.filename}`;
|
|
54
|
-
try {
|
|
55
|
-
await sandbox.file.uploadFile({ file: filePath, data: buffer });
|
|
56
|
-
} catch (err) {
|
|
57
|
-
reply.status(500).send({ error: String(err) });
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const relativePath = filePath.replace(`~/`, "");
|
|
62
|
-
const result = { id: relativePath, name: data.filename, size: buffer.length };
|
|
63
|
-
|
|
64
|
-
return reply.status(200).send({ message: "File uploaded successfully", ...result });
|
|
65
|
-
} catch (error: unknown) {
|
|
66
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
-
return reply.status(502).send({ error: `Upload proxy error: ${message}` });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Download file from sandbox: GET with query param path (file path in sandbox, e.g. /home/gem/uploads/foo.txt)
|
|
73
|
-
app.get<{
|
|
74
|
-
Params: SandboxParams;
|
|
75
|
-
Querystring: { path: string };
|
|
76
|
-
}>(
|
|
77
|
-
"/api/assistants/:assistantId/threads/:threadId/sandbox/downloadfile",
|
|
78
|
-
async (request, reply) => {
|
|
79
|
-
const { assistantId, threadId } = request.params;
|
|
80
|
-
const { path: filePath } = request.query;
|
|
81
|
-
const tenantId = (request.headers["x-tenant-id"] as string) || "default";
|
|
82
|
-
|
|
83
|
-
if (!filePath || typeof filePath !== "string") {
|
|
84
|
-
return reply.status(400).send({ error: "Query parameter 'path' is required" });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const vmIsolation = sandboxService.getFilesystemVmIsolation(tenantId, assistantId);
|
|
88
|
-
if (!vmIsolation) {
|
|
89
|
-
return reply.status(500).send({ error: "Assistant filesystem vmIsolation not found" });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const workspaceId = request.headers["x-workspace-id"] as string;
|
|
93
|
-
const projectId = request.headers["x-project-id"] as string;
|
|
94
|
-
|
|
95
|
-
const sandboxManager = getSandBoxManager();
|
|
96
|
-
const sandbox = await sandboxManager.getSandboxFromConfig({
|
|
97
|
-
assistant_id: assistantId,
|
|
98
|
-
thread_id: threadId,
|
|
99
|
-
tenantId,
|
|
100
|
-
workspaceId,
|
|
101
|
-
projectId,
|
|
102
|
-
vmIsolation,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// Normalize path to /-prefixed absolute path
|
|
107
|
-
const resolvedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
|
|
108
|
-
const filename = getFilenameFromPath(resolvedPath);
|
|
109
|
-
const inferredContentType = getContentTypeFromFilename(filename);
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const buf = await sandbox.file.downloadFile({ file: resolvedPath });
|
|
113
|
-
const contentDisposition = `inline; filename="${filename.replace(/"/g, '\\"')}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
114
|
-
reply = reply.status(200).type(inferredContentType).header("Content-Disposition", contentDisposition).send(buf);
|
|
115
|
-
return reply;
|
|
116
|
-
} catch (err) {
|
|
117
|
-
reply.status(500).send({ error: String(err) });
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
} catch (error: unknown) {
|
|
121
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
-
return reply.status(502).send({ error: `Download proxy error: ${message}` });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// app.get<{ Params: SandboxParams }>(
|
|
128
|
-
// "/api/assistants/:assistantId/threads/:threadId/sandbox/browser",
|
|
129
|
-
// async (request, reply) => {
|
|
130
|
-
// const { assistantId, threadId } = request.params;
|
|
131
|
-
|
|
132
|
-
// const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
133
|
-
// if (!sandboxConfig) {
|
|
134
|
-
// const errorHtml = sandboxService.generateErrorHtml(
|
|
135
|
-
// assistantId,
|
|
136
|
-
// threadId,
|
|
137
|
-
// "unknown",
|
|
138
|
-
// `Assistant ${assistantId} not found`
|
|
139
|
-
// );
|
|
140
|
-
// return reply.status(404).type("text/html").send(errorHtml);
|
|
141
|
-
// }
|
|
142
|
-
|
|
143
|
-
// const { vmIsolation } = sandboxConfig;
|
|
144
|
-
// const sandboxName = sandboxService.computeSandboxName(
|
|
145
|
-
// assistantId,
|
|
146
|
-
// threadId,
|
|
147
|
-
// vmIsolation
|
|
148
|
-
// );
|
|
149
|
-
|
|
150
|
-
// try {
|
|
151
|
-
// const html = await sandboxService.getVncHtml(sandboxName);
|
|
152
|
-
// const rewrittenHtml = sandboxService.rewriteHtml(html, assistantId, threadId);
|
|
153
|
-
// return reply.type("text/html").send(rewrittenHtml);
|
|
154
|
-
// } catch (error: any) {
|
|
155
|
-
// const errorHtml = sandboxService.generateErrorHtml(
|
|
156
|
-
// assistantId,
|
|
157
|
-
// threadId,
|
|
158
|
-
// vmIsolation,
|
|
159
|
-
// error.message || "Failed to connect to sandbox"
|
|
160
|
-
// );
|
|
161
|
-
// return reply.status(502).type("text/html").send(errorHtml);
|
|
162
|
-
// }
|
|
163
|
-
// }
|
|
164
|
-
// );
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// app.get<{ Params: SandboxParams }>(
|
|
168
|
-
// "/api/assistants/:assistantId/threads/:threadId/sandbox/websockify",
|
|
169
|
-
// { websocket: true },
|
|
170
|
-
|
|
171
|
-
// (connection, request) => {
|
|
172
|
-
|
|
173
|
-
// const url = (connection?.url) as string;
|
|
174
|
-
// console.log(`[WebSocket] Received connection from URL: ${url}`);
|
|
175
|
-
|
|
176
|
-
// const urlMatch = url.match(/\/api\/assistants\/([^/]+)\/threads\/([^/]+)\/sandbox\/websockify/);
|
|
177
|
-
// if (!urlMatch) {
|
|
178
|
-
// console.error(`[WebSocket] Failed to parse params from URL: ${url}`);
|
|
179
|
-
// connection.close(1008, "Invalid URL format");
|
|
180
|
-
// return;
|
|
181
|
-
// }
|
|
182
|
-
|
|
183
|
-
// const assistantId = urlMatch[1];
|
|
184
|
-
// const threadId = urlMatch[2];
|
|
185
|
-
// console.log(`[WebSocket] Parsed params - assistantId: ${assistantId}, threadId: ${threadId}`);
|
|
186
|
-
|
|
187
|
-
// const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
188
|
-
// if (!sandboxConfig) {
|
|
189
|
-
// console.error(`[WebSocket] Assistant ${assistantId} not found`);
|
|
190
|
-
// connection.close(1008, "Assistant not found");
|
|
191
|
-
// return;
|
|
192
|
-
// }
|
|
193
|
-
|
|
194
|
-
// const { vmIsolation } = sandboxConfig;
|
|
195
|
-
// const sandboxName = sandboxService.computeSandboxName(
|
|
196
|
-
// assistantId,
|
|
197
|
-
// threadId,
|
|
198
|
-
// vmIsolation
|
|
199
|
-
// );
|
|
200
|
-
|
|
201
|
-
// const targetUrl = sandboxService.getTargetUrl(sandboxName);
|
|
202
|
-
// const targetWsUrl = targetUrl.replace(/^http/, "ws").replace(/^https/, "wss") + "/websockify";
|
|
203
|
-
|
|
204
|
-
// console.log(`[WebSocket] Connecting to target: ${targetWsUrl}`);
|
|
205
|
-
|
|
206
|
-
// const targetSocket = new WebSocket(targetWsUrl);
|
|
207
|
-
// const clientStream = createWebSocketStream(connection, { encoding: "utf8" });
|
|
208
|
-
|
|
209
|
-
// const targetStream = createWebSocketStream(targetSocket, { encoding: "utf8" });
|
|
210
|
-
|
|
211
|
-
// const forward = pipeline(clientStream, targetStream);
|
|
212
|
-
// const backward = pipeline(targetStream, clientStream);
|
|
213
|
-
|
|
214
|
-
// Promise.all([forward, backward]).catch((err) => {
|
|
215
|
-
// console.error(`[WebSocket] Proxy pipeline failed:`, err.message);
|
|
216
|
-
// targetSocket.terminate();
|
|
217
|
-
// connection.terminate();
|
|
218
|
-
// });
|
|
219
|
-
// }
|
|
220
|
-
// );
|
|
221
|
-
|
|
222
|
-
// app.get<{ Params: ProxyParams }>(
|
|
223
|
-
// "/api/assistants/:assistantId/threads/:threadId/sandbox/browser/vnc/*",
|
|
224
|
-
// async (request, reply) => {
|
|
225
|
-
// const { assistantId, threadId, "*": restPath } = request.params;
|
|
226
|
-
|
|
227
|
-
// const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
228
|
-
// if (!sandboxConfig) {
|
|
229
|
-
// return reply.status(404).send("Assistant not found");
|
|
230
|
-
// }
|
|
231
|
-
|
|
232
|
-
// const { vmIsolation } = sandboxConfig;
|
|
233
|
-
// const sandboxName = sandboxService.computeSandboxName(
|
|
234
|
-
// assistantId,
|
|
235
|
-
// threadId,
|
|
236
|
-
// vmIsolation
|
|
237
|
-
// );
|
|
238
|
-
|
|
239
|
-
// const targetPath = restPath ? `/vnc/${restPath}` : "/vnc/";
|
|
240
|
-
// const targetUrl = `${sandboxService.getTargetUrl(sandboxName)}${targetPath}`;
|
|
241
|
-
|
|
242
|
-
// try {
|
|
243
|
-
// const response = await fetch(targetUrl);
|
|
244
|
-
// const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
245
|
-
|
|
246
|
-
// const body = await response.arrayBuffer();
|
|
247
|
-
// reply.status(response.status).type(contentType).send(Buffer.from(body));
|
|
248
|
-
// } catch (error: any) {
|
|
249
|
-
// reply.status(502).send(`Proxy error: ${error.message}`);
|
|
250
|
-
// }
|
|
251
|
-
// }
|
|
252
|
-
// );
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|