@ggui-ai/protocol 0.2.0-alpha.4 → 0.4.0-rc.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 (200) hide show
  1. package/README.md +16 -16
  2. package/dist/envelope-adapters.d.ts +6 -5
  3. package/dist/envelope-adapters.d.ts.map +1 -1
  4. package/dist/envelope-adapters.js +3 -3
  5. package/dist/envelopes/builders.d.ts +6 -32
  6. package/dist/envelopes/builders.d.ts.map +1 -1
  7. package/dist/envelopes/builders.js +3 -25
  8. package/dist/errors/version-mismatch.d.ts +1 -1
  9. package/dist/errors/version-mismatch.js +1 -1
  10. package/dist/gadgets/resolve-app-gadgets.d.ts +10 -0
  11. package/dist/gadgets/resolve-app-gadgets.d.ts.map +1 -0
  12. package/dist/gadgets/resolve-app-gadgets.js +19 -0
  13. package/dist/gadgets/resolve-contract-gadgets.d.ts +6 -6
  14. package/dist/gadgets/resolve-contract-gadgets.js +5 -5
  15. package/dist/gadgets/stdlib-gadgets.d.ts +1 -1
  16. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -1
  17. package/dist/gadgets/stdlib-gadgets.js +43 -22
  18. package/dist/iframe-bridge.d.ts +4 -43
  19. package/dist/iframe-bridge.d.ts.map +1 -1
  20. package/dist/iframe-bridge.js +4 -141
  21. package/dist/index.d.ts +60 -62
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +66 -61
  24. package/dist/integrations/mcp-apps.d.ts +83 -38
  25. package/dist/integrations/mcp-apps.d.ts.map +1 -1
  26. package/dist/integrations/mcp-apps.js +98 -52
  27. package/dist/recommended-prompts.d.ts +1 -1
  28. package/dist/recommended-prompts.js +1 -1
  29. package/dist/registry/blueprint-key.d.ts +3 -0
  30. package/dist/registry/blueprint-key.d.ts.map +1 -1
  31. package/dist/registry/blueprint-key.js +9 -0
  32. package/dist/registry/blueprint-stamp.d.ts +12 -0
  33. package/dist/registry/blueprint-stamp.d.ts.map +1 -0
  34. package/dist/registry/blueprint-stamp.js +12 -0
  35. package/dist/registry/canonicalize-contract.d.ts +31 -2
  36. package/dist/registry/canonicalize-contract.d.ts.map +1 -1
  37. package/dist/registry/canonicalize-contract.js +125 -13
  38. package/dist/registry/portable-blueprint.d.ts +71 -0
  39. package/dist/registry/portable-blueprint.d.ts.map +1 -0
  40. package/dist/registry/portable-blueprint.js +111 -0
  41. package/dist/registry/summarize-contract.d.ts +7 -2
  42. package/dist/registry/summarize-contract.d.ts.map +1 -1
  43. package/dist/registry/summarize-contract.js +12 -3
  44. package/dist/registry/variant-key.d.ts +17 -0
  45. package/dist/registry/variant-key.d.ts.map +1 -0
  46. package/dist/registry/variant-key.js +22 -0
  47. package/dist/schema-learning/derive-contract.d.ts +2 -2
  48. package/dist/schema-learning/derive-contract.js +2 -2
  49. package/dist/schema-learning/merge.d.ts.map +1 -1
  50. package/dist/schema-learning/merge.js +3 -2
  51. package/dist/schemas/app-theme.d.ts +38 -0
  52. package/dist/schemas/app-theme.d.ts.map +1 -0
  53. package/dist/schemas/app-theme.js +43 -0
  54. package/dist/schemas/blueprint.d.ts +30 -11
  55. package/dist/schemas/blueprint.d.ts.map +1 -1
  56. package/dist/schemas/blueprint.js +62 -26
  57. package/dist/schemas/data-contract.d.ts +28 -30
  58. package/dist/schemas/data-contract.d.ts.map +1 -1
  59. package/dist/schemas/data-contract.js +26 -26
  60. package/dist/schemas/handshake-suggestion.d.ts +2 -4
  61. package/dist/schemas/handshake-suggestion.d.ts.map +1 -1
  62. package/dist/schemas/handshake-suggestion.js +5 -14
  63. package/dist/schemas/mcp.d.ts +294 -59
  64. package/dist/schemas/mcp.d.ts.map +1 -1
  65. package/dist/schemas/mcp.js +350 -94
  66. package/dist/schemas/ops-blueprint.d.ts +12 -12
  67. package/dist/schemas/ops-blueprint.d.ts.map +1 -1
  68. package/dist/schemas/ops-blueprint.js +14 -24
  69. package/dist/screen-blueprints/define.d.ts +2 -2
  70. package/dist/screen-blueprints/match.d.ts +12 -7
  71. package/dist/screen-blueprints/match.d.ts.map +1 -1
  72. package/dist/screen-blueprints/match.js +7 -5
  73. package/dist/screen-blueprints/types.d.ts +14 -18
  74. package/dist/screen-blueprints/types.d.ts.map +1 -1
  75. package/dist/transport/websocket.d.ts +5 -26
  76. package/dist/transport/websocket.d.ts.map +1 -1
  77. package/dist/types/app-config.d.ts +0 -1
  78. package/dist/types/app-config.d.ts.map +1 -1
  79. package/dist/types/auth.d.ts +1 -46
  80. package/dist/types/auth.d.ts.map +1 -1
  81. package/dist/types/blueprint-source.d.ts +123 -0
  82. package/dist/types/blueprint-source.d.ts.map +1 -0
  83. package/dist/types/blueprint-source.js +126 -0
  84. package/dist/types/blueprint.d.ts +25 -18
  85. package/dist/types/blueprint.d.ts.map +1 -1
  86. package/dist/types/contract-inference.d.ts +2 -43
  87. package/dist/types/contract-inference.d.ts.map +1 -1
  88. package/dist/types/data-contract.d.ts +59 -344
  89. package/dist/types/data-contract.d.ts.map +1 -1
  90. package/dist/types/data-contract.js +5 -12
  91. package/dist/types/events.d.ts +31 -69
  92. package/dist/types/events.d.ts.map +1 -1
  93. package/dist/types/{render-event.d.ts → ggui-session-event.d.ts} +15 -15
  94. package/dist/types/ggui-session-event.d.ts.map +1 -0
  95. package/dist/types/{render-event.js → ggui-session-event.js} +5 -5
  96. package/dist/types/handshake-suggestion.d.ts +49 -35
  97. package/dist/types/handshake-suggestion.d.ts.map +1 -1
  98. package/dist/types/host-context.d.ts +8 -8
  99. package/dist/types/host-context.d.ts.map +1 -1
  100. package/dist/types/host-context.js +5 -5
  101. package/dist/types/invoke.d.ts +1 -13
  102. package/dist/types/invoke.d.ts.map +1 -1
  103. package/dist/types/{canvas-lifecycle.d.ts → lifecycle.d.ts} +35 -37
  104. package/dist/types/lifecycle.d.ts.map +1 -0
  105. package/dist/types/{canvas-lifecycle.js → lifecycle.js} +12 -14
  106. package/dist/types/live-channel.d.ts +48 -104
  107. package/dist/types/live-channel.d.ts.map +1 -1
  108. package/dist/types/llm-route.d.ts +4 -4
  109. package/dist/types/llm-route.d.ts.map +1 -1
  110. package/dist/types/llm-route.js +24 -2
  111. package/dist/types/llm.d.ts +13 -24
  112. package/dist/types/llm.d.ts.map +1 -1
  113. package/dist/types/llm.js +23 -53
  114. package/dist/types/mcp.d.ts +74 -177
  115. package/dist/types/mcp.d.ts.map +1 -1
  116. package/dist/types/mcp.js +11 -4
  117. package/dist/types/oauth-user-id.d.ts +12 -0
  118. package/dist/types/oauth-user-id.d.ts.map +1 -0
  119. package/dist/types/oauth-user-id.js +10 -0
  120. package/dist/types/portable-blueprint.d.ts +61 -0
  121. package/dist/types/portable-blueprint.d.ts.map +1 -0
  122. package/dist/types/portable-blueprint.js +8 -0
  123. package/dist/types/{session.d.ts → render.d.ts} +51 -53
  124. package/dist/types/render.d.ts.map +1 -0
  125. package/dist/types/thread.d.ts.map +1 -1
  126. package/dist/types/thread.js +3 -3
  127. package/dist/types/ui-generator.d.ts +24 -47
  128. package/dist/types/ui-generator.d.ts.map +1 -1
  129. package/dist/types/ui-generator.js +1 -53
  130. package/dist/validation/ajv-runtime.d.ts +3 -3
  131. package/dist/validation/ajv-runtime.d.ts.map +1 -1
  132. package/dist/validation/ajv-runtime.js +6 -5
  133. package/dist/validation/contract-validator.d.ts +23 -22
  134. package/dist/validation/contract-validator.d.ts.map +1 -1
  135. package/dist/validation/contract-validator.js +28 -26
  136. package/dist/validation/cross-references.d.ts +3 -3
  137. package/dist/validation/cross-references.js +3 -3
  138. package/dist/validation/hygiene-rules.d.ts +9 -9
  139. package/dist/validation/hygiene-rules.js +13 -13
  140. package/dist/validation/is-record.d.ts +17 -0
  141. package/dist/validation/is-record.d.ts.map +1 -0
  142. package/dist/validation/is-record.js +18 -0
  143. package/dist/validation/lint-contract.d.ts +1 -1
  144. package/dist/validation/lint-contract.js +3 -3
  145. package/dist/validation/name-invariants.d.ts +4 -4
  146. package/dist/validation/name-invariants.js +4 -4
  147. package/dist/validation/reserved-channels.d.ts +43 -46
  148. package/dist/validation/reserved-channels.d.ts.map +1 -1
  149. package/dist/validation/reserved-channels.js +14 -177
  150. package/dist/validation/resolve-stream-channel.d.ts +3 -9
  151. package/dist/validation/resolve-stream-channel.d.ts.map +1 -1
  152. package/dist/validation/resolve-stream-channel.js +3 -4
  153. package/dist/validation/sanitize-error.d.ts +10 -9
  154. package/dist/validation/sanitize-error.d.ts.map +1 -1
  155. package/dist/validation/sanitize-error.js +19 -18
  156. package/dist/validation/schema-compat-invariants.d.ts +1 -1
  157. package/dist/validation/schema-compat-invariants.js +3 -3
  158. package/dist/validation/schema-meta-validation.d.ts +4 -4
  159. package/dist/validation/schema-meta-validation.d.ts.map +1 -1
  160. package/dist/validation/schema-meta-validation.js +7 -9
  161. package/dist/validation/schema-subset.d.ts +9 -8
  162. package/dist/validation/schema-subset.d.ts.map +1 -1
  163. package/dist/validation/ui-security.d.ts +0 -17
  164. package/dist/validation/ui-security.d.ts.map +1 -1
  165. package/dist/validation/ui-security.js +17 -22
  166. package/dist/validation/zod-to-json-schema.d.ts +1 -1
  167. package/dist/validation/zod-to-json-schema.d.ts.map +1 -1
  168. package/dist/validation/zod-to-json-schema.js +4 -3
  169. package/dist/version.d.ts +818 -9
  170. package/dist/version.d.ts.map +1 -1
  171. package/dist/version.js +817 -8
  172. package/package.json +3 -3
  173. package/dist/bridge/invoke-agent.d.ts +0 -65
  174. package/dist/bridge/invoke-agent.d.ts.map +0 -1
  175. package/dist/bridge/invoke-agent.js +0 -113
  176. package/dist/stream/stream-parser.d.ts +0 -62
  177. package/dist/stream/stream-parser.d.ts.map +0 -1
  178. package/dist/stream/stream-parser.js +0 -199
  179. package/dist/types/canvas-lifecycle.d.ts.map +0 -1
  180. package/dist/types/credential.d.ts +0 -41
  181. package/dist/types/credential.d.ts.map +0 -1
  182. package/dist/types/credential.js +0 -32
  183. package/dist/types/data-bindings.d.ts +0 -322
  184. package/dist/types/data-bindings.d.ts.map +0 -1
  185. package/dist/types/data-bindings.js +0 -29
  186. package/dist/types/feedback.d.ts +0 -22
  187. package/dist/types/feedback.d.ts.map +0 -1
  188. package/dist/types/feedback.js +0 -7
  189. package/dist/types/mcp-proxy.d.ts +0 -67
  190. package/dist/types/mcp-proxy.d.ts.map +0 -1
  191. package/dist/types/mcp-proxy.js +0 -46
  192. package/dist/types/openrouter-models.d.ts +0 -23
  193. package/dist/types/openrouter-models.d.ts.map +0 -1
  194. package/dist/types/openrouter-models.js +0 -4843
  195. package/dist/types/region.d.ts +0 -26
  196. package/dist/types/region.d.ts.map +0 -1
  197. package/dist/types/region.js +0 -36
  198. package/dist/types/render-event.d.ts.map +0 -1
  199. package/dist/types/session.d.ts.map +0 -1
  200. /package/dist/types/{session.js → render.js} +0 -0
@@ -3,10 +3,30 @@
3
3
  *
4
4
  * These schemas define the validation rules for MCP tool inputs.
5
5
  * TypeScript types in types/mcp.ts are derived from these via z.infer.
6
- * The server handler imports these for runtime validation.
6
+ *
7
+ * Two consumption patterns, both anchored here:
8
+ *
9
+ * - **Wired raw shapes** (`*InputShape`): handlers in
10
+ * `@ggui-ai/mcp-server-handlers` (and the hosted pod's
11
+ * discover / request-credential tools) import the SHAPE directly as
12
+ * their `inputSchema` and validate with `z.object(shape)` — unknown
13
+ * keys are STRIPPED. The shape is the one authored copy of the
14
+ * validation rules AND the agent-facing `.describe()` strings that
15
+ * ship via `tools/list`.
16
+ *
17
+ * - **Lifecycle triad** (`ggui_handshake` / `ggui_update`): the
18
+ * handlers carry deliberate input divergences (handshake's
19
+ * blueprintDraft contract is loose ON PURPOSE so the negotiator can
20
+ * repair malformations; update's sessionId is optional for
21
+ * in-process dispatch), so they author their own raw shapes. The
22
+ * schemas here remain the canonical strict wire contract + the
23
+ * `z.infer` source for the published types. `ggui_render` is wired:
24
+ * its handler imports {@link renderInputShape}.
7
25
  */
8
26
  import { z } from 'zod';
9
- import { blueprintDraftSchema, handshakeSuggestionSchema, pushDecisionSchema, } from './handshake-suggestion.js';
27
+ import { blueprintDraftSchema, handshakeSuggestionSchema, } from './handshake-suggestion.js';
28
+ import { dataContractSchema } from './data-contract.js';
29
+ import { blueprintVarianceSchema } from './blueprint.js';
10
30
  // ── Shared Sub-Schemas ──
11
31
  export const viewportSchema = z.object({
12
32
  width: z.number(),
@@ -23,52 +43,84 @@ export const interfaceContextSchema = z.object({
23
43
  colorScheme: z.enum(['light', 'dark']).optional(),
24
44
  reducedMotion: z.boolean().optional(),
25
45
  }).passthrough();
26
- // ── Other Tool Schemas ──
27
- // Input schemas: pre-launch posture is `.strict()` — unknown keys reject.
28
- // Pre-fix all of these had `.passthrough()` as forward-compat shims. Pre-
29
- // launch No Backward Compatibility (CLAUDE.md) supersedes — typos in agent
30
- // args (`renderid`, `redner_id`, etc.) surface immediately at the wire
31
- // boundary instead of silently no-op-ing because the server stripped them.
32
- export const consumeInputSchema = z.object({
33
- renderId: z.string().describe('Render opaque id (UUID) — returned by ggui_render.'),
34
- timeout: z.number().min(0).max(25).optional()
35
- .describe('Long-poll timeout in seconds (default short; max 25).'),
36
- }).strict();
46
+ // ── Wired Tool Input Shapes ──
47
+ //
48
+ // Raw zod shapes for the non-lifecycle tools. The SHAPE is the canonical
49
+ // authored artifact: the live handlers register it as their `inputSchema`
50
+ // (the transport layer projects it onto `tools/list`) and validate with
51
+ // `z.object(shape)` — unknown keys are stripped at the wire boundary.
52
+ // The derived `z.object(...)` schema next to each shape is the same
53
+ // contract as a composed validator and the `z.infer` source for the
54
+ // published input types in `types/mcp.ts`.
37
55
  /**
38
- * Input schema for `ggui_emit` — emit a stamped delivery on a declared
56
+ * `ggui_consume` input. The long-poll bound is SPEC §7.3: integer
57
+ * seconds in `[0, 25]` — the cap dodges infrastructure kill windows
58
+ * (API-gateway 30s HTTP limits, host MCP clients that abort long tool
59
+ * calls). Longer waits are the agent's loop, not a server knob.
60
+ */
61
+ export const consumeInputShape = {
62
+ sessionId: z
63
+ .string()
64
+ .min(1)
65
+ .describe('Globally-unique sessionId to consume events from. Cross-tenant access surfaces uniformly as session_not_found.'),
66
+ timeout: z
67
+ .number()
68
+ .int()
69
+ .min(0)
70
+ .max(25)
71
+ .optional()
72
+ .describe('Inline long-poll seconds, integer in [0, 25]. 0 = immediate. Values outside the bound reject INVALID_PARAMS. Returns on first event OR timeout; re-call on empty to keep waiting — longer waits are your loop, not a bigger timeout.'),
73
+ };
74
+ export const consumeInputSchema = z.object(consumeInputShape);
75
+ /**
76
+ * `ggui_emit` input — emit a stamped delivery on a declared
39
77
  * `streamSpec[channel]`.
40
78
  */
41
- export const emitInputSchema = z.object({
42
- renderId: z.string().describe('Render opaque id (UUID) — returned by ggui_render.'),
43
- channel: z.string()
44
- .describe('Channel name declared on the active render streamSpec.'),
45
- payload: z.unknown().describe('Payload — must match streamSpec[channel].schema.'),
46
- complete: z.boolean().optional()
47
- .describe('True marks the stream complete; subsequent emits on this channel reject.'),
48
- }).strict();
49
- export const getRenderInputSchema = z.object({
50
- renderId: z.string().describe('Render opaque id (UUID) — returned by ggui_render.'),
51
- }).strict();
52
- export const listFeaturedBlueprintsInputSchema = z.object({
53
- level: z.enum(['primitive', 'component', 'composite', 'template']).optional(),
54
- category: z.string().optional(),
55
- tags: z.array(z.string()).optional(),
56
- limit: z.number().optional(),
57
- }).strict();
58
- export const searchBlueprintsInputSchema = z.object({
59
- query: z.string(),
60
- limit: z.number().optional(),
61
- }).strict();
62
- export const renderBlueprintInputSchema = z.object({
63
- blueprintId: z.string(),
64
- props: z.record(z.string(), z.unknown()).optional(),
65
- }).strict();
66
- export const discoverInputSchema = z.object({}).strict();
67
- export const requestCredentialInputSchema = z.object({
68
- serviceId: z.string().describe('OAuth service ID (e.g., "bashdoor", "ubot")'),
69
- reason: z.string().optional().describe('Why the agent needs this credential (shown to user)'),
70
- renderId: z.string().optional().describe('Existing render id to push consent UI into.'),
71
- }).strict();
79
+ export const emitInputShape = {
80
+ sessionId: z.string().min(1),
81
+ channel: z.string().min(1),
82
+ payload: z.unknown(),
83
+ complete: z.boolean().optional(),
84
+ };
85
+ export const emitInputSchema = z.object(emitInputShape);
86
+ export const getSessionInputShape = {
87
+ sessionId: z
88
+ .string()
89
+ .min(1)
90
+ .describe('GguiSession opaque id (UUID) — returned by ggui_render.'),
91
+ };
92
+ export const getSessionInputSchema = z.object(getSessionInputShape);
93
+ /**
94
+ * `ggui_list_featured_blueprints` input — intentionally EMPTY. The
95
+ * pre-launch No-Backcompat scrub deleted the level/category/tags/limit
96
+ * filters (delete-until-wired); filters re-enter here when a real
97
+ * consumer passes them.
98
+ */
99
+ export const listFeaturedBlueprintsInputShape = {};
100
+ export const listFeaturedBlueprintsInputSchema = z.object(listFeaturedBlueprintsInputShape);
101
+ export const searchBlueprintsInputShape = {
102
+ query: z
103
+ .string()
104
+ .min(1)
105
+ .describe("Natural-language description of the UI you're looking for"),
106
+ limit: z.number().int().min(1).max(100).optional(),
107
+ };
108
+ export const searchBlueprintsInputSchema = z.object(searchBlueprintsInputShape);
109
+ export const renderBlueprintInputShape = {
110
+ blueprintId: z
111
+ .string()
112
+ .min(1)
113
+ .describe("The stable blueprint id declared via ggui.ui.json#id. Must match an entry in this server's UI registry."),
114
+ };
115
+ export const renderBlueprintInputSchema = z.object(renderBlueprintInputShape);
116
+ export const discoverInputShape = {};
117
+ export const discoverInputSchema = z.object(discoverInputShape);
118
+ export const requestCredentialInputShape = {
119
+ serviceId: z.string().min(1),
120
+ reason: z.string().optional(),
121
+ sessionId: z.string().optional(),
122
+ };
123
+ export const requestCredentialInputSchema = z.object(requestCredentialInputShape);
72
124
  // ── Post-Phase-B — canonical tool triad ──
73
125
  //
74
126
  // `ggui_handshake` → `ggui_render` → `ggui_update` / `ggui_consume`.
@@ -76,7 +128,7 @@ export const requestCredentialInputSchema = z.object({
76
128
  // render server-side. Conversation grouping (sibling renders within one
77
129
  // host chat) lives on the unchanged `_meta["ai.ggui/host-session"]`
78
130
  // channel, captured ONCE at render creation, never threaded by the
79
- // agent. The collapse of Session→Render means `renderId` is the single
131
+ // agent. The collapse of Session→GguiSession means `sessionId` is the single
80
132
  // identity the wire references everywhere.
81
133
  //
82
134
  /**
@@ -100,10 +152,10 @@ export const requestCredentialInputSchema = z.object({
100
152
  * - The agent is the contract authority; synth amends only when
101
153
  * validation fails.
102
154
  * - Post-Phase-B the handshake input carries NO `sessionId`. The
103
- * server mints `renderId` on the paired `ggui_render`; host
155
+ * server mints `sessionId` on the paired `ggui_render`; host
104
156
  * conversation grouping flows via the host-supplied
105
157
  * `_meta["ai.ggui/host-session"]` envelope captured at render
106
- * creation (see {@link RenderBase.hostSession}).
158
+ * creation (see {@link GguiSessionBase.hostSession}).
107
159
  */
108
160
  export const handshakeInputSchema = z.object({
109
161
  /**
@@ -138,18 +190,19 @@ export const handshakeInputSchema = z.object({
138
190
  *
139
191
  * The agent reads `suggestion.origin` to branch the paired render call:
140
192
  *
141
- * - `cache` → render `{decision: {kind: 'accept'}}` for cache delivery.
142
- * - `agent` → render `{decision: {kind: 'accept'}}` to gen against the draft.
143
- * - `synth` → render `{decision: {kind: 'accept'}}` to gen against the amended contract.
193
+ * - `cache` → render `{handshakeId, props}` (omit `override`) for cache delivery.
194
+ * - `agent` → render `{handshakeId, props}` (omit `override`) to gen against the draft.
195
+ * - `synth` → render `{handshakeId, props}` (omit `override`) to gen against the amended contract.
144
196
  *
145
- * Any origin → render `{decision: {kind: 'override', blueprintDraft: {...}}}` to
146
- * discard the suggestion and gen against a fresh draft (mints a new
147
- * `blueprintId` server-side).
197
+ * Any origin → render `{handshakeId, props, override: {contract?, variance?}}`
198
+ * to re-aim the suggestion — `override.contract` gens against a fresh
199
+ * contract; `override.variance` re-aims the variant axis.
148
200
  *
149
- * Wire-output is intentionally lean. The handler carries `reason`,
150
- * `target`, `alternatives`, `contractHash`, `serverCapabilities` on
151
- * its internal `HandshakeOutput` TS shape for telemetry / post-classify
152
- * tracing — zod strips them before structuredContent serialization.
201
+ * Wire-output is intentionally lean. The handler carries `target`,
202
+ * `alternatives`, `contractHash`, `serverCapabilities` on its internal
203
+ * `HandshakeOutput` TS shape for telemetry / post-classify tracing —
204
+ * zod strips them before structuredContent serialization. `reason` IS
205
+ * a wire field (optional, ≤280 chars — see below).
153
206
  *
154
207
  * `serverCapabilities` reaches the iframe via the `ai.ggui/render`
155
208
  * slice meta (see `slice-meta-derivation.ts`), not via this response.
@@ -168,7 +221,7 @@ export const handshakeOutputSchema = z.object({
168
221
  * conditional on the routing outcome.
169
222
  */
170
223
  suggestion: handshakeSuggestionSchema
171
- .describe('Server\'s suggestion — origin-routed (cache | agent | synth). Always carries a provisional `blueprintMeta` the agent reuses by sending `decision: \'accept\'` on render.'),
224
+ .describe('Server\'s suggestion — origin-routed (cache | agent | synth). Always carries a provisional `blueprintMeta` the agent reuses by rendering WITHOUT `override` (accept the proposal as-is).'),
172
225
  /**
173
226
  * Truncated human-readable rationale for the `action` value. Helps
174
227
  * the agent and the operator narrate why the server chose to reuse a cached
@@ -191,28 +244,39 @@ export const handshakeOutputSchema = z.object({
191
244
  * `ggui_render` — materialises a UI emission. Step 3 of the three-step
192
245
  * handshake protocol.
193
246
  *
194
- * The agent commits its decision relative to the prior handshake's
195
- * suggestion: ACCEPT (use the provisional `blueprintMeta` from
196
- * step-2 verbatim) or OVERRIDE (mint a fresh blueprintId with a NEW
197
- * `blueprintDraft`).
247
+ * The agent commits relative to the prior handshake's suggestion by
248
+ * PRESENCE of `override` (no discriminated union): omit `override` to
249
+ * ACCEPT the proposal as-is, or provide `override: {contract?, variance?}`
250
+ * to re-aim the contract and/or the variant axis (PATCH semantics).
198
251
  *
199
252
  * Locked decisions:
200
253
  *
201
- * - `decision` discriminator: `{kind: 'accept'} |
202
- * {kind: 'override', blueprintDraft: {...}}`.
203
- * - `accept` reuses `handshake.suggestion.blueprintMeta.blueprintId`
204
- * exactly; `override` discards the provisional id and mints fresh.
254
+ * - ACCEPT (omit `override`) reuses the agreed contract + the proposed
255
+ * variance, resolving the proposed `(contractKey, variantKey)`.
256
+ * - `override.contract` re-drafts the contract (STRICT — must already
257
+ * conform; the server does not repair it) and cold-gens against it.
258
+ * - `override.variance` re-aims the variant axis while keeping the
259
+ * agreed contract, re-resolving the effective
260
+ * `(contractKey, variantKey(newVariance))`.
261
+ * - `props` is REQUIRED (pass `{}` when the effective contract declares
262
+ * no propsSpec).
205
263
  *
206
264
  * There is no separate `ggui_commit` — render absorbs that responsibility.
207
265
  *
208
266
  * Post-Phase-B rename from `ggui_push` — the tool materialises a single
209
267
  * render (no stack of N to push onto); the new name reflects what the
210
268
  * tool does at the protocol surface.
269
+ *
270
+ * WIRED shape — `@ggui-ai/mcp-server-handlers`'s `ggui_render` registers
271
+ * {@link renderInputShape} as its `inputSchema` and validates with
272
+ * `z.object(shape)` (unknown top-level keys strip; `infra` / `override`
273
+ * sub-objects stay `.strict()` so typos inside them surface as clear zod
274
+ * paths).
211
275
  */
212
- export const renderInputSchema = z.object({
276
+ export const renderInputShape = {
213
277
  handshakeId: z
214
278
  .string({
215
- message: 'ggui_render: handshakeId is REQUIRED. Call ggui_handshake({intent, blueprintDraft}) first to negotiate — handshake returns a handshakeId + suggestion. Then render with {handshakeId, decision: {kind: \'accept\'}} (accept the suggestion) or {handshakeId, decision: {kind: \'override\', blueprintDraft: {...}}} (override with a fresh draft). Direct-render without a handshakeId is not supported.',
279
+ message: 'ggui_render: handshakeId is REQUIRED. Call ggui_handshake({intent, blueprintDraft}) first to negotiate — handshake returns a handshakeId + suggestion. Then render with {handshakeId, props} (accept the suggestion as-is) or {handshakeId, props, override: {contract?, variance?}} (re-aim the contract and/or variance). Direct-render without a handshakeId is not supported.',
216
280
  })
217
281
  .min(1, 'ggui_render: handshakeId must be a non-empty string from a prior ggui_handshake call.'),
218
282
  /**
@@ -220,27 +284,142 @@ export const renderInputSchema = z.object({
220
284
  * effective contract's `propsSpec` — required-field checks + type
221
285
  * checks per spec entry. Validation failures fail the render with a
222
286
  * recoverable `ContractViolationError`.
287
+ *
288
+ * REQUIRED — pass `{}` when the effective contract declares no
289
+ * propsSpec (the field is required, the value may be empty).
223
290
  */
224
- props: z.record(z.string(), z.unknown()).optional(),
291
+ props: z.record(z.string(), z.unknown()),
225
292
  /**
226
- * Decision discriminator (REQUIRED).
293
+ * Per-render theme override. When set, lands on the committed
294
+ * render and takes priority over `App.defaultThemeId` at
295
+ * bootstrap-projection time. Use sparingly — most renders should
296
+ * inherit the app default.
297
+ */
298
+ themeId: z
299
+ .string()
300
+ .min(1)
301
+ .optional()
302
+ .describe('Per-render theme override. Wins over App.defaultThemeId for THIS render. Omit to inherit the app theme.'),
303
+ /**
304
+ * Typed `infra` envelope. Today carries one field (`model`); future
305
+ * expansion (temperature, max_tokens, provider hints) lands here
306
+ * additively. `model` MUST be a provider-prefixed id
307
+ * (`provider/model-name`); a bound generator may also accept
308
+ * generator-specific prefixes for alternate transports.
227
309
  *
228
- * - `{kind: 'accept'}` — use the handshake's
229
- * `suggestion.blueprintMeta` verbatim. Cache delivery (origin
230
- * === 'cache') or gen-against-suggestion (origin === 'agent' /
231
- * 'synth'). Reuses the provisional `blueprintId`.
232
- * - `{kind: 'override', blueprintDraft: {...}}` — mint a fresh
233
- * `blueprintId` and gen against the agent's NEW draft. The
234
- * provisional id from the handshake is discarded. Telemetry
235
- * threads via `handshakeId`.
310
+ * Strict — extra keys at `infra.*` are not silently dropped, so a
311
+ * typo (`infra.modelId`) surfaces as a clear zod path instead of a
312
+ * silent default-model fallback.
236
313
  */
237
- decision: pushDecisionSchema
238
- .describe('Accept the handshake suggestion (use provisional blueprintId verbatim) or override with a fresh draft (mint new blueprintId).'),
239
- }).strict();
314
+ infra: z
315
+ .object({
316
+ model: z
317
+ .string()
318
+ .min(1)
319
+ .optional()
320
+ .describe('Provider-prefixed model id (e.g., `anthropic/claude-haiku-4-5`, `openai/gpt-5`). Generator-specific prefixes (e.g., `bedrock/...` for AWS Bedrock routing) supported when the bound generator handles them.'),
321
+ })
322
+ .strict()
323
+ .optional(),
324
+ /**
325
+ * Re-aim the handshake proposal (PATCH semantics). Omit to ACCEPT the
326
+ * proposal as-is; provide to re-draft the contract and/or re-aim the
327
+ * variant axis. At least one of `contract` / `variance` MUST be set —
328
+ * an empty `override: {}` is rejected.
329
+ *
330
+ * - `contract` — STRICT full re-draft of the contract. The server
331
+ * does NOT repair it; it must already conform.
332
+ * - `variance` — re-aim the variant axis (persona / aesthetic /
333
+ * context / seedPrompt) while keeping the agreed contract. A
334
+ * different variance resolves a distinct cached component.
335
+ */
336
+ override: z
337
+ .object({
338
+ contract: dataContractSchema
339
+ .optional()
340
+ .describe('STRICT full re-draft of the contract — must already conform; the server will not repair it.'),
341
+ variance: blueprintVarianceSchema
342
+ .optional()
343
+ .describe('Re-aim the variant (persona/aesthetic/context/seedPrompt); keeps the agreed contract.'),
344
+ })
345
+ .strict()
346
+ .refine((o) => o.contract !== undefined || o.variance !== undefined, {
347
+ message: 'override must set contract and/or variance — omit override entirely to ACCEPT the handshake proposal as-is.',
348
+ })
349
+ .optional()
350
+ .describe('Omit to ACCEPT the proposal as-is. Provide to re-aim contract and/or variance (PATCH semantics).'),
351
+ };
352
+ export const renderInputSchema = z.object(renderInputShape);
240
353
  /**
241
- * Wire-output shape — intentionally lean: `{renderId, nextStep?, action}`.
354
+ * Reuse outcome for a single `ggui_render` — surfaced on the wire so an
355
+ * agent or operator can tell whether a stored component was served or a
356
+ * new one was generated. Counts generation calls only; it carries no
357
+ * cost or tier semantics.
358
+ */
359
+ export const renderCacheMarkerSchema = z.object({
360
+ hit: z
361
+ .boolean()
362
+ .describe('True when a stored component was served without generating new code.'),
363
+ similarity: z
364
+ .number()
365
+ .optional()
366
+ .describe('Cosine similarity of the matched component to the request (semantic match only).'),
367
+ cachedBlueprintId: z
368
+ .string()
369
+ .optional()
370
+ .describe('The stored component id that was matched. Equals top-level blueprintId on a hit.'),
371
+ llmCallsAvoided: z
372
+ .number()
373
+ .describe('Generation calls skipped by serving the stored component (0 on a fresh generation).'),
374
+ kind: z
375
+ .enum(['full-template', 'cold'])
376
+ .optional()
377
+ .describe('full-template = a whole stored component was served; cold = freshly generated.'),
378
+ reason: z
379
+ .string()
380
+ .optional()
381
+ .describe('Compact human-readable explanation of the cache outcome — why this render reused a stored component or generated cold. Diagnostic; default-available without the verbose env-gated trace.'),
382
+ });
383
+ /**
384
+ * Canonical failure codes for the in-result `ggui_render` failure
385
+ * envelope (SPEC §7.9 Plane 3). Closed enum — a failed render's
386
+ * `error.code` is always one of these four; finer-grained diagnostics
387
+ * ride on `error.message`.
388
+ *
389
+ * - `PRODUCTION_FAILED` — generation ran but did not produce a
390
+ * component (LLM/compile/commit failure).
391
+ * - `VALIDATION_ERROR` — a server-side precondition rejected the
392
+ * render before generation could run (misconfigured generation
393
+ * route, unusable stored config).
394
+ * - `NO_PLATFORM_KEY` — the server's managed provider-key
395
+ * configuration has no key for the resolved route.
396
+ * - `NO_CREDENTIALS` — no generation credentials are configured on
397
+ * the server at all.
398
+ */
399
+ export const renderErrorCodeSchema = z.enum([
400
+ 'PRODUCTION_FAILED',
401
+ 'VALIDATION_ERROR',
402
+ 'NO_PLATFORM_KEY',
403
+ 'NO_CREDENTIALS',
404
+ ]);
405
+ /**
406
+ * In-result failure marker for `ggui_render`. Present on the wire
407
+ * output iff the tool result is `isError: true` — the structuredContent
408
+ * stays schema-conformant on failures, and this field carries the
409
+ * canonical failure classification.
410
+ */
411
+ export const renderErrorSchema = z.object({
412
+ code: renderErrorCodeSchema.describe('Canonical failure class. PRODUCTION_FAILED: generation did not produce a component. VALIDATION_ERROR: a server-side precondition rejected the render before generation. NO_PLATFORM_KEY: the server\'s managed provider-key configuration has no key for the resolved route. NO_CREDENTIALS: no generation credentials are configured on the server.'),
413
+ message: z
414
+ .string()
415
+ .describe('Human-readable failure detail — fold into the next attempt or surface to the operator.'),
416
+ });
417
+ /**
418
+ * Wire-output shape — `{sessionId, resourceUri?, action, contractHash,
419
+ * cache, error?, nextStep?}`. `contractHash` (data-contract identity)
420
+ * and `cache` (reuse outcome) are required wire fields on this schema.
242
421
  * The handler carries `shortCode`, `codeReady`, `handshakeId`,
243
- * `decision`, `contract`, `contractHash`, `cache`, `codeUrl`, `codeHash`
422
+ * `decision`, `contract`, `codeUrl`, `codeHash`
244
423
  * on its internal `RenderOutput` TS shape for telemetry / post-classify
245
424
  * tracing — zod strips them before structuredContent serialization.
246
425
  *
@@ -248,15 +427,21 @@ export const renderInputSchema = z.object({
248
427
  * `expiresAt`) via the single `ai.ggui/render` slice meta, not via this
249
428
  * response. There is no clickable `url` field — post-R5 the `/r/`
250
429
  * shortCode route was deleted (every host either resolves the
251
- * `_meta.ui.resourceUri` iframe or reads `{renderId}` via
430
+ * `_meta.ui.resourceUri` iframe or reads `{sessionId}` via
252
431
  * `render-resource/...`). Leaving a dead URL on the wire had the model
253
432
  * hallucinating links that resolve nowhere.
254
433
  *
434
+ * Failure envelope (SPEC §7.1): a failed/rejected generation returns
435
+ * this same schema-conformant shape on an `isError: true` tool result —
436
+ * `error` present, `resourceUri` absent (nothing mountable), no
437
+ * `_meta` on the result. The error GguiSession is still committed, so
438
+ * `sessionId` remains a live handle into the session channel.
439
+ *
255
440
  * Post-Phase-B the `'compose'` action enum value is gone — there is no
256
441
  * stack of N renders to compose against.
257
442
  */
258
443
  export const renderOutputSchema = z.object({
259
- renderId: z.string(),
444
+ sessionId: z.string(),
260
445
  /**
261
446
  * Spec-canonical MCP-Apps entry-point — same `ui://ggui/render/{id}`
262
447
  * URI surfaced on `_meta.ui.resourceUri`. Surfacing it on the LLM-
@@ -264,9 +449,35 @@ export const renderOutputSchema = z.object({
264
449
  * tool_results (OpenAI Agents SDK, Google ADK) reach the mount URI;
265
450
  * SDKs that preserve `_meta` see the same value on both fields.
266
451
  * Mirrors the `resourceUri` field on `ggui_update`'s output.
452
+ *
453
+ * OPTIONAL — present iff the render is mountable. Absent on the
454
+ * failure envelope (`error` present): a failed render commits an
455
+ * error GguiSession but exposes no mount affordance.
267
456
  */
268
- resourceUri: z.string(),
457
+ resourceUri: z
458
+ .string()
459
+ .optional()
460
+ .describe('MCP-Apps mount URI (ui://ggui/render/{id}). Present iff the render is mountable; absent on a failed render.'),
269
461
  action: z.enum(['create', 'reuse', 'update', 'replace', 'declined']),
462
+ contractHash: z
463
+ .string()
464
+ .describe('Canonical hash of the rendered data contract (shape only — fields, types, specs). Same hash ⟺ same data flow.'),
465
+ blueprintId: z
466
+ .string()
467
+ .describe('Opaque id of the materialised component for this render. On the handshake-decided reuse paths (accept a cache-origin proposal, or a variance re-aim that resolves to an existing variant) it is the stored id — equal ids across renders mean the same stored component. override.contract always generates cold and mints a fresh id, even for an identical contract.'),
468
+ variantKey: z
469
+ .string()
470
+ .describe('Canonical hash of the design-time variance (persona, aesthetic, seed prompt, context). With contractHash it forms the reuse key: the same pair reuses one component; a different variant of the same contract gets its own.'),
471
+ cache: renderCacheMarkerSchema.describe('Reuse outcome for this render: whether a stored component was served, its similarity, the matched component id, and how many generation calls that avoided.'),
472
+ /**
473
+ * In-result failure marker — present iff the tool result is
474
+ * `isError: true`. The structuredContent stays schema-conformant on
475
+ * failures; this field carries the canonical `{code, message}`
476
+ * classification. Absent on every successful render.
477
+ */
478
+ error: renderErrorSchema
479
+ .optional()
480
+ .describe('Present iff the tool result is isError — canonical {code, message} for a failed/rejected generation. Absent on success.'),
270
481
  /**
271
482
  * Wire-shape recovery hint for the next call. Emitted ONLY when the
272
483
  * rendered contract has a non-empty `actionSpec` — i.e. the agent will
@@ -276,7 +487,7 @@ export const renderOutputSchema = z.object({
276
487
  * Mirrors the chain at `handshake.nextStep` (→ render). Closes the loop
277
488
  * with consume.
278
489
  *
279
- * `args.renderId` is the literal value the agent passes to
490
+ * `args.sessionId` is the literal value the agent passes to
280
491
  * `ggui_consume` — copy-paste shape.
281
492
  */
282
493
  nextStep: z.object({
@@ -284,9 +495,9 @@ export const renderOutputSchema = z.object({
284
495
  description: z.string(),
285
496
  example: z.string(),
286
497
  args: z.object({
287
- renderId: z.string(),
498
+ sessionId: z.string(),
288
499
  }),
289
- }).optional().describe('Recovery hint — when the rendered contract has actions, points the agent at ggui_consume({renderId}) for the inbound action loop. Absent for pure-display renders.'),
500
+ }).optional().describe('Recovery hint — when the rendered contract has actions, points the agent at ggui_consume({sessionId}) for the inbound action loop. Absent for pure-display renders.'),
290
501
  });
291
502
  /**
292
503
  * `ggui_update` — refresh the rendered UI with new state.
@@ -316,18 +527,18 @@ export const renderOutputSchema = z.object({
316
527
  * partial patches that would break required fields, type-mismatch
317
528
  * values, etc. all reject pre-persist.
318
529
  *
319
- * `renderId` is globally unique; the server tenancy-checks via
530
+ * `sessionId` is globally unique; the server tenancy-checks via
320
531
  * `ctx.appId`.
321
532
  */
322
533
  export const updateInputSchema = z.discriminatedUnion('kind', [
323
534
  z.object({
324
- renderId: z.string().describe('Render opaque id (UUID) — returned by ggui_render.'),
535
+ sessionId: z.string().describe('GguiSession opaque id (UUID) — returned by ggui_render.'),
325
536
  kind: z.literal('replace'),
326
537
  props: z.record(z.string(), z.unknown())
327
538
  .describe('Full replacement props map. New map IS the new state.'),
328
539
  }).strict(),
329
540
  z.object({
330
- renderId: z.string().describe('Render opaque id (UUID) — returned by ggui_render.'),
541
+ sessionId: z.string().describe('GguiSession opaque id (UUID) — returned by ggui_render.'),
331
542
  kind: z.literal('merge'),
332
543
  patch: z.record(z.string(), z.unknown())
333
544
  .describe('RFC 7396 JSON Merge Patch — null deletes a key; arrays fully replace.'),
@@ -345,6 +556,51 @@ export const updateInputSchema = z.discriminatedUnion('kind', [
345
556
  * acknowledgement.
346
557
  */
347
558
  export const updateOutputSchema = z.object({
348
- renderId: z.string(),
559
+ sessionId: z.string(),
349
560
  updated: z.boolean(),
561
+ /**
562
+ * Unchanged from the initial render — the same `ui://ggui/render/{id}`
563
+ * URI the mount stamped. Mirrored on the LLM-visible structuredContent
564
+ * so SDKs that strip `_meta` from tool_results can still reach the
565
+ * mount URI. Kept in sync with the update handler's wire shape —
566
+ * this export and the handler's inline schema must not drift.
567
+ */
568
+ resourceUri: z.string(),
350
569
  });
570
+ /**
571
+ * `ggui_runtime_declare_tool_catalog` — the host runtime declares its
572
+ * per-app canonical tool-identity catalog (one row per app).
573
+ *
574
+ * The map is `bare tool name → the canonical serverInfo` that the tool's
575
+ * MCP server announced in its `initialize` reply. ggui folds this into
576
+ * the handshake step (`canonicalizeToolIdentity`) so a reused blueprint's
577
+ * `agentCapabilities.tools[*].serverInfo` is rewritten to the canonical
578
+ * value regardless of whether the inbound contract authored a config-key
579
+ * name, fabricated one, or omitted it. That makes blueprint reuse
580
+ * identity-stable across runtimes.
581
+ *
582
+ * Keyed by the BARE tool name — the same key the canonicalization step
583
+ * matches on. `version` is OPTIONAL: it rides along as metadata; tool
584
+ * identity is `(name)` matched by bare name, never `(name, version)`.
585
+ *
586
+ * `appId` is NOT on the input — the handler reads it off `ctx.appId`
587
+ * resolved by the upstream auth adapter, so a declaration can only ever
588
+ * write its own app's row. The output echoes the resolved `appId` so the
589
+ * caller can confirm which app row it wrote.
590
+ *
591
+ * REPLACE semantics: each declaration overwrites the app's prior catalog
592
+ * wholesale (the host re-declares its full current toolset on connect).
593
+ */
594
+ export const declareToolCatalogInputSchema = z
595
+ .object({
596
+ toolCatalog: z
597
+ .record(z.string(), z.object({ name: z.string(), version: z.string().optional() }).strict())
598
+ .describe("Per-app canonical tool identities: bare tool name -> its server's initialize-declared serverInfo. Host/library-supplied; not an agent action."),
599
+ })
600
+ .strict();
601
+ export const declareToolCatalogOutputSchema = z
602
+ .object({
603
+ saved: z.boolean(),
604
+ appId: z.string(),
605
+ })
606
+ .strict();