@actuate-media/cms-core 0.42.0 → 0.43.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/__tests__/ai/ai.test.d.ts +2 -0
- package/dist/__tests__/ai/ai.test.d.ts.map +1 -0
- package/dist/__tests__/ai/ai.test.js +261 -0
- package/dist/__tests__/ai/ai.test.js.map +1 -0
- package/dist/ai/catalog.d.ts +29 -0
- package/dist/ai/catalog.d.ts.map +1 -0
- package/dist/ai/catalog.js +340 -0
- package/dist/ai/catalog.js.map +1 -0
- package/dist/ai/config-store.d.ts +55 -0
- package/dist/ai/config-store.d.ts.map +1 -0
- package/dist/ai/config-store.js +177 -0
- package/dist/ai/config-store.js.map +1 -0
- package/dist/ai/feature-defaults.d.ts +20 -0
- package/dist/ai/feature-defaults.d.ts.map +1 -0
- package/dist/ai/feature-defaults.js +104 -0
- package/dist/ai/feature-defaults.js.map +1 -0
- package/dist/ai/index.d.ts +13 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +13 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/models.d.ts +37 -0
- package/dist/ai/models.d.ts.map +1 -0
- package/dist/ai/models.js +126 -0
- package/dist/ai/models.js.map +1 -0
- package/dist/ai/providers/anthropic.d.ts +9 -0
- package/dist/ai/providers/anthropic.d.ts.map +1 -0
- package/dist/ai/providers/anthropic.js +36 -0
- package/dist/ai/providers/anthropic.js.map +1 -0
- package/dist/ai/providers/custom.d.ts +11 -0
- package/dist/ai/providers/custom.d.ts.map +1 -0
- package/dist/ai/providers/custom.js +70 -0
- package/dist/ai/providers/custom.js.map +1 -0
- package/dist/ai/providers/http.d.ts +32 -0
- package/dist/ai/providers/http.d.ts.map +1 -0
- package/dist/ai/providers/http.js +90 -0
- package/dist/ai/providers/http.js.map +1 -0
- package/dist/ai/providers/index.d.ts +16 -0
- package/dist/ai/providers/index.d.ts.map +1 -0
- package/dist/ai/providers/index.js +25 -0
- package/dist/ai/providers/index.js.map +1 -0
- package/dist/ai/providers/openai.d.ts +10 -0
- package/dist/ai/providers/openai.d.ts.map +1 -0
- package/dist/ai/providers/openai.js +41 -0
- package/dist/ai/providers/openai.js.map +1 -0
- package/dist/ai/providers/xai.d.ts +9 -0
- package/dist/ai/providers/xai.d.ts.map +1 -0
- package/dist/ai/providers/xai.js +35 -0
- package/dist/ai/providers/xai.js.map +1 -0
- package/dist/ai/types.d.ts +161 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +11 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/ai/usage.d.ts +18 -0
- package/dist/ai/usage.d.ts.map +1 -0
- package/dist/ai/usage.js +70 -0
- package/dist/ai/usage.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +477 -0
- package/dist/api/handlers.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/ai/ai.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { defaultAISettings, encryptProviderKey, getAIConfig, getEnvApiKey, last4, resolveProviderKey, toPublicProvider, } from '../../ai/config-store.js';
|
|
3
|
+
import { mergeFeatures } from '../../ai/feature-defaults.js';
|
|
4
|
+
import { curatedFallbackCatalog, normalizeModel } from '../../ai/catalog.js';
|
|
5
|
+
import { resolveModelOrUnavailable } from '../../ai/models.js';
|
|
6
|
+
import { getUsageSummary } from '../../ai/usage.js';
|
|
7
|
+
import * as openai from '../../ai/providers/openai.js';
|
|
8
|
+
import * as anthropic from '../../ai/providers/anthropic.js';
|
|
9
|
+
import * as custom from '../../ai/providers/custom.js';
|
|
10
|
+
function makeConn(overrides = {}) {
|
|
11
|
+
return {
|
|
12
|
+
id: 'c1',
|
|
13
|
+
provider: 'anthropic',
|
|
14
|
+
displayName: 'Anthropic',
|
|
15
|
+
enabled: true,
|
|
16
|
+
status: 'unknown',
|
|
17
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
18
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
19
|
+
updatedBy: 'u1',
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function mockResponse(status, body) {
|
|
24
|
+
return {
|
|
25
|
+
ok: status >= 200 && status < 300,
|
|
26
|
+
status,
|
|
27
|
+
json: async () => body,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
describe('ai/config-store', () => {
|
|
31
|
+
const origEnv = { ...process.env };
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
process.env = { ...origEnv };
|
|
34
|
+
vi.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
it('encrypts a key and masks to last4 without leaking the key', async () => {
|
|
37
|
+
const { apiKeyEncrypted, apiKeyLast4 } = await encryptProviderKey('sk-ant-secret-7788');
|
|
38
|
+
expect(apiKeyLast4).toBe('7788');
|
|
39
|
+
const conn = makeConn({ apiKeyEncrypted, apiKeyLast4 });
|
|
40
|
+
const pub = toPublicProvider(conn);
|
|
41
|
+
expect(pub.apiKeyEncrypted).toBeUndefined();
|
|
42
|
+
expect(pub.apiKeyLast4).toBe('7788');
|
|
43
|
+
expect(pub.hasKey).toBe(true);
|
|
44
|
+
expect(pub.apiKeySource).toBe('database');
|
|
45
|
+
expect(pub.keyManagedExternally).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it('treats an env-managed provider as read-only and reflects the env key suffix', () => {
|
|
48
|
+
process.env.OPENAI_API_KEY = 'sk-env-key-9090';
|
|
49
|
+
const conn = makeConn({ provider: 'openai' });
|
|
50
|
+
const pub = toPublicProvider(conn);
|
|
51
|
+
expect(pub.apiKeySource).toBe('environment');
|
|
52
|
+
expect(pub.keyManagedExternally).toBe(true);
|
|
53
|
+
expect(pub.apiKeyLast4).toBe('9090');
|
|
54
|
+
expect(getEnvApiKey('openai')).toBe('sk-env-key-9090');
|
|
55
|
+
});
|
|
56
|
+
it('resolves the env key over a stored key, else decrypts the stored key', async () => {
|
|
57
|
+
const { apiKeyEncrypted } = await encryptProviderKey('stored-key-1111');
|
|
58
|
+
const conn = makeConn({ provider: 'xai', apiKeyEncrypted });
|
|
59
|
+
expect(await resolveProviderKey(conn)).toBe('stored-key-1111');
|
|
60
|
+
process.env.XAI_API_KEY = 'env-key-2222';
|
|
61
|
+
expect(await resolveProviderKey(conn)).toBe('env-key-2222');
|
|
62
|
+
});
|
|
63
|
+
it('last4 handles short and empty values', () => {
|
|
64
|
+
expect(last4('abcdef')).toBe('cdef');
|
|
65
|
+
expect(last4('ab')).toBe('ab');
|
|
66
|
+
expect(last4('')).toBeUndefined();
|
|
67
|
+
expect(last4(undefined)).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
it('getAIConfig returns populated defaults when no document exists', async () => {
|
|
70
|
+
const db = { document: { findFirst: async () => null } };
|
|
71
|
+
const config = await getAIConfig(db);
|
|
72
|
+
expect(config.providers).toEqual([]);
|
|
73
|
+
expect(config.settings.features.length).toBeGreaterThan(0);
|
|
74
|
+
expect(config.settings.budget.alertAtPercentages).toEqual([50, 80, 100]);
|
|
75
|
+
});
|
|
76
|
+
it('getAIConfig merges stored feature toggles over current defaults', async () => {
|
|
77
|
+
const db = {
|
|
78
|
+
document: {
|
|
79
|
+
findFirst: async () => ({
|
|
80
|
+
data: {
|
|
81
|
+
providers: [],
|
|
82
|
+
settings: {
|
|
83
|
+
features: [{ key: 'ai.features.contentWriting', enabled: false }],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const config = await getAIConfig(db);
|
|
90
|
+
const cw = config.settings.features.find((f) => f.key === 'ai.features.contentWriting');
|
|
91
|
+
expect(cw?.enabled).toBe(false);
|
|
92
|
+
// Other defaults still present.
|
|
93
|
+
expect(config.settings.features.length).toBe(defaultAISettings().features.length);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('ai/feature-defaults mergeFeatures', () => {
|
|
97
|
+
it('preserves enabled state and drops unknown keys', () => {
|
|
98
|
+
const merged = mergeFeatures([
|
|
99
|
+
{ key: 'ai.features.mediaAltText', enabled: false },
|
|
100
|
+
{ key: 'ai.features.removed', enabled: true },
|
|
101
|
+
]);
|
|
102
|
+
expect(merged.find((f) => f.key === 'ai.features.mediaAltText')?.enabled).toBe(false);
|
|
103
|
+
expect(merged.find((f) => f.key === 'ai.features.removed')).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('ai/catalog normalizeModel', () => {
|
|
107
|
+
const now = '2026-01-01T00:00:00.000Z';
|
|
108
|
+
it('merges curated capabilities + display name for a dated provider id', () => {
|
|
109
|
+
const m = normalizeModel({
|
|
110
|
+
provider: 'openai',
|
|
111
|
+
providerModelId: 'gpt-4o-2024-08-06',
|
|
112
|
+
source: 'provider',
|
|
113
|
+
lastSyncedAt: now,
|
|
114
|
+
});
|
|
115
|
+
expect(m.displayName).toBe('GPT-4o (balanced)');
|
|
116
|
+
expect(m.capabilities.vision).toBe(true);
|
|
117
|
+
expect(m.capabilities.text).toBe(true);
|
|
118
|
+
expect(m.pricing?.inputPerMillion).toBe(2.5);
|
|
119
|
+
});
|
|
120
|
+
it('prefers the longest match (gpt-4o-mini over gpt-4o)', () => {
|
|
121
|
+
const m = normalizeModel({
|
|
122
|
+
provider: 'openai',
|
|
123
|
+
providerModelId: 'gpt-4o-mini-2024-07-18',
|
|
124
|
+
source: 'provider',
|
|
125
|
+
lastSyncedAt: now,
|
|
126
|
+
});
|
|
127
|
+
expect(m.displayName).toBe('GPT-4o mini (fast)');
|
|
128
|
+
});
|
|
129
|
+
it('infers conservative capabilities for an unknown model', () => {
|
|
130
|
+
const m = normalizeModel({
|
|
131
|
+
provider: 'custom',
|
|
132
|
+
providerModelId: 'mystery-llm-v9',
|
|
133
|
+
source: 'manual',
|
|
134
|
+
lastSyncedAt: now,
|
|
135
|
+
});
|
|
136
|
+
expect(m.capabilities.text).toBe(true);
|
|
137
|
+
expect(m.capabilities.vision).toBe(false);
|
|
138
|
+
expect(m.metadata).toEqual({});
|
|
139
|
+
});
|
|
140
|
+
it('detects embedding models by id', () => {
|
|
141
|
+
const m = normalizeModel({
|
|
142
|
+
provider: 'custom',
|
|
143
|
+
providerModelId: 'my-embedding-model',
|
|
144
|
+
source: 'manual',
|
|
145
|
+
lastSyncedAt: now,
|
|
146
|
+
});
|
|
147
|
+
expect(m.capabilities.embeddings).toBe(true);
|
|
148
|
+
expect(m.capabilities.text).toBe(false);
|
|
149
|
+
expect(m.outputModalities).toEqual(['embedding']);
|
|
150
|
+
});
|
|
151
|
+
it('curatedFallbackCatalog returns normalized models for a provider', () => {
|
|
152
|
+
const models = curatedFallbackCatalog('anthropic', now);
|
|
153
|
+
expect(models.length).toBeGreaterThan(0);
|
|
154
|
+
expect(models.every((m) => m.provider === 'anthropic')).toBe(true);
|
|
155
|
+
expect(models.some((m) => m.displayName === 'Claude Sonnet (balanced)')).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('ai/models resolveModelOrUnavailable', () => {
|
|
159
|
+
it('returns the catalog model when present', () => {
|
|
160
|
+
const catalog = curatedFallbackCatalog('anthropic', '2026-01-01T00:00:00.000Z');
|
|
161
|
+
const target = catalog[0];
|
|
162
|
+
expect(resolveModelOrUnavailable(target.id, catalog)?.status).not.toBe('unavailable');
|
|
163
|
+
});
|
|
164
|
+
it('synthesizes an unavailable model when the id is gone', () => {
|
|
165
|
+
const catalog = curatedFallbackCatalog('anthropic', '2026-01-01T00:00:00.000Z');
|
|
166
|
+
const m = resolveModelOrUnavailable('openai:ghost-model', catalog);
|
|
167
|
+
expect(m?.status).toBe('unavailable');
|
|
168
|
+
expect(m?.provider).toBe('openai');
|
|
169
|
+
expect(m?.providerModelId).toBe('ghost-model');
|
|
170
|
+
});
|
|
171
|
+
it('returns null for an empty id', () => {
|
|
172
|
+
expect(resolveModelOrUnavailable(undefined, [])).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('ai/providers adapters (mocked fetch)', () => {
|
|
176
|
+
it('openai testConnection maps 200 -> connected and lists model ids', async () => {
|
|
177
|
+
const fetchImpl = vi
|
|
178
|
+
.fn()
|
|
179
|
+
.mockResolvedValue(mockResponse(200, { data: [{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }] }));
|
|
180
|
+
const res = await openai.testConnection({ apiKey: 'k', fetchImpl });
|
|
181
|
+
expect(res.status).toBe('connected');
|
|
182
|
+
expect(res.ok).toBe(true);
|
|
183
|
+
const ids = await openai.listModels({ apiKey: 'k', fetchImpl });
|
|
184
|
+
expect(ids).toEqual(['gpt-4o', 'gpt-4o-mini']);
|
|
185
|
+
});
|
|
186
|
+
it('openai testConnection maps 401 -> invalid_key', async () => {
|
|
187
|
+
const fetchImpl = vi.fn().mockResolvedValue(mockResponse(401, {}));
|
|
188
|
+
const res = await openai.testConnection({ apiKey: 'bad', fetchImpl });
|
|
189
|
+
expect(res.status).toBe('invalid_key');
|
|
190
|
+
expect(res.ok).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
it('openai testConnection reports needs_setup with no key', async () => {
|
|
193
|
+
const res = await openai.testConnection({ apiKey: '' });
|
|
194
|
+
expect(res.status).toBe('needs_setup');
|
|
195
|
+
});
|
|
196
|
+
it('anthropic sends x-api-key + version headers', async () => {
|
|
197
|
+
const fetchImpl = vi
|
|
198
|
+
.fn()
|
|
199
|
+
.mockResolvedValue(mockResponse(200, { data: [] }));
|
|
200
|
+
await anthropic.testConnection({ apiKey: 'k', fetchImpl });
|
|
201
|
+
const call = fetchImpl.mock.calls[0];
|
|
202
|
+
const headers = call[1].headers;
|
|
203
|
+
expect(headers['x-api-key']).toBe('k');
|
|
204
|
+
expect(headers['anthropic-version']).toBeTruthy();
|
|
205
|
+
});
|
|
206
|
+
it('custom provider rejects an SSRF base URL before fetching', async () => {
|
|
207
|
+
const fetchImpl = vi.fn();
|
|
208
|
+
const res = await custom.testConnection({
|
|
209
|
+
apiKey: 'k',
|
|
210
|
+
baseUrl: 'http://127.0.0.1/v1',
|
|
211
|
+
fetchImpl,
|
|
212
|
+
});
|
|
213
|
+
expect(res.ok).toBe(false);
|
|
214
|
+
expect(res.message.toLowerCase()).toContain('unsafe');
|
|
215
|
+
expect(fetchImpl.mock.calls.length).toBe(0);
|
|
216
|
+
});
|
|
217
|
+
it('custom provider treats 404 /models as connected (manual model entry)', async () => {
|
|
218
|
+
const fetchImpl = vi.fn().mockResolvedValue(mockResponse(404, {}));
|
|
219
|
+
// Public IP literal so the SSRF guard's DNS resolution is skipped (offline test).
|
|
220
|
+
const res = await custom.testConnection({
|
|
221
|
+
apiKey: 'k',
|
|
222
|
+
baseUrl: 'https://93.184.216.34/v1',
|
|
223
|
+
fetchImpl,
|
|
224
|
+
});
|
|
225
|
+
expect(res.ok).toBe(true);
|
|
226
|
+
expect(res.status).toBe('connected');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('ai/usage getUsageSummary', () => {
|
|
230
|
+
beforeEach(() => {
|
|
231
|
+
vi.useFakeTimers();
|
|
232
|
+
vi.setSystemTime(new Date('2026-06-15T12:00:00.000Z'));
|
|
233
|
+
});
|
|
234
|
+
afterEach(() => {
|
|
235
|
+
vi.useRealTimers();
|
|
236
|
+
});
|
|
237
|
+
it('aggregates token totals from audit rows within the month', async () => {
|
|
238
|
+
const db = {
|
|
239
|
+
document: { findFirst: async () => null },
|
|
240
|
+
auditLog: {
|
|
241
|
+
findMany: async () => [
|
|
242
|
+
{ details: { totalTokensUsed: 100 } },
|
|
243
|
+
{ details: { tokensUsed: 50 } },
|
|
244
|
+
{ details: { promptTokens: 10, completionTokens: 5 } },
|
|
245
|
+
{ details: { action: 'noop' } },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
const summary = await getUsageSummary(db);
|
|
250
|
+
expect(summary.totalTokens).toBe(165);
|
|
251
|
+
expect(summary.runCount).toBe(3);
|
|
252
|
+
expect(summary.periodStart).toBe('2026-06-01T00:00:00.000Z');
|
|
253
|
+
});
|
|
254
|
+
it('returns zero usage when there are no rows', async () => {
|
|
255
|
+
const db = { document: { findFirst: async () => null }, auditLog: { findMany: async () => [] } };
|
|
256
|
+
const summary = await getUsageSummary(db);
|
|
257
|
+
expect(summary.totalTokens).toBe(0);
|
|
258
|
+
expect(summary.runCount).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
//# sourceMappingURL=ai.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.test.js","sourceRoot":"","sources":["../../../src/__tests__/ai/ai.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated model metadata + normalization.
|
|
3
|
+
*
|
|
4
|
+
* Live provider listing endpoints return model IDs and little else (rarely
|
|
5
|
+
* capabilities or pricing). We merge those IDs with a curated capability/pricing
|
|
6
|
+
* map so the catalog always knows what a model can do, what it costs, and
|
|
7
|
+
* whether it is current. When no key/live listing is available, the curated
|
|
8
|
+
* entries also serve as the fallback catalog.
|
|
9
|
+
*
|
|
10
|
+
* Matching is prefix-based (longest match wins) so dated provider IDs like
|
|
11
|
+
* `gpt-4o-2024-08-06` resolve to the `gpt-4o` entry, while `gpt-4o-mini-*`
|
|
12
|
+
* resolves to its own entry.
|
|
13
|
+
*/
|
|
14
|
+
import type { AIModel, AIProviderKey } from './types.js';
|
|
15
|
+
export interface NormalizeInput {
|
|
16
|
+
provider: AIProviderKey;
|
|
17
|
+
providerModelId: string;
|
|
18
|
+
source: AIModel['source'];
|
|
19
|
+
lastSyncedAt: string;
|
|
20
|
+
/** Override status (e.g. 'available' from a live listing). */
|
|
21
|
+
status?: AIModel['status'];
|
|
22
|
+
/** Provider-supplied display name, if any. */
|
|
23
|
+
displayName?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Merge a (live or fallback) model id with curated metadata into an AIModel. */
|
|
26
|
+
export declare function normalizeModel(input: NormalizeInput): AIModel;
|
|
27
|
+
/** Curated fallback catalog for a provider (used when no live listing). */
|
|
28
|
+
export declare function curatedFallbackCatalog(provider: AIProviderKey, lastSyncedAt: string): AIModel[];
|
|
29
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/ai/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAuC,aAAa,EAAE,MAAM,YAAY,CAAA;AAmT7F,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CA4B7D;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,EAAE,CAc/F"}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated model metadata + normalization.
|
|
3
|
+
*
|
|
4
|
+
* Live provider listing endpoints return model IDs and little else (rarely
|
|
5
|
+
* capabilities or pricing). We merge those IDs with a curated capability/pricing
|
|
6
|
+
* map so the catalog always knows what a model can do, what it costs, and
|
|
7
|
+
* whether it is current. When no key/live listing is available, the curated
|
|
8
|
+
* entries also serve as the fallback catalog.
|
|
9
|
+
*
|
|
10
|
+
* Matching is prefix-based (longest match wins) so dated provider IDs like
|
|
11
|
+
* `gpt-4o-2024-08-06` resolve to the `gpt-4o` entry, while `gpt-4o-mini-*`
|
|
12
|
+
* resolves to its own entry.
|
|
13
|
+
*/
|
|
14
|
+
const NO_CAPABILITIES = {
|
|
15
|
+
text: false,
|
|
16
|
+
vision: false,
|
|
17
|
+
imageGeneration: false,
|
|
18
|
+
embeddings: false,
|
|
19
|
+
toolCalling: false,
|
|
20
|
+
structuredOutput: false,
|
|
21
|
+
streaming: false,
|
|
22
|
+
reasoning: false,
|
|
23
|
+
longContext: false,
|
|
24
|
+
};
|
|
25
|
+
const CURATED = {
|
|
26
|
+
openai: [
|
|
27
|
+
{
|
|
28
|
+
match: ['gpt-4o-mini'],
|
|
29
|
+
canonicalId: 'gpt-4o-mini',
|
|
30
|
+
displayName: 'GPT-4o mini (fast)',
|
|
31
|
+
description: 'Low-cost multimodal model for high-volume tasks.',
|
|
32
|
+
capabilities: {
|
|
33
|
+
text: true,
|
|
34
|
+
vision: true,
|
|
35
|
+
toolCalling: true,
|
|
36
|
+
structuredOutput: true,
|
|
37
|
+
streaming: true,
|
|
38
|
+
longContext: true,
|
|
39
|
+
},
|
|
40
|
+
contextWindow: 128000,
|
|
41
|
+
maxOutputTokens: 16384,
|
|
42
|
+
inputModalities: ['text', 'image'],
|
|
43
|
+
outputModalities: ['text'],
|
|
44
|
+
pricing: { inputPerMillion: 0.15, outputPerMillion: 0.6 },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
match: ['gpt-4o', 'chatgpt-4o'],
|
|
48
|
+
canonicalId: 'gpt-4o',
|
|
49
|
+
displayName: 'GPT-4o (balanced)',
|
|
50
|
+
description: 'Flagship multimodal model balancing quality, speed, and cost.',
|
|
51
|
+
capabilities: {
|
|
52
|
+
text: true,
|
|
53
|
+
vision: true,
|
|
54
|
+
toolCalling: true,
|
|
55
|
+
structuredOutput: true,
|
|
56
|
+
streaming: true,
|
|
57
|
+
longContext: true,
|
|
58
|
+
},
|
|
59
|
+
contextWindow: 128000,
|
|
60
|
+
maxOutputTokens: 16384,
|
|
61
|
+
inputModalities: ['text', 'image'],
|
|
62
|
+
outputModalities: ['text'],
|
|
63
|
+
pricing: { inputPerMillion: 2.5, outputPerMillion: 10 },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
match: ['o3-mini', 'o1-mini'],
|
|
67
|
+
canonicalId: 'o3-mini',
|
|
68
|
+
displayName: 'o3-mini (reasoning)',
|
|
69
|
+
description: 'Fast reasoning model for structured problem solving.',
|
|
70
|
+
capabilities: {
|
|
71
|
+
text: true,
|
|
72
|
+
toolCalling: true,
|
|
73
|
+
structuredOutput: true,
|
|
74
|
+
reasoning: true,
|
|
75
|
+
longContext: true,
|
|
76
|
+
},
|
|
77
|
+
contextWindow: 200000,
|
|
78
|
+
maxOutputTokens: 100000,
|
|
79
|
+
inputModalities: ['text'],
|
|
80
|
+
outputModalities: ['text'],
|
|
81
|
+
pricing: { inputPerMillion: 1.1, outputPerMillion: 4.4 },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
match: ['o3', 'o1'],
|
|
85
|
+
canonicalId: 'o3',
|
|
86
|
+
displayName: 'o3 (reasoning)',
|
|
87
|
+
description: 'High-capability reasoning model.',
|
|
88
|
+
capabilities: {
|
|
89
|
+
text: true,
|
|
90
|
+
vision: true,
|
|
91
|
+
toolCalling: true,
|
|
92
|
+
structuredOutput: true,
|
|
93
|
+
reasoning: true,
|
|
94
|
+
longContext: true,
|
|
95
|
+
},
|
|
96
|
+
contextWindow: 200000,
|
|
97
|
+
maxOutputTokens: 100000,
|
|
98
|
+
inputModalities: ['text', 'image'],
|
|
99
|
+
outputModalities: ['text'],
|
|
100
|
+
pricing: { inputPerMillion: 10, outputPerMillion: 40 },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
match: ['text-embedding-3-large'],
|
|
104
|
+
canonicalId: 'text-embedding-3-large',
|
|
105
|
+
displayName: 'Embedding 3 (large)',
|
|
106
|
+
capabilities: { embeddings: true },
|
|
107
|
+
inputModalities: ['text'],
|
|
108
|
+
outputModalities: ['embedding'],
|
|
109
|
+
pricing: { inputPerMillion: 0.13 },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
match: ['text-embedding-3-small', 'text-embedding'],
|
|
113
|
+
canonicalId: 'text-embedding-3-small',
|
|
114
|
+
displayName: 'Embedding 3 (small)',
|
|
115
|
+
capabilities: { embeddings: true },
|
|
116
|
+
inputModalities: ['text'],
|
|
117
|
+
outputModalities: ['embedding'],
|
|
118
|
+
pricing: { inputPerMillion: 0.02 },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
match: ['gpt-image-1', 'dall-e-3', 'dall-e'],
|
|
122
|
+
canonicalId: 'gpt-image-1',
|
|
123
|
+
displayName: 'GPT Image',
|
|
124
|
+
capabilities: { imageGeneration: true },
|
|
125
|
+
inputModalities: ['text'],
|
|
126
|
+
outputModalities: ['image'],
|
|
127
|
+
pricing: { image: 0.04 },
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
anthropic: [
|
|
131
|
+
{
|
|
132
|
+
match: ['claude-3-5-haiku', 'claude-haiku', 'claude-3-haiku'],
|
|
133
|
+
canonicalId: 'claude-3-5-haiku-latest',
|
|
134
|
+
displayName: 'Claude Haiku (fast)',
|
|
135
|
+
description: 'Fast, low-cost model for high-throughput tasks.',
|
|
136
|
+
capabilities: {
|
|
137
|
+
text: true,
|
|
138
|
+
vision: true,
|
|
139
|
+
toolCalling: true,
|
|
140
|
+
structuredOutput: true,
|
|
141
|
+
streaming: true,
|
|
142
|
+
longContext: true,
|
|
143
|
+
},
|
|
144
|
+
contextWindow: 200000,
|
|
145
|
+
maxOutputTokens: 8192,
|
|
146
|
+
inputModalities: ['text', 'image'],
|
|
147
|
+
outputModalities: ['text'],
|
|
148
|
+
pricing: { inputPerMillion: 0.8, outputPerMillion: 4 },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
match: ['claude-opus', 'claude-3-opus'],
|
|
152
|
+
canonicalId: 'claude-opus-4-latest',
|
|
153
|
+
displayName: 'Claude Opus (most capable)',
|
|
154
|
+
description: 'Highest-capability Claude model for complex reasoning.',
|
|
155
|
+
capabilities: {
|
|
156
|
+
text: true,
|
|
157
|
+
vision: true,
|
|
158
|
+
toolCalling: true,
|
|
159
|
+
structuredOutput: true,
|
|
160
|
+
streaming: true,
|
|
161
|
+
reasoning: true,
|
|
162
|
+
longContext: true,
|
|
163
|
+
},
|
|
164
|
+
contextWindow: 200000,
|
|
165
|
+
maxOutputTokens: 32000,
|
|
166
|
+
inputModalities: ['text', 'image'],
|
|
167
|
+
outputModalities: ['text'],
|
|
168
|
+
pricing: { inputPerMillion: 15, outputPerMillion: 75 },
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
match: ['claude-sonnet', 'claude-3-5-sonnet', 'claude-3-sonnet', 'claude-'],
|
|
172
|
+
canonicalId: 'claude-sonnet-4-latest',
|
|
173
|
+
displayName: 'Claude Sonnet (balanced)',
|
|
174
|
+
description: 'Balanced Claude model for everyday content and reasoning.',
|
|
175
|
+
capabilities: {
|
|
176
|
+
text: true,
|
|
177
|
+
vision: true,
|
|
178
|
+
toolCalling: true,
|
|
179
|
+
structuredOutput: true,
|
|
180
|
+
streaming: true,
|
|
181
|
+
longContext: true,
|
|
182
|
+
},
|
|
183
|
+
contextWindow: 200000,
|
|
184
|
+
maxOutputTokens: 16384,
|
|
185
|
+
inputModalities: ['text', 'image'],
|
|
186
|
+
outputModalities: ['text'],
|
|
187
|
+
pricing: { inputPerMillion: 3, outputPerMillion: 15 },
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
xai: [
|
|
191
|
+
{
|
|
192
|
+
match: ['grok-4-fast', 'grok-3-mini', 'grok-mini'],
|
|
193
|
+
canonicalId: 'grok-4-fast',
|
|
194
|
+
displayName: 'Grok (fast)',
|
|
195
|
+
description: 'Low-latency Grok model for high-volume tasks.',
|
|
196
|
+
capabilities: {
|
|
197
|
+
text: true,
|
|
198
|
+
toolCalling: true,
|
|
199
|
+
structuredOutput: true,
|
|
200
|
+
streaming: true,
|
|
201
|
+
longContext: true,
|
|
202
|
+
},
|
|
203
|
+
contextWindow: 131072,
|
|
204
|
+
maxOutputTokens: 16384,
|
|
205
|
+
inputModalities: ['text'],
|
|
206
|
+
outputModalities: ['text'],
|
|
207
|
+
pricing: { inputPerMillion: 0.2, outputPerMillion: 0.5 },
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
match: ['grok-vision', 'grok-2-vision'],
|
|
211
|
+
canonicalId: 'grok-vision',
|
|
212
|
+
displayName: 'Grok Vision',
|
|
213
|
+
description: 'Multimodal Grok model with image understanding.',
|
|
214
|
+
capabilities: {
|
|
215
|
+
text: true,
|
|
216
|
+
vision: true,
|
|
217
|
+
toolCalling: true,
|
|
218
|
+
structuredOutput: true,
|
|
219
|
+
streaming: true,
|
|
220
|
+
longContext: true,
|
|
221
|
+
},
|
|
222
|
+
contextWindow: 131072,
|
|
223
|
+
maxOutputTokens: 16384,
|
|
224
|
+
inputModalities: ['text', 'image'],
|
|
225
|
+
outputModalities: ['text'],
|
|
226
|
+
pricing: { inputPerMillion: 2, outputPerMillion: 10 },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
match: ['grok'],
|
|
230
|
+
canonicalId: 'grok-4',
|
|
231
|
+
displayName: 'Grok (balanced)',
|
|
232
|
+
description: 'Flagship Grok model.',
|
|
233
|
+
capabilities: {
|
|
234
|
+
text: true,
|
|
235
|
+
toolCalling: true,
|
|
236
|
+
structuredOutput: true,
|
|
237
|
+
streaming: true,
|
|
238
|
+
reasoning: true,
|
|
239
|
+
longContext: true,
|
|
240
|
+
},
|
|
241
|
+
contextWindow: 131072,
|
|
242
|
+
maxOutputTokens: 16384,
|
|
243
|
+
inputModalities: ['text'],
|
|
244
|
+
outputModalities: ['text'],
|
|
245
|
+
pricing: { inputPerMillion: 3, outputPerMillion: 15 },
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
/** Find the curated entry whose longest `match` prefix applies to the id. */
|
|
250
|
+
function findCurated(provider, providerModelId) {
|
|
251
|
+
if (provider === 'custom')
|
|
252
|
+
return null;
|
|
253
|
+
const list = CURATED[provider];
|
|
254
|
+
if (!list)
|
|
255
|
+
return null;
|
|
256
|
+
const id = providerModelId.toLowerCase();
|
|
257
|
+
let best = null;
|
|
258
|
+
for (const meta of list) {
|
|
259
|
+
for (const m of meta.match) {
|
|
260
|
+
if (id.startsWith(m) && (!best || m.length > best.len)) {
|
|
261
|
+
best = { meta, len: m.length };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return best?.meta ?? null;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Heuristic capability inference for models with no curated entry (e.g. an
|
|
269
|
+
* unknown custom-provider model). Conservative: assumes text unless the id
|
|
270
|
+
* clearly indicates embeddings/image-only.
|
|
271
|
+
*/
|
|
272
|
+
function inferCapabilities(providerModelId) {
|
|
273
|
+
const id = providerModelId.toLowerCase();
|
|
274
|
+
const caps = { ...NO_CAPABILITIES };
|
|
275
|
+
if (id.includes('embed')) {
|
|
276
|
+
caps.embeddings = true;
|
|
277
|
+
return caps;
|
|
278
|
+
}
|
|
279
|
+
if (id.includes('image') || id.includes('dall-e') || id.includes('imagen')) {
|
|
280
|
+
caps.imageGeneration = true;
|
|
281
|
+
return caps;
|
|
282
|
+
}
|
|
283
|
+
caps.text = true;
|
|
284
|
+
caps.streaming = true;
|
|
285
|
+
if (id.includes('vision') ||
|
|
286
|
+
id.includes('-o') ||
|
|
287
|
+
id.includes('4o') ||
|
|
288
|
+
id.includes('multimodal')) {
|
|
289
|
+
caps.vision = true;
|
|
290
|
+
}
|
|
291
|
+
if (id.includes('reason') || id.includes('think') || /\bo[13]\b/.test(id)) {
|
|
292
|
+
caps.reasoning = true;
|
|
293
|
+
}
|
|
294
|
+
return caps;
|
|
295
|
+
}
|
|
296
|
+
/** Merge a (live or fallback) model id with curated metadata into an AIModel. */
|
|
297
|
+
export function normalizeModel(input) {
|
|
298
|
+
const curated = findCurated(input.provider, input.providerModelId);
|
|
299
|
+
const capabilities = curated
|
|
300
|
+
? { ...NO_CAPABILITIES, ...curated.capabilities }
|
|
301
|
+
: inferCapabilities(input.providerModelId);
|
|
302
|
+
return {
|
|
303
|
+
id: `${input.provider}:${input.providerModelId}`,
|
|
304
|
+
provider: input.provider,
|
|
305
|
+
providerModelId: input.providerModelId,
|
|
306
|
+
displayName: curated?.displayName ?? input.displayName ?? input.providerModelId,
|
|
307
|
+
description: curated?.description,
|
|
308
|
+
status: input.status ?? (curated?.deprecated ? 'deprecated' : 'available'),
|
|
309
|
+
capabilities,
|
|
310
|
+
contextWindow: curated?.contextWindow,
|
|
311
|
+
maxOutputTokens: curated?.maxOutputTokens,
|
|
312
|
+
inputModalities: curated?.inputModalities ?? (capabilities.embeddings ? ['text'] : ['text']),
|
|
313
|
+
outputModalities: curated?.outputModalities ??
|
|
314
|
+
(capabilities.embeddings
|
|
315
|
+
? ['embedding']
|
|
316
|
+
: capabilities.imageGeneration
|
|
317
|
+
? ['image']
|
|
318
|
+
: ['text']),
|
|
319
|
+
pricing: curated?.pricing,
|
|
320
|
+
lastSyncedAt: input.lastSyncedAt,
|
|
321
|
+
source: input.source,
|
|
322
|
+
metadata: curated ? { curated: true } : {},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/** Curated fallback catalog for a provider (used when no live listing). */
|
|
326
|
+
export function curatedFallbackCatalog(provider, lastSyncedAt) {
|
|
327
|
+
if (provider === 'custom')
|
|
328
|
+
return [];
|
|
329
|
+
const list = CURATED[provider] ?? [];
|
|
330
|
+
return list
|
|
331
|
+
.filter((m) => m.fallback !== false)
|
|
332
|
+
.map((m) => normalizeModel({
|
|
333
|
+
provider,
|
|
334
|
+
providerModelId: m.canonicalId,
|
|
335
|
+
source: 'config',
|
|
336
|
+
status: m.deprecated ? 'deprecated' : 'available',
|
|
337
|
+
lastSyncedAt,
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=catalog.js.map
|