@ggui-ai/protocol 0.15.0 → 0.16.0

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.
Files changed (75) hide show
  1. package/dist/errors/domain-error.d.ts +70 -0
  2. package/dist/errors/domain-error.d.ts.map +1 -0
  3. package/dist/errors/domain-error.js +118 -0
  4. package/dist/gadgets/stdlib-gadgets.d.ts +1 -1
  5. package/dist/gadgets/stdlib-gadgets.js +1 -1
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -2
  9. package/dist/integrations/mcp-apps.d.ts +33 -7
  10. package/dist/integrations/mcp-apps.d.ts.map +1 -1
  11. package/dist/integrations/mcp-apps.js +13 -13
  12. package/dist/integrations/overlay-hash.d.ts +19 -0
  13. package/dist/integrations/overlay-hash.d.ts.map +1 -0
  14. package/dist/integrations/overlay-hash.js +29 -0
  15. package/dist/integrations/theme-binding.d.ts +14 -12
  16. package/dist/integrations/theme-binding.d.ts.map +1 -1
  17. package/dist/integrations/theme-binding.js +10 -6
  18. package/dist/registry/blueprint-key.d.ts.map +1 -1
  19. package/dist/registry/blueprint-key.js +4 -3
  20. package/dist/schemas/app-theme.d.ts +46 -10
  21. package/dist/schemas/app-theme.d.ts.map +1 -1
  22. package/dist/schemas/app-theme.js +108 -40
  23. package/dist/schemas/blueprint.d.ts +2 -1
  24. package/dist/schemas/blueprint.d.ts.map +1 -1
  25. package/dist/schemas/blueprint.js +15 -3
  26. package/dist/schemas/data-contract.d.ts +1 -1
  27. package/dist/schemas/data-contract.js +1 -1
  28. package/dist/schemas/handshake-suggestion.d.ts.map +1 -1
  29. package/dist/schemas/handshake-suggestion.js +8 -1
  30. package/dist/schemas/mcp.d.ts +48 -22
  31. package/dist/schemas/mcp.d.ts.map +1 -1
  32. package/dist/schemas/mcp.js +44 -15
  33. package/dist/schemas/ops-blueprint.d.ts +4 -4
  34. package/dist/schemas/ops-blueprint.d.ts.map +1 -1
  35. package/dist/schemas/ops-blueprint.js +13 -8
  36. package/dist/schemas/render-input-envelope.js +1 -1
  37. package/dist/types/blueprint-source.d.ts +27 -7
  38. package/dist/types/blueprint-source.d.ts.map +1 -1
  39. package/dist/types/blueprint-source.js +19 -3
  40. package/dist/types/blueprint.d.ts +2 -2
  41. package/dist/types/blueprint.d.ts.map +1 -1
  42. package/dist/types/data-contract.d.ts +3 -3
  43. package/dist/types/domain-error-codes.d.ts +167 -0
  44. package/dist/types/domain-error-codes.d.ts.map +1 -0
  45. package/dist/types/domain-error-codes.js +170 -0
  46. package/dist/types/handshake-suggestion.d.ts +2 -2
  47. package/dist/types/live-channel.d.ts +1 -1
  48. package/dist/types/llm-route.d.ts +59 -2
  49. package/dist/types/llm-route.d.ts.map +1 -1
  50. package/dist/types/llm-route.js +62 -16
  51. package/dist/types/llm.d.ts +22 -2
  52. package/dist/types/llm.d.ts.map +1 -1
  53. package/dist/types/llm.js +29 -2
  54. package/dist/types/mcp.d.ts +31 -39
  55. package/dist/types/mcp.d.ts.map +1 -1
  56. package/dist/types/mcp.js +22 -10
  57. package/dist/types/refusal-codes.d.ts +31 -93
  58. package/dist/types/refusal-codes.d.ts.map +1 -1
  59. package/dist/types/refusal-codes.js +41 -96
  60. package/dist/types/render.d.ts +3 -3
  61. package/dist/types/render.d.ts.map +1 -1
  62. package/dist/validation/contract-validator.d.ts +8 -2
  63. package/dist/validation/contract-validator.d.ts.map +1 -1
  64. package/dist/validation/contract-validator.js +12 -4
  65. package/dist/validation/lint-contract.d.ts +2 -2
  66. package/dist/validation/lint-contract.d.ts.map +1 -1
  67. package/dist/validation/lint-contract.js +6 -4
  68. package/dist/validation/schema-subset.d.ts +3 -3
  69. package/dist/version.d.ts +357 -6
  70. package/dist/version.d.ts.map +1 -1
  71. package/dist/version.js +356 -5
  72. package/package.json +1 -1
  73. package/dist/envelope-adapters.d.ts +0 -12
  74. package/dist/envelope-adapters.d.ts.map +0 -1
  75. package/dist/envelope-adapters.js +0 -30
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The Plane-2 registry — the ONLY list of domain-error slugs (ggui#880).
3
+ *
4
+ * SPEC §7.9 Plane 2: a recoverable, contract-level failure the caller can
5
+ * fix — a missing session or handshake, props that do not satisfy the
6
+ * contract, an undeclared channel. The MCP SDK ships a thrown handler error
7
+ * to the agent as `{content: [{type: 'text', text: error.message}],
8
+ * isError: true}` and nothing else, so the slug travels as the LEADING
9
+ * token of that text — `<code>: <detail>` — composed by the `DomainError`
10
+ * base in `../errors/domain-error` from a code in this registry. A reader
11
+ * branches on `text.startsWith(code + ': ')` for a registered code and on
12
+ * nothing else.
13
+ *
14
+ * Same posture as the refusal registry in `./refusal-codes`: `code === key`
15
+ * is forced at the definer, the code type is derived from the keys, and
16
+ * the two registries are disjoint (pinned) — one code names one plane.
17
+ */
18
+ /**
19
+ * How a caller recovers from a Plane-2 failure.
20
+ *
21
+ * - `retry-same-id` — fix the input and retry on the SAME handshakeId /
22
+ * sessionId; nothing was consumed.
23
+ * - `re-mint` — the id itself is gone (unknown, consumed, expired,
24
+ * another app's); mint a fresh one (`ggui_handshake`, then `ggui_render`).
25
+ * - `later` — a dependency the server fetches was unavailable; time
26
+ * restores it, the input was fine.
27
+ */
28
+ export const DOMAIN_ERROR_RECOVERIES = ['retry-same-id', 're-mint', 'later'];
29
+ /**
30
+ * The registry is the ONLY list of domain-error codes: {@link DomainErrorCode}
31
+ * is derived from the keys below, and this definer forces every row's
32
+ * `code` to equal its key at the type level.
33
+ */
34
+ function defineDomainErrorRegistry(rows) {
35
+ return rows;
36
+ }
37
+ /**
38
+ * The registry with its per-key literal types kept (ggui#889):
39
+ * `DOMAIN_ERROR_ROWS.session_not_found.code` is `'session_not_found'`,
40
+ * its `recovery` is `'re-mint'`. {@link DOMAIN_ERROR_REGISTRY} is the
41
+ * same object read through the normalized row type.
42
+ */
43
+ export const DOMAIN_ERROR_ROWS = /* @__PURE__ */ defineDomainErrorRegistry({
44
+ // ── identity ──────────────────────────────────────────────────────
45
+ session_not_found: {
46
+ code: 'session_not_found',
47
+ tools: [
48
+ 'ggui_consume',
49
+ 'ggui_get_session',
50
+ 'ggui_get_render_source',
51
+ 'ggui_update',
52
+ 'ggui_amend',
53
+ 'ggui_emit',
54
+ 'ggui_runtime_pull',
55
+ ],
56
+ recovery: 're-mint',
57
+ emitter: 'the session lookup on every session-scoped tool',
58
+ description: 'The sessionId names no session this caller can reach — never minted, expired, closed, or owned by another app (cross-app access surfaces uniformly). Re-handshake and re-render to mint a fresh one.',
59
+ },
60
+ handshake_not_found: {
61
+ code: 'handshake_not_found',
62
+ tools: ['ggui_render'],
63
+ recovery: 're-mint',
64
+ emitter: 'the handshake-record lookup at ggui_render',
65
+ description: 'The handshakeId is unknown, already consumed by a render, or expired. Call ggui_handshake again for a fresh id, then render with the new pair.',
66
+ },
67
+ // ── the contract gate ─────────────────────────────────────────────
68
+ contract_violation: {
69
+ code: 'contract_violation',
70
+ tools: ['ggui_render', 'ggui_update', 'ggui_amend', 'ggui_emit'],
71
+ recovery: 'retry-same-id',
72
+ emitter: 'the props / payload validator against the contract',
73
+ description: "Props or a payload do not satisfy the contract's spec — a missing required prop included. Fix the values and retry with the same id; nothing was consumed.",
74
+ },
75
+ schema_mismatch_error: {
76
+ code: 'schema_mismatch_error',
77
+ tools: ['ggui_render'],
78
+ recovery: 'retry-same-id',
79
+ emitter: 'the schema-compatibility check between the contract and the registered tool shapes',
80
+ description: "An actionSpec or streamSpec schema is not a subset of the named tool's registered shape. Adjust the contract or the tool, then retry with the same handshakeId.",
81
+ },
82
+ contract_validation_failed: {
83
+ code: 'contract_validation_failed',
84
+ tools: ['ggui_render'],
85
+ recovery: 'retry-same-id',
86
+ emitter: 'the contract lint gate (shape, retired fields, schema metadata, cross-references, schema compatibility)',
87
+ description: 'The contract failed the lint gate; the detail names the category and the finding. Fix the contract and retry with the same handshakeId.',
88
+ },
89
+ override_contract_invalid: {
90
+ code: 'override_contract_invalid',
91
+ tools: ['ggui_render'],
92
+ recovery: 'retry-same-id',
93
+ emitter: 'the override gate at ggui_render',
94
+ description: 'override.contract failed validation — an override commits the caller to a conforming contract. Drop the override and render the suggestion, or fix the draft.',
95
+ },
96
+ // ── the live channel ──────────────────────────────────────────────
97
+ channel_not_declared: {
98
+ code: 'channel_not_declared',
99
+ tools: ['ggui_emit'],
100
+ recovery: 'retry-same-id',
101
+ emitter: "ggui_emit's channel check against the contract's streamSpec",
102
+ description: "The channel is not declared by the session's contract. Emit on a declared channel; the detail lists them.",
103
+ },
104
+ invalid_complete: {
105
+ code: 'invalid_complete',
106
+ tools: ['ggui_emit'],
107
+ recovery: 'retry-same-id',
108
+ emitter: "ggui_emit's completion check",
109
+ description: 'A completion was signalled for a channel that cannot complete in its current state. Emit on the channel first, or omit the completion.',
110
+ },
111
+ // ── the gadget gate ───────────────────────────────────────────────
112
+ gadget_not_registered: {
113
+ code: 'gadget_not_registered',
114
+ tools: ['ggui_render'],
115
+ recovery: 'retry-same-id',
116
+ emitter: 'the gadget gate at ggui_render',
117
+ description: "A clientCapabilities.gadgets reference names an export the app's gadget registry does not carry. Drop the reference or fix the name; the detail suggests the nearest match.",
118
+ },
119
+ gadget_package_mismatch: {
120
+ code: 'gadget_package_mismatch',
121
+ tools: ['ggui_render'],
122
+ recovery: 'retry-same-id',
123
+ emitter: 'the gadget gate at ggui_render',
124
+ description: 'A gadget export is registered, but only under a different package than the contract requested. Request the registered package; the detail names it.',
125
+ },
126
+ gadget_public_env_missing: {
127
+ code: 'gadget_public_env_missing',
128
+ tools: ['ggui_render'],
129
+ recovery: 'retry-same-id',
130
+ emitter: 'the gadget gate at ggui_render',
131
+ description: "A registered wrapper requires a public-env key the app has not set. Set the key on the app's public env, then retry.",
132
+ },
133
+ duplicate_gadget_hook: {
134
+ code: 'duplicate_gadget_hook',
135
+ tools: ['ggui_render'],
136
+ recovery: 'retry-same-id',
137
+ emitter: 'the gadget gate at ggui_render',
138
+ description: 'Two gadget packages export the same name; the generated import would collide. Reference one of them.',
139
+ },
140
+ gadget_types_fetch_failed: {
141
+ code: 'gadget_types_fetch_failed',
142
+ tools: ['ggui_render'],
143
+ recovery: 'later',
144
+ emitter: 'the gadget type fetch before cold generation',
145
+ description: "Fetching a gadget package's types failed; the input was fine. Retry after a short delay.",
146
+ },
147
+ gadget_catalog_integrity: {
148
+ code: 'gadget_catalog_integrity',
149
+ tools: ['ggui_render'],
150
+ recovery: 'retry-same-id',
151
+ emitter: "the app's gadget catalog resolver at ggui_render",
152
+ description: "The app's gadget catalog is inconsistent with its registrations. Repair the catalog, then retry with the same handshakeId.",
153
+ },
154
+ // ── the blueprint registry ────────────────────────────────────────
155
+ blueprint_rejected: {
156
+ code: 'blueprint_rejected',
157
+ tools: ['ggui_render'],
158
+ recovery: 'retry-same-id',
159
+ emitter: 'the blueprint registry on registration',
160
+ description: "The blueprint was rejected by the registry's error-level findings; the detail lists them. Fix the contract and retry with the same handshakeId.",
161
+ },
162
+ });
163
+ /** The registry consumers read. */
164
+ export const DOMAIN_ERROR_REGISTRY = DOMAIN_ERROR_ROWS;
165
+ /** Whether `value` is a registered domain-error code. */
166
+ export function isDomainErrorCode(value) {
167
+ return Object.hasOwn(DOMAIN_ERROR_ROWS, value);
168
+ }
169
+ /** Every registered code, in registry order. */
170
+ export const DOMAIN_ERROR_CODES = Object.keys(DOMAIN_ERROR_ROWS).filter(isDomainErrorCode);
@@ -89,12 +89,12 @@ export interface BlueprintDraft {
89
89
  readonly seedPrompt?: string;
90
90
  };
91
91
  /**
92
- * Generator slug hint (e.g. `'ui-gen-advanced-opus-4-7'`). The
92
+ * Generator identity hint (e.g. `'ui-gen-advanced'` — de-modeled, ggui#924). The
93
93
  * server resolves the effective generator as:
94
94
  *
95
95
  * 1. Operator app-pin (`App.pinnedGenerator`) — wins if set.
96
96
  * 2. This hint — if registered in the GeneratorRegistry.
97
- * 3. Registry default (`ui-gen-default-haiku-4-5`).
97
+ * 3. Registry default (`ui-gen-default`).
98
98
  *
99
99
  * Hint-only; unknown slugs fall through to the registry default.
100
100
  */
@@ -23,7 +23,7 @@ import type { JsonObject, JsonValue, StreamChannelMode } from './data-contract.j
23
23
  export interface SubscribePayload {
24
24
  sessionId: string;
25
25
  /**
26
- * App (tenant) identity. OPTIONAL — absent ⇒ the server resolves the
26
+ * App identity — the `appId` the session belongs to. OPTIONAL — absent ⇒ the server resolves the
27
27
  * caller's identity-default app, the same resolution rule the MCP
28
28
  * route uses: a token binding (`wsToken` binds `(sessionId, appId)`)
29
29
  * wins, then the deployment's identity → appId mapping, then the
@@ -52,7 +52,7 @@
52
52
  */
53
53
  export declare const MODELS: {
54
54
  readonly anthropic: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-haiku-4-5-20251001", "claude-opus-4-8", "claude-sonnet-4-6", "claude-opus-4-7", "claude-opus-4-6"];
55
- readonly openai: readonly ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "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"];
55
+ readonly openai: readonly ["gpt-6-astra", "gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "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
56
  readonly google: readonly ["gemini-3.7-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-flash-latest", "gemini-3.1-flash-lite", "gemini-3.1-pro-preview", "gemini-3.1-flash-lite-preview", "gemini-3-flash-preview"];
57
57
  readonly bedrock: readonly ["anthropic.claude-fable-5-1", "anthropic.claude-fable-5", "anthropic.claude-opus-5", "anthropic.claude-sonnet-5", "anthropic.claude-opus-4-8", "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
58
  readonly openrouter: readonly ["anthropic/claude-fable-5", "anthropic/claude-opus-5", "anthropic/claude-sonnet-5", "anthropic/claude-haiku-4.5", "anthropic/claude-sonnet-4.6", "anthropic/claude-opus-4.7", "openai/gpt-5.6-sol", "openai/gpt-5.6-terra", "openai/gpt-5.6-luna", "openai/gpt-5.5", "openai/gpt-5.5-pro", "openai/gpt-5.4-mini", "openai/gpt-5.4-nano", "google/gemini-3.6-flash", "google/gemini-3.5-flash", "google/gemini-3.5-flash-lite", "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"];
@@ -189,6 +189,25 @@ export declare function serializeLlmRoute(route: LlmRoute): string;
189
189
  * Round-trip with {@link serializeLlmRoute}.
190
190
  */
191
191
  export declare function parseLlmRoute(serialized: string): LlmRoute | null;
192
+ /**
193
+ * Map from LiteLLM transport prefix → our `LlmProvider` enum. Used by
194
+ * {@link parseLiteLlmString} to accept legacy ggui.json files and
195
+ * ecosystem inputs without forcing a migration on every operator.
196
+ *
197
+ * Note: LiteLLM separates `gemini/` (AI Studio) from `vertex_ai/`
198
+ * (enterprise Vertex). We don't have a Vertex variant in this slice;
199
+ * `vertex_ai/` parsing falls through to `null` until Vertex lands.
200
+ */
201
+ /**
202
+ * The prefixes a {@link ModelRef} may start with — the LiteLLM spellings,
203
+ * and the model registry's. The single source: the prefix→provider map
204
+ * below is keyed by it (exhaustive by type), and the JSON-Schema-facing
205
+ * `pattern` in `llmBlueprintSourceSchema` is built from it.
206
+ */
207
+ export declare const MODEL_REF_PREFIXES: readonly ["anthropic", "gemini", "openai", "bedrock", "openrouter"];
208
+ export type ModelRefPrefix = (typeof MODEL_REF_PREFIXES)[number];
209
+ /** Whether `s` is one of {@link MODEL_REF_PREFIXES}. */
210
+ export declare function isModelRefPrefix(s: string): s is ModelRefPrefix;
192
211
  /**
193
212
  * Parse a LiteLLM-format string (`<prefix>/<model>` or
194
213
  * `<prefix>/<sub>/<model>` for OpenRouter) into an `LlmRoute`.
@@ -218,7 +237,7 @@ export declare function parseLiteLlmString(s: string): LlmRoute | null;
218
237
  * For models without an explicit LITELLM_TO_WIRE mapping (Gemini,
219
238
  * OpenAI, OpenRouter), serialization uses the wire model as-is.
220
239
  */
221
- export declare function toLiteLlmString(route: LlmRoute): string;
240
+ export declare function toLiteLlmString(route: LlmRoute): ModelRef;
222
241
  /**
223
242
  * Single-entry parser that accepts both canonical (`provider:model`)
224
243
  * and LiteLLM (`prefix/model`) formats. Used at wire boundaries that
@@ -230,4 +249,42 @@ export declare function toLiteLlmString(route: LlmRoute): string;
230
249
  * format produces a valid route.
231
250
  */
232
251
  export declare function parseAnyLlmRoute(s: string): LlmRoute | null;
252
+ /**
253
+ * A route rendered as ONE string in the model registry's spelling —
254
+ * the LiteLLM form `<prefix>/<model>` (#818: one model-id vocabulary,
255
+ * two separators). Every registry id (`ModelId`, e.g.
256
+ * `anthropic/claude-haiku-4-5`, `gemini/gemini-3.5-flash`) is the ref
257
+ * of the route it names, so `ModelId ⊂ ModelRef`; a bedrock inference
258
+ * profile or an unlisted OpenRouter model is a `ModelRef` the registry
259
+ * does not list. Where a wire model has a LiteLLM alias
260
+ * (`claude-haiku-4-5-20251001` ↔ `claude-haiku-4-5`) the ref is the
261
+ * ALIAS — the dated wire id is not a ref spelling.
262
+ *
263
+ * Split at the FIRST `/`: prefixes contain no slash, so
264
+ * `openrouter/anthropic/claude-3.5-sonnet` is unambiguous.
265
+ *
266
+ * Used where a record must say which model a run used and readers must
267
+ * recover the route: `LlmBlueprintSource.model`.
268
+ */
269
+ export type ModelRef = `${ModelRefPrefix}/${string}`;
270
+ /**
271
+ * The one composer of a {@link ModelRef}: render the route the run used,
272
+ * inverting the LiteLLM alias map so a resolved `{anthropic,
273
+ * claude-haiku-4-5-20251001}` renders as `anthropic/claude-haiku-4-5` —
274
+ * the registry id. Total: every `LlmRoute` has a ref. Feed it the ROUTE
275
+ * the call used; the ref is the same whether the route was configured
276
+ * as the alias or already resolved.
277
+ */
278
+ export declare function modelRefOfRoute(route: LlmRoute): ModelRef;
279
+ /**
280
+ * Recover the route from a {@link ModelRef}. `null` unless `s` is a
281
+ * LiteLLM-form string that parses ({@link parseLiteLlmString}) AND is
282
+ * the spelling {@link modelRefOfRoute} produces for that route — a ref
283
+ * has one spelling, so the dated wire id of an aliased model and a
284
+ * wrong prefix are refused here even though the lenient wire parsers
285
+ * accept them.
286
+ */
287
+ export declare function parseModelRef(s: string): LlmRoute | null;
288
+ /** Whether `s` is a {@link ModelRef} — see {@link parseModelRef}. */
289
+ export declare function isModelRef(s: string): s is ModelRef;
233
290
  //# sourceMappingURL=llm-route.d.ts.map
@@ -1 +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;;;;;;CAuJT,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,CAajE;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"}
1
+ {"version":3,"file":"llm-route.d.ts","sourceRoot":"","sources":["../../src/types/llm-route.ts"],"names":[],"mappings":"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"}
@@ -78,6 +78,7 @@ export const MODELS = {
78
78
  // reasoning), `terra` (balanced), `luna` (fast/cost). The bare
79
79
  // `gpt-5.6` alias routes to Sol on OpenAI's side. No dated
80
80
  // siblings registered at adoption time.
81
+ 'gpt-6-astra', // ggui#977 (2026-09-09): listed by `GET /v1/models/gpt-6-astra` → 200
81
82
  'gpt-5.6',
82
83
  'gpt-5.6-sol',
83
84
  'gpt-5.6-terra',
@@ -345,6 +346,13 @@ export function parseLlmRoute(serialized) {
345
346
  * (enterprise Vertex). We don't have a Vertex variant in this slice;
346
347
  * `vertex_ai/` parsing falls through to `null` until Vertex lands.
347
348
  */
349
+ /**
350
+ * The prefixes a {@link ModelRef} may start with — the LiteLLM spellings,
351
+ * and the model registry's. The single source: the prefix→provider map
352
+ * below is keyed by it (exhaustive by type), and the JSON-Schema-facing
353
+ * `pattern` in `llmBlueprintSourceSchema` is built from it.
354
+ */
355
+ export const MODEL_REF_PREFIXES = ['anthropic', 'gemini', 'openai', 'bedrock', 'openrouter'];
348
356
  const LITELLM_PROVIDER_PREFIX_MAP = {
349
357
  anthropic: 'anthropic',
350
358
  gemini: 'google',
@@ -352,6 +360,26 @@ const LITELLM_PROVIDER_PREFIX_MAP = {
352
360
  bedrock: 'bedrock',
353
361
  openrouter: 'openrouter',
354
362
  };
363
+ /**
364
+ * Provider → its ONE LiteLLM prefix. Exhaustive by type: adding a
365
+ * provider to `MODELS` without a prefix here is a compile error, which
366
+ * is what makes {@link modelRefOfRoute} total.
367
+ */
368
+ const PROVIDER_TO_LITELLM_PREFIX = {
369
+ anthropic: 'anthropic',
370
+ google: 'gemini',
371
+ openai: 'openai',
372
+ bedrock: 'bedrock',
373
+ openrouter: 'openrouter',
374
+ };
375
+ const MODEL_REF_PREFIX_LIST = MODEL_REF_PREFIXES;
376
+ /** Whether `s` is one of {@link MODEL_REF_PREFIXES}. */
377
+ export function isModelRefPrefix(s) {
378
+ return MODEL_REF_PREFIX_LIST.includes(s);
379
+ }
380
+ function liteLlmPrefixProvider(prefix) {
381
+ return isModelRefPrefix(prefix) ? LITELLM_PROVIDER_PREFIX_MAP[prefix] : undefined;
382
+ }
355
383
  /**
356
384
  * LiteLLM canonical → wire-canonical model mappings for providers
357
385
  * whose LiteLLM-form differs from the wire-canonical form.
@@ -396,7 +424,7 @@ export function parseLiteLlmString(s) {
396
424
  if (firstSlash <= 0)
397
425
  return null;
398
426
  const prefix = s.substring(0, firstSlash);
399
- const provider = LITELLM_PROVIDER_PREFIX_MAP[prefix];
427
+ const provider = liteLlmPrefixProvider(prefix);
400
428
  if (!provider)
401
429
  return null;
402
430
  // For OpenRouter the model is everything after the first slash
@@ -446,23 +474,10 @@ function getWireToLitellm() {
446
474
  * OpenAI, OpenRouter), serialization uses the wire model as-is.
447
475
  */
448
476
  export function toLiteLlmString(route) {
449
- const prefix = providerLiteLlmPrefix(route.provider);
450
- const inverseMap = getWireToLitellm()[route.provider];
451
- const modelForLitellm = inverseMap?.[route.model] ?? route.model;
452
- return `${prefix}/${modelForLitellm}`;
477
+ return modelRefOfRoute(route);
453
478
  }
454
479
  function providerLiteLlmPrefix(provider) {
455
- // Find the LiteLLM prefix that maps to this provider. There's at
456
- // most one prefix per provider in LITELLM_PROVIDER_PREFIX_MAP today.
457
- for (const [prefix, p] of Object.entries(LITELLM_PROVIDER_PREFIX_MAP)) {
458
- if (p === provider)
459
- return prefix;
460
- }
461
- // Defensive: every provider in MODELS should have a LiteLLM prefix.
462
- // If not, fall through to the provider's own name (degrades gracefully
463
- // for observability — the trace just won't match LiteLLM's exact
464
- // wording, but it still names the provider).
465
- return provider;
480
+ return PROVIDER_TO_LITELLM_PREFIX[provider];
466
481
  }
467
482
  // ============================================================================
468
483
  // Combined parser (accepts either format)
@@ -480,3 +495,34 @@ function providerLiteLlmPrefix(provider) {
480
495
  export function parseAnyLlmRoute(s) {
481
496
  return parseLlmRoute(s) ?? parseLiteLlmString(s);
482
497
  }
498
+ /**
499
+ * The one composer of a {@link ModelRef}: render the route the run used,
500
+ * inverting the LiteLLM alias map so a resolved `{anthropic,
501
+ * claude-haiku-4-5-20251001}` renders as `anthropic/claude-haiku-4-5` —
502
+ * the registry id. Total: every `LlmRoute` has a ref. Feed it the ROUTE
503
+ * the call used; the ref is the same whether the route was configured
504
+ * as the alias or already resolved.
505
+ */
506
+ export function modelRefOfRoute(route) {
507
+ const inverseMap = getWireToLitellm()[route.provider];
508
+ const model = inverseMap?.[route.model] ?? route.model;
509
+ return `${providerLiteLlmPrefix(route.provider)}/${model}`;
510
+ }
511
+ /**
512
+ * Recover the route from a {@link ModelRef}. `null` unless `s` is a
513
+ * LiteLLM-form string that parses ({@link parseLiteLlmString}) AND is
514
+ * the spelling {@link modelRefOfRoute} produces for that route — a ref
515
+ * has one spelling, so the dated wire id of an aliased model and a
516
+ * wrong prefix are refused here even though the lenient wire parsers
517
+ * accept them.
518
+ */
519
+ export function parseModelRef(s) {
520
+ const route = parseLiteLlmString(s);
521
+ if (route === null)
522
+ return null;
523
+ return modelRefOfRoute(route) === s ? route : null;
524
+ }
525
+ /** Whether `s` is a {@link ModelRef} — see {@link parseModelRef}. */
526
+ export function isModelRef(s) {
527
+ return parseModelRef(s) !== null;
528
+ }
@@ -6,7 +6,7 @@
6
6
  * - `MODEL_REGISTRY` here = LiteLLM-keyed pricing + capability
7
7
  * metadata. Keys are LiteLLM strings (`"gemini/gemini-3.5-flash"`,
8
8
  * `"openai/gpt-5.4-mini"`, …). Read by:
9
- * - `cloud/ggui-protocol-pod` pricing-drift test (vendored
9
+ * - a hosted deployment's pricing-drift test (vendored
10
10
  * LiteLLM JSON pricing table)
11
11
  * - `oss/misc/benchmark` runner (bench harness; picks model
12
12
  * configs by LiteLLM key)
@@ -40,7 +40,7 @@
40
40
  * EXISTS, and `scripts/check-litellm-pricing-drift.ts` compares the
41
41
  * snapshot against upstream, never the registry. That gap let this
42
42
  * table sit four months stale while production routed to models it did
43
- * not even list. The pod's `pricing-tables.test.ts` now asserts every
43
+ * not even list. A hosted deployment's `pricing-tables.test.ts` now asserts every
44
44
  * entry's `costs` match the vendored rate (with an explicit, dated
45
45
  * PENDING_UPSTREAM allowlist for prices we learn before LiteLLM does).
46
46
  * Add a model here and you MUST re-vendor the snapshot in the same
@@ -336,6 +336,22 @@ declare const MODEL_ROWS: {
336
336
  readonly maxTokens: 1000000;
337
337
  readonly supportsTools: true;
338
338
  };
339
+ readonly "openai/gpt-6-astra": {
340
+ readonly id: "openai/gpt-6-astra";
341
+ readonly provider: "openai";
342
+ readonly displayName: "GPT-6 Astra";
343
+ readonly tier: "premium";
344
+ readonly state: "active";
345
+ readonly lineup: false;
346
+ readonly costs: {
347
+ readonly inputPer1M: 10;
348
+ readonly outputPer1M: 50;
349
+ readonly cacheWritePer1M: 12.5;
350
+ readonly cacheReadPer1M: 1;
351
+ };
352
+ readonly maxTokens: 922000;
353
+ readonly supportsTools: true;
354
+ };
339
355
  readonly "openai/gpt-5.6-sol": {
340
356
  readonly id: "openai/gpt-5.6-sol";
341
357
  readonly provider: "openai";
@@ -453,6 +469,10 @@ export interface ModelConfig extends ModelConfigBase {
453
469
  * {@link ModelId} and enforce `id === key`.
454
470
  */
455
471
  export declare const MODEL_REGISTRY: Readonly<Record<ModelId, ModelConfig>>;
472
+ /** Whether `value` is a registry model id — provider-qualified, e.g. `anthropic/claude-haiku-4-5`. */
473
+ export declare function isModelId(value: string): value is ModelId;
474
+ /** Every registry model id, in registry order (ggui#924: the blueprint record's `model` is one of these). */
475
+ export declare const MODEL_IDS: readonly ModelId[];
456
476
  /**
457
477
  * The curated "current lineup" — derived once from the rows' `lineup`
458
478
  * fact, in registry order. Pickers render this up front and put the
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/types/llm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAMlD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7C,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB;;;;WAIG;QACH,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB;;;;WAIG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Ud,CAAC;AAEH,iEAAiE;AACjE,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,UAAU,CAAC;AAE9C,4CAA4C;AAC5C,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAc,CAAC;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,OAAO,EAEA,CAAC;AAE5C,+CAA+C;AAC/C,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,OAAsC,CAAC"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/types/llm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAMlD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7C,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB;;;;WAIG;QACH,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB;;;;WAIG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmWd,CAAC;AAEH,iEAAiE;AACjE,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,UAAU,CAAC;AAE9C,4CAA4C;AAC5C,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAc,CAAC;AAEjF,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,OAAO,CAEzD;AAED,6GAA6G;AAC7G,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAA8C,CAAC;AAEvF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,OAAO,EAEA,CAAC;AAE5C,+CAA+C;AAC/C,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,OAAsC,CAAC"}
package/dist/types/llm.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * - `MODEL_REGISTRY` here = LiteLLM-keyed pricing + capability
7
7
  * metadata. Keys are LiteLLM strings (`"gemini/gemini-3.5-flash"`,
8
8
  * `"openai/gpt-5.4-mini"`, …). Read by:
9
- * - `cloud/ggui-protocol-pod` pricing-drift test (vendored
9
+ * - a hosted deployment's pricing-drift test (vendored
10
10
  * LiteLLM JSON pricing table)
11
11
  * - `oss/misc/benchmark` runner (bench harness; picks model
12
12
  * configs by LiteLLM key)
@@ -40,7 +40,7 @@
40
40
  * EXISTS, and `scripts/check-litellm-pricing-drift.ts` compares the
41
41
  * snapshot against upstream, never the registry. That gap let this
42
42
  * table sit four months stale while production routed to models it did
43
- * not even list. The pod's `pricing-tables.test.ts` now asserts every
43
+ * not even list. A hosted deployment's `pricing-tables.test.ts` now asserts every
44
44
  * entry's `costs` match the vendored rate (with an explicit, dated
45
45
  * PENDING_UPSTREAM allowlist for prices we learn before LiteLLM does).
46
46
  * Add a model here and you MUST re-vendor the snapshot in the same
@@ -307,6 +307,27 @@ const MODEL_ROWS = defineModelRegistry({
307
307
  // confirmed upstream LiteLLM has ingested both cuts, so these costs
308
308
  // now agree with the vendored snapshot (no PENDING_UPSTREAM
309
309
  // exemption needed).
310
+ // ggui#977 (2026-09-09) — Exp 008's founder-ruled second arm. Receipts on
311
+ // the issue: existence `GET /v1/models/gpt-6-astra` → 200; pricing from
312
+ // platform.openai.com/docs/pricing, standard short-context tier; context
313
+ // window and tool support from the model page. No retirement date is
314
+ // published → `retireNotBefore` unset. Not in the lineup (experiment arm).
315
+ "openai/gpt-6-astra": {
316
+ id: "openai/gpt-6-astra",
317
+ provider: "openai",
318
+ displayName: "GPT-6 Astra",
319
+ tier: "premium",
320
+ state: "active",
321
+ lineup: false,
322
+ costs: {
323
+ inputPer1M: 10.0,
324
+ outputPer1M: 50.0,
325
+ cacheWritePer1M: 12.5,
326
+ cacheReadPer1M: 1.0,
327
+ },
328
+ maxTokens: 922000,
329
+ supportsTools: true,
330
+ },
310
331
  "openai/gpt-5.6-sol": {
311
332
  id: "openai/gpt-5.6-sol",
312
333
  provider: "openai",
@@ -399,6 +420,12 @@ const MODEL_ROWS = defineModelRegistry({
399
420
  * {@link ModelId} and enforce `id === key`.
400
421
  */
401
422
  export const MODEL_REGISTRY = MODEL_ROWS;
423
+ /** Whether `value` is a registry model id — provider-qualified, e.g. `anthropic/claude-haiku-4-5`. */
424
+ export function isModelId(value) {
425
+ return Object.hasOwn(MODEL_ROWS, value);
426
+ }
427
+ /** Every registry model id, in registry order (ggui#924: the blueprint record's `model` is one of these). */
428
+ export const MODEL_IDS = Object.keys(MODEL_ROWS).filter(isModelId);
402
429
  /**
403
430
  * The curated "current lineup" — derived once from the rows' `lineup`
404
431
  * fact, in registry order. Pickers render this up front and put the