pi-free 2.2.3 → 2.2.4

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.
@@ -1,296 +1,296 @@
1
- /**
2
- * Model detection utilities for pi-free-providers.
3
- * Extracts and adapts model family detection from pi-models.
4
- * Used for failover when providers hit rate limits.
5
- */
6
-
7
- import type { Model } from "@earendil-works/pi-ai";
8
- import type { ProviderModelConfig } from "./types.ts";
9
-
10
- export interface ModelInfo {
11
- id: string;
12
- name?: string;
13
- provider: string;
14
- isFree: boolean;
15
- inputCost: number;
16
- outputCost: number;
17
- }
18
-
19
- export interface ModelFamily {
20
- id: string; // Normalized family ID (e.g., "claude-sonnet")
21
- displayName: string; // Human readable (e.g., "Claude Sonnet")
22
- models: ModelInfo[]; // All models in this family
23
- }
24
-
25
- /**
26
- * Convert Pi's Model type to ModelInfo for internal use
27
- */
28
- export function toModelInfo(model: Model<any>): ModelInfo {
29
- return {
30
- id: model.id,
31
- name: model.name,
32
- provider: model.provider,
33
- isFree: !model.cost || (model.cost.input === 0 && model.cost.output === 0),
34
- inputCost: model.cost?.input ?? 0,
35
- outputCost: model.cost?.output ?? 0,
36
- };
37
- }
38
-
39
- /**
40
- * Convert ProviderModelConfig to ModelInfo for internal use
41
- */
42
- export function toProviderModelInfo(model: ProviderModelConfig): ModelInfo {
43
- return {
44
- id: model.id,
45
- name: model.name,
46
- provider: "", // Will be set by caller
47
- isFree: !model.cost || (model.cost.input === 0 && model.cost.output === 0),
48
- inputCost: model.cost?.input ?? 0,
49
- outputCost: model.cost?.output ?? 0,
50
- };
51
- }
52
-
53
- // =============================================================================
54
- // Shared helpers for model family detection
55
- // =============================================================================
56
-
57
- const VERSION_RE = /^v?\d+(\.\d+)?$/;
58
- const ROUTER_RE = /\b(?:router|auto)\b/;
59
- const SKIP_PARTS = new Set([
60
- "latest",
61
- "preview",
62
- "rc",
63
- "beta",
64
- "alpha",
65
- "dev",
66
- "free",
67
- ]);
68
-
69
- interface BrandMapping {
70
- keywords: string[];
71
- familyId: string;
72
- familyName: string;
73
- }
74
-
75
- const BRAND_MAPPINGS: BrandMapping[] = [
76
- { keywords: ["claude"], familyId: "claude", familyName: "Claude" },
77
- { keywords: ["deepseek"], familyId: "deepseek", familyName: "DeepSeek" },
78
- { keywords: ["gemini"], familyId: "gemini", familyName: "Gemini" },
79
- { keywords: ["gpt"], familyId: "gpt", familyName: "GPT" },
80
- { keywords: ["llama"], familyId: "llama", familyName: "Llama" },
81
- { keywords: ["minimax"], familyId: "minimax", familyName: "MiniMax" },
82
- { keywords: ["qwen"], familyId: "qwen", familyName: "Qwen" },
83
- { keywords: ["nemotron"], familyId: "nemotron", familyName: "Nemotron" },
84
- { keywords: ["kimi", "moonshot"], familyId: "kimi", familyName: "Kimi" },
85
- { keywords: ["glm", "chatglm"], familyId: "glm", familyName: "GLM" },
86
- { keywords: ["mistral"], familyId: "mistral", familyName: "Mistral" },
87
- { keywords: ["arcee", "trinity"], familyId: "arcee", familyName: "Arcee" },
88
- { keywords: ["o1", "o3"], familyId: "openai-o", familyName: "OpenAI o" },
89
- ];
90
-
91
- const PROVIDER_MAPPINGS: Record<
92
- string,
93
- { familyId: string; familyName: string }
94
- > = {
95
- minimax: { familyId: "minimax", familyName: "MiniMax" },
96
- minimaxai: { familyId: "minimax", familyName: "MiniMax" },
97
- deepseek: { familyId: "deepseek", familyName: "DeepSeek" },
98
- nvidia: { familyId: "nemotron", familyName: "Nemotron" },
99
- moonshot: { familyId: "kimi", familyName: "Kimi" },
100
- zhipu: { familyId: "glm", familyName: "GLM" },
101
- };
102
-
103
- function capitalize(s: string): string {
104
- return s.charAt(0).toUpperCase() + s.slice(1);
105
- }
106
-
107
- function findBrandInText(
108
- text: string,
109
- ): { familyId: string; familyName: string } | null {
110
- for (const mapping of BRAND_MAPPINGS) {
111
- for (const keyword of mapping.keywords) {
112
- if (text.includes(keyword)) {
113
- return { familyId: mapping.familyId, familyName: mapping.familyName };
114
- }
115
- }
116
- }
117
- return null;
118
- }
119
-
120
- function findBrandInParts(
121
- parts: string[],
122
- ): { familyId: string; familyName: string } | null {
123
- for (const part of parts) {
124
- const result = findBrandInText(part);
125
- if (result) return result;
126
- }
127
- return null;
128
- }
129
-
130
- /**
131
- * Detect the model family from a model's ID or name.
132
- * Returns the family ID and display name.
133
- */
134
- export function detectModelFamily(
135
- model: ModelInfo,
136
- ): { familyId: string; familyName: string } | null {
137
- const id = model.id.toLowerCase();
138
- const name = (model.name || "").toLowerCase();
139
- const fullText = `${id} ${name}`;
140
-
141
- // Router models (gateways to free models) - group into "other"
142
- if (ROUTER_RE.test(fullText) || id === "kilo-auto/free") {
143
- return { familyId: "other", familyName: "Other" };
144
- }
145
-
146
- // Known brand keywords in full text
147
- const brandFromText = findBrandInText(fullText);
148
- if (brandFromText) return brandFromText;
149
-
150
- // Provider-specific fallbacks for models without brand in ID/name
151
- const providerResult = PROVIDER_MAPPINGS[model.provider];
152
- if (providerResult) return providerResult;
153
-
154
- // Fallback: try to identify brand from model ID structure
155
- const parts = id.split(/[-_:.@]/);
156
- const firstPart = parts[0];
157
-
158
- const brandFromParts = findBrandInParts(parts);
159
- if (brandFromParts) return brandFromParts;
160
-
161
- // Use first part as brand if it looks brand-like
162
- if (firstPart && !VERSION_RE.test(firstPart)) {
163
- return { familyId: firstPart, familyName: capitalize(firstPart) };
164
- }
165
-
166
- // First non-version, non-skip part
167
- const nonVersion = parts.find(
168
- (p) => p && !VERSION_RE.test(p) && !SKIP_PARTS.has(p),
169
- );
170
- if (nonVersion) {
171
- return { familyId: nonVersion, familyName: capitalize(nonVersion) };
172
- }
173
-
174
- return {
175
- familyId: firstPart || id,
176
- familyName: capitalize(firstPart || id),
177
- };
178
- }
179
-
180
- /**
181
- * Normalize a model name for comparison by removing provider-specific suffixes
182
- * and common qualifiers. This helps detect when the same model is offered by
183
- * multiple providers with slightly different naming.
184
- *
185
- * Uses string operations instead of regex backtracking to avoid ReDoS warnings.
186
- */
187
- export function normalizeModelName(name: string): string {
188
- const suffixes = ["(free)", "(cline)", "-free", "free"];
189
- let normalized = name.toLowerCase().trimEnd();
190
-
191
- // Remove common literal suffixes — simple string ops, no regex backtracking
192
- for (const suffix of suffixes) {
193
- while (normalized.endsWith(suffix)) {
194
- normalized = normalized.slice(0, -suffix.length).trimEnd();
195
- }
196
- }
197
-
198
- // CI score suffix — regex with disjoint char classes (linear)
199
- // Anchored with $, matches at most once → .replace() is correct (S4144 N/A)
200
- normalized = normalized.replace(/\(ci:\s*[\d.]+\)$/, "").trimEnd();
201
- normalized = normalized.replace(/\[ci:\s*[\d.]+\]$/, "").trimEnd();
202
-
203
- // Remove any trailing parenthetical — non-regex loop
204
- while (normalized.endsWith(")")) {
205
- const idx = normalized.lastIndexOf("(", normalized.length - 1);
206
- if (idx === -1) break;
207
- normalized = normalized.slice(0, idx).trimEnd();
208
- }
209
-
210
- return normalized.trim();
211
- }
212
-
213
- /**
214
- * Try to merge a model into another family if its normalized name
215
- * matches a model in a different family.
216
- */
217
- function tryMergeFamily(
218
- byFamily: Map<string, ModelInfo[]>,
219
- nameToFamilyId: Map<string, string>,
220
- familyId: string,
221
- model: ModelInfo,
222
- ): boolean {
223
- const normalizedName = normalizeModelName(model.name || model.id);
224
- if (!normalizedName) return false;
225
-
226
- const existingFamilyForName = nameToFamilyId.get(normalizedName);
227
- if (!existingFamilyForName || existingFamilyForName === familyId) {
228
- nameToFamilyId.set(normalizedName, familyId);
229
- return false;
230
- }
231
-
232
- // Same model name found in different family - merge them
233
- const targetFamily = byFamily.get(existingFamilyForName);
234
- const sourceFamily = byFamily.get(familyId);
235
- if (!targetFamily || !sourceFamily) return false;
236
-
237
- targetFamily.push(...sourceFamily);
238
- byFamily.delete(familyId);
239
- return true;
240
- }
241
-
242
- /**
243
- * Build a sorted list of ModelFamily from a by-family grouping map.
244
- */
245
- function buildFamiliesList(byFamily: Map<string, ModelInfo[]>): ModelFamily[] {
246
- const families: ModelFamily[] = [];
247
- for (const [id, familyModels] of byFamily) {
248
- const firstModel = familyModels[0]!;
249
- const familyInfo = detectModelFamily(firstModel)!;
250
-
251
- families.push({
252
- id,
253
- displayName: familyInfo.familyName,
254
- models: familyModels.sort(
255
- (a, b) =>
256
- a.provider.localeCompare(b.provider) || b.id.localeCompare(a.id),
257
- ),
258
- });
259
- }
260
-
261
- return families.sort((a, b) => a.displayName.localeCompare(b.displayName));
262
- }
263
-
264
- /**
265
- * Get all model families from a list of models.
266
- * Groups models by family and merges same-name models across providers.
267
- */
268
- export function getModelFamilies(models: ModelInfo[]): ModelFamily[] {
269
- const byFamily = new Map<string, ModelInfo[]>();
270
- const nameToFamilyId = new Map<string, string>();
271
-
272
- // First pass: group models by detected family
273
- for (const model of models) {
274
- const family = detectModelFamily(model);
275
- if (!family) continue;
276
-
277
- const existing = byFamily.get(family.familyId) ?? [];
278
- existing.push(model);
279
- byFamily.set(family.familyId, existing);
280
- }
281
-
282
- // Second pass: merge families whose models have the same normalized name
283
- const familyIds = [...byFamily.keys()];
284
- for (const familyId of familyIds) {
285
- const familyModels = byFamily.get(familyId);
286
- if (!familyModels) continue;
287
-
288
- for (const model of familyModels) {
289
- if (tryMergeFamily(byFamily, nameToFamilyId, familyId, model)) {
290
- break;
291
- }
292
- }
293
- }
294
-
295
- return buildFamiliesList(byFamily);
296
- }
1
+ /**
2
+ * Model detection utilities for pi-free-providers.
3
+ * Extracts and adapts model family detection from pi-models.
4
+ * Used for failover when providers hit rate limits.
5
+ */
6
+
7
+ import type { Model } from "@earendil-works/pi-ai";
8
+ import type { ProviderModelConfig } from "./types.ts";
9
+
10
+ export interface ModelInfo {
11
+ id: string;
12
+ name?: string;
13
+ provider: string;
14
+ isFree: boolean;
15
+ inputCost: number;
16
+ outputCost: number;
17
+ }
18
+
19
+ export interface ModelFamily {
20
+ id: string; // Normalized family ID (e.g., "claude-sonnet")
21
+ displayName: string; // Human readable (e.g., "Claude Sonnet")
22
+ models: ModelInfo[]; // All models in this family
23
+ }
24
+
25
+ /**
26
+ * Convert Pi's Model type to ModelInfo for internal use
27
+ */
28
+ export function toModelInfo(model: Model<any>): ModelInfo {
29
+ return {
30
+ id: model.id,
31
+ name: model.name,
32
+ provider: model.provider,
33
+ isFree: !model.cost || (model.cost.input === 0 && model.cost.output === 0),
34
+ inputCost: model.cost?.input ?? 0,
35
+ outputCost: model.cost?.output ?? 0,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Convert ProviderModelConfig to ModelInfo for internal use
41
+ */
42
+ export function toProviderModelInfo(model: ProviderModelConfig): ModelInfo {
43
+ return {
44
+ id: model.id,
45
+ name: model.name,
46
+ provider: "", // Will be set by caller
47
+ isFree: !model.cost || (model.cost.input === 0 && model.cost.output === 0),
48
+ inputCost: model.cost?.input ?? 0,
49
+ outputCost: model.cost?.output ?? 0,
50
+ };
51
+ }
52
+
53
+ // =============================================================================
54
+ // Shared helpers for model family detection
55
+ // =============================================================================
56
+
57
+ const VERSION_RE = /^v?\d+(\.\d+)?$/;
58
+ const ROUTER_RE = /\b(?:router|auto)\b/;
59
+ const SKIP_PARTS = new Set([
60
+ "latest",
61
+ "preview",
62
+ "rc",
63
+ "beta",
64
+ "alpha",
65
+ "dev",
66
+ "free",
67
+ ]);
68
+
69
+ interface BrandMapping {
70
+ keywords: string[];
71
+ familyId: string;
72
+ familyName: string;
73
+ }
74
+
75
+ const BRAND_MAPPINGS: BrandMapping[] = [
76
+ { keywords: ["claude"], familyId: "claude", familyName: "Claude" },
77
+ { keywords: ["deepseek"], familyId: "deepseek", familyName: "DeepSeek" },
78
+ { keywords: ["gemini"], familyId: "gemini", familyName: "Gemini" },
79
+ { keywords: ["gpt"], familyId: "gpt", familyName: "GPT" },
80
+ { keywords: ["llama"], familyId: "llama", familyName: "Llama" },
81
+ { keywords: ["minimax"], familyId: "minimax", familyName: "MiniMax" },
82
+ { keywords: ["qwen"], familyId: "qwen", familyName: "Qwen" },
83
+ { keywords: ["nemotron"], familyId: "nemotron", familyName: "Nemotron" },
84
+ { keywords: ["kimi", "moonshot"], familyId: "kimi", familyName: "Kimi" },
85
+ { keywords: ["glm", "chatglm"], familyId: "glm", familyName: "GLM" },
86
+ { keywords: ["mistral"], familyId: "mistral", familyName: "Mistral" },
87
+ { keywords: ["arcee", "trinity"], familyId: "arcee", familyName: "Arcee" },
88
+ { keywords: ["o1", "o3"], familyId: "openai-o", familyName: "OpenAI o" },
89
+ ];
90
+
91
+ const PROVIDER_MAPPINGS: Record<
92
+ string,
93
+ { familyId: string; familyName: string }
94
+ > = {
95
+ minimax: { familyId: "minimax", familyName: "MiniMax" },
96
+ minimaxai: { familyId: "minimax", familyName: "MiniMax" },
97
+ deepseek: { familyId: "deepseek", familyName: "DeepSeek" },
98
+ nvidia: { familyId: "nemotron", familyName: "Nemotron" },
99
+ moonshot: { familyId: "kimi", familyName: "Kimi" },
100
+ zhipu: { familyId: "glm", familyName: "GLM" },
101
+ };
102
+
103
+ function capitalize(s: string): string {
104
+ return s.charAt(0).toUpperCase() + s.slice(1);
105
+ }
106
+
107
+ function findBrandInText(
108
+ text: string,
109
+ ): { familyId: string; familyName: string } | null {
110
+ for (const mapping of BRAND_MAPPINGS) {
111
+ for (const keyword of mapping.keywords) {
112
+ if (text.includes(keyword)) {
113
+ return { familyId: mapping.familyId, familyName: mapping.familyName };
114
+ }
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+
120
+ function findBrandInParts(
121
+ parts: string[],
122
+ ): { familyId: string; familyName: string } | null {
123
+ for (const part of parts) {
124
+ const result = findBrandInText(part);
125
+ if (result) return result;
126
+ }
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Detect the model family from a model's ID or name.
132
+ * Returns the family ID and display name.
133
+ */
134
+ export function detectModelFamily(
135
+ model: ModelInfo,
136
+ ): { familyId: string; familyName: string } | null {
137
+ const id = model.id.toLowerCase();
138
+ const name = (model.name || "").toLowerCase();
139
+ const fullText = `${id} ${name}`;
140
+
141
+ // Router models (gateways to free models) - group into "other"
142
+ if (ROUTER_RE.test(fullText) || id === "kilo-auto/free") {
143
+ return { familyId: "other", familyName: "Other" };
144
+ }
145
+
146
+ // Known brand keywords in full text
147
+ const brandFromText = findBrandInText(fullText);
148
+ if (brandFromText) return brandFromText;
149
+
150
+ // Provider-specific fallbacks for models without brand in ID/name
151
+ const providerResult = PROVIDER_MAPPINGS[model.provider];
152
+ if (providerResult) return providerResult;
153
+
154
+ // Fallback: try to identify brand from model ID structure
155
+ const parts = id.split(/[-_:.@]/);
156
+ const firstPart = parts[0];
157
+
158
+ const brandFromParts = findBrandInParts(parts);
159
+ if (brandFromParts) return brandFromParts;
160
+
161
+ // Use first part as brand if it looks brand-like
162
+ if (firstPart && !VERSION_RE.test(firstPart)) {
163
+ return { familyId: firstPart, familyName: capitalize(firstPart) };
164
+ }
165
+
166
+ // First non-version, non-skip part
167
+ const nonVersion = parts.find(
168
+ (p) => p && !VERSION_RE.test(p) && !SKIP_PARTS.has(p),
169
+ );
170
+ if (nonVersion) {
171
+ return { familyId: nonVersion, familyName: capitalize(nonVersion) };
172
+ }
173
+
174
+ return {
175
+ familyId: firstPart || id,
176
+ familyName: capitalize(firstPart || id),
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Normalize a model name for comparison by removing provider-specific suffixes
182
+ * and common qualifiers. This helps detect when the same model is offered by
183
+ * multiple providers with slightly different naming.
184
+ *
185
+ * Uses string operations instead of regex backtracking to avoid ReDoS warnings.
186
+ */
187
+ export function normalizeModelName(name: string): string {
188
+ const suffixes = ["(free)", "(cline)", "-free", "free"];
189
+ let normalized = name.toLowerCase().trimEnd();
190
+
191
+ // Remove common literal suffixes — simple string ops, no regex backtracking
192
+ for (const suffix of suffixes) {
193
+ while (normalized.endsWith(suffix)) {
194
+ normalized = normalized.slice(0, -suffix.length).trimEnd();
195
+ }
196
+ }
197
+
198
+ // CI score suffix — regex with disjoint char classes (linear)
199
+ // Anchored with $, matches at most once → .replace() is correct (S4144 N/A)
200
+ normalized = normalized.replace(/\(ci:\s*[\d.]+\)$/, "").trimEnd();
201
+ normalized = normalized.replace(/\[ci:\s*[\d.]+\]$/, "").trimEnd();
202
+
203
+ // Remove any trailing parenthetical — non-regex loop
204
+ while (normalized.endsWith(")")) {
205
+ const idx = normalized.lastIndexOf("(", normalized.length - 1);
206
+ if (idx === -1) break;
207
+ normalized = normalized.slice(0, idx).trimEnd();
208
+ }
209
+
210
+ return normalized.trim();
211
+ }
212
+
213
+ /**
214
+ * Try to merge a model into another family if its normalized name
215
+ * matches a model in a different family.
216
+ */
217
+ function tryMergeFamily(
218
+ byFamily: Map<string, ModelInfo[]>,
219
+ nameToFamilyId: Map<string, string>,
220
+ familyId: string,
221
+ model: ModelInfo,
222
+ ): boolean {
223
+ const normalizedName = normalizeModelName(model.name || model.id);
224
+ if (!normalizedName) return false;
225
+
226
+ const existingFamilyForName = nameToFamilyId.get(normalizedName);
227
+ if (!existingFamilyForName || existingFamilyForName === familyId) {
228
+ nameToFamilyId.set(normalizedName, familyId);
229
+ return false;
230
+ }
231
+
232
+ // Same model name found in different family - merge them
233
+ const targetFamily = byFamily.get(existingFamilyForName);
234
+ const sourceFamily = byFamily.get(familyId);
235
+ if (!targetFamily || !sourceFamily) return false;
236
+
237
+ targetFamily.push(...sourceFamily);
238
+ byFamily.delete(familyId);
239
+ return true;
240
+ }
241
+
242
+ /**
243
+ * Build a sorted list of ModelFamily from a by-family grouping map.
244
+ */
245
+ function buildFamiliesList(byFamily: Map<string, ModelInfo[]>): ModelFamily[] {
246
+ const families: ModelFamily[] = [];
247
+ for (const [id, familyModels] of byFamily) {
248
+ const firstModel = familyModels[0]!;
249
+ const familyInfo = detectModelFamily(firstModel)!;
250
+
251
+ families.push({
252
+ id,
253
+ displayName: familyInfo.familyName,
254
+ models: familyModels.sort(
255
+ (a, b) =>
256
+ a.provider.localeCompare(b.provider) || b.id.localeCompare(a.id),
257
+ ),
258
+ });
259
+ }
260
+
261
+ return families.sort((a, b) => a.displayName.localeCompare(b.displayName));
262
+ }
263
+
264
+ /**
265
+ * Get all model families from a list of models.
266
+ * Groups models by family and merges same-name models across providers.
267
+ */
268
+ export function getModelFamilies(models: ModelInfo[]): ModelFamily[] {
269
+ const byFamily = new Map<string, ModelInfo[]>();
270
+ const nameToFamilyId = new Map<string, string>();
271
+
272
+ // First pass: group models by detected family
273
+ for (const model of models) {
274
+ const family = detectModelFamily(model);
275
+ if (!family) continue;
276
+
277
+ const existing = byFamily.get(family.familyId) ?? [];
278
+ existing.push(model);
279
+ byFamily.set(family.familyId, existing);
280
+ }
281
+
282
+ // Second pass: merge families whose models have the same normalized name
283
+ const familyIds = [...byFamily.keys()];
284
+ for (const familyId of familyIds) {
285
+ const familyModels = byFamily.get(familyId);
286
+ if (!familyModels) continue;
287
+
288
+ for (const model of familyModels) {
289
+ if (tryMergeFamily(byFamily, nameToFamilyId, familyId, model)) {
290
+ break;
291
+ }
292
+ }
293
+ }
294
+
295
+ return buildFamiliesList(byFamily);
296
+ }
@@ -45,7 +45,9 @@ function errorMessage(error: unknown): string {
45
45
  return error instanceof Error ? error.message : String(error);
46
46
  }
47
47
 
48
- async function fetchModelsDevCatalog(): Promise<Record<string, ModelsDevProvider>> {
48
+ async function fetchModelsDevCatalog(): Promise<
49
+ Record<string, ModelsDevProvider>
50
+ > {
49
51
  let lastError: unknown;
50
52
 
51
53
  for (let attempt = 1; attempt <= MODELS_DEV_RETRIES; attempt++) {
@@ -291,7 +293,9 @@ function enrichModel<T extends ProviderModelConfig>(
291
293
  ? (["text", "image"] as const)
292
294
  : model.input;
293
295
  const reasoning =
294
- ctx.enrichReasoning && modelMeta.reasoning === true ? true : model.reasoning;
296
+ ctx.enrichReasoning && modelMeta.reasoning === true
297
+ ? true
298
+ : model.reasoning;
295
299
  const thinkingLevelMap =
296
300
  ctx.enrichReasoning && model.thinkingLevelMap === undefined
297
301
  ? thinkingMapFromReasoningOptions(modelMeta.reasoning_options)
@@ -301,7 +305,10 @@ function enrichModel<T extends ProviderModelConfig>(
301
305
  ? (costFromModelsDev(modelMeta.cost) ?? model.cost)
302
306
  : model.cost;
303
307
  const compat = ctx.enrichCompat
304
- ? mergeCompat(model.compat, getProxyModelCompat(identityFromMeta(model, modelMeta)))
308
+ ? mergeCompat(
309
+ model.compat,
310
+ getProxyModelCompat(identityFromMeta(model, modelMeta)),
311
+ )
305
312
  : model.compat;
306
313
 
307
314
  const modelsDevMetadata: ModelMatchHints = {