@layer-ai/core 2.0.2 → 2.0.4

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/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { default as logsRouter } from './routes/v1/logs.js';
5
5
  export { default as completeRouter } from './routes/v2/complete.js';
6
6
  export { default as chatRouter } from './routes/v3/chat.js';
7
7
  export { default as imageRouter } from './routes/v3/image.js';
8
+ export { default as videoRouter } from './routes/v3/video.js';
8
9
  export { authenticate } from './middleware/auth.js';
9
10
  export type {} from './middleware/auth.js';
10
11
  export { db } from './lib/db/postgres.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG3C,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAGrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAG3E,CAAC;AAGF,cAAc,6BAA6B,CAAC;AAG5C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG3C,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAGrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAG3E,CAAC;AAGF,cAAc,6BAA6B,CAAC;AAG5C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export { default as completeRouter } from './routes/v2/complete.js';
8
8
  // v3 routes
9
9
  export { default as chatRouter } from './routes/v3/chat.js';
10
10
  export { default as imageRouter } from './routes/v3/image.js';
11
+ export { default as videoRouter } from './routes/v3/video.js';
11
12
  // Middleware
12
13
  export { authenticate } from './middleware/auth.js';
13
14
  // Database
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=video.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/video.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA6NpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,181 @@
1
+ import { Router } from 'express';
2
+ import { db } from '../../lib/db/postgres.js';
3
+ import { authenticate } from '../../middleware/auth.js';
4
+ import { callAdapter, normalizeModelId } from '../../lib/provider-factory.js';
5
+ import { OverrideField } from '@layer-ai/sdk';
6
+ const router = Router();
7
+ // MARK:- Helper Functions
8
+ function isOverrideAllowed(allowOverrides, field) {
9
+ if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
10
+ return true;
11
+ if (allowOverrides === false)
12
+ return false;
13
+ return allowOverrides[field] ?? false;
14
+ }
15
+ function resolveFinalRequest(gateConfig, request) {
16
+ let finalModel = gateConfig.model;
17
+ if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
18
+ try {
19
+ finalModel = normalizeModelId(request.model);
20
+ }
21
+ catch {
22
+ finalModel = gateConfig.model;
23
+ }
24
+ }
25
+ // For image generation, we don't merge gate config like we do for chat
26
+ // The prompt and parameters are specific to the request
27
+ return {
28
+ ...request,
29
+ type: 'video',
30
+ model: normalizeModelId(finalModel),
31
+ };
32
+ }
33
+ function getModelsToTry(gateConfig, primaryModel) {
34
+ const modelsToTry = [primaryModel];
35
+ if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
36
+ modelsToTry.push(...gateConfig.fallbackModels);
37
+ }
38
+ return modelsToTry;
39
+ }
40
+ async function executeWithFallback(request, modelsToTry, userId) {
41
+ let result = null;
42
+ let lastError = null;
43
+ let modelUsed = request.model;
44
+ for (const modelToTry of modelsToTry) {
45
+ try {
46
+ const modelRequest = { ...request, model: modelToTry };
47
+ result = await callAdapter(modelRequest, userId);
48
+ modelUsed = modelToTry;
49
+ break;
50
+ }
51
+ catch (error) {
52
+ lastError = error;
53
+ console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
54
+ continue;
55
+ }
56
+ }
57
+ if (!result) {
58
+ throw lastError || new Error('All models failed');
59
+ }
60
+ return { result, modelUsed };
61
+ }
62
+ async function executeWithRoundRobin(gateConfig, request, userId) {
63
+ if (!gateConfig.fallbackModels?.length) {
64
+ const result = await callAdapter(request, userId);
65
+ return { result, modelUsed: request.model };
66
+ }
67
+ const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
68
+ const modelIndex = Math.floor(Math.random() * allModels.length);
69
+ const selectedModel = allModels[modelIndex];
70
+ const modelRequest = { ...request, model: selectedModel };
71
+ const result = await callAdapter(modelRequest, userId);
72
+ return { result, modelUsed: selectedModel };
73
+ }
74
+ async function executeWithRouting(gateConfig, request, userId) {
75
+ const modelsToTry = getModelsToTry(gateConfig, request.model);
76
+ switch (gateConfig.routingStrategy) {
77
+ case 'fallback':
78
+ return await executeWithFallback(request, modelsToTry, userId);
79
+ case 'round-robin':
80
+ return await executeWithRoundRobin(gateConfig, request, userId);
81
+ case 'single':
82
+ default:
83
+ const result = await callAdapter(request, userId);
84
+ return { result, modelUsed: request.model };
85
+ }
86
+ }
87
+ // MARK:- Route Handler
88
+ router.post('/', authenticate, async (req, res) => {
89
+ const startTime = Date.now();
90
+ if (!req.userId) {
91
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
92
+ return;
93
+ }
94
+ const userId = req.userId;
95
+ let gateConfig = null;
96
+ let request = null;
97
+ try {
98
+ const rawRequest = req.body;
99
+ if (!rawRequest.gateId) {
100
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
101
+ return;
102
+ }
103
+ const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawRequest.gateId);
104
+ if (!isUUID) {
105
+ res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
106
+ return;
107
+ }
108
+ gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
109
+ if (!gateConfig) {
110
+ res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
111
+ return;
112
+ }
113
+ // Validate video-specific fields
114
+ if (!rawRequest.data?.prompt || typeof rawRequest.data.prompt !== 'string' || rawRequest.data.prompt.trim().length === 0) {
115
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.prompt (must be a non-empty string)' });
116
+ return;
117
+ }
118
+ // Warn if gate is configured for a different task type
119
+ if (gateConfig.taskType && gateConfig.taskType !== 'video') {
120
+ console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
121
+ `but received request to /v3/video endpoint. Processing as video request.`);
122
+ }
123
+ request = {
124
+ gateId: rawRequest.gateId,
125
+ type: 'video',
126
+ data: rawRequest.data,
127
+ model: rawRequest.model,
128
+ metadata: rawRequest.metadata
129
+ };
130
+ const finalRequest = resolveFinalRequest(gateConfig, request);
131
+ const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
132
+ const latencyMs = Date.now() - startTime;
133
+ // Log request to database
134
+ db.logRequest({
135
+ userId,
136
+ gateId: gateConfig.id,
137
+ gateName: gateConfig.name,
138
+ modelRequested: request.model || gateConfig.model,
139
+ modelUsed: modelUsed,
140
+ promptTokens: result.usage?.promptTokens || 0,
141
+ completionTokens: result.usage?.completionTokens || 0,
142
+ totalTokens: result.usage?.totalTokens || 0,
143
+ costUsd: result.cost || 0,
144
+ latencyMs,
145
+ success: true,
146
+ errorMessage: null,
147
+ userAgent: req.headers['user-agent'] || null,
148
+ ipAddress: req.ip || null,
149
+ }).catch(err => console.error('Failed to log request:', err));
150
+ // Return LayerResponse with additional metadata
151
+ const response = {
152
+ ...result,
153
+ model: modelUsed,
154
+ latencyMs,
155
+ };
156
+ res.json(response);
157
+ }
158
+ catch (error) {
159
+ const latencyMs = Date.now() - startTime;
160
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
161
+ db.logRequest({
162
+ userId,
163
+ gateId: gateConfig?.id || null,
164
+ gateName: req.body?.gate || null,
165
+ modelRequested: (request?.model || gateConfig?.model) || 'unknown',
166
+ modelUsed: null,
167
+ promptTokens: 0,
168
+ completionTokens: 0,
169
+ totalTokens: 0,
170
+ costUsd: 0,
171
+ latencyMs,
172
+ success: false,
173
+ errorMessage,
174
+ userAgent: req.headers['user-agent'] || null,
175
+ ipAddress: req.ip || null,
176
+ }).catch(err => console.error('Failed to log request:', err));
177
+ console.error('Video generation error:', error);
178
+ res.status(500).json({ error: 'internal_error', message: errorMessage });
179
+ }
180
+ });
181
+ export default router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layer-ai/core",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Core API routes and services for Layer AI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "nanoid": "^5.0.4",
37
37
  "openai": "^4.24.0",
38
38
  "pg": "^8.11.3",
39
- "@layer-ai/sdk": "^2.2.0"
39
+ "@layer-ai/sdk": "^2.4.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bcryptjs": "^2.4.6",