@layer-ai/core 0.8.8 → 0.8.10
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/dist/lib/db/postgres.d.ts +1 -0
- package/dist/lib/db/postgres.d.ts.map +1 -1
- package/dist/lib/db/postgres.js +65 -0
- package/dist/lib/gate-utils.d.ts +6 -0
- package/dist/lib/gate-utils.d.ts.map +1 -0
- package/dist/lib/gate-utils.js +55 -0
- package/dist/routes/v1/gates.d.ts.map +1 -1
- package/dist/routes/v1/gates.js +91 -16
- package/package.json +1 -1
|
@@ -45,6 +45,7 @@ export declare const db: {
|
|
|
45
45
|
getGateHistoryById(id: string): Promise<any | null>;
|
|
46
46
|
createActivityLog(gateId: string, userId: string | null, action: "manual_update" | "auto_update" | "reanalysis" | "rollback", details: any): Promise<void>;
|
|
47
47
|
getActivityLog(gateId: string, limit?: number): Promise<any[]>;
|
|
48
|
+
rollbackGate(gateId: string, historyId: string, userId: string): Promise<Gate | null>;
|
|
48
49
|
};
|
|
49
50
|
export default getPool;
|
|
50
51
|
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/lib/db/postgres.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/lib/db/postgres.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/db/postgres.js
CHANGED
|
@@ -301,5 +301,70 @@ export const db = {
|
|
|
301
301
|
LIMIT $2`, [gateId, limit]);
|
|
302
302
|
return result.rows.map(toCamelCase);
|
|
303
303
|
},
|
|
304
|
+
async rollbackGate(gateId, historyId, userId) {
|
|
305
|
+
// Get the historical configuration
|
|
306
|
+
const historyEntry = await this.getGateHistoryById(historyId);
|
|
307
|
+
if (!historyEntry || historyEntry.gateId !== gateId) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
// Get the current gate state before rollback for history snapshot
|
|
311
|
+
const currentGate = await this.getGateById(gateId);
|
|
312
|
+
if (!currentGate) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
// Create a snapshot of the current state before rolling back
|
|
316
|
+
await this.createGateHistory(gateId, currentGate, 'user');
|
|
317
|
+
// Update the gate with the historical configuration
|
|
318
|
+
const result = await getPool().query(`UPDATE gates SET
|
|
319
|
+
name = $2,
|
|
320
|
+
description = $3,
|
|
321
|
+
model = $4,
|
|
322
|
+
fallback_models = $5,
|
|
323
|
+
routing_strategy = $6,
|
|
324
|
+
temperature = $7,
|
|
325
|
+
max_tokens = $8,
|
|
326
|
+
top_p = $9,
|
|
327
|
+
cost_weight = $10,
|
|
328
|
+
latency_weight = $11,
|
|
329
|
+
quality_weight = $12,
|
|
330
|
+
analysis_method = $13,
|
|
331
|
+
task_type = $14,
|
|
332
|
+
task_analysis = $15,
|
|
333
|
+
system_prompt = $16,
|
|
334
|
+
reanalysis_period = $17,
|
|
335
|
+
auto_apply_recommendations = $18,
|
|
336
|
+
updated_at = NOW()
|
|
337
|
+
WHERE id = $1 RETURNING *`, [
|
|
338
|
+
gateId,
|
|
339
|
+
historyEntry.name,
|
|
340
|
+
historyEntry.description,
|
|
341
|
+
historyEntry.model,
|
|
342
|
+
typeof historyEntry.fallbackModels === 'string' ? historyEntry.fallbackModels : JSON.stringify(historyEntry.fallbackModels || []),
|
|
343
|
+
historyEntry.routingStrategy,
|
|
344
|
+
historyEntry.temperature,
|
|
345
|
+
historyEntry.maxTokens,
|
|
346
|
+
historyEntry.topP,
|
|
347
|
+
historyEntry.costWeight ?? 0.33,
|
|
348
|
+
historyEntry.latencyWeight ?? 0.33,
|
|
349
|
+
historyEntry.qualityWeight ?? 0.34,
|
|
350
|
+
historyEntry.analysisMethod ?? 'balanced',
|
|
351
|
+
historyEntry.taskType,
|
|
352
|
+
typeof historyEntry.taskAnalysis === 'string' ? historyEntry.taskAnalysis : (historyEntry.taskAnalysis ? JSON.stringify(historyEntry.taskAnalysis) : null),
|
|
353
|
+
historyEntry.systemPrompt,
|
|
354
|
+
historyEntry.reanalysisPeriod ?? 'never',
|
|
355
|
+
historyEntry.autoApplyRecommendations ?? false
|
|
356
|
+
]);
|
|
357
|
+
const rolledBackGate = result.rows[0] ? toCamelCase(result.rows[0]) : null;
|
|
358
|
+
if (rolledBackGate) {
|
|
359
|
+
// Log the rollback activity
|
|
360
|
+
await this.createActivityLog(gateId, userId, 'rollback', {
|
|
361
|
+
historyId: historyId,
|
|
362
|
+
rolledBackTo: historyEntry.createdAt,
|
|
363
|
+
previousModel: currentGate.model,
|
|
364
|
+
newModel: historyEntry.model
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return rolledBackGate;
|
|
368
|
+
},
|
|
304
369
|
};
|
|
305
370
|
export default getPool;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects which significant fields have changed between existing and new gate configurations.
|
|
3
|
+
* Returns array of field names that changed. Only tracks fields that warrant a history snapshot.
|
|
4
|
+
*/
|
|
5
|
+
export declare function detectSignificantChanges(existing: any, updates: any): string[];
|
|
6
|
+
//# sourceMappingURL=gate-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-utils.d.ts","sourceRoot":"","sources":["../../src/lib/gate-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,CAuD9E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects which significant fields have changed between existing and new gate configurations.
|
|
3
|
+
* Returns array of field names that changed. Only tracks fields that warrant a history snapshot.
|
|
4
|
+
*/
|
|
5
|
+
export function detectSignificantChanges(existing, updates) {
|
|
6
|
+
const changedFields = [];
|
|
7
|
+
const normalizeArray = (val) => {
|
|
8
|
+
if (Array.isArray(val))
|
|
9
|
+
return JSON.stringify(val.sort());
|
|
10
|
+
if (typeof val === 'string') {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(val);
|
|
13
|
+
return JSON.stringify(Array.isArray(parsed) ? parsed.sort() : parsed);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return val;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return JSON.stringify(val || []);
|
|
20
|
+
};
|
|
21
|
+
const hasChanged = (field, existingVal, newVal) => {
|
|
22
|
+
if (newVal === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
if (field === 'fallbackModels') {
|
|
25
|
+
return normalizeArray(existingVal) !== normalizeArray(newVal);
|
|
26
|
+
}
|
|
27
|
+
const normalizedExisting = existingVal ?? null;
|
|
28
|
+
const normalizedNew = newVal ?? null;
|
|
29
|
+
return normalizedExisting !== normalizedNew;
|
|
30
|
+
};
|
|
31
|
+
const significantFields = {
|
|
32
|
+
name: 'name',
|
|
33
|
+
description: 'description',
|
|
34
|
+
model: 'model',
|
|
35
|
+
fallbackModels: 'fallbackModels',
|
|
36
|
+
routingStrategy: 'routingStrategy',
|
|
37
|
+
temperature: 'temperature',
|
|
38
|
+
maxTokens: 'maxTokens',
|
|
39
|
+
topP: 'topP',
|
|
40
|
+
costWeight: 'costWeight',
|
|
41
|
+
latencyWeight: 'latencyWeight',
|
|
42
|
+
qualityWeight: 'qualityWeight',
|
|
43
|
+
analysisMethod: 'analysisMethod',
|
|
44
|
+
taskType: 'taskType',
|
|
45
|
+
systemPrompt: 'systemPrompt',
|
|
46
|
+
reanalysisPeriod: 'reanalysisPeriod',
|
|
47
|
+
autoApplyRecommendations: 'autoApplyRecommendations',
|
|
48
|
+
};
|
|
49
|
+
for (const [field, displayName] of Object.entries(significantFields)) {
|
|
50
|
+
if (hasChanged(field, existing[field], updates[field])) {
|
|
51
|
+
changedFields.push(displayName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return changedFields;
|
|
55
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../src/routes/v1/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AASpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAsiBpC,eAAe,MAAM,CAAC"}
|
package/dist/routes/v1/gates.js
CHANGED
|
@@ -4,6 +4,7 @@ import { cache } from '../../lib/db/redis.js';
|
|
|
4
4
|
import { authenticate } from '../../middleware/auth.js';
|
|
5
5
|
import { callAdapter } from '../../lib/provider-factory.js';
|
|
6
6
|
import { MODEL_REGISTRY } from '@layer-ai/sdk';
|
|
7
|
+
import { detectSignificantChanges } from '../../lib/gate-utils.js';
|
|
7
8
|
const router = Router();
|
|
8
9
|
// All routes require authentication (SDK auth with Bearer token)
|
|
9
10
|
router.use(authenticate);
|
|
@@ -118,7 +119,7 @@ router.patch('/name/:name', async (req, res) => {
|
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
121
|
try {
|
|
121
|
-
const { description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, analysisMethod, reanalysisPeriod, taskAnalysis } = req.body;
|
|
122
|
+
const { description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, analysisMethod, reanalysisPeriod, taskAnalysis, autoApplyRecommendations } = req.body;
|
|
122
123
|
const existing = await db.getGateByUserAndName(req.userId, req.params.name);
|
|
123
124
|
if (!existing) {
|
|
124
125
|
res.status(404).json({ error: 'not_found', message: 'Gate not found' });
|
|
@@ -128,6 +129,24 @@ router.patch('/name/:name', async (req, res) => {
|
|
|
128
129
|
res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
132
|
+
// Detect significant changes before updating
|
|
133
|
+
const changedFields = detectSignificantChanges(existing, {
|
|
134
|
+
description,
|
|
135
|
+
taskType,
|
|
136
|
+
model,
|
|
137
|
+
systemPrompt,
|
|
138
|
+
temperature,
|
|
139
|
+
maxTokens,
|
|
140
|
+
topP,
|
|
141
|
+
routingStrategy,
|
|
142
|
+
fallbackModels,
|
|
143
|
+
costWeight,
|
|
144
|
+
latencyWeight,
|
|
145
|
+
qualityWeight,
|
|
146
|
+
analysisMethod,
|
|
147
|
+
reanalysisPeriod,
|
|
148
|
+
autoApplyRecommendations,
|
|
149
|
+
});
|
|
131
150
|
const updated = await db.updateGate(existing.id, {
|
|
132
151
|
description,
|
|
133
152
|
taskType,
|
|
@@ -146,15 +165,16 @@ router.patch('/name/:name', async (req, res) => {
|
|
|
146
165
|
analysisMethod,
|
|
147
166
|
reanalysisPeriod,
|
|
148
167
|
taskAnalysis,
|
|
168
|
+
autoApplyRecommendations,
|
|
149
169
|
});
|
|
150
|
-
//
|
|
151
|
-
if (updated) {
|
|
152
|
-
await db.createGateHistory(existing.id, updated, 'user');
|
|
170
|
+
// Only create history snapshot if significant changes were detected
|
|
171
|
+
if (updated && changedFields.length > 0) {
|
|
172
|
+
await db.createGateHistory(existing.id, updated, 'user', changedFields);
|
|
173
|
+
// Log manual update activity with specific changed fields
|
|
174
|
+
await db.createActivityLog(existing.id, req.userId, 'manual_update', {
|
|
175
|
+
changedFields
|
|
176
|
+
});
|
|
153
177
|
}
|
|
154
|
-
// Log manual update activity
|
|
155
|
-
await db.createActivityLog(existing.id, req.userId, 'manual_update', {
|
|
156
|
-
changedFields: Object.keys(req.body)
|
|
157
|
-
});
|
|
158
178
|
await cache.invalidateGate(req.userId, existing.name);
|
|
159
179
|
res.json(updated);
|
|
160
180
|
}
|
|
@@ -170,7 +190,7 @@ router.patch('/:id', async (req, res) => {
|
|
|
170
190
|
return;
|
|
171
191
|
}
|
|
172
192
|
try {
|
|
173
|
-
const { name, description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, analysisMethod, reanalysisPeriod, taskAnalysis } = req.body;
|
|
193
|
+
const { name, description, taskType, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels, costWeight, latencyWeight, qualityWeight, analysisMethod, reanalysisPeriod, taskAnalysis, autoApplyRecommendations } = req.body;
|
|
174
194
|
const existing = await db.getGateById(req.params.id);
|
|
175
195
|
if (!existing) {
|
|
176
196
|
res.status(404).json({ error: 'not_found', message: 'Gate not found' });
|
|
@@ -184,6 +204,25 @@ router.patch('/:id', async (req, res) => {
|
|
|
184
204
|
res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
|
|
185
205
|
return;
|
|
186
206
|
}
|
|
207
|
+
// Detect significant changes before updating
|
|
208
|
+
const changedFields = detectSignificantChanges(existing, {
|
|
209
|
+
name,
|
|
210
|
+
description,
|
|
211
|
+
taskType,
|
|
212
|
+
model,
|
|
213
|
+
systemPrompt,
|
|
214
|
+
temperature,
|
|
215
|
+
maxTokens,
|
|
216
|
+
topP,
|
|
217
|
+
routingStrategy,
|
|
218
|
+
fallbackModels,
|
|
219
|
+
costWeight,
|
|
220
|
+
latencyWeight,
|
|
221
|
+
qualityWeight,
|
|
222
|
+
analysisMethod,
|
|
223
|
+
reanalysisPeriod,
|
|
224
|
+
autoApplyRecommendations,
|
|
225
|
+
});
|
|
187
226
|
const updated = await db.updateGate(req.params.id, {
|
|
188
227
|
name,
|
|
189
228
|
description,
|
|
@@ -203,15 +242,16 @@ router.patch('/:id', async (req, res) => {
|
|
|
203
242
|
analysisMethod,
|
|
204
243
|
reanalysisPeriod,
|
|
205
244
|
taskAnalysis,
|
|
245
|
+
autoApplyRecommendations,
|
|
206
246
|
});
|
|
207
|
-
//
|
|
208
|
-
if (updated) {
|
|
209
|
-
await db.createGateHistory(req.params.id, updated, 'user');
|
|
247
|
+
// Only create history snapshot if significant changes were detected
|
|
248
|
+
if (updated && changedFields.length > 0) {
|
|
249
|
+
await db.createGateHistory(req.params.id, updated, 'user', changedFields);
|
|
250
|
+
// Log manual update activity with specific changed fields
|
|
251
|
+
await db.createActivityLog(req.params.id, req.userId, 'manual_update', {
|
|
252
|
+
changedFields
|
|
253
|
+
});
|
|
210
254
|
}
|
|
211
|
-
// Log manual update activity
|
|
212
|
-
await db.createActivityLog(req.params.id, req.userId, 'manual_update', {
|
|
213
|
-
changedFields: Object.keys(req.body)
|
|
214
|
-
});
|
|
215
255
|
await cache.invalidateGate(req.userId, existing.name);
|
|
216
256
|
res.json(updated);
|
|
217
257
|
}
|
|
@@ -404,4 +444,39 @@ router.post('/suggestions', async (req, res) => {
|
|
|
404
444
|
res.status(500).json({ error: 'internal_error', message: 'Failed to fetch suggestions' });
|
|
405
445
|
}
|
|
406
446
|
});
|
|
447
|
+
// POST /:id/rollback - Rollback gate to a previous configuration from history
|
|
448
|
+
router.post('/:id/rollback', async (req, res) => {
|
|
449
|
+
if (!req.userId) {
|
|
450
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const { historyId } = req.body;
|
|
455
|
+
if (!historyId) {
|
|
456
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: historyId' });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const gate = await db.getGateById(req.params.id);
|
|
460
|
+
if (!gate) {
|
|
461
|
+
res.status(404).json({ error: 'not_found', message: 'Gate not found' });
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (gate.userId !== req.userId) {
|
|
465
|
+
res.status(404).json({ error: 'not_found', message: 'Gate not found' });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Rollback the gate to the historical configuration
|
|
469
|
+
const updated = await db.rollbackGate(req.params.id, historyId, req.userId);
|
|
470
|
+
if (!updated) {
|
|
471
|
+
res.status(404).json({ error: 'not_found', message: 'History entry not found' });
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
await cache.invalidateGate(req.userId, gate.name);
|
|
475
|
+
res.json(updated);
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
console.error('Rollback gate error:', error);
|
|
479
|
+
res.status(500).json({ error: 'internal_error', message: 'Failed to rollback gate' });
|
|
480
|
+
}
|
|
481
|
+
});
|
|
407
482
|
export default router;
|