@oh-my-pi/pi-catalog 17.4.0 → 17.4.2
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/CHANGELOG.md +32 -0
- package/LICENSE +22 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/discovery/cursor-proto.d.ts +6 -0
- package/dist/types/identity/family.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/model-manager.d.ts +5 -5
- package/dist/types/provider-models/cache-provider-id.d.ts +2 -0
- package/dist/types/provider-models/descriptors.d.ts +1 -1
- package/dist/types/provider-models/openai-compat.d.ts +3 -1
- package/dist/types/types.d.ts +3 -1
- package/dist/types/variant-collapse.d.ts +19 -17
- package/dist/types/wire/codex.d.ts +21 -0
- package/dist/types/wire/image-fetchers.d.ts +76 -0
- package/package.json +7 -5
- package/src/compat/openai.ts +1 -1
- package/src/discovery/codex.ts +4 -0
- package/src/discovery/cursor-proto.ts +7 -0
- package/src/identity/family.ts +10 -0
- package/src/index.ts +1 -0
- package/src/model-cache.ts +3 -3
- package/src/model-manager.ts +5 -5
- package/src/models.json +599 -1651
- package/src/provider-models/cache-provider-id.ts +15 -2
- package/src/provider-models/google.ts +8 -6
- package/src/provider-models/openai-compat.ts +151 -48
- package/src/types.ts +3 -1
- package/src/variant-collapse.ts +332 -52
- package/src/wire/codex.ts +54 -0
- package/src/wire/image-fetchers.ts +159 -0
|
@@ -5,6 +5,17 @@ export interface ModelCacheProviderIdOptions {
|
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS: Readonly<Record<string, true>> = {
|
|
9
|
+
"opencode-go": true,
|
|
10
|
+
"opencode-zen": true,
|
|
11
|
+
"github-copilot": true,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Whether a provider's model-cache namespace requires its resolved credential. */
|
|
15
|
+
export function isCredentialScopedModelCacheProvider(providerId: string): boolean {
|
|
16
|
+
return CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS[providerId] === true;
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
export function getDefaultModelDiscoveryBaseUrl(providerId: string): string | undefined {
|
|
9
20
|
switch (providerId) {
|
|
10
21
|
case "ollama":
|
|
@@ -48,15 +59,17 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
48
59
|
return "cursor:max-mode-v3";
|
|
49
60
|
case "litellm": {
|
|
50
61
|
const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
|
|
51
|
-
return `litellm:rich-
|
|
62
|
+
return `litellm:rich-v6:${Bun.hash(baseUrl).toString(36)}`;
|
|
52
63
|
}
|
|
53
64
|
case "opencode-go":
|
|
54
65
|
case "opencode-zen": {
|
|
66
|
+
// v2: muse-spark-1.2 rows cached before the reasoning/thinking
|
|
67
|
+
// recovery carry `reasoning: false` and must be refetched.
|
|
55
68
|
const configuredBaseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
|
|
56
69
|
const trimmedBaseUrl = configuredBaseUrl.endsWith("/") ? configuredBaseUrl.slice(0, -1) : configuredBaseUrl;
|
|
57
70
|
const discoveryBaseUrl = trimmedBaseUrl.endsWith("/v1") ? trimmedBaseUrl : `${trimmedBaseUrl}/v1`;
|
|
58
71
|
const scope = `${options.apiKey ?? ""}\u0000${discoveryBaseUrl}`;
|
|
59
|
-
return `${providerId}:models-
|
|
72
|
+
return `${providerId}:models-v2:${Bun.hash(scope).toString(36)}`;
|
|
60
73
|
}
|
|
61
74
|
case "github-copilot": {
|
|
62
75
|
// Copilot model specs bake in the plan-specific endpoint (personal vs
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetchAntigravityDiscoveryModels } from "../discovery/antigravity";
|
|
2
2
|
import { fetchGeminiModels } from "../discovery/gemini";
|
|
3
|
+
import { isGeminiModelId } from "../identity/family";
|
|
3
4
|
import type { ModelManagerOptions } from "../model-manager";
|
|
4
5
|
import type { FetchImpl } from "../types";
|
|
5
6
|
import { GEMINI_CLI_VARIANT_COLLAPSE_TABLE } from "../variant-collapse";
|
|
@@ -88,18 +89,19 @@ export function googleGeminiCliModelManagerOptions(
|
|
|
88
89
|
fetchDynamicModels: async () => {
|
|
89
90
|
const models = await fetchAntigravityDiscoveryModels({
|
|
90
91
|
token,
|
|
91
|
-
endpoint,
|
|
92
92
|
fetcher: toDiscoveryFetch(config?.fetch),
|
|
93
93
|
collapseTable: GEMINI_CLI_VARIANT_COLLAPSE_TABLE,
|
|
94
94
|
});
|
|
95
95
|
if (models === null) {
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
|
-
return models
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
return models
|
|
99
|
+
.filter(m => isGeminiModelId(m.id))
|
|
100
|
+
.map(m => ({
|
|
101
|
+
...m,
|
|
102
|
+
provider: "google-gemini-cli" as const,
|
|
103
|
+
baseUrl: endpoint,
|
|
104
|
+
}));
|
|
103
105
|
},
|
|
104
106
|
}
|
|
105
107
|
: undefined),
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isGrokReasoningEffortCapable,
|
|
17
17
|
isKimiK3ModelId,
|
|
18
18
|
isKimiModelId,
|
|
19
|
+
isMuseSparkModelId,
|
|
19
20
|
isQwen38PlusTemplateEffortModelId,
|
|
20
21
|
isReasoningGlmModelId,
|
|
21
22
|
} from "../identity/family";
|
|
@@ -541,12 +542,15 @@ const OPENAI_NON_RESPONSES_PREFIXES = [
|
|
|
541
542
|
"gpt-realtime",
|
|
542
543
|
] as const;
|
|
543
544
|
|
|
544
|
-
function isLikelyOpenAIResponsesModelId(
|
|
545
|
+
function isLikelyOpenAIResponsesModelId(
|
|
546
|
+
id: string,
|
|
547
|
+
references?: ReadonlyMap<string, ModelSpec<"openai-responses">>,
|
|
548
|
+
): boolean {
|
|
545
549
|
const trimmed = id.trim();
|
|
546
550
|
if (!trimmed) {
|
|
547
551
|
return false;
|
|
548
552
|
}
|
|
549
|
-
if (references
|
|
553
|
+
if (references?.has(trimmed)) {
|
|
550
554
|
return true;
|
|
551
555
|
}
|
|
552
556
|
const normalized = trimmed.toLowerCase();
|
|
@@ -2621,6 +2625,26 @@ function openCodeModelManagerOptions(
|
|
|
2621
2625
|
fallbackApi(defaults.id, base) ??
|
|
2622
2626
|
defaults.api;
|
|
2623
2627
|
const baseUrl = openCodeBaseUrlForApi(api, basePath);
|
|
2628
|
+
if (isMuseSparkModelId(defaults.id)) {
|
|
2629
|
+
// Gateway lists these as bare ids with no capability
|
|
2630
|
+
// metadata and no local bundled row, so the generic
|
|
2631
|
+
// defaults would hide the effort dial
|
|
2632
|
+
// (`reasoning: false`). Keep the pinned/fallback route
|
|
2633
|
+
// and restore the documented thinking surface.
|
|
2634
|
+
return {
|
|
2635
|
+
...(reference ?? defaults),
|
|
2636
|
+
id: defaults.id,
|
|
2637
|
+
name,
|
|
2638
|
+
api,
|
|
2639
|
+
provider: providerId,
|
|
2640
|
+
baseUrl,
|
|
2641
|
+
reasoning: true,
|
|
2642
|
+
input: reference?.input ?? ["text", "image"],
|
|
2643
|
+
thinking: reference?.thinking ?? META_MUSE_SPARK_THINKING,
|
|
2644
|
+
contextWindow: toPositiveNumber(entry.context_length, reference?.contextWindow ?? 1_048_576),
|
|
2645
|
+
maxTokens: toPositiveNumber(entry.max_completion_tokens, reference?.maxTokens ?? 131_072),
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2624
2648
|
if (!reference) {
|
|
2625
2649
|
return { ...defaults, name, api, baseUrl };
|
|
2626
2650
|
}
|
|
@@ -3296,6 +3320,12 @@ export function vercelAiGatewayModelManagerOptions(
|
|
|
3296
3320
|
): ModelSpec<"anthropic-messages"> => {
|
|
3297
3321
|
const pricing = entry.pricing as Record<string, unknown> | undefined;
|
|
3298
3322
|
const tags = Array.isArray(entry.tags) ? (entry.tags as string[]) : [];
|
|
3323
|
+
const reportedMaxTokens = typeof entry.max_tokens === "number" ? entry.max_tokens : defaults.maxTokens;
|
|
3324
|
+
const modelId = typeof entry.id === "string" ? entry.id : defaults.id;
|
|
3325
|
+
const maxTokens =
|
|
3326
|
+
modelId === "meta/muse-spark-1.2-contributor" && typeof reportedMaxTokens === "number"
|
|
3327
|
+
? Math.min(reportedMaxTokens, 131_072)
|
|
3328
|
+
: reportedMaxTokens;
|
|
3299
3329
|
|
|
3300
3330
|
return {
|
|
3301
3331
|
...defaults,
|
|
@@ -3310,7 +3340,7 @@ export function vercelAiGatewayModelManagerOptions(
|
|
|
3310
3340
|
},
|
|
3311
3341
|
contextWindow:
|
|
3312
3342
|
typeof entry.context_window === "number" ? entry.context_window : defaults.contextWindow,
|
|
3313
|
-
maxTokens
|
|
3343
|
+
maxTokens,
|
|
3314
3344
|
};
|
|
3315
3345
|
},
|
|
3316
3346
|
fetch: config?.fetch,
|
|
@@ -4629,11 +4659,14 @@ export interface FetchLiteLLMRichModelsOptions<TApi extends Api> {
|
|
|
4629
4659
|
signal?: AbortSignal;
|
|
4630
4660
|
timeoutMs?: number;
|
|
4631
4661
|
referenceResolver?: (modelId: string) => ModelSpec<TApi> | undefined;
|
|
4662
|
+
resolveApi?: (entry: Record<string, unknown>, modelId: string) => TApi;
|
|
4632
4663
|
}
|
|
4633
4664
|
|
|
4634
4665
|
type LiteLLMRichModelEntry = Record<string, unknown>;
|
|
4666
|
+
type LiteLLMApiRoute = "openai" | "other" | "unknown";
|
|
4635
4667
|
type LiteLLMRichEndpointModel<TApi extends Api> = {
|
|
4636
4668
|
model: ModelSpec<TApi>;
|
|
4669
|
+
apiRoute: LiteLLMApiRoute;
|
|
4637
4670
|
supportsVision: unknown;
|
|
4638
4671
|
supportsReasoning: unknown;
|
|
4639
4672
|
hasContextWindow: boolean;
|
|
@@ -4714,14 +4747,15 @@ function toLiteLLMDisplayName(modelName: string | undefined, referenceName: stri
|
|
|
4714
4747
|
return referenceName ? stripLiteLLMResellerUsageSuffix(referenceName) : id;
|
|
4715
4748
|
}
|
|
4716
4749
|
|
|
4717
|
-
function mapLiteLLMOpenAICompatibleModel
|
|
4750
|
+
function mapLiteLLMOpenAICompatibleModel(
|
|
4718
4751
|
entry: OpenAICompatibleModelRecord,
|
|
4719
|
-
defaults: ModelSpec<
|
|
4720
|
-
reference: ModelSpec<
|
|
4721
|
-
): ModelSpec<
|
|
4752
|
+
defaults: ModelSpec<Api>,
|
|
4753
|
+
reference: ModelSpec<Api> | undefined,
|
|
4754
|
+
): ModelSpec<Api> {
|
|
4722
4755
|
const model = mapWithBundledReference(entry, defaults, reference);
|
|
4723
4756
|
return {
|
|
4724
4757
|
...model,
|
|
4758
|
+
api: resolveLiteLLMApi(undefined, model.id),
|
|
4725
4759
|
name: stripLiteLLMResellerUsageSuffix(model.name),
|
|
4726
4760
|
};
|
|
4727
4761
|
}
|
|
@@ -4808,6 +4842,55 @@ function getSupportedOpenAIParams(entry: LiteLLMRichModelEntry): string[] | unde
|
|
|
4808
4842
|
return value.flatMap(item => (typeof item === "string" ? [item] : []));
|
|
4809
4843
|
}
|
|
4810
4844
|
|
|
4845
|
+
function getLiteLLMProviders(entry: LiteLLMRichModelEntry): string[] | undefined {
|
|
4846
|
+
if (!Array.isArray(entry.providers)) {
|
|
4847
|
+
return undefined;
|
|
4848
|
+
}
|
|
4849
|
+
const providers = entry.providers.flatMap(provider => {
|
|
4850
|
+
const normalized = toNonEmptyString(provider)?.toLowerCase();
|
|
4851
|
+
return normalized ? [normalized] : [];
|
|
4852
|
+
});
|
|
4853
|
+
return providers.length > 0 ? providers : undefined;
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
function classifyLiteLLMApiRoute(entry: LiteLLMRichModelEntry | undefined, id: string): LiteLLMApiRoute {
|
|
4857
|
+
if (entry) {
|
|
4858
|
+
const providers = getLiteLLMProviders(entry);
|
|
4859
|
+
if (providers) {
|
|
4860
|
+
return providers.every(provider => provider === "openai") ? "openai" : "other";
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
const params = getLiteLLMParams(entry);
|
|
4864
|
+
const configuredProvider = toNonEmptyString(params?.custom_llm_provider)?.toLowerCase();
|
|
4865
|
+
if (configuredProvider) {
|
|
4866
|
+
return configuredProvider === "openai" ? "openai" : "other";
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
const backendModel = toNonEmptyString(params?.model);
|
|
4870
|
+
const backendSeparator = backendModel?.indexOf("/") ?? -1;
|
|
4871
|
+
if (backendModel && backendSeparator > 0) {
|
|
4872
|
+
return backendModel.slice(0, backendSeparator).toLowerCase() === "openai" ? "openai" : "other";
|
|
4873
|
+
}
|
|
4874
|
+
|
|
4875
|
+
const baseModel = toNonEmptyString(getLiteLLMMetadataValue(entry, "base_model"));
|
|
4876
|
+
const baseSeparator = baseModel?.indexOf("/") ?? -1;
|
|
4877
|
+
if (baseModel && baseSeparator > 0) {
|
|
4878
|
+
return baseModel.slice(0, baseSeparator).toLowerCase() === "openai" ? "openai" : "other";
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
const modelId = id.toLowerCase().startsWith("openai/") ? id.slice("openai/".length) : id;
|
|
4883
|
+
return isLikelyOpenAIResponsesModelId(modelId) ? "openai" : "unknown";
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
export function resolveLiteLLMApi(
|
|
4887
|
+
entry: Record<string, unknown> | undefined,
|
|
4888
|
+
id: string,
|
|
4889
|
+
fallbackApi: Api = "openai-completions",
|
|
4890
|
+
): Api {
|
|
4891
|
+
return classifyLiteLLMApiRoute(entry, id) === "openai" ? "openai-responses" : fallbackApi;
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4811
4894
|
function isLiteLLMUnusableSentinelPlaceholder(entry: LiteLLMRichModelEntry): boolean {
|
|
4812
4895
|
const modelGroup = toNonEmptyString(entry.model_group);
|
|
4813
4896
|
const id = toNonEmptyString(entry.id);
|
|
@@ -4900,7 +4983,7 @@ function mapLiteLLMRichEntry<TApi extends Api>(
|
|
|
4900
4983
|
return {
|
|
4901
4984
|
id,
|
|
4902
4985
|
name: toLiteLLMDisplayName(modelName, reference?.name, id),
|
|
4903
|
-
api: options.api,
|
|
4986
|
+
api: options.resolveApi?.(entry, id) ?? options.api,
|
|
4904
4987
|
provider: options.provider,
|
|
4905
4988
|
baseUrl: runtimeBaseUrl,
|
|
4906
4989
|
contextWindow,
|
|
@@ -4919,6 +5002,34 @@ function mapLiteLLMRichEntry<TApi extends Api>(
|
|
|
4919
5002
|
};
|
|
4920
5003
|
}
|
|
4921
5004
|
|
|
5005
|
+
function mergeLiteLLMRichEndpointModels<TApi extends Api>(
|
|
5006
|
+
existing: LiteLLMRichEndpointModel<TApi>,
|
|
5007
|
+
next: LiteLLMRichEndpointModel<TApi>,
|
|
5008
|
+
): LiteLLMRichEndpointModel<TApi> {
|
|
5009
|
+
const apiRoute =
|
|
5010
|
+
existing.apiRoute === "other" || next.apiRoute === "other"
|
|
5011
|
+
? "other"
|
|
5012
|
+
: existing.apiRoute === "openai" || next.apiRoute === "openai"
|
|
5013
|
+
? "openai"
|
|
5014
|
+
: "unknown";
|
|
5015
|
+
const api = next.apiRoute === apiRoute ? next.model.api : existing.model.api;
|
|
5016
|
+
const model: ModelSpec<TApi> = {
|
|
5017
|
+
...existing.model,
|
|
5018
|
+
api,
|
|
5019
|
+
name: next.model.name === next.model.id ? existing.model.name : next.model.name,
|
|
5020
|
+
contextWindow: next.hasContextWindow ? next.model.contextWindow : existing.model.contextWindow,
|
|
5021
|
+
maxTokens: next.hasMaxTokens ? next.model.maxTokens : existing.model.maxTokens,
|
|
5022
|
+
input: next.supportsVision === true || next.supportsVision === false ? next.model.input : existing.model.input,
|
|
5023
|
+
reasoning: typeof next.supportsReasoning === "boolean" ? next.model.reasoning : existing.model.reasoning,
|
|
5024
|
+
cost: next.hasCost ? next.model.cost : existing.model.cost,
|
|
5025
|
+
compat: next.hasSupportedOpenAIParams ? next.model.compat : existing.model.compat,
|
|
5026
|
+
};
|
|
5027
|
+
if (next.hasToolMetadata) {
|
|
5028
|
+
model.supportsTools = next.model.supportsTools;
|
|
5029
|
+
}
|
|
5030
|
+
return { ...next, apiRoute, model };
|
|
5031
|
+
}
|
|
5032
|
+
|
|
4922
5033
|
async function fetchLiteLLMRichEndpoint<TApi extends Api>(
|
|
4923
5034
|
endpoint: string,
|
|
4924
5035
|
options: FetchLiteLLMRichModelsOptions<TApi>,
|
|
@@ -4958,7 +5069,6 @@ async function fetchLiteLLMRichEndpoint<TApi extends Api>(
|
|
|
4958
5069
|
return null;
|
|
4959
5070
|
}
|
|
4960
5071
|
const deduped = new Map<string, LiteLLMRichEndpointModel<TApi>>();
|
|
4961
|
-
let incompleteVisionMetadata = false;
|
|
4962
5072
|
for (const entry of entries) {
|
|
4963
5073
|
const model = mapLiteLLMRichEntry(entry, options, runtimeBaseUrl);
|
|
4964
5074
|
if (model) {
|
|
@@ -4966,11 +5076,9 @@ async function fetchLiteLLMRichEndpoint<TApi extends Api>(
|
|
|
4966
5076
|
const supportsReasoning = getLiteLLMMetadataValue(entry, "supports_reasoning");
|
|
4967
5077
|
const supportsFunctionCalling = getLiteLLMMetadataValue(entry, "supports_function_calling");
|
|
4968
5078
|
const supportedOpenAIParams = getSupportedOpenAIParams(entry);
|
|
4969
|
-
|
|
4970
|
-
incompleteVisionMetadata = true;
|
|
4971
|
-
}
|
|
4972
|
-
deduped.set(model.id, {
|
|
5079
|
+
const next: LiteLLMRichEndpointModel<TApi> = {
|
|
4973
5080
|
model,
|
|
5081
|
+
apiRoute: classifyLiteLLMApiRoute(entry, model.id),
|
|
4974
5082
|
supportsVision,
|
|
4975
5083
|
supportsReasoning,
|
|
4976
5084
|
hasContextWindow: toPositiveNumber(getLiteLLMMetadataValue(entry, "max_input_tokens"), null) !== null,
|
|
@@ -4981,19 +5089,22 @@ async function fetchLiteLLMRichEndpoint<TApi extends Api>(
|
|
|
4981
5089
|
supportedOpenAIParams !== undefined,
|
|
4982
5090
|
hasSupportedOpenAIParams: supportedOpenAIParams !== undefined,
|
|
4983
5091
|
hasCost: getLiteLLMCost(entry) !== undefined,
|
|
4984
|
-
}
|
|
5092
|
+
};
|
|
5093
|
+
const existing = deduped.get(model.id);
|
|
5094
|
+
deduped.set(model.id, existing ? mergeLiteLLMRichEndpointModels(existing, next) : next);
|
|
4985
5095
|
}
|
|
4986
5096
|
}
|
|
4987
5097
|
if (deduped.size === 0) {
|
|
4988
5098
|
return null;
|
|
4989
5099
|
}
|
|
5100
|
+
const models = Array.from(deduped.values()).sort((left, right) => left.model.id.localeCompare(right.model.id));
|
|
4990
5101
|
return {
|
|
4991
|
-
models
|
|
4992
|
-
incompleteVisionMetadata,
|
|
5102
|
+
models,
|
|
5103
|
+
incompleteVisionMetadata: models.some(entry => entry.supportsVision !== true && entry.supportsVision !== false),
|
|
4993
5104
|
};
|
|
4994
5105
|
}
|
|
4995
5106
|
|
|
4996
|
-
|
|
5107
|
+
async function fetchLiteLLMRichModelsInternal<TApi extends Api>(
|
|
4997
5108
|
options: FetchLiteLLMRichModelsOptions<TApi>,
|
|
4998
5109
|
): Promise<ModelSpec<TApi>[] | null> {
|
|
4999
5110
|
const managementBaseUrl = normalizeLiteLLMManagementBaseUrl(options.baseUrl);
|
|
@@ -5033,32 +5144,19 @@ export async function fetchLiteLLMRichModels<TApi extends Api>(
|
|
|
5033
5144
|
}
|
|
5034
5145
|
continue;
|
|
5035
5146
|
}
|
|
5036
|
-
|
|
5037
|
-
...existing.model,
|
|
5038
|
-
name: next.model.name === next.model.id ? existing.model.name : next.model.name,
|
|
5039
|
-
contextWindow: next.hasContextWindow ? next.model.contextWindow : existing.model.contextWindow,
|
|
5040
|
-
maxTokens: next.hasMaxTokens ? next.model.maxTokens : existing.model.maxTokens,
|
|
5041
|
-
input:
|
|
5042
|
-
next.supportsVision === true || next.supportsVision === false
|
|
5043
|
-
? next.model.input
|
|
5044
|
-
: existing.model.input,
|
|
5045
|
-
reasoning: typeof next.supportsReasoning === "boolean" ? next.model.reasoning : existing.model.reasoning,
|
|
5046
|
-
cost: next.hasCost ? next.model.cost : existing.model.cost,
|
|
5047
|
-
compat: next.hasSupportedOpenAIParams ? next.model.compat : existing.model.compat,
|
|
5048
|
-
};
|
|
5049
|
-
if (next.hasToolMetadata) {
|
|
5050
|
-
model.supportsTools = next.model.supportsTools;
|
|
5051
|
-
}
|
|
5052
|
-
deduped.set(next.model.id, { ...next, model });
|
|
5147
|
+
deduped.set(next.model.id, mergeLiteLLMRichEndpointModels(existing, next));
|
|
5053
5148
|
}
|
|
5054
|
-
let
|
|
5149
|
+
let needsMoreMetadata = false;
|
|
5055
5150
|
for (const entry of deduped.values()) {
|
|
5056
|
-
if (
|
|
5057
|
-
|
|
5151
|
+
if (
|
|
5152
|
+
(entry.supportsVision !== true && entry.supportsVision !== false) ||
|
|
5153
|
+
(options.resolveApi !== undefined && entry.apiRoute === "unknown")
|
|
5154
|
+
) {
|
|
5155
|
+
needsMoreMetadata = true;
|
|
5058
5156
|
break;
|
|
5059
5157
|
}
|
|
5060
5158
|
}
|
|
5061
|
-
if (!
|
|
5159
|
+
if (!needsMoreMetadata) {
|
|
5062
5160
|
break;
|
|
5063
5161
|
}
|
|
5064
5162
|
}
|
|
@@ -5078,40 +5176,45 @@ export async function fetchLiteLLMRichModels<TApi extends Api>(
|
|
|
5078
5176
|
return options.timeoutMs !== undefined ? withCatalogDiscoveryTimeout(options.timeoutMs, fetchModels) : fetchModels();
|
|
5079
5177
|
}
|
|
5080
5178
|
|
|
5081
|
-
export function
|
|
5082
|
-
|
|
5083
|
-
):
|
|
5179
|
+
export async function fetchLiteLLMRichModels<TApi extends Api>(
|
|
5180
|
+
options: FetchLiteLLMRichModelsOptions<TApi>,
|
|
5181
|
+
): Promise<ModelSpec<TApi>[] | null> {
|
|
5182
|
+
return fetchLiteLLMRichModelsInternal(options);
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
export function litellmModelManagerOptions(config?: LiteLLMModelManagerConfig): ModelManagerOptions<Api> {
|
|
5084
5186
|
const apiKey = config?.apiKey;
|
|
5085
5187
|
const baseUrl = config?.baseUrl ?? getDefaultModelDiscoveryBaseUrl("litellm")!;
|
|
5086
5188
|
return {
|
|
5087
5189
|
providerId: "litellm",
|
|
5088
|
-
// rich-
|
|
5190
|
+
// rich-v6 invalidates rows cached before OpenAI models moved to Responses.
|
|
5089
5191
|
// Earlier versions added bundled reference fallback, continued discovery
|
|
5090
5192
|
// past incomplete `/model_group/info`, stripped reseller usage suffixes,
|
|
5091
|
-
//
|
|
5092
|
-
// whenever the mappers below change, or warm authoritative
|
|
5093
|
-
// serving pre-change rows for the full TTL.
|
|
5193
|
+
// filtered placeholder-only `all-team-models` rows, and mapped rich pricing.
|
|
5194
|
+
// Bump the version whenever the mappers below change, or warm authoritative
|
|
5195
|
+
// caches keep serving pre-change rows for the full TTL.
|
|
5094
5196
|
cacheProviderId: resolveModelCacheProviderId("litellm", { baseUrl }),
|
|
5095
5197
|
// litellm is a local-only proxy and is never bundled in models.json (that
|
|
5096
5198
|
// would leak the machine's localhost catalog). Prefer the proxy's richer
|
|
5097
5199
|
// management metadata, then enrich ids against models.dev with the bundled
|
|
5098
5200
|
// catalog as a fallback before using /v1/models.
|
|
5099
5201
|
fetchDynamicModels: async () => {
|
|
5100
|
-
const modelsDevReferences = await loadModelsDevReferences<
|
|
5202
|
+
const modelsDevReferences = await loadModelsDevReferences<Api>(config?.fetch);
|
|
5101
5203
|
const resolveReference = createReferenceResolver(modelsDevReferences);
|
|
5102
|
-
const richModels = await fetchLiteLLMRichModels({
|
|
5204
|
+
const richModels = await fetchLiteLLMRichModels<Api>({
|
|
5103
5205
|
api: "openai-completions",
|
|
5104
5206
|
provider: "litellm",
|
|
5105
5207
|
baseUrl,
|
|
5106
5208
|
apiKey,
|
|
5107
5209
|
fetch: config?.fetch,
|
|
5108
5210
|
referenceResolver: resolveReference,
|
|
5211
|
+
resolveApi: resolveLiteLLMApi,
|
|
5109
5212
|
timeoutMs: 10_000,
|
|
5110
5213
|
});
|
|
5111
5214
|
if (richModels && richModels.length > 0) {
|
|
5112
5215
|
return richModels;
|
|
5113
5216
|
}
|
|
5114
|
-
return fetchOpenAICompatibleModels({
|
|
5217
|
+
return fetchOpenAICompatibleModels<Api>({
|
|
5115
5218
|
api: "openai-completions",
|
|
5116
5219
|
provider: "litellm",
|
|
5117
5220
|
baseUrl,
|
package/src/types.ts
CHANGED
|
@@ -161,7 +161,7 @@ export type OpenAIReasoningDisableMode =
|
|
|
161
161
|
| "qwen-enable-thinking-false"
|
|
162
162
|
| "qwen-template-false";
|
|
163
163
|
|
|
164
|
-
export type OpenAIStreamMarkupHealingPattern = "kimi" | "dsml" | "thinking";
|
|
164
|
+
export type OpenAIStreamMarkupHealingPattern = "kimi" | "dsml" | "qwen" | "thinking";
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* Compatibility settings for openai-completions API.
|
|
@@ -965,6 +965,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
965
965
|
preferWebsockets?: boolean;
|
|
966
966
|
/** Codex Responses Lite transport: send the lite marker and carry instructions/tools as input items (mirrors codex-rs `use_responses_lite`). */
|
|
967
967
|
useResponsesLite?: boolean;
|
|
968
|
+
/** Codex Code Mode restriction: model expects tools routed through a programmatic exec surface (mirrors codex-rs `tool_mode`). */
|
|
969
|
+
toolMode?: "code_mode_only";
|
|
968
970
|
/** Preferred model to switch to when context promotion is triggered (model id or provider/id). */
|
|
969
971
|
contextPromotionTarget?: string;
|
|
970
972
|
/** Preferred model to use only for compaction (model id or provider/id); the active session model is unchanged. */
|