@ggui-ai/protocol 0.1.0-rc.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.
Files changed (222) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +46 -0
  3. package/dist/bridge/invoke-agent.d.ts +65 -0
  4. package/dist/bridge/invoke-agent.d.ts.map +1 -0
  5. package/dist/bridge/invoke-agent.js +113 -0
  6. package/dist/envelope-adapters.d.ts +24 -0
  7. package/dist/envelope-adapters.d.ts.map +1 -0
  8. package/dist/envelope-adapters.js +14 -0
  9. package/dist/envelopes/builders.d.ts +145 -0
  10. package/dist/envelopes/builders.d.ts.map +1 -0
  11. package/dist/envelopes/builders.js +113 -0
  12. package/dist/errors/unknown-permission-name.d.ts +12 -0
  13. package/dist/errors/unknown-permission-name.d.ts.map +1 -0
  14. package/dist/errors/unknown-permission-name.js +29 -0
  15. package/dist/errors/version-mismatch.d.ts +55 -0
  16. package/dist/errors/version-mismatch.d.ts.map +1 -0
  17. package/dist/errors/version-mismatch.js +52 -0
  18. package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
  19. package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
  20. package/dist/gadgets/resolve-contract-gadgets.js +119 -0
  21. package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
  22. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
  23. package/dist/gadgets/stdlib-gadgets.js +161 -0
  24. package/dist/iframe-bridge.d.ts +63 -0
  25. package/dist/iframe-bridge.d.ts.map +1 -0
  26. package/dist/iframe-bridge.js +166 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  30. package/dist/integrations/mcp-apps.d.ts +1218 -0
  31. package/dist/integrations/mcp-apps.d.ts.map +1 -0
  32. package/dist/integrations/mcp-apps.js +427 -0
  33. package/dist/navigation/index.d.ts +3 -0
  34. package/dist/navigation/index.d.ts.map +1 -0
  35. package/dist/navigation/index.js +1 -0
  36. package/dist/navigation/stack-navigation.d.ts +55 -0
  37. package/dist/navigation/stack-navigation.d.ts.map +1 -0
  38. package/dist/navigation/stack-navigation.js +80 -0
  39. package/dist/recommended-prompts.d.ts +56 -0
  40. package/dist/recommended-prompts.d.ts.map +1 -0
  41. package/dist/recommended-prompts.js +55 -0
  42. package/dist/registry/blueprint-key.d.ts +9 -0
  43. package/dist/registry/blueprint-key.d.ts.map +1 -0
  44. package/dist/registry/blueprint-key.js +28 -0
  45. package/dist/registry/canonicalize-contract.d.ts +35 -0
  46. package/dist/registry/canonicalize-contract.d.ts.map +1 -0
  47. package/dist/registry/canonicalize-contract.js +166 -0
  48. package/dist/registry/summarize-contract.d.ts +46 -0
  49. package/dist/registry/summarize-contract.d.ts.map +1 -0
  50. package/dist/registry/summarize-contract.js +63 -0
  51. package/dist/schema-learning/derive-contract.d.ts +67 -0
  52. package/dist/schema-learning/derive-contract.d.ts.map +1 -0
  53. package/dist/schema-learning/derive-contract.js +117 -0
  54. package/dist/schema-learning/merge.d.ts +32 -0
  55. package/dist/schema-learning/merge.d.ts.map +1 -0
  56. package/dist/schema-learning/merge.js +146 -0
  57. package/dist/schemas/blueprint.d.ts +32 -0
  58. package/dist/schemas/blueprint.d.ts.map +1 -0
  59. package/dist/schemas/blueprint.js +92 -0
  60. package/dist/schemas/data-contract.d.ts +750 -0
  61. package/dist/schemas/data-contract.d.ts.map +1 -0
  62. package/dist/schemas/data-contract.js +663 -0
  63. package/dist/schemas/gadget-name-grammar.d.ts +29 -0
  64. package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
  65. package/dist/schemas/gadget-name-grammar.js +28 -0
  66. package/dist/schemas/handshake-suggestion.d.ts +46 -0
  67. package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
  68. package/dist/schemas/handshake-suggestion.js +107 -0
  69. package/dist/schemas/invoke.d.ts +337 -0
  70. package/dist/schemas/invoke.d.ts.map +1 -0
  71. package/dist/schemas/invoke.js +169 -0
  72. package/dist/schemas/mcp.d.ts +301 -0
  73. package/dist/schemas/mcp.d.ts.map +1 -0
  74. package/dist/schemas/mcp.js +373 -0
  75. package/dist/schemas/ops-blueprint.d.ts +176 -0
  76. package/dist/schemas/ops-blueprint.d.ts.map +1 -0
  77. package/dist/schemas/ops-blueprint.js +259 -0
  78. package/dist/schemas/sync-check.d.ts +11 -0
  79. package/dist/schemas/sync-check.d.ts.map +1 -0
  80. package/dist/schemas/sync-check.js +60 -0
  81. package/dist/screen-blueprints/define.d.ts +22 -0
  82. package/dist/screen-blueprints/define.d.ts.map +1 -0
  83. package/dist/screen-blueprints/define.js +3 -0
  84. package/dist/screen-blueprints/index.d.ts +4 -0
  85. package/dist/screen-blueprints/index.d.ts.map +1 -0
  86. package/dist/screen-blueprints/index.js +3 -0
  87. package/dist/screen-blueprints/match.d.ts +35 -0
  88. package/dist/screen-blueprints/match.d.ts.map +1 -0
  89. package/dist/screen-blueprints/match.js +51 -0
  90. package/dist/screen-blueprints/types.d.ts +164 -0
  91. package/dist/screen-blueprints/types.d.ts.map +1 -0
  92. package/dist/screen-blueprints/types.js +1 -0
  93. package/dist/stream/stream-parser.d.ts +62 -0
  94. package/dist/stream/stream-parser.d.ts.map +1 -0
  95. package/dist/stream/stream-parser.js +199 -0
  96. package/dist/transport/websocket.d.ts +178 -0
  97. package/dist/transport/websocket.d.ts.map +1 -0
  98. package/dist/transport/websocket.js +1 -0
  99. package/dist/types/app-config.d.ts +61 -0
  100. package/dist/types/app-config.d.ts.map +1 -0
  101. package/dist/types/app-config.js +1 -0
  102. package/dist/types/auth.d.ts +61 -0
  103. package/dist/types/auth.d.ts.map +1 -0
  104. package/dist/types/auth.js +1 -0
  105. package/dist/types/blueprint.d.ts +206 -0
  106. package/dist/types/blueprint.d.ts.map +1 -0
  107. package/dist/types/blueprint.js +1 -0
  108. package/dist/types/canvas-lifecycle.d.ts +105 -0
  109. package/dist/types/canvas-lifecycle.d.ts.map +1 -0
  110. package/dist/types/canvas-lifecycle.js +38 -0
  111. package/dist/types/capabilities.d.ts +40 -0
  112. package/dist/types/capabilities.d.ts.map +1 -0
  113. package/dist/types/capabilities.js +19 -0
  114. package/dist/types/contract-inference.d.ts +401 -0
  115. package/dist/types/contract-inference.d.ts.map +1 -0
  116. package/dist/types/contract-inference.js +44 -0
  117. package/dist/types/credential.d.ts +41 -0
  118. package/dist/types/credential.d.ts.map +1 -0
  119. package/dist/types/credential.js +32 -0
  120. package/dist/types/data-bindings.d.ts +322 -0
  121. package/dist/types/data-bindings.d.ts.map +1 -0
  122. package/dist/types/data-bindings.js +29 -0
  123. package/dist/types/data-contract.d.ts +1296 -0
  124. package/dist/types/data-contract.d.ts.map +1 -0
  125. package/dist/types/data-contract.js +111 -0
  126. package/dist/types/events.d.ts +182 -0
  127. package/dist/types/events.d.ts.map +1 -0
  128. package/dist/types/events.js +8 -0
  129. package/dist/types/feedback.d.ts +24 -0
  130. package/dist/types/feedback.d.ts.map +1 -0
  131. package/dist/types/feedback.js +7 -0
  132. package/dist/types/gadget.d.ts +121 -0
  133. package/dist/types/gadget.d.ts.map +1 -0
  134. package/dist/types/gadget.js +24 -0
  135. package/dist/types/handshake-suggestion.d.ts +264 -0
  136. package/dist/types/handshake-suggestion.d.ts.map +1 -0
  137. package/dist/types/handshake-suggestion.js +70 -0
  138. package/dist/types/host-context.d.ts +163 -0
  139. package/dist/types/host-context.d.ts.map +1 -0
  140. package/dist/types/host-context.js +142 -0
  141. package/dist/types/interface-context.d.ts +105 -0
  142. package/dist/types/interface-context.d.ts.map +1 -0
  143. package/dist/types/interface-context.js +115 -0
  144. package/dist/types/invoke.d.ts +28 -0
  145. package/dist/types/invoke.d.ts.map +1 -0
  146. package/dist/types/invoke.js +7 -0
  147. package/dist/types/live-channel.d.ts +613 -0
  148. package/dist/types/live-channel.d.ts.map +1 -0
  149. package/dist/types/live-channel.js +1 -0
  150. package/dist/types/llm.d.ts +61 -0
  151. package/dist/types/llm.d.ts.map +1 -0
  152. package/dist/types/llm.js +186 -0
  153. package/dist/types/mcp-proxy.d.ts +67 -0
  154. package/dist/types/mcp-proxy.d.ts.map +1 -0
  155. package/dist/types/mcp-proxy.js +46 -0
  156. package/dist/types/mcp.d.ts +637 -0
  157. package/dist/types/mcp.d.ts.map +1 -0
  158. package/dist/types/mcp.js +30 -0
  159. package/dist/types/openrouter-models.d.ts +22 -0
  160. package/dist/types/openrouter-models.d.ts.map +1 -0
  161. package/dist/types/openrouter-models.js +4843 -0
  162. package/dist/types/region.d.ts +26 -0
  163. package/dist/types/region.d.ts.map +1 -0
  164. package/dist/types/region.js +36 -0
  165. package/dist/types/session.d.ts +419 -0
  166. package/dist/types/session.d.ts.map +1 -0
  167. package/dist/types/session.js +1 -0
  168. package/dist/types/thread.d.ts +207 -0
  169. package/dist/types/thread.d.ts.map +1 -0
  170. package/dist/types/thread.js +57 -0
  171. package/dist/types/ui-generator.d.ts +100 -0
  172. package/dist/types/ui-generator.d.ts.map +1 -0
  173. package/dist/types/ui-generator.js +53 -0
  174. package/dist/validation/ajv-runtime.d.ts +140 -0
  175. package/dist/validation/ajv-runtime.d.ts.map +1 -0
  176. package/dist/validation/ajv-runtime.js +452 -0
  177. package/dist/validation/content-hash.d.ts +3 -0
  178. package/dist/validation/content-hash.d.ts.map +1 -0
  179. package/dist/validation/content-hash.js +21 -0
  180. package/dist/validation/contract-validator.d.ts +244 -0
  181. package/dist/validation/contract-validator.d.ts.map +1 -0
  182. package/dist/validation/contract-validator.js +711 -0
  183. package/dist/validation/cross-references.d.ts +105 -0
  184. package/dist/validation/cross-references.d.ts.map +1 -0
  185. package/dist/validation/cross-references.js +164 -0
  186. package/dist/validation/hygiene-rules.d.ts +250 -0
  187. package/dist/validation/hygiene-rules.d.ts.map +1 -0
  188. package/dist/validation/hygiene-rules.js +564 -0
  189. package/dist/validation/lint-contract.d.ts +130 -0
  190. package/dist/validation/lint-contract.d.ts.map +1 -0
  191. package/dist/validation/lint-contract.js +225 -0
  192. package/dist/validation/name-invariants.d.ts +117 -0
  193. package/dist/validation/name-invariants.d.ts.map +1 -0
  194. package/dist/validation/name-invariants.js +172 -0
  195. package/dist/validation/reserved-channels.d.ts +156 -0
  196. package/dist/validation/reserved-channels.d.ts.map +1 -0
  197. package/dist/validation/reserved-channels.js +356 -0
  198. package/dist/validation/resolve-stream-channel.d.ts +78 -0
  199. package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
  200. package/dist/validation/resolve-stream-channel.js +64 -0
  201. package/dist/validation/sanitize-error.d.ts +46 -0
  202. package/dist/validation/sanitize-error.d.ts.map +1 -0
  203. package/dist/validation/sanitize-error.js +88 -0
  204. package/dist/validation/schema-compat-invariants.d.ts +140 -0
  205. package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
  206. package/dist/validation/schema-compat-invariants.js +220 -0
  207. package/dist/validation/schema-meta-validation.d.ts +60 -0
  208. package/dist/validation/schema-meta-validation.d.ts.map +1 -0
  209. package/dist/validation/schema-meta-validation.js +131 -0
  210. package/dist/validation/schema-subset.d.ts +165 -0
  211. package/dist/validation/schema-subset.d.ts.map +1 -0
  212. package/dist/validation/schema-subset.js +295 -0
  213. package/dist/validation/ui-security.d.ts +54 -0
  214. package/dist/validation/ui-security.d.ts.map +1 -0
  215. package/dist/validation/ui-security.js +138 -0
  216. package/dist/validation/zod-to-json-schema.d.ts +63 -0
  217. package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
  218. package/dist/validation/zod-to-json-schema.js +126 -0
  219. package/dist/version.d.ts +1458 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +1459 -0
  222. package/package.json +113 -0
@@ -0,0 +1,138 @@
1
+ // packages/protocol/src/validation/ui-security.ts
2
+ //
3
+ // Shared UI security validation — single source of truth for dangerous
4
+ // patterns and UI classification. Used by:
5
+ // - core/src/tools/validation.ts (generator pipeline)
6
+ // - core/src/validation/ui-compiler.ts (CLI compiler)
7
+ // - cloud/amplify/functions/rest-api/cli-api/ui-register-handler.ts (register endpoint)
8
+ //
9
+ // This module is PUBLIC (@ggui-ai/protocol) — keep it dependency-free.
10
+ /**
11
+ * Patterns that are NEVER allowed in sandboxed UI components.
12
+ * These are security-critical — changes here affect every validation consumer.
13
+ */
14
+ export const DANGEROUS_PATTERNS = [
15
+ {
16
+ pattern: /\beval\s*\(/,
17
+ name: 'eval()',
18
+ suggestion: 'Remove eval() - it allows arbitrary code execution. Use proper data handling instead.',
19
+ },
20
+ {
21
+ pattern: /\bFunction\s*\(/,
22
+ name: 'Function constructor',
23
+ suggestion: 'Remove Function() constructor - it allows arbitrary code execution.',
24
+ },
25
+ {
26
+ pattern: /\binnerHTML\s*=/,
27
+ name: 'innerHTML',
28
+ suggestion: 'Use React components instead of innerHTML to prevent XSS vulnerabilities.',
29
+ },
30
+ {
31
+ pattern: /\bdangerouslySetInnerHTML\b/,
32
+ name: 'dangerouslySetInnerHTML',
33
+ suggestion: 'Avoid dangerouslySetInnerHTML - use React components for rendering.',
34
+ },
35
+ {
36
+ pattern: /\bdocument\.\w+/,
37
+ name: 'document access',
38
+ suggestion: 'Do not access document directly - use React refs and state instead.',
39
+ },
40
+ {
41
+ pattern: /\bwindow\.(?!__GGUI)/,
42
+ name: 'window access',
43
+ suggestion: 'Do not access window directly - use React patterns instead.',
44
+ },
45
+ {
46
+ pattern: /\blocalStorage\b/,
47
+ name: 'localStorage',
48
+ suggestion: 'Do not use localStorage - pass data through props and onSubmit.',
49
+ },
50
+ {
51
+ pattern: /\bsessionStorage\b/,
52
+ name: 'sessionStorage',
53
+ suggestion: 'Do not use sessionStorage - pass data through props and onSubmit.',
54
+ },
55
+ {
56
+ pattern: /\bfetch\s*\(/,
57
+ name: 'fetch()',
58
+ suggestion: 'Do not make network requests - use adapters and onSubmit for data operations.',
59
+ },
60
+ {
61
+ pattern: /\bXMLHttpRequest\b/,
62
+ name: 'XMLHttpRequest',
63
+ suggestion: 'Do not make network requests - use adapters and onSubmit for data operations.',
64
+ },
65
+ {
66
+ pattern: /\bimport\s*\(/,
67
+ name: 'dynamic import',
68
+ suggestion: 'Do not use dynamic imports - all dependencies must be static imports.',
69
+ },
70
+ {
71
+ pattern: /<script\b/i,
72
+ name: 'script tag',
73
+ suggestion: 'Do not include script tags - use React components only.',
74
+ },
75
+ {
76
+ pattern: /\bnew\s+WebSocket\b/,
77
+ name: 'WebSocket',
78
+ suggestion: 'Do not create WebSocket connections - communication is handled by ggui.',
79
+ },
80
+ {
81
+ pattern: /\bnavigator\./,
82
+ name: 'navigator access',
83
+ suggestion: 'Do not access navigator - use React patterns for user interactions.',
84
+ },
85
+ {
86
+ pattern: /\blocation\./,
87
+ name: 'location access',
88
+ suggestion: 'Do not access location - routing is handled externally.',
89
+ },
90
+ {
91
+ pattern: /\bhistory\./,
92
+ name: 'history access',
93
+ suggestion: 'Do not access history - navigation is handled externally.',
94
+ },
95
+ ];
96
+ // ── UI Classification ───────────────────────────────────────────────
97
+ /** Import prefixes that indicate a fullstack UI (requires client bundle). */
98
+ export const FULLSTACK_IMPORT_PREFIXES = [
99
+ '@ggui-ai/wire',
100
+ '@ggui-ai/react',
101
+ '@app/components',
102
+ ];
103
+ /**
104
+ * Classify a component as sandboxed or fullstack based on its imports.
105
+ *
106
+ * - **sandboxed**: Pure React + @ggui-ai/design primitives. Portable, publishable.
107
+ * - **fullstack**: Uses @ggui-ai/wire, @ggui-ai/react, or @app/components. Private.
108
+ *
109
+ * Works on both source (.tsx) and compiled (.js) code.
110
+ */
111
+ export function classifyUi(code) {
112
+ const importRegex = /(?:from|require\()\s*['"]([^'"]+)['"]/g;
113
+ let match;
114
+ while ((match = importRegex.exec(code)) !== null) {
115
+ const src = match[1];
116
+ if (FULLSTACK_IMPORT_PREFIXES.some((prefix) => src === prefix || src.startsWith(prefix + '/'))) {
117
+ return 'fullstack';
118
+ }
119
+ }
120
+ return 'sandboxed';
121
+ }
122
+ /**
123
+ * Check code for dangerous patterns.
124
+ * Works on both source and compiled code.
125
+ */
126
+ export function checkSecurity(code) {
127
+ const violations = [];
128
+ for (const { pattern, name, suggestion } of DANGEROUS_PATTERNS) {
129
+ // Reset lastIndex for global regexes
130
+ const re = new RegExp(pattern.source, pattern.flags);
131
+ const match = re.exec(code);
132
+ if (match) {
133
+ const line = code.substring(0, match.index).split('\n').length;
134
+ violations.push({ name, suggestion, line });
135
+ }
136
+ }
137
+ return { safe: violations.length === 0, violations };
138
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * zod → {@link JsonSchema} conversion, normalized to the shape our
3
+ * {@link isSchemaSubset} algorithm accepts.
4
+ *
5
+ * **Why zod's built-in vs. the `zod-to-json-schema` package.** That
6
+ * library only understands zod v3 internals — passing a zod v4 schema
7
+ * yields an empty `{$schema}` document. The protocol package is on
8
+ * zod v4, which ships its own built-in `z.toJSONSchema()` helper that
9
+ * produces correct JSON Schema output for every construct we care
10
+ * about. The wrapper below normalizes the v4 output — draft-2020-12
11
+ * by default, plus a small handful of zod-specific quirks — onto the
12
+ * {@link JsonSchema} shape the subset algorithm consumes.
13
+ *
14
+ * **Normalizations applied.**
15
+ *
16
+ * 1. Strip the top-level `$schema` URI. Our {@link JsonSchema} does
17
+ * not carry it, and the subset algorithm's "unsupported
18
+ * constructs" flagging would treat unknown top-level keys as
19
+ * surprises. `$schema` is metadata, not structure.
20
+ * 2. Preserve draft-2020-12 `additionalProperties: {}` (zod emits
21
+ * this for `.passthrough()`) as `additionalProperties: {}` —
22
+ * the subset algorithm treats the empty-schema case as a
23
+ * structured-but-unconstrained extras slot. Callers that want
24
+ * "strictly true" must convert explicitly.
25
+ * 3. Leave `anyOf` / `const` / `enum` shapes intact. The subset
26
+ * algorithm flags them as P1/P2 deferred constructs — the
27
+ * caller receives an honest `unsupported` violation rather
28
+ * than a silent pass.
29
+ * 4. `z.any()` / `z.unknown()` produce an empty schema (no keys).
30
+ * The subset algorithm treats an empty schema as a wildcard
31
+ * (matches `isSchemaSubset(..., {type: 'string'})` as
32
+ * compatible), which mirrors JSON Schema semantics.
33
+ *
34
+ * **Intended call sites.**
35
+ *
36
+ * - Push-time + blueprint-registration schema-compat checks in
37
+ * `@ggui-ai/mcp-server`: a mount-registered tool handler exposes
38
+ * its `inputSchema` as a {@link ZodRawShape}; wrapping it in
39
+ * `z.object(shape)` and converting gives the JsonSchema that
40
+ * pairs against the declared `actionSpec[name].schema`.
41
+ * - Ad-hoc authoring tools (e.g. console panels) that need a
42
+ * human-readable JSON shape for a zod definition.
43
+ *
44
+ * @see ./schema-subset.ts
45
+ */
46
+ import { type ZodRawShape, type ZodType } from 'zod';
47
+ import type { JsonSchema } from '../types/data-contract.js';
48
+ /**
49
+ * Convert a zod schema (or raw shape) to a {@link JsonSchema} suitable
50
+ * for the subset algorithm.
51
+ *
52
+ * - Pass a `ZodType` to convert it directly.
53
+ * - Pass a {@link ZodRawShape} (the raw `{ key: ZodType, ... }` map
54
+ * shape {@link SharedHandler.inputSchema} carries) to have it
55
+ * wrapped in `z.object(...)` before conversion.
56
+ *
57
+ * Never throws on legitimate input. If zod's native emitter returns
58
+ * a non-object (it shouldn't for any supported construct), we coerce
59
+ * to an empty schema `{}` so downstream comparison treats it as
60
+ * unconstrained.
61
+ */
62
+ export declare function zodToJsonSchema(input: ZodType | ZodRawShape): JsonSchema;
63
+ //# sourceMappingURL=zod-to-json-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-to-json-schema.d.ts","sourceRoot":"","sources":["../../src/validation/zod-to-json-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,EAAK,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAQxE"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * zod → {@link JsonSchema} conversion, normalized to the shape our
3
+ * {@link isSchemaSubset} algorithm accepts.
4
+ *
5
+ * **Why zod's built-in vs. the `zod-to-json-schema` package.** That
6
+ * library only understands zod v3 internals — passing a zod v4 schema
7
+ * yields an empty `{$schema}` document. The protocol package is on
8
+ * zod v4, which ships its own built-in `z.toJSONSchema()` helper that
9
+ * produces correct JSON Schema output for every construct we care
10
+ * about. The wrapper below normalizes the v4 output — draft-2020-12
11
+ * by default, plus a small handful of zod-specific quirks — onto the
12
+ * {@link JsonSchema} shape the subset algorithm consumes.
13
+ *
14
+ * **Normalizations applied.**
15
+ *
16
+ * 1. Strip the top-level `$schema` URI. Our {@link JsonSchema} does
17
+ * not carry it, and the subset algorithm's "unsupported
18
+ * constructs" flagging would treat unknown top-level keys as
19
+ * surprises. `$schema` is metadata, not structure.
20
+ * 2. Preserve draft-2020-12 `additionalProperties: {}` (zod emits
21
+ * this for `.passthrough()`) as `additionalProperties: {}` —
22
+ * the subset algorithm treats the empty-schema case as a
23
+ * structured-but-unconstrained extras slot. Callers that want
24
+ * "strictly true" must convert explicitly.
25
+ * 3. Leave `anyOf` / `const` / `enum` shapes intact. The subset
26
+ * algorithm flags them as P1/P2 deferred constructs — the
27
+ * caller receives an honest `unsupported` violation rather
28
+ * than a silent pass.
29
+ * 4. `z.any()` / `z.unknown()` produce an empty schema (no keys).
30
+ * The subset algorithm treats an empty schema as a wildcard
31
+ * (matches `isSchemaSubset(..., {type: 'string'})` as
32
+ * compatible), which mirrors JSON Schema semantics.
33
+ *
34
+ * **Intended call sites.**
35
+ *
36
+ * - Push-time + blueprint-registration schema-compat checks in
37
+ * `@ggui-ai/mcp-server`: a mount-registered tool handler exposes
38
+ * its `inputSchema` as a {@link ZodRawShape}; wrapping it in
39
+ * `z.object(shape)` and converting gives the JsonSchema that
40
+ * pairs against the declared `actionSpec[name].schema`.
41
+ * - Ad-hoc authoring tools (e.g. console panels) that need a
42
+ * human-readable JSON shape for a zod definition.
43
+ *
44
+ * @see ./schema-subset.ts
45
+ */
46
+ import { z } from 'zod';
47
+ /**
48
+ * Convert a zod schema (or raw shape) to a {@link JsonSchema} suitable
49
+ * for the subset algorithm.
50
+ *
51
+ * - Pass a `ZodType` to convert it directly.
52
+ * - Pass a {@link ZodRawShape} (the raw `{ key: ZodType, ... }` map
53
+ * shape {@link SharedHandler.inputSchema} carries) to have it
54
+ * wrapped in `z.object(...)` before conversion.
55
+ *
56
+ * Never throws on legitimate input. If zod's native emitter returns
57
+ * a non-object (it shouldn't for any supported construct), we coerce
58
+ * to an empty schema `{}` so downstream comparison treats it as
59
+ * unconstrained.
60
+ */
61
+ export function zodToJsonSchema(input) {
62
+ const schema = isZodType(input) ? input : z.object(input);
63
+ // zod v4's native emitter returns a plain JSON-Schema-shaped object.
64
+ // `z.toJSONSchema` is typed as `unknown` when narrowed by our
65
+ // JsonSchema; cast + normalize.
66
+ const raw = z.toJSONSchema(schema);
67
+ if (raw === null || typeof raw !== 'object')
68
+ return {};
69
+ return normalize(raw);
70
+ }
71
+ /**
72
+ * Strip zod / draft-2020-12 quirks the subset algorithm doesn't
73
+ * consume, recursively. Does NOT strip unsupported constructs
74
+ * (oneOf/anyOf/enum/const/$ref/allOf) — those surface as explicit
75
+ * `unsupported` violations from the subset algorithm, which is what
76
+ * we want.
77
+ */
78
+ function normalize(value) {
79
+ if (Array.isArray(value))
80
+ return value.map(normalize);
81
+ if (value === null || typeof value !== 'object')
82
+ return value;
83
+ const out = {};
84
+ for (const [key, v] of Object.entries(value)) {
85
+ if (key === '$schema')
86
+ continue;
87
+ if (key === 'properties' && v !== null && typeof v === 'object') {
88
+ const props = {};
89
+ for (const [pkey, pval] of Object.entries(v)) {
90
+ props[pkey] = normalize(pval);
91
+ }
92
+ out[key] = props;
93
+ continue;
94
+ }
95
+ if (key === 'additionalProperties') {
96
+ if (typeof v === 'boolean') {
97
+ out[key] = v;
98
+ }
99
+ else {
100
+ out[key] = normalize(v);
101
+ }
102
+ continue;
103
+ }
104
+ if (key === 'items') {
105
+ out[key] = normalize(v);
106
+ continue;
107
+ }
108
+ out[key] = normalize(v);
109
+ }
110
+ return out;
111
+ }
112
+ /**
113
+ * Discriminator between `ZodType` and `ZodRawShape`. Zod v4 schemas
114
+ * carry a `_def` field (via the internal def bag). A raw shape is a
115
+ * plain object with string keys mapping to ZodType instances. We
116
+ * check for the presence of a ZodType marker to distinguish; absence
117
+ * means treat as raw shape.
118
+ */
119
+ function isZodType(value) {
120
+ if (value === null || typeof value !== 'object')
121
+ return false;
122
+ // Every zod v4 schema has `parse` and `_def`. A raw shape — a plain
123
+ // object of ZodType values — does not.
124
+ const bag = value;
125
+ return typeof bag['parse'] === 'function' && '_def' in bag;
126
+ }