@wenathlan/getry 2.0.165 → 2.0.167
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 +1 -1
- package/accounts.ts +2087 -0
- package/antigravity.ts +2171 -0
- package/auth.ts +265 -0
- package/cli.ts +3 -3
- package/constants.ts +950 -0
- package/db.ts +810 -0
- package/docs/distributionlanes.md +1 -1
- package/docs/features.md +1 -2
- package/docs/gateway.md +2 -3
- package/docs/getry.md +33 -0
- package/engine.ts +1535 -0
- package/fingerprint.ts +1461 -0
- package/gateway.ts +1306 -0
- package/gatewaycheck.ts +142 -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 +580 -225
- 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/{tests → test}/cli.test.ts +1 -1
- package/{tests → test}/gateway.test.ts +2 -2
- package/{tests → test}/gatewayversions.test.ts +1 -1
- package/{tests → test}/intelligence.test.ts +1 -1
- package/{tests → test}/keyrotation.test.ts +1 -1
- package/{tests → test}/onboardingstate.test.ts +1 -1
- package/{tests → test}/providerfixtures.ts +12 -12
- package/{tests → test}/researchfixtures.ts +1 -1
- package/{tests → test}/sandboxmodality.test.ts +1 -1
- 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/{tests → test}/debonairlane.test.ts +0 -0
- /package/{tests → test}/design.test.ts +0 -0
- /package/{tests → test}/family.test.ts +0 -0
- /package/{tests → test}/gatewayserver.ts +0 -0
- /package/{tests → test}/jsonpolicy.test.ts +0 -0
- /package/{tests → test}/llm.test.ts +0 -0
- /package/{tests → test}/modelroute.test.ts +0 -0
- /package/{tests → test}/promptlibrary.test.ts +0 -0
- /package/{tests → test}/providercli.test.ts +0 -0
- /package/{tests → test}/providerconstants.test.ts +0 -0
- /package/{tests → test}/providerfingerprint.test.ts +0 -0
- /package/{tests → test}/providermodels.test.ts +0 -0
- /package/{tests → test}/provideroauth.test.ts +0 -0
- /package/{tests → test}/routerotation.test.ts +0 -0
- /package/{tests → test}/serverauth.test.ts +0 -0
- /package/{tests → test}/serverengine.test.ts +0 -0
- /package/{tests → test}/serverproperty.test.ts +0 -0
- /package/{tests → test}/serverutils.test.ts +0 -0
- /package/{tests → test}/streamfallback.test.ts +0 -0
- /package/{tests → test}/thinkingladder.test.ts +0 -0
- /package/{tests → test}/torturellm.test.ts +0 -0
|
@@ -289,7 +289,7 @@ describe("cli init with db keys", () => {
|
|
|
289
289
|
);
|
|
290
290
|
expect(keyscode).toBe(0);
|
|
291
291
|
expect(stdout).toContain("register keys");
|
|
292
|
-
const seed = join(dbworkdir, "
|
|
292
|
+
const seed = join(dbworkdir, "test", "seedkeys.mjs");
|
|
293
293
|
expect(existsSync(seed)).toBe(true);
|
|
294
294
|
const content = readFileSync(seed, "utf8");
|
|
295
295
|
expect(content).toContain("key-aaaaaaaaaa");
|
|
@@ -889,9 +889,9 @@ describe("hardcoded provider url scan of the source tree", () => {
|
|
|
889
889
|
const outsidescan = new Set([...gatewaysources, "providers.ts", "gateway.ts", "types.ts"]);
|
|
890
890
|
for (const entry of await readdir(root))
|
|
891
891
|
if (entry.endsWith(".ts") && !outsidescan.has(entry)) files.push(join(root, entry));
|
|
892
|
-
for (const entry of await readdir(join(root, "
|
|
892
|
+
for (const entry of await readdir(join(root, "test")))
|
|
893
893
|
if (entry.endsWith(".ts") && !entry.endsWith(".test.ts") && entry !== "reviewedorigins.ts")
|
|
894
|
-
files.push(join(root, "
|
|
894
|
+
files.push(join(root, "test", entry));
|
|
895
895
|
expect(files.length).toBeGreaterThan(40);
|
|
896
896
|
const providerhosts = [
|
|
897
897
|
/api\.openai\.com/,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* through the thinking seat. the suite pins its own DATABASE_URL before the
|
|
7
7
|
* db import so the mirror opens its own seat (one writer per wal file — the
|
|
8
8
|
* SQLITE_BUSY lesson). runnable with vitest:
|
|
9
|
-
* pnpm vitest run
|
|
9
|
+
* pnpm vitest run test/gatewayversions.test.ts
|
|
10
10
|
*/
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
12
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// provenance and metrics domain rides its new owner — the battery
|
|
6
6
|
// answers the same contracts the engine suite once pinned: the rag
|
|
7
7
|
// chunking, the retrieval provenance and the token budget trio).
|
|
8
|
-
// runnable with vitest: pnpm vitest run
|
|
8
|
+
// runnable with vitest: pnpm vitest run test/intelligence.test.ts
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
10
|
import { describe, it } from "vitest";
|
|
11
11
|
import {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* the health-aware round-robin of nextkeyrow (the pr19 key rotation law —
|
|
4
4
|
* never expose keys, rotation periods, per-provider health) beside the
|
|
5
5
|
* masking discipline the render paths owe. runnable with vitest:
|
|
6
|
-
* pnpm vitest run
|
|
6
|
+
* pnpm vitest run test/keyrotation.test.ts
|
|
7
7
|
*/
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
import { describe, it } from "vitest";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// # onboardingstate.test — the pure contract of the seen flags: the host
|
|
2
2
|
// validation of the platform base (https only, public hosts only, mapped
|
|
3
3
|
// ipv6 normalized) and the seen-wins stage merge the accessor and the
|
|
4
|
-
// pages share. runnable with vitest: pnpm vitest run
|
|
4
|
+
// pages share. runnable with vitest: pnpm vitest run test/onboardingstate.test.ts
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { describe, it } from "vitest";
|
|
7
7
|
import { hostallowed, mergeStage } from "../onboardingstate.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The recorded provider wire fixtures of the gateway family as one dense typescript module: the streams, the completions, the model lists and the tags answer of the four provider shapes (anthropic, gemini, ollama and the openai compatible surface), each export holding the exact original file text of its tests root source. The localhost gateway fixture server serves them over plain http so the adapter suite and the container smoke run the full request cycle offline — no external network, no provider endpoint, no key — and the gateway tests parse the same bodies the stub transports answer. */
|
|
2
2
|
|
|
3
|
-
/** the anthropic message stream (sse events of the content block delta family) — the exact text of the former
|
|
3
|
+
/** the anthropic message stream (sse events of the content block delta family) — the exact text of the former test/anthropic-stream.txt. */
|
|
4
4
|
export const anthropicstream = `event: message_start
|
|
5
5
|
data: {"type":"message_start","message":{"id":"msg-fixture-1","role":"assistant"}}
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ data: {"type":"message_stop"}
|
|
|
24
24
|
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
|
-
/** the anthropic completion answer (the stop reason and the usage record) — the exact text of the former
|
|
27
|
+
/** the anthropic completion answer (the stop reason and the usage record) — the exact text of the former test/anthropic-completion.json. */
|
|
28
28
|
export const anthropiccompletion = `{
|
|
29
29
|
"id": "msg-fixture-1",
|
|
30
30
|
"type": "message",
|
|
@@ -41,7 +41,7 @@ export const anthropiccompletion = `{
|
|
|
41
41
|
}
|
|
42
42
|
`;
|
|
43
43
|
|
|
44
|
-
/** the anthropic model list (the fixture claude shapes with their context windows) — the exact text of the former
|
|
44
|
+
/** the anthropic model list (the fixture claude shapes with their context windows) — the exact text of the former test/anthropic-models.json. */
|
|
45
45
|
export const anthropicmodels = `{
|
|
46
46
|
"data": [
|
|
47
47
|
{
|
|
@@ -61,7 +61,7 @@ export const anthropicmodels = `{
|
|
|
61
61
|
}
|
|
62
62
|
`;
|
|
63
63
|
|
|
64
|
-
/** the gemini generatecontent stream (the sse quoted-json chunks of the streamed answer) — the exact text of the former
|
|
64
|
+
/** the gemini generatecontent stream (the sse quoted-json chunks of the streamed answer) — the exact text of the former test/gemini-stream.txt. */
|
|
65
65
|
export const geministream = `data: {"candidates":[{"content":{"parts":[{"text":"gemini "}],"role":"model"},"index":0}]}
|
|
66
66
|
|
|
67
67
|
data: {"candidates":[{"content":{"parts":[{"text":"candidate parts "}],"role":"model"},"index":0}]}
|
|
@@ -70,7 +70,7 @@ data: {"candidates":[{"content":{"parts":[{"text":"stream"}],"role":"model"},"in
|
|
|
70
70
|
|
|
71
71
|
`;
|
|
72
72
|
|
|
73
|
-
/** the gemini completion answer (the candidates with the finish reason) — the exact text of the former
|
|
73
|
+
/** the gemini completion answer (the candidates with the finish reason) — the exact text of the former test/gemini-completion.json. */
|
|
74
74
|
export const geminicompletion = `{
|
|
75
75
|
"candidates": [
|
|
76
76
|
{
|
|
@@ -86,7 +86,7 @@ export const geminicompletion = `{
|
|
|
86
86
|
}
|
|
87
87
|
`;
|
|
88
88
|
|
|
89
|
-
/** the gemini model list (the fixture gemini shapes) — the exact text of the former
|
|
89
|
+
/** the gemini model list (the fixture gemini shapes) — the exact text of the former test/gemini-models.json. */
|
|
90
90
|
export const geminimodels = `{
|
|
91
91
|
"models": [
|
|
92
92
|
{
|
|
@@ -106,13 +106,13 @@ export const geminimodels = `{
|
|
|
106
106
|
}
|
|
107
107
|
`;
|
|
108
108
|
|
|
109
|
-
/** the ollama chat stream (the ndjson lines of the streamed answer) — the exact text of the former
|
|
109
|
+
/** the ollama chat stream (the ndjson lines of the streamed answer) — the exact text of the former test/ollama-stream.txt. */
|
|
110
110
|
export const ollamastream = `{"model":"fixture-local:7b","created_at":"2026-01-01T00:00:00Z","message":{"role":"assistant","content":"the local "},"done":false}
|
|
111
111
|
{"model":"fixture-local:7b","created_at":"2026-01-01T00:00:00Z","message":{"role":"assistant","content":"runtime streams "},"done":false}
|
|
112
112
|
{"model":"fixture-local:7b","created_at":"2026-01-01T00:00:00Z","message":{"role":"assistant","content":"line by line"},"done":true}
|
|
113
113
|
`;
|
|
114
114
|
|
|
115
|
-
/** the ollama chat completion answer (the message and the done record) — the exact text of the former
|
|
115
|
+
/** the ollama chat completion answer (the message and the done record) — the exact text of the former test/ollama-completion.json. */
|
|
116
116
|
export const ollamacompletion = `{
|
|
117
117
|
"model": "fixture-local:7b",
|
|
118
118
|
"created_at": "2026-01-01T00:00:00Z",
|
|
@@ -126,7 +126,7 @@ export const ollamacompletion = `{
|
|
|
126
126
|
}
|
|
127
127
|
`;
|
|
128
128
|
|
|
129
|
-
/** the ollama tags answer (the local model list with sizes and digests) — the exact text of the former
|
|
129
|
+
/** the ollama tags answer (the local model list with sizes and digests) — the exact text of the former test/ollama-tags.json. */
|
|
130
130
|
export const ollamatags = `{
|
|
131
131
|
"models": [
|
|
132
132
|
{
|
|
@@ -161,7 +161,7 @@ export const ollamatags = `{
|
|
|
161
161
|
}
|
|
162
162
|
`;
|
|
163
163
|
|
|
164
|
-
/** the openai compatible chat stream (the sse chunks of the streamed answer) — the exact text of the former
|
|
164
|
+
/** the openai compatible chat stream (the sse chunks of the streamed answer) — the exact text of the former test/openaicompat-stream.txt. */
|
|
165
165
|
export const openaicompatstream = `data: {"id":"chatcmpl-fixture-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"{\\"goal\\":"}}]}
|
|
166
166
|
|
|
167
167
|
data: {"id":"chatcmpl-fixture-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"\\"streamed answer\\","}}]}
|
|
@@ -172,7 +172,7 @@ data: [DONE]
|
|
|
172
172
|
|
|
173
173
|
`;
|
|
174
174
|
|
|
175
|
-
/** the openai compatible chat completion answer (the choices with the message and the usage) — the exact text of the former
|
|
175
|
+
/** the openai compatible chat completion answer (the choices with the message and the usage) — the exact text of the former test/openaicompat-completion.json. */
|
|
176
176
|
export const openaicompatcompletion = `{
|
|
177
177
|
"id": "chatcmpl-fixture-1",
|
|
178
178
|
"object": "chat.completion",
|
|
@@ -191,7 +191,7 @@ export const openaicompatcompletion = `{
|
|
|
191
191
|
}
|
|
192
192
|
`;
|
|
193
193
|
|
|
194
|
-
/** the openai compatible model list (the fixture model shapes) — the exact text of the former
|
|
194
|
+
/** the openai compatible model list (the fixture model shapes) — the exact text of the former test/openaicompat-models.json. */
|
|
195
195
|
export const openaicompatmodels = `{
|
|
196
196
|
"object": "list",
|
|
197
197
|
"data": [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The public research catalog sources as one dense typescript module: the twelve public github search exports beside the two markdown documents the catalog build derives from them, each export holding the exact original file text of its
|
|
1
|
+
/** The public research catalog sources as one dense typescript module: the twelve public github search exports beside the two markdown documents the catalog build derives from them, each export holding the exact original file text of its test/code source. The exports record source metadata only — the catalog build never downloads or executes repository code — and the markdown documents carry the licensed sample, the method notes and the comparative feature and flow matrix the research review reads. */
|
|
2
2
|
|
|
3
3
|
/** The public repository metadata of the browser agent search export (one recorded repository), harvested from the public github search index on 2026-08-27 — the exact text of the former reposagent.json. */
|
|
4
4
|
export const reposagent = `[
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// modality id pins the seat for the platform's join to validate at
|
|
10
10
|
// dispatch — this module never judges engine vocabulary). runnable with
|
|
11
11
|
// vitest:
|
|
12
|
-
// pnpm vitest run
|
|
12
|
+
// pnpm vitest run test/sandboxmodality.test.ts
|
|
13
13
|
import assert from "node:assert/strict";
|
|
14
14
|
import { describe, it } from "vitest";
|
|
15
15
|
import { lanesof } from "@wenathlan/saddle/workloadlaw";
|
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
|
+
}
|