@axiom-lattice/gateway 2.1.21 → 2.1.23

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.
@@ -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,8 @@
1
1
  import fastify from "fastify";
2
2
  import cors from "@fastify/cors";
3
+ import multipart from "@fastify/multipart";
3
4
  import sensible from "@fastify/sensible";
5
+ import websocket from "@fastify/websocket";
4
6
  import { registerLatticeRoutes } from "./routes";
5
7
  import { configureSwagger } from "./swagger";
6
8
  import {
@@ -19,6 +21,7 @@ import {
19
21
  PinoFileOptions,
20
22
  } from "@axiom-lattice/protocols";
21
23
 
24
+
22
25
  process.on("unhandledRejection", (reason, promise) => {
23
26
  console.error("未处理的Promise拒绝:", reason);
24
27
  // 可以在这里进行日志记录或其他处理
@@ -59,6 +62,7 @@ const app = fastify({
59
62
  bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024, // Default 50MB, configurable via BODY_LIMIT env var
60
63
  });
61
64
 
65
+
62
66
  // Add custom logging hooks
63
67
  app.addHook("onRequest", (request, reply, done) => {
64
68
  // Convert headers to strings (Fastify headers can be string | string[])
@@ -111,11 +115,19 @@ app.register(cors, {
111
115
  "X-Requested-With",
112
116
  "x-tenant-id",
113
117
  "x-request-id",
118
+ "x-assistant-id",
119
+ "x-thread-id",
114
120
  ],
115
121
  exposedHeaders: ["Content-Type"],
116
122
  credentials: true,
117
123
  });
118
124
  app.register(sensible);
125
+ app.register(multipart, {
126
+ limits: {
127
+ fileSize: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024,
128
+ },
129
+ });
130
+ app.register(websocket);
119
131
 
120
132
  // Error handler
121
133
  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
+ };
@@ -59,7 +59,11 @@ export async function agent_invoke({
59
59
  }
60
60
 
61
61
  // Get runConfig from agent config and merge into configurable
62
- const runConfig = agentLattice?.config?.runConfig || {};
62
+ const runConfig = {
63
+ ...agentLattice?.config?.runConfig || {},
64
+ assistant_id,
65
+ sandboxConfig: agentLattice?.config?.connectedSandbox,
66
+ }
63
67
 
64
68
  const result = await runnable_agent.invoke(
65
69
  command
@@ -72,6 +76,7 @@ export async function agent_invoke({
72
76
  "x-tenant-id": tenant_id,
73
77
  "x-request-id": run_id,
74
78
  "x-thread-id": thread_id,
79
+ "x-assistant-id": assistant_id,
75
80
  runConfig, // Inject runConfig for tools to access
76
81
  },
77
82
  recursionLimit: 200,
@@ -116,7 +121,11 @@ export async function agent_stream({
116
121
  const chunkBuffer = getOrCreateChunkBuffer();
117
122
 
118
123
  // Get runConfig from agent config and merge into configurable
119
- const runConfig = agentLattice?.config?.runConfig || {};
124
+ const runConfig = {
125
+ ...agentLattice?.config?.runConfig || {},
126
+ assistant_id,
127
+ sandboxConfig: agentLattice?.config?.connectedSandbox,
128
+ }
120
129
 
121
130
  try {
122
131
  if (!runnable_agent) {
@@ -126,10 +135,10 @@ export async function agent_stream({
126
135
  command
127
136
  ? new Command(command)
128
137
  : {
129
- ...rest,
130
- messages,
131
- "x-tenant-id": tenant_id,
132
- },
138
+ ...rest,
139
+ messages,
140
+ "x-tenant-id": tenant_id,
141
+ },
133
142
 
134
143
  {
135
144
  configurable: {
@@ -138,6 +147,7 @@ export async function agent_stream({
138
147
  "x-tenant-id": tenant_id,
139
148
  "x-request-id": run_id,
140
149
  "x-thread-id": thread_id,
150
+ "x-assistant-id": assistant_id,
141
151
  runConfig, // Inject runConfig for tools to access
142
152
  },
143
153
  streamMode: ["updates", "messages"],