@samrito/pi-cliproxyapi-provider 0.16.0 → 0.17.1
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/README.md +13 -0
- package/data/models-dev-fallback.json +1 -1
- package/package.json +1 -1
- package/src/matching.ts +207 -3
- package/src/provider.ts +5 -2
package/package.json
CHANGED
package/src/matching.ts
CHANGED
|
@@ -16,7 +16,17 @@ const CANONICAL_OWNER_PREFIXES: Record<string, string> = {
|
|
|
16
16
|
cohere: "cohere",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export type MetadataMatchMethod =
|
|
19
|
+
export type MetadataMatchMethod =
|
|
20
|
+
| "alias"
|
|
21
|
+
| "exact"
|
|
22
|
+
| "owner-prefix"
|
|
23
|
+
| "owner-hint"
|
|
24
|
+
| "suffix"
|
|
25
|
+
| "normalized-suffix"
|
|
26
|
+
| "provider-fallback"
|
|
27
|
+
| "vendor-prefix"
|
|
28
|
+
| "vendor-first-party"
|
|
29
|
+
| "vendor-hint";
|
|
20
30
|
|
|
21
31
|
export interface MetadataMatch {
|
|
22
32
|
metadataId: string;
|
|
@@ -59,16 +69,33 @@ function ownerHintMatch(
|
|
|
59
69
|
owner: string | undefined,
|
|
60
70
|
candidates: string[],
|
|
61
71
|
catalog: ModelsDevCatalog,
|
|
72
|
+
includeIdPath = false,
|
|
62
73
|
): string | undefined {
|
|
63
74
|
if (!owner) return undefined;
|
|
64
75
|
const ownerTokens = identifierTokens(owner);
|
|
76
|
+
const normalizedOwner = normalizeModelName(owner);
|
|
65
77
|
const matches = candidates.flatMap((metadataId) => {
|
|
66
78
|
const metadata = catalog[metadataId];
|
|
67
79
|
if (!metadata) return [];
|
|
80
|
+
const found = [];
|
|
68
81
|
const provider = sourceProvider(metadataId, metadata);
|
|
69
82
|
const providerTokens = identifierTokens(provider);
|
|
70
|
-
if (
|
|
71
|
-
|
|
83
|
+
if (containsContiguousTokens(ownerTokens, providerTokens)) {
|
|
84
|
+
found.push({ metadataId, tokenCount: providerTokens.length, characterCount: provider.length });
|
|
85
|
+
}
|
|
86
|
+
if (includeIdPath && normalizedOwner) {
|
|
87
|
+
// Reseller entries often keep the upstream provider in the metadata id
|
|
88
|
+
// path (e.g. `poe/xai/grok-3-mini` has id `xai/grok-3-mini`). Match the
|
|
89
|
+
// owner against those path segments as a last-resort vendor hint. The
|
|
90
|
+
// large token count ranks this above sourceProvider token matches.
|
|
91
|
+
const idPathSegments = metadata.id.split("/").slice(0, -1);
|
|
92
|
+
for (const segment of idPathSegments) {
|
|
93
|
+
if (normalizeModelName(segment) === normalizedOwner) {
|
|
94
|
+
found.push({ metadataId, tokenCount: 1000 + segment.length, characterCount: segment.length });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return found;
|
|
72
99
|
});
|
|
73
100
|
if (matches.length === 0) return undefined;
|
|
74
101
|
|
|
@@ -148,3 +175,180 @@ export function findMetadataMatch(
|
|
|
148
175
|
|
|
149
176
|
return undefined;
|
|
150
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Proxy-side variant suffixes that CLIProxyAPI appends to model IDs while
|
|
181
|
+
* models.dev publishes the base model. Stripping them lets `grok-4.7-build-fast`
|
|
182
|
+
* resolve to `xai/grok-4.7` and `gemini-3.6-flash-high` to
|
|
183
|
+
* `google/gemini-3.6-flash`. Ordered longest-first so compound suffixes strip
|
|
184
|
+
* one segment at a time.
|
|
185
|
+
*/
|
|
186
|
+
const VARIANT_SUFFIXES = [
|
|
187
|
+
"-non-reasoning",
|
|
188
|
+
"-reasoning",
|
|
189
|
+
"-thinking",
|
|
190
|
+
"-preview",
|
|
191
|
+
"-medium",
|
|
192
|
+
"-build",
|
|
193
|
+
"-high",
|
|
194
|
+
"-fast",
|
|
195
|
+
"-low",
|
|
196
|
+
] as const;
|
|
197
|
+
|
|
198
|
+
function stripVariantSuffix(name: string): string | undefined {
|
|
199
|
+
for (const suffix of VARIANT_SUFFIXES) {
|
|
200
|
+
if (name.endsWith(suffix) && name.length > suffix.length) return name.slice(0, -suffix.length);
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Candidate names to try when the primary matcher fails. For each stripped
|
|
207
|
+
* variant it also tries a `-preview` variant, because some proxies drop the
|
|
208
|
+
* `-preview` suffix that models.dev keeps (`gemini-3-flash` vs
|
|
209
|
+
* `google/gemini-3-flash-preview`).
|
|
210
|
+
*/
|
|
211
|
+
function modelNameCandidates(id: string): string[] {
|
|
212
|
+
const modelName = id.split("/").at(-1) ?? id;
|
|
213
|
+
const candidates: string[] = [];
|
|
214
|
+
const push = (name: string) => {
|
|
215
|
+
if (!candidates.includes(name)) candidates.push(name);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let current = modelName;
|
|
219
|
+
while (true) {
|
|
220
|
+
push(current);
|
|
221
|
+
if (!current.endsWith("-preview")) push(`${current}-preview`);
|
|
222
|
+
const stripped = stripVariantSuffix(current);
|
|
223
|
+
if (!stripped) break;
|
|
224
|
+
current = stripped;
|
|
225
|
+
}
|
|
226
|
+
return candidates;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const MODEL_NAME_PROVIDERS: ReadonlyArray<{ test: RegExp; provider: string }> = [
|
|
230
|
+
{ test: /^claude/, provider: "anthropic" },
|
|
231
|
+
{ test: /^(gemini|gemma|veo|imagen|nano-banana|lyria)/, provider: "google" },
|
|
232
|
+
{ test: /^(gpt-|o1|o3|o4|chatgpt|dall-e|sora|text-embedding|whisper|tts)/, provider: "openai" },
|
|
233
|
+
{ test: /^deepseek/, provider: "deepseek" },
|
|
234
|
+
{ test: /^grok/, provider: "xai" },
|
|
235
|
+
{ test: /^(mistral|codestral|mixtral|ministral|pixtral)/, provider: "mistral" },
|
|
236
|
+
{ test: /^glm/, provider: "zhipuai" },
|
|
237
|
+
{ test: /^qwen/, provider: "alibaba" },
|
|
238
|
+
{ test: /^(kimi|moonshot)/, provider: "moonshotai" },
|
|
239
|
+
{ test: /^(minimax|abab)/, provider: "minimax" },
|
|
240
|
+
{ test: /^nemotron/, provider: "nvidia" },
|
|
241
|
+
{ test: /^command-r/, provider: "cohere" },
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Infer the canonical provider from a model name. This is used only when the
|
|
246
|
+
* primary matcher fails, so it cannot steal matches from the stricter,
|
|
247
|
+
* owner-driven resolution; it exists to rescue proxy-renamed models whose
|
|
248
|
+
* `owned_by` is missing, generic, or misleading (`deepseek-flash` reported as
|
|
249
|
+
* `openai`, for example).
|
|
250
|
+
*/
|
|
251
|
+
function canonicalProviderForModelName(modelName: string): string | undefined {
|
|
252
|
+
const lower = modelName.toLowerCase();
|
|
253
|
+
return MODEL_NAME_PROVIDERS.find(({ test }) => test.test(lower))?.provider;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function matchCandidateWithInference(
|
|
257
|
+
cpaModel: Pick<CpaModel, "id" | "owned_by">,
|
|
258
|
+
candidate: string,
|
|
259
|
+
catalog: ModelsDevCatalog,
|
|
260
|
+
fallbackProvider?: string | null,
|
|
261
|
+
): MetadataMatch | undefined {
|
|
262
|
+
if (catalog[candidate]) {
|
|
263
|
+
return { metadataId: candidate, metadata: catalog[candidate], method: "exact" };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const keys = Object.keys(catalog);
|
|
267
|
+
const exactMetadataCandidates = keys.filter((key) => catalog[key]?.id === candidate);
|
|
268
|
+
const exactMetadataKey = oneMatch(exactMetadataCandidates);
|
|
269
|
+
if (exactMetadataKey) {
|
|
270
|
+
return { metadataId: exactMetadataKey, metadata: catalog[exactMetadataKey], method: "exact" };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const suffixCandidates = keys.filter(
|
|
274
|
+
(key) => metadataModelName(key, catalog[key]) === candidate,
|
|
275
|
+
);
|
|
276
|
+
const normalizedSuffixCandidates = keys.filter(
|
|
277
|
+
(key) => normalizeModelName(metadataModelName(key, catalog[key])) === normalizeModelName(candidate),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const vendor = canonicalProviderForModelName(candidate);
|
|
281
|
+
if (vendor) {
|
|
282
|
+
const vendorKey = `${vendor}/${candidate}`;
|
|
283
|
+
if (catalog[vendorKey]) {
|
|
284
|
+
return { metadataId: vendorKey, metadata: catalog[vendorKey], method: "vendor-prefix" };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (vendor) {
|
|
289
|
+
const firstParty = suffixCandidates.filter((key) => sourceProvider(key, catalog[key]) === vendor);
|
|
290
|
+
const firstPartyKey = oneMatch(firstParty);
|
|
291
|
+
if (firstPartyKey) {
|
|
292
|
+
return { metadataId: firstPartyKey, metadata: catalog[firstPartyKey], method: "vendor-first-party" };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const owner = cpaModel.owned_by?.trim().toLowerCase();
|
|
297
|
+
const hintedKey = ownerHintMatch(owner, normalizedSuffixCandidates, catalog, true);
|
|
298
|
+
if (hintedKey) {
|
|
299
|
+
return { metadataId: hintedKey, metadata: catalog[hintedKey], method: "vendor-hint" };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const suffixKey = oneMatch(suffixCandidates);
|
|
303
|
+
if (suffixKey) {
|
|
304
|
+
return { metadataId: suffixKey, metadata: catalog[suffixKey], method: "suffix" };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const normalizedSuffixKey = oneMatch(normalizedSuffixCandidates);
|
|
308
|
+
if (normalizedSuffixKey) {
|
|
309
|
+
return { metadataId: normalizedSuffixKey, metadata: catalog[normalizedSuffixKey], method: "normalized-suffix" };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (fallbackProvider) {
|
|
313
|
+
const normalizedFallbackProvider = fallbackProvider.trim().toLowerCase();
|
|
314
|
+
const fallbackKey = oneMatch(normalizedSuffixCandidates.filter((metadataId) => {
|
|
315
|
+
const metadata = catalog[metadataId];
|
|
316
|
+
return metadata && sourceProvider(metadataId, metadata).toLowerCase() === normalizedFallbackProvider;
|
|
317
|
+
}));
|
|
318
|
+
if (fallbackKey) {
|
|
319
|
+
return { metadataId: fallbackKey, metadata: catalog[fallbackKey], method: "provider-fallback" };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Resolve metadata when the primary matcher fails, by stripping proxy variant
|
|
328
|
+
* suffixes and inferring the vendor from the model name. This is deliberately a
|
|
329
|
+
* fallback: models the primary matcher resolves keep their existing, more
|
|
330
|
+
* conservative match source.
|
|
331
|
+
*/
|
|
332
|
+
export function inferMetadataMatch(
|
|
333
|
+
cpaModel: Pick<CpaModel, "id" | "owned_by">,
|
|
334
|
+
catalog: ModelsDevCatalog,
|
|
335
|
+
fallbackProvider?: string | null,
|
|
336
|
+
): MetadataMatch | undefined {
|
|
337
|
+
for (const candidate of modelNameCandidates(cpaModel.id)) {
|
|
338
|
+
const match = matchCandidateWithInference(cpaModel, candidate, catalog, fallbackProvider);
|
|
339
|
+
if (match) return match;
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function resolveMetadataMatch(
|
|
345
|
+
cpaModel: Pick<CpaModel, "id" | "owned_by">,
|
|
346
|
+
catalog: ModelsDevCatalog,
|
|
347
|
+
aliases: Record<string, string>,
|
|
348
|
+
fallbackProvider?: string | null,
|
|
349
|
+
): MetadataMatch | undefined {
|
|
350
|
+
return (
|
|
351
|
+
findMetadataMatch(cpaModel, catalog, aliases, fallbackProvider)
|
|
352
|
+
?? inferMetadataMatch(cpaModel, catalog, fallbackProvider)
|
|
353
|
+
);
|
|
354
|
+
}
|
package/src/provider.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CpaModel } from "./cpa.ts";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveMetadataMatch, type MetadataMatchMethod } from "./matching.ts";
|
|
3
3
|
import { getModelApiOverride, isGpt56Model, type ModelApiContext } from "./model-api.ts";
|
|
4
4
|
import { getModelCapabilityOverrides } from "./model-capabilities.ts";
|
|
5
5
|
import { thinkingLevelMapFromReasoningOptions } from "./reasoning-levels.ts";
|
|
@@ -139,6 +139,9 @@ function emptyMatchMethods(): Record<MetadataMatchMethod, number> {
|
|
|
139
139
|
suffix: 0,
|
|
140
140
|
"normalized-suffix": 0,
|
|
141
141
|
"provider-fallback": 0,
|
|
142
|
+
"vendor-prefix": 0,
|
|
143
|
+
"vendor-first-party": 0,
|
|
144
|
+
"vendor-hint": 0,
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
|
|
@@ -173,7 +176,7 @@ export function buildProviderModels(
|
|
|
173
176
|
let enriched = 0;
|
|
174
177
|
|
|
175
178
|
const models = cpaModels.map((cpaModel) => {
|
|
176
|
-
const match =
|
|
179
|
+
const match = resolveMetadataMatch(cpaModel, catalog, aliases, metadataFallbackProvider);
|
|
177
180
|
if (!match) {
|
|
178
181
|
unmatchedModelIds.push(cpaModel.id);
|
|
179
182
|
return applyModelOverride(defaultModel(cpaModel, gpt56ContextWindow), overrides);
|