@layer-ai/core 1.0.0 → 2.0.0
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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/routes/v2/complete.d.ts.map +1 -1
- package/dist/routes/v2/complete.js +116 -26
- package/dist/routes/v3/chat.d.ts +4 -0
- package/dist/routes/v3/chat.d.ts.map +1 -0
- package/dist/routes/v3/chat.js +203 -0
- package/dist/routes/v3/completions/chat.d.ts +4 -0
- package/dist/routes/v3/completions/chat.d.ts.map +1 -0
- package/dist/routes/v3/completions/chat.js +178 -0
- package/dist/routes/v3/completions/embed.d.ts +4 -0
- package/dist/routes/v3/completions/embed.d.ts.map +1 -0
- package/dist/routes/v3/completions/embed.js +94 -0
- package/dist/routes/v3/completions/image.d.ts +4 -0
- package/dist/routes/v3/completions/image.d.ts.map +1 -0
- package/dist/routes/v3/completions/image.js +155 -0
- package/dist/routes/v3/completions/ocr.d.ts +4 -0
- package/dist/routes/v3/completions/ocr.d.ts.map +1 -0
- package/dist/routes/v3/completions/ocr.js +94 -0
- package/dist/routes/v3/completions/tts.d.ts +4 -0
- package/dist/routes/v3/completions/tts.d.ts.map +1 -0
- package/dist/routes/v3/completions/tts.js +94 -0
- package/dist/routes/v3/completions/video.d.ts +4 -0
- package/dist/routes/v3/completions/video.d.ts.map +1 -0
- package/dist/routes/v3/completions/video.js +94 -0
- package/dist/services/providers/base-adapter.d.ts.map +1 -1
- package/dist/services/providers/base-adapter.js +5 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { default as gatesRouter } from './routes/v1/gates.js';
|
|
|
3
3
|
export { default as keysRouter } from './routes/v1/keys.js';
|
|
4
4
|
export { default as logsRouter } from './routes/v1/logs.js';
|
|
5
5
|
export { default as completeRouter } from './routes/v2/complete.js';
|
|
6
|
+
export { default as chatRouter } from './routes/v3/chat.js';
|
|
6
7
|
export { authenticate } from './middleware/auth.js';
|
|
7
8
|
export type {} from './middleware/auth.js';
|
|
8
9
|
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;
|
|
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;AAG5D,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
|
@@ -3,7 +3,10 @@ export { default as authRouter } from './routes/v1/auth.js';
|
|
|
3
3
|
export { default as gatesRouter } from './routes/v1/gates.js';
|
|
4
4
|
export { default as keysRouter } from './routes/v1/keys.js';
|
|
5
5
|
export { default as logsRouter } from './routes/v1/logs.js';
|
|
6
|
+
// v2 routes
|
|
6
7
|
export { default as completeRouter } from './routes/v2/complete.js';
|
|
8
|
+
// v3 routes
|
|
9
|
+
export { default as chatRouter } from './routes/v3/chat.js';
|
|
7
10
|
// Middleware
|
|
8
11
|
export { authenticate } from './middleware/auth.js';
|
|
9
12
|
// Database
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AASpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"complete.d.ts","sourceRoot":"","sources":["../../../src/routes/v2/complete.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AASpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAkVpC,eAAe,MAAM,CAAC"}
|
|
@@ -13,7 +13,6 @@ function isOverrideAllowed(allowOverrides, field) {
|
|
|
13
13
|
return allowOverrides[field] ?? false;
|
|
14
14
|
}
|
|
15
15
|
function resolveFinalRequest(gateConfig, request) {
|
|
16
|
-
const finalRequest = { ...request };
|
|
17
16
|
let finalModel = gateConfig.model;
|
|
18
17
|
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
19
18
|
try {
|
|
@@ -23,33 +22,85 @@ function resolveFinalRequest(gateConfig, request) {
|
|
|
23
22
|
finalModel = gateConfig.model;
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
chatData.systemPrompt
|
|
25
|
+
// Use discriminated union to handle each request type
|
|
26
|
+
switch (request.type) {
|
|
27
|
+
case 'chat': {
|
|
28
|
+
const chatData = { ...request.data };
|
|
29
|
+
if (!chatData.systemPrompt && gateConfig.systemPrompt) {
|
|
30
|
+
chatData.systemPrompt = gateConfig.systemPrompt;
|
|
31
|
+
}
|
|
32
|
+
if (chatData.temperature === undefined && gateConfig.temperature !== undefined) {
|
|
33
|
+
chatData.temperature = gateConfig.temperature;
|
|
34
|
+
}
|
|
35
|
+
else if (chatData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
|
|
36
|
+
chatData.temperature = gateConfig.temperature;
|
|
37
|
+
}
|
|
38
|
+
if (chatData.maxTokens === undefined && gateConfig.maxTokens !== undefined) {
|
|
39
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
40
|
+
}
|
|
41
|
+
else if (chatData.maxTokens !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.MaxTokens)) {
|
|
42
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
43
|
+
}
|
|
44
|
+
if (chatData.topP === undefined && gateConfig.topP !== undefined) {
|
|
45
|
+
chatData.topP = gateConfig.topP;
|
|
46
|
+
}
|
|
47
|
+
else if (chatData.topP !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.TopP)) {
|
|
48
|
+
chatData.topP = gateConfig.topP;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...request,
|
|
52
|
+
model: normalizeModelId(finalModel),
|
|
53
|
+
data: chatData,
|
|
54
|
+
};
|
|
31
55
|
}
|
|
32
|
-
|
|
33
|
-
|
|
56
|
+
case 'image': {
|
|
57
|
+
// TODO: Future enhancement - intelligently apply gate-level defaults
|
|
58
|
+
// Potential features:
|
|
59
|
+
// - Apply systemPrompt by intelligently merging with user prompt
|
|
60
|
+
// - Support defaults for size, quality, style, count
|
|
61
|
+
// - Make this opt-in with a gate setting (e.g., applySystemPromptToAllTasks)
|
|
62
|
+
return {
|
|
63
|
+
...request,
|
|
64
|
+
model: normalizeModelId(finalModel),
|
|
65
|
+
};
|
|
34
66
|
}
|
|
35
|
-
|
|
36
|
-
|
|
67
|
+
case 'video': {
|
|
68
|
+
// TODO: Future enhancement - intelligently apply gate-level defaults
|
|
69
|
+
// Similar to image generation, could support systemPrompt integration
|
|
70
|
+
// and video-specific defaults (duration, size, fps)
|
|
71
|
+
return {
|
|
72
|
+
...request,
|
|
73
|
+
model: normalizeModelId(finalModel),
|
|
74
|
+
};
|
|
37
75
|
}
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
case 'embeddings': {
|
|
77
|
+
// Embeddings are deterministic and don't typically need gate-level defaults
|
|
78
|
+
// beyond model selection (already handled)
|
|
79
|
+
return {
|
|
80
|
+
...request,
|
|
81
|
+
model: normalizeModelId(finalModel),
|
|
82
|
+
};
|
|
40
83
|
}
|
|
41
|
-
|
|
42
|
-
|
|
84
|
+
case 'tts': {
|
|
85
|
+
// TODO: Future enhancement - support defaults for voice, speed, format
|
|
86
|
+
return {
|
|
87
|
+
...request,
|
|
88
|
+
model: normalizeModelId(finalModel),
|
|
89
|
+
};
|
|
43
90
|
}
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
case 'ocr': {
|
|
92
|
+
// TODO: Future enhancement - support defaults for tableFormat, includeImageBase64, etc.
|
|
93
|
+
return {
|
|
94
|
+
...request,
|
|
95
|
+
model: normalizeModelId(finalModel),
|
|
96
|
+
};
|
|
46
97
|
}
|
|
47
|
-
|
|
48
|
-
|
|
98
|
+
default: {
|
|
99
|
+
// This will cause a TypeScript error if we miss a case
|
|
100
|
+
const exhaustiveCheck = request;
|
|
101
|
+
throw new Error(`Unhandled request type: ${exhaustiveCheck.type}`);
|
|
49
102
|
}
|
|
50
|
-
finalRequest.data = chatData;
|
|
51
103
|
}
|
|
52
|
-
return finalRequest;
|
|
53
104
|
}
|
|
54
105
|
function getModelsToTry(gateConfig, primaryModel) {
|
|
55
106
|
const modelsToTry = [primaryModel];
|
|
@@ -131,7 +182,13 @@ router.post('/', authenticate, async (req, res) => {
|
|
|
131
182
|
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
132
183
|
return;
|
|
133
184
|
}
|
|
134
|
-
|
|
185
|
+
// Default to gate's taskType, allow override via request.type
|
|
186
|
+
const requestType = rawRequest.type || gateConfig.taskType;
|
|
187
|
+
// Warn if request type doesn't match gate's taskType (possible misconfiguration)
|
|
188
|
+
if (rawRequest.type && gateConfig.taskType && rawRequest.type !== gateConfig.taskType) {
|
|
189
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
190
|
+
`but received request with type="${rawRequest.type}". Using request type as override.`);
|
|
191
|
+
}
|
|
135
192
|
request = {
|
|
136
193
|
gateId: rawRequest.gateId,
|
|
137
194
|
type: requestType,
|
|
@@ -139,11 +196,44 @@ router.post('/', authenticate, async (req, res) => {
|
|
|
139
196
|
model: rawRequest.model,
|
|
140
197
|
metadata: rawRequest.metadata
|
|
141
198
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
199
|
+
// Validate required fields based on request type
|
|
200
|
+
switch (request.type) {
|
|
201
|
+
case 'chat':
|
|
202
|
+
if (!request.data.messages || !Array.isArray(request.data.messages) || request.data.messages.length === 0) {
|
|
203
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.messages' });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
case 'image':
|
|
208
|
+
if (!request.data.prompt) {
|
|
209
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.prompt' });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
case 'video':
|
|
214
|
+
if (!request.data.prompt) {
|
|
215
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.prompt' });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
case 'embeddings':
|
|
220
|
+
if (!request.data.input) {
|
|
221
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.input' });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case 'tts':
|
|
226
|
+
if (!request.data.input) {
|
|
227
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.input' });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case 'ocr':
|
|
232
|
+
if (!request.data.documentUrl && !request.data.imageUrl && !request.data.base64) {
|
|
233
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data must contain one of documentUrl, imageUrl, or base64' });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
147
237
|
}
|
|
148
238
|
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
149
239
|
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAqPpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
// Since this is v3/chat endpoint, we know the data is ChatRequest
|
|
26
|
+
const chatData = { ...request.data };
|
|
27
|
+
if (!chatData.systemPrompt && gateConfig.systemPrompt) {
|
|
28
|
+
chatData.systemPrompt = gateConfig.systemPrompt;
|
|
29
|
+
}
|
|
30
|
+
if (chatData.temperature === undefined && gateConfig.temperature !== undefined) {
|
|
31
|
+
chatData.temperature = gateConfig.temperature;
|
|
32
|
+
}
|
|
33
|
+
else if (chatData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
|
|
34
|
+
chatData.temperature = gateConfig.temperature;
|
|
35
|
+
}
|
|
36
|
+
if (chatData.maxTokens === undefined && gateConfig.maxTokens !== undefined) {
|
|
37
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
38
|
+
}
|
|
39
|
+
else if (chatData.maxTokens !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.MaxTokens)) {
|
|
40
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
41
|
+
}
|
|
42
|
+
if (chatData.topP === undefined && gateConfig.topP !== undefined) {
|
|
43
|
+
chatData.topP = gateConfig.topP;
|
|
44
|
+
}
|
|
45
|
+
else if (chatData.topP !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.TopP)) {
|
|
46
|
+
chatData.topP = gateConfig.topP;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...request,
|
|
50
|
+
type: 'chat',
|
|
51
|
+
model: normalizeModelId(finalModel),
|
|
52
|
+
data: chatData,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
56
|
+
const modelsToTry = [primaryModel];
|
|
57
|
+
if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
|
|
58
|
+
modelsToTry.push(...gateConfig.fallbackModels);
|
|
59
|
+
}
|
|
60
|
+
return modelsToTry;
|
|
61
|
+
}
|
|
62
|
+
async function executeWithFallback(request, modelsToTry, userId) {
|
|
63
|
+
let result = null;
|
|
64
|
+
let lastError = null;
|
|
65
|
+
let modelUsed = request.model;
|
|
66
|
+
for (const modelToTry of modelsToTry) {
|
|
67
|
+
try {
|
|
68
|
+
const modelRequest = { ...request, model: modelToTry };
|
|
69
|
+
result = await callAdapter(modelRequest, userId);
|
|
70
|
+
modelUsed = modelToTry;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
lastError = error;
|
|
75
|
+
console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!result) {
|
|
80
|
+
throw lastError || new Error('All models failed');
|
|
81
|
+
}
|
|
82
|
+
return { result, modelUsed };
|
|
83
|
+
}
|
|
84
|
+
async function executeWithRoundRobin(gateConfig, request, userId) {
|
|
85
|
+
if (!gateConfig.fallbackModels?.length) {
|
|
86
|
+
const result = await callAdapter(request, userId);
|
|
87
|
+
return { result, modelUsed: request.model };
|
|
88
|
+
}
|
|
89
|
+
const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
|
|
90
|
+
const modelIndex = Math.floor(Math.random() * allModels.length);
|
|
91
|
+
const selectedModel = allModels[modelIndex];
|
|
92
|
+
const modelRequest = { ...request, model: selectedModel };
|
|
93
|
+
const result = await callAdapter(modelRequest, userId);
|
|
94
|
+
return { result, modelUsed: selectedModel };
|
|
95
|
+
}
|
|
96
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
97
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
98
|
+
switch (gateConfig.routingStrategy) {
|
|
99
|
+
case 'fallback':
|
|
100
|
+
return await executeWithFallback(request, modelsToTry, userId);
|
|
101
|
+
case 'round-robin':
|
|
102
|
+
return await executeWithRoundRobin(gateConfig, request, userId);
|
|
103
|
+
case 'single':
|
|
104
|
+
default:
|
|
105
|
+
const result = await callAdapter(request, userId);
|
|
106
|
+
return { result, modelUsed: request.model };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// MARK:- Route Handler
|
|
110
|
+
router.post('/', authenticate, async (req, res) => {
|
|
111
|
+
const startTime = Date.now();
|
|
112
|
+
if (!req.userId) {
|
|
113
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const userId = req.userId;
|
|
117
|
+
let gateConfig = null;
|
|
118
|
+
let request = null;
|
|
119
|
+
try {
|
|
120
|
+
const rawRequest = req.body;
|
|
121
|
+
if (!rawRequest.gateId) {
|
|
122
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
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);
|
|
126
|
+
if (!isUUID) {
|
|
127
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
131
|
+
if (!gateConfig) {
|
|
132
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Validate chat-specific fields
|
|
136
|
+
if (!rawRequest.data?.messages || !Array.isArray(rawRequest.data.messages) || rawRequest.data.messages.length === 0) {
|
|
137
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.messages (must be a non-empty array)' });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Warn if gate is configured for a different task type
|
|
141
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'chat') {
|
|
142
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
143
|
+
`but received request to /v3/chat endpoint. Processing as chat request.`);
|
|
144
|
+
}
|
|
145
|
+
request = {
|
|
146
|
+
gateId: rawRequest.gateId,
|
|
147
|
+
type: 'chat',
|
|
148
|
+
data: rawRequest.data,
|
|
149
|
+
model: rawRequest.model,
|
|
150
|
+
metadata: rawRequest.metadata
|
|
151
|
+
};
|
|
152
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
153
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
154
|
+
const latencyMs = Date.now() - startTime;
|
|
155
|
+
// Log request to database
|
|
156
|
+
db.logRequest({
|
|
157
|
+
userId,
|
|
158
|
+
gateId: gateConfig.id,
|
|
159
|
+
gateName: gateConfig.name,
|
|
160
|
+
modelRequested: request.model || gateConfig.model,
|
|
161
|
+
modelUsed: modelUsed,
|
|
162
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
163
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
164
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
165
|
+
costUsd: result.cost || 0,
|
|
166
|
+
latencyMs,
|
|
167
|
+
success: true,
|
|
168
|
+
errorMessage: null,
|
|
169
|
+
userAgent: req.headers['user-agent'] || null,
|
|
170
|
+
ipAddress: req.ip || null,
|
|
171
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
172
|
+
// Return LayerResponse with additional metadata
|
|
173
|
+
const response = {
|
|
174
|
+
...result,
|
|
175
|
+
model: modelUsed,
|
|
176
|
+
latencyMs,
|
|
177
|
+
};
|
|
178
|
+
res.json(response);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const latencyMs = Date.now() - startTime;
|
|
182
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
183
|
+
db.logRequest({
|
|
184
|
+
userId,
|
|
185
|
+
gateId: gateConfig?.id || null,
|
|
186
|
+
gateName: req.body?.gate || null,
|
|
187
|
+
modelRequested: (request?.model || gateConfig?.model) || 'unknown',
|
|
188
|
+
modelUsed: null,
|
|
189
|
+
promptTokens: 0,
|
|
190
|
+
completionTokens: 0,
|
|
191
|
+
totalTokens: 0,
|
|
192
|
+
costUsd: 0,
|
|
193
|
+
latencyMs,
|
|
194
|
+
success: false,
|
|
195
|
+
errorMessage,
|
|
196
|
+
userAgent: req.headers['user-agent'] || null,
|
|
197
|
+
ipAddress: req.ip || null,
|
|
198
|
+
}).catch(err => console.error('Failed to log request:', err));
|
|
199
|
+
console.error('Chat completion error:', error);
|
|
200
|
+
res.status(500).json({ error: 'internal_error', message: errorMessage });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAoNpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const chatData = { ...request.data };
|
|
25
|
+
if (!chatData.systemPrompt && gateConfig.systemPrompt) {
|
|
26
|
+
chatData.systemPrompt = gateConfig.systemPrompt;
|
|
27
|
+
}
|
|
28
|
+
if (chatData.temperature === undefined && gateConfig.temperature !== undefined) {
|
|
29
|
+
chatData.temperature = gateConfig.temperature;
|
|
30
|
+
}
|
|
31
|
+
else if (chatData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
|
|
32
|
+
chatData.temperature = gateConfig.temperature;
|
|
33
|
+
}
|
|
34
|
+
if (chatData.maxTokens === undefined && gateConfig.maxTokens !== undefined) {
|
|
35
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
36
|
+
}
|
|
37
|
+
else if (chatData.maxTokens !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.MaxTokens)) {
|
|
38
|
+
chatData.maxTokens = gateConfig.maxTokens;
|
|
39
|
+
}
|
|
40
|
+
if (chatData.topP === undefined && gateConfig.topP !== undefined) {
|
|
41
|
+
chatData.topP = gateConfig.topP;
|
|
42
|
+
}
|
|
43
|
+
else if (chatData.topP !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.TopP)) {
|
|
44
|
+
chatData.topP = gateConfig.topP;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
...request,
|
|
48
|
+
model: normalizeModelId(finalModel),
|
|
49
|
+
data: chatData,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
53
|
+
const modelsToTry = [primaryModel];
|
|
54
|
+
if (gateConfig.fallback1) {
|
|
55
|
+
try {
|
|
56
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Invalid fallback1 model: ${gateConfig.fallback1}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (gateConfig.fallback2) {
|
|
63
|
+
try {
|
|
64
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(`Invalid fallback2 model: ${gateConfig.fallback2}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return modelsToTry;
|
|
71
|
+
}
|
|
72
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
73
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
74
|
+
let lastError = null;
|
|
75
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
76
|
+
const model = modelsToTry[i];
|
|
77
|
+
const requestWithModel = {
|
|
78
|
+
...request,
|
|
79
|
+
model,
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
const result = await callAdapter(requestWithModel, userId);
|
|
83
|
+
return { result, modelUsed: model };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(`Model ${model} failed (attempt ${i + 1}/${modelsToTry.length}):`, error);
|
|
87
|
+
lastError = error;
|
|
88
|
+
if (i < modelsToTry.length - 1) {
|
|
89
|
+
console.log(`Trying fallback model: ${modelsToTry[i + 1]}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastError || new Error('All models failed');
|
|
94
|
+
}
|
|
95
|
+
router.post('/', authenticate, async (req, res) => {
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
if (!req.userId) {
|
|
98
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const userId = req.userId;
|
|
102
|
+
let gateConfig = null;
|
|
103
|
+
try {
|
|
104
|
+
const rawRequest = req.body;
|
|
105
|
+
if (!rawRequest.gateId) {
|
|
106
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!rawRequest.data || !rawRequest.data.messages || !Array.isArray(rawRequest.data.messages) || rawRequest.data.messages.length === 0) {
|
|
110
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.messages (must be a non-empty array)' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
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);
|
|
114
|
+
if (!isUUID) {
|
|
115
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
119
|
+
if (!gateConfig) {
|
|
120
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'chat') {
|
|
124
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
125
|
+
`but received request to /v3/chat endpoint. Processing as chat request.`);
|
|
126
|
+
}
|
|
127
|
+
const request = {
|
|
128
|
+
gateId: rawRequest.gateId,
|
|
129
|
+
type: 'chat',
|
|
130
|
+
data: rawRequest.data,
|
|
131
|
+
model: rawRequest.model,
|
|
132
|
+
metadata: rawRequest.metadata
|
|
133
|
+
};
|
|
134
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
135
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
136
|
+
const latencyMs = Date.now() - startTime;
|
|
137
|
+
db.logRequest({
|
|
138
|
+
userId,
|
|
139
|
+
gateId: gateConfig.id,
|
|
140
|
+
model: modelUsed,
|
|
141
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
142
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
143
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
144
|
+
cost: result.cost || 0,
|
|
145
|
+
latencyMs,
|
|
146
|
+
success: true,
|
|
147
|
+
}).catch(error => {
|
|
148
|
+
console.error('Failed to log request:', error);
|
|
149
|
+
});
|
|
150
|
+
res.status(200).json(result);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const latencyMs = Date.now() - startTime;
|
|
154
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
155
|
+
console.error('[v3/chat] Error processing request:', error);
|
|
156
|
+
if (gateConfig) {
|
|
157
|
+
db.logRequest({
|
|
158
|
+
userId,
|
|
159
|
+
gateId: gateConfig.id,
|
|
160
|
+
model: gateConfig.model,
|
|
161
|
+
promptTokens: 0,
|
|
162
|
+
completionTokens: 0,
|
|
163
|
+
totalTokens: 0,
|
|
164
|
+
cost: 0,
|
|
165
|
+
latencyMs,
|
|
166
|
+
success: false,
|
|
167
|
+
errorMessage,
|
|
168
|
+
}).catch(logError => {
|
|
169
|
+
console.error('Failed to log error:', logError);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
res.status(500).json({
|
|
173
|
+
error: 'internal_server_error',
|
|
174
|
+
message: errorMessage
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/embed.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgGpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...request, model: normalizeModelId(finalModel) };
|
|
25
|
+
}
|
|
26
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
27
|
+
const modelsToTry = [primaryModel];
|
|
28
|
+
if (gateConfig.fallback1) {
|
|
29
|
+
try {
|
|
30
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
31
|
+
}
|
|
32
|
+
catch (error) { }
|
|
33
|
+
}
|
|
34
|
+
if (gateConfig.fallback2) {
|
|
35
|
+
try {
|
|
36
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) { }
|
|
39
|
+
}
|
|
40
|
+
return modelsToTry;
|
|
41
|
+
}
|
|
42
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
43
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
44
|
+
let lastError = null;
|
|
45
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
46
|
+
const model = modelsToTry[i];
|
|
47
|
+
try {
|
|
48
|
+
const result = await callAdapter({ ...request, model }, userId);
|
|
49
|
+
return { result, modelUsed: model };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError || new Error('All models failed');
|
|
56
|
+
}
|
|
57
|
+
router.post('/', authenticate, async (req, res) => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
if (!req.userId) {
|
|
60
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const userId = req.userId;
|
|
64
|
+
let gateConfig = null;
|
|
65
|
+
try {
|
|
66
|
+
const rawRequest = req.body;
|
|
67
|
+
if (!rawRequest.gateId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawRequest.gateId)) {
|
|
68
|
+
res.status(400).json({ error: 'bad_request', message: 'Valid gateId (UUID) is required' });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!rawRequest.data?.prompt || typeof rawRequest.data.input !== 'string' || !rawRequest.data.input.trim()) {
|
|
72
|
+
res.status(400).json({ error: 'bad_request', message: 'data.input is required' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
76
|
+
if (!gateConfig) {
|
|
77
|
+
res.status(404).json({ error: 'not_found', message: `Gate not found` });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const request = { gateId: rawRequest.gateId, type: 'embeddings', data: rawRequest.data, model: rawRequest.model, metadata: rawRequest.metadata };
|
|
81
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
82
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
83
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: modelUsed, promptTokens: result.usage?.promptTokens || 0, completionTokens: result.usage?.completionTokens || 0, totalTokens: result.usage?.totalTokens || 0, cost: result.cost || 0, latencyMs: Date.now() - startTime, success: true }).catch(() => { });
|
|
84
|
+
res.status(200).json(result);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
88
|
+
if (gateConfig) {
|
|
89
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: gateConfig.model, promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0, latencyMs: Date.now() - startTime, success: false, errorMessage }).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
res.status(500).json({ error: 'internal_server_error', message: errorMessage });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA2LpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
...request,
|
|
26
|
+
model: normalizeModelId(finalModel),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
30
|
+
const modelsToTry = [primaryModel];
|
|
31
|
+
if (gateConfig.fallback1) {
|
|
32
|
+
try {
|
|
33
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(`Invalid fallback1 model: ${gateConfig.fallback1}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (gateConfig.fallback2) {
|
|
40
|
+
try {
|
|
41
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(`Invalid fallback2 model: ${gateConfig.fallback2}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return modelsToTry;
|
|
48
|
+
}
|
|
49
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
50
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
51
|
+
let lastError = null;
|
|
52
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
53
|
+
const model = modelsToTry[i];
|
|
54
|
+
const requestWithModel = {
|
|
55
|
+
...request,
|
|
56
|
+
model,
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
const result = await callAdapter(requestWithModel, userId);
|
|
60
|
+
return { result, modelUsed: model };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error(`Model ${model} failed (attempt ${i + 1}/${modelsToTry.length}):`, error);
|
|
64
|
+
lastError = error;
|
|
65
|
+
if (i < modelsToTry.length - 1) {
|
|
66
|
+
console.log(`Trying fallback model: ${modelsToTry[i + 1]}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw lastError || new Error('All models failed');
|
|
71
|
+
}
|
|
72
|
+
router.post('/', authenticate, async (req, res) => {
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
if (!req.userId) {
|
|
75
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const userId = req.userId;
|
|
79
|
+
let gateConfig = null;
|
|
80
|
+
try {
|
|
81
|
+
const rawRequest = req.body;
|
|
82
|
+
if (!rawRequest.gateId) {
|
|
83
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: gateId' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!rawRequest.data || !rawRequest.data.prompt || typeof rawRequest.data.prompt !== 'string' || rawRequest.data.prompt.trim().length === 0) {
|
|
87
|
+
res.status(400).json({ error: 'bad_request', message: 'Missing required field: data.prompt (must be a non-empty string)' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
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);
|
|
91
|
+
if (!isUUID) {
|
|
92
|
+
res.status(400).json({ error: 'bad_request', message: 'gateId must be a valid UUID' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
96
|
+
if (!gateConfig) {
|
|
97
|
+
res.status(404).json({ error: 'not_found', message: `Gate with ID "${rawRequest.gateId}" not found` });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'image') {
|
|
101
|
+
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
102
|
+
`but received request to /v3/image endpoint. Processing as image request.`);
|
|
103
|
+
}
|
|
104
|
+
const request = {
|
|
105
|
+
gateId: rawRequest.gateId,
|
|
106
|
+
type: 'image',
|
|
107
|
+
data: rawRequest.data,
|
|
108
|
+
model: rawRequest.model,
|
|
109
|
+
metadata: rawRequest.metadata
|
|
110
|
+
};
|
|
111
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
112
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
113
|
+
const latencyMs = Date.now() - startTime;
|
|
114
|
+
db.logRequest({
|
|
115
|
+
userId,
|
|
116
|
+
gateId: gateConfig.id,
|
|
117
|
+
model: modelUsed,
|
|
118
|
+
promptTokens: result.usage?.promptTokens || 0,
|
|
119
|
+
completionTokens: result.usage?.completionTokens || 0,
|
|
120
|
+
totalTokens: result.usage?.totalTokens || 0,
|
|
121
|
+
cost: result.cost || 0,
|
|
122
|
+
latencyMs,
|
|
123
|
+
success: true,
|
|
124
|
+
}).catch(error => {
|
|
125
|
+
console.error('Failed to log request:', error);
|
|
126
|
+
});
|
|
127
|
+
res.status(200).json(result);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const latencyMs = Date.now() - startTime;
|
|
131
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
132
|
+
console.error('[v3/image] Error processing request:', error);
|
|
133
|
+
if (gateConfig) {
|
|
134
|
+
db.logRequest({
|
|
135
|
+
userId,
|
|
136
|
+
gateId: gateConfig.id,
|
|
137
|
+
model: gateConfig.model,
|
|
138
|
+
promptTokens: 0,
|
|
139
|
+
completionTokens: 0,
|
|
140
|
+
totalTokens: 0,
|
|
141
|
+
cost: 0,
|
|
142
|
+
latencyMs,
|
|
143
|
+
success: false,
|
|
144
|
+
errorMessage,
|
|
145
|
+
}).catch(logError => {
|
|
146
|
+
console.error('Failed to log error:', logError);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
res.status(500).json({
|
|
150
|
+
error: 'internal_server_error',
|
|
151
|
+
message: errorMessage
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/ocr.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgGpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...request, model: normalizeModelId(finalModel) };
|
|
25
|
+
}
|
|
26
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
27
|
+
const modelsToTry = [primaryModel];
|
|
28
|
+
if (gateConfig.fallback1) {
|
|
29
|
+
try {
|
|
30
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
31
|
+
}
|
|
32
|
+
catch (error) { }
|
|
33
|
+
}
|
|
34
|
+
if (gateConfig.fallback2) {
|
|
35
|
+
try {
|
|
36
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) { }
|
|
39
|
+
}
|
|
40
|
+
return modelsToTry;
|
|
41
|
+
}
|
|
42
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
43
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
44
|
+
let lastError = null;
|
|
45
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
46
|
+
const model = modelsToTry[i];
|
|
47
|
+
try {
|
|
48
|
+
const result = await callAdapter({ ...request, model }, userId);
|
|
49
|
+
return { result, modelUsed: model };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError || new Error('All models failed');
|
|
56
|
+
}
|
|
57
|
+
router.post('/', authenticate, async (req, res) => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
if (!req.userId) {
|
|
60
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const userId = req.userId;
|
|
64
|
+
let gateConfig = null;
|
|
65
|
+
try {
|
|
66
|
+
const rawRequest = req.body;
|
|
67
|
+
if (!rawRequest.gateId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawRequest.gateId)) {
|
|
68
|
+
res.status(400).json({ error: 'bad_request', message: 'Valid gateId (UUID) is required' });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!rawRequest.data?.prompt || typeof rawRequest.data.documentUrl !== 'string' || !rawRequest.data.documentUrl.trim()) {
|
|
72
|
+
res.status(400).json({ error: 'bad_request', message: 'data.documentUrl is required' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
76
|
+
if (!gateConfig) {
|
|
77
|
+
res.status(404).json({ error: 'not_found', message: `Gate not found` });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const request = { gateId: rawRequest.gateId, type: 'ocr', data: rawRequest.data, model: rawRequest.model, metadata: rawRequest.metadata };
|
|
81
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
82
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
83
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: modelUsed, promptTokens: result.usage?.promptTokens || 0, completionTokens: result.usage?.completionTokens || 0, totalTokens: result.usage?.totalTokens || 0, cost: result.cost || 0, latencyMs: Date.now() - startTime, success: true }).catch(() => { });
|
|
84
|
+
res.status(200).json(result);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
88
|
+
if (gateConfig) {
|
|
89
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: gateConfig.model, promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0, latencyMs: Date.now() - startTime, success: false, errorMessage }).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
res.status(500).json({ error: 'internal_server_error', message: errorMessage });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/tts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgGpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...request, model: normalizeModelId(finalModel) };
|
|
25
|
+
}
|
|
26
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
27
|
+
const modelsToTry = [primaryModel];
|
|
28
|
+
if (gateConfig.fallback1) {
|
|
29
|
+
try {
|
|
30
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
31
|
+
}
|
|
32
|
+
catch (error) { }
|
|
33
|
+
}
|
|
34
|
+
if (gateConfig.fallback2) {
|
|
35
|
+
try {
|
|
36
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) { }
|
|
39
|
+
}
|
|
40
|
+
return modelsToTry;
|
|
41
|
+
}
|
|
42
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
43
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
44
|
+
let lastError = null;
|
|
45
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
46
|
+
const model = modelsToTry[i];
|
|
47
|
+
try {
|
|
48
|
+
const result = await callAdapter({ ...request, model }, userId);
|
|
49
|
+
return { result, modelUsed: model };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError || new Error('All models failed');
|
|
56
|
+
}
|
|
57
|
+
router.post('/', authenticate, async (req, res) => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
if (!req.userId) {
|
|
60
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const userId = req.userId;
|
|
64
|
+
let gateConfig = null;
|
|
65
|
+
try {
|
|
66
|
+
const rawRequest = req.body;
|
|
67
|
+
if (!rawRequest.gateId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawRequest.gateId)) {
|
|
68
|
+
res.status(400).json({ error: 'bad_request', message: 'Valid gateId (UUID) is required' });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!rawRequest.data?.prompt || typeof rawRequest.data.input !== 'string' || !rawRequest.data.input.trim()) {
|
|
72
|
+
res.status(400).json({ error: 'bad_request', message: 'data.input is required' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
76
|
+
if (!gateConfig) {
|
|
77
|
+
res.status(404).json({ error: 'not_found', message: `Gate not found` });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const request = { gateId: rawRequest.gateId, type: 'tts', data: rawRequest.data, model: rawRequest.model, metadata: rawRequest.metadata };
|
|
81
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
82
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
83
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: modelUsed, promptTokens: result.usage?.promptTokens || 0, completionTokens: result.usage?.completionTokens || 0, totalTokens: result.usage?.totalTokens || 0, cost: result.cost || 0, latencyMs: Date.now() - startTime, success: true }).catch(() => { });
|
|
84
|
+
res.status(200).json(result);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
88
|
+
if (gateConfig) {
|
|
89
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: gateConfig.model, promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0, latencyMs: Date.now() - startTime, success: false, errorMessage }).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
res.status(500).json({ error: 'internal_server_error', message: errorMessage });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../../../src/routes/v3/completions/video.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAgGpC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
function isOverrideAllowed(allowOverrides, field) {
|
|
8
|
+
if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
|
|
9
|
+
return true;
|
|
10
|
+
if (allowOverrides === false)
|
|
11
|
+
return false;
|
|
12
|
+
return allowOverrides[field] ?? false;
|
|
13
|
+
}
|
|
14
|
+
function resolveFinalRequest(gateConfig, request) {
|
|
15
|
+
let finalModel = gateConfig.model;
|
|
16
|
+
if (request.model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model)) {
|
|
17
|
+
try {
|
|
18
|
+
finalModel = normalizeModelId(request.model);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
finalModel = gateConfig.model;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...request, model: normalizeModelId(finalModel) };
|
|
25
|
+
}
|
|
26
|
+
function getModelsToTry(gateConfig, primaryModel) {
|
|
27
|
+
const modelsToTry = [primaryModel];
|
|
28
|
+
if (gateConfig.fallback1) {
|
|
29
|
+
try {
|
|
30
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback1));
|
|
31
|
+
}
|
|
32
|
+
catch (error) { }
|
|
33
|
+
}
|
|
34
|
+
if (gateConfig.fallback2) {
|
|
35
|
+
try {
|
|
36
|
+
modelsToTry.push(normalizeModelId(gateConfig.fallback2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) { }
|
|
39
|
+
}
|
|
40
|
+
return modelsToTry;
|
|
41
|
+
}
|
|
42
|
+
async function executeWithRouting(gateConfig, request, userId) {
|
|
43
|
+
const modelsToTry = getModelsToTry(gateConfig, request.model);
|
|
44
|
+
let lastError = null;
|
|
45
|
+
for (let i = 0; i < modelsToTry.length; i++) {
|
|
46
|
+
const model = modelsToTry[i];
|
|
47
|
+
try {
|
|
48
|
+
const result = await callAdapter({ ...request, model }, userId);
|
|
49
|
+
return { result, modelUsed: model };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
lastError = error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError || new Error('All models failed');
|
|
56
|
+
}
|
|
57
|
+
router.post('/', authenticate, async (req, res) => {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
if (!req.userId) {
|
|
60
|
+
res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const userId = req.userId;
|
|
64
|
+
let gateConfig = null;
|
|
65
|
+
try {
|
|
66
|
+
const rawRequest = req.body;
|
|
67
|
+
if (!rawRequest.gateId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawRequest.gateId)) {
|
|
68
|
+
res.status(400).json({ error: 'bad_request', message: 'Valid gateId (UUID) is required' });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!rawRequest.data?.prompt || typeof rawRequest.data.prompt !== 'string' || !rawRequest.data.prompt.trim()) {
|
|
72
|
+
res.status(400).json({ error: 'bad_request', message: 'data.prompt is required' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
gateConfig = await db.getGateByUserAndId(userId, rawRequest.gateId);
|
|
76
|
+
if (!gateConfig) {
|
|
77
|
+
res.status(404).json({ error: 'not_found', message: `Gate not found` });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const request = { gateId: rawRequest.gateId, type: 'video', data: rawRequest.data, model: rawRequest.model, metadata: rawRequest.metadata };
|
|
81
|
+
const finalRequest = resolveFinalRequest(gateConfig, request);
|
|
82
|
+
const { result, modelUsed } = await executeWithRouting(gateConfig, finalRequest, userId);
|
|
83
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: modelUsed, promptTokens: result.usage?.promptTokens || 0, completionTokens: result.usage?.completionTokens || 0, totalTokens: result.usage?.totalTokens || 0, cost: result.cost || 0, latencyMs: Date.now() - startTime, success: true }).catch(() => { });
|
|
84
|
+
res.status(200).json(result);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
88
|
+
if (gateConfig) {
|
|
89
|
+
db.logRequest({ userId, gateId: gateConfig.id, model: gateConfig.model, promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0, latencyMs: Date.now() - startTime, success: false, errorMessage }).catch(() => { });
|
|
90
|
+
}
|
|
91
|
+
res.status(500).json({ error: 'internal_server_error', message: errorMessage });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
export default router;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,eAAe,EAGhB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,8BAAsB,mBAAmB;IACvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACtC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAE1B,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC/D,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9D,SAAS,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1D,SAAS,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxD,SAAS,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5D,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChE,SAAS,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAElE,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAE7E,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAcrC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAQpE,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS;IAQ9D,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAQ3D,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS;IAQjE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;IAQvE,SAAS,CAAC,eAAe,CAAC,oBAAoB,EAAE,MAAM,GAAG,YAAY;IAQrE,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS;IAYxE,SAAS,CAAC,aAAa,CACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACvB,MAAM;CAUV"}
|
|
@@ -82,9 +82,12 @@ export class BaseProviderAdapter {
|
|
|
82
82
|
}
|
|
83
83
|
calculateCost(model, promptTokens, completionTokens) {
|
|
84
84
|
const modelInfo = MODEL_REGISTRY[model];
|
|
85
|
-
if (!modelInfo || !('pricing' in modelInfo) || !modelInfo.pricing?.input
|
|
85
|
+
if (!modelInfo || !('pricing' in modelInfo) || !modelInfo.pricing?.input) {
|
|
86
86
|
return 0;
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
const pricing = modelInfo.pricing;
|
|
89
|
+
const inputCost = promptTokens / 1000000 * pricing.input;
|
|
90
|
+
const outputCost = ('output' in pricing && pricing.output) ? (completionTokens / 1000000 * pricing.output) : 0;
|
|
91
|
+
return inputCost + outputCost;
|
|
89
92
|
}
|
|
90
93
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layer-ai/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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.1.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bcryptjs": "^2.4.6",
|