@aliou/pi-synthetic 0.17.3 → 0.18.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/package.json +5 -4
- package/src/extensions/provider/models.ts +78 -93
- package/src/extensions/provider/index.test.ts +0 -46
- package/src/extensions/provider/models.test.ts +0 -217
- package/src/services/quota-store.test.ts +0 -211
- package/src/services/quota-warnings.test.ts +0 -393
- package/src/utils/quotas-severity.test.ts +0 -295
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-synthetic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"src",
|
|
32
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"!src/**/*.test.ts"
|
|
33
34
|
],
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"@earendil-works/pi-coding-agent": "0.74.0",
|
|
@@ -40,8 +41,8 @@
|
|
|
40
41
|
"@aliou/pi-utils-ui": "^0.4.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@aliou/biome-plugins": "^0.
|
|
44
|
-
"@biomejs/biome": "^2.4.
|
|
44
|
+
"@aliou/biome-plugins": "^0.8.1",
|
|
45
|
+
"@biomejs/biome": "^2.4.15",
|
|
45
46
|
"@changesets/cli": "^2.27.11",
|
|
46
47
|
"@earendil-works/pi-coding-agent": "0.74.0",
|
|
47
48
|
"typebox": "^1.1.37",
|
|
@@ -91,38 +91,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
91
91
|
contextWindow: 196608,
|
|
92
92
|
maxTokens: 65536,
|
|
93
93
|
},
|
|
94
|
-
// models.dev: synthetic/hf:meta-llama/Llama-3.3-70B-Instruct → ctx=128000, out=32768
|
|
95
|
-
{
|
|
96
|
-
id: "hf:meta-llama/Llama-3.3-70B-Instruct",
|
|
97
|
-
name: "meta-llama/Llama-3.3-70B-Instruct",
|
|
98
|
-
provider: "together",
|
|
99
|
-
reasoning: false,
|
|
100
|
-
input: ["text"],
|
|
101
|
-
cost: {
|
|
102
|
-
input: 0.88,
|
|
103
|
-
output: 0.88,
|
|
104
|
-
cacheRead: 0.88,
|
|
105
|
-
cacheWrite: 0,
|
|
106
|
-
},
|
|
107
|
-
contextWindow: 131072,
|
|
108
|
-
maxTokens: 32768,
|
|
109
|
-
},
|
|
110
|
-
// models.dev: synthetic/hf:deepseek-ai/DeepSeek-R1-0528 → ctx=128000, out=128000
|
|
111
|
-
{
|
|
112
|
-
id: "hf:deepseek-ai/DeepSeek-R1-0528",
|
|
113
|
-
name: "deepseek-ai/DeepSeek-R1-0528",
|
|
114
|
-
provider: "together",
|
|
115
|
-
reasoning: true,
|
|
116
|
-
input: ["text"],
|
|
117
|
-
cost: {
|
|
118
|
-
input: 3,
|
|
119
|
-
output: 8,
|
|
120
|
-
cacheRead: 3,
|
|
121
|
-
cacheWrite: 0,
|
|
122
|
-
},
|
|
123
|
-
contextWindow: 131072,
|
|
124
|
-
maxTokens: 128000,
|
|
125
|
-
},
|
|
126
94
|
// models.dev: synthetic/hf:deepseek-ai/DeepSeek-V3.2 → ctx=162816, out=8000
|
|
127
95
|
{
|
|
128
96
|
id: "hf:deepseek-ai/DeepSeek-V3.2",
|
|
@@ -191,122 +159,139 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
191
159
|
contextWindow: 262144,
|
|
192
160
|
maxTokens: 65536,
|
|
193
161
|
},
|
|
194
|
-
// API: hf:
|
|
162
|
+
// API: hf:Qwen/Qwen3.5-397B-A17B → ctx=262144, out=65536
|
|
195
163
|
{
|
|
196
|
-
id: "hf:
|
|
197
|
-
name: "
|
|
164
|
+
id: "hf:Qwen/Qwen3.5-397B-A17B",
|
|
165
|
+
name: "Qwen/Qwen3.5-397B-A17B",
|
|
198
166
|
provider: "together",
|
|
199
167
|
reasoning: true,
|
|
200
168
|
input: ["text", "image"],
|
|
201
169
|
cost: {
|
|
202
|
-
input: 0.
|
|
203
|
-
output:
|
|
204
|
-
cacheRead: 0.
|
|
170
|
+
input: 0.6,
|
|
171
|
+
output: 3.6,
|
|
172
|
+
cacheRead: 0.6,
|
|
205
173
|
cacheWrite: 0,
|
|
206
174
|
},
|
|
207
175
|
contextWindow: 262144,
|
|
208
176
|
maxTokens: 65536,
|
|
209
177
|
},
|
|
210
|
-
// API: hf:
|
|
178
|
+
// API: hf:MiniMaxAI/MiniMax-M2.5 → ctx=191488, out=65536
|
|
211
179
|
{
|
|
212
|
-
id: "hf:
|
|
213
|
-
name: "
|
|
214
|
-
provider: "
|
|
180
|
+
id: "hf:MiniMaxAI/MiniMax-M2.5",
|
|
181
|
+
name: "MiniMaxAI/MiniMax-M2.5",
|
|
182
|
+
provider: "synthetic",
|
|
215
183
|
reasoning: true,
|
|
216
|
-
|
|
184
|
+
thinkingLevelMap: { off: null, minimal: null, low: null, xhigh: null },
|
|
185
|
+
input: ["text"],
|
|
217
186
|
cost: {
|
|
218
|
-
input: 0.
|
|
219
|
-
output: 2
|
|
220
|
-
cacheRead: 0.
|
|
187
|
+
input: 0.4,
|
|
188
|
+
output: 2,
|
|
189
|
+
cacheRead: 0.4,
|
|
221
190
|
cacheWrite: 0,
|
|
222
191
|
},
|
|
223
|
-
contextWindow:
|
|
192
|
+
contextWindow: 191488,
|
|
224
193
|
maxTokens: 65536,
|
|
194
|
+
compat: {
|
|
195
|
+
supportsReasoningEffort: true,
|
|
196
|
+
maxTokensField: "max_completion_tokens",
|
|
197
|
+
},
|
|
225
198
|
},
|
|
226
|
-
//
|
|
199
|
+
// API: hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 → ctx=262144, out=65536
|
|
227
200
|
{
|
|
228
|
-
id: "hf:
|
|
229
|
-
name: "
|
|
230
|
-
provider: "
|
|
201
|
+
id: "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
|
|
202
|
+
name: "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
|
|
203
|
+
provider: "synthetic",
|
|
231
204
|
reasoning: true,
|
|
205
|
+
thinkingLevelMap: { minimal: null, low: null, xhigh: null },
|
|
206
|
+
compat: {
|
|
207
|
+
supportsReasoningEffort: true,
|
|
208
|
+
},
|
|
232
209
|
input: ["text"],
|
|
233
210
|
cost: {
|
|
234
|
-
input:
|
|
235
|
-
output: 1
|
|
236
|
-
cacheRead:
|
|
211
|
+
input: 0.3,
|
|
212
|
+
output: 1,
|
|
213
|
+
cacheRead: 0.3,
|
|
237
214
|
cacheWrite: 0,
|
|
238
215
|
},
|
|
239
|
-
contextWindow:
|
|
240
|
-
maxTokens:
|
|
216
|
+
contextWindow: 262144,
|
|
217
|
+
maxTokens: 65536,
|
|
241
218
|
},
|
|
242
|
-
//
|
|
219
|
+
// API: syn:large:text → alias for hf:zai-org/GLM-5.1 → ctx=196608, out=65536
|
|
243
220
|
{
|
|
244
|
-
id: "
|
|
245
|
-
name: "
|
|
246
|
-
provider: "
|
|
221
|
+
id: "syn:large:text",
|
|
222
|
+
name: "syn:large:text",
|
|
223
|
+
provider: "synthetic",
|
|
247
224
|
reasoning: true,
|
|
225
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
226
|
+
compat: {
|
|
227
|
+
supportsReasoningEffort: true,
|
|
228
|
+
supportsDeveloperRole: false,
|
|
229
|
+
},
|
|
248
230
|
input: ["text"],
|
|
249
231
|
cost: {
|
|
250
|
-
input:
|
|
232
|
+
input: 1,
|
|
251
233
|
output: 3,
|
|
252
|
-
cacheRead:
|
|
234
|
+
cacheRead: 1,
|
|
253
235
|
cacheWrite: 0,
|
|
254
236
|
},
|
|
255
|
-
contextWindow:
|
|
256
|
-
maxTokens:
|
|
237
|
+
contextWindow: 196608,
|
|
238
|
+
maxTokens: 65536,
|
|
257
239
|
},
|
|
258
|
-
// API: hf:
|
|
240
|
+
// API: syn:small:text → alias for hf:zai-org/GLM-4.7-Flash → ctx=196608, out=65536
|
|
259
241
|
{
|
|
260
|
-
id: "
|
|
261
|
-
name: "
|
|
262
|
-
provider: "
|
|
242
|
+
id: "syn:small:text",
|
|
243
|
+
name: "syn:small:text",
|
|
244
|
+
provider: "synthetic",
|
|
263
245
|
reasoning: true,
|
|
264
|
-
|
|
246
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
247
|
+
compat: {
|
|
248
|
+
supportsReasoningEffort: true,
|
|
249
|
+
},
|
|
250
|
+
input: ["text"],
|
|
265
251
|
cost: {
|
|
266
|
-
input: 0.
|
|
267
|
-
output:
|
|
268
|
-
cacheRead: 0.
|
|
252
|
+
input: 0.1,
|
|
253
|
+
output: 0.5,
|
|
254
|
+
cacheRead: 0.1,
|
|
269
255
|
cacheWrite: 0,
|
|
270
256
|
},
|
|
271
|
-
contextWindow:
|
|
257
|
+
contextWindow: 196608,
|
|
272
258
|
maxTokens: 65536,
|
|
273
259
|
},
|
|
274
|
-
// API: hf:
|
|
260
|
+
// API: syn:large:vision → alias for hf:moonshotai/Kimi-K2.6 → ctx=262144, out=65536
|
|
275
261
|
{
|
|
276
|
-
id: "
|
|
277
|
-
name: "
|
|
262
|
+
id: "syn:large:vision",
|
|
263
|
+
name: "syn:large:vision",
|
|
278
264
|
provider: "synthetic",
|
|
279
265
|
reasoning: true,
|
|
280
|
-
thinkingLevelMap: {
|
|
281
|
-
|
|
266
|
+
thinkingLevelMap: { minimal: null, low: null, xhigh: null },
|
|
267
|
+
compat: {
|
|
268
|
+
supportsReasoningEffort: true,
|
|
269
|
+
},
|
|
270
|
+
input: ["text", "image"],
|
|
282
271
|
cost: {
|
|
283
|
-
input: 0.
|
|
284
|
-
output:
|
|
285
|
-
cacheRead: 0.
|
|
272
|
+
input: 0.95,
|
|
273
|
+
output: 4,
|
|
274
|
+
cacheRead: 0.95,
|
|
286
275
|
cacheWrite: 0,
|
|
287
276
|
},
|
|
288
|
-
contextWindow:
|
|
277
|
+
contextWindow: 262144,
|
|
289
278
|
maxTokens: 65536,
|
|
290
|
-
compat: {
|
|
291
|
-
supportsReasoningEffort: true,
|
|
292
|
-
maxTokensField: "max_completion_tokens",
|
|
293
|
-
},
|
|
294
279
|
},
|
|
295
|
-
// API: hf:
|
|
280
|
+
// API: syn:small:vision → alias for hf:moonshotai/Kimi-K2.6 → ctx=262144, out=65536
|
|
296
281
|
{
|
|
297
|
-
id: "
|
|
298
|
-
name: "
|
|
282
|
+
id: "syn:small:vision",
|
|
283
|
+
name: "syn:small:vision",
|
|
299
284
|
provider: "synthetic",
|
|
300
285
|
reasoning: true,
|
|
301
286
|
thinkingLevelMap: { minimal: null, low: null, xhigh: null },
|
|
302
287
|
compat: {
|
|
303
288
|
supportsReasoningEffort: true,
|
|
304
289
|
},
|
|
305
|
-
input: ["text"],
|
|
290
|
+
input: ["text", "image"],
|
|
306
291
|
cost: {
|
|
307
|
-
input: 0.
|
|
308
|
-
output:
|
|
309
|
-
cacheRead: 0.
|
|
292
|
+
input: 0.95,
|
|
293
|
+
output: 4,
|
|
294
|
+
cacheRead: 0.95,
|
|
310
295
|
cacheWrite: 0,
|
|
311
296
|
},
|
|
312
297
|
contextWindow: 262144,
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { buildSyntheticProviderModels } from "./index";
|
|
3
|
-
import { SYNTHETIC_MODELS } from "./models";
|
|
4
|
-
|
|
5
|
-
describe("buildSyntheticProviderModels", () => {
|
|
6
|
-
it("excludes proxied models when includeProxiedModels is false", () => {
|
|
7
|
-
const models = buildSyntheticProviderModels(false);
|
|
8
|
-
for (const model of models) {
|
|
9
|
-
const source = SYNTHETIC_MODELS.find((m) => m.id === model.id);
|
|
10
|
-
expect(source).toBeDefined();
|
|
11
|
-
expect(source?.provider).toBe("synthetic");
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("includes all models when includeProxiedModels is true", () => {
|
|
16
|
-
const models = buildSyntheticProviderModels(true);
|
|
17
|
-
expect(models).toHaveLength(SYNTHETIC_MODELS.length);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("does not expose the internal provider field", () => {
|
|
21
|
-
const models = buildSyntheticProviderModels(true);
|
|
22
|
-
for (const model of models) {
|
|
23
|
-
expect(model).not.toHaveProperty("provider");
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("sets default compat fields on every model", () => {
|
|
28
|
-
const models = buildSyntheticProviderModels(true);
|
|
29
|
-
for (const model of models) {
|
|
30
|
-
expect(model.compat).toMatchObject({
|
|
31
|
-
supportsDeveloperRole: false,
|
|
32
|
-
});
|
|
33
|
-
expect(model.compat).toHaveProperty("maxTokensField");
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("preserves model-specific compat overrides", () => {
|
|
38
|
-
const models = buildSyntheticProviderModels(true);
|
|
39
|
-
const miniMax = models.find((m) => m.id === "hf:MiniMaxAI/MiniMax-M2.5");
|
|
40
|
-
expect(miniMax).toBeDefined();
|
|
41
|
-
expect(miniMax?.compat).toMatchObject({
|
|
42
|
-
supportsDeveloperRole: false,
|
|
43
|
-
maxTokensField: "max_completion_tokens",
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { SYNTHETIC_MODELS } from "./models";
|
|
3
|
-
|
|
4
|
-
interface ApiModel {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
provider: string | null;
|
|
8
|
-
input_modalities: string[];
|
|
9
|
-
output_modalities: string[];
|
|
10
|
-
context_length: number;
|
|
11
|
-
max_output_length: number;
|
|
12
|
-
pricing: {
|
|
13
|
-
prompt: string;
|
|
14
|
-
completion: string;
|
|
15
|
-
input_cache_reads: string;
|
|
16
|
-
input_cache_writes: string;
|
|
17
|
-
};
|
|
18
|
-
supported_features?: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ApiResponse {
|
|
22
|
-
data: ApiModel[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface Discrepancy {
|
|
26
|
-
model: string;
|
|
27
|
-
field: string;
|
|
28
|
-
hardcoded: unknown;
|
|
29
|
-
api: unknown;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function fetchApiModels(): Promise<ApiModel[]> {
|
|
33
|
-
// Making ourselves known
|
|
34
|
-
const response = await fetch("https://api.synthetic.new/openai/v1/models", {
|
|
35
|
-
headers: {
|
|
36
|
-
Referer: "https://github.com/aliou/pi-synthetic",
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`API request failed: ${response.status} ${response.statusText}`,
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const data: ApiResponse = await response.json();
|
|
47
|
-
return data.data;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function parsePrice(priceStr: string): number {
|
|
51
|
-
// Convert "$0.0000006" to 0.6 (dollars per million tokens)
|
|
52
|
-
const match = priceStr.match(/\$?(\d+\.?\d*)/);
|
|
53
|
-
if (!match) return 0;
|
|
54
|
-
const pricePerToken = Number.parseFloat(match[1]);
|
|
55
|
-
// API prices are per token, hardcoded prices are per million tokens
|
|
56
|
-
return pricePerToken * 1_000_000;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function compareModels(
|
|
60
|
-
apiModels: ApiModel[],
|
|
61
|
-
hardcodedModels: typeof SYNTHETIC_MODELS,
|
|
62
|
-
): Discrepancy[] {
|
|
63
|
-
const discrepancies: Discrepancy[] = [];
|
|
64
|
-
|
|
65
|
-
for (const hardcoded of hardcodedModels) {
|
|
66
|
-
const apiModel = apiModels.find((m) => m.id === hardcoded.id);
|
|
67
|
-
|
|
68
|
-
if (!apiModel) {
|
|
69
|
-
discrepancies.push({
|
|
70
|
-
model: hardcoded.id,
|
|
71
|
-
field: "exists",
|
|
72
|
-
hardcoded: true,
|
|
73
|
-
api: false,
|
|
74
|
-
});
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check input modalities (text vs image support)
|
|
79
|
-
const apiInputs = apiModel.input_modalities.sort();
|
|
80
|
-
const hardcodedInputs = [...hardcoded.input].sort();
|
|
81
|
-
if (JSON.stringify(apiInputs) !== JSON.stringify(hardcodedInputs)) {
|
|
82
|
-
discrepancies.push({
|
|
83
|
-
model: hardcoded.id,
|
|
84
|
-
field: "input",
|
|
85
|
-
hardcoded: hardcodedInputs,
|
|
86
|
-
api: apiInputs,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Check context window
|
|
91
|
-
if (apiModel.context_length !== hardcoded.contextWindow) {
|
|
92
|
-
discrepancies.push({
|
|
93
|
-
model: hardcoded.id,
|
|
94
|
-
field: "contextWindow",
|
|
95
|
-
hardcoded: hardcoded.contextWindow,
|
|
96
|
-
api: apiModel.context_length,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check max output tokens (skip if API doesn't provide it)
|
|
101
|
-
if (
|
|
102
|
-
apiModel.max_output_length !== undefined &&
|
|
103
|
-
apiModel.max_output_length !== hardcoded.maxTokens
|
|
104
|
-
) {
|
|
105
|
-
discrepancies.push({
|
|
106
|
-
model: hardcoded.id,
|
|
107
|
-
field: "maxTokens",
|
|
108
|
-
hardcoded: hardcoded.maxTokens,
|
|
109
|
-
api: apiModel.max_output_length,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Check input cost (convert API price to per-million rate)
|
|
114
|
-
const apiInputCost = parsePrice(apiModel.pricing.prompt);
|
|
115
|
-
const epsilon = 0.001; // Small tolerance for floating point
|
|
116
|
-
if (Math.abs(apiInputCost - hardcoded.cost.input) > epsilon) {
|
|
117
|
-
discrepancies.push({
|
|
118
|
-
model: hardcoded.id,
|
|
119
|
-
field: "cost.input",
|
|
120
|
-
hardcoded: hardcoded.cost.input,
|
|
121
|
-
api: apiInputCost,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check output cost
|
|
126
|
-
const apiOutputCost = parsePrice(apiModel.pricing.completion);
|
|
127
|
-
if (Math.abs(apiOutputCost - hardcoded.cost.output) > epsilon) {
|
|
128
|
-
discrepancies.push({
|
|
129
|
-
model: hardcoded.id,
|
|
130
|
-
field: "cost.output",
|
|
131
|
-
hardcoded: hardcoded.cost.output,
|
|
132
|
-
api: apiOutputCost,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Check cache read cost
|
|
137
|
-
const apiCacheReadCost = parsePrice(apiModel.pricing.input_cache_reads);
|
|
138
|
-
if (Math.abs(apiCacheReadCost - hardcoded.cost.cacheRead) > epsilon) {
|
|
139
|
-
discrepancies.push({
|
|
140
|
-
model: hardcoded.id,
|
|
141
|
-
field: "cost.cacheRead",
|
|
142
|
-
hardcoded: hardcoded.cost.cacheRead,
|
|
143
|
-
api: apiCacheReadCost,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check reasoning capability from supported_features (skip if API doesn't provide it)
|
|
148
|
-
if (apiModel.supported_features !== undefined) {
|
|
149
|
-
const apiSupportsReasoning =
|
|
150
|
-
apiModel.supported_features.includes("reasoning");
|
|
151
|
-
if (apiSupportsReasoning !== hardcoded.reasoning) {
|
|
152
|
-
discrepancies.push({
|
|
153
|
-
model: hardcoded.id,
|
|
154
|
-
field: "reasoning",
|
|
155
|
-
hardcoded: hardcoded.reasoning,
|
|
156
|
-
api: apiSupportsReasoning,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Check provider
|
|
162
|
-
if (
|
|
163
|
-
apiModel.provider !== null &&
|
|
164
|
-
apiModel.provider !== hardcoded.provider
|
|
165
|
-
) {
|
|
166
|
-
discrepancies.push({
|
|
167
|
-
model: hardcoded.id,
|
|
168
|
-
field: "provider",
|
|
169
|
-
hardcoded: hardcoded.provider,
|
|
170
|
-
api: apiModel.provider,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Check for API models not in hardcoded list
|
|
176
|
-
for (const apiModel of apiModels) {
|
|
177
|
-
const hardcoded = hardcodedModels.find((m) => m.id === apiModel.id);
|
|
178
|
-
if (!hardcoded) {
|
|
179
|
-
discrepancies.push({
|
|
180
|
-
model: apiModel.id,
|
|
181
|
-
field: "exists",
|
|
182
|
-
hardcoded: false,
|
|
183
|
-
api: true,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return discrepancies;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
describe("Synthetic models", () => {
|
|
192
|
-
it("should match API model definitions", { timeout: 30000 }, async () => {
|
|
193
|
-
const apiModels = await fetchApiModels();
|
|
194
|
-
const discrepancies = compareModels(apiModels, SYNTHETIC_MODELS);
|
|
195
|
-
|
|
196
|
-
if (discrepancies.length > 0) {
|
|
197
|
-
console.error("\nModel discrepancies found:");
|
|
198
|
-
console.error("==========================");
|
|
199
|
-
for (const d of discrepancies) {
|
|
200
|
-
if (d.field === "exists") {
|
|
201
|
-
if (d.hardcoded) {
|
|
202
|
-
console.error(` ${d.model}: Missing from API`);
|
|
203
|
-
} else {
|
|
204
|
-
console.error(` ${d.model}: Missing from hardcoded models (NEW)`);
|
|
205
|
-
}
|
|
206
|
-
} else {
|
|
207
|
-
console.error(` ${d.model}.${d.field}:`);
|
|
208
|
-
console.error(` hardcoded: ${JSON.stringify(d.hardcoded)}`);
|
|
209
|
-
console.error(` api: ${JSON.stringify(d.api)}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
console.error("==========================\n");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
expect(discrepancies).toHaveLength(0);
|
|
216
|
-
});
|
|
217
|
-
});
|