@wenathlan/getry 2.0.165 → 2.0.166
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/accounts.ts +2087 -0
- package/antigravity.ts +2171 -0
- package/auth.ts +265 -0
- package/constants.ts +950 -0
- package/db.ts +810 -0
- package/docs/distributionlanes.md +1 -1
- package/docs/getry.md +33 -0
- package/engine.ts +1535 -0
- package/fingerprint.ts +1461 -0
- package/gateway.ts +1306 -0
- package/gatewayprobe.ts +61 -0
- package/gatewaysmoke.ts +64 -0
- package/intelligence.ts +169 -0
- package/json.ts +932 -0
- package/labels.ts +262 -0
- package/llm.ts +2709 -0
- package/models.ts +4017 -0
- package/nodeshim.ts +265 -0
- package/oauth.ts +2201 -0
- package/package.json +575 -215
- package/project.ts +1293 -0
- package/providers.ts +3238 -0
- package/quota.ts +1891 -0
- package/recovery.ts +2434 -0
- package/request.ts +4496 -0
- package/search.ts +1761 -0
- package/server.ts +766 -0
- package/streaming.ts +4398 -0
- package/thinking.ts +416 -0
- package/types.ts +809 -0
- package/utils.ts +263 -0
- package/validate.ts +61 -0
- package/versionregistry.ts +1325 -0
package/thinking.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* thinking.ts — the thinking seat of getry (root layer): the registerable
|
|
3
|
+
* reasoning ladder plus the routing the rungs drive.
|
|
4
|
+
*
|
|
5
|
+
* the reasoning ladder the gateway accepts on every chat route: named
|
|
6
|
+
* levels, each bound to the thinking budget the provider receives. the
|
|
7
|
+
* ladder is registered, never fixed: the seven rungs below are the family
|
|
8
|
+
* seed only — a registration (the GETRY_THINKING_LEVELS surface at boot,
|
|
9
|
+
* or registerthinkinglevels from the host) replaces the whole ladder with
|
|
10
|
+
* whatever the person registered: any count, any names, any budgets. no
|
|
11
|
+
* ceiling lives in this engine — a plan that wants fewer rungs blocks in
|
|
12
|
+
* the devthink system layer, never here.
|
|
13
|
+
*
|
|
14
|
+
* beside the ladder the seat owns the routing the level selects: the model
|
|
15
|
+
* tier the rung targets (tier routing), the token budget clamped to the
|
|
16
|
+
* model output ceiling, the two-calls pattern verdict, the ordered
|
|
17
|
+
* fallback chain over the provider catalog, and the session/stream lanes a
|
|
18
|
+
* thinking request rides. every routing function below is pure,
|
|
19
|
+
* dependency-free math over structural rows — the gateway modules (engine,
|
|
20
|
+
* gateway) and the family consumers (devthink and siblings) wire the real
|
|
21
|
+
* catalog rows in, never a second copy of the math.
|
|
22
|
+
*
|
|
23
|
+
* the logicfold3 fold retired the thinkingtypes.ts mirror this seat once
|
|
24
|
+
* carried (the declaration surface that repeated every export below as an
|
|
25
|
+
* `export declare` tombstone — zero import sites repo-wide) — every
|
|
26
|
+
* declaration it mirrored answers from this implementation alone.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** the name of one thinking level — the seven seed rungs stay literate for
|
|
30
|
+
* completion, and any registered name rides beside them: the vocabulary is
|
|
31
|
+
* what the person registers, never a closed union. */
|
|
32
|
+
export type ThinkingLevelName = (string & {}) | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
33
|
+
|
|
34
|
+
/** one rung of the reasoning ladder. */
|
|
35
|
+
export type ThinkingLevel = {
|
|
36
|
+
level: ThinkingLevelName;
|
|
37
|
+
budget: number;
|
|
38
|
+
desc: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** the family seed ladder — the seven rungs the self-hosted build answers
|
|
42
|
+
* until a registration replaces them. the seed is a default, never a
|
|
43
|
+
* ceiling: registerthinkinglevels accepts any count of rungs. */
|
|
44
|
+
export const THINKINGLEVELS: ThinkingLevel[] = [
|
|
45
|
+
{ level: "none", budget: 0, desc: "no thinking" },
|
|
46
|
+
{ level: "minimal", budget: 1400, desc: "quick reasoning" },
|
|
47
|
+
{ level: "low", budget: 5500, desc: "light reasoning" },
|
|
48
|
+
{ level: "medium", budget: 17000, desc: "balanced" },
|
|
49
|
+
{ level: "high", budget: 68000, desc: "deep reasoning default" },
|
|
50
|
+
{ level: "xhigh", budget: 68000, desc: "extra deep" },
|
|
51
|
+
{ level: "max", budget: 68000, desc: "maximum" },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/** the seed rung the gateway resolves when the request names none — the
|
|
55
|
+
* registered default (GETRY_THINKING_DEFAULT or registerthinkinglevels)
|
|
56
|
+
* overrides it at runtime through defaultthinkinglevel(). */
|
|
57
|
+
export const DEFAULTTHINKINGLEVEL: ThinkingLevelName = "high";
|
|
58
|
+
|
|
59
|
+
/** the registration surface: GETRY_THINKING_LEVELS — a JSON array of rung
|
|
60
|
+
* objects ({"level","budget","desc"}) the person registers at boot; any
|
|
61
|
+
* count rides. invalid json answers the family seed untouched. */
|
|
62
|
+
const LEVELS_ENV = "GETRY_THINKING_LEVELS";
|
|
63
|
+
|
|
64
|
+
/** the registration surface for the default rung: GETRY_THINKING_DEFAULT —
|
|
65
|
+
* a level name that must name a rung of the registered ladder. */
|
|
66
|
+
const DEFAULT_ENV = "GETRY_THINKING_DEFAULT";
|
|
67
|
+
|
|
68
|
+
/** parses one registration payload into a ladder — the honest rungs only:
|
|
69
|
+
* named, finite-budgeted, deduped (last wins). no count cap applies. */
|
|
70
|
+
function parseRegisteredLevels(raw: string): ThinkingLevel[] | null {
|
|
71
|
+
let parsed: unknown;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(raw);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
78
|
+
const rungs = new Map<string, ThinkingLevel>();
|
|
79
|
+
for (const entry of parsed) {
|
|
80
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
81
|
+
const { level, budget, desc } = entry as Record<string, unknown>;
|
|
82
|
+
if (typeof level !== "string" || level.trim() === "") continue;
|
|
83
|
+
const name = level.toLowerCase().trim();
|
|
84
|
+
const tokens = typeof budget === "number" && Number.isFinite(budget) && budget >= 0 ? Math.floor(budget) : 0;
|
|
85
|
+
rungs.set(name, {
|
|
86
|
+
level: name,
|
|
87
|
+
budget: tokens,
|
|
88
|
+
desc: typeof desc === "string" && desc !== "" ? desc : `registered level ${name}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return rungs.size === 0 ? null : [...rungs.values()];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** the live ladder: the registered rungs, or the family seed when no
|
|
95
|
+
* registration ever landed. resolved once at import from the environment
|
|
96
|
+
* so the load-time budget maps capture the registered ladder too. */
|
|
97
|
+
let ladder: ThinkingLevel[] = parseRegisteredLevels(process.env[LEVELS_ENV] ?? "") ?? THINKINGLEVELS;
|
|
98
|
+
|
|
99
|
+
/** the live default rung: a registered default that names a real rung, or
|
|
100
|
+
* the family seed default. */
|
|
101
|
+
let ladderDefault: ThinkingLevelName | null = (() => {
|
|
102
|
+
const raw = process.env[DEFAULT_ENV];
|
|
103
|
+
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
104
|
+
const name = raw.toLowerCase().trim();
|
|
105
|
+
return ladder.some((rung) => rung.level === name) ? name : null;
|
|
106
|
+
})();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* registers the thinking ladder — the owner's word is the law: any count of
|
|
110
|
+
* rungs, any names, any budgets (no ceiling in this engine). the
|
|
111
|
+
* registration replaces the whole ladder; pass the seed back to restore the
|
|
112
|
+
* family default. invalid entries are skipped, duplicates keep the last
|
|
113
|
+
* write, and an empty registration answers the seed untouched.
|
|
114
|
+
*
|
|
115
|
+
* @law levels depend on what the person registers — never a fixed 7
|
|
116
|
+
* @param levels the rungs to register.
|
|
117
|
+
* @param options the default rung of the registration (must name a rung).
|
|
118
|
+
* @returns the live ladder after the registration.
|
|
119
|
+
*/
|
|
120
|
+
export function registerthinkinglevels(
|
|
121
|
+
levels: readonly ThinkingLevel[],
|
|
122
|
+
options?: { default?: ThinkingLevelName },
|
|
123
|
+
): ThinkingLevel[] {
|
|
124
|
+
const rungs = parseRegisteredLevels(JSON.stringify(levels));
|
|
125
|
+
if (rungs === null) return ladder;
|
|
126
|
+
ladder = rungs;
|
|
127
|
+
const wanted = options?.default;
|
|
128
|
+
if (typeof wanted === "string" && wanted.trim() !== "" && islevel(wanted)) {
|
|
129
|
+
ladderDefault = wanted.toLowerCase().trim();
|
|
130
|
+
} else if (wanted === undefined) {
|
|
131
|
+
ladderDefault = ladder.some((rung) => rung.level === DEFAULTTHINKINGLEVEL) ? DEFAULTTHINKINGLEVEL : null;
|
|
132
|
+
}
|
|
133
|
+
return ladder;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* lists the live ladder in serve order (the registration order, none first
|
|
138
|
+
* when the seed rides).
|
|
139
|
+
*
|
|
140
|
+
* @returns the thinking levels the engine accepts right now.
|
|
141
|
+
*/
|
|
142
|
+
export function listlevels(): ThinkingLevel[] {
|
|
143
|
+
return ladder;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** the live default rung — the registered default or the family seed. */
|
|
147
|
+
export function defaultthinkinglevel(): ThinkingLevelName {
|
|
148
|
+
return ladderDefault ?? DEFAULTTHINKINGLEVEL;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* resolves the budget of one named level against the live ladder.
|
|
153
|
+
*
|
|
154
|
+
* @param level the level name to resolve.
|
|
155
|
+
* @returns the thinking budget in tokens — 0 when the rung never registered.
|
|
156
|
+
*/
|
|
157
|
+
export function budgetof(level: ThinkingLevelName): number {
|
|
158
|
+
return ladder.find((rung) => rung.level === (typeof level === "string" ? level.toLowerCase().trim() : level))
|
|
159
|
+
?.budget ?? 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* checks whether a request value names a real rung of the live ladder.
|
|
164
|
+
*
|
|
165
|
+
* @param candidate the raw value from the request.
|
|
166
|
+
* @returns true when the value is a registered level.
|
|
167
|
+
*/
|
|
168
|
+
export function islevel(candidate: string): candidate is ThinkingLevelName {
|
|
169
|
+
const name = candidate.toLowerCase().trim();
|
|
170
|
+
return ladder.some((rung) => rung.level === name);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* formats a budget the way the theme renders it (grouped thousands).
|
|
175
|
+
*
|
|
176
|
+
* @param budget the budget in tokens.
|
|
177
|
+
* @returns the formatted label.
|
|
178
|
+
*/
|
|
179
|
+
export function formatbudget(budget: number): string {
|
|
180
|
+
return budget.toLocaleString("en-US");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ===== block: tier routing ===== */
|
|
184
|
+
|
|
185
|
+
/** the model tier vocabulary the provider catalog rows carry (the
|
|
186
|
+
* intelligencerank tier of the gateway types): 1 the flagship tier, 2 the
|
|
187
|
+
* strong tier, 3 the mid tier, 4 the economy tier. */
|
|
188
|
+
export type thinkingmodeltier = 1 | 2 | 3 | 4;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* resolves the model tier one thinking level targets — the rung POSITION
|
|
192
|
+
* on the live ladder selects the tier, never the name: the fraction of the
|
|
193
|
+
* ladder the rung sits at walks the tiers bottom-up (at or below a quarter
|
|
194
|
+
* the economy tier 4, at or below a half the mid tier 3, at or below three
|
|
195
|
+
* quarters the strong tier 2, above it the flagship tier 1), so the family
|
|
196
|
+
* seed routes none/minimal to 4, low/medium to 3, high to 2 and xhigh/max
|
|
197
|
+
* to the flagship 1 — and a registered ladder of any length routes the same
|
|
198
|
+
* way. a single-rung registration answers the ceiling the rung carries (a
|
|
199
|
+
* zero-budget rung targets the economy tier, every other single rung the
|
|
200
|
+
* flagship). a level that names no rung of the live ladder routes the
|
|
201
|
+
* default rung's tier, the same honest fallback budgetof answers.
|
|
202
|
+
*
|
|
203
|
+
* @law the level selects the model tier — the tier ladder never hardcodes
|
|
204
|
+
* the seven seed names
|
|
205
|
+
* @param level the level name to route.
|
|
206
|
+
* @returns the model tier the level targets.
|
|
207
|
+
*/
|
|
208
|
+
export function targettier(level: ThinkingLevelName): thinkingmodeltier {
|
|
209
|
+
const rungs = ladder;
|
|
210
|
+
const named = typeof level === "string" ? level.toLowerCase().trim() : level;
|
|
211
|
+
let index = rungs.findIndex((rung) => rung.level === named);
|
|
212
|
+
if (index < 0) index = rungs.findIndex((rung) => rung.level === defaultthinkinglevel());
|
|
213
|
+
if (index < 0) return 2;
|
|
214
|
+
if (rungs.length <= 1) return rungs[index].budget === 0 ? 4 : 1;
|
|
215
|
+
const fraction = index / (rungs.length - 1);
|
|
216
|
+
if (fraction <= 0.25) return 4;
|
|
217
|
+
if (fraction <= 0.5) return 3;
|
|
218
|
+
if (fraction <= 0.75) return 2;
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ===== block: token budgets ===== */
|
|
223
|
+
|
|
224
|
+
/** the two-calls threshold the family seed pins (98304 tokens — the glm
|
|
225
|
+
* max output window the v1 lane rides); a config threshold always wins. */
|
|
226
|
+
export const TWOCALLSTHRESHOLD = 98304;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* clamps the budget of one level to a model's output ceiling — the gateway
|
|
230
|
+
* never promises the provider more thinking tokens than the model can
|
|
231
|
+
* answer with (the clamp discipline the bodybuild config names).
|
|
232
|
+
*
|
|
233
|
+
* @param level the level whose budget rides.
|
|
234
|
+
* @param ceiling the model max output tokens (no clamp when absent).
|
|
235
|
+
* @returns the budget in tokens, clamped down to the ceiling.
|
|
236
|
+
*/
|
|
237
|
+
export function clampbudget(level: ThinkingLevelName, ceiling?: number): number {
|
|
238
|
+
const budget = budgetof(level);
|
|
239
|
+
if (ceiling === undefined || !Number.isFinite(ceiling) || ceiling <= 0) return budget;
|
|
240
|
+
return Math.min(budget, Math.floor(ceiling));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* decides whether a request rides the two-calls pattern (call 1 thinking
|
|
245
|
+
* enabled, call 2 a fresh response when the guarantee rides): the pattern
|
|
246
|
+
* fires when the thinking budget plus the response ceiling crosses the
|
|
247
|
+
* threshold, or whenever the level thinks at all — the exact math the
|
|
248
|
+
* engine's zai sdk lane always carried, lifted here so every consumer
|
|
249
|
+
* answers the same verdict.
|
|
250
|
+
*
|
|
251
|
+
* @param level the thinking level of the request.
|
|
252
|
+
* @param responsemax the response ceiling in tokens (max_tokens or the
|
|
253
|
+
* metamodel output window).
|
|
254
|
+
* @param threshold the two-calls trigger threshold (the family default
|
|
255
|
+
* 98304 when absent).
|
|
256
|
+
* @param budget the effective thinking budget when a config map overrides
|
|
257
|
+
* the ladder (the live ladder answers when absent).
|
|
258
|
+
* @returns true when the request rides the two-calls pattern.
|
|
259
|
+
*/
|
|
260
|
+
export function twocallspattern(input: {
|
|
261
|
+
level: ThinkingLevelName;
|
|
262
|
+
responsemax: number;
|
|
263
|
+
threshold?: number;
|
|
264
|
+
budget?: number;
|
|
265
|
+
}): boolean {
|
|
266
|
+
const budget = input.budget ?? budgetof(input.level);
|
|
267
|
+
const threshold = input.threshold ?? TWOCALLSTHRESHOLD;
|
|
268
|
+
return budget + input.responsemax > threshold || input.level !== "none";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* ===== block: fallback chains ===== */
|
|
272
|
+
|
|
273
|
+
/** one model row of the provider catalog the seat routes over — the
|
|
274
|
+
* structural slice of the modeldef kind the gateway types own (id,
|
|
275
|
+
* reasoning flag, output ceiling, leaderboard rank): the seat never
|
|
276
|
+
* imports the gateway graph, so the rows ride as plain shapes. */
|
|
277
|
+
export type thinkingmodelrow = {
|
|
278
|
+
id: string;
|
|
279
|
+
reasoning?: boolean;
|
|
280
|
+
maxoutput?: number;
|
|
281
|
+
rank?: { score?: number; tier?: thinkingmodeltier };
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* checks whether one catalog model can serve a thinking level: a model
|
|
286
|
+
* that carries no reasoning flag counts as capable (the family convention
|
|
287
|
+
* — an unknown capability answers true the way the engine's
|
|
288
|
+
* supports_thinking projection does), a model that declares reasoning
|
|
289
|
+
* false never carries a thinking budget.
|
|
290
|
+
*
|
|
291
|
+
* @param model the catalog row to check.
|
|
292
|
+
* @param level the level the request rides.
|
|
293
|
+
* @returns true when the model may serve the level.
|
|
294
|
+
*/
|
|
295
|
+
export function modelcapable(model: thinkingmodelrow, level: ThinkingLevelName): boolean {
|
|
296
|
+
if (model.reasoning === false) return level === "none";
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* builds the ordered fallback chain of one provider's catalog for a level:
|
|
302
|
+
* the capable models ordered by tier distance from the level's target
|
|
303
|
+
* tier (exact tier first, adjacent tiers outward), then by whether the
|
|
304
|
+
* output ceiling holds the full budget unclamped, then by leaderboard
|
|
305
|
+
* score, then by id — a stable, honest order the caller walks top to
|
|
306
|
+
* bottom on failure. unranked models ride after every ranked tier.
|
|
307
|
+
*
|
|
308
|
+
* @param models the provider catalog rows.
|
|
309
|
+
* @param level the level the request rides.
|
|
310
|
+
* @returns the ordered capable rows.
|
|
311
|
+
*/
|
|
312
|
+
export function fallbackchain(models: readonly thinkingmodelrow[], level: ThinkingLevelName): thinkingmodelrow[] {
|
|
313
|
+
const target = targettier(level);
|
|
314
|
+
const budget = budgetof(level);
|
|
315
|
+
const capable = models.filter((model) => modelcapable(model, level));
|
|
316
|
+
const distance = (model: thinkingmodelrow): number => {
|
|
317
|
+
const tier = model.rank?.tier;
|
|
318
|
+
if (tier === undefined) return Number.MAX_SAFE_INTEGER;
|
|
319
|
+
return Math.abs(tier - target);
|
|
320
|
+
};
|
|
321
|
+
const fullfit = (model: thinkingmodelrow): boolean =>
|
|
322
|
+
model.maxoutput === undefined || model.maxoutput >= budget;
|
|
323
|
+
return capable.sort((left, right) => {
|
|
324
|
+
const bydistance = distance(left) - distance(right);
|
|
325
|
+
if (bydistance !== 0) return bydistance;
|
|
326
|
+
const byfit = Number(fullfit(right)) - Number(fullfit(left));
|
|
327
|
+
if (byfit !== 0) return byfit;
|
|
328
|
+
const byscore = (right.rank?.score ?? -Infinity) - (left.rank?.score ?? -Infinity);
|
|
329
|
+
if (byscore !== 0) return byscore;
|
|
330
|
+
return left.id.localeCompare(right.id);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ===== block: session and stream lanes ===== */
|
|
335
|
+
|
|
336
|
+
/** the lane a thinking request rides: "session" pins the per-session
|
|
337
|
+
* rotation context (the rotation cursor and the thinking budget bind to
|
|
338
|
+
* the (sessionid, provider) row the sessions store owns), "stream" rides
|
|
339
|
+
* the sse emission contract (reasoning deltas before content, the parser
|
|
340
|
+
* pattern the streaming routes follow). a request may ride both lanes at
|
|
341
|
+
* once — a session-scoped stream. */
|
|
342
|
+
export type thinkinglane = "session" | "stream";
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* resolves the lanes one thinking request rides, in bind order: the
|
|
346
|
+
* session lane first (the rotation context binds before any byte leaves
|
|
347
|
+
* the gateway), the stream lane second (the emission order of the
|
|
348
|
+
* answer). a request that names neither lane rides no named lane — a
|
|
349
|
+
* plain one-shot completion.
|
|
350
|
+
*
|
|
351
|
+
* @param input the request shape: stream true asks the sse contract,
|
|
352
|
+
* sessionid binds the session rotation context.
|
|
353
|
+
* @returns the lanes in bind order (empty when the request is a plain
|
|
354
|
+
* one-shot completion).
|
|
355
|
+
*/
|
|
356
|
+
export function lanesof(input: { stream?: boolean; sessionid?: string | null }): thinkinglane[] {
|
|
357
|
+
const lanes: thinkinglane[] = [];
|
|
358
|
+
if (typeof input.sessionid === "string" && input.sessionid.trim() !== "") lanes.push("session");
|
|
359
|
+
if (input.stream === true) lanes.push("stream");
|
|
360
|
+
return lanes;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* ===== block: provider catalog ===== */
|
|
364
|
+
|
|
365
|
+
/** one provider row of the catalog the seat routes over — the structural
|
|
366
|
+
* slice of the gateway version rows (provider name, lifecycle badge, the
|
|
367
|
+
* model catalog). */
|
|
368
|
+
export type thinkingproviderrow = {
|
|
369
|
+
provider: string;
|
|
370
|
+
badge?: "live" | "paused";
|
|
371
|
+
models: readonly thinkingmodelrow[];
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* lists the providers whose catalog serves a level (at least one capable
|
|
376
|
+
* model), live providers first — a paused provider still serves the list
|
|
377
|
+
* as a fallback seat the way the paused v2 lane falls back to v1, it just
|
|
378
|
+
* never leads the chain.
|
|
379
|
+
*
|
|
380
|
+
* @param catalog the provider rows.
|
|
381
|
+
* @param level the level the request rides.
|
|
382
|
+
* @returns the serving providers, live first, input order stable within
|
|
383
|
+
* each badge group.
|
|
384
|
+
*/
|
|
385
|
+
export function providersserving(catalog: readonly thinkingproviderrow[], level: ThinkingLevelName): thinkingproviderrow[] {
|
|
386
|
+
return catalog
|
|
387
|
+
.filter((provider) => provider.models.some((model) => modelcapable(model, level)))
|
|
388
|
+
.sort((left, right) => Number(left.badge !== "live") - Number(right.badge !== "live"));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** one provider/model candidate of the cross-provider chain. */
|
|
392
|
+
export type thinkingcandidate = { provider: string; model: thinkingmodelrow };
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* builds the cross-provider fallback chain for a level: every serving
|
|
396
|
+
* provider in live-first order, each contributing its own ordered
|
|
397
|
+
* fallbackchain — the single honest walk order a consumer retries through
|
|
398
|
+
* on failure (the tier-exact candidate of the live providers first, the
|
|
399
|
+
* paused providers' candidates last).
|
|
400
|
+
*
|
|
401
|
+
* @param catalog the provider rows.
|
|
402
|
+
* @param level the level the request rides.
|
|
403
|
+
* @returns the ordered provider/model candidates.
|
|
404
|
+
*/
|
|
405
|
+
export function providerchain(
|
|
406
|
+
catalog: readonly thinkingproviderrow[],
|
|
407
|
+
level: ThinkingLevelName,
|
|
408
|
+
): thinkingcandidate[] {
|
|
409
|
+
const chain: thinkingcandidate[] = [];
|
|
410
|
+
for (const provider of providersserving(catalog, level)) {
|
|
411
|
+
for (const model of fallbackchain(provider.models, level)) {
|
|
412
|
+
chain.push({ provider: provider.provider, model });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return chain;
|
|
416
|
+
}
|