@gajae-code/ai 0.1.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/CHANGELOG.md +2644 -0
- package/README.md +1181 -0
- package/dist/types/api-registry.d.ts +30 -0
- package/dist/types/auth-broker/client.d.ts +66 -0
- package/dist/types/auth-broker/index.d.ts +5 -0
- package/dist/types/auth-broker/refresher.d.ts +25 -0
- package/dist/types/auth-broker/remote-store.d.ts +96 -0
- package/dist/types/auth-broker/server.d.ts +32 -0
- package/dist/types/auth-broker/types.d.ts +105 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
- package/dist/types/auth-gateway/http.d.ts +39 -0
- package/dist/types/auth-gateway/index.d.ts +3 -0
- package/dist/types/auth-gateway/server.d.ts +17 -0
- package/dist/types/auth-gateway/types.d.ts +115 -0
- package/dist/types/auth-storage.d.ts +641 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/model-cache.d.ts +17 -0
- package/dist/types/model-manager.d.ts +62 -0
- package/dist/types/model-thinking.d.ts +71 -0
- package/dist/types/models.d.ts +12 -0
- package/dist/types/provider-details.d.ts +24 -0
- package/dist/types/provider-models/bundled-references.d.ts +4 -0
- package/dist/types/provider-models/descriptors.d.ts +48 -0
- package/dist/types/provider-models/google.d.ts +20 -0
- package/dist/types/provider-models/index.d.ts +5 -0
- package/dist/types/provider-models/ollama.d.ts +7 -0
- package/dist/types/provider-models/openai-compat.d.ts +237 -0
- package/dist/types/provider-models/special.d.ts +16 -0
- package/dist/types/providers/amazon-bedrock.d.ts +36 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
- package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
- package/dist/types/providers/anthropic.d.ts +188 -0
- package/dist/types/providers/aws-credentials.d.ts +43 -0
- package/dist/types/providers/aws-eventstream.d.ts +38 -0
- package/dist/types/providers/aws-sigv4.d.ts +55 -0
- package/dist/types/providers/azure-openai-responses.d.ts +15 -0
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
- package/dist/types/providers/cursor.d.ts +42 -0
- package/dist/types/providers/error-message.d.ts +27 -0
- package/dist/types/providers/github-copilot-headers.d.ts +40 -0
- package/dist/types/providers/gitlab-duo.d.ts +27 -0
- package/dist/types/providers/google-auth.d.ts +24 -0
- package/dist/types/providers/google-gemini-cli.d.ts +72 -0
- package/dist/types/providers/google-gemini-headers.d.ts +18 -0
- package/dist/types/providers/google-shared.d.ts +163 -0
- package/dist/types/providers/google-types.d.ts +138 -0
- package/dist/types/providers/google-vertex.d.ts +7 -0
- package/dist/types/providers/google.d.ts +4 -0
- package/dist/types/providers/grammar.d.ts +1 -0
- package/dist/types/providers/kimi.d.ts +27 -0
- package/dist/types/providers/mock.d.ts +175 -0
- package/dist/types/providers/ollama.d.ts +6 -0
- package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
- package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
- package/dist/types/providers/openai-chat-server.d.ts +16 -0
- package/dist/types/providers/openai-codex/constants.d.ts +26 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
- package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
- package/dist/types/providers/openai-codex-responses.d.ts +67 -0
- package/dist/types/providers/openai-completions-compat.d.ts +25 -0
- package/dist/types/providers/openai-completions.d.ts +33 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
- package/dist/types/providers/openai-responses-server.d.ts +17 -0
- package/dist/types/providers/openai-responses-shared.d.ts +89 -0
- package/dist/types/providers/openai-responses.d.ts +32 -0
- package/dist/types/providers/pi-native-client.d.ts +13 -0
- package/dist/types/providers/pi-native-server.d.ts +68 -0
- package/dist/types/providers/register-builtins.d.ts +31 -0
- package/dist/types/providers/synthetic.d.ts +26 -0
- package/dist/types/providers/transform-messages.d.ts +12 -0
- package/dist/types/providers/vision-guard.d.ts +8 -0
- package/dist/types/rate-limit-utils.d.ts +19 -0
- package/dist/types/stream.d.ts +24 -0
- package/dist/types/types.d.ts +746 -0
- package/dist/types/usage/claude.d.ts +3 -0
- package/dist/types/usage/gemini.d.ts +2 -0
- package/dist/types/usage/github-copilot.d.ts +7 -0
- package/dist/types/usage/google-antigravity.d.ts +2 -0
- package/dist/types/usage/kimi.d.ts +2 -0
- package/dist/types/usage/minimax-code.d.ts +2 -0
- package/dist/types/usage/openai-codex.d.ts +3 -0
- package/dist/types/usage/shared.d.ts +1 -0
- package/dist/types/usage/zai.d.ts +2 -0
- package/dist/types/usage.d.ts +258 -0
- package/dist/types/utils/abort.d.ts +19 -0
- package/dist/types/utils/anthropic-auth.d.ts +31 -0
- package/dist/types/utils/discovery/antigravity.d.ts +61 -0
- package/dist/types/utils/discovery/codex.d.ts +38 -0
- package/dist/types/utils/discovery/cursor.d.ts +23 -0
- package/dist/types/utils/discovery/gemini.d.ts +25 -0
- package/dist/types/utils/discovery/index.d.ts +4 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
- package/dist/types/utils/event-stream.d.ts +28 -0
- package/dist/types/utils/fireworks-model-id.d.ts +10 -0
- package/dist/types/utils/foundry.d.ts +1 -0
- package/dist/types/utils/h2-fetch.d.ts +22 -0
- package/dist/types/utils/http-inspector.d.ts +31 -0
- package/dist/types/utils/idle-iterator.d.ts +67 -0
- package/dist/types/utils/json-parse.d.ts +10 -0
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
- package/dist/types/utils/oauth/anthropic.d.ts +22 -0
- package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
- package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
- package/dist/types/utils/oauth/callback-server.d.ts +57 -0
- package/dist/types/utils/oauth/cerebras.d.ts +1 -0
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/cursor.d.ts +15 -0
- package/dist/types/utils/oauth/deepseek.d.ts +10 -0
- package/dist/types/utils/oauth/firepass.d.ts +1 -0
- package/dist/types/utils/oauth/fireworks.d.ts +1 -0
- package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
- package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
- package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
- package/dist/types/utils/oauth/huggingface.d.ts +19 -0
- package/dist/types/utils/oauth/index.d.ts +38 -0
- package/dist/types/utils/oauth/kagi.d.ts +17 -0
- package/dist/types/utils/oauth/kilo.d.ts +5 -0
- package/dist/types/utils/oauth/kimi.d.ts +21 -0
- package/dist/types/utils/oauth/litellm.d.ts +18 -0
- package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
- package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
- package/dist/types/utils/oauth/moonshot.d.ts +1 -0
- package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
- package/dist/types/utils/oauth/nvidia.d.ts +18 -0
- package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
- package/dist/types/utils/oauth/ollama.d.ts +18 -0
- package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
- package/dist/types/utils/oauth/opencode.d.ts +18 -0
- package/dist/types/utils/oauth/parallel.d.ts +17 -0
- package/dist/types/utils/oauth/perplexity.d.ts +9 -0
- package/dist/types/utils/oauth/pkce.d.ts +8 -0
- package/dist/types/utils/oauth/qianfan.d.ts +17 -0
- package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
- package/dist/types/utils/oauth/synthetic.d.ts +1 -0
- package/dist/types/utils/oauth/tavily.d.ts +17 -0
- package/dist/types/utils/oauth/together.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +44 -0
- package/dist/types/utils/oauth/venice.d.ts +18 -0
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
- package/dist/types/utils/oauth/vllm.d.ts +16 -0
- package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
- package/dist/types/utils/oauth/zai.d.ts +18 -0
- package/dist/types/utils/oauth/zenmux.d.ts +1 -0
- package/dist/types/utils/overflow.d.ts +54 -0
- package/dist/types/utils/parse-bind.d.ts +23 -0
- package/dist/types/utils/provider-response.d.ts +3 -0
- package/dist/types/utils/retry-after.d.ts +3 -0
- package/dist/types/utils/retry.d.ts +26 -0
- package/dist/types/utils/schema/adapt.d.ts +24 -0
- package/dist/types/utils/schema/compatibility.d.ts +30 -0
- package/dist/types/utils/schema/dereference.d.ts +11 -0
- package/dist/types/utils/schema/draft.d.ts +10 -0
- package/dist/types/utils/schema/equality.d.ts +4 -0
- package/dist/types/utils/schema/fields.d.ts +49 -0
- package/dist/types/utils/schema/index.d.ts +13 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
- package/dist/types/utils/schema/meta-validator.d.ts +2 -0
- package/dist/types/utils/schema/normalize.d.ts +93 -0
- package/dist/types/utils/schema/spill.d.ts +8 -0
- package/dist/types/utils/schema/stamps.d.ts +25 -0
- package/dist/types/utils/schema/types.d.ts +4 -0
- package/dist/types/utils/schema/wire.d.ts +54 -0
- package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
- package/dist/types/utils/sse-debug.d.ts +10 -0
- package/dist/types/utils/tool-call-healing.d.ts +71 -0
- package/dist/types/utils/tool-choice.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +17 -0
- package/dist/types/utils.d.ts +28 -0
- package/package.json +146 -0
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +358 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/refresher.ts +127 -0
- package/src/auth-broker/remote-store.ts +623 -0
- package/src/auth-broker/server.ts +644 -0
- package/src/auth-broker/types.ts +127 -0
- package/src/auth-broker/wire-schemas.ts +200 -0
- package/src/auth-gateway/http.ts +194 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +717 -0
- package/src/auth-gateway/types.ts +134 -0
- package/src/auth-storage.ts +4104 -0
- package/src/cli.ts +262 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +450 -0
- package/src/model-thinking.ts +691 -0
- package/src/models.json +73853 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +90 -0
- package/src/provider-models/bundled-references.ts +38 -0
- package/src/provider-models/descriptors.ts +308 -0
- package/src/provider-models/google.ts +91 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2275 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/amazon-bedrock.ts +849 -0
- package/src/providers/anthropic-messages-server-schema.ts +229 -0
- package/src/providers/anthropic-messages-server.ts +677 -0
- package/src/providers/anthropic.ts +2696 -0
- package/src/providers/aws-credentials.ts +501 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +337 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2561 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +372 -0
- package/src/providers/google-auth.ts +252 -0
- package/src/providers/google-gemini-cli.ts +795 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +902 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +88 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/mock.ts +500 -0
- package/src/providers/ollama.ts +544 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +243 -0
- package/src/providers/openai-chat-server.ts +628 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +161 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2598 -0
- package/src/providers/openai-completions-compat.ts +279 -0
- package/src/providers/openai-completions.ts +1853 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1183 -0
- package/src/providers/openai-responses-shared.ts +800 -0
- package/src/providers/openai-responses.ts +621 -0
- package/src/providers/pi-native-client.ts +228 -0
- package/src/providers/pi-native-server.ts +210 -0
- package/src/providers/register-builtins.ts +412 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +309 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +895 -0
- package/src/types.ts +884 -0
- package/src/usage/claude.ts +431 -0
- package/src/usage/gemini.ts +250 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +201 -0
- package/src/usage/kimi.ts +271 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +503 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +183 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/anthropic-auth.ts +87 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +142 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/h2-fetch.ts +60 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +250 -0
- package/src/utils/json-parse.ts +148 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +200 -0
- package/src/utils/oauth/api-key-login.ts +87 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepseek.ts +53 -0
- package/src/utils/oauth/firepass.ts +24 -0
- package/src/utils/oauth/fireworks.ts +15 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +200 -0
- package/src/utils/oauth/google-gemini-cli.ts +256 -0
- package/src/utils/oauth/google-oauth-shared.ts +110 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +444 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +254 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +16 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama-cloud.ts +28 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +299 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +206 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +16 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +94 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +137 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/overflow.ts +137 -0
- package/src/utils/parse-bind.ts +54 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +54 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +190 -0
- package/src/utils/schema/index.ts +13 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.ts +11 -0
- package/src/utils/schema/wire.ts +213 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/tool-call-healing.ts +271 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1019 -0
- package/src/utils.ts +166 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline `$ref` / `$defs` in a JSON Schema so every consumer sees
|
|
3
|
+
* the full definition without needing a resolver.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Local `$ref` pointers (`#/$defs/Foo`, `#/definitions/Foo`)
|
|
7
|
+
* - Nested `$defs` / `definitions` blocks
|
|
8
|
+
* - Circular references (breaks the cycle by emitting `{}`)
|
|
9
|
+
*
|
|
10
|
+
* After dereferencing, `$defs` and `definitions` are stripped from the root.
|
|
11
|
+
*/
|
|
12
|
+
import { isJsonObject, type JsonObject } from "./types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a JSON-pointer-style `$ref` against the root schema's `$defs`
|
|
16
|
+
* or `definitions` block. Returns `undefined` for external or unresolvable refs.
|
|
17
|
+
*/
|
|
18
|
+
function resolveLocalRef(ref: string, root: JsonObject): JsonObject | undefined {
|
|
19
|
+
// Only handle local refs: #/$defs/Name or #/definitions/Name
|
|
20
|
+
const match = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref);
|
|
21
|
+
if (!match) return undefined;
|
|
22
|
+
|
|
23
|
+
const [, defsKey, name] = match;
|
|
24
|
+
const defs = root[defsKey!];
|
|
25
|
+
if (!isJsonObject(defs)) return undefined;
|
|
26
|
+
|
|
27
|
+
const resolved = defs[name!];
|
|
28
|
+
return isJsonObject(resolved) ? resolved : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Recursively dereference a JSON Schema node, inlining all local `$ref` pointers.
|
|
33
|
+
*/
|
|
34
|
+
function dereferenceNode(node: unknown, root: JsonObject, visiting: Set<string>): unknown {
|
|
35
|
+
if (!isJsonObject(node)) return node;
|
|
36
|
+
if (Array.isArray(node)) return node.map(item => dereferenceNode(item, root, visiting));
|
|
37
|
+
|
|
38
|
+
const ref = node.$ref;
|
|
39
|
+
if (typeof ref === "string") {
|
|
40
|
+
// Break circular references
|
|
41
|
+
if (visiting.has(ref)) return {};
|
|
42
|
+
const resolved = resolveLocalRef(ref, root);
|
|
43
|
+
if (!resolved) return node; // External ref — leave as-is
|
|
44
|
+
visiting.add(ref);
|
|
45
|
+
const inlined = dereferenceNode(resolved, root, visiting);
|
|
46
|
+
visiting.delete(ref);
|
|
47
|
+
|
|
48
|
+
// Merge sibling keywords (e.g. description, default) from the
|
|
49
|
+
// referencing node. In draft 2020-12 these are valid alongside $ref.
|
|
50
|
+
let hasSiblings = false;
|
|
51
|
+
for (const k in node) {
|
|
52
|
+
if (k !== "$ref") {
|
|
53
|
+
hasSiblings = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!hasSiblings || !isJsonObject(inlined)) return inlined;
|
|
58
|
+
const merged: JsonObject = { ...inlined, ...node };
|
|
59
|
+
delete merged.$ref;
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result: JsonObject = {};
|
|
64
|
+
for (const key in node) {
|
|
65
|
+
const value = node[key];
|
|
66
|
+
// Skip $defs/definitions — they get inlined into consumers
|
|
67
|
+
if (key === "$defs" || key === "definitions") continue;
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
result[key] = value.map(item => dereferenceNode(item, root, visiting));
|
|
71
|
+
} else if (isJsonObject(value)) {
|
|
72
|
+
result[key] = dereferenceNode(value, root, visiting);
|
|
73
|
+
} else {
|
|
74
|
+
result[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Dereference all local `$ref` pointers in a JSON Schema, inlining definitions
|
|
82
|
+
* from `$defs` / `definitions`. The `$defs` block is stripped from the output.
|
|
83
|
+
*
|
|
84
|
+
* Non-local refs (e.g. `http://...`) are left untouched.
|
|
85
|
+
* Circular references are broken with `{}`.
|
|
86
|
+
*
|
|
87
|
+
* @returns A new schema object with all local refs inlined, or the input unchanged
|
|
88
|
+
* if it's not an object or has no `$defs`/`definitions`.
|
|
89
|
+
*/
|
|
90
|
+
export function dereferenceJsonSchema(schema: unknown): unknown {
|
|
91
|
+
if (!isJsonObject(schema)) return schema;
|
|
92
|
+
|
|
93
|
+
// Fast path: nothing to dereference
|
|
94
|
+
const hasDefs = schema.$defs !== undefined || schema.definitions !== undefined;
|
|
95
|
+
if (!hasDefs) return schema;
|
|
96
|
+
|
|
97
|
+
return dereferenceNode(schema, schema, new Set());
|
|
98
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { areJsonValuesEqual } from "./equality";
|
|
2
|
+
import { epochNext, once } from "./stamps";
|
|
3
|
+
import { isJsonObject, type JsonObject } from "./types";
|
|
4
|
+
|
|
5
|
+
export const JSON_SCHEMA_DRAFT_2020_12_URI = "https://json-schema.org/draft/2020-12/schema";
|
|
6
|
+
|
|
7
|
+
/** Draft-07 schema URIs we recognise as needing an upgrade. The trailing `#` is the canonical form in the JSON Schema spec, but providers (and Zod) emit both. */
|
|
8
|
+
const DRAFT_07_SCHEMA_URIS: Record<string, true> = {
|
|
9
|
+
"http://json-schema.org/draft-07/schema#": true,
|
|
10
|
+
"https://json-schema.org/draft-07/schema#": true,
|
|
11
|
+
"http://json-schema.org/draft-07/schema": true,
|
|
12
|
+
"https://json-schema.org/draft-07/schema": true,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Keys whose values are property-name → schema maps. We recurse into each map
|
|
17
|
+
* entry rather than the map object itself so legacy `definitions`-style refs
|
|
18
|
+
* inside property schemas get rewritten.
|
|
19
|
+
*/
|
|
20
|
+
const SCHEMA_MAP_KEYS: Record<string, true> = { properties: true, patternProperties: true, dependentSchemas: true };
|
|
21
|
+
/**
|
|
22
|
+
* Keys whose values are JSON-Schema *values*, not nested schemas. The upgrade
|
|
23
|
+
* walker must NOT descend into these — `type: ["string","null"]` is not a
|
|
24
|
+
* schema, and recursing would corrupt `enum`/`const`/`default` payloads.
|
|
25
|
+
*/
|
|
26
|
+
const NON_SCHEMA_VALUE_KEYS: Record<string, true> = {
|
|
27
|
+
const: true,
|
|
28
|
+
default: true,
|
|
29
|
+
enum: true,
|
|
30
|
+
example: true,
|
|
31
|
+
examples: true,
|
|
32
|
+
required: true,
|
|
33
|
+
dependentRequired: true,
|
|
34
|
+
type: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Rewrite draft-07's `#/definitions/Foo` ref form to draft 2020-12's `#/$defs/Foo`. External refs (`http://…`) pass through. */
|
|
38
|
+
function convertRef(value: string): string {
|
|
39
|
+
return value.startsWith("#/definitions/") ? `#/$defs/${value.slice("#/definitions/".length)}` : value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Get-or-create a child object map on `target[key]`. Used to lazily build up `$defs`/`dependentRequired`/`dependentSchemas` during conversion. */
|
|
43
|
+
function getObjectMap(target: JsonObject, key: string): JsonObject {
|
|
44
|
+
const existing = target[key];
|
|
45
|
+
if (isJsonObject(existing)) return existing;
|
|
46
|
+
const next: JsonObject = {};
|
|
47
|
+
target[key] = next;
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Recursively upgrade every entry of a schema-map (e.g. `properties`) and merge into `target[key]`. */
|
|
52
|
+
function mergeSchemaMap(target: JsonObject, key: string, value: JsonObject, cache: WeakMap<object, unknown>): void {
|
|
53
|
+
const map = getObjectMap(target, key);
|
|
54
|
+
for (const name in value) {
|
|
55
|
+
map[name] = upgradeJsonSchemaTo202012Impl(value[name], cache);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Copy a schema-map field with upgrade; non-object values are passed through verbatim. */
|
|
59
|
+
function copySchemaMap(target: JsonObject, key: string, value: unknown, cache: WeakMap<object, unknown>): void {
|
|
60
|
+
if (!isJsonObject(value)) {
|
|
61
|
+
target[key] = value;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
mergeSchemaMap(target, key, value, cache);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Intersect two schemas. Used when draft-07 `dependencies` map keys collide
|
|
69
|
+
* with each other or with existing `dependentSchemas` entries.
|
|
70
|
+
* - `true`/undefined is the identity (matches anything).
|
|
71
|
+
* - `false` is the absorbing element (matches nothing).
|
|
72
|
+
* - Equal schemas collapse. Otherwise wrap in `allOf` so both still apply.
|
|
73
|
+
*/
|
|
74
|
+
function combineSchemas(left: unknown, right: unknown): unknown {
|
|
75
|
+
if (left === undefined || left === true) return right;
|
|
76
|
+
if (right === undefined || right === true) return left;
|
|
77
|
+
if (left === false || right === false) return false;
|
|
78
|
+
if (areJsonValuesEqual(left, right)) return left;
|
|
79
|
+
return { allOf: [left, right] };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Union two arrays of JSON values, deduping by deep equality. Used to merge `dependentRequired` arrays. */
|
|
83
|
+
function mergeArrayValues(left: unknown[], right: unknown[]): unknown[] {
|
|
84
|
+
const merged = [...left];
|
|
85
|
+
for (const value of right) {
|
|
86
|
+
if (!merged.some(existing => areJsonValuesEqual(existing, value))) {
|
|
87
|
+
merged.push(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Merge converted tuple items into an existing `prefixItems` array. When the
|
|
95
|
+
* same index already has a schema (e.g. from a prior recursive pass via the
|
|
96
|
+
* cache), intersect the two so both constraints survive.
|
|
97
|
+
*/
|
|
98
|
+
function mergePrefixItems(existing: unknown, convertedItems: unknown[]): unknown[] {
|
|
99
|
+
if (!Array.isArray(existing)) return convertedItems;
|
|
100
|
+
const merged = [...existing];
|
|
101
|
+
for (let index = 0; index < convertedItems.length; index += 1) {
|
|
102
|
+
merged[index] = index in merged ? combineSchemas(merged[index], convertedItems[index]) : convertedItems[index];
|
|
103
|
+
}
|
|
104
|
+
return merged;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Record `key → deps` in `dependentRequired`, unioning with any existing array. */
|
|
108
|
+
function mergeDependentRequired(target: JsonObject, key: string, deps: unknown[]): void {
|
|
109
|
+
const dependentRequired = getObjectMap(target, "dependentRequired");
|
|
110
|
+
const existing = dependentRequired[key];
|
|
111
|
+
if (existing === undefined) {
|
|
112
|
+
dependentRequired[key] = deps;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(existing)) {
|
|
116
|
+
dependentRequired[key] = mergeArrayValues(existing, deps);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Record `key → schema` in `dependentSchemas`, intersecting with any existing entry. */
|
|
121
|
+
function mergeDependentSchema(target: JsonObject, key: string, schema: unknown): void {
|
|
122
|
+
const dependentSchemas = getObjectMap(target, "dependentSchemas");
|
|
123
|
+
dependentSchemas[key] = combineSchemas(dependentSchemas[key], schema);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Convert draft-07's `dependencies` keyword (which mixes array deps and schema
|
|
128
|
+
* deps under one key) into the draft 2020-12 split:
|
|
129
|
+
* - array value → `dependentRequired`
|
|
130
|
+
* - schema value → `dependentSchemas`
|
|
131
|
+
*/
|
|
132
|
+
function convertDependencies(source: JsonObject, target: JsonObject, cache: WeakMap<object, unknown>): void {
|
|
133
|
+
const dependencies = source.dependencies;
|
|
134
|
+
if (!isJsonObject(dependencies)) return;
|
|
135
|
+
for (const key in dependencies) {
|
|
136
|
+
const dependency = dependencies[key];
|
|
137
|
+
const converted = upgradeJsonSchemaTo202012Impl(dependency, cache);
|
|
138
|
+
if (Array.isArray(converted)) {
|
|
139
|
+
mergeDependentRequired(target, key, converted);
|
|
140
|
+
} else {
|
|
141
|
+
mergeDependentSchema(target, key, converted);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** True if `type` is `"null"` or an array that includes `"null"`. */
|
|
147
|
+
function hasNullType(type: unknown): boolean {
|
|
148
|
+
return type === "null" || (Array.isArray(type) && type.includes("null"));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** True if any variant in `anyOf` declares (only) a null type. Used to avoid double-adding `{type:"null"}`. */
|
|
152
|
+
function hasNullVariant(variants: unknown[]): boolean {
|
|
153
|
+
return variants.some(variant => isJsonObject(variant) && hasNullType(variant.type));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Mutate `schema` in place to accept `null`. Strategy depends on existing shape:
|
|
158
|
+
* - scalar type → expand to `[type, "null"]`.
|
|
159
|
+
* - type array → append `"null"` if missing.
|
|
160
|
+
* - existing `anyOf` → append `{type:"null"}` branch if missing.
|
|
161
|
+
* - otherwise → wrap the whole schema in `anyOf:[schema, {type:"null"}]`.
|
|
162
|
+
* Returns the resulting object (which may be a new wrapper).
|
|
163
|
+
*/
|
|
164
|
+
function makeNullable(schema: JsonObject): JsonObject {
|
|
165
|
+
const type = schema.type;
|
|
166
|
+
if (typeof type === "string") {
|
|
167
|
+
if (type !== "null") schema.type = [type, "null"];
|
|
168
|
+
return schema;
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(type)) {
|
|
171
|
+
if (!type.includes("null")) schema.type = [...type, "null"];
|
|
172
|
+
return schema;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(schema.anyOf)) {
|
|
175
|
+
if (!hasNullVariant(schema.anyOf)) schema.anyOf = [...schema.anyOf, { type: "null" }];
|
|
176
|
+
return schema;
|
|
177
|
+
}
|
|
178
|
+
return { anyOf: [schema, { type: "null" }] };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** True if any entry in a schema-map needs upgrading. Shortcut used during pre-check to skip the full clone when nothing has changed. */
|
|
182
|
+
function schemaMapNeedsDraft202012Upgrade(value: unknown, epoch: number): boolean {
|
|
183
|
+
if (!isJsonObject(value)) return false;
|
|
184
|
+
for (const k in value) {
|
|
185
|
+
if (schemaNeedsDraft202012UpgradeImpl(value[k], epoch)) return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Cheap pre-check: walk the schema looking for any keyword/value that the
|
|
192
|
+
* upgrade pass would have to rewrite. Lets the public entrypoint short-circuit
|
|
193
|
+
* and return the input identity-unchanged when there is nothing to do.
|
|
194
|
+
*
|
|
195
|
+
* Uses `once(value, epoch)` to break cycles without allocating a per-call set.
|
|
196
|
+
*/
|
|
197
|
+
function schemaNeedsDraft202012UpgradeImpl(value: unknown, epoch: number): boolean {
|
|
198
|
+
if (Array.isArray(value)) {
|
|
199
|
+
if (!once(value, epoch)) return false;
|
|
200
|
+
return value.some(entry => schemaNeedsDraft202012UpgradeImpl(entry, epoch));
|
|
201
|
+
}
|
|
202
|
+
if (!isJsonObject(value)) return false;
|
|
203
|
+
if (!once(value, epoch)) return false;
|
|
204
|
+
|
|
205
|
+
for (const key in value) {
|
|
206
|
+
const entry = value[key];
|
|
207
|
+
if (key === "$schema") {
|
|
208
|
+
if (typeof entry === "string" && entry in DRAFT_07_SCHEMA_URIS) return true;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (key === "definitions" || key === "dependencies" || key === "additionalItems" || key === "nullable") {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (key === "$ref") {
|
|
215
|
+
if (typeof entry === "string" && entry.startsWith("#/definitions/")) return true;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (key === "items" && Array.isArray(entry)) return true;
|
|
219
|
+
if (key === "$defs" || key in SCHEMA_MAP_KEYS) {
|
|
220
|
+
if (schemaMapNeedsDraft202012Upgrade(entry, epoch)) return true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (key in NON_SCHEMA_VALUE_KEYS) continue;
|
|
224
|
+
if (schemaNeedsDraft202012UpgradeImpl(entry, epoch)) return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Recursive upgrade core. The `cache` WeakMap keys input objects to their
|
|
232
|
+
* converted output so shared subgraphs are converted once and cycles terminate
|
|
233
|
+
* — we insert the empty result into the cache *before* recursing so back-edges
|
|
234
|
+
* resolve to a (later-populated) reference rather than infinite-looping.
|
|
235
|
+
*/
|
|
236
|
+
function upgradeJsonSchemaTo202012Impl(value: unknown, cache: WeakMap<object, unknown>): unknown {
|
|
237
|
+
if (Array.isArray(value)) {
|
|
238
|
+
const cached = cache.get(value);
|
|
239
|
+
if (cached !== undefined) return cached;
|
|
240
|
+
const result: unknown[] = [];
|
|
241
|
+
cache.set(value, result);
|
|
242
|
+
for (const entry of value) {
|
|
243
|
+
result.push(upgradeJsonSchemaTo202012Impl(entry, cache));
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
if (!isJsonObject(value)) return value;
|
|
248
|
+
|
|
249
|
+
const cached = cache.get(value);
|
|
250
|
+
if (cached !== undefined) return cached;
|
|
251
|
+
|
|
252
|
+
const result: JsonObject = {};
|
|
253
|
+
// Seed cache before recursion so back-edges in cyclic graphs resolve.
|
|
254
|
+
cache.set(value, result);
|
|
255
|
+
for (const key in value) {
|
|
256
|
+
const entry = value[key];
|
|
257
|
+
// `definitions` is the draft-07 name; merge under the canonical `$defs`.
|
|
258
|
+
// `$defs` may appear pre-upgraded — still walk entries to upgrade their bodies.
|
|
259
|
+
if (key === "definitions" || key === "$defs") {
|
|
260
|
+
if (isJsonObject(entry)) mergeSchemaMap(result, "$defs", entry, cache);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
// Recurse into each entry; the map shape itself is preserved.
|
|
264
|
+
if (key in SCHEMA_MAP_KEYS) {
|
|
265
|
+
copySchemaMap(result, key, entry, cache);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
// JSON-Schema *value* keywords — copy verbatim.
|
|
269
|
+
if (key in NON_SCHEMA_VALUE_KEYS) {
|
|
270
|
+
result[key] = entry;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
// Draft-07-only keywords with no draft 2020-12 spelling — drop entirely.
|
|
274
|
+
// `items` arrays are handled below via `prefixItems` conversion.
|
|
275
|
+
if (key === "dependencies" || key === "additionalItems" || key === "nullable") {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
// Rewrite `$schema` URI to the 2020-12 form; non-draft-07 URIs pass through.
|
|
279
|
+
if (key === "$schema") {
|
|
280
|
+
result.$schema =
|
|
281
|
+
typeof entry === "string" && entry in DRAFT_07_SCHEMA_URIS ? JSON_SCHEMA_DRAFT_2020_12_URI : entry;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
// `#/definitions/Foo` → `#/$defs/Foo`.
|
|
285
|
+
if (key === "$ref" && typeof entry === "string") {
|
|
286
|
+
result.$ref = convertRef(entry);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
// Array-valued `items` is the draft-07 tuple form — handled after the loop.
|
|
290
|
+
if (key === "items" && Array.isArray(entry)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
result[key] = upgradeJsonSchemaTo202012Impl(entry, cache);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Draft-07 tuple form: `items: [a, b]` (+ optional `additionalItems`) becomes
|
|
297
|
+
// draft 2020-12 `prefixItems: [a, b]` (+ optional `items` for the rest).
|
|
298
|
+
if (Array.isArray(value.items)) {
|
|
299
|
+
const convertedItems = upgradeJsonSchemaTo202012Impl(value.items, cache) as unknown[];
|
|
300
|
+
result.prefixItems = mergePrefixItems(result.prefixItems, convertedItems);
|
|
301
|
+
if (value.additionalItems !== undefined && value.additionalItems !== true) {
|
|
302
|
+
result.items = upgradeJsonSchemaTo202012Impl(value.additionalItems, cache);
|
|
303
|
+
} else {
|
|
304
|
+
// `additionalItems: true` (or absent) in draft-07 == no `items` in 2020-12.
|
|
305
|
+
delete result.items;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
convertDependencies(value, result, cache);
|
|
310
|
+
|
|
311
|
+
// OpenAPI 3.0 `nullable: true` → 2020-12 nullability. `makeNullable` may
|
|
312
|
+
// return a fresh wrapper object, in which case update the cache so callers
|
|
313
|
+
// referring to the same input see the wrapper instead of the inner result.
|
|
314
|
+
if (value.nullable === true) {
|
|
315
|
+
const nullable = makeNullable(result);
|
|
316
|
+
if (nullable !== result) cache.set(value, nullable);
|
|
317
|
+
return nullable;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Pre-check entrypoint. Exposed so callers can decide whether to take the upgrade path at all. */
|
|
324
|
+
export function schemaNeedsDraft202012Upgrade(schema: unknown): boolean {
|
|
325
|
+
return schemaNeedsDraft202012UpgradeImpl(schema, epochNext());
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Upgrade legacy JSON Schema shapes to the draft 2020-12 form emitted by Zod.
|
|
330
|
+
*
|
|
331
|
+
* This keeps extension/MCP/TypeBox schemas compatible with providers whose tool
|
|
332
|
+
* validators reject draft-07 tuple and dependency keywords.
|
|
333
|
+
*/
|
|
334
|
+
// `WeakMap` is intentional: this cache is per-call and seeded *before* recursion
|
|
335
|
+
// (see `cache.set(value, result)` in `upgradeJsonSchemaTo202012Impl`) so cyclic
|
|
336
|
+
// graphs resolve to a knot-tied reference. A symbol stamp would either cache
|
|
337
|
+
// across calls (unsafe under input mutation) or require an epoch indirection.
|
|
338
|
+
export function upgradeJsonSchemaTo202012(schema: unknown): unknown {
|
|
339
|
+
if (!schemaNeedsDraft202012Upgrade(schema)) return schema;
|
|
340
|
+
return upgradeJsonSchemaTo202012Impl(schema, new WeakMap<object, unknown>());
|
|
341
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { JsonObject } from "./types";
|
|
2
|
+
import { isJsonObject } from "./types";
|
|
3
|
+
|
|
4
|
+
export function areJsonValuesEqual(left: unknown, right: unknown): boolean {
|
|
5
|
+
if (Object.is(left, right)) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
9
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
13
|
+
if (!areJsonValuesEqual(left[i], right[i])) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
let rightLen = 0;
|
|
23
|
+
for (const _ in right) rightLen++;
|
|
24
|
+
let leftLen = 0;
|
|
25
|
+
for (const key in left) {
|
|
26
|
+
leftLen++;
|
|
27
|
+
if (!(key in right) || !areJsonValuesEqual(left[key], right[key])) return false;
|
|
28
|
+
}
|
|
29
|
+
return leftLen === rightLen;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function mergeCompatibleEnumSchemas(existing: unknown, incoming: unknown): JsonObject | null {
|
|
33
|
+
if (!isJsonObject(existing) || !isJsonObject(incoming)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const existingEnum = Array.isArray(existing.enum) ? existing.enum : null;
|
|
37
|
+
const incomingEnum = Array.isArray(incoming.enum) ? incoming.enum : null;
|
|
38
|
+
if (!existingEnum || !incomingEnum) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (!areJsonValuesEqual(existing.type, incoming.type)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
let existingNonEnumCount = 0;
|
|
45
|
+
for (const key in existing) {
|
|
46
|
+
if (key !== "enum") existingNonEnumCount++;
|
|
47
|
+
}
|
|
48
|
+
let incomingNonEnumCount = 0;
|
|
49
|
+
for (const key in incoming) {
|
|
50
|
+
if (key !== "enum") incomingNonEnumCount++;
|
|
51
|
+
}
|
|
52
|
+
if (existingNonEnumCount !== incomingNonEnumCount) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
for (const key in existing) {
|
|
56
|
+
if (key === "enum") continue;
|
|
57
|
+
if (!(key in incoming) || !areJsonValuesEqual(existing[key], incoming[key])) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mergedEnum = [...existingEnum];
|
|
63
|
+
for (const enumValue of incomingEnum) {
|
|
64
|
+
if (!mergedEnum.some(existingValue => areJsonValuesEqual(existingValue, enumValue))) {
|
|
65
|
+
mergedEnum.push(enumValue);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...existing,
|
|
70
|
+
enum: mergedEnum,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getAnyOfVariants(schema: unknown): unknown[] {
|
|
75
|
+
if (isJsonObject(schema) && Array.isArray(schema.anyOf)) {
|
|
76
|
+
return schema.anyOf;
|
|
77
|
+
}
|
|
78
|
+
return [schema];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function mergePropertySchemas(existing: unknown, incoming: unknown): unknown {
|
|
82
|
+
if (areJsonValuesEqual(existing, incoming)) {
|
|
83
|
+
return existing;
|
|
84
|
+
}
|
|
85
|
+
const mergedEnumSchema = mergeCompatibleEnumSchemas(existing, incoming);
|
|
86
|
+
if (mergedEnumSchema !== null) {
|
|
87
|
+
return mergedEnumSchema;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const mergedAnyOf = [...getAnyOfVariants(existing)];
|
|
91
|
+
for (const variant of getAnyOfVariants(incoming)) {
|
|
92
|
+
if (!mergedAnyOf.some(existingVariant => areJsonValuesEqual(existingVariant, variant))) {
|
|
93
|
+
mergedAnyOf.push(variant);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return mergedAnyOf.length === 1 ? mergedAnyOf[0] : { anyOf: mergedAnyOf };
|
|
97
|
+
}
|