@axiom-lattice/gateway 2.1.111 → 2.1.113

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 (32) hide show
  1. package/.turbo/turbo-build.log +13 -13
  2. package/CHANGELOG.md +20 -0
  3. package/dist/index.js +2118 -319
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1948 -145
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +5 -6
  8. package/src/controllers/__tests__/workflow-tracking.test.ts +113 -0
  9. package/src/controllers/connections.ts +7 -0
  10. package/src/controllers/eval.ts +44 -4
  11. package/src/controllers/export-import.ts +166 -0
  12. package/src/controllers/run.ts +30 -4
  13. package/src/controllers/workflow-tracking.ts +105 -11
  14. package/src/export_registrations/a2a-api-key.registration.ts +89 -0
  15. package/src/export_registrations/agent.registration.ts +80 -0
  16. package/src/export_registrations/binding.registration.ts +106 -0
  17. package/src/export_registrations/channel-installation.registration.ts +88 -0
  18. package/src/export_registrations/collection.registration.ts +85 -0
  19. package/src/export_registrations/connection.registration.ts +97 -0
  20. package/src/export_registrations/database-config.registration.ts +94 -0
  21. package/src/export_registrations/eval.registration.ts +334 -0
  22. package/src/export_registrations/index.ts +31 -0
  23. package/src/export_registrations/mcp-config.registration.ts +97 -0
  24. package/src/export_registrations/menu.registration.ts +91 -0
  25. package/src/export_registrations/metrics-config.registration.ts +94 -0
  26. package/src/export_registrations/skill.registration.ts +88 -0
  27. package/src/export_registrations/task.registration.ts +99 -0
  28. package/src/index.ts +18 -8
  29. package/src/routes/index.ts +42 -1
  30. package/src/services/ExportImportService.ts +296 -0
  31. package/src/services/__tests__/ExportImportService.test.ts +130 -0
  32. package/src/services/eval-runner.ts +63 -55
@@ -0,0 +1,130 @@
1
+ import { ExportImportService } from '../ExportImportService';
2
+ import { ExportableEntityRegistry } from '@axiom-lattice/core';
3
+ import type { ExportableEntityDefinition, ExportableEntity } from '@axiom-lattice/core';
4
+
5
+ function makeDef(
6
+ entityType: string,
7
+ entities: ExportableEntity[] = [],
8
+ dependsOn: string[] = [],
9
+ ): ExportableEntityDefinition {
10
+ return {
11
+ entityType,
12
+ label: entityType,
13
+ category: 'core',
14
+ dependsOn,
15
+ cascadeParents: [],
16
+ listForExport: jest.fn().mockResolvedValue(entities),
17
+ previewImport: jest.fn().mockResolvedValue({ conflicts: [], insertions: [], errors: [] }),
18
+ applyImport: jest.fn().mockResolvedValue({ newId: 'new-id' }),
19
+ };
20
+ }
21
+
22
+ describe('ExportImportService', () => {
23
+ beforeEach(() => {
24
+ (ExportableEntityRegistry as any).instance = undefined;
25
+ const registry = ExportableEntityRegistry.getInstance();
26
+ const s1 = { _exportId: '', data: { id: 's1', name: 'Skill 1' } };
27
+ const s2 = { _exportId: '', data: { id: 's2', name: 'Skill 2' } };
28
+ registry.register(makeDef('skill', [s1, s2]));
29
+ registry.register(makeDef('agent', [], ['skill']));
30
+ registry.register(makeDef('binding', [], ['agent']));
31
+ });
32
+
33
+ it('generates a valid ExportBundle', async () => {
34
+ const svc = new ExportImportService();
35
+ const { bundle } = await svc.export('tenant-1', ['skill']);
36
+
37
+ expect(bundle.version).toBe(1);
38
+ expect(bundle.sourceTenantId).toBe('tenant-1');
39
+ expect(bundle.entities.skill).toHaveLength(2);
40
+ expect(bundle.entities.skill[0]._exportId).toMatch(/^exp-skill-0/);
41
+ expect(bundle.entities.skill[0].data.id).toBe('s1');
42
+ expect(bundle.entities.skill[0].data).not.toHaveProperty('tenantId');
43
+ expect(bundle.entities.skill[0].data).not.toHaveProperty('createdAt');
44
+ expect(bundle.entities.skill[0].data).not.toHaveProperty('updatedAt');
45
+ expect(bundle.dependencyOrder).toEqual(['skill']);
46
+ });
47
+
48
+ it('filters entities by entityIds', async () => {
49
+ const svc = new ExportImportService();
50
+ const { bundle } = await svc.export('tenant-1', ['skill'], { skill: ['s1'] });
51
+ expect(bundle.entities.skill).toHaveLength(1);
52
+ expect(bundle.entities.skill[0].data.id).toBe('s1');
53
+ });
54
+
55
+ it('exportPreview detects missing dependencies', async () => {
56
+ const svc = new ExportImportService();
57
+ const result = await svc.exportPreview('tenant-1', ['agent']);
58
+ expect(result.missingDependencies).toContain('skill');
59
+ expect(result.cascadeAdditions).toEqual([]);
60
+ });
61
+
62
+ it('export includes transitive deps when explicitly requested', async () => {
63
+ const svc = new ExportImportService();
64
+ const { bundle } = await svc.export('tenant-1', ['skill', 'agent']);
65
+ expect(bundle.dependencyOrder).toContain('skill');
66
+ expect(bundle.dependencyOrder).toContain('agent');
67
+ expect(bundle.dependencyOrder.indexOf('skill')).toBeLessThan(bundle.dependencyOrder.indexOf('agent'));
68
+ });
69
+
70
+ it('previewImport returns insertions for new entities', async () => {
71
+ const svc = new ExportImportService();
72
+ const bundle = {
73
+ version: 1 as const,
74
+ exportedAt: new Date().toISOString(),
75
+ sourceTenantId: 't1',
76
+ entities: {
77
+ skill: [{ _exportId: 'exp-s-1', data: { id: 's1', name: 'Skill' } }],
78
+ },
79
+ dependencyOrder: ['skill'],
80
+ };
81
+
82
+ await svc.previewImport('tenant-2', bundle);
83
+ const registry = ExportableEntityRegistry.getInstance();
84
+ expect(registry.get('skill').previewImport).toHaveBeenCalled();
85
+ });
86
+
87
+ it('throws on invalid bundle version', async () => {
88
+ const svc = new ExportImportService();
89
+ await expect(
90
+ svc.previewImport('tenant-2', {
91
+ version: 99 as any,
92
+ entities: {},
93
+ dependencyOrder: [],
94
+ exportedAt: '',
95
+ sourceTenantId: '',
96
+ }),
97
+ ).rejects.toThrow(/version/);
98
+ });
99
+
100
+ it('throws on missing entities field', async () => {
101
+ const svc = new ExportImportService();
102
+ await expect(
103
+ svc.previewImport('tenant-2', {
104
+ version: 1,
105
+ exportedAt: '',
106
+ sourceTenantId: '',
107
+ dependencyOrder: [],
108
+ } as any),
109
+ ).rejects.toThrow(/entities/);
110
+ });
111
+
112
+ it('getExportJob returns undefined for unknown jobId', () => {
113
+ const svc = new ExportImportService();
114
+ expect(svc.getExportJob('nonexistent', 'tenant-1')).toBeUndefined();
115
+ });
116
+
117
+ it('getExportJob returns bundle after export', async () => {
118
+ const svc = new ExportImportService();
119
+ const { jobId, bundle } = await svc.export('tenant-1', ['skill']);
120
+ const retrieved = svc.getExportJob(jobId, 'tenant-1');
121
+ expect(retrieved).toEqual(bundle);
122
+ });
123
+
124
+ it('listExportableTypes returns registered types', () => {
125
+ const svc = new ExportImportService();
126
+ const types = svc.listExportableTypes();
127
+ expect(types).toHaveLength(3);
128
+ expect(types.map((t) => t.entityType)).toEqual(['skill', 'agent', 'binding']);
129
+ });
130
+ });
@@ -1,6 +1,9 @@
1
1
  import { EventEmitter } from "events";
2
- import { getStoreLattice, modelLatticeManager } from "@axiom-lattice/core";
3
- import { LatticeEvalProject } from "@axiom-lattice/agent-eval";
2
+ import {
3
+ getStoreLattice,
4
+ modelLatticeManager,
5
+ LatticeEvalProject,
6
+ } from "@axiom-lattice/core";
4
7
  import type { EvalStore } from "@axiom-lattice/protocols";
5
8
  import type {
6
9
  CaseRunResult,
@@ -8,8 +11,7 @@ import type {
8
11
  LatticeEvalProjectType,
9
12
  LatticeEvalSuiteType,
10
13
  LatticeEvalLogEvent,
11
- OutputType,
12
- } from "@axiom-lattice/agent-eval";
14
+ } from "@axiom-lattice/core";
13
15
  import { v4 as uuidv4 } from "uuid";
14
16
 
15
17
  export interface EvalStreamEvent {
@@ -48,6 +50,12 @@ class EvalRunner {
48
50
  const project = await store.getProjectById(tenantId, projectId);
49
51
  if (!project) throw new Error("Project not found");
50
52
 
53
+ for (const ctx of this.runs.values()) {
54
+ if (ctx.projectId === projectId) {
55
+ throw new Error("A run is already in progress for this project");
56
+ }
57
+ }
58
+
51
59
  const existingRuns = await store.getRunsByTenant(tenantId, { projectId, status: "running" });
52
60
  if (existingRuns.length > 0) {
53
61
  throw new Error("A run is already in progress for this project");
@@ -65,8 +73,8 @@ class EvalRunner {
65
73
  cases: cases.map((c) => ({
66
74
  caseId: c.id,
67
75
  input: { message: c.inputMessage, files: c.inputFiles },
68
- steps: c.steps as Array<{ agent_id: string; override_message?: string }>,
69
- output: { type: c.outputType } as OutputType,
76
+ steps: c.steps,
77
+ output: { type: "message_content" },
70
78
  eval: {
71
79
  content_assertion: c.contentAssertion,
72
80
  eval_rubrics: c.rubrics?.map((r) => ({
@@ -79,39 +87,35 @@ class EvalRunner {
79
87
  });
80
88
  }
81
89
 
82
- const runId = uuidv4();
83
- const concurrency = (project as unknown as Record<string, unknown>).concurrency as number || 3;
84
- await store.createRun(tenantId, projectId, runId, { totalCases, concurrency });
85
-
86
- const judgeCfg = project.judgeModelConfig as Record<string, unknown>;
87
- const hasModelKey = Boolean(judgeCfg.modelKey);
88
- const hasApiKey = Boolean(judgeCfg.apiKeyEnvName || judgeCfg.apiKey);
89
- const hasCredentials = hasApiKey;
90
-
91
- let judgeModelConfig: { modelKey?: string; model?: any } = {};
90
+ const judgeCfg = (project.judgeModelConfig ?? {}) as Record<string, unknown>;
91
+ const judgeModelKey = (judgeCfg.modelKey as string) || "";
92
+ let resolvedJudgeModelKey: string;
92
93
 
93
- if (hasModelKey) {
94
- judgeModelConfig = { modelKey: judgeCfg.modelKey as string };
95
- } else if (!hasCredentials) {
96
- const firstModel = modelLatticeManager.getAllLattices()[0];
97
- if (firstModel) {
98
- judgeModelConfig = { modelKey: firstModel.key };
99
- } else {
100
- judgeModelConfig = { model: judgeCfg as any };
94
+ if (judgeModelKey) {
95
+ if (!modelLatticeManager.hasLattice(judgeModelKey)) {
96
+ throw new Error(`Judge model "${judgeModelKey}" is not registered`);
101
97
  }
98
+ resolvedJudgeModelKey = judgeModelKey;
102
99
  } else {
103
- judgeModelConfig = { model: judgeCfg as any };
100
+ const firstModel = modelLatticeManager.getAllLattices()[0];
101
+ if (!firstModel) {
102
+ throw new Error("No model registered — cannot run eval without a judge model");
103
+ }
104
+ resolvedJudgeModelKey = firstModel.key;
105
+ console.warn(`[eval-runner] Project "${project.name}" has no judge modelKey — falling back to first registered model "${firstModel.key}"`);
104
106
  }
105
107
 
108
+ const runId = uuidv4();
109
+ const concurrency = Math.max(1, Math.floor((project as unknown as Record<string, unknown>).concurrency as number) || 3);
110
+ await store.createRun(tenantId, projectId, runId, { totalCases, concurrency });
111
+
106
112
  const projectConfig: LatticeEvalProjectType = {
107
113
  projectName: project.name,
108
114
  version: project.version,
109
115
  description: project.description,
110
116
  suites: evalSuites,
111
- judge_agent_config: judgeModelConfig,
117
+ judge_agent_config: { modelKey: resolvedJudgeModelKey },
112
118
  lattice_server_config: {
113
- base_url: (project.targetServerConfig as Record<string, string>).base_url || "",
114
- api_key: (project.targetServerConfig as Record<string, string>).api_key || "",
115
119
  tenant_id: tenantId,
116
120
  },
117
121
  concurrency,
@@ -163,37 +167,41 @@ class EvalRunner {
163
167
  const runPromise = (async () => {
164
168
  try {
165
169
  const evalProject = new LatticeEvalProject(projectConfig, onCaseComplete);
166
- const { report } = await evalProject.runAllSuitesBatch(concurrency);
167
-
168
- const completedCount = stats.passed + stats.failed;
169
- await store.updateRunStatus(tenantId, runId, {
170
- status: "completed",
171
- completedAt: new Date(),
172
- passedCases: stats.passed,
173
- failedCases: stats.failed,
174
- avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0,
175
- });
176
-
177
- this.eventEmitter.emit(`run:${runId}`, {
178
- type: "completed",
179
- runId,
180
- data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 },
181
- } satisfies EvalStreamEvent);
170
+ const { report } = await evalProject.runAllSuitesBatch(concurrency, abortController.signal);
171
+
172
+ if (!abortController.signal.aborted) {
173
+ const completedCount = stats.passed + stats.failed;
174
+ await store.updateRunStatus(tenantId, runId, {
175
+ status: "completed",
176
+ completedAt: new Date(),
177
+ passedCases: stats.passed,
178
+ failedCases: stats.failed,
179
+ avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0,
180
+ });
181
+
182
+ this.eventEmitter.emit(`run:${runId}`, {
183
+ type: "completed",
184
+ runId,
185
+ data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 },
186
+ } satisfies EvalStreamEvent);
187
+ }
182
188
 
183
189
  return { report };
184
190
  } catch (err) {
185
191
  const errorMsg = (err as Error).message;
186
- await store.updateRunStatus(tenantId, runId, {
187
- status: "failed",
188
- error: errorMsg,
189
- completedAt: new Date(),
190
- });
191
-
192
- this.eventEmitter.emit(`run:${runId}`, {
193
- type: "error",
194
- runId,
195
- data: { message: errorMsg },
196
- } satisfies EvalStreamEvent);
192
+ if (!abortController.signal.aborted) {
193
+ await store.updateRunStatus(tenantId, runId, {
194
+ status: "failed",
195
+ error: errorMsg,
196
+ completedAt: new Date(),
197
+ });
198
+
199
+ this.eventEmitter.emit(`run:${runId}`, {
200
+ type: "error",
201
+ runId,
202
+ data: { message: errorMsg },
203
+ } satisfies EvalStreamEvent);
204
+ }
197
205
 
198
206
  throw err;
199
207
  } finally {