@ggui-ai/protocol 0.1.0-rc.1 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/bridge/invoke-agent.d.ts +8 -8
- package/dist/bridge/invoke-agent.d.ts.map +1 -1
- package/dist/bridge/invoke-agent.js +12 -12
- package/dist/envelopes/builders.d.ts +2 -4
- package/dist/envelopes/builders.d.ts.map +1 -1
- package/dist/envelopes/builders.js +2 -6
- package/dist/gadgets/stdlib-gadgets.d.ts +1 -1
- package/dist/gadgets/stdlib-gadgets.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/integrations/mcp-apps.d.ts +354 -711
- package/dist/integrations/mcp-apps.d.ts.map +1 -1
- package/dist/integrations/mcp-apps.js +266 -208
- package/dist/recommended-prompts.d.ts +1 -1
- package/dist/recommended-prompts.js +1 -1
- package/dist/schemas/data-contract.d.ts +4 -3
- package/dist/schemas/data-contract.d.ts.map +1 -1
- package/dist/schemas/data-contract.js +4 -3
- package/dist/schemas/invoke.d.ts +2 -2
- package/dist/schemas/invoke.js +2 -2
- package/dist/schemas/mcp.d.ts +60 -79
- package/dist/schemas/mcp.d.ts.map +1 -1
- package/dist/schemas/mcp.js +93 -116
- package/dist/schemas/sync-check.js +1 -1
- package/dist/stream/stream-parser.d.ts +4 -4
- package/dist/stream/stream-parser.d.ts.map +1 -1
- package/dist/stream/stream-parser.js +9 -9
- package/dist/transport/websocket.d.ts +14 -49
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/types/app-config.d.ts +17 -32
- package/dist/types/app-config.d.ts.map +1 -1
- package/dist/types/canvas-lifecycle.d.ts +13 -13
- package/dist/types/canvas-lifecycle.d.ts.map +1 -1
- package/dist/types/canvas-lifecycle.js +3 -3
- package/dist/types/contract-inference.d.ts +4 -7
- package/dist/types/contract-inference.d.ts.map +1 -1
- package/dist/types/data-bindings.d.ts +1 -1
- package/dist/types/data-bindings.d.ts.map +1 -1
- package/dist/types/data-contract.d.ts +10 -10
- package/dist/types/data-contract.d.ts.map +1 -1
- package/dist/types/events.d.ts +14 -56
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/events.js +1 -8
- package/dist/types/feedback.d.ts +2 -4
- package/dist/types/feedback.d.ts.map +1 -1
- package/dist/types/handshake-suggestion.d.ts +1 -1
- package/dist/types/host-context.d.ts +5 -15
- package/dist/types/host-context.d.ts.map +1 -1
- package/dist/types/host-context.js +1 -1
- package/dist/types/live-channel.d.ts +128 -191
- package/dist/types/live-channel.d.ts.map +1 -1
- package/dist/types/llm-route.d.ts +233 -0
- package/dist/types/llm-route.d.ts.map +1 -0
- package/dist/types/llm-route.js +433 -0
- package/dist/types/llm.d.ts +35 -17
- package/dist/types/llm.d.ts.map +1 -1
- package/dist/types/llm.js +50 -39
- package/dist/types/mcp.d.ts +47 -137
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/types/mcp.js +2 -2
- package/dist/types/openrouter-models.d.ts +3 -2
- package/dist/types/openrouter-models.d.ts.map +1 -1
- package/dist/types/render-event.d.ts +119 -0
- package/dist/types/render-event.d.ts.map +1 -0
- package/dist/types/render-event.js +38 -0
- package/dist/types/session.d.ts +216 -342
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/ui-generator.d.ts +8 -9
- package/dist/types/ui-generator.d.ts.map +1 -1
- package/dist/validation/contract-validator.d.ts +60 -4
- package/dist/validation/contract-validator.d.ts.map +1 -1
- package/dist/validation/contract-validator.js +101 -5
- package/dist/validation/reserved-channels.d.ts +1 -1
- package/dist/validation/reserved-channels.js +5 -5
- package/dist/validation/sanitize-error.js +1 -1
- package/dist/validation/schema-compat-invariants.d.ts +2 -2
- package/dist/validation/schema-compat-invariants.js +2 -2
- package/dist/validation/schema-meta-validation.d.ts +1 -1
- package/dist/validation/schema-meta-validation.js +2 -2
- package/dist/validation/schema-subset.d.ts +1 -1
- package/dist/version.d.ts +253 -46
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +253 -46
- package/package.json +17 -16
- package/dist/navigation/index.d.ts +0 -3
- package/dist/navigation/index.d.ts.map +0 -1
- package/dist/navigation/index.js +0 -1
- package/dist/navigation/stack-navigation.d.ts +0 -55
- package/dist/navigation/stack-navigation.d.ts.map +0 -1
- package/dist/navigation/stack-navigation.js +0 -80
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed `LlmRoute` system — the structural-correctness replacement for
|
|
3
|
+
* string-typed model identifiers.
|
|
4
|
+
*
|
|
5
|
+
* Two concepts, one type:
|
|
6
|
+
* - `provider` — the API endpoint you authenticate against, owns its
|
|
7
|
+
* own model namespace. Includes marketplaces (Bedrock, OpenRouter)
|
|
8
|
+
* and direct-author APIs (Claude/Anthropic, OpenAI, Google AI
|
|
9
|
+
* Studio). NOT a separate "platform" dimension — each provider's
|
|
10
|
+
* API surface IS the platform.
|
|
11
|
+
* - `model` — the wire-canonical string for THIS provider. Registry
|
|
12
|
+
* KEY == what goes on the HTTP wire to the provider. No
|
|
13
|
+
* transformation at dispatch — what you write in `MODELS[provider]`
|
|
14
|
+
* is exactly what the API sees.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: three bugs of the same class in one week (#22 CLI
|
|
17
|
+
* sent slash-prefixed `google/gemini-3.5-flash` → Gemini 404; #42
|
|
18
|
+
* mcp-server negotiator sent `anthropic/claude-haiku-4-5` → Anthropic
|
|
19
|
+
* 404; the next one would have been...). The pattern was always
|
|
20
|
+
* "someone wrote a code path that bypassed `getUpstreamModelId`". The
|
|
21
|
+
* typed-route system makes the bug class structurally impossible —
|
|
22
|
+
* `LlmRoute` is a discriminated union, the dispatch is
|
|
23
|
+
* exhaustiveness-checked, and there's no transformation step to
|
|
24
|
+
* forget because the model string IS the wire form.
|
|
25
|
+
*
|
|
26
|
+
* Slice spec: `docs/plans/2026-05-25-llm-route-typed-system.md`
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Wire-canonical model names per provider. The KEY for each entry is
|
|
30
|
+
* EXACTLY what the provider's API expects on the wire — no
|
|
31
|
+
* transformation, no prefix-strip, no map. Register a new model by
|
|
32
|
+
* adding its wire-canonical string to the matching provider's array.
|
|
33
|
+
*
|
|
34
|
+
* Provider naming uses the COMPANY name for consistency across all
|
|
35
|
+
* providers (`anthropic`, `openai`, `google`) — matches LiteLLM's
|
|
36
|
+
* `anthropic/` prefix + the existing `ANTHROPIC_API_KEY` env var
|
|
37
|
+
* convention. Marketplaces (`bedrock`, `openrouter`) keep their
|
|
38
|
+
* platform name because that IS the company you authenticate against.
|
|
39
|
+
*
|
|
40
|
+
* - `anthropic` — Anthropic's direct API
|
|
41
|
+
* (`api.anthropic.com`). Auth: `ANTHROPIC_API_KEY`.
|
|
42
|
+
* - `openai` — OpenAI's direct API.
|
|
43
|
+
* - `google` — Google AI Studio (`generativelanguage.googleapis.com`).
|
|
44
|
+
* - `bedrock` — AWS Bedrock marketplace. Region prefix (`us.`,
|
|
45
|
+
* `eu.`, `apac.`) is part of the wire name; each region is its
|
|
46
|
+
* own registry entry, not a `{region}` field.
|
|
47
|
+
* - `openrouter` — OpenRouter marketplace. Authors are sub-namespaced
|
|
48
|
+
* in the model string (`<author>/<model>`).
|
|
49
|
+
* - `vertex` — DEFERRED to its own slice. Vertex needs region +
|
|
50
|
+
* projectId + GCP IAM setup; migration is purely additive when
|
|
51
|
+
* ready.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MODELS: {
|
|
54
|
+
readonly anthropic: readonly ["claude-haiku-4-5-20251001", "claude-sonnet-4-6", "claude-opus-4-7", "claude-opus-4-6"];
|
|
55
|
+
readonly openai: readonly ["gpt-5.5", "gpt-5.5-2026-04-23", "gpt-5.5-pro", "gpt-5.5-pro-2026-04-23", "gpt-5.4", "gpt-5.4-2026-03-05", "gpt-5.4-mini", "gpt-5.4-mini-2026-03-17", "gpt-5.4-nano", "gpt-5.4-nano-2026-03-17", "gpt-5.3-codex"];
|
|
56
|
+
readonly google: readonly ["gemini-3.5-flash", "gemini-flash-latest", "gemini-3.1-flash-lite", "gemini-3.1-pro-preview", "gemini-3.1-flash-lite-preview", "gemini-3-flash-preview"];
|
|
57
|
+
readonly bedrock: readonly ["us.anthropic.claude-haiku-4-5-20251001-v1:0", "eu.anthropic.claude-haiku-4-5-20251001-v1:0", "apac.anthropic.claude-haiku-4-5-20251001-v1:0", "global.anthropic.claude-haiku-4-5-20251001-v1:0", "us.anthropic.claude-sonnet-4-6", "eu.anthropic.claude-sonnet-4-6", "apac.anthropic.claude-sonnet-4-6", "us.anthropic.claude-opus-4-7", "eu.anthropic.claude-opus-4-7", "apac.anthropic.claude-opus-4-7", "global.anthropic.claude-opus-4-7", "us.anthropic.claude-opus-4-6-v1"];
|
|
58
|
+
readonly openrouter: readonly ["anthropic/claude-haiku-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-opus-4.7", "openai/gpt-5.5", "openai/gpt-5.5-pro", "openai/gpt-5.4-mini", "openai/gpt-5.4-nano", "google/gemini-3.5-flash", "google/gemini-3.1-pro-preview", "google/gemini-3.1-flash-lite", "x-ai/grok-4.3", "meta-llama/llama-4-maverick", "meta-llama/llama-3.3-70b-instruct", "deepseek/deepseek-v4-pro", "deepseek/deepseek-r1-0528", "qwen/qwen3.7-max", "qwen/qwen3-coder", "mistralai/mistral-large-2512", "openai/gpt-oss-120b"];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Every supported LLM provider. Adding a new provider = add a key to
|
|
62
|
+
* `MODELS` + add a dispatch case wherever `LlmRoute` is consumed (TS
|
|
63
|
+
* exhaustiveness check forces handling).
|
|
64
|
+
*/
|
|
65
|
+
export type LlmProvider = keyof typeof MODELS;
|
|
66
|
+
/**
|
|
67
|
+
* Model names known at compile time for a given provider. For
|
|
68
|
+
* OpenRouter, this is the enumerated subset; for every other provider,
|
|
69
|
+
* this is the full set (their model lists are small + stable).
|
|
70
|
+
*/
|
|
71
|
+
export type KnownModelOf<P extends LlmProvider> = (typeof MODELS)[P][number];
|
|
72
|
+
/**
|
|
73
|
+
* Model names accepted on a route for a given provider. Two providers
|
|
74
|
+
* use the `(string & {})` escape hatch:
|
|
75
|
+
*
|
|
76
|
+
* - **OpenRouter** — `<author>/<model>` permutation space is too
|
|
77
|
+
* large to enumerate. Curated subset gives IDE autocomplete;
|
|
78
|
+
* arbitrary strings pass validation via shape (matches the
|
|
79
|
+
* `<author>/<model>` rule).
|
|
80
|
+
* - **Bedrock** — operators pick from AWS-supplied foundation model
|
|
81
|
+
* ids (e.g. `'anthropic.claude-sonnet-4-6'`), cross-region
|
|
82
|
+
* inference profile ids (e.g. `'us.anthropic.claude-haiku-4-5-
|
|
83
|
+
* 20251001-v1:0'`), or even custom inference profile ARNs
|
|
84
|
+
* (`'arn:aws:bedrock:...'`). The MODELS.bedrock list curates the
|
|
85
|
+
* common cross-region profiles for autocomplete; arbitrary
|
|
86
|
+
* strings pass at runtime so operators with custom profiles +
|
|
87
|
+
* non-curated foundation models stay supported.
|
|
88
|
+
*
|
|
89
|
+
* Every other provider is strict-enum: only names in
|
|
90
|
+
* `MODELS[provider]` typecheck.
|
|
91
|
+
*
|
|
92
|
+
* The `(string & {})` trick preserves IDE autocomplete on the known
|
|
93
|
+
* subset while still accepting arbitrary strings — without it, the
|
|
94
|
+
* union collapses to `string` and the known entries lose autocomplete.
|
|
95
|
+
*/
|
|
96
|
+
export type ModelOf<P extends LlmProvider> = P extends 'openrouter' | 'bedrock' ? KnownModelOf<P> | (string & {}) : KnownModelOf<P>;
|
|
97
|
+
/**
|
|
98
|
+
* A typed LLM call target. The pair `(provider, model)` is sufficient
|
|
99
|
+
* — `provider` selects the SDK / endpoint / auth scheme, `model` is
|
|
100
|
+
* the wire string that SDK sends literally.
|
|
101
|
+
*
|
|
102
|
+
* Discriminated union via mapped type: `LlmRoute` is the union of
|
|
103
|
+
* `{provider: P, model: ModelOf<P>}` for every `P`. TypeScript
|
|
104
|
+
* enforces that the model belongs to the provider's namespace — e.g.
|
|
105
|
+
* `{provider: 'bedrock', model: 'claude-haiku-4-5-20251001'}` is a
|
|
106
|
+
* compile error (that model lives under `claude`, not `bedrock`).
|
|
107
|
+
*
|
|
108
|
+
* Vertex AI is deferred — it needs `region` and `projectId` fields
|
|
109
|
+
* for endpoint construction + IAM. When added, the Vertex variant
|
|
110
|
+
* will look like `{provider: 'vertex', model: ..., region: ...,
|
|
111
|
+
* projectId: ...}` — purely additive, doesn't disturb existing routes.
|
|
112
|
+
*/
|
|
113
|
+
export type LlmRoute = {
|
|
114
|
+
[P in LlmProvider]: {
|
|
115
|
+
provider: P;
|
|
116
|
+
model: ModelOf<P>;
|
|
117
|
+
};
|
|
118
|
+
}[LlmProvider];
|
|
119
|
+
/**
|
|
120
|
+
* Runtime type guard for the provider enum. Pairs with the parser at
|
|
121
|
+
* wire boundaries (ggui.json, CLI flags, env vars) where the value
|
|
122
|
+
* arrives as an unvalidated string.
|
|
123
|
+
*/
|
|
124
|
+
export declare function isLlmProvider(s: string): s is LlmProvider;
|
|
125
|
+
/**
|
|
126
|
+
* Runtime type guard for a known model string under a given provider.
|
|
127
|
+
* Strict-enum check; does NOT accept OpenRouter's arbitrary-string
|
|
128
|
+
* extension — for that, use {@link isValidOpenrouterModel}.
|
|
129
|
+
*/
|
|
130
|
+
export declare function isKnownModel<P extends LlmProvider>(provider: P, model: string): model is KnownModelOf<P>;
|
|
131
|
+
/**
|
|
132
|
+
* Validate an OpenRouter model string by shape: `<author>/<model>`
|
|
133
|
+
* where both segments are non-empty and contain only the characters
|
|
134
|
+
* OpenRouter's catalog uses (alphanumerics + `-` + `.` + `_` + `:`).
|
|
135
|
+
*
|
|
136
|
+
* Used for the OpenRouter escape hatch — strings that pass this check
|
|
137
|
+
* are accepted into `LlmRoute` even if not in `MODELS.openrouter[]`.
|
|
138
|
+
* Strings that fail it are rejected at the parser boundary.
|
|
139
|
+
*/
|
|
140
|
+
export declare function isValidOpenrouterModel(s: string): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Validate a Bedrock model string by shape. AWS accepts THREE forms:
|
|
143
|
+
*
|
|
144
|
+
* - Cross-region inference profile ids — `<region>.<inner>` where
|
|
145
|
+
* region is `us`/`eu`/`apac`/`global` and inner contains `.`,
|
|
146
|
+
* `-`, `:`, alphanumerics (e.g.
|
|
147
|
+
* `'us.anthropic.claude-haiku-4-5-20251001-v1:0'`).
|
|
148
|
+
* - Bedrock foundation model ids — bare `<vendor>.<model>` form
|
|
149
|
+
* (e.g. `'anthropic.claude-sonnet-4-6'`).
|
|
150
|
+
* - Inference profile ARNs — `'arn:aws:bedrock:...'`.
|
|
151
|
+
*
|
|
152
|
+
* Used for the Bedrock escape hatch — strings passing this check are
|
|
153
|
+
* accepted into `LlmRoute` even if not in `MODELS.bedrock[]`.
|
|
154
|
+
*/
|
|
155
|
+
export declare function isValidBedrockModel(s: string): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Validate that a (provider, model) pair would construct a valid
|
|
158
|
+
* `LlmRoute`. Two providers have arbitrary-string escape hatches:
|
|
159
|
+
*
|
|
160
|
+
* - `openrouter` — accepts any string passing
|
|
161
|
+
* {@link isValidOpenrouterModel}
|
|
162
|
+
* - `bedrock` — accepts any string passing {@link isValidBedrockModel}
|
|
163
|
+
*
|
|
164
|
+
* Every other provider requires the model to be in
|
|
165
|
+
* `MODELS[provider]`.
|
|
166
|
+
*/
|
|
167
|
+
export declare function isValidLlmRoute(provider: string, model: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Serialize an `LlmRoute` to the canonical `provider:model` string.
|
|
170
|
+
* Round-trip with {@link parseLlmRoute}. Used for human-readable
|
|
171
|
+
* logging, `ggui.json` config values, and CLI `--model` flags.
|
|
172
|
+
*
|
|
173
|
+
* Examples:
|
|
174
|
+
* `{provider: 'anthropic', model: 'claude-haiku-4-5-20251001'}`
|
|
175
|
+
* → `'anthropic:claude-haiku-4-5-20251001'`
|
|
176
|
+
* `{provider: 'bedrock', model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0'}`
|
|
177
|
+
* → `'bedrock:us.anthropic.claude-haiku-4-5-20251001-v1:0'`
|
|
178
|
+
* `{provider: 'openrouter', model: 'anthropic/claude-3-5-sonnet'}`
|
|
179
|
+
* → `'openrouter:anthropic/claude-3-5-sonnet'`
|
|
180
|
+
*/
|
|
181
|
+
export declare function serializeLlmRoute(route: LlmRoute): string;
|
|
182
|
+
/**
|
|
183
|
+
* Parse a canonical `provider:model` string into an `LlmRoute`.
|
|
184
|
+
* Returns `null` if the string isn't well-formed or if (provider,
|
|
185
|
+
* model) wouldn't construct a valid route. Permissive on the model
|
|
186
|
+
* side for OpenRouter — accepts any string passing
|
|
187
|
+
* {@link isValidOpenrouterModel} even when not in `MODELS.openrouter[]`.
|
|
188
|
+
*
|
|
189
|
+
* Round-trip with {@link serializeLlmRoute}.
|
|
190
|
+
*/
|
|
191
|
+
export declare function parseLlmRoute(serialized: string): LlmRoute | null;
|
|
192
|
+
/**
|
|
193
|
+
* Parse a LiteLLM-format string (`<prefix>/<model>` or
|
|
194
|
+
* `<prefix>/<sub>/<model>` for OpenRouter) into an `LlmRoute`.
|
|
195
|
+
* Returns `null` if the prefix is unknown or the resulting route
|
|
196
|
+
* wouldn't construct.
|
|
197
|
+
*
|
|
198
|
+
* Supports the historical formats ggui has used:
|
|
199
|
+
* - `anthropic/claude-haiku-4-5` → `{anthropic, claude-haiku-4-5-20251001}`
|
|
200
|
+
* - `gemini/gemini-3.5-flash` → `{google, gemini-3.5-flash}`
|
|
201
|
+
* - `openai/gpt-5.5-...` → `{openai, gpt-5.5-...}`
|
|
202
|
+
* - `bedrock/us.anthropic...` → `{bedrock, us.anthropic...}`
|
|
203
|
+
* - `openrouter/anthropic/claude-3-5-sonnet`
|
|
204
|
+
* → `{openrouter, anthropic/claude-3-5-sonnet}`
|
|
205
|
+
*
|
|
206
|
+
* Used at wire boundaries (ggui.json parser, CLI flag, env var) so
|
|
207
|
+
* existing operator configs keep working without a forced migration.
|
|
208
|
+
*/
|
|
209
|
+
export declare function parseLiteLlmString(s: string): LlmRoute | null;
|
|
210
|
+
/**
|
|
211
|
+
* Serialize an `LlmRoute` to LiteLLM format for outbound observability
|
|
212
|
+
* (Datadog, PostHog, OpenTelemetry `gen_ai.*` semantic conventions
|
|
213
|
+
* recognize LiteLLM IDs). Inverse of {@link parseLiteLlmString}.
|
|
214
|
+
*
|
|
215
|
+
* `{provider: 'anthropic', model: 'claude-haiku-4-5-20251001'}`
|
|
216
|
+
* → `'anthropic/claude-haiku-4-5'`
|
|
217
|
+
*
|
|
218
|
+
* For models without an explicit LITELLM_TO_WIRE mapping (Gemini,
|
|
219
|
+
* OpenAI, OpenRouter), serialization uses the wire model as-is.
|
|
220
|
+
*/
|
|
221
|
+
export declare function toLiteLlmString(route: LlmRoute): string;
|
|
222
|
+
/**
|
|
223
|
+
* Single-entry parser that accepts both canonical (`provider:model`)
|
|
224
|
+
* and LiteLLM (`prefix/model`) formats. Used at wire boundaries that
|
|
225
|
+
* receive unvalidated strings — `ggui.json#generation.model`, CLI
|
|
226
|
+
* `--model` flag, env-var defaults.
|
|
227
|
+
*
|
|
228
|
+
* Tries the canonical form first (no internal `/` ambiguity); if it
|
|
229
|
+
* doesn't parse, falls back to LiteLLM. Returns `null` if neither
|
|
230
|
+
* format produces a valid route.
|
|
231
|
+
*/
|
|
232
|
+
export declare function parseAnyLlmRoute(s: string): LlmRoute | null;
|
|
233
|
+
//# sourceMappingURL=llm-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-route.d.ts","sourceRoot":"","sources":["../../src/types/llm-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,MAAM;;;;;;CA2GT,CAAC;AAMX;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,MAAM,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,SAC1C,YAAY,GACZ,SAAS,GACT,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAC/B,YAAY,CAAC,CAAC,CAAC,CAAC;AAEpB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,QAAQ,GAAG;KACpB,CAAC,IAAI,WAAW,GAAG;QAAE,QAAQ,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;KAAE;CACvD,CAAC,WAAW,CAAC,CAAC;AAMf;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,WAAW,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAChD,QAAQ,EAAE,CAAC,EACX,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC,CAE1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAKzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAMtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CASxE;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAEzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAQjE;AA8CD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgB7D;AAuBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAKvD;AAmBD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAE3D"}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed `LlmRoute` system — the structural-correctness replacement for
|
|
3
|
+
* string-typed model identifiers.
|
|
4
|
+
*
|
|
5
|
+
* Two concepts, one type:
|
|
6
|
+
* - `provider` — the API endpoint you authenticate against, owns its
|
|
7
|
+
* own model namespace. Includes marketplaces (Bedrock, OpenRouter)
|
|
8
|
+
* and direct-author APIs (Claude/Anthropic, OpenAI, Google AI
|
|
9
|
+
* Studio). NOT a separate "platform" dimension — each provider's
|
|
10
|
+
* API surface IS the platform.
|
|
11
|
+
* - `model` — the wire-canonical string for THIS provider. Registry
|
|
12
|
+
* KEY == what goes on the HTTP wire to the provider. No
|
|
13
|
+
* transformation at dispatch — what you write in `MODELS[provider]`
|
|
14
|
+
* is exactly what the API sees.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: three bugs of the same class in one week (#22 CLI
|
|
17
|
+
* sent slash-prefixed `google/gemini-3.5-flash` → Gemini 404; #42
|
|
18
|
+
* mcp-server negotiator sent `anthropic/claude-haiku-4-5` → Anthropic
|
|
19
|
+
* 404; the next one would have been...). The pattern was always
|
|
20
|
+
* "someone wrote a code path that bypassed `getUpstreamModelId`". The
|
|
21
|
+
* typed-route system makes the bug class structurally impossible —
|
|
22
|
+
* `LlmRoute` is a discriminated union, the dispatch is
|
|
23
|
+
* exhaustiveness-checked, and there's no transformation step to
|
|
24
|
+
* forget because the model string IS the wire form.
|
|
25
|
+
*
|
|
26
|
+
* Slice spec: `docs/plans/2026-05-25-llm-route-typed-system.md`
|
|
27
|
+
*/
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// MODELS registry — single source of truth
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Wire-canonical model names per provider. The KEY for each entry is
|
|
33
|
+
* EXACTLY what the provider's API expects on the wire — no
|
|
34
|
+
* transformation, no prefix-strip, no map. Register a new model by
|
|
35
|
+
* adding its wire-canonical string to the matching provider's array.
|
|
36
|
+
*
|
|
37
|
+
* Provider naming uses the COMPANY name for consistency across all
|
|
38
|
+
* providers (`anthropic`, `openai`, `google`) — matches LiteLLM's
|
|
39
|
+
* `anthropic/` prefix + the existing `ANTHROPIC_API_KEY` env var
|
|
40
|
+
* convention. Marketplaces (`bedrock`, `openrouter`) keep their
|
|
41
|
+
* platform name because that IS the company you authenticate against.
|
|
42
|
+
*
|
|
43
|
+
* - `anthropic` — Anthropic's direct API
|
|
44
|
+
* (`api.anthropic.com`). Auth: `ANTHROPIC_API_KEY`.
|
|
45
|
+
* - `openai` — OpenAI's direct API.
|
|
46
|
+
* - `google` — Google AI Studio (`generativelanguage.googleapis.com`).
|
|
47
|
+
* - `bedrock` — AWS Bedrock marketplace. Region prefix (`us.`,
|
|
48
|
+
* `eu.`, `apac.`) is part of the wire name; each region is its
|
|
49
|
+
* own registry entry, not a `{region}` field.
|
|
50
|
+
* - `openrouter` — OpenRouter marketplace. Authors are sub-namespaced
|
|
51
|
+
* in the model string (`<author>/<model>`).
|
|
52
|
+
* - `vertex` — DEFERRED to its own slice. Vertex needs region +
|
|
53
|
+
* projectId + GCP IAM setup; migration is purely additive when
|
|
54
|
+
* ready.
|
|
55
|
+
*/
|
|
56
|
+
export const MODELS = {
|
|
57
|
+
anthropic: [
|
|
58
|
+
// Wire-canonical IDs accepted by api.anthropic.com/v1/messages.
|
|
59
|
+
// Per Anthropic's official models doc (claude.com/docs/about-claude/models/overview):
|
|
60
|
+
// 4.6/4.7 generation dropped the date suffix in the wire ID;
|
|
61
|
+
// Haiku 4.5 still uses the dated form.
|
|
62
|
+
'claude-haiku-4-5-20251001',
|
|
63
|
+
'claude-sonnet-4-6',
|
|
64
|
+
'claude-opus-4-7',
|
|
65
|
+
'claude-opus-4-6',
|
|
66
|
+
],
|
|
67
|
+
openai: [
|
|
68
|
+
// Per OpenAI's model registry (developers.openai.com/api/docs/models/all).
|
|
69
|
+
// Both unversioned aliases AND dated snapshots are valid wire IDs;
|
|
70
|
+
// we enumerate both because operators reasonably use either.
|
|
71
|
+
'gpt-5.5',
|
|
72
|
+
'gpt-5.5-2026-04-23',
|
|
73
|
+
'gpt-5.5-pro',
|
|
74
|
+
'gpt-5.5-pro-2026-04-23',
|
|
75
|
+
'gpt-5.4',
|
|
76
|
+
'gpt-5.4-2026-03-05',
|
|
77
|
+
'gpt-5.4-mini',
|
|
78
|
+
'gpt-5.4-mini-2026-03-17',
|
|
79
|
+
'gpt-5.4-nano',
|
|
80
|
+
'gpt-5.4-nano-2026-03-17',
|
|
81
|
+
'gpt-5.3-codex',
|
|
82
|
+
],
|
|
83
|
+
google: [
|
|
84
|
+
// Per Google AI Studio's model registry (ai.google.dev/gemini-api/docs/models).
|
|
85
|
+
// Stable + the commonly-used previews. The `-preview` suffix is
|
|
86
|
+
// load-bearing on the wire for preview models — Gemini's API
|
|
87
|
+
// rejects the bare name for those.
|
|
88
|
+
'gemini-3.5-flash',
|
|
89
|
+
'gemini-flash-latest',
|
|
90
|
+
'gemini-3.1-flash-lite',
|
|
91
|
+
'gemini-3.1-pro-preview',
|
|
92
|
+
'gemini-3.1-flash-lite-preview',
|
|
93
|
+
'gemini-3-flash-preview',
|
|
94
|
+
],
|
|
95
|
+
bedrock: [
|
|
96
|
+
// AWS cross-region inference profile IDs. Each region is its own
|
|
97
|
+
// wire-canonical entry — no `{region}` field on the route.
|
|
98
|
+
// Per AWS docs the 4.6/4.7 generation dropped `-vN:0`; Haiku 4.5
|
|
99
|
+
// keeps it. Coverage per region (us/eu/apac/global) varies per
|
|
100
|
+
// model — verify on individual model card pages under
|
|
101
|
+
// docs.aws.amazon.com/bedrock/latest/userguide/model-cards.html
|
|
102
|
+
// before locking expanded coverage.
|
|
103
|
+
// Haiku 4.5 — has full us/eu/apac/global coverage per Anthropic docs
|
|
104
|
+
'us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
|
105
|
+
'eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
|
106
|
+
'apac.anthropic.claude-haiku-4-5-20251001-v1:0',
|
|
107
|
+
'global.anthropic.claude-haiku-4-5-20251001-v1:0',
|
|
108
|
+
// Sonnet 4.6 — current generation, full multi-region per Anthropic docs
|
|
109
|
+
'us.anthropic.claude-sonnet-4-6',
|
|
110
|
+
'eu.anthropic.claude-sonnet-4-6',
|
|
111
|
+
'apac.anthropic.claude-sonnet-4-6',
|
|
112
|
+
// Opus 4.7 — current generation, full multi-region per Anthropic docs
|
|
113
|
+
'us.anthropic.claude-opus-4-7',
|
|
114
|
+
'eu.anthropic.claude-opus-4-7',
|
|
115
|
+
'apac.anthropic.claude-opus-4-7',
|
|
116
|
+
'global.anthropic.claude-opus-4-7',
|
|
117
|
+
// Opus 4.6 — legacy generation, US-only profile listed in Anthropic docs
|
|
118
|
+
'us.anthropic.claude-opus-4-6-v1',
|
|
119
|
+
],
|
|
120
|
+
openrouter: [
|
|
121
|
+
// Commonly-used routes get type-level coverage. Arbitrary
|
|
122
|
+
// `<author>/<model>` strings are also accepted at runtime via the
|
|
123
|
+
// `string & {}` escape hatch on the `ModelOf<'openrouter'>` type
|
|
124
|
+
// — OpenRouter hosts hundreds of model permutations and
|
|
125
|
+
// enumerating all of them in source would create a maintenance
|
|
126
|
+
// burden the type system can't repay.
|
|
127
|
+
// Curated from a live fetch of openrouter.ai/api/v1/models (358
|
|
128
|
+
// models on 2026-05-25). Picks favor the latest stable slug per
|
|
129
|
+
// slot, skipping `:free`, dated `-preview-MMDD`, `-fast`, image/
|
|
130
|
+
// audio/vision-only variants. Note OpenRouter uses dot-separator
|
|
131
|
+
// for Anthropic version (`claude-haiku-4.5`) where Anthropic direct
|
|
132
|
+
// uses dash (`claude-haiku-4-5`) — preserve OpenRouter's wire form.
|
|
133
|
+
// Anthropic family
|
|
134
|
+
'anthropic/claude-haiku-4.5',
|
|
135
|
+
'anthropic/claude-sonnet-4.6',
|
|
136
|
+
'anthropic/claude-opus-4.7',
|
|
137
|
+
// OpenAI family
|
|
138
|
+
'openai/gpt-5.5',
|
|
139
|
+
'openai/gpt-5.5-pro',
|
|
140
|
+
'openai/gpt-5.4-mini',
|
|
141
|
+
'openai/gpt-5.4-nano',
|
|
142
|
+
// Google family
|
|
143
|
+
'google/gemini-3.5-flash',
|
|
144
|
+
'google/gemini-3.1-pro-preview',
|
|
145
|
+
'google/gemini-3.1-flash-lite',
|
|
146
|
+
// Frontier alternatives
|
|
147
|
+
'x-ai/grok-4.3',
|
|
148
|
+
// Open-source heavy hitters
|
|
149
|
+
'meta-llama/llama-4-maverick',
|
|
150
|
+
'meta-llama/llama-3.3-70b-instruct',
|
|
151
|
+
'deepseek/deepseek-v4-pro',
|
|
152
|
+
'deepseek/deepseek-r1-0528',
|
|
153
|
+
'qwen/qwen3.7-max',
|
|
154
|
+
'qwen/qwen3-coder',
|
|
155
|
+
'mistralai/mistral-large-2512',
|
|
156
|
+
'openai/gpt-oss-120b',
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Type guards
|
|
161
|
+
// ============================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Runtime type guard for the provider enum. Pairs with the parser at
|
|
164
|
+
* wire boundaries (ggui.json, CLI flags, env vars) where the value
|
|
165
|
+
* arrives as an unvalidated string.
|
|
166
|
+
*/
|
|
167
|
+
export function isLlmProvider(s) {
|
|
168
|
+
return s in MODELS;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Runtime type guard for a known model string under a given provider.
|
|
172
|
+
* Strict-enum check; does NOT accept OpenRouter's arbitrary-string
|
|
173
|
+
* extension — for that, use {@link isValidOpenrouterModel}.
|
|
174
|
+
*/
|
|
175
|
+
export function isKnownModel(provider, model) {
|
|
176
|
+
return MODELS[provider].includes(model);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Validate an OpenRouter model string by shape: `<author>/<model>`
|
|
180
|
+
* where both segments are non-empty and contain only the characters
|
|
181
|
+
* OpenRouter's catalog uses (alphanumerics + `-` + `.` + `_` + `:`).
|
|
182
|
+
*
|
|
183
|
+
* Used for the OpenRouter escape hatch — strings that pass this check
|
|
184
|
+
* are accepted into `LlmRoute` even if not in `MODELS.openrouter[]`.
|
|
185
|
+
* Strings that fail it are rejected at the parser boundary.
|
|
186
|
+
*/
|
|
187
|
+
export function isValidOpenrouterModel(s) {
|
|
188
|
+
// `<author>/<model>` with both segments non-empty.
|
|
189
|
+
// Permissive char class: alphanumerics, `-`, `.`, `_`, `:`, and one
|
|
190
|
+
// `/` separator. OpenRouter's catalog uses all of these.
|
|
191
|
+
return /^[A-Za-z0-9._:-]+\/[A-Za-z0-9._:-]+$/.test(s);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Validate a Bedrock model string by shape. AWS accepts THREE forms:
|
|
195
|
+
*
|
|
196
|
+
* - Cross-region inference profile ids — `<region>.<inner>` where
|
|
197
|
+
* region is `us`/`eu`/`apac`/`global` and inner contains `.`,
|
|
198
|
+
* `-`, `:`, alphanumerics (e.g.
|
|
199
|
+
* `'us.anthropic.claude-haiku-4-5-20251001-v1:0'`).
|
|
200
|
+
* - Bedrock foundation model ids — bare `<vendor>.<model>` form
|
|
201
|
+
* (e.g. `'anthropic.claude-sonnet-4-6'`).
|
|
202
|
+
* - Inference profile ARNs — `'arn:aws:bedrock:...'`.
|
|
203
|
+
*
|
|
204
|
+
* Used for the Bedrock escape hatch — strings passing this check are
|
|
205
|
+
* accepted into `LlmRoute` even if not in `MODELS.bedrock[]`.
|
|
206
|
+
*/
|
|
207
|
+
export function isValidBedrockModel(s) {
|
|
208
|
+
if (s.startsWith('arn:aws:bedrock:'))
|
|
209
|
+
return true;
|
|
210
|
+
// `<segment>.<segment>` with both non-empty + a `.` separator.
|
|
211
|
+
// Permissive char class to cover Bedrock's mix of `.`, `-`, `:`,
|
|
212
|
+
// and alphanumerics.
|
|
213
|
+
return /^[A-Za-z0-9._:-]+\.[A-Za-z0-9._:-]+$/.test(s);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Validate that a (provider, model) pair would construct a valid
|
|
217
|
+
* `LlmRoute`. Two providers have arbitrary-string escape hatches:
|
|
218
|
+
*
|
|
219
|
+
* - `openrouter` — accepts any string passing
|
|
220
|
+
* {@link isValidOpenrouterModel}
|
|
221
|
+
* - `bedrock` — accepts any string passing {@link isValidBedrockModel}
|
|
222
|
+
*
|
|
223
|
+
* Every other provider requires the model to be in
|
|
224
|
+
* `MODELS[provider]`.
|
|
225
|
+
*/
|
|
226
|
+
export function isValidLlmRoute(provider, model) {
|
|
227
|
+
if (!isLlmProvider(provider))
|
|
228
|
+
return false;
|
|
229
|
+
if (provider === 'openrouter') {
|
|
230
|
+
return isKnownModel('openrouter', model) || isValidOpenrouterModel(model);
|
|
231
|
+
}
|
|
232
|
+
if (provider === 'bedrock') {
|
|
233
|
+
return isKnownModel('bedrock', model) || isValidBedrockModel(model);
|
|
234
|
+
}
|
|
235
|
+
return isKnownModel(provider, model);
|
|
236
|
+
}
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Canonical serialization — `provider:model`
|
|
239
|
+
// ============================================================================
|
|
240
|
+
/**
|
|
241
|
+
* Canonical wire format separator. `:` chosen over `/` because
|
|
242
|
+
* `/` collides with OpenRouter's own `<author>/<model>` form
|
|
243
|
+
* (`openrouter/anthropic/claude-3-5-sonnet` has TWO slashes; ambiguous
|
|
244
|
+
* to split). Bedrock's model string also contains `.` and `:` — `:`
|
|
245
|
+
* is fine as the OUTER separator because the inner `:` is always
|
|
246
|
+
* preceded by a `.` or alphanumerics; the FIRST `:` in the serialized
|
|
247
|
+
* string is unambiguously the provider/model boundary.
|
|
248
|
+
*/
|
|
249
|
+
const CANONICAL_SEPARATOR = ':';
|
|
250
|
+
/**
|
|
251
|
+
* Serialize an `LlmRoute` to the canonical `provider:model` string.
|
|
252
|
+
* Round-trip with {@link parseLlmRoute}. Used for human-readable
|
|
253
|
+
* logging, `ggui.json` config values, and CLI `--model` flags.
|
|
254
|
+
*
|
|
255
|
+
* Examples:
|
|
256
|
+
* `{provider: 'anthropic', model: 'claude-haiku-4-5-20251001'}`
|
|
257
|
+
* → `'anthropic:claude-haiku-4-5-20251001'`
|
|
258
|
+
* `{provider: 'bedrock', model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0'}`
|
|
259
|
+
* → `'bedrock:us.anthropic.claude-haiku-4-5-20251001-v1:0'`
|
|
260
|
+
* `{provider: 'openrouter', model: 'anthropic/claude-3-5-sonnet'}`
|
|
261
|
+
* → `'openrouter:anthropic/claude-3-5-sonnet'`
|
|
262
|
+
*/
|
|
263
|
+
export function serializeLlmRoute(route) {
|
|
264
|
+
return `${route.provider}${CANONICAL_SEPARATOR}${route.model}`;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Parse a canonical `provider:model` string into an `LlmRoute`.
|
|
268
|
+
* Returns `null` if the string isn't well-formed or if (provider,
|
|
269
|
+
* model) wouldn't construct a valid route. Permissive on the model
|
|
270
|
+
* side for OpenRouter — accepts any string passing
|
|
271
|
+
* {@link isValidOpenrouterModel} even when not in `MODELS.openrouter[]`.
|
|
272
|
+
*
|
|
273
|
+
* Round-trip with {@link serializeLlmRoute}.
|
|
274
|
+
*/
|
|
275
|
+
export function parseLlmRoute(serialized) {
|
|
276
|
+
const sep = serialized.indexOf(CANONICAL_SEPARATOR);
|
|
277
|
+
if (sep <= 0)
|
|
278
|
+
return null;
|
|
279
|
+
const provider = serialized.substring(0, sep);
|
|
280
|
+
const model = serialized.substring(sep + 1);
|
|
281
|
+
if (model.length === 0)
|
|
282
|
+
return null;
|
|
283
|
+
if (!isValidLlmRoute(provider, model))
|
|
284
|
+
return null;
|
|
285
|
+
return { provider, model };
|
|
286
|
+
}
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// LiteLLM back-compat parser/serializer
|
|
289
|
+
// ============================================================================
|
|
290
|
+
/**
|
|
291
|
+
* Map from LiteLLM transport prefix → our `LlmProvider` enum. Used by
|
|
292
|
+
* {@link parseLiteLlmString} to accept legacy ggui.json files and
|
|
293
|
+
* ecosystem inputs without forcing a migration on every operator.
|
|
294
|
+
*
|
|
295
|
+
* Note: LiteLLM separates `gemini/` (AI Studio) from `vertex_ai/`
|
|
296
|
+
* (enterprise Vertex). We don't have a Vertex variant in this slice;
|
|
297
|
+
* `vertex_ai/` parsing falls through to `null` until Vertex lands.
|
|
298
|
+
*/
|
|
299
|
+
const LITELLM_PROVIDER_PREFIX_MAP = {
|
|
300
|
+
anthropic: 'anthropic',
|
|
301
|
+
gemini: 'google',
|
|
302
|
+
openai: 'openai',
|
|
303
|
+
bedrock: 'bedrock',
|
|
304
|
+
openrouter: 'openrouter',
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* LiteLLM canonical → wire-canonical model mappings for providers
|
|
308
|
+
* whose LiteLLM-form differs from the wire-canonical form.
|
|
309
|
+
* Anthropic's LiteLLM strings (`anthropic/claude-haiku-4-5`) map to
|
|
310
|
+
* dated wire IDs (`claude-haiku-4-5-20251001`).
|
|
311
|
+
*
|
|
312
|
+
* For providers where LiteLLM form == wire form (Gemini, OpenAI,
|
|
313
|
+
* OpenRouter), no mapping is needed — the parser passes the model
|
|
314
|
+
* through after stripping the prefix.
|
|
315
|
+
*/
|
|
316
|
+
const LITELLM_TO_WIRE = {
|
|
317
|
+
// Anthropic's 4.6/4.7 generation wire IDs ARE the short LiteLLM
|
|
318
|
+
// form (no date suffix), so no mapping needed for those. Only
|
|
319
|
+
// Haiku 4.5 keeps the dated wire form, so it needs an explicit
|
|
320
|
+
// LiteLLM-short → wire-dated entry. Adding a new mapping here is
|
|
321
|
+
// only required when the LiteLLM short form differs from the wire
|
|
322
|
+
// ID (typically only Anthropic Haiku-class models, which keep
|
|
323
|
+
// dated IDs per Anthropic convention).
|
|
324
|
+
anthropic: {
|
|
325
|
+
'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Parse a LiteLLM-format string (`<prefix>/<model>` or
|
|
330
|
+
* `<prefix>/<sub>/<model>` for OpenRouter) into an `LlmRoute`.
|
|
331
|
+
* Returns `null` if the prefix is unknown or the resulting route
|
|
332
|
+
* wouldn't construct.
|
|
333
|
+
*
|
|
334
|
+
* Supports the historical formats ggui has used:
|
|
335
|
+
* - `anthropic/claude-haiku-4-5` → `{anthropic, claude-haiku-4-5-20251001}`
|
|
336
|
+
* - `gemini/gemini-3.5-flash` → `{google, gemini-3.5-flash}`
|
|
337
|
+
* - `openai/gpt-5.5-...` → `{openai, gpt-5.5-...}`
|
|
338
|
+
* - `bedrock/us.anthropic...` → `{bedrock, us.anthropic...}`
|
|
339
|
+
* - `openrouter/anthropic/claude-3-5-sonnet`
|
|
340
|
+
* → `{openrouter, anthropic/claude-3-5-sonnet}`
|
|
341
|
+
*
|
|
342
|
+
* Used at wire boundaries (ggui.json parser, CLI flag, env var) so
|
|
343
|
+
* existing operator configs keep working without a forced migration.
|
|
344
|
+
*/
|
|
345
|
+
export function parseLiteLlmString(s) {
|
|
346
|
+
const firstSlash = s.indexOf('/');
|
|
347
|
+
if (firstSlash <= 0)
|
|
348
|
+
return null;
|
|
349
|
+
const prefix = s.substring(0, firstSlash);
|
|
350
|
+
const provider = LITELLM_PROVIDER_PREFIX_MAP[prefix];
|
|
351
|
+
if (!provider)
|
|
352
|
+
return null;
|
|
353
|
+
// For OpenRouter the model is everything after the first slash
|
|
354
|
+
// (which itself includes a `<author>/<model>` sub-path); for
|
|
355
|
+
// every other provider, it's also everything after the first slash
|
|
356
|
+
// but with no internal `/`.
|
|
357
|
+
const rawModel = s.substring(firstSlash + 1);
|
|
358
|
+
if (rawModel.length === 0)
|
|
359
|
+
return null;
|
|
360
|
+
// Apply LiteLLM → wire mapping if one exists for this provider.
|
|
361
|
+
const mapped = LITELLM_TO_WIRE[provider]?.[rawModel] ?? rawModel;
|
|
362
|
+
if (!isValidLlmRoute(provider, mapped))
|
|
363
|
+
return null;
|
|
364
|
+
return { provider, model: mapped };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Inverse mapping for {@link toLiteLlmString} — wire-canonical model
|
|
368
|
+
* back to the LiteLLM short form for a provider's LiteLLM serialization.
|
|
369
|
+
* Built lazily by inverting LITELLM_TO_WIRE on first use.
|
|
370
|
+
*/
|
|
371
|
+
let WIRE_TO_LITELLM_CACHE = null;
|
|
372
|
+
function getWireToLitellm() {
|
|
373
|
+
if (WIRE_TO_LITELLM_CACHE)
|
|
374
|
+
return WIRE_TO_LITELLM_CACHE;
|
|
375
|
+
const out = {};
|
|
376
|
+
for (const [provider, map] of Object.entries(LITELLM_TO_WIRE)) {
|
|
377
|
+
if (!map)
|
|
378
|
+
continue;
|
|
379
|
+
const inverted = {};
|
|
380
|
+
for (const [litellm, wire] of Object.entries(map)) {
|
|
381
|
+
inverted[wire] = litellm;
|
|
382
|
+
}
|
|
383
|
+
out[provider] = inverted;
|
|
384
|
+
}
|
|
385
|
+
WIRE_TO_LITELLM_CACHE = out;
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Serialize an `LlmRoute` to LiteLLM format for outbound observability
|
|
390
|
+
* (Datadog, PostHog, OpenTelemetry `gen_ai.*` semantic conventions
|
|
391
|
+
* recognize LiteLLM IDs). Inverse of {@link parseLiteLlmString}.
|
|
392
|
+
*
|
|
393
|
+
* `{provider: 'anthropic', model: 'claude-haiku-4-5-20251001'}`
|
|
394
|
+
* → `'anthropic/claude-haiku-4-5'`
|
|
395
|
+
*
|
|
396
|
+
* For models without an explicit LITELLM_TO_WIRE mapping (Gemini,
|
|
397
|
+
* OpenAI, OpenRouter), serialization uses the wire model as-is.
|
|
398
|
+
*/
|
|
399
|
+
export function toLiteLlmString(route) {
|
|
400
|
+
const prefix = providerLiteLlmPrefix(route.provider);
|
|
401
|
+
const inverseMap = getWireToLitellm()[route.provider];
|
|
402
|
+
const modelForLitellm = inverseMap?.[route.model] ?? route.model;
|
|
403
|
+
return `${prefix}/${modelForLitellm}`;
|
|
404
|
+
}
|
|
405
|
+
function providerLiteLlmPrefix(provider) {
|
|
406
|
+
// Find the LiteLLM prefix that maps to this provider. There's at
|
|
407
|
+
// most one prefix per provider in LITELLM_PROVIDER_PREFIX_MAP today.
|
|
408
|
+
for (const [prefix, p] of Object.entries(LITELLM_PROVIDER_PREFIX_MAP)) {
|
|
409
|
+
if (p === provider)
|
|
410
|
+
return prefix;
|
|
411
|
+
}
|
|
412
|
+
// Defensive: every provider in MODELS should have a LiteLLM prefix.
|
|
413
|
+
// If not, fall through to the provider's own name (degrades gracefully
|
|
414
|
+
// for observability — the trace just won't match LiteLLM's exact
|
|
415
|
+
// wording, but it still names the provider).
|
|
416
|
+
return provider;
|
|
417
|
+
}
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Combined parser (accepts either format)
|
|
420
|
+
// ============================================================================
|
|
421
|
+
/**
|
|
422
|
+
* Single-entry parser that accepts both canonical (`provider:model`)
|
|
423
|
+
* and LiteLLM (`prefix/model`) formats. Used at wire boundaries that
|
|
424
|
+
* receive unvalidated strings — `ggui.json#generation.model`, CLI
|
|
425
|
+
* `--model` flag, env-var defaults.
|
|
426
|
+
*
|
|
427
|
+
* Tries the canonical form first (no internal `/` ambiguity); if it
|
|
428
|
+
* doesn't parse, falls back to LiteLLM. Returns `null` if neither
|
|
429
|
+
* format produces a valid route.
|
|
430
|
+
*/
|
|
431
|
+
export function parseAnyLlmRoute(s) {
|
|
432
|
+
return parseLlmRoute(s) ?? parseLiteLlmString(s);
|
|
433
|
+
}
|