@layer-ai/core 2.0.3 → 2.0.5
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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/routes/v3/embeddings.d.ts +4 -0
- package/dist/routes/v3/embeddings.d.ts.map +1 -0
- package/dist/routes/v3/embeddings.js +196 -0
- package/dist/routes/v3/ocr.d.ts +4 -0
- package/dist/routes/v3/ocr.d.ts.map +1 -0
- package/dist/routes/v3/ocr.js +184 -0
- package/dist/routes/v3/tts.d.ts +4 -0
- package/dist/routes/v3/tts.d.ts.map +1 -0
- package/dist/routes/v3/tts.js +180 -0
- package/dist/routes/v3/video.d.ts +4 -0
- package/dist/routes/v3/video.d.ts.map +1 -0
- package/dist/routes/v3/video.js +181 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ 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';
|
|
9
|
+
export { default as embeddingsRouter } from './routes/v3/embeddings.js';
|
|
10
|
+
export { default as ttsRouter } from './routes/v3/tts.js';
|
|
11
|
+
export { default as ocrRouter } from './routes/v3/ocr.js';
|
|
8
12
|
export { authenticate } from './middleware/auth.js';
|
|
9
13
|
export type {} from './middleware/auth.js';
|
|
10
14
|
export { db } from './lib/db/postgres.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAC9D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG1D,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,10 @@ 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';
|
|
12
|
+
export { default as embeddingsRouter } from './routes/v3/embeddings.js';
|
|
13
|
+
export { default as ttsRouter } from './routes/v3/tts.js';
|
|
14
|
+
export { default as ocrRouter } from './routes/v3/ocr.js';
|
|
11
15
|
// Middleware
|
|
12
16
|
export { authenticate } from './middleware/auth.js';
|
|
13
17
|
// Database
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/embeddings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA8OpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
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 embeddings, we don't merge gate config like we do for chat
|
|
26
|
+
return {
|
|
27
|
+
...request,
|
|
28
|
+
type: 'embeddings',
|
|
29
|
+
model: normalizeModelId(finalModel),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
33
|
+
const modelsToTry = [primaryModel];
|
|
34
|
+
if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
|
|
35
|
+
modelsToTry.push(...gateConfig.fallbackModels);
|
|
36
|
+
}
|
|
37
|
+
return modelsToTry;
|
|
38
|
+
}
|
|
39
|
+
async function executeWithFallback(request, modelsToTry, userId) {
|
|
40
|
+
let result = null;
|
|
41
|
+
let lastError = null;
|
|
42
|
+
let modelUsed = request.model;
|
|
43
|
+
for (const modelToTry of modelsToTry) {
|
|
44
|
+
try {
|
|
45
|
+
const modelRequest = { ...request, model: modelToTry };
|
|
46
|
+
result = await callAdapter(modelRequest, userId);
|
|
47
|
+
modelUsed = modelToTry;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!result) {
|
|
57
|
+
throw lastError || new Error('All models failed');
|
|
58
|
+
}
|
|
59
|
+
return { result, modelUsed };
|
|
60
|
+
}
|
|
61
|
+
async function executeWithRoundRobin(gateConfig, request, userId) {
|
|
62
|
+
if (!gateConfig.fallbackModels?.length) {
|
|
63
|
+
const result = await callAdapter(request, userId);
|
|
64
|
+
return { result, modelUsed: request.model };
|
|
65
|
+
}
|
|
66
|
+
const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
|
|
67
|
+
const modelIndex = Math.floor(Math.random() * allModels.length);
|
|
68
|
+
const selectedModel = allModels[modelIndex];
|
|
69
|
+
const modelRequest = { ...request, model: selectedModel };
|
|
70
|
+
const result = await callAdapter(modelRequest, userId);
|
|
71
|
+
return { result, modelUsed: selectedModel };
|
|
72
|
+
}
|
|
73
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
74
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
75
|
+
switch (gateConfig.routingStrategy) {
|
|
76
|
+
case 'fallback':
|
|
77
|
+
return await executeWithFallback(request, modelsToTry, userId);
|
|
78
|
+
case 'round-robin':
|
|
79
|
+
return await executeWithRoundRobin(gateConfig, request, userId);
|
|
80
|
+
case 'single':
|
|
81
|
+
default:
|
|
82
|
+
const result = await callAdapter(request, userId);
|
|
83
|
+
return { result, modelUsed: request.model };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// MARK:- Route Handler
|
|
87
|
+
router.post('/', authenticate, async (req, res) => {
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
if (!req.userId) {
|
|
90
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const userId = req.userId;
|
|
94
|
+
let gateConfig = null;
|
|
95
|
+
let request = null;
|
|
96
|
+
try {
|
|
97
|
+
const rawRequest = req.body;
|
|
98
|
+
if (!rawRequest.gateId) {
|
|
99
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
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);
|
|
103
|
+
if (!isUUID) {
|
|
104
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
108
|
+
if (!gateConfig) {
|
|
109
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Validate embeddings-specific fields
|
|
113
|
+
if (!rawRequest.data?.input) {
|
|
114
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.input' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Validate input is string or array of strings
|
|
118
|
+
const input = rawRequest.data.input;
|
|
119
|
+
if (typeof input !== 'string' && !Array.isArray(input)) {
|
|
120
|
+
res.status(400).json({ error: 'bad_request', message: 'data.input must be a string or array of strings' });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(input)) {
|
|
124
|
+
if (input.length === 0) {
|
|
125
|
+
res.status(400).json({ error: 'bad_request', message: 'data.input array must not be empty' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!input.every(item => typeof item === 'string')) {
|
|
129
|
+
res.status(400).json({ error: 'bad_request', message: 'data.input array must contain only strings' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Warn if gate is configured for a different task type
|
|
134
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'embeddings') {
|
|
135
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
136
|
+
`but received request to /v3/embeddings endpoint. Processing as embeddings request.`);
|
|
137
|
+
}
|
|
138
|
+
request = {
|
|
139
|
+
gateId: rawRequest.gateId,
|
|
140
|
+
type: 'embeddings',
|
|
141
|
+
data: rawRequest.data,
|
|
142
|
+
model: rawRequest.model,
|
|
143
|
+
metadata: rawRequest.metadata
|
|
144
|
+
};
|
|
145
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
146
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
147
|
+
const latencyMs = Date.now() - startTime;
|
|
148
|
+
// Log request to database
|
|
149
|
+
db.logRequest({
|
|
150
|
+
userId,
|
|
151
|
+
gateId: gateConfig.id,
|
|
152
|
+
gateName: gateConfig.name,
|
|
153
|
+
modelRequested: request.model || gateConfig.model,
|
|
154
|
+
modelUsed: modelUsed,
|
|
155
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
156
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
157
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
158
|
+
costUsd: result.cost || 0,
|
|
159
|
+
latencyMs,
|
|
160
|
+
success: true,
|
|
161
|
+
errorMessage: null,
|
|
162
|
+
userAgent: req.headers['user-agent'] || null,
|
|
163
|
+
ipAddress: req.ip || null,
|
|
164
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
165
|
+
// Return LayerResponse with additional metadata
|
|
166
|
+
const response = {
|
|
167
|
+
...result,
|
|
168
|
+
model: modelUsed,
|
|
169
|
+
latencyMs,
|
|
170
|
+
};
|
|
171
|
+
res.json(response);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const latencyMs = Date.now() - startTime;
|
|
175
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
176
|
+
db.logRequest({
|
|
177
|
+
userId,
|
|
178
|
+
gateId: gateConfig?.id || null,
|
|
179
|
+
gateName: req.body?.gate || null,
|
|
180
|
+
modelRequested: (request?.model || gateConfig?.model) || 'unknown',
|
|
181
|
+
modelUsed: null,
|
|
182
|
+
promptTokens: 0,
|
|
183
|
+
completionTokens: 0,
|
|
184
|
+
totalTokens: 0,
|
|
185
|
+
costUsd: 0,
|
|
186
|
+
latencyMs,
|
|
187
|
+
success: false,
|
|
188
|
+
errorMessage,
|
|
189
|
+
userAgent: req.headers['user-agent'] || null,
|
|
190
|
+
ipAddress: req.ip || null,
|
|
191
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
192
|
+
console.error('Embeddings error:', error);
|
|
193
|
+
res.status(500).json({ error: 'internal_error', message: errorMessage });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/ocr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgOpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
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 OCR, we don't merge gate config like we do for chat
|
|
26
|
+
return {
|
|
27
|
+
...request,
|
|
28
|
+
type: 'ocr',
|
|
29
|
+
model: normalizeModelId(finalModel),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
33
|
+
const modelsToTry = [primaryModel];
|
|
34
|
+
if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
|
|
35
|
+
modelsToTry.push(...gateConfig.fallbackModels);
|
|
36
|
+
}
|
|
37
|
+
return modelsToTry;
|
|
38
|
+
}
|
|
39
|
+
async function executeWithFallback(request, modelsToTry, userId) {
|
|
40
|
+
let result = null;
|
|
41
|
+
let lastError = null;
|
|
42
|
+
let modelUsed = request.model;
|
|
43
|
+
for (const modelToTry of modelsToTry) {
|
|
44
|
+
try {
|
|
45
|
+
const modelRequest = { ...request, model: modelToTry };
|
|
46
|
+
result = await callAdapter(modelRequest, userId);
|
|
47
|
+
modelUsed = modelToTry;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!result) {
|
|
57
|
+
throw lastError || new Error('All models failed');
|
|
58
|
+
}
|
|
59
|
+
return { result, modelUsed };
|
|
60
|
+
}
|
|
61
|
+
async function executeWithRoundRobin(gateConfig, request, userId) {
|
|
62
|
+
if (!gateConfig.fallbackModels?.length) {
|
|
63
|
+
const result = await callAdapter(request, userId);
|
|
64
|
+
return { result, modelUsed: request.model };
|
|
65
|
+
}
|
|
66
|
+
const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
|
|
67
|
+
const modelIndex = Math.floor(Math.random() * allModels.length);
|
|
68
|
+
const selectedModel = allModels[modelIndex];
|
|
69
|
+
const modelRequest = { ...request, model: selectedModel };
|
|
70
|
+
const result = await callAdapter(modelRequest, userId);
|
|
71
|
+
return { result, modelUsed: selectedModel };
|
|
72
|
+
}
|
|
73
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
74
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
75
|
+
switch (gateConfig.routingStrategy) {
|
|
76
|
+
case 'fallback':
|
|
77
|
+
return await executeWithFallback(request, modelsToTry, userId);
|
|
78
|
+
case 'round-robin':
|
|
79
|
+
return await executeWithRoundRobin(gateConfig, request, userId);
|
|
80
|
+
case 'single':
|
|
81
|
+
default:
|
|
82
|
+
const result = await callAdapter(request, userId);
|
|
83
|
+
return { result, modelUsed: request.model };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// MARK:- Route Handler
|
|
87
|
+
router.post('/', authenticate, async (req, res) => {
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
if (!req.userId) {
|
|
90
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const userId = req.userId;
|
|
94
|
+
let gateConfig = null;
|
|
95
|
+
let request = null;
|
|
96
|
+
try {
|
|
97
|
+
const rawRequest = req.body;
|
|
98
|
+
if (!rawRequest.gateId) {
|
|
99
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
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);
|
|
103
|
+
if (!isUUID) {
|
|
104
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
108
|
+
if (!gateConfig) {
|
|
109
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Validate OCR-specific fields - must have one of documentUrl, imageUrl, or base64
|
|
113
|
+
const data = rawRequest.data;
|
|
114
|
+
if (!data || (!data.documentUrl && !data.imageUrl && !data.base64)) {
|
|
115
|
+
res.status(400).json({
|
|
116
|
+
error: 'bad_request',
|
|
117
|
+
message: 'Missing required field: data must contain one of documentUrl, imageUrl, or base64'
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Warn if gate is configured for a different task type
|
|
122
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'document') {
|
|
123
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
124
|
+
`but received request to /v3/ocr endpoint. Processing as OCR request.`);
|
|
125
|
+
}
|
|
126
|
+
request = {
|
|
127
|
+
gateId: rawRequest.gateId,
|
|
128
|
+
type: 'ocr',
|
|
129
|
+
data: rawRequest.data,
|
|
130
|
+
model: rawRequest.model,
|
|
131
|
+
metadata: rawRequest.metadata
|
|
132
|
+
};
|
|
133
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
134
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
135
|
+
const latencyMs = Date.now() - startTime;
|
|
136
|
+
// Log request to database
|
|
137
|
+
db.logRequest({
|
|
138
|
+
userId,
|
|
139
|
+
gateId: gateConfig.id,
|
|
140
|
+
gateName: gateConfig.name,
|
|
141
|
+
modelRequested: request.model || gateConfig.model,
|
|
142
|
+
modelUsed: modelUsed,
|
|
143
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
144
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
145
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
146
|
+
costUsd: result.cost || 0,
|
|
147
|
+
latencyMs,
|
|
148
|
+
success: true,
|
|
149
|
+
errorMessage: null,
|
|
150
|
+
userAgent: req.headers['user-agent'] || null,
|
|
151
|
+
ipAddress: req.ip || null,
|
|
152
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
153
|
+
// Return LayerResponse with additional metadata
|
|
154
|
+
const response = {
|
|
155
|
+
...result,
|
|
156
|
+
model: modelUsed,
|
|
157
|
+
latencyMs,
|
|
158
|
+
};
|
|
159
|
+
res.json(response);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const latencyMs = Date.now() - startTime;
|
|
163
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
164
|
+
db.logRequest({
|
|
165
|
+
userId,
|
|
166
|
+
gateId: gateConfig?.id || null,
|
|
167
|
+
gateName: req.body?.gate || null,
|
|
168
|
+
modelRequested: (request?.model || gateConfig?.model) || 'unknown',
|
|
169
|
+
modelUsed: null,
|
|
170
|
+
promptTokens: 0,
|
|
171
|
+
completionTokens: 0,
|
|
172
|
+
totalTokens: 0,
|
|
173
|
+
costUsd: 0,
|
|
174
|
+
latencyMs,
|
|
175
|
+
success: false,
|
|
176
|
+
errorMessage,
|
|
177
|
+
userAgent: req.headers['user-agent'] || null,
|
|
178
|
+
ipAddress: req.ip || null,
|
|
179
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
180
|
+
console.error('OCR error:', error);
|
|
181
|
+
res.status(500).json({ error: 'internal_error', message: errorMessage });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/tts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA4NpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
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 TTS, we don't merge gate config like we do for chat
|
|
26
|
+
return {
|
|
27
|
+
...request,
|
|
28
|
+
type: 'tts',
|
|
29
|
+
model: normalizeModelId(finalModel),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
33
|
+
const modelsToTry = [primaryModel];
|
|
34
|
+
if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
|
|
35
|
+
modelsToTry.push(...gateConfig.fallbackModels);
|
|
36
|
+
}
|
|
37
|
+
return modelsToTry;
|
|
38
|
+
}
|
|
39
|
+
async function executeWithFallback(request, modelsToTry, userId) {
|
|
40
|
+
let result = null;
|
|
41
|
+
let lastError = null;
|
|
42
|
+
let modelUsed = request.model;
|
|
43
|
+
for (const modelToTry of modelsToTry) {
|
|
44
|
+
try {
|
|
45
|
+
const modelRequest = { ...request, model: modelToTry };
|
|
46
|
+
result = await callAdapter(modelRequest, userId);
|
|
47
|
+
modelUsed = modelToTry;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!result) {
|
|
57
|
+
throw lastError || new Error('All models failed');
|
|
58
|
+
}
|
|
59
|
+
return { result, modelUsed };
|
|
60
|
+
}
|
|
61
|
+
async function executeWithRoundRobin(gateConfig, request, userId) {
|
|
62
|
+
if (!gateConfig.fallbackModels?.length) {
|
|
63
|
+
const result = await callAdapter(request, userId);
|
|
64
|
+
return { result, modelUsed: request.model };
|
|
65
|
+
}
|
|
66
|
+
const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
|
|
67
|
+
const modelIndex = Math.floor(Math.random() * allModels.length);
|
|
68
|
+
const selectedModel = allModels[modelIndex];
|
|
69
|
+
const modelRequest = { ...request, model: selectedModel };
|
|
70
|
+
const result = await callAdapter(modelRequest, userId);
|
|
71
|
+
return { result, modelUsed: selectedModel };
|
|
72
|
+
}
|
|
73
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
74
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
75
|
+
switch (gateConfig.routingStrategy) {
|
|
76
|
+
case 'fallback':
|
|
77
|
+
return await executeWithFallback(request, modelsToTry, userId);
|
|
78
|
+
case 'round-robin':
|
|
79
|
+
return await executeWithRoundRobin(gateConfig, request, userId);
|
|
80
|
+
case 'single':
|
|
81
|
+
default:
|
|
82
|
+
const result = await callAdapter(request, userId);
|
|
83
|
+
return { result, modelUsed: request.model };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// MARK:- Route Handler
|
|
87
|
+
router.post('/', authenticate, async (req, res) => {
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
if (!req.userId) {
|
|
90
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const userId = req.userId;
|
|
94
|
+
let gateConfig = null;
|
|
95
|
+
let request = null;
|
|
96
|
+
try {
|
|
97
|
+
const rawRequest = req.body;
|
|
98
|
+
if (!rawRequest.gateId) {
|
|
99
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
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);
|
|
103
|
+
if (!isUUID) {
|
|
104
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
108
|
+
if (!gateConfig) {
|
|
109
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Validate TTS-specific fields
|
|
113
|
+
if (!rawRequest.data?.input || typeof rawRequest.data.input !== 'string' || rawRequest.data.input.trim().length === 0) {
|
|
114
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.input (must be a non-empty string)' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Warn if gate is configured for a different task type
|
|
118
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'tts') {
|
|
119
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
120
|
+
`but received request to /v3/tts endpoint. Processing as TTS request.`);
|
|
121
|
+
}
|
|
122
|
+
request = {
|
|
123
|
+
gateId: rawRequest.gateId,
|
|
124
|
+
type: 'tts',
|
|
125
|
+
data: rawRequest.data,
|
|
126
|
+
model: rawRequest.model,
|
|
127
|
+
metadata: rawRequest.metadata
|
|
128
|
+
};
|
|
129
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
130
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
131
|
+
const latencyMs = Date.now() - startTime;
|
|
132
|
+
// Log request to database
|
|
133
|
+
db.logRequest({
|
|
134
|
+
userId,
|
|
135
|
+
gateId: gateConfig.id,
|
|
136
|
+
gateName: gateConfig.name,
|
|
137
|
+
modelRequested: request.model || gateConfig.model,
|
|
138
|
+
modelUsed: modelUsed,
|
|
139
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
140
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
141
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
142
|
+
costUsd: result.cost || 0,
|
|
143
|
+
latencyMs,
|
|
144
|
+
success: true,
|
|
145
|
+
errorMessage: null,
|
|
146
|
+
userAgent: req.headers['user-agent'] || null,
|
|
147
|
+
ipAddress: req.ip || null,
|
|
148
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
149
|
+
// Return LayerResponse with additional metadata
|
|
150
|
+
const response = {
|
|
151
|
+
...result,
|
|
152
|
+
model: modelUsed,
|
|
153
|
+
latencyMs,
|
|
154
|
+
};
|
|
155
|
+
res.json(response);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const latencyMs = Date.now() - startTime;
|
|
159
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
160
|
+
db.logRequest({
|
|
161
|
+
userId,
|
|
162
|
+
gateId: gateConfig?.id || null,
|
|
163
|
+
gateName: req.body?.gate || null,
|
|
164
|
+
modelRequested: (request?.model || gateConfig?.model) || 'unknown',
|
|
165
|
+
modelUsed: null,
|
|
166
|
+
promptTokens: 0,
|
|
167
|
+
completionTokens: 0,
|
|
168
|
+
totalTokens: 0,
|
|
169
|
+
costUsd: 0,
|
|
170
|
+
latencyMs,
|
|
171
|
+
success: false,
|
|
172
|
+
errorMessage,
|
|
173
|
+
userAgent: req.headers['user-agent'] || null,
|
|
174
|
+
ipAddress: req.ip || null,
|
|
175
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
176
|
+
console.error('TTS error:', error);
|
|
177
|
+
res.status(500).json({ error: 'internal_error', message: errorMessage });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
export default router;
|
|
@@ -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.
|
|
3
|
+
"version": "2.0.5",
|
|
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.
|
|
39
|
+
"@layer-ai/sdk": "^2.5.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bcryptjs": "^2.4.6",
|