@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.
@@ -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":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAyB,WAAW,EAAE,MAAM,eAAe,CAAC;AAO5F,iBAAS,OAAO,IAAI,EAAE,CAAC,IAAI,CAqB1B;AA0BD,eAAO,MAAM,EAAE;gBAEK,MAAM,WAAW,GAAG,EAAE;0BASZ,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;oBAQnC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;sBAQ3B,MAAM,gBAAgB,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;6BASrC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;yBAQnC,MAAM,WAAW,MAAM,aAAa,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;kCAQjE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;8BAO1B,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;qBAQnC,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;iCAS7B,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;+BAQjD,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;4BAQhD,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;uBAQ7B,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;oBA6BpC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;mBAQ9B,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;mBAgDxC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;qBASvB,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;iCAgBP,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;6BAQhE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;qCAehB,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;2BAQhC,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;4BAQrD,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;8BASnD,MAAM,YACJ,MAAM,gBACF;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,aACrD,MAAM,GAChB,OAAO,CAAC,WAAW,CAAC;8BAWb,MAAM,YACJ,MAAM,gBACF;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,aACrD,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;8BAWE,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;kCAQvC,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;qCAQzC,MAAM,GAAQ,OAAO,CAAC,WAAW,EAAE,CAAC;8BAahE,MAAM,QACR,OAAO,CAAC,IAAI,CAAC,aACR,MAAM,GAAG,MAAM,kBACV,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC;2BA8Ca,MAAM,UAAS,MAAM,GAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;2BAW3C,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;8BAU/C,MAAM,UACN,MAAM,GAAG,IAAI,UACb,eAAe,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,WAC1D,GAAG,GACX,OAAO,CAAC,IAAI,CAAC;2BAQa,MAAM,UAAS,MAAM,GAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;yBAW7C,MAAM,aAAa,MAAM,UAAU,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;CA4E5F,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -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;AAQpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAidpC,eAAe,MAAM,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"}
@@ -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
- // Create history snapshot after update with the updated values
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
- // Create history snapshot after update with the updated values
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layer-ai/core",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "Core API routes and services for Layer AI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",