@axiom-lattice/gateway 2.1.53 → 2.1.55

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.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/CHANGELOG.md +19 -0
  3. package/dist/index.js +1043 -526
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +977 -449
  6. package/dist/index.mjs.map +1 -1
  7. package/jest.config.js +5 -0
  8. package/package.json +6 -5
  9. package/src/__tests__/__mocks__/e2b.ts +1 -0
  10. package/src/__tests__/channel-installations.test.ts +199 -0
  11. package/src/__tests__/sandbox-provider-registration.test.ts +74 -0
  12. package/src/__tests__/workspace.test.ts +119 -8
  13. package/src/channels/__tests__/routes.test.ts +71 -0
  14. package/src/channels/lark/README.md +187 -0
  15. package/src/channels/lark/__tests__/aggregator.test.ts +23 -0
  16. package/src/channels/lark/__tests__/controller.test.ts +270 -0
  17. package/src/channels/lark/__tests__/mapping-service.test.ts +118 -0
  18. package/src/channels/lark/__tests__/parser.test.ts +72 -0
  19. package/src/channels/lark/__tests__/sender.test.ts +37 -0
  20. package/src/channels/lark/__tests__/verification.test.ts +157 -0
  21. package/src/channels/lark/aggregator.ts +16 -0
  22. package/src/channels/lark/config.ts +44 -0
  23. package/src/channels/lark/controller.ts +189 -0
  24. package/src/channels/lark/mapping-service.ts +138 -0
  25. package/src/channels/lark/parser.ts +68 -0
  26. package/src/channels/lark/routes.ts +121 -0
  27. package/src/channels/lark/runner.ts +37 -0
  28. package/src/channels/lark/sender.ts +58 -0
  29. package/src/channels/lark/types.ts +33 -0
  30. package/src/channels/lark/verification.ts +67 -0
  31. package/src/channels/routes.ts +25 -0
  32. package/src/controllers/channel-installations.ts +354 -0
  33. package/src/controllers/sandbox.ts +30 -80
  34. package/src/controllers/skills.ts +71 -321
  35. package/src/controllers/threads.ts +8 -6
  36. package/src/controllers/workspace.ts +64 -179
  37. package/src/index.ts +28 -5
  38. package/src/routes/channel-installations.ts +33 -0
  39. package/src/routes/index.ts +6 -0
  40. package/src/schemas/index.ts +2 -2
  41. package/src/services/sandbox_service.ts +21 -21
  42. package/src/services/skill_service.ts +97 -0
@@ -1,55 +1,40 @@
1
1
  import { FastifyRequest, FastifyReply } from "fastify";
2
- import { getStoreLattice } from "@axiom-lattice/core";
3
- import { validateSkillName } from "@axiom-lattice/core";
4
- import type {
5
- Skill,
6
- CreateSkillRequest,
7
- } from "@axiom-lattice/protocols";
2
+ import { SkillService } from "../services/skill_service";
3
+ import type { Skill, CreateSkillRequest } from "@axiom-lattice/protocols";
8
4
 
9
- /**
10
- * Get tenant ID from authenticated user context
11
- * Falls back to request headers for backward compatibility
12
- */
13
- function getTenantId(request: FastifyRequest): string {
14
- // First try to get from authenticated user context
15
- const userTenantId = (request as any).user?.tenantId;
16
- if (userTenantId) {
17
- return userTenantId;
18
- }
5
+ const skillService = new SkillService();
19
6
 
20
- // Fallback to request headers for backward compatibility
21
- return (request.headers["x-tenant-id"] as string) || "default";
7
+ function serializeSkill(skill: Skill): any {
8
+ const serialized: any = {
9
+ id: skill.id,
10
+ name: skill.name,
11
+ description: skill.description,
12
+ license: skill.license,
13
+ compatibility: skill.compatibility,
14
+ metadata: skill.metadata || {},
15
+ content: skill.content,
16
+ subSkills: skill.subSkills,
17
+ createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : new Date().toISOString(),
18
+ updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : new Date().toISOString(),
19
+ };
20
+ Object.keys(serialized).forEach((key) => {
21
+ if (serialized[key] === undefined) delete serialized[key];
22
+ });
23
+ return serialized;
22
24
  }
23
25
 
24
- /**
25
- * Skills Controller
26
- * Handles skill-related CRUD operations
27
- */
28
-
29
- /**
30
- * Skill list response interface
31
- */
32
26
  interface SkillListResponse {
33
27
  success: boolean;
34
28
  message: string;
35
- data: {
36
- records: Skill[];
37
- total: number;
38
- };
29
+ data: { records: any[]; total: number };
39
30
  }
40
31
 
41
- /**
42
- * Skill response interface
43
- */
44
32
  interface SkillResponse {
45
33
  success: boolean;
46
34
  message: string;
47
- data?: Skill;
35
+ data?: any;
48
36
  }
49
37
 
50
- /**
51
- * Skill update request body interface
52
- */
53
38
  interface SkillUpdateBody {
54
39
  name?: string;
55
40
  description?: string;
@@ -60,438 +45,203 @@ interface SkillUpdateBody {
60
45
  subSkills?: string[];
61
46
  }
62
47
 
63
- /**
64
- * Serialize Skill object for JSON response
65
- * Converts Date objects to ISO strings
66
- * Explicitly creates a plain object to ensure all fields are serializable
67
- */
68
- function serializeSkill(skill: Skill): any {
69
- // Explicitly create a plain object with all fields
70
- // This ensures Fastify can properly serialize the response
71
- const serialized: any = {
72
- id: skill.id,
73
- name: skill.name,
74
- description: skill.description,
75
- license: skill.license,
76
- compatibility: skill.compatibility,
77
- metadata: skill.metadata || {},
78
- content: skill.content,
79
- subSkills: skill.subSkills,
80
- createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : (skill.createdAt ? new Date(skill.createdAt).toISOString() : new Date().toISOString()),
81
- updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : (skill.updatedAt ? new Date(skill.updatedAt).toISOString() : new Date().toISOString()),
82
- };
83
-
84
- // Remove undefined fields to avoid serialization issues
85
- Object.keys(serialized).forEach((key) => {
86
- if (serialized[key] === undefined) {
87
- delete serialized[key];
88
- }
89
- });
90
-
91
- return serialized;
92
- }
93
-
94
- /**
95
- * Get list of all skills
96
- */
97
- export async function getSkillList(
98
- request: FastifyRequest,
99
- reply: FastifyReply
100
- ): Promise<SkillListResponse> {
48
+ export async function getSkillList(request: FastifyRequest, reply: FastifyReply): Promise<SkillListResponse> {
101
49
  try {
102
- const tenantId = getTenantId(request);
103
- const storeLattice = getStoreLattice("default", "skill");
104
- const skillStore = storeLattice.store;
105
- const skills = await skillStore.getAllSkills(tenantId);
106
-
107
- // Serialize skills to convert Date objects to ISO strings
108
- const serializedSkills = skills.map(serializeSkill);
109
-
50
+ const skills = await skillService.getAllSkills(request);
110
51
  return {
111
52
  success: true,
112
53
  message: "Successfully retrieved skill list",
113
- data: {
114
- records: serializedSkills,
115
- total: serializedSkills.length,
116
- },
54
+ data: { records: skills.map(serializeSkill), total: skills.length },
117
55
  };
118
56
  } catch (error: any) {
119
57
  return reply.status(500).send({
120
58
  success: false,
121
59
  message: `Failed to retrieve skills: ${error.message}`,
122
- data: {
123
- records: [],
124
- total: 0,
125
- },
60
+ data: { records: [], total: 0 },
126
61
  });
127
62
  }
128
63
  }
129
64
 
130
- /**
131
- * Get a single skill by ID
132
- */
133
65
  export async function getSkill(
134
66
  request: FastifyRequest<{ Params: { id: string } }>,
135
67
  reply: FastifyReply
136
68
  ): Promise<SkillResponse> {
137
69
  try {
138
- const tenantId = getTenantId(request);
139
70
  const { id } = request.params;
140
-
141
- const storeLattice = getStoreLattice("default", "skill");
142
- const skillStore = storeLattice.store;
143
- const skill = await skillStore.getSkillById(tenantId, id);
71
+ const skill = await skillService.getSkillById(request, id);
144
72
 
145
73
  if (!skill) {
146
- return reply.status(404).send({
147
- success: false,
148
- message: "Skill not found",
149
- });
74
+ return reply.status(404).send({ success: false, message: "Skill not found" });
150
75
  }
151
76
 
152
- return {
153
- success: true,
154
- message: "Successfully retrieved skill",
155
- data: serializeSkill(skill),
156
- };
77
+ return { success: true, message: "Successfully retrieved skill", data: serializeSkill(skill) };
157
78
  } catch (error: any) {
158
- return reply.status(500).send({
159
- success: false,
160
- message: `Failed to retrieve skill: ${error.message}`,
161
- });
79
+ return reply.status(500).send({ success: false, message: `Failed to retrieve skill: ${error.message}` });
162
80
  }
163
81
  }
164
82
 
165
- /**
166
- * Create a new skill
167
- */
168
83
  export async function createSkill(
169
84
  request: FastifyRequest<{ Body: CreateSkillRequest }>,
170
85
  reply: FastifyReply
171
86
  ): Promise<SkillResponse> {
172
87
  try {
173
88
  const data = request.body;
174
-
175
- // Validate required fields
176
89
  if (!data.name) {
177
- return reply.status(400).send({
178
- success: false,
179
- message: "name is required",
180
- });
90
+ return reply.status(400).send({ success: false, message: "name is required" });
181
91
  }
182
-
183
92
  if (!data.description) {
184
- return reply.status(400).send({
185
- success: false,
186
- message: "description is required",
187
- });
93
+ return reply.status(400).send({ success: false, message: "description is required" });
188
94
  }
189
95
 
190
- // Validate name format
191
- try {
192
- validateSkillName(data.name);
193
- } catch (error: any) {
194
- return reply.status(400).send({
195
- success: false,
196
- message: error.message || "Invalid skill name format",
197
- });
198
- }
199
-
200
- // ID must equal name (name is used for path addressing)
201
96
  const id = (request.body as any).id || data.name;
202
-
203
97
  if (id !== data.name) {
204
98
  return reply.status(400).send({
205
99
  success: false,
206
- message: `id "${id}" must equal name "${data.name}" (name is used for path addressing)`,
100
+ message: `id "${id}" must equal name "${data.name}"`,
207
101
  });
208
102
  }
209
103
 
210
- const tenantId = getTenantId(request);
211
- const storeLattice = getStoreLattice("default", "skill");
212
- const skillStore = storeLattice.store;
213
-
214
- // Check if skill already exists
215
- const exists = await skillStore.hasSkill(tenantId, id);
216
- if (exists) {
217
- return reply.status(409).send({
218
- success: false,
219
- message: `Skill with id "${id}" already exists`,
220
- });
221
- }
222
-
223
- // Create skill
224
- const newSkill = await skillStore.createSkill(tenantId, id, data);
225
-
104
+ const skill = await skillService.createSkill(request, id, data);
226
105
  return reply.status(201).send({
227
106
  success: true,
228
107
  message: "Successfully created skill",
229
- data: serializeSkill(newSkill),
108
+ data: serializeSkill(skill),
230
109
  });
231
110
  } catch (error: any) {
232
- return reply.status(500).send({
233
- success: false,
234
- message: `Failed to create skill: ${error.message}`,
235
- });
111
+ if (error.message?.includes("already exists")) {
112
+ return reply.status(409).send({ success: false, message: error.message });
113
+ }
114
+ if (error.message?.includes("must equal name") || error.message?.includes("Invalid skill name")) {
115
+ return reply.status(400).send({ success: false, message: error.message });
116
+ }
117
+ return reply.status(500).send({ success: false, message: `Failed to create skill: ${error.message}` });
236
118
  }
237
119
  }
238
120
 
239
- /**
240
- * Update an existing skill by ID
241
- */
242
121
  export async function updateSkill(
243
- request: FastifyRequest<{
244
- Params: { id: string };
245
- Body: SkillUpdateBody;
246
- }>,
122
+ request: FastifyRequest<{ Params: { id: string }; Body: SkillUpdateBody }>,
247
123
  reply: FastifyReply
248
124
  ): Promise<SkillResponse> {
249
125
  try {
250
126
  const { id } = request.params;
251
127
  const updates = request.body;
252
128
 
253
- // Validate name format if name is being updated
254
- if (updates.name !== undefined) {
255
- try {
256
- validateSkillName(updates.name);
257
- } catch (error: any) {
258
- return reply.status(400).send({
259
- success: false,
260
- message: error.message || "Invalid skill name format",
261
- });
262
- }
263
- }
264
-
265
- const tenantId = getTenantId(request);
266
- const storeLattice = getStoreLattice("default", "skill");
267
- const skillStore = storeLattice.store;
268
-
269
- // Check if skill exists
270
- const exists = await skillStore.hasSkill(tenantId, id);
271
- if (!exists) {
272
- return reply.status(404).send({
273
- success: false,
274
- message: "Skill not found",
275
- });
276
- }
277
-
278
- // Update skill
279
- const updatedSkill = await skillStore.updateSkill(tenantId, id, updates);
129
+ const skill = await skillService.updateSkill(request, id, updates);
280
130
 
281
- if (!updatedSkill) {
282
- return reply.status(500).send({
283
- success: false,
284
- message: "Failed to update skill",
285
- });
131
+ if (!skill) {
132
+ return reply.status(404).send({ success: false, message: "Skill not found" });
286
133
  }
287
134
 
288
- return {
289
- success: true,
290
- message: "Successfully updated skill",
291
- data: serializeSkill(updatedSkill),
292
- };
135
+ return { success: true, message: "Successfully updated skill", data: serializeSkill(skill) };
293
136
  } catch (error: any) {
294
- return reply.status(500).send({
295
- success: false,
296
- message: `Failed to update skill: ${error.message}`,
297
- });
137
+ if (error.message?.includes("Invalid skill name")) {
138
+ return reply.status(400).send({ success: false, message: error.message });
139
+ }
140
+ return reply.status(500).send({ success: false, message: `Failed to update skill: ${error.message}` });
298
141
  }
299
142
  }
300
143
 
301
- /**
302
- * Delete a skill by ID
303
- */
304
144
  export async function deleteSkill(
305
145
  request: FastifyRequest<{ Params: { id: string } }>,
306
146
  reply: FastifyReply
307
147
  ): Promise<{ success: boolean; message: string }> {
308
148
  try {
309
149
  const { id } = request.params;
150
+ const result = await skillService.deleteSkill(request, id);
310
151
 
311
- const tenantId = getTenantId(request);
312
- const storeLattice = getStoreLattice("default", "skill");
313
- const skillStore = storeLattice.store;
314
-
315
- // Check if skill exists
316
- const exists = await skillStore.hasSkill(tenantId, id);
317
- if (!exists) {
318
- return reply.status(404).send({
319
- success: false,
320
- message: "Skill not found",
321
- });
322
- }
323
-
324
- // Delete the skill
325
- const deleted = await skillStore.deleteSkill(tenantId, id);
326
-
327
- if (!deleted) {
328
- return reply.status(500).send({
329
- success: false,
330
- message: "Failed to delete skill",
331
- });
152
+ if (!result) {
153
+ return reply.status(404).send({ success: false, message: "Skill not found" });
332
154
  }
333
155
 
334
- return {
335
- success: true,
336
- message: "Successfully deleted skill",
337
- };
156
+ return { success: true, message: "Successfully deleted skill" };
338
157
  } catch (error: any) {
339
- return reply.status(500).send({
340
- success: false,
341
- message: `Failed to delete skill: ${error.message}`,
342
- });
158
+ return reply.status(500).send({ success: false, message: `Failed to delete skill: ${error.message}` });
343
159
  }
344
160
  }
345
161
 
346
- /**
347
- * Search skills by metadata
348
- */
349
162
  export async function searchSkillsByMetadata(
350
- request: FastifyRequest<{
351
- Querystring: { key: string; value: string };
352
- }>,
163
+ request: FastifyRequest<{ Querystring: { key: string; value: string } }>,
353
164
  reply: FastifyReply
354
165
  ): Promise<SkillListResponse> {
355
166
  try {
356
167
  const { key, value } = request.query;
357
-
358
168
  if (!key || !value) {
359
169
  return reply.status(400).send({
360
170
  success: false,
361
171
  message: "key and value query parameters are required",
362
- data: {
363
- records: [],
364
- total: 0,
365
- },
172
+ data: { records: [], total: 0 },
366
173
  });
367
174
  }
368
175
 
369
- const tenantId = getTenantId(request);
370
- const storeLattice = getStoreLattice("default", "skill");
371
- const skillStore = storeLattice.store;
372
- const skills = await skillStore.searchByMetadata(tenantId, key, value);
373
-
374
- // Serialize skills to convert Date objects to ISO strings
375
- const serializedSkills = skills.map(serializeSkill);
376
-
176
+ const skills = await skillService.searchByMetadata(request, key, value);
377
177
  return {
378
178
  success: true,
379
179
  message: "Successfully searched skills",
380
- data: {
381
- records: serializedSkills,
382
- total: serializedSkills.length,
383
- },
180
+ data: { records: skills.map(serializeSkill), total: skills.length },
384
181
  };
385
182
  } catch (error: any) {
386
183
  return reply.status(500).send({
387
184
  success: false,
388
185
  message: `Failed to search skills: ${error.message}`,
389
- data: {
390
- records: [],
391
- total: 0,
392
- },
186
+ data: { records: [], total: 0 },
393
187
  });
394
188
  }
395
189
  }
396
190
 
397
- /**
398
- * Filter skills by compatibility
399
- */
400
191
  export async function filterSkillsByCompatibility(
401
- request: FastifyRequest<{
402
- Querystring: { compatibility: string };
403
- }>,
192
+ request: FastifyRequest<{ Querystring: { compatibility: string } }>,
404
193
  reply: FastifyReply
405
194
  ): Promise<SkillListResponse> {
406
195
  try {
407
196
  const { compatibility } = request.query;
408
-
409
197
  if (!compatibility) {
410
198
  return reply.status(400).send({
411
199
  success: false,
412
200
  message: "compatibility query parameter is required",
413
- data: {
414
- records: [],
415
- total: 0,
416
- },
201
+ data: { records: [], total: 0 },
417
202
  });
418
203
  }
419
204
 
420
- const tenantId = getTenantId(request);
421
- const storeLattice = getStoreLattice("default", "skill");
422
- const skillStore = storeLattice.store;
423
- const skills = await skillStore.filterByCompatibility(tenantId, compatibility);
424
-
425
- // Serialize skills to convert Date objects to ISO strings
426
- const serializedSkills = skills.map(serializeSkill);
427
-
205
+ const skills = await skillService.filterByCompatibility(request, compatibility);
428
206
  return {
429
207
  success: true,
430
208
  message: "Successfully filtered skills",
431
- data: {
432
- records: serializedSkills,
433
- total: serializedSkills.length,
434
- },
209
+ data: { records: skills.map(serializeSkill), total: skills.length },
435
210
  };
436
211
  } catch (error: any) {
437
212
  return reply.status(500).send({
438
213
  success: false,
439
214
  message: `Failed to filter skills: ${error.message}`,
440
- data: {
441
- records: [],
442
- total: 0,
443
- },
215
+ data: { records: [], total: 0 },
444
216
  });
445
217
  }
446
218
  }
447
219
 
448
- /**
449
- * Filter skills by license
450
- */
451
220
  export async function filterSkillsByLicense(
452
- request: FastifyRequest<{
453
- Querystring: { license: string };
454
- }>,
221
+ request: FastifyRequest<{ Querystring: { license: string } }>,
455
222
  reply: FastifyReply
456
223
  ): Promise<SkillListResponse> {
457
224
  try {
458
225
  const { license } = request.query;
459
-
460
226
  if (!license) {
461
227
  return reply.status(400).send({
462
228
  success: false,
463
229
  message: "license query parameter is required",
464
- data: {
465
- records: [],
466
- total: 0,
467
- },
230
+ data: { records: [], total: 0 },
468
231
  });
469
232
  }
470
233
 
471
- const tenantId = getTenantId(request);
472
- const storeLattice = getStoreLattice("default", "skill");
473
- const skillStore = storeLattice.store;
474
- const skills = await skillStore.filterByLicense(tenantId, license);
475
-
476
- // Serialize skills to convert Date objects to ISO strings
477
- const serializedSkills = skills.map(serializeSkill);
478
-
234
+ const skills = await skillService.filterByLicense(request, license);
479
235
  return {
480
236
  success: true,
481
237
  message: "Successfully filtered skills",
482
- data: {
483
- records: serializedSkills,
484
- total: serializedSkills.length,
485
- },
238
+ data: { records: skills.map(serializeSkill), total: skills.length },
486
239
  };
487
240
  } catch (error: any) {
488
241
  return reply.status(500).send({
489
242
  success: false,
490
243
  message: `Failed to filter skills: ${error.message}`,
491
- data: {
492
- records: [],
493
- total: 0,
494
- },
244
+ data: { records: [], total: 0 },
495
245
  });
496
246
  }
497
247
  }
@@ -58,20 +58,22 @@ interface ThreadUpdateBody {
58
58
  export async function getThreadList(
59
59
  request: FastifyRequest<{
60
60
  Params: { assistantId: string };
61
- Querystring: { workspaceId?: string; projectId?: string };
62
61
  }>,
63
62
  reply: FastifyReply
64
63
  ): Promise<ThreadListResponse> {
65
64
  const tenantId = getTenantId(request);
66
65
  const { assistantId } = request.params;
67
66
 
68
- // Build metadata filter from query params
67
+ // Build metadata filter from headers (workspace/project isolation)
69
68
  const metadataFilter: Record<string, string> = {};
70
- if (request.query.workspaceId) {
71
- metadataFilter.workspaceId = request.query.workspaceId;
69
+ const workspaceId = request.headers["x-workspace-id"] as string | undefined;
70
+ const projectId = request.headers["x-project-id"] as string | undefined;
71
+
72
+ if (workspaceId) {
73
+ metadataFilter.workspaceId = workspaceId;
72
74
  }
73
- if (request.query.projectId) {
74
- metadataFilter.projectId = request.query.projectId;
75
+ if (projectId) {
76
+ metadataFilter.projectId = projectId;
75
77
  }
76
78
 
77
79
  const storeLattice = getStoreLattice("default", "thread");