@layer-ai/core 2.0.46 → 2.0.48
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 +2 -0
- package/dist/lib/db/migrations/009_rename_document_to_ocr.sql +2 -0
- package/dist/lib/provider-factory.d.ts.map +1 -1
- package/dist/lib/provider-factory.js +4 -4
- package/dist/lib/registry.d.ts +17 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +50 -0
- package/dist/routes/tests/test-chat-streaming.js +1 -1
- package/dist/routes/tests/test-chat-structured-output.js +1 -1
- package/dist/routes/v1/gates.js +3 -3
- package/dist/routes/v3/ocr.js +1 -1
- package/dist/services/providers/base-adapter.d.ts.map +1 -1
- package/dist/services/providers/base-adapter.js +29 -18
- package/dist/services/providers/tests/test-google-adapter.js +16 -4
- package/dist/services/providers/tests/test-json-response-format-openai.js +2 -2
- package/dist/services/providers/tests/test-mistral-adapter.js +8 -8
- package/dist/services/providers/tests/test-openai-adapter.js +9 -5
- package/dist/services/providers/tests/test-openai-streaming.js +2 -2
- package/dist/services/task-analysis.d.ts.map +1 -1
- package/dist/services/task-analysis.js +5 -4
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type { EncryptedData } from '@layer-ai/sdk';
|
|
|
20
20
|
export declare const createSessionKey: (userId: string) => Promise<string>;
|
|
21
21
|
export declare const deleteSessionKeysForUser: (userId: string) => Promise<void>;
|
|
22
22
|
export * from './services/task-analysis.js';
|
|
23
|
+
export { initializeRegistry, getRegistry, getModel, hasModel, getModelEntries } from './lib/registry.js';
|
|
23
24
|
export { PROVIDER, PROVIDERS, type Provider, callAdapter, normalizeModelId, getProviderForModel } from './lib/provider-factory.js';
|
|
24
25
|
export { spendingTracker } from './lib/spending-tracker.js';
|
|
25
26
|
export { spendingJobs } from './lib/spending-jobs.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;AAC5D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,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,QAAQ,EAAE,MAAM,iBAAiB,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;AAGnI,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,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;AAC5D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGpE,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,QAAQ,EAAE,MAAM,iBAAiB,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,kBAAkB,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzG,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGnI,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,8 @@ export const deleteSessionKeysForUser = async (userId) => {
|
|
|
33
33
|
};
|
|
34
34
|
// Services
|
|
35
35
|
export * from './services/task-analysis.js';
|
|
36
|
+
// Model Registry
|
|
37
|
+
export { initializeRegistry, getRegistry, getModel, hasModel, getModelEntries } from './lib/registry.js';
|
|
36
38
|
// Provider Factory
|
|
37
39
|
export { PROVIDER, PROVIDERS, callAdapter, normalizeModelId, getProviderForModel } from './lib/provider-factory.js';
|
|
38
40
|
// Spending Management
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-factory.d.ts","sourceRoot":"","sources":["../../src/lib/provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"provider-factory.d.ts","sourceRoot":"","sources":["../../src/lib/provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAI7E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,CAAC;AAa9C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAmBhE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ,CAMnE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAWhG;AAED;;;;;GAKG;AACH,wBAAuB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,CAgB7G"}
|
|
@@ -9,8 +9,8 @@ import { OpenAIAdapter } from '../services/providers/openai-adapter.js';
|
|
|
9
9
|
import { AnthropicAdapter } from '../services/providers/anthropic-adapter.js';
|
|
10
10
|
import { GoogleAdapter } from '../services/providers/google-adapter.js';
|
|
11
11
|
import { MistralAdapter } from '../services/providers/mistral-adapter.js';
|
|
12
|
-
import { MODEL_REGISTRY } from '@layer-ai/sdk';
|
|
13
12
|
import { PROVIDER, PROVIDERS } from './provider-constants.js';
|
|
13
|
+
import { getModel, hasModel } from './registry.js';
|
|
14
14
|
// Re-export for convenience
|
|
15
15
|
export { PROVIDER, PROVIDERS };
|
|
16
16
|
/**
|
|
@@ -28,7 +28,7 @@ const PROVIDER_ADAPTERS = {
|
|
|
28
28
|
* Supports both full IDs (e.g., "openai/gpt-4") and short IDs (e.g., "gpt-4").
|
|
29
29
|
*/
|
|
30
30
|
export function normalizeModelId(modelId) {
|
|
31
|
-
if (
|
|
31
|
+
if (hasModel(modelId)) {
|
|
32
32
|
return modelId;
|
|
33
33
|
}
|
|
34
34
|
const providers = [
|
|
@@ -39,7 +39,7 @@ export function normalizeModelId(modelId) {
|
|
|
39
39
|
];
|
|
40
40
|
for (const provider of providers) {
|
|
41
41
|
const fullId = `${provider}/${modelId}`;
|
|
42
|
-
if (
|
|
42
|
+
if (hasModel(fullId)) {
|
|
43
43
|
return fullId;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -49,7 +49,7 @@ export function normalizeModelId(modelId) {
|
|
|
49
49
|
* Gets the provider type for a given model
|
|
50
50
|
*/
|
|
51
51
|
export function getProviderForModel(model) {
|
|
52
|
-
const modelInfo =
|
|
52
|
+
const modelInfo = getModel(model);
|
|
53
53
|
if (!modelInfo) {
|
|
54
54
|
throw new Error(`Model "${model}" not found in registry`);
|
|
55
55
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Model Registry
|
|
3
|
+
*
|
|
4
|
+
* Starts with static SDK data, fetches fresh data from the API
|
|
5
|
+
* at startup. Falls back to SDK data if the API is unreachable or
|
|
6
|
+
* API_URL is not set (e.g., local dev).
|
|
7
|
+
*/
|
|
8
|
+
interface InitOptions {
|
|
9
|
+
refreshIntervalMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function initializeRegistry(options?: InitOptions): Promise<void>;
|
|
12
|
+
export declare function getRegistry(): Record<string, any>;
|
|
13
|
+
export declare function getModel(modelId: string): any | undefined;
|
|
14
|
+
export declare function hasModel(modelId: string): boolean;
|
|
15
|
+
export declare function getModelEntries(): [string, any][];
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,UAAU,WAAW;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,WAAgB,iBAcjE;AAgBD,wBAAgB,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAEjD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAEzD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,eAAe,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAEjD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Model Registry
|
|
3
|
+
*
|
|
4
|
+
* Starts with static SDK data, fetches fresh data from the API
|
|
5
|
+
* at startup. Falls back to SDK data if the API is unreachable or
|
|
6
|
+
* API_URL is not set (e.g., local dev).
|
|
7
|
+
*/
|
|
8
|
+
import { MODEL_REGISTRY as STATIC_REGISTRY } from '@layer-ai/sdk';
|
|
9
|
+
let registry = { ...STATIC_REGISTRY };
|
|
10
|
+
let refreshTimer = null;
|
|
11
|
+
export async function initializeRegistry(options = {}) {
|
|
12
|
+
const apiUrl = process.env.API_URL;
|
|
13
|
+
if (!apiUrl) {
|
|
14
|
+
console.log('[Registry] No API_URL set, using static SDK data');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await fetchRegistry(apiUrl);
|
|
18
|
+
if (options.refreshIntervalMs) {
|
|
19
|
+
refreshTimer = setInterval(() => fetchRegistry(apiUrl), options.refreshIntervalMs);
|
|
20
|
+
// Allow the process to exit even if the timer is active
|
|
21
|
+
refreshTimer.unref();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function fetchRegistry(apiUrl) {
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`${apiUrl}/v1/registry`, {
|
|
27
|
+
signal: AbortSignal.timeout(10000),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
throw new Error(`HTTP ${res.status}`);
|
|
31
|
+
const data = await res.json();
|
|
32
|
+
registry = data.models;
|
|
33
|
+
console.log(`[Registry] Loaded ${Object.keys(registry).length} models from API`);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.warn('[Registry] Fetch failed, using existing data:', err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function getRegistry() {
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
42
|
+
export function getModel(modelId) {
|
|
43
|
+
return registry[modelId];
|
|
44
|
+
}
|
|
45
|
+
export function hasModel(modelId) {
|
|
46
|
+
return modelId in registry;
|
|
47
|
+
}
|
|
48
|
+
export function getModelEntries() {
|
|
49
|
+
return Object.entries(registry);
|
|
50
|
+
}
|
|
@@ -138,7 +138,7 @@ async function testAnthropicBetaMode() {
|
|
|
138
138
|
console.log('-'.repeat(80));
|
|
139
139
|
const gateConfig = {
|
|
140
140
|
...baseGateConfig,
|
|
141
|
-
model: 'claude-
|
|
141
|
+
model: 'claude-sonnet-4-20250514',
|
|
142
142
|
responseFormatEnabled: true,
|
|
143
143
|
responseFormatType: 'json_object',
|
|
144
144
|
};
|
package/dist/routes/v1/gates.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import { db } from '../../lib/db/postgres.js';
|
|
3
3
|
import { cache } from '../../lib/db/redis.js';
|
|
4
4
|
import { callAdapter } from '../../lib/provider-factory.js';
|
|
5
|
-
import {
|
|
5
|
+
import { hasModel } from '../../lib/registry.js';
|
|
6
6
|
import { detectSignificantChanges } from '../../lib/gate-utils.js';
|
|
7
7
|
const router = Router();
|
|
8
8
|
// POST / - Create a new gate
|
|
@@ -17,7 +17,7 @@ router.post('/', async (req, res) => {
|
|
|
17
17
|
res.status(400).json({ error: 'bad_request', message: 'Missing required fields: name and model' });
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
if (!
|
|
20
|
+
if (!hasModel(model)) {
|
|
21
21
|
res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
@@ -259,7 +259,7 @@ router.patch('/:id', async (req, res) => {
|
|
|
259
259
|
res.status(404).json({ error: 'not_found', message: 'Gate not found' });
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
|
-
if (model && !
|
|
262
|
+
if (model && !hasModel(model)) {
|
|
263
263
|
res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
|
|
264
264
|
return;
|
|
265
265
|
}
|
package/dist/routes/v3/ocr.js
CHANGED
|
@@ -118,7 +118,7 @@ router.post('/', async (req, res) => {
|
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
// Warn if gate is configured for a different task type
|
|
121
|
-
if (gateConfig.taskType && gateConfig.taskType !== '
|
|
121
|
+
if (gateConfig.taskType && gateConfig.taskType !== 'ocr') {
|
|
122
122
|
console.warn(`[Type Mismatch] Gate "${gateConfig.name}" (${gateConfig.id}) configured for taskType="${gateConfig.taskType}" ` +
|
|
123
123
|
`but received request to /v3/ocr endpoint. Processing as OCR request.`);
|
|
124
124
|
}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/providers/base-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ADAPTER_HANDLED,
|
|
1
|
+
import { ADAPTER_HANDLED, } from '@layer-ai/sdk';
|
|
2
|
+
import { getModel } from '../../lib/registry.js';
|
|
2
3
|
export { ADAPTER_HANDLED };
|
|
3
4
|
export class BaseProviderAdapter {
|
|
4
5
|
mapRole(role) {
|
|
@@ -81,7 +82,7 @@ export class BaseProviderAdapter {
|
|
|
81
82
|
return this.toolChoiceMappings[choice];
|
|
82
83
|
}
|
|
83
84
|
calculateCost(model, promptTokens, completionTokens) {
|
|
84
|
-
const modelInfo =
|
|
85
|
+
const modelInfo = getModel(model);
|
|
85
86
|
if (!modelInfo || !('pricing' in modelInfo) || !modelInfo.pricing?.input) {
|
|
86
87
|
return 0;
|
|
87
88
|
}
|
|
@@ -91,17 +92,17 @@ export class BaseProviderAdapter {
|
|
|
91
92
|
return inputCost + outputCost;
|
|
92
93
|
}
|
|
93
94
|
calculateImageCost(model, quality, size, count = 1) {
|
|
94
|
-
const modelInfo =
|
|
95
|
-
if (!modelInfo || !('
|
|
95
|
+
const modelInfo = getModel(model);
|
|
96
|
+
if (!modelInfo || !('unitPricing' in modelInfo) || !modelInfo.unitPricing) {
|
|
96
97
|
return 0;
|
|
97
98
|
}
|
|
98
|
-
const
|
|
99
|
-
// Flat-rate pricing (e.g. Google Imagen
|
|
100
|
-
if (typeof
|
|
101
|
-
return
|
|
99
|
+
const unitPricing = modelInfo.unitPricing;
|
|
100
|
+
// Flat-rate pricing (e.g. Google Imagen: unitPricing = 0.02)
|
|
101
|
+
if (typeof unitPricing === 'number') {
|
|
102
|
+
return unitPricing * count;
|
|
102
103
|
}
|
|
103
|
-
//
|
|
104
|
-
const pricingTable =
|
|
104
|
+
// Object pricing (e.g. DALL-E: unitPricing = { "hd-1024x1024": 0.08, ... })
|
|
105
|
+
const pricingTable = unitPricing;
|
|
105
106
|
const pricingKey = quality && size ? `${quality}-${size}` : size || 'standard-1024x1024';
|
|
106
107
|
const pricePerImage = pricingTable[pricingKey];
|
|
107
108
|
if (!pricePerImage) {
|
|
@@ -112,16 +113,26 @@ export class BaseProviderAdapter {
|
|
|
112
113
|
return pricePerImage * count;
|
|
113
114
|
}
|
|
114
115
|
calculateVideoCost(model, duration, count = 1) {
|
|
115
|
-
const modelInfo =
|
|
116
|
-
if (!modelInfo || !('
|
|
116
|
+
const modelInfo = getModel(model);
|
|
117
|
+
if (!modelInfo || !('unitPricing' in modelInfo) || !modelInfo.unitPricing) {
|
|
117
118
|
return 0;
|
|
118
119
|
}
|
|
119
|
-
const
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return pricePerUnit * duration * count;
|
|
120
|
+
const unitPricing = modelInfo.unitPricing;
|
|
121
|
+
// Flat-rate pricing (e.g. unitPricing = 0.50 per video)
|
|
122
|
+
if (typeof unitPricing === 'number') {
|
|
123
|
+
return unitPricing * count;
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
// Object pricing with per_second or per_video keys
|
|
126
|
+
const pricing = unitPricing;
|
|
127
|
+
if (pricing.per_second && duration) {
|
|
128
|
+
return pricing.per_second * duration * count;
|
|
129
|
+
}
|
|
130
|
+
if (pricing.per_video) {
|
|
131
|
+
return pricing.per_video * count;
|
|
132
|
+
}
|
|
133
|
+
if (pricing.per_minute && duration) {
|
|
134
|
+
return pricing.per_minute * (duration / 60) * count;
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
126
137
|
}
|
|
127
138
|
}
|
|
@@ -53,7 +53,7 @@ async function testImageGeneration() {
|
|
|
53
53
|
console.log('Testing image generation...');
|
|
54
54
|
const request = {
|
|
55
55
|
gateId: 'test-gate',
|
|
56
|
-
model: 'imagen-4.0-generate-001',
|
|
56
|
+
model: 'imagen-4.0-fast-generate-001',
|
|
57
57
|
type: 'image',
|
|
58
58
|
data: {
|
|
59
59
|
prompt: 'A cute cat playing with a ball of yarn',
|
|
@@ -64,6 +64,10 @@ async function testImageGeneration() {
|
|
|
64
64
|
console.log('Generated images:', response.images?.length);
|
|
65
65
|
console.log('Image base64 length:', response.images?.[0]?.base64?.length);
|
|
66
66
|
console.log('Latency:', response.latencyMs + 'ms');
|
|
67
|
+
console.log('Cost:', response.cost);
|
|
68
|
+
if (!response.cost || response.cost <= 0) {
|
|
69
|
+
throw new Error(`Expected image generation cost > 0, got ${response.cost}`);
|
|
70
|
+
}
|
|
67
71
|
console.log('✅ Image generation test passed\n');
|
|
68
72
|
}
|
|
69
73
|
async function testEmbeddings() {
|
|
@@ -196,6 +200,14 @@ async function testVideoGeneration() {
|
|
|
196
200
|
console.log('Video URL:', response.videos?.[0]?.url);
|
|
197
201
|
console.log('Video duration:', response.videos?.[0]?.duration);
|
|
198
202
|
console.log('Latency:', response.latencyMs + 'ms');
|
|
203
|
+
console.log('Cost:', response.cost);
|
|
204
|
+
// Note: video cost may be 0 if unitPricing is not set for this model in the registry
|
|
205
|
+
if (response.cost && response.cost > 0) {
|
|
206
|
+
console.log('✅ Video cost calculation working');
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
console.warn('⚠️ Video cost is 0 (unitPricing may not be set for this model)');
|
|
210
|
+
}
|
|
199
211
|
console.log('✅ Video generation test passed\n');
|
|
200
212
|
}
|
|
201
213
|
async function runTests() {
|
|
@@ -203,9 +215,6 @@ async function runTests() {
|
|
|
203
215
|
console.log('Chat completion tests...');
|
|
204
216
|
await testChatCompletion();
|
|
205
217
|
await testChatWithVision();
|
|
206
|
-
console.log('Embeddings...');
|
|
207
|
-
await testEmbeddings();
|
|
208
|
-
await testEmbeddingsMultiple();
|
|
209
218
|
console.log('Image generation...');
|
|
210
219
|
await testImageGeneration();
|
|
211
220
|
console.log('Text-to-speech...');
|
|
@@ -214,6 +223,9 @@ async function runTests() {
|
|
|
214
223
|
await testVideoGeneration();
|
|
215
224
|
console.log('Tool calling...');
|
|
216
225
|
await testToolCalling();
|
|
226
|
+
console.log('Embeddings...');
|
|
227
|
+
await testEmbeddings();
|
|
228
|
+
await testEmbeddingsMultiple();
|
|
217
229
|
console.log('✅ All tests passed!');
|
|
218
230
|
}
|
|
219
231
|
catch (error) {
|
|
@@ -14,7 +14,7 @@ async function testJsonObjectMode() {
|
|
|
14
14
|
console.log('-'.repeat(80));
|
|
15
15
|
const request = {
|
|
16
16
|
gateId: 'test-gate',
|
|
17
|
-
model: 'gpt-4o
|
|
17
|
+
model: 'gpt-4o',
|
|
18
18
|
type: 'chat',
|
|
19
19
|
data: {
|
|
20
20
|
messages: [
|
|
@@ -118,7 +118,7 @@ async function testTextMode() {
|
|
|
118
118
|
console.log('-'.repeat(80));
|
|
119
119
|
const request = {
|
|
120
120
|
gateId: 'test-gate',
|
|
121
|
-
model: 'gpt-4o
|
|
121
|
+
model: 'gpt-4o',
|
|
122
122
|
type: 'chat',
|
|
123
123
|
data: {
|
|
124
124
|
messages: [
|
|
@@ -8,7 +8,7 @@ async function testChatCompletion() {
|
|
|
8
8
|
const request = {
|
|
9
9
|
gateId: 'test-gate',
|
|
10
10
|
type: 'chat',
|
|
11
|
-
model: 'mistral-small-
|
|
11
|
+
model: 'mistral-small-2501',
|
|
12
12
|
data: {
|
|
13
13
|
messages: [
|
|
14
14
|
{
|
|
@@ -33,7 +33,7 @@ async function testChatWithSystemPrompt() {
|
|
|
33
33
|
const request = {
|
|
34
34
|
gateId: 'test-gate',
|
|
35
35
|
type: 'chat',
|
|
36
|
-
model: 'mistral-small-
|
|
36
|
+
model: 'mistral-small-2501',
|
|
37
37
|
data: {
|
|
38
38
|
systemPrompt: 'You are a helpful assistant that responds in JSON format only.',
|
|
39
39
|
messages: [
|
|
@@ -56,7 +56,7 @@ async function testChatWithTools() {
|
|
|
56
56
|
const request = {
|
|
57
57
|
gateId: 'test-gate',
|
|
58
58
|
type: 'chat',
|
|
59
|
-
model: 'mistral-small-
|
|
59
|
+
model: 'mistral-small-2501',
|
|
60
60
|
data: {
|
|
61
61
|
messages: [
|
|
62
62
|
{
|
|
@@ -104,7 +104,7 @@ async function testToolResponse() {
|
|
|
104
104
|
const initialRequest = {
|
|
105
105
|
gateId: 'test-gate',
|
|
106
106
|
type: 'chat',
|
|
107
|
-
model: 'mistral-small-
|
|
107
|
+
model: 'mistral-small-2501',
|
|
108
108
|
data: {
|
|
109
109
|
messages: [
|
|
110
110
|
{
|
|
@@ -145,7 +145,7 @@ async function testToolResponse() {
|
|
|
145
145
|
const followUpRequest = {
|
|
146
146
|
gateId: 'test-gate',
|
|
147
147
|
type: 'chat',
|
|
148
|
-
model: 'mistral-small-
|
|
148
|
+
model: 'mistral-small-2501',
|
|
149
149
|
data: {
|
|
150
150
|
messages: [
|
|
151
151
|
{
|
|
@@ -226,7 +226,7 @@ async function testResponseFormat() {
|
|
|
226
226
|
const request = {
|
|
227
227
|
gateId: 'test-gate',
|
|
228
228
|
type: 'chat',
|
|
229
|
-
model: 'mistral-small-
|
|
229
|
+
model: 'mistral-small-2501',
|
|
230
230
|
data: {
|
|
231
231
|
messages: [
|
|
232
232
|
{
|
|
@@ -247,7 +247,7 @@ async function testMultiTurn() {
|
|
|
247
247
|
const request = {
|
|
248
248
|
gateId: 'test-gate',
|
|
249
249
|
type: 'chat',
|
|
250
|
-
model: 'mistral-small-
|
|
250
|
+
model: 'mistral-small-2501',
|
|
251
251
|
data: {
|
|
252
252
|
messages: [
|
|
253
253
|
{
|
|
@@ -297,7 +297,7 @@ async function testUnsupportedModality() {
|
|
|
297
297
|
const request = {
|
|
298
298
|
gateId: 'test-gate',
|
|
299
299
|
type: 'image',
|
|
300
|
-
model: 'mistral-large-
|
|
300
|
+
model: 'mistral-large-2512',
|
|
301
301
|
data: {
|
|
302
302
|
prompt: 'A sunset over the ocean',
|
|
303
303
|
},
|
|
@@ -4,7 +4,7 @@ async function testChatCompletion() {
|
|
|
4
4
|
console.log('Testing chat completion...');
|
|
5
5
|
const request = {
|
|
6
6
|
gateId: 'test-gate',
|
|
7
|
-
model: 'gpt-4o
|
|
7
|
+
model: 'gpt-4o',
|
|
8
8
|
type: 'chat',
|
|
9
9
|
data: {
|
|
10
10
|
messages: [
|
|
@@ -26,7 +26,7 @@ async function testChatWithVision() {
|
|
|
26
26
|
console.log('Testing chat with vision...');
|
|
27
27
|
const request = {
|
|
28
28
|
gateId: 'test-gate',
|
|
29
|
-
model: 'gpt-4o
|
|
29
|
+
model: 'gpt-4o',
|
|
30
30
|
type: 'chat',
|
|
31
31
|
data: {
|
|
32
32
|
messages: [
|
|
@@ -64,6 +64,10 @@ async function testImageGeneration() {
|
|
|
64
64
|
console.log('Generated images:', response.images?.length);
|
|
65
65
|
console.log('Image URL:', response.images?.[0]?.url);
|
|
66
66
|
console.log('Revised prompt:', response.images?.[0]?.revisedPrompt);
|
|
67
|
+
console.log('Cost:', response.cost);
|
|
68
|
+
if (!response.cost || response.cost <= 0) {
|
|
69
|
+
throw new Error(`Expected image generation cost > 0, got ${response.cost}`);
|
|
70
|
+
}
|
|
67
71
|
console.log('✅ Image generation test passed\n');
|
|
68
72
|
}
|
|
69
73
|
async function testEmbeddings() {
|
|
@@ -105,7 +109,7 @@ async function testToolCalling() {
|
|
|
105
109
|
// Step 1: Initial request with tool available
|
|
106
110
|
const request = {
|
|
107
111
|
gateId: 'test-gate',
|
|
108
|
-
model: 'gpt-4o
|
|
112
|
+
model: 'gpt-4o',
|
|
109
113
|
type: 'chat',
|
|
110
114
|
data: {
|
|
111
115
|
messages: [
|
|
@@ -146,7 +150,7 @@ async function testToolCalling() {
|
|
|
146
150
|
// Step 2: Send tool response back
|
|
147
151
|
const toolResponseRequest = {
|
|
148
152
|
gateId: 'test-gate',
|
|
149
|
-
model: 'gpt-4o
|
|
153
|
+
model: 'gpt-4o',
|
|
150
154
|
type: 'chat',
|
|
151
155
|
data: {
|
|
152
156
|
messages: [
|
|
@@ -174,7 +178,7 @@ async function testContentAndToolCalls() {
|
|
|
174
178
|
// This tests the fix we made - assistant messages can have BOTH content and toolCalls
|
|
175
179
|
const request = {
|
|
176
180
|
gateId: 'test-gate',
|
|
177
|
-
model: 'gpt-4o
|
|
181
|
+
model: 'gpt-4o',
|
|
178
182
|
type: 'chat',
|
|
179
183
|
data: {
|
|
180
184
|
messages: [
|
|
@@ -4,7 +4,7 @@ async function testChatStreamingBasic() {
|
|
|
4
4
|
console.log('Testing basic chat streaming...');
|
|
5
5
|
const request = {
|
|
6
6
|
gateId: 'test-gate',
|
|
7
|
-
model: 'gpt-4o
|
|
7
|
+
model: 'gpt-4o',
|
|
8
8
|
type: 'chat',
|
|
9
9
|
data: {
|
|
10
10
|
messages: [
|
|
@@ -48,7 +48,7 @@ async function testChatStreamingWithToolCalls() {
|
|
|
48
48
|
console.log('Testing chat streaming with tool calls...');
|
|
49
49
|
const request = {
|
|
50
50
|
gateId: 'test-gate',
|
|
51
|
-
model: 'gpt-4o
|
|
51
|
+
model: 'gpt-4o',
|
|
52
52
|
type: 'chat',
|
|
53
53
|
data: {
|
|
54
54
|
messages: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-analysis.d.ts","sourceRoot":"","sources":["../../src/services/task-analysis.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"task-analysis.d.ts","sourceRoot":"","sources":["../../src/services/task-analysis.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAmC,MAAM,eAAe,CAAC;AA6D9E,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,GACA,OAAO,CAAC,YAAY,CAAC,CA6HvB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { getModelEntries } from '../lib/registry.js';
|
|
3
3
|
async function detectTaskType(description, anthropic) {
|
|
4
4
|
const prompt = `Analyze this task description and determine what TYPE of AI task it is.
|
|
5
5
|
|
|
@@ -14,7 +14,8 @@ AVAILABLE TASK TYPES:
|
|
|
14
14
|
- tts: Text-to-speech, voice synthesis
|
|
15
15
|
- stt: Speech-to-text, audio transcription
|
|
16
16
|
- embeddings: Text embeddings, semantic search
|
|
17
|
-
-
|
|
17
|
+
- ocr: Document processing, OCR
|
|
18
|
+
- moderation: Content moderation
|
|
18
19
|
- responses: Complex reasoning tasks (o3-pro style models)
|
|
19
20
|
- language-completion: Legacy text completion
|
|
20
21
|
|
|
@@ -39,7 +40,7 @@ Return ONLY the task type as a single word, nothing else.`;
|
|
|
39
40
|
throw new Error('Unexpected response type from Claude');
|
|
40
41
|
}
|
|
41
42
|
const detectedType = responseContent.text.trim().toLowerCase();
|
|
42
|
-
const validTypes = ['chat', 'image', 'video', 'audio', 'tts', 'stt', 'embeddings', '
|
|
43
|
+
const validTypes = ['chat', 'image', 'video', 'audio', 'tts', 'stt', 'embeddings', 'ocr', 'moderation', 'responses', 'language-completion'];
|
|
43
44
|
if (validTypes.includes(detectedType)) {
|
|
44
45
|
return detectedType;
|
|
45
46
|
}
|
|
@@ -66,7 +67,7 @@ export async function analyzeTask(description, userPreferences) {
|
|
|
66
67
|
console.error('Failed to detect task type, defaulting to chat:', error);
|
|
67
68
|
}
|
|
68
69
|
const filteredRegistry = {};
|
|
69
|
-
for (const [key, model] of
|
|
70
|
+
for (const [key, model] of getModelEntries()) {
|
|
70
71
|
// Filter by task type
|
|
71
72
|
if (model.type !== taskType) {
|
|
72
73
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layer-ai/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.48",
|
|
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.5.
|
|
39
|
+
"@layer-ai/sdk": "^2.5.12"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bcryptjs": "^2.4.6",
|