@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.
- package/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/bridge/invoke-agent.d.ts +65 -0
- package/dist/bridge/invoke-agent.d.ts.map +1 -0
- package/dist/bridge/invoke-agent.js +113 -0
- package/dist/envelope-adapters.d.ts +24 -0
- package/dist/envelope-adapters.d.ts.map +1 -0
- package/dist/envelope-adapters.js +14 -0
- package/dist/envelopes/builders.d.ts +145 -0
- package/dist/envelopes/builders.d.ts.map +1 -0
- package/dist/envelopes/builders.js +113 -0
- package/dist/errors/unknown-permission-name.d.ts +12 -0
- package/dist/errors/unknown-permission-name.d.ts.map +1 -0
- package/dist/errors/unknown-permission-name.js +29 -0
- package/dist/errors/version-mismatch.d.ts +55 -0
- package/dist/errors/version-mismatch.d.ts.map +1 -0
- package/dist/errors/version-mismatch.js +52 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
- package/dist/gadgets/resolve-contract-gadgets.js +119 -0
- package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
- package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
- package/dist/gadgets/stdlib-gadgets.js +161 -0
- package/dist/iframe-bridge.d.ts +63 -0
- package/dist/iframe-bridge.d.ts.map +1 -0
- package/dist/iframe-bridge.js +166 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/integrations/mcp-apps.d.ts +1218 -0
- package/dist/integrations/mcp-apps.d.ts.map +1 -0
- package/dist/integrations/mcp-apps.js +427 -0
- package/dist/navigation/index.d.ts +3 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/stack-navigation.d.ts +55 -0
- package/dist/navigation/stack-navigation.d.ts.map +1 -0
- package/dist/navigation/stack-navigation.js +80 -0
- package/dist/recommended-prompts.d.ts +56 -0
- package/dist/recommended-prompts.d.ts.map +1 -0
- package/dist/recommended-prompts.js +55 -0
- package/dist/registry/blueprint-key.d.ts +9 -0
- package/dist/registry/blueprint-key.d.ts.map +1 -0
- package/dist/registry/blueprint-key.js +28 -0
- package/dist/registry/canonicalize-contract.d.ts +35 -0
- package/dist/registry/canonicalize-contract.d.ts.map +1 -0
- package/dist/registry/canonicalize-contract.js +166 -0
- package/dist/registry/summarize-contract.d.ts +46 -0
- package/dist/registry/summarize-contract.d.ts.map +1 -0
- package/dist/registry/summarize-contract.js +63 -0
- package/dist/schema-learning/derive-contract.d.ts +67 -0
- package/dist/schema-learning/derive-contract.d.ts.map +1 -0
- package/dist/schema-learning/derive-contract.js +117 -0
- package/dist/schema-learning/merge.d.ts +32 -0
- package/dist/schema-learning/merge.d.ts.map +1 -0
- package/dist/schema-learning/merge.js +146 -0
- package/dist/schemas/blueprint.d.ts +32 -0
- package/dist/schemas/blueprint.d.ts.map +1 -0
- package/dist/schemas/blueprint.js +92 -0
- package/dist/schemas/data-contract.d.ts +750 -0
- package/dist/schemas/data-contract.d.ts.map +1 -0
- package/dist/schemas/data-contract.js +663 -0
- package/dist/schemas/gadget-name-grammar.d.ts +29 -0
- package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
- package/dist/schemas/gadget-name-grammar.js +28 -0
- package/dist/schemas/handshake-suggestion.d.ts +46 -0
- package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
- package/dist/schemas/handshake-suggestion.js +107 -0
- package/dist/schemas/invoke.d.ts +337 -0
- package/dist/schemas/invoke.d.ts.map +1 -0
- package/dist/schemas/invoke.js +169 -0
- package/dist/schemas/mcp.d.ts +301 -0
- package/dist/schemas/mcp.d.ts.map +1 -0
- package/dist/schemas/mcp.js +373 -0
- package/dist/schemas/ops-blueprint.d.ts +176 -0
- package/dist/schemas/ops-blueprint.d.ts.map +1 -0
- package/dist/schemas/ops-blueprint.js +259 -0
- package/dist/schemas/sync-check.d.ts +11 -0
- package/dist/schemas/sync-check.d.ts.map +1 -0
- package/dist/schemas/sync-check.js +60 -0
- package/dist/screen-blueprints/define.d.ts +22 -0
- package/dist/screen-blueprints/define.d.ts.map +1 -0
- package/dist/screen-blueprints/define.js +3 -0
- package/dist/screen-blueprints/index.d.ts +4 -0
- package/dist/screen-blueprints/index.d.ts.map +1 -0
- package/dist/screen-blueprints/index.js +3 -0
- package/dist/screen-blueprints/match.d.ts +35 -0
- package/dist/screen-blueprints/match.d.ts.map +1 -0
- package/dist/screen-blueprints/match.js +51 -0
- package/dist/screen-blueprints/types.d.ts +164 -0
- package/dist/screen-blueprints/types.d.ts.map +1 -0
- package/dist/screen-blueprints/types.js +1 -0
- package/dist/stream/stream-parser.d.ts +62 -0
- package/dist/stream/stream-parser.d.ts.map +1 -0
- package/dist/stream/stream-parser.js +199 -0
- package/dist/transport/websocket.d.ts +178 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +1 -0
- package/dist/types/app-config.d.ts +61 -0
- package/dist/types/app-config.d.ts.map +1 -0
- package/dist/types/app-config.js +1 -0
- package/dist/types/auth.d.ts +61 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/blueprint.d.ts +206 -0
- package/dist/types/blueprint.d.ts.map +1 -0
- package/dist/types/blueprint.js +1 -0
- package/dist/types/canvas-lifecycle.d.ts +105 -0
- package/dist/types/canvas-lifecycle.d.ts.map +1 -0
- package/dist/types/canvas-lifecycle.js +38 -0
- package/dist/types/capabilities.d.ts +40 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +19 -0
- package/dist/types/contract-inference.d.ts +401 -0
- package/dist/types/contract-inference.d.ts.map +1 -0
- package/dist/types/contract-inference.js +44 -0
- package/dist/types/credential.d.ts +41 -0
- package/dist/types/credential.d.ts.map +1 -0
- package/dist/types/credential.js +32 -0
- package/dist/types/data-bindings.d.ts +322 -0
- package/dist/types/data-bindings.d.ts.map +1 -0
- package/dist/types/data-bindings.js +29 -0
- package/dist/types/data-contract.d.ts +1296 -0
- package/dist/types/data-contract.d.ts.map +1 -0
- package/dist/types/data-contract.js +111 -0
- package/dist/types/events.d.ts +182 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/feedback.d.ts +24 -0
- package/dist/types/feedback.d.ts.map +1 -0
- package/dist/types/feedback.js +7 -0
- package/dist/types/gadget.d.ts +121 -0
- package/dist/types/gadget.d.ts.map +1 -0
- package/dist/types/gadget.js +24 -0
- package/dist/types/handshake-suggestion.d.ts +264 -0
- package/dist/types/handshake-suggestion.d.ts.map +1 -0
- package/dist/types/handshake-suggestion.js +70 -0
- package/dist/types/host-context.d.ts +163 -0
- package/dist/types/host-context.d.ts.map +1 -0
- package/dist/types/host-context.js +142 -0
- package/dist/types/interface-context.d.ts +105 -0
- package/dist/types/interface-context.d.ts.map +1 -0
- package/dist/types/interface-context.js +115 -0
- package/dist/types/invoke.d.ts +28 -0
- package/dist/types/invoke.d.ts.map +1 -0
- package/dist/types/invoke.js +7 -0
- package/dist/types/live-channel.d.ts +613 -0
- package/dist/types/live-channel.d.ts.map +1 -0
- package/dist/types/live-channel.js +1 -0
- package/dist/types/llm.d.ts +61 -0
- package/dist/types/llm.d.ts.map +1 -0
- package/dist/types/llm.js +186 -0
- package/dist/types/mcp-proxy.d.ts +67 -0
- package/dist/types/mcp-proxy.d.ts.map +1 -0
- package/dist/types/mcp-proxy.js +46 -0
- package/dist/types/mcp.d.ts +637 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +30 -0
- package/dist/types/openrouter-models.d.ts +22 -0
- package/dist/types/openrouter-models.d.ts.map +1 -0
- package/dist/types/openrouter-models.js +4843 -0
- package/dist/types/region.d.ts +26 -0
- package/dist/types/region.d.ts.map +1 -0
- package/dist/types/region.js +36 -0
- package/dist/types/session.d.ts +419 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +1 -0
- package/dist/types/thread.d.ts +207 -0
- package/dist/types/thread.d.ts.map +1 -0
- package/dist/types/thread.js +57 -0
- package/dist/types/ui-generator.d.ts +100 -0
- package/dist/types/ui-generator.d.ts.map +1 -0
- package/dist/types/ui-generator.js +53 -0
- package/dist/validation/ajv-runtime.d.ts +140 -0
- package/dist/validation/ajv-runtime.d.ts.map +1 -0
- package/dist/validation/ajv-runtime.js +452 -0
- package/dist/validation/content-hash.d.ts +3 -0
- package/dist/validation/content-hash.d.ts.map +1 -0
- package/dist/validation/content-hash.js +21 -0
- package/dist/validation/contract-validator.d.ts +244 -0
- package/dist/validation/contract-validator.d.ts.map +1 -0
- package/dist/validation/contract-validator.js +711 -0
- package/dist/validation/cross-references.d.ts +105 -0
- package/dist/validation/cross-references.d.ts.map +1 -0
- package/dist/validation/cross-references.js +164 -0
- package/dist/validation/hygiene-rules.d.ts +250 -0
- package/dist/validation/hygiene-rules.d.ts.map +1 -0
- package/dist/validation/hygiene-rules.js +564 -0
- package/dist/validation/lint-contract.d.ts +130 -0
- package/dist/validation/lint-contract.d.ts.map +1 -0
- package/dist/validation/lint-contract.js +225 -0
- package/dist/validation/name-invariants.d.ts +117 -0
- package/dist/validation/name-invariants.d.ts.map +1 -0
- package/dist/validation/name-invariants.js +172 -0
- package/dist/validation/reserved-channels.d.ts +156 -0
- package/dist/validation/reserved-channels.d.ts.map +1 -0
- package/dist/validation/reserved-channels.js +356 -0
- package/dist/validation/resolve-stream-channel.d.ts +78 -0
- package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
- package/dist/validation/resolve-stream-channel.js +64 -0
- package/dist/validation/sanitize-error.d.ts +46 -0
- package/dist/validation/sanitize-error.d.ts.map +1 -0
- package/dist/validation/sanitize-error.js +88 -0
- package/dist/validation/schema-compat-invariants.d.ts +140 -0
- package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
- package/dist/validation/schema-compat-invariants.js +220 -0
- package/dist/validation/schema-meta-validation.d.ts +60 -0
- package/dist/validation/schema-meta-validation.d.ts.map +1 -0
- package/dist/validation/schema-meta-validation.js +131 -0
- package/dist/validation/schema-subset.d.ts +165 -0
- package/dist/validation/schema-subset.d.ts.map +1 -0
- package/dist/validation/schema-subset.js +295 -0
- package/dist/validation/ui-security.d.ts +54 -0
- package/dist/validation/ui-security.d.ts.map +1 -0
- package/dist/validation/ui-security.js +138 -0
- package/dist/validation/zod-to-json-schema.d.ts +63 -0
- package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
- package/dist/validation/zod-to-json-schema.js +126 -0
- package/dist/version.d.ts +1458 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1459 -0
- 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
|
+
}
|