@layer-ai/core 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -6,6 +6,9 @@ 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
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';
9
12
  export { authenticate } from './middleware/auth.js';
10
13
  export type {} from './middleware/auth.js';
11
14
  export { db } from './lib/db/postgres.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAG5D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAG3C,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAC;AAC1C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,eAAO,MAAM,gBAAgB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,MAAM,CAGrE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAG3E,CAAC;AAGF,cAAc,6BAA6B,CAAC;AAG5C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
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
@@ -9,6 +9,9 @@ export { default as completeRouter } from './routes/v2/complete.js';
9
9
  export { default as chatRouter } from './routes/v3/chat.js';
10
10
  export { default as imageRouter } from './routes/v3/image.js';
11
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';
12
15
  // Middleware
13
16
  export { authenticate } from './middleware/auth.js';
14
17
  // Database
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=embeddings.d.ts.map
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/image.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"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/routes/v3/image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AAuOpC,eAAe,MAAM,CAAC"}
@@ -22,12 +22,21 @@ function resolveFinalRequest(gateConfig, request) {
22
22
  finalModel = gateConfig.model;
23
23
  }
24
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
25
+ // Apply gate config temperature if not provided in request
26
+ // While most image models don't support temperature, some providers/future models might
27
+ const imageData = { ...request.data };
28
+ // Apply temperature from gate config if available
29
+ if (imageData.temperature === undefined && gateConfig.temperature !== undefined) {
30
+ imageData.temperature = gateConfig.temperature;
31
+ }
32
+ else if (imageData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
33
+ imageData.temperature = gateConfig.temperature;
34
+ }
27
35
  return {
28
36
  ...request,
29
37
  type: 'image',
30
38
  model: normalizeModelId(finalModel),
39
+ data: imageData,
31
40
  };
32
41
  }
33
42
  function getModelsToTry(gateConfig, primaryModel) {
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=ocr.d.ts.map
@@ -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,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=tts.d.ts.map
@@ -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;
@@ -1 +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"}
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;AAuOpC,eAAe,MAAM,CAAC"}
@@ -22,12 +22,21 @@ function resolveFinalRequest(gateConfig, request) {
22
22
  finalModel = gateConfig.model;
23
23
  }
24
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
25
+ // Apply gate config temperature if not provided in request
26
+ // While most video models don't support temperature, some providers/future models might
27
+ const videoData = { ...request.data };
28
+ // Apply temperature from gate config if available
29
+ if (videoData.temperature === undefined && gateConfig.temperature !== undefined) {
30
+ videoData.temperature = gateConfig.temperature;
31
+ }
32
+ else if (videoData.temperature !== undefined && !isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
33
+ videoData.temperature = gateConfig.temperature;
34
+ }
27
35
  return {
28
36
  ...request,
29
37
  type: 'video',
30
38
  model: normalizeModelId(finalModel),
39
+ data: videoData,
31
40
  };
32
41
  }
33
42
  function getModelsToTry(gateConfig, primaryModel) {
@@ -30,5 +30,7 @@ export declare abstract class BaseProviderAdapter {
30
30
  protected mapFinishReason(providerFinishReason: string): FinishReason;
31
31
  protected mapToolChoice(choice: ToolChoice): string | object | undefined;
32
32
  protected calculateCost(model: string, promptTokens: number, completionTokens: number): number;
33
+ protected calculateImageCost(model: string, quality?: string, size?: string, count?: number): number;
34
+ protected calculateVideoCost(model: string, duration?: number, count?: number): number;
33
35
  }
34
36
  //# sourceMappingURL=base-adapter.d.ts.map
@@ -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":"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"}
@@ -90,4 +90,33 @@ export class BaseProviderAdapter {
90
90
  const outputCost = ('output' in pricing && pricing.output) ? (completionTokens / 1000000 * pricing.output) : 0;
91
91
  return inputCost + outputCost;
92
92
  }
93
+ calculateImageCost(model, quality, size, count = 1) {
94
+ const modelInfo = MODEL_REGISTRY[model];
95
+ if (!modelInfo || !('imagePricing' in modelInfo) || !modelInfo.imagePricing) {
96
+ return 0;
97
+ }
98
+ const imagePricing = modelInfo.imagePricing;
99
+ // Build pricing key from quality and size (e.g., 'hd-1024x1024' or 'standard-1024x1024')
100
+ const pricingKey = quality && size ? `${quality}-${size}` : size || 'standard-1024x1024';
101
+ const pricePerImage = imagePricing[pricingKey];
102
+ if (!pricePerImage) {
103
+ // If exact match not found, try without quality prefix
104
+ const fallbackPrice = imagePricing[size || '1024x1024'];
105
+ return (fallbackPrice || 0) * count;
106
+ }
107
+ return pricePerImage * count;
108
+ }
109
+ calculateVideoCost(model, duration, count = 1) {
110
+ const modelInfo = MODEL_REGISTRY[model];
111
+ if (!modelInfo || !('videoPricing' in modelInfo) || !modelInfo.videoPricing) {
112
+ return 0;
113
+ }
114
+ const videoPricing = modelInfo.videoPricing;
115
+ // Video pricing might be per-second or per-video
116
+ const pricePerUnit = videoPricing.perVideo || videoPricing.perSecond || 0;
117
+ if (videoPricing.perSecond && duration) {
118
+ return pricePerUnit * duration * count;
119
+ }
120
+ return pricePerUnit * count;
121
+ }
93
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAmB;IAE/C,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAIxD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK1D;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAQpD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtD;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAKpD;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAOxD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAoB5D,UAAU;YA4GV,qBAAqB;YA8BrB,gBAAgB;YAkChB,kBAAkB;CA+BjC"}
1
+ {"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/openai-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAoB1E,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAmB;IAE/C,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAQ1C;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAIxD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAK1D;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAQpD;IAEF,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG1D;IAEF,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtD;IAEF,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAKpD;IAEF,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAOxD;IAEI,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;YAoB5D,UAAU;YA4GV,qBAAqB;YAuCrB,gBAAgB;YAkChB,kBAAkB;CA+BjC"}
@@ -200,12 +200,15 @@ export class OpenAIAdapter extends BaseProviderAdapter {
200
200
  ...(image.count && { n: image.count }),
201
201
  ...(image.style && { style: this.mapImageStyle(image.style) }),
202
202
  });
203
+ // Calculate cost based on quality, size, and count
204
+ const cost = this.calculateImageCost(model, image.quality || 'standard', image.size || '1024x1024', image.count || 1);
203
205
  return {
204
206
  images: (response.data || []).map(img => ({
205
207
  url: img.url,
206
208
  revisedPrompt: img.revised_prompt,
207
209
  })),
208
210
  model: model,
211
+ cost,
209
212
  latencyMs: Date.now() - startTime,
210
213
  usedPlatformKey,
211
214
  raw: response,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layer-ai/core",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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.4.0"
39
+ "@layer-ai/sdk": "^2.5.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bcryptjs": "^2.4.6",