@axiom-lattice/gateway 2.1.20 → 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.
@@ -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) => {
@@ -1,4 +1,5 @@
1
1
  import { FastifyInstance } from "fastify";
2
+ import { ScheduledTaskStatus } from "@axiom-lattice/protocols";
2
3
  import * as assistantController from "../controllers/assistant";
3
4
  import * as runController from "../controllers/run";
4
5
  import * as memoryController from "../controllers/memory";
@@ -9,6 +10,8 @@ import * as schedulesController from "../controllers/schedules";
9
10
  import * as configController from "../controllers/config";
10
11
  import * as modelsController from "../controllers/models";
11
12
  import * as healthController from "../controllers/health";
13
+ import * as skillsController from "../controllers/skills";
14
+ import * as toolsController from "../controllers/tools";
12
15
  import {
13
16
  createRunSchema,
14
17
  getAllMemoryItemsSchema,
@@ -23,7 +26,9 @@ import {
23
26
  updateConfigSchema,
24
27
  getConfigSchema,
25
28
  getHealthSchema,
29
+ getSandboxUrlSchema,
26
30
  } from "../schemas";
31
+ import { registerSandboxProxyRoutes } from "../controllers/sandbox";
27
32
 
28
33
  export const registerLatticeRoutes = (app: FastifyInstance): void => {
29
34
  // 运行路由
@@ -190,6 +195,9 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
190
195
  Body: { models: any[] };
191
196
  }>("/api/models", modelsController.updateModels);
192
197
 
198
+ // Tools config meta route
199
+ app.get("/api/tools", toolsController.getToolConfigs);
200
+
193
201
  // Health check route
194
202
  app.get(
195
203
  "/health",
@@ -200,7 +208,11 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
200
208
  // Schedule routes for viewing scheduled tasks by thread
201
209
  app.get<{
202
210
  Params: { assistantId: string; threadId: string };
203
- Querystring: { status?: string; limit?: string; offset?: string };
211
+ Querystring: {
212
+ status?: ScheduledTaskStatus;
213
+ limit?: string;
214
+ offset?: string;
215
+ };
204
216
  }>(
205
217
  "/api/assistants/:assistantId/threads/:threadId/schedules",
206
218
  schedulesController.getThreadSchedules
@@ -225,4 +237,60 @@ export const registerLatticeRoutes = (app: FastifyInstance): void => {
225
237
  app.post<{
226
238
  Params: { taskId: string };
227
239
  }>("/api/schedules/:taskId/resume", schedulesController.resumeScheduledTask);
240
+
241
+ // Skills CRUD routes
242
+ app.get("/api/skills", skillsController.getSkillList);
243
+
244
+ app.get<{
245
+ Params: { id: string };
246
+ }>(
247
+ "/api/skills/:id",
248
+ skillsController.getSkill
249
+ );
250
+
251
+ app.post<{
252
+ Body: any;
253
+ }>(
254
+ "/api/skills",
255
+ skillsController.createSkill
256
+ );
257
+
258
+ app.put<{
259
+ Params: { id: string };
260
+ Body: any;
261
+ }>(
262
+ "/api/skills/:id",
263
+ skillsController.updateSkill
264
+ );
265
+
266
+ app.delete<{
267
+ Params: { id: string };
268
+ }>(
269
+ "/api/skills/:id",
270
+ skillsController.deleteSkill
271
+ );
272
+
273
+ // Skills search and filter routes
274
+ app.get<{
275
+ Querystring: { key: string; value: string };
276
+ }>(
277
+ "/api/skills/search/metadata",
278
+ skillsController.searchSkillsByMetadata
279
+ );
280
+
281
+ app.get<{
282
+ Querystring: { compatibility: string };
283
+ }>(
284
+ "/api/skills/filter/compatibility",
285
+ skillsController.filterSkillsByCompatibility
286
+ );
287
+
288
+ app.get<{
289
+ Querystring: { license: string };
290
+ }>(
291
+ "/api/skills/filter/license",
292
+ skillsController.filterSkillsByLicense
293
+ );
294
+
295
+ registerSandboxProxyRoutes(app);
228
296
  };