@linxiraos/pi-catalog 1.1.13 → 1.1.15
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 +2 -69
- package/README.md +20 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/models.d.ts +10 -6
- package/dist/types/pricing.d.ts +5 -0
- package/dist/types/provider-models/descriptor-types.d.ts +15 -0
- package/dist/types/provider-models/descriptors.d.ts +20 -0
- package/dist/types/provider-models/openai-compat.d.ts +44 -0
- package/dist/types/types.d.ts +43 -2
- package/dist/types/wire/charm-hyper.d.ts +21 -0
- package/dist/types/wire/github-copilot.d.ts +19 -0
- package/package.json +4 -4
- package/src/build.ts +16 -2
- package/src/compat/auth-ids.ts +4 -0
- package/src/compat/axes.ts +9 -0
- package/src/compat/resolve.ts +18 -4
- package/src/compat/rules/README.md +37 -0
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/charm-hyper.kdl +19 -0
- package/src/compat/rules/auth/commandcode.kdl +13 -0
- package/src/compat/rules/classes/anthropic.kdl +41 -0
- package/src/compat/rules/classes/deepseek.kdl +11 -0
- package/src/compat/rules/classes/minimax.kdl +4 -2
- package/src/compat/rules/providers/amazon-bedrock.kdl +7 -0
- package/src/compat/rules/providers/anthropic.kdl +8 -0
- package/src/compat/rules/providers/charm-hyper.kdl +82 -0
- package/src/compat/rules/providers/commandcode.kdl +601 -0
- package/src/compat/rules/providers/deepseek.kdl +83 -1
- package/src/compat/rules/providers/opencode-go.kdl +22 -0
- package/src/compat/rules/providers/opencode-zen.kdl +12 -0
- package/src/compat/rules/runtime/behavior.kdl +5 -0
- package/src/compat/rules.json +2143 -131
- package/src/model-manager.ts +27 -13
- package/src/models.json +12953 -5481
- package/src/models.ts +93 -19
- package/src/pricing.ts +136 -0
- package/src/provider-models/cache-provider-id.ts +28 -4
- package/src/provider-models/descriptor-types.ts +15 -0
- package/src/provider-models/descriptors.ts +36 -0
- package/src/provider-models/openai-compat.ts +297 -3
- package/src/types.ts +46 -1
- package/src/wire/charm-hyper.ts +26 -0
- package/src/wire/github-copilot.ts +26 -0
package/src/models.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { classifyModel } from "./compat/taxonomy";
|
|
2
2
|
import MODELS from "./models.json" with { type: "json" };
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Api,
|
|
5
|
+
EffectiveTokenCost,
|
|
6
|
+
KnownProvider,
|
|
7
|
+
Model,
|
|
8
|
+
ModelCost,
|
|
9
|
+
TimeBasedCost,
|
|
10
|
+
TokenCost,
|
|
11
|
+
Usage,
|
|
12
|
+
} from "./types";
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Static bundled model registry loaded from `models.json`.
|
|
@@ -48,38 +57,103 @@ export function getBundledModels(provider: GeneratedProvider): Model<Api>[] {
|
|
|
48
57
|
const models = getProviderModels(provider);
|
|
49
58
|
return models ? (Array.from(models.values()) as Model<Api>[]) : [];
|
|
50
59
|
}
|
|
51
|
-
function resolveTokenCost(cost: ModelCost, promptInputTokens: number): TokenCost {
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
function resolveTokenCost(cost: ModelCost, promptInputTokens: number, timestamp: number | undefined): TokenCost {
|
|
61
|
+
let rates: ModelCost | EffectiveTokenCost = cost;
|
|
62
|
+
let effectiveFrom = -Infinity;
|
|
63
|
+
if (timestamp !== undefined && cost.timeBased?.effectiveRates) {
|
|
64
|
+
for (const candidate of cost.timeBased.effectiveRates) {
|
|
65
|
+
if (candidate.effectiveFrom <= timestamp && candidate.effectiveFrom > effectiveFrom) {
|
|
66
|
+
rates = candidate;
|
|
67
|
+
effectiveFrom = candidate.effectiveFrom;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const longContext = rates.longContext;
|
|
72
|
+
if (!longContext) return rates;
|
|
54
73
|
const reachesThreshold =
|
|
55
74
|
promptInputTokens > longContext.inputThreshold ||
|
|
56
75
|
(longContext.inputThresholdInclusive === true && promptInputTokens === longContext.inputThreshold);
|
|
57
|
-
return reachesThreshold ? longContext :
|
|
76
|
+
return reachesThreshold ? longContext : rates;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isPeakPricingPeriod(schedule: TimeBasedCost, timestamp: number): boolean {
|
|
80
|
+
// Unix epoch was Thursday. Arithmetic keeps this UTC-only without allocating a Date.
|
|
81
|
+
const day = Math.floor(timestamp / 86_400_000);
|
|
82
|
+
const weekday = (((day + 4) % 7) + 7) % 7;
|
|
83
|
+
const minute = Math.floor((timestamp - day * 86_400_000) / 60_000);
|
|
84
|
+
for (const window of schedule.peakWindows) {
|
|
85
|
+
if (minute >= window.startMinute && minute < window.endMinute && window.weekdays.includes(weekday)) return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function timeBasedMultiplier(schedule: TimeBasedCost | undefined, timestamp: number | undefined): number {
|
|
91
|
+
if (!schedule || timestamp === undefined) return 1;
|
|
92
|
+
return isPeakPricingPeriod(schedule, timestamp) ? 1 : schedule.offPeakMultiplier;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Return the recurring UTC tariff period, independently of its monetary multiplier. */
|
|
96
|
+
export function getTimeBasedPricingPeriod(cost: ModelCost, timestamp?: number): "peak" | "off-peak" | undefined {
|
|
97
|
+
const schedule = cost.timeBased;
|
|
98
|
+
if (!schedule) return undefined;
|
|
99
|
+
return isPeakPricingPeriod(schedule, timestamp ?? Date.now()) ? "peak" : "off-peak";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Return the next actual peak/off-peak change strictly after the Unix-ms timestamp. */
|
|
103
|
+
export function getNextTimeBasedPricingTransition(cost: ModelCost, timestamp?: number): number | undefined {
|
|
104
|
+
const schedule = cost.timeBased;
|
|
105
|
+
if (!schedule) return undefined;
|
|
106
|
+
const now = timestamp ?? Date.now();
|
|
107
|
+
const firstDay = Math.floor(now / 86_400_000);
|
|
108
|
+
const horizon = now + 7 * 86_400_000;
|
|
109
|
+
let next = Infinity;
|
|
110
|
+
// All changes occur at window edges; one UTC week covers the entire recurrence.
|
|
111
|
+
for (let offset = 0; offset <= 7; offset++) {
|
|
112
|
+
const day = firstDay + offset;
|
|
113
|
+
const weekday = (((day + 4) % 7) + 7) % 7;
|
|
114
|
+
for (const window of schedule.peakWindows) {
|
|
115
|
+
if (!window.weekdays.includes(weekday)) continue;
|
|
116
|
+
for (let edge = 0; edge < 2; edge++) {
|
|
117
|
+
const minute = edge === 0 ? window.startMinute : window.endMinute;
|
|
118
|
+
const candidate = day * 86_400_000 + minute * 60_000;
|
|
119
|
+
if (candidate <= now || candidate > horizon || candidate >= next) continue;
|
|
120
|
+
// Overlapping/touching windows can hide an edge, including at midnight.
|
|
121
|
+
if (isPeakPricingPeriod(schedule, candidate - 1) !== isPeakPricingPeriod(schedule, candidate)) {
|
|
122
|
+
next = candidate;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return next === Infinity ? undefined : next;
|
|
58
128
|
}
|
|
59
129
|
|
|
60
|
-
/** Price a
|
|
61
|
-
export function calculateUncachedInputCost(cost: ModelCost, promptInputTokens: number): number {
|
|
62
|
-
const
|
|
63
|
-
|
|
130
|
+
/** Price a fully uncached prompt at its request timestamp (Unix ms); scheduled prices default to now. */
|
|
131
|
+
export function calculateUncachedInputCost(cost: ModelCost, promptInputTokens: number, timestamp?: number): number {
|
|
132
|
+
const pricingTimestamp = cost.timeBased ? (timestamp ?? Date.now()) : undefined;
|
|
133
|
+
const rates = resolveTokenCost(cost, promptInputTokens, pricingTimestamp);
|
|
134
|
+
return (rates.input / 1_000_000) * promptInputTokens * timeBasedMultiplier(cost.timeBased, pricingTimestamp);
|
|
64
135
|
}
|
|
65
136
|
|
|
66
|
-
/** Price
|
|
67
|
-
export function calculateUsageCost(cost: ModelCost, usage: Usage): Usage["cost"] {
|
|
137
|
+
/** Price usage at its request timestamp (Unix ms); only scheduled prices default to now. */
|
|
138
|
+
export function calculateUsageCost(cost: ModelCost, usage: Usage, timestamp?: number): Usage["cost"] {
|
|
68
139
|
const orchestration = usage.orchestration;
|
|
69
140
|
const promptInputTokens =
|
|
70
141
|
usage.input + usage.cacheRead + usage.cacheWrite + (orchestration?.input ?? 0) + (orchestration?.cacheRead ?? 0);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
usage.cost.
|
|
75
|
-
usage.cost.
|
|
142
|
+
const pricingTimestamp = cost.timeBased ? (timestamp ?? Date.now()) : undefined;
|
|
143
|
+
const rates = resolveTokenCost(cost, promptInputTokens, pricingTimestamp);
|
|
144
|
+
const multiplier = timeBasedMultiplier(cost.timeBased, pricingTimestamp);
|
|
145
|
+
usage.cost.input = (rates.input / 1000000) * (usage.input + (orchestration?.input ?? 0)) * multiplier;
|
|
146
|
+
usage.cost.output = (rates.output / 1000000) * (usage.output + (orchestration?.output ?? 0)) * multiplier;
|
|
147
|
+
usage.cost.cacheRead =
|
|
148
|
+
(rates.cacheRead / 1000000) * (usage.cacheRead + (orchestration?.cacheRead ?? 0)) * multiplier;
|
|
149
|
+
usage.cost.cacheWrite = cacheWriteCost(rates, usage) * multiplier;
|
|
76
150
|
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
77
151
|
return usage.cost;
|
|
78
152
|
}
|
|
79
153
|
|
|
80
|
-
/** Price
|
|
81
|
-
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
|
|
82
|
-
return calculateUsageCost(model.cost, usage);
|
|
154
|
+
/** Price usage at its request timestamp (Unix ms); preserve the resulting monetary amounts for display. */
|
|
155
|
+
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage, timestamp?: number): Usage["cost"] {
|
|
156
|
+
return calculateUsageCost(model.cost, usage, timestamp);
|
|
83
157
|
}
|
|
84
158
|
|
|
85
159
|
/**
|
package/src/pricing.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { EffectiveTokenCost, LongContextTokenCost, PeakPricingWindow, TimeBasedCost, TokenCost } from "./types";
|
|
2
|
+
import { isRecord } from "./utils";
|
|
3
|
+
|
|
4
|
+
function nonnegative(value: unknown): value is number {
|
|
5
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isRates(value: unknown): value is TokenCost {
|
|
9
|
+
return (
|
|
10
|
+
isRecord(value) &&
|
|
11
|
+
nonnegative(value.input) &&
|
|
12
|
+
nonnegative(value.output) &&
|
|
13
|
+
nonnegative(value.cacheRead) &&
|
|
14
|
+
nonnegative(value.cacheWrite)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isLongContext(value: unknown): value is LongContextTokenCost {
|
|
19
|
+
return (
|
|
20
|
+
isRecord(value) &&
|
|
21
|
+
isRates(value) &&
|
|
22
|
+
typeof value.inputThreshold === "number" &&
|
|
23
|
+
Number.isFinite(value.inputThreshold) &&
|
|
24
|
+
value.inputThreshold > 0 &&
|
|
25
|
+
(value.inputThresholdInclusive === undefined || typeof value.inputThresholdInclusive === "boolean")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isPeakWindow(value: unknown): value is PeakPricingWindow {
|
|
30
|
+
return (
|
|
31
|
+
isRecord(value) &&
|
|
32
|
+
Array.isArray(value.weekdays) &&
|
|
33
|
+
value.weekdays.length > 0 &&
|
|
34
|
+
value.weekdays.every(day => Number.isInteger(day) && day >= 0 && day <= 6) &&
|
|
35
|
+
new Set(value.weekdays).size === value.weekdays.length &&
|
|
36
|
+
typeof value.startMinute === "number" &&
|
|
37
|
+
Number.isInteger(value.startMinute) &&
|
|
38
|
+
typeof value.endMinute === "number" &&
|
|
39
|
+
Number.isInteger(value.endMinute) &&
|
|
40
|
+
value.startMinute >= 0 &&
|
|
41
|
+
value.endMinute <= 1440 &&
|
|
42
|
+
value.startMinute < value.endMinute
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isEffectiveRate(value: unknown): value is EffectiveTokenCost {
|
|
47
|
+
return (
|
|
48
|
+
isRecord(value) &&
|
|
49
|
+
isRates(value) &&
|
|
50
|
+
typeof value.effectiveFrom === "number" &&
|
|
51
|
+
Number.isSafeInteger(value.effectiveFrom) &&
|
|
52
|
+
Math.abs(value.effectiveFrom) <= 8_640_000_000_000_000 &&
|
|
53
|
+
(value.longContext === undefined || isLongContext(value.longContext))
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Validate serialized schedules before admitting cached model rows. */
|
|
58
|
+
export function isTimeBasedCost(value: unknown): value is TimeBasedCost {
|
|
59
|
+
if (
|
|
60
|
+
!isRecord(value) ||
|
|
61
|
+
!nonnegative(value.offPeakMultiplier) ||
|
|
62
|
+
!Array.isArray(value.peakWindows) ||
|
|
63
|
+
!value.peakWindows.every(isPeakWindow)
|
|
64
|
+
) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (value.effectiveRates === undefined) return true;
|
|
68
|
+
if (!Array.isArray(value.effectiveRates) || !value.effectiveRates.every(isEffectiveRate)) return false;
|
|
69
|
+
const dates = new Set<number>();
|
|
70
|
+
for (const rate of value.effectiveRates) {
|
|
71
|
+
if (dates.has(rate.effectiveFrom)) return false;
|
|
72
|
+
dates.add(rate.effectiveFrom);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function payload(value: unknown, keys: readonly string[], field: string): Record<string, unknown> {
|
|
78
|
+
if (!isRecord(value) || Object.keys(value).some(key => !keys.includes(key))) {
|
|
79
|
+
throw new Error(`Invalid time-based-cost ${field}`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function namedEntries(value: unknown, field: string): unknown[] {
|
|
85
|
+
if (!isRecord(value)) throw new Error(`Invalid time-based-cost ${field}: expected named objects`);
|
|
86
|
+
return Object.values(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** KDL object children are named records; normalize them once while constructing the model. */
|
|
90
|
+
export function materializeTimeBasedCost(value: unknown): TimeBasedCost {
|
|
91
|
+
const source = payload(value, ["offPeakMultiplier", "peakWindows", "effectiveRates"], "schedule");
|
|
92
|
+
const peakWindows = namedEntries(source.peakWindows, "peak-windows").map(entry => {
|
|
93
|
+
const window = payload(entry, ["weekdays", "startMinute", "endMinute"], "peak-window");
|
|
94
|
+
if (typeof window.weekdays !== "string" || !/^[0-6](,[0-6])*$/.test(window.weekdays)) {
|
|
95
|
+
throw new Error("Invalid time-based-cost weekdays: expected comma-separated UTC weekday numbers");
|
|
96
|
+
}
|
|
97
|
+
return { ...window, weekdays: window.weekdays.split(",").map(Number) };
|
|
98
|
+
});
|
|
99
|
+
const effectiveRates =
|
|
100
|
+
source.effectiveRates === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: namedEntries(source.effectiveRates, "effective-rates").map(entry => {
|
|
103
|
+
const rate = payload(
|
|
104
|
+
entry,
|
|
105
|
+
["effectiveFrom", "input", "output", "cacheRead", "cacheWrite", "longContext"],
|
|
106
|
+
"effective-rate",
|
|
107
|
+
);
|
|
108
|
+
const date = rate.effectiveFrom;
|
|
109
|
+
if (typeof date !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(date)) {
|
|
110
|
+
throw new Error("Invalid time-based-cost effective-from: expected an ISO UTC timestamp");
|
|
111
|
+
}
|
|
112
|
+
const effectiveFrom = Date.parse(date);
|
|
113
|
+
if (
|
|
114
|
+
!Number.isFinite(effectiveFrom) ||
|
|
115
|
+
new Date(effectiveFrom).toISOString() !== (date.includes(".") ? date : date.replace("Z", ".000Z"))
|
|
116
|
+
) {
|
|
117
|
+
throw new Error("Invalid time-based-cost effective-from: invalid UTC date");
|
|
118
|
+
}
|
|
119
|
+
if (rate.longContext !== undefined) {
|
|
120
|
+
payload(
|
|
121
|
+
rate.longContext,
|
|
122
|
+
["inputThreshold", "inputThresholdInclusive", "input", "output", "cacheRead", "cacheWrite"],
|
|
123
|
+
"long-context",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return { ...rate, effectiveFrom };
|
|
127
|
+
});
|
|
128
|
+
const schedule = {
|
|
129
|
+
offPeakMultiplier: source.offPeakMultiplier,
|
|
130
|
+
peakWindows,
|
|
131
|
+
...(effectiveRates && { effectiveRates }),
|
|
132
|
+
};
|
|
133
|
+
if (!isTimeBasedCost(schedule))
|
|
134
|
+
throw new Error("Invalid time-based-cost schedule: invalid windows, rates, or multiplier");
|
|
135
|
+
return schedule;
|
|
136
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CHARM_HYPER_API_BASE_URL, normalizeCharmHyperBaseUrl } from "../wire/charm-hyper";
|
|
1
2
|
import { PERSONAL_GITHUB_COPILOT_BASE_URL } from "../wire/github-copilot";
|
|
2
3
|
|
|
3
4
|
export interface ModelCacheProviderIdOptions {
|
|
@@ -19,6 +20,8 @@ export function isCredentialScopedModelCacheProvider(providerId: string): boolea
|
|
|
19
20
|
|
|
20
21
|
export function getDefaultModelDiscoveryBaseUrl(providerId: string): string | undefined {
|
|
21
22
|
switch (providerId) {
|
|
23
|
+
case "charm-hyper":
|
|
24
|
+
return CHARM_HYPER_API_BASE_URL;
|
|
22
25
|
case "meta":
|
|
23
26
|
case "muse-code":
|
|
24
27
|
return "https://api.meta.ai/v1";
|
|
@@ -62,6 +65,23 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
62
65
|
// carry `requestModelId: *-low`, which the Start plan refuses; refetch
|
|
63
66
|
// so the collapsed default is re-pointed to `-medium` (issue #9478).
|
|
64
67
|
return "cursor:default-effort-v4";
|
|
68
|
+
case "charm-hyper": {
|
|
69
|
+
// Discovery is authoritative for this gateway, so a warm cache is served
|
|
70
|
+
// for its full TTL without re-probing: the namespace must follow the
|
|
71
|
+
// configured endpoint, or a self-hosted proxy keeps serving the canonical
|
|
72
|
+
// host's roster, capabilities and tariffs until expiry.
|
|
73
|
+
//
|
|
74
|
+
// Endpoint-only scope is deliberate. `/v1/models` is public here, so the
|
|
75
|
+
// roster does not vary by key, and `charm-hyper` is absent from
|
|
76
|
+
// CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS — `ModelRegistry` resolves this
|
|
77
|
+
// namespace with no credential at all, so hashing one would split it
|
|
78
|
+
// against the namespace discovery computes and miss forever.
|
|
79
|
+
//
|
|
80
|
+
// Normalized through the shared helper because the registry passes the
|
|
81
|
+
// raw configured value while `charmHyperModelManagerOptions` passes a
|
|
82
|
+
// `/v1`-suffixed one; both must land on one namespace.
|
|
83
|
+
return `charm-hyper:models-v1:${Bun.hash(normalizeCharmHyperBaseUrl(options.baseUrl)).toString(36)}`;
|
|
84
|
+
}
|
|
65
85
|
case "muse-code": {
|
|
66
86
|
const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
|
|
67
87
|
const scope = `${options.apiKey ?? ""}\u0000${baseUrl}`;
|
|
@@ -69,10 +89,14 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
69
89
|
}
|
|
70
90
|
case "litellm": {
|
|
71
91
|
const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
|
|
72
|
-
// rich-
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
92
|
+
// rich-v9 unions compat across the management endpoints and keys the
|
|
93
|
+
// deployment's `supports_vision` declaration into it, so a warm
|
|
94
|
+
// rich-v8 row would keep retracting axes an earlier endpoint reported
|
|
95
|
+
// (issue #11982). rich-v8 invalidated rows whose `compatConfig`
|
|
96
|
+
// retained a colliding bundled model's provider-specific transport
|
|
97
|
+
// (e.g. Fireworks `wireModelIdMode`) before that leak was fixed
|
|
98
|
+
// (issue #9938).
|
|
99
|
+
return `litellm:rich-v9:${Bun.hash(baseUrl).toString(36)}`;
|
|
76
100
|
}
|
|
77
101
|
case "opencode-go":
|
|
78
102
|
case "opencode-zen": {
|
|
@@ -37,6 +37,14 @@ export interface ProviderDescriptor {
|
|
|
37
37
|
dynamicModelsAuthoritative?: boolean;
|
|
38
38
|
/** Catalog discovery configuration. Only providers with this field participate in generate-models.ts. */
|
|
39
39
|
catalogDiscovery?: CatalogDiscoveryConfig;
|
|
40
|
+
/**
|
|
41
|
+
* When true, generator backfills never copy reasoning/input/limits from
|
|
42
|
+
* same-id rows on other providers into this provider's rows. Set for
|
|
43
|
+
* providers whose endpoint discovery is the deployment truth and whose
|
|
44
|
+
* corrections live in KDL, so a stencil.so or canonical-family reference
|
|
45
|
+
* cannot reintroduce foreign metadata.
|
|
46
|
+
*/
|
|
47
|
+
skipCrossProviderReferenceFills?: boolean;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/** A provider descriptor that has catalog discovery configured. */
|
|
@@ -77,6 +85,13 @@ export interface ProviderCatalogEntry {
|
|
|
77
85
|
readonly dynamicModelsAuthoritative?: boolean;
|
|
78
86
|
/** Catalog discovery configuration for generate-models.ts. */
|
|
79
87
|
readonly catalogDiscovery?: CatalogDiscoveryConfig;
|
|
88
|
+
/**
|
|
89
|
+
* When true, generator backfills never copy reasoning/input/limits from
|
|
90
|
+
* same-id rows on other providers into this provider's rows. Set for
|
|
91
|
+
* providers whose endpoint discovery is the deployment truth and whose
|
|
92
|
+
* corrections live in KDL.
|
|
93
|
+
*/
|
|
94
|
+
readonly skipCrossProviderReferenceFills?: boolean;
|
|
80
95
|
/**
|
|
81
96
|
* Built bespoke by the coding-agent runtime (OAuth-token-driven managers);
|
|
82
97
|
* excluded from `PROVIDER_DESCRIPTORS` even though models are discoverable.
|
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
basetenModelManagerOptions,
|
|
19
19
|
bedrockMantleModelManagerOptions,
|
|
20
20
|
cerebrasModelManagerOptions,
|
|
21
|
+
charmHyperModelManagerOptions,
|
|
21
22
|
clinePassModelManagerOptions,
|
|
22
23
|
cloudflareAiGatewayModelManagerOptions,
|
|
24
|
+
commandCodeModelManagerOptions,
|
|
23
25
|
coreWeaveModelManagerOptions,
|
|
24
26
|
deepinfraModelManagerOptions,
|
|
25
27
|
deepseekModelManagerOptions,
|
|
@@ -149,6 +151,26 @@ export const CATALOG_PROVIDERS = [
|
|
|
149
151
|
createModelManagerOptions: (config: ModelManagerConfig) => cerebrasModelManagerOptions(config),
|
|
150
152
|
catalogDiscovery: { label: "Cerebras" },
|
|
151
153
|
},
|
|
154
|
+
{
|
|
155
|
+
id: "charm-hyper",
|
|
156
|
+
defaultModel: "glm-5.3",
|
|
157
|
+
envVars: ["CHARM_HYPER_API_KEY", "HYPER_API_KEY"],
|
|
158
|
+
createModelManagerOptions: (config: ModelManagerConfig) => charmHyperModelManagerOptions(config),
|
|
159
|
+
allowUnauthenticated: true,
|
|
160
|
+
dynamicModelsAuthoritative: true,
|
|
161
|
+
// The gateway row is the whole truth for a Hyper deployment. Same-id rows
|
|
162
|
+
// on other hosts disagree with it in both directions (it serves
|
|
163
|
+
// non-thinking Kimi K2.5/K2.7-Code and a text-only Gemma 4 that their
|
|
164
|
+
// upstream homes list as reasoning/vision), so foreign backfills would
|
|
165
|
+
// advertise capabilities this deployment does not have.
|
|
166
|
+
skipCrossProviderReferenceFills: true,
|
|
167
|
+
// Deliberately NO `catalogDiscovery`: that field is what enrolls a provider
|
|
168
|
+
// in generate-models.ts. This gateway's catalog is live deployment truth,
|
|
169
|
+
// so generating would freeze one hyper.charm.land snapshot into
|
|
170
|
+
// models.json — and since discovery here needs no credentials, it would
|
|
171
|
+
// happen on every regen, contradicting the runtime-only contract
|
|
172
|
+
// compat-conformance.test.ts pins for this provider.
|
|
173
|
+
},
|
|
152
174
|
{
|
|
153
175
|
id: "cloudflare-ai-gateway",
|
|
154
176
|
defaultModel: "anthropic/claude-opus-4-8",
|
|
@@ -156,6 +178,19 @@ export const CATALOG_PROVIDERS = [
|
|
|
156
178
|
createModelManagerOptions: (config: ModelManagerConfig) => cloudflareAiGatewayModelManagerOptions(config),
|
|
157
179
|
catalogDiscovery: { label: "Cloudflare AI Gateway" },
|
|
158
180
|
},
|
|
181
|
+
{
|
|
182
|
+
id: "commandcode",
|
|
183
|
+
defaultModel: "claude-sonnet-4-6",
|
|
184
|
+
envVars: ["COMMAND_CODE_API_KEY", "COMMANDCODE_API_KEY"],
|
|
185
|
+
createModelManagerOptions: (config: ModelManagerConfig) => commandCodeModelManagerOptions(config),
|
|
186
|
+
allowUnauthenticated: true,
|
|
187
|
+
dynamicModelsAuthoritative: true,
|
|
188
|
+
catalogDiscovery: { label: "Command Code", allowUnauthenticated: true },
|
|
189
|
+
// The Provider API rows carry no reasoning/modality metadata and KDL
|
|
190
|
+
// owns the deployment policy: same-id references on other hosts must
|
|
191
|
+
// not backfill reasoning, input, or limits during generation.
|
|
192
|
+
skipCrossProviderReferenceFills: true,
|
|
193
|
+
},
|
|
159
194
|
{
|
|
160
195
|
id: "cursor",
|
|
161
196
|
defaultModel: "claude-4.6-opus-high",
|
|
@@ -611,6 +646,7 @@ export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = CATALOG_ENTRY
|
|
|
611
646
|
createModelManagerOptions: provider.createModelManagerOptions,
|
|
612
647
|
allowUnauthenticated: provider.allowUnauthenticated,
|
|
613
648
|
dynamicModelsAuthoritative: provider.dynamicModelsAuthoritative,
|
|
649
|
+
skipCrossProviderReferenceFills: provider.skipCrossProviderReferenceFills,
|
|
614
650
|
catalogDiscovery: provider.catalogDiscovery
|
|
615
651
|
? { ...provider.catalogDiscovery, envVars: provider.catalogDiscovery.envVars ?? provider.envVars ?? [] }
|
|
616
652
|
: undefined,
|