@axiom-lattice/gateway 2.1.21 → 2.1.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/gateway",
3
- "version": "2.1.21",
3
+ "version": "2.1.22",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -17,9 +17,12 @@
17
17
  "description": "API Gateway for LangGraph agent-based applications",
18
18
  "dependencies": {
19
19
  "@fastify/cors": "^11.0.0",
20
+ "@fastify/http-proxy": "^9.5.0",
21
+ "@fastify/reply-from": "^12.5.0",
20
22
  "@fastify/sensible": "^6.0.3",
21
23
  "@fastify/swagger": "^9.5.1",
22
24
  "@fastify/swagger-ui": "^5.2.3",
25
+ "@fastify/websocket": "^11.0.1",
23
26
  "@langchain/core": "1.1.4",
24
27
  "@langchain/langgraph": "1.0.4",
25
28
  "@supabase/supabase-js": "^2.49.1",
@@ -32,15 +35,16 @@
32
35
  "pino-roll": "^3.1.0",
33
36
  "redis": "^5.0.1",
34
37
  "uuid": "^9.0.1",
35
- "@axiom-lattice/core": "2.1.16",
36
- "@axiom-lattice/protocols": "2.1.10",
37
- "@axiom-lattice/queue-redis": "1.0.9"
38
+ "@axiom-lattice/core": "2.1.17",
39
+ "@axiom-lattice/protocols": "2.1.11",
40
+ "@axiom-lattice/queue-redis": "1.0.10"
38
41
  },
39
42
  "devDependencies": {
40
43
  "@types/jest": "^29.5.14",
41
44
  "@types/lodash": "^4.17.16",
42
45
  "@types/node": "^20.17.23",
43
46
  "@types/uuid": "^9.0.8",
47
+ "@types/ws": "^8.18.1",
44
48
  "@typescript-eslint/eslint-plugin": "^7.2.0",
45
49
  "@typescript-eslint/parser": "^7.2.0",
46
50
  "eslint": "^8.57.0",
@@ -0,0 +1,150 @@
1
+ import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
2
+ // import { WebSocket, createWebSocketStream } from "ws";
3
+ import { pipeline } from "stream/promises";
4
+ import { sandboxService } from "../services/sandbox_service";
5
+ const SANDBOX_BASE_URL = process.env.SANDBOX_BASE_URL || "http://localhost:8080";
6
+
7
+ interface SandboxParams {
8
+ assistantId: string;
9
+ threadId: string;
10
+ }
11
+
12
+ interface ProxyParams extends SandboxParams {
13
+ "*": string;
14
+ }
15
+
16
+ interface ResourceParams extends SandboxParams {
17
+ resourcePath: string;
18
+ }
19
+
20
+ export async function registerSandboxProxyRoutes(app: FastifyInstance): Promise<void> {
21
+ app.get<{ Params: SandboxParams }>(
22
+ "/api/assistants/:assistantId/threads/:threadId/sandbox",
23
+ async (request, reply) => {
24
+ const { assistantId, threadId } = request.params;
25
+
26
+ const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
27
+ if (!sandboxConfig) {
28
+ const errorHtml = sandboxService.generateErrorHtml(
29
+ assistantId,
30
+ threadId,
31
+ "unknown",
32
+ `Assistant ${assistantId} not found`
33
+ );
34
+ return reply.status(404).type("text/html").send(errorHtml);
35
+ }
36
+
37
+ const { isolatedLevel } = sandboxConfig;
38
+ const sandboxName = sandboxService.computeSandboxName(
39
+ assistantId,
40
+ threadId,
41
+ isolatedLevel
42
+ );
43
+
44
+ try {
45
+ const html = await sandboxService.getVncHtml(sandboxName);
46
+ const rewrittenHtml = sandboxService.rewriteHtml(html, assistantId, threadId);
47
+ return reply.type("text/html").send(rewrittenHtml);
48
+ } catch (error: any) {
49
+ const errorHtml = sandboxService.generateErrorHtml(
50
+ assistantId,
51
+ threadId,
52
+ isolatedLevel,
53
+ error.message || "Failed to connect to sandbox"
54
+ );
55
+ return reply.status(502).type("text/html").send(errorHtml);
56
+ }
57
+ }
58
+ );
59
+
60
+
61
+
62
+ // app.get<{ Params: SandboxParams }>(
63
+ // "/api/assistants/:assistantId/threads/:threadId/sandbox/websockify",
64
+ // { websocket: true },
65
+
66
+ // (connection, request) => {
67
+
68
+ // const url = (connection?.url) as string;
69
+ // console.log(`[WebSocket] Received connection from URL: ${url}`);
70
+
71
+ // const urlMatch = url.match(/\/api\/assistants\/([^/]+)\/threads\/([^/]+)\/sandbox\/websockify/);
72
+ // if (!urlMatch) {
73
+ // console.error(`[WebSocket] Failed to parse params from URL: ${url}`);
74
+ // connection.close(1008, "Invalid URL format");
75
+ // return;
76
+ // }
77
+
78
+ // const assistantId = urlMatch[1];
79
+ // const threadId = urlMatch[2];
80
+ // console.log(`[WebSocket] Parsed params - assistantId: ${assistantId}, threadId: ${threadId}`);
81
+
82
+ // const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
83
+ // if (!sandboxConfig) {
84
+ // console.error(`[WebSocket] Assistant ${assistantId} not found`);
85
+ // connection.close(1008, "Assistant not found");
86
+ // return;
87
+ // }
88
+
89
+ // const { isolatedLevel } = sandboxConfig;
90
+ // const sandboxName = sandboxService.computeSandboxName(
91
+ // assistantId,
92
+ // threadId,
93
+ // isolatedLevel
94
+ // );
95
+
96
+ // const targetUrl = sandboxService.getTargetUrl(sandboxName);
97
+ // const targetWsUrl = targetUrl.replace(/^http/, "ws").replace(/^https/, "wss") + "/websockify";
98
+
99
+ // console.log(`[WebSocket] Connecting to target: ${targetWsUrl}`);
100
+
101
+ // const targetSocket = new WebSocket(targetWsUrl);
102
+ // const clientStream = createWebSocketStream(connection, { encoding: "utf8" });
103
+
104
+ // const targetStream = createWebSocketStream(targetSocket, { encoding: "utf8" });
105
+
106
+ // const forward = pipeline(clientStream, targetStream);
107
+ // const backward = pipeline(targetStream, clientStream);
108
+
109
+ // Promise.all([forward, backward]).catch((err) => {
110
+ // console.error(`[WebSocket] Proxy pipeline failed:`, err.message);
111
+ // targetSocket.terminate();
112
+ // connection.terminate();
113
+ // });
114
+ // }
115
+ // );
116
+
117
+ app.get<{ Params: ProxyParams }>(
118
+ "/api/assistants/:assistantId/threads/:threadId/sandbox/vnc/*",
119
+ async (request, reply) => {
120
+ const { assistantId, threadId, "*": restPath } = request.params;
121
+
122
+ const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
123
+ if (!sandboxConfig) {
124
+ return reply.status(404).send("Assistant not found");
125
+ }
126
+
127
+ const { isolatedLevel } = sandboxConfig;
128
+ const sandboxName = sandboxService.computeSandboxName(
129
+ assistantId,
130
+ threadId,
131
+ isolatedLevel
132
+ );
133
+
134
+ const targetPath = restPath ? `/vnc/${restPath}` : "/vnc/";
135
+ const targetUrl = `${sandboxService.getTargetUrl(sandboxName)}${targetPath}`;
136
+
137
+ try {
138
+ const response = await fetch(targetUrl);
139
+ const contentType = response.headers.get("content-type") || "application/octet-stream";
140
+
141
+ const body = await response.arrayBuffer();
142
+ reply.status(response.status).type(contentType).send(Buffer.from(body));
143
+ } catch (error: any) {
144
+ reply.status(502).send(`Proxy error: ${error.message}`);
145
+ }
146
+ }
147
+ );
148
+
149
+
150
+ }
@@ -0,0 +1,410 @@
1
+ import { FastifyRequest, FastifyReply } from "fastify";
2
+ import { getStoreLattice, toolLatticeManager } from "@axiom-lattice/core";
3
+ import type {
4
+ Tool,
5
+ ToolConfig,
6
+ } from "@axiom-lattice/protocols";
7
+
8
+ /**
9
+ * Tools Controller
10
+ * Handles tool-related CRUD operations
11
+ */
12
+
13
+ /**
14
+ * Tool list response interface
15
+ */
16
+ interface ToolListResponse {
17
+ success: boolean;
18
+ message: string;
19
+ data: {
20
+ records: Tool[];
21
+ total: number;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Tool response interface
27
+ */
28
+ interface ToolResponse {
29
+ success: boolean;
30
+ message: string;
31
+ data?: Tool;
32
+ }
33
+
34
+ /**
35
+ * Serialize Tool object for JSON response
36
+ * Converts Date objects to ISO strings
37
+ * Explicitly creates a plain object to ensure all fields are serializable
38
+ */
39
+ function serializeTool(tool: Tool): any {
40
+ // Explicitly create a plain object with all fields
41
+ // This ensures Fastify can properly serialize the response
42
+ const serialized: any = {
43
+ id: tool.id,
44
+ name: tool.name,
45
+ description: tool.description,
46
+ license: tool.license,
47
+ compatibility: tool.compatibility,
48
+ metadata: tool.metadata || {},
49
+ config: tool.config,
50
+ createdAt: tool.createdAt instanceof Date ? tool.createdAt.toISOString() : (tool.createdAt ? new Date(tool.createdAt).toISOString() : new Date().toISOString()),
51
+ updatedAt: tool.updatedAt instanceof Date ? tool.updatedAt.toISOString() : (tool.updatedAt ? new Date(tool.updatedAt).toISOString() : new Date().toISOString()),
52
+ };
53
+
54
+ // Remove undefined fields to avoid serialization issues
55
+ Object.keys(serialized).forEach((key) => {
56
+ if (serialized[key] === undefined) {
57
+ delete serialized[key];
58
+ }
59
+ });
60
+
61
+ return serialized;
62
+ }
63
+
64
+ /**
65
+ * Get list of all tools
66
+ */
67
+ export async function getToolList(
68
+ request: FastifyRequest,
69
+ reply: FastifyReply
70
+ ): Promise<ToolListResponse> {
71
+ try {
72
+ const storeLattice = getStoreLattice("default", "tool");
73
+ const toolStore = storeLattice.store;
74
+ const tools = await toolStore.getAllTools();
75
+
76
+ // Serialize tools to convert Date objects to ISO strings
77
+ const serializedTools = tools.map(serializeTool);
78
+
79
+ return {
80
+ success: true,
81
+ message: "Successfully retrieved tool list",
82
+ data: {
83
+ records: serializedTools,
84
+ total: serializedTools.length,
85
+ },
86
+ };
87
+ } catch (error: any) {
88
+ return reply.status(500).send({
89
+ success: false,
90
+ message: `Failed to retrieve tools: ${error.message}`,
91
+ data: {
92
+ records: [],
93
+ total: 0,
94
+ },
95
+ });
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get a single tool by ID
101
+ */
102
+ export async function getTool(
103
+ request: FastifyRequest<{ Params: { id: string } }>,
104
+ reply: FastifyReply
105
+ ): Promise<ToolResponse> {
106
+ try {
107
+ const { id } = request.params;
108
+
109
+ const storeLattice = getStoreLattice("default", "tool");
110
+ const toolStore = storeLattice.store;
111
+ const tool = await toolStore.getToolById(id);
112
+
113
+ if (!tool) {
114
+ return reply.status(404).send({
115
+ success: false,
116
+ message: "Tool not found",
117
+ });
118
+ }
119
+
120
+ return {
121
+ success: true,
122
+ message: "Successfully retrieved tool",
123
+ data: serializeTool(tool),
124
+ };
125
+ } catch (error: any) {
126
+ return reply.status(500).send({
127
+ success: false,
128
+ message: `Failed to retrieve tool: ${error.message}`,
129
+ });
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Search tools by metadata
135
+ */
136
+ export async function searchToolsByMetadata(
137
+ request: FastifyRequest<{
138
+ Querystring: { key: string; value: string };
139
+ }>,
140
+ reply: FastifyReply
141
+ ): Promise<ToolListResponse> {
142
+ try {
143
+ const { key, value } = request.query;
144
+
145
+ if (!key || !value) {
146
+ return reply.status(400).send({
147
+ success: false,
148
+ message: "key and value query parameters are required",
149
+ data: {
150
+ records: [],
151
+ total: 0,
152
+ },
153
+ });
154
+ }
155
+
156
+ const storeLattice = getStoreLattice("default", "tool");
157
+ const toolStore = storeLattice.store;
158
+ const tools = await toolStore.searchByMetadata(key, value);
159
+
160
+ // Serialize tools to convert Date objects to ISO strings
161
+ const serializedTools = tools.map(serializeTool);
162
+
163
+ return {
164
+ success: true,
165
+ message: "Successfully searched tools",
166
+ data: {
167
+ records: serializedTools,
168
+ total: serializedTools.length,
169
+ },
170
+ };
171
+ } catch (error: any) {
172
+ return reply.status(500).send({
173
+ success: false,
174
+ message: `Failed to search tools: ${error.message}`,
175
+ data: {
176
+ records: [],
177
+ total: 0,
178
+ },
179
+ });
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Filter tools by compatibility
185
+ */
186
+ export async function filterToolsByCompatibility(
187
+ request: FastifyRequest<{
188
+ Querystring: { compatibility: string };
189
+ }>,
190
+ reply: FastifyReply
191
+ ): Promise<ToolListResponse> {
192
+ try {
193
+ const { compatibility } = request.query;
194
+
195
+ if (!compatibility) {
196
+ return reply.status(400).send({
197
+ success: false,
198
+ message: "compatibility query parameter is required",
199
+ data: {
200
+ records: [],
201
+ total: 0,
202
+ },
203
+ });
204
+ }
205
+
206
+ const storeLattice = getStoreLattice("default", "tool");
207
+ const toolStore = storeLattice.store;
208
+ const tools = await toolStore.filterByCompatibility(compatibility);
209
+
210
+ // Serialize tools to convert Date objects to ISO strings
211
+ const serializedTools = tools.map(serializeTool);
212
+
213
+ return {
214
+ success: true,
215
+ message: "Successfully filtered tools",
216
+ data: {
217
+ records: serializedTools,
218
+ total: serializedTools.length,
219
+ },
220
+ };
221
+ } catch (error: any) {
222
+ return reply.status(500).send({
223
+ success: false,
224
+ message: `Failed to filter tools: ${error.message}`,
225
+ data: {
226
+ records: [],
227
+ total: 0,
228
+ },
229
+ });
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Filter tools by license
235
+ */
236
+ export async function filterToolsByLicense(
237
+ request: FastifyRequest<{
238
+ Querystring: { license: string };
239
+ }>,
240
+ reply: FastifyReply
241
+ ): Promise<ToolListResponse> {
242
+ try {
243
+ const { license } = request.query;
244
+
245
+ if (!license) {
246
+ return reply.status(400).send({
247
+ success: false,
248
+ message: "license query parameter is required",
249
+ data: {
250
+ records: [],
251
+ total: 0,
252
+ },
253
+ });
254
+ }
255
+
256
+ const storeLattice = getStoreLattice("default", "tool");
257
+ const toolStore = storeLattice.store;
258
+ const tools = await toolStore.filterByLicense(license);
259
+
260
+ // Serialize tools to convert Date objects to ISO strings
261
+ const serializedTools = tools.map(serializeTool);
262
+
263
+ return {
264
+ success: true,
265
+ message: "Successfully filtered tools",
266
+ data: {
267
+ records: serializedTools,
268
+ total: serializedTools.length,
269
+ },
270
+ };
271
+ } catch (error: any) {
272
+ return reply.status(500).send({
273
+ success: false,
274
+ message: `Failed to filter tools: ${error.message}`,
275
+ data: {
276
+ records: [],
277
+ total: 0,
278
+ },
279
+ });
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Tool config meta response interface
285
+ */
286
+ interface ToolConfigMetaResponse {
287
+ success: boolean;
288
+ message: string;
289
+ data: {
290
+ records: Array<{
291
+ id: string;
292
+ name: string;
293
+ description: string;
294
+ schema?: any; // Serialized schema
295
+ returnDirect?: boolean;
296
+ needUserApprove?: boolean;
297
+ }>;
298
+ total: number;
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Serialize ZodSchema to JSON-serializable format
304
+ * Attempts to extract schema information for API response
305
+ */
306
+ function serializeSchema(schema: any): any {
307
+ if (!schema) {
308
+ return undefined;
309
+ }
310
+
311
+ try {
312
+ // Try to get JSON schema if available (zod-to-json-schema or similar)
313
+ if (schema._def) {
314
+ // For Zod schemas, try to extract basic info
315
+ const def = schema._def;
316
+ if (def.typeName === "ZodObject") {
317
+ // Extract shape information
318
+ const shape = def.shape();
319
+ const properties: Record<string, any> = {};
320
+ const required: string[] = [];
321
+
322
+ for (const [key, value] of Object.entries(shape)) {
323
+ const fieldDef = (value as any)._def;
324
+ if (fieldDef) {
325
+ properties[key] = {
326
+ type: fieldDef.typeName === "ZodString" ? "string" :
327
+ fieldDef.typeName === "ZodNumber" ? "number" :
328
+ fieldDef.typeName === "ZodBoolean" ? "boolean" :
329
+ fieldDef.typeName === "ZodArray" ? "array" :
330
+ fieldDef.typeName === "ZodObject" ? "object" : "unknown",
331
+ description: fieldDef.description,
332
+ };
333
+ if (!(value as any).isOptional()) {
334
+ required.push(key);
335
+ }
336
+ }
337
+ }
338
+
339
+ return {
340
+ type: "object",
341
+ properties,
342
+ required: required.length > 0 ? required : undefined,
343
+ };
344
+ }
345
+ }
346
+
347
+ // Fallback: return schema description or type name
348
+ return {
349
+ type: "object",
350
+ description: schema.description || "Schema definition",
351
+ };
352
+ } catch (error) {
353
+ // If serialization fails, return a safe fallback
354
+ return {
355
+ type: "object",
356
+ description: "Schema definition",
357
+ };
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Get tool config meta from ToolLatticeManager
363
+ * Exposes tool configuration metadata via /api/tools endpoint
364
+ */
365
+ export async function getToolConfigs(
366
+ request: FastifyRequest,
367
+ reply: FastifyReply
368
+ ): Promise<ToolConfigMetaResponse> {
369
+ try {
370
+ const allLattices = toolLatticeManager.getAllLattices();
371
+
372
+ const toolConfigs = allLattices.map((lattice) => {
373
+ const config = { ...lattice.config };
374
+
375
+ // Serialize schema if present
376
+ const serializedSchema = config.schema ? serializeSchema(config.schema) : undefined;
377
+
378
+ return {
379
+ id: lattice.key,
380
+ name: config.name,
381
+ description: config.description,
382
+ schema: serializedSchema,
383
+ returnDirect: config.returnDirect,
384
+ needUserApprove: config.needUserApprove,
385
+ };
386
+ });
387
+
388
+ return reply.send({
389
+ success: true,
390
+ message: "Successfully retrieved tool configs",
391
+ data: {
392
+ records: toolConfigs,
393
+ total: toolConfigs.length,
394
+ },
395
+ });
396
+ } catch (error: any) {
397
+ console.error("Failed to get tool configs", {
398
+ error: error.message,
399
+ stack: error.stack,
400
+ });
401
+ return reply.status(500).send({
402
+ success: false,
403
+ message: `Failed to retrieve tool configs: ${error.message}`,
404
+ data: {
405
+ records: [],
406
+ total: 0,
407
+ },
408
+ });
409
+ }
410
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import fastify from "fastify";
2
2
  import cors from "@fastify/cors";
3
3
  import sensible from "@fastify/sensible";
4
+ import websocket from "@fastify/websocket";
4
5
  import { registerLatticeRoutes } from "./routes";
5
6
  import { configureSwagger } from "./swagger";
6
7
  import {
@@ -19,6 +20,7 @@ import {
19
20
  PinoFileOptions,
20
21
  } from "@axiom-lattice/protocols";
21
22
 
23
+
22
24
  process.on("unhandledRejection", (reason, promise) => {
23
25
  console.error("未处理的Promise拒绝:", reason);
24
26
  // 可以在这里进行日志记录或其他处理
@@ -59,6 +61,7 @@ const app = fastify({
59
61
  bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024, // Default 50MB, configurable via BODY_LIMIT env var
60
62
  });
61
63
 
64
+
62
65
  // Add custom logging hooks
63
66
  app.addHook("onRequest", (request, reply, done) => {
64
67
  // Convert headers to strings (Fastify headers can be string | string[])
@@ -116,6 +119,7 @@ app.register(cors, {
116
119
  credentials: true,
117
120
  });
118
121
  app.register(sensible);
122
+ app.register(websocket);
119
123
 
120
124
  // Error handler
121
125
  app.setErrorHandler((error, request, reply) => {
@@ -11,6 +11,7 @@ import * as configController from "../controllers/config";
11
11
  import * as modelsController from "../controllers/models";
12
12
  import * as healthController from "../controllers/health";
13
13
  import * as skillsController from "../controllers/skills";
14
+ import * as toolsController from "../controllers/tools";
14
15
  import {
15
16
  createRunSchema,
16
17
  getAllMemoryItemsSchema,
@@ -25,7 +26,9 @@ import {
25
26
  updateConfigSchema,
26
27
  getConfigSchema,
27
28
  getHealthSchema,
29
+ getSandboxUrlSchema,
28
30
  } from "../schemas";
31
+ import { registerSandboxProxyRoutes } from "../controllers/sandbox";
29
32
 
30
33
  export const registerLatticeRoutes = (app: FastifyInstance): void => {
31
34
  // 运行路由
@@ -192,6 +195,9 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
192
195
  Body: { models: any[] };
193
196
  }>("/api/models", modelsController.updateModels);
194
197
 
198
+ // Tools config meta route
199
+ app.get("/api/tools", toolsController.getToolConfigs);
200
+
195
201
  // Health check route
196
202
  app.get(
197
203
  "/health",
@@ -285,4 +291,6 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
285
291
  "/api/skills/filter/license",
286
292
  skillsController.filterSkillsByLicense
287
293
  );
294
+
295
+ registerSandboxProxyRoutes(app);
288
296
  };
@@ -805,3 +805,45 @@ export const filterSkillsByLicenseSchema: FastifySchema = {
805
805
  },
806
806
  },
807
807
  };
808
+
809
+ export const getSandboxUrlSchema: FastifySchema = {
810
+ description: "Get sandbox URL based on assistant and thread configuration",
811
+ tags: ["Sandbox"],
812
+ summary: "Get Sandbox URL",
813
+ params: {
814
+ type: "object",
815
+ properties: {
816
+ assistantId: { type: "string", description: "Assistant ID" },
817
+ threadId: { type: "string", description: "Thread ID" },
818
+ },
819
+ required: ["assistantId", "threadId"],
820
+ },
821
+ response: {
822
+ 200: {
823
+ type: "object",
824
+ properties: {
825
+ success: { type: "boolean" },
826
+ message: { type: "string" },
827
+ data: {
828
+ type: "object",
829
+ properties: {
830
+ sandboxUrl: { type: "string", description: "Sandbox URL" },
831
+ sandboxName: { type: "string", description: "Sandbox name" },
832
+ isolatedLevel: {
833
+ type: "string",
834
+ enum: ["agent", "thread", "global"],
835
+ description: "Sandbox isolation level",
836
+ },
837
+ },
838
+ },
839
+ },
840
+ },
841
+ 404: {
842
+ type: "object",
843
+ properties: {
844
+ success: { type: "boolean" },
845
+ message: { type: "string" },
846
+ },
847
+ },
848
+ },
849
+ };