@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,164 @@
|
|
|
1
|
+
# Schema Constraints
|
|
2
|
+
|
|
3
|
+
This document is the operational contract for schema normalization/strictness in `packages/ai/src/utils/schema`.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- Applies to provider-facing tool schemas produced by:
|
|
8
|
+
- `normalize.ts` — Google, CCA, MCP, OpenAI Responses, and OpenAI strict-mode (sanitize + enforce) sanitization. All schema walkers live here.
|
|
9
|
+
- `adapt.ts` — thin composer wrapping `tryEnforceStrictSchema` for provider call sites, plus the `PI_NO_STRICT` env flag callers consult to opt out of strict mode.
|
|
10
|
+
- `fields.ts` — keyword classification sets used by the walkers.
|
|
11
|
+
- Covers OpenAI-style strict mode, OpenAI Responses `oneOf` rejection, Google schema constraints, and Cloud Code Assist Anthropic constraints.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1) OpenAI-style strict mode (`adaptSchemaForStrict` / `tryEnforceStrictSchema`)
|
|
15
|
+
|
|
16
|
+
When strict mode is requested (`strict=true` at call site), the schema MUST satisfy all of the following after adaptation:
|
|
17
|
+
|
|
18
|
+
1. **Non-structural keywords are removed before strict enforcement**
|
|
19
|
+
- Sanitization uses `sanitizeSchemaForStrictMode`.
|
|
20
|
+
- Removed keys include formatting/validation/decorative keywords and unsupported structural extras:
|
|
21
|
+
- `format`, `pattern`, `minLength`, `maxLength`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
|
|
22
|
+
- `minItems`, `maxItems`, `uniqueItems`, `multipleOf`
|
|
23
|
+
- `$schema`, `examples`, `default`, `title`, `$comment`
|
|
24
|
+
- `if`, `then`, `else`, `not`
|
|
25
|
+
- `unevaluatedProperties`, `unevaluatedItems`, `patternProperties`
|
|
26
|
+
- `propertyNames`, `contains`, `minContains`, `maxContains`
|
|
27
|
+
- `dependentRequired`, `dependentSchemas`
|
|
28
|
+
- `contentEncoding`, `contentMediaType`, `contentSchema`
|
|
29
|
+
- `deprecated`, `readOnly`, `writeOnly`
|
|
30
|
+
- `minProperties`, `maxProperties`
|
|
31
|
+
- `$dynamicRef`, `$dynamicAnchor`
|
|
32
|
+
- Before stripping `default`, its value is inlined into the sibling `description` as ` (default: X)` so that strict-mode providers retain the default hint in free-form text. Inlining is skipped when `description` already contains `(default:` or when no sibling `description` is present.
|
|
33
|
+
|
|
34
|
+
2. **`const` is normalized to `enum`**
|
|
35
|
+
- If a node contains `const`, strict sanitization converts it to `enum: [const]`.
|
|
36
|
+
|
|
37
|
+
3. **Object and tuple strictness is enforced recursively**
|
|
38
|
+
- Every object node gets `additionalProperties: false`.
|
|
39
|
+
- Every property key is included in `required`.
|
|
40
|
+
- Optional properties are wrapped as nullable unions:
|
|
41
|
+
- `anyOf: [<original schema>, { "type": "null" }]`.
|
|
42
|
+
- Tuple entries in `prefixItems` are strictified recursively.
|
|
43
|
+
|
|
44
|
+
4. **Schema nodes must be representable in strict mode**
|
|
45
|
+
- Nodes without `type`, combinator, `$ref`, or `not` are invalid in strict enforcement and MUST throw.
|
|
46
|
+
- Example invalid node: `{}` or `{ items: {} }`.
|
|
47
|
+
|
|
48
|
+
5. **Failure mode is fail-open to non-strict**
|
|
49
|
+
- `tryEnforceStrictSchema` MUST return `{ strict: false, schema: original }` when strict enforcement throws.
|
|
50
|
+
- It MUST NOT emit partially-broken strict schema.
|
|
51
|
+
|
|
52
|
+
6. **Provider payload strict flag must match effective strictness**
|
|
53
|
+
- Callers MUST send `strict: true` only if enforcement succeeded (`effectiveStrict === true`).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 2) Google Gemini / Vertex / Gemini CLI (`normalizeSchemaForGoogle`)
|
|
58
|
+
|
|
59
|
+
Schemas sent on the Google JSON Schema path MUST follow:
|
|
60
|
+
|
|
61
|
+
1. **Unsupported JSON Schema keywords are stripped (except property names under `properties`)**
|
|
62
|
+
- Unsupported keys (`UNSUPPORTED_SCHEMA_FIELDS`):
|
|
63
|
+
- `$schema`, `$ref`, `$defs`, `$dynamicRef`, `$dynamicAnchor`
|
|
64
|
+
- `examples`, `prefixItems`, `unevaluatedProperties`, `unevaluatedItems`
|
|
65
|
+
- `patternProperties`, `additionalProperties`
|
|
66
|
+
- `minItems`, `maxItems`, `minLength`, `maxLength`
|
|
67
|
+
- `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
|
|
68
|
+
- `pattern`, `format`
|
|
69
|
+
- Important: keys inside a `properties` object are treated as property names and MUST NOT be stripped by keyword match.
|
|
70
|
+
- Human-meaningful stripped keys (`pattern`, `format`, min/max constraints, `default`, `examples`, etc.) are appended to the sibling `description` as an Anthropic-style spill block: `{pattern: "^foo$", minimum: 0}`. Structural/meta keys such as `$ref`, `$defs`, and `additionalProperties` are not spilled.
|
|
71
|
+
|
|
72
|
+
2. **`type` arrays are normalized to scalar type + nullable marker**
|
|
73
|
+
- `type: ["T", "null"]` becomes `type: "T"` and `nullable: true`.
|
|
74
|
+
- Google expects scalar type, not `type[]`.
|
|
75
|
+
|
|
76
|
+
3. **`const` is converted to `enum`**
|
|
77
|
+
- If `const` exists, schema uses/merges `enum` with the const value.
|
|
78
|
+
|
|
79
|
+
4. **Object schemas get an explicit properties map**
|
|
80
|
+
- `{ "type": "object" }` becomes `{ "type": "object", "properties": {} }`.
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3) Anthropic model via Cloud Code Assist (`normalizeSchemaForCCA`)
|
|
84
|
+
|
|
85
|
+
For Cloud Code Assist Anthropic tool declarations, schema MUST satisfy stricter constraints than generic Google path.
|
|
86
|
+
|
|
87
|
+
### 3.1 Transport contract
|
|
88
|
+
|
|
89
|
+
1. **Use legacy `parameters` field** (not `parametersJsonSchema`) for CCA Anthropic model.
|
|
90
|
+
2. CCA path uses the full `normalizeSchemaForCCA` pipeline.
|
|
91
|
+
|
|
92
|
+
### 3.2 Sanitization contract
|
|
93
|
+
|
|
94
|
+
1. Start with Google unsupported-key stripping behavior.
|
|
95
|
+
2. **`nullable` keyword MUST be stripped** in CCA Anthropic model path.
|
|
96
|
+
3. `type: ["T", "null"]` becomes `type: "T"` with no `nullable` marker.
|
|
97
|
+
4. Human-meaningful stripped keys are appended to `description` with the same spill format used by the Google dispatcher.
|
|
98
|
+
|
|
99
|
+
### 3.3 Combiner/union normalization contract
|
|
100
|
+
|
|
101
|
+
1. Object-only `anyOf`/`oneOf` variants SHOULD be merged into a single object shape where safe.
|
|
102
|
+
2. Same-type combiner variants SHOULD be collapsed to one schema.
|
|
103
|
+
3. Mixed-type combiner variants MAY be lossy-collapsed to first non-null scalar type when required for CCA acceptance.
|
|
104
|
+
4. Residual combiners are recursively stripped where collapsible (`stripResidualCombiners`).
|
|
105
|
+
|
|
106
|
+
### 3.4 Nullable property normalization contract
|
|
107
|
+
|
|
108
|
+
1. Property-local nullability expressed as:
|
|
109
|
+
- `nullable: true`, or
|
|
110
|
+
- `type` union including `null`, or
|
|
111
|
+
- `anyOf`/`oneOf` with one `{ "type": "null" }` branch
|
|
112
|
+
MUST be converted to non-required property semantics where possible.
|
|
113
|
+
2. If a property is detected nullable after normalization, it MUST be removed from `required`.
|
|
114
|
+
|
|
115
|
+
### 3.5 Residual incompatibility gate (hard stop)
|
|
116
|
+
|
|
117
|
+
After normalization, schema MUST NOT contain any of:
|
|
118
|
+
|
|
119
|
+
- `type` as array
|
|
120
|
+
- `type: "null"`
|
|
121
|
+
- `nullable` key
|
|
122
|
+
- `anyOf`, `oneOf`, `allOf` arrays
|
|
123
|
+
|
|
124
|
+
If any remain, schema is incompatible.
|
|
125
|
+
|
|
126
|
+
### 3.6 Validation + fallback contract
|
|
127
|
+
|
|
128
|
+
1. Normalized schema is validated with AJV 2020 schema validation.
|
|
129
|
+
2. If invalid OR residual incompatibilities exist, output MUST fallback to:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{ "type": "object", "properties": {} }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
3. Fallback is per-tool and fail-open; one bad tool schema MUST NOT fail the whole request.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4) Practical provider mapping
|
|
140
|
+
|
|
141
|
+
- **OpenAI-compatible strict paths** (`openai-completions`, `openai-responses`, `openai-code-responses`):
|
|
142
|
+
- Use `adaptSchemaForStrict`.
|
|
143
|
+
- Emit `strict: true` only when effective strict enforcement succeeded.
|
|
144
|
+
|
|
145
|
+
- **Google Gemini/Vertex/Gemini CLI (non-CCA Anthropic model)**:
|
|
146
|
+
- Use `normalizeSchemaForGoogle` and send schema on `parametersJsonSchema` path.
|
|
147
|
+
|
|
148
|
+
- **Cloud Code Assist Anthropic models (`model.id` starts with `anthropic-model-`)**:
|
|
149
|
+
- Use `normalizeSchemaForCCA` and send sanitized normalized schema in `parameters`.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 5) Maintenance rules
|
|
154
|
+
|
|
155
|
+
When adding/changing provider adapters:
|
|
156
|
+
|
|
157
|
+
1. Any new unsupported keyword MUST be added to the appropriate set in `fields.ts`.
|
|
158
|
+
2. Any new normalization rule MUST include regression tests under `packages/ai/test`.
|
|
159
|
+
3. Never bypass adapter helpers (`adaptSchemaForStrict`, `normalizeSchemaForGoogle`, `normalizeSchemaForCCA`, `normalizeSchemaForMCP`) in provider code.
|
|
160
|
+
4. If a provider rejects schema with partial support, prefer deterministic per-tool fallback over request-wide failure.
|
|
161
|
+
|
|
162
|
+
## 6) Gemini CLI / Antigravity CCA parity
|
|
163
|
+
|
|
164
|
+
The Gemini CLI / Antigravity Anthropic model path MUST run the same full `normalizeSchemaForCCA` pipeline as the shared Google Anthropic path. It MUST NOT call only the first keyword-stripping pass, because that leaves object combiners, nullable unions, residual combiners, and fallback gating inconsistent between transports.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { $flag } from "@gajae-code/utils";
|
|
2
|
+
import { upgradeJsonSchemaTo202012 } from "./draft";
|
|
3
|
+
import { tryEnforceStrictSchema } from "./normalize";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set when callers want to globally bypass OpenAI strict-mode enforcement
|
|
7
|
+
* (e.g. for debugging a provider that misreports strict support, or when
|
|
8
|
+
* comparing strict vs non-strict outputs).
|
|
9
|
+
*
|
|
10
|
+
* Honored by every provider that emits `strict: true` on its function tools —
|
|
11
|
+
* see `openai-completions`, `openai-responses`, `OpenAI code provider-responses`, and
|
|
12
|
+
* the strict candidate selection in `anthropic`.
|
|
13
|
+
*/
|
|
14
|
+
export const NO_STRICT = $flag("PI_NO_STRICT");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Consolidated helper for OpenAI-style strict schema enforcement.
|
|
18
|
+
*
|
|
19
|
+
* Each provider computes its own `strict` boolean (logic differs), then calls
|
|
20
|
+
* this to handle the tryEnforceStrictSchema dance uniformly:
|
|
21
|
+
* - Draft-07-shaped inputs are upgraded to draft 2020-12 first.
|
|
22
|
+
* - If `strict` is false, passes the upgraded schema through unchanged.
|
|
23
|
+
* - If `strict` is true, attempts to enforce strict mode; falls back to
|
|
24
|
+
* non-strict if the schema isn't representable.
|
|
25
|
+
*/
|
|
26
|
+
export function adaptSchemaForStrict(
|
|
27
|
+
schema: Record<string, unknown>,
|
|
28
|
+
strict: boolean,
|
|
29
|
+
): { schema: Record<string, unknown>; strict: boolean } {
|
|
30
|
+
const upgraded = upgradeJsonSchemaTo202012(schema) as Record<string, unknown>;
|
|
31
|
+
if (!strict) {
|
|
32
|
+
return { schema: upgraded, strict: false };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return tryEnforceStrictSchema(upgraded);
|
|
36
|
+
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CCA_UNSUPPORTED_SCHEMA_FIELDS,
|
|
3
|
+
COMBINATOR_KEYS,
|
|
4
|
+
NON_STRUCTURAL_SCHEMA_KEYS,
|
|
5
|
+
UNSUPPORTED_SCHEMA_FIELDS,
|
|
6
|
+
} from "./fields";
|
|
7
|
+
import { isValidJsonSchema } from "./meta-validator";
|
|
8
|
+
import { isJsonObject, type JsonObject } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Schema compatibility audits.
|
|
12
|
+
*
|
|
13
|
+
* Each provider has a different idea of what JSON Schema features it accepts
|
|
14
|
+
* for tool definitions. The normalizers in `normalize.ts`, `strict-mode`,
|
|
15
|
+
* and `adapt.ts` rewrite incoming schemas to fit. This module is the
|
|
16
|
+
* *audit* counterpart: it walks a (presumably already-sanitized) schema and
|
|
17
|
+
* reports any feature the target provider would reject. Tests use it to lock
|
|
18
|
+
* down the contract; the runtime uses it to fail-open with diagnostic logs
|
|
19
|
+
* rather than silently shipping a broken tool definition.
|
|
20
|
+
*/
|
|
21
|
+
export type SchemaCompatibilityProvider = "openai-strict" | "google" | "cloud-code-assist-claude";
|
|
22
|
+
|
|
23
|
+
export interface SchemaCompatibilityViolation {
|
|
24
|
+
path: string;
|
|
25
|
+
rule: string;
|
|
26
|
+
message: string;
|
|
27
|
+
key?: string;
|
|
28
|
+
value?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SchemaCompatibilityResult {
|
|
32
|
+
provider: SchemaCompatibilityProvider;
|
|
33
|
+
compatible: boolean;
|
|
34
|
+
violations: SchemaCompatibilityViolation[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface StrictSchemaEnforcementResult {
|
|
38
|
+
schema: Record<string, unknown>;
|
|
39
|
+
strict: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Per-provider forbidden-key sets. Subsets of the shared `fields.ts` constants
|
|
43
|
+
// plus a few provider-specific extras (`const`, `nullable`) folded in here so
|
|
44
|
+
// each rule is defined in exactly one place.
|
|
45
|
+
const STRICT_FORBIDDEN_KEYS: Record<string, true> = { ...NON_STRUCTURAL_SCHEMA_KEYS, const: true, nullable: true };
|
|
46
|
+
const GOOGLE_FORBIDDEN_KEYS: Record<string, true> = { ...UNSUPPORTED_SCHEMA_FIELDS, const: true };
|
|
47
|
+
const CCA_FORBIDDEN_KEYS: Record<string, true> = { ...CCA_UNSUPPORTED_SCHEMA_FIELDS, const: true };
|
|
48
|
+
|
|
49
|
+
// Keys whose values are JSON-Schema *containers* (arrays of values, scalars,
|
|
50
|
+
// etc.) rather than nested schemas. The traversal must skip these — recursing
|
|
51
|
+
// would walk into `enum` strings or `default` objects and emit spurious
|
|
52
|
+
// violations against keys that happen to share JSON-Schema keyword names.
|
|
53
|
+
const NON_SCHEMA_CONTAINER_ARRAY_KEYS: Record<string, true> = {
|
|
54
|
+
enum: true,
|
|
55
|
+
required: true,
|
|
56
|
+
examples: true,
|
|
57
|
+
type: true,
|
|
58
|
+
};
|
|
59
|
+
const NON_SCHEMA_CONTAINER_OBJECT_KEYS: Record<string, true> = { const: true, default: true, example: true };
|
|
60
|
+
|
|
61
|
+
interface TraversalState {
|
|
62
|
+
path: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createViolation(
|
|
66
|
+
path: string,
|
|
67
|
+
rule: string,
|
|
68
|
+
message: string,
|
|
69
|
+
key?: string,
|
|
70
|
+
value?: unknown,
|
|
71
|
+
): SchemaCompatibilityViolation {
|
|
72
|
+
return {
|
|
73
|
+
path,
|
|
74
|
+
rule,
|
|
75
|
+
message,
|
|
76
|
+
...(key === undefined ? {} : { key }),
|
|
77
|
+
...(value === undefined ? {} : { value }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Recursively visit every schema node in a JSON Schema tree.
|
|
83
|
+
*
|
|
84
|
+
* The walker is *structural*, not type-aware: it knows which keywords contain
|
|
85
|
+
* nested schemas vs. plain values, so it descends into `properties.*`,
|
|
86
|
+
* `$defs.*`, `items`, combinator arrays, etc. but never into `enum`, `const`,
|
|
87
|
+
* `default`, or `type` arrays.
|
|
88
|
+
*/
|
|
89
|
+
function walkSchema(
|
|
90
|
+
value: unknown,
|
|
91
|
+
state: TraversalState,
|
|
92
|
+
visitNode: (node: JsonObject, state: TraversalState) => void,
|
|
93
|
+
): void {
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
for (let index = 0; index < value.length; index++) {
|
|
96
|
+
walkSchema(value[index], { path: `${state.path}[${index}]` }, visitNode);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!isJsonObject(value)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
visitNode(value, state);
|
|
106
|
+
|
|
107
|
+
for (const key in value) {
|
|
108
|
+
// Schema-map keywords: value is `{ name: schema, … }`. Recurse into each
|
|
109
|
+
// entry's schema rather than the map object itself.
|
|
110
|
+
const entry = value[key];
|
|
111
|
+
if (key === "properties" || key === "$defs" || key === "definitions" || key === "dependentSchemas") {
|
|
112
|
+
if (isJsonObject(entry)) {
|
|
113
|
+
for (const name in entry) {
|
|
114
|
+
const child = entry[name];
|
|
115
|
+
walkSchema(child, { path: `${state.path}.${key}.${name}` }, visitNode);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Non-schema container keywords — values are not schemas, do not descend.
|
|
121
|
+
|
|
122
|
+
if (key in NON_SCHEMA_CONTAINER_ARRAY_KEYS || key in NON_SCHEMA_CONTAINER_OBJECT_KEYS) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Array-of-schemas keywords (e.g. `allOf`, `anyOf`, `oneOf`, `prefixItems`).
|
|
126
|
+
|
|
127
|
+
if (Array.isArray(entry)) {
|
|
128
|
+
for (let index = 0; index < entry.length; index++) {
|
|
129
|
+
walkSchema(entry[index], { path: `${state.path}.${key}[${index}]` }, visitNode);
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isJsonObject(entry)) {
|
|
135
|
+
walkSchema(entry, { path: `${state.path}.${key}` }, visitNode);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Strict-mode audit (OpenAI Responses / OpenAI code backend `strict: true`):
|
|
142
|
+
* 1. Forbid keywords that strict mode disallows (`format`, `pattern`, `const`,
|
|
143
|
+
* `nullable`, etc. — see `STRICT_FORBIDDEN_KEYS`).
|
|
144
|
+
* 2. Every node must declare *something* concrete: a `type`, a combinator,
|
|
145
|
+
* a `$ref`, or a `not` branch. Empty `{}` is rejected.
|
|
146
|
+
* 3. Object nodes must set `additionalProperties: false`, declare a real
|
|
147
|
+
* `properties` map, and require every property in that map. Required
|
|
148
|
+
* properties not in `properties` are also rejected — strict mode demands
|
|
149
|
+
* a closed object shape.
|
|
150
|
+
*/
|
|
151
|
+
function validateStrictNode(node: JsonObject, state: TraversalState): SchemaCompatibilityViolation[] {
|
|
152
|
+
const violations: SchemaCompatibilityViolation[] = [];
|
|
153
|
+
|
|
154
|
+
for (const key in node) {
|
|
155
|
+
const value = node[key];
|
|
156
|
+
if (!(key in STRICT_FORBIDDEN_KEYS)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
violations.push(
|
|
161
|
+
createViolation(
|
|
162
|
+
`${state.path}.${key}`,
|
|
163
|
+
"strict-forbidden-key",
|
|
164
|
+
`Strict schema contains forbidden key "${key}"`,
|
|
165
|
+
key,
|
|
166
|
+
value,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
// Rule 2: node must declare at least one concrete shape descriptor.
|
|
171
|
+
|
|
172
|
+
const hasCombinator = COMBINATOR_KEYS.some(key => Array.isArray(node[key]));
|
|
173
|
+
const hasRef = typeof node.$ref === "string";
|
|
174
|
+
const hasNot = isJsonObject(node.not);
|
|
175
|
+
if (node.type === undefined && !hasCombinator && !hasRef && !hasNot) {
|
|
176
|
+
violations.push(
|
|
177
|
+
createViolation(
|
|
178
|
+
state.path,
|
|
179
|
+
"strict-unrepresentable-node",
|
|
180
|
+
"Strict schema node must declare type, combinator, $ref, or not",
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
// Rules 3a-3d apply only to object-shaped nodes.
|
|
185
|
+
|
|
186
|
+
const isObjectNode = node.type === "object" || isJsonObject(node.properties);
|
|
187
|
+
if (!isObjectNode) {
|
|
188
|
+
return violations;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (node.additionalProperties !== false) {
|
|
192
|
+
violations.push(
|
|
193
|
+
createViolation(
|
|
194
|
+
`${state.path}.additionalProperties`,
|
|
195
|
+
"strict-object-additional-properties",
|
|
196
|
+
"Strict object schema must set additionalProperties to false",
|
|
197
|
+
"additionalProperties",
|
|
198
|
+
node.additionalProperties,
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
// 3b: `properties` must exist and be an object — without it strict mode has nothing to validate.
|
|
203
|
+
|
|
204
|
+
if (!isJsonObject(node.properties)) {
|
|
205
|
+
violations.push(
|
|
206
|
+
createViolation(
|
|
207
|
+
`${state.path}.properties`,
|
|
208
|
+
"strict-object-properties",
|
|
209
|
+
"Strict object schema must provide an object-valued properties map",
|
|
210
|
+
"properties",
|
|
211
|
+
node.properties,
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
return violations;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const propertyNames = Object.keys(node.properties);
|
|
218
|
+
const requiredValues = Array.isArray(node.required)
|
|
219
|
+
? node.required.filter((entry): entry is string => typeof entry === "string")
|
|
220
|
+
: [];
|
|
221
|
+
const requiredSet = new Set(requiredValues);
|
|
222
|
+
|
|
223
|
+
for (const propertyName of propertyNames) {
|
|
224
|
+
if (requiredSet.has(propertyName)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
violations.push(
|
|
228
|
+
createViolation(
|
|
229
|
+
`${state.path}.required`,
|
|
230
|
+
"strict-object-required",
|
|
231
|
+
`Strict object schema must require property "${propertyName}"`,
|
|
232
|
+
"required",
|
|
233
|
+
node.required,
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
// 3d: any property declared in `required` but missing from `properties` is unrepresentable.
|
|
238
|
+
|
|
239
|
+
const propertyNameSet = new Set(propertyNames);
|
|
240
|
+
for (const requiredKey of requiredValues) {
|
|
241
|
+
if (propertyNameSet.has(requiredKey)) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
violations.push(
|
|
245
|
+
createViolation(
|
|
246
|
+
`${state.path}.required`,
|
|
247
|
+
"strict-object-required-extra",
|
|
248
|
+
`Strict object schema requires non-existent property "${requiredKey}"`,
|
|
249
|
+
"required",
|
|
250
|
+
node.required,
|
|
251
|
+
),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return violations;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function validateGoogleNode(node: JsonObject, state: TraversalState): SchemaCompatibilityViolation[] {
|
|
259
|
+
const violations: SchemaCompatibilityViolation[] = [];
|
|
260
|
+
|
|
261
|
+
for (const key in node) {
|
|
262
|
+
const value = node[key];
|
|
263
|
+
if (!(key in GOOGLE_FORBIDDEN_KEYS)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
violations.push(
|
|
267
|
+
createViolation(
|
|
268
|
+
`${state.path}.${key}`,
|
|
269
|
+
"google-forbidden-key",
|
|
270
|
+
`Google schema contains unsupported key "${key}"`,
|
|
271
|
+
key,
|
|
272
|
+
value,
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (Array.isArray(node.type)) {
|
|
278
|
+
violations.push(
|
|
279
|
+
createViolation(
|
|
280
|
+
`${state.path}.type`,
|
|
281
|
+
"google-type-array",
|
|
282
|
+
"Google schema type must be a scalar string, not an array",
|
|
283
|
+
"type",
|
|
284
|
+
node.type,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return violations;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function validateCloudCodeAssistNode(node: JsonObject, state: TraversalState): SchemaCompatibilityViolation[] {
|
|
293
|
+
const violations: SchemaCompatibilityViolation[] = [];
|
|
294
|
+
|
|
295
|
+
for (const key in node) {
|
|
296
|
+
const value = node[key];
|
|
297
|
+
if (key in CCA_FORBIDDEN_KEYS) {
|
|
298
|
+
violations.push(
|
|
299
|
+
createViolation(
|
|
300
|
+
`${state.path}.${key}`,
|
|
301
|
+
"cca-forbidden-key",
|
|
302
|
+
`Cloud Code Assist schema contains unsupported key "${key}"`,
|
|
303
|
+
key,
|
|
304
|
+
value,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (Array.isArray(node.type)) {
|
|
311
|
+
violations.push(
|
|
312
|
+
createViolation(
|
|
313
|
+
`${state.path}.type`,
|
|
314
|
+
"cca-type-array",
|
|
315
|
+
"Cloud Code Assist schema forbids array-valued type",
|
|
316
|
+
"type",
|
|
317
|
+
node.type,
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (node.type === "null") {
|
|
323
|
+
violations.push(
|
|
324
|
+
createViolation(
|
|
325
|
+
`${state.path}.type`,
|
|
326
|
+
"cca-null-type",
|
|
327
|
+
'Cloud Code Assist schema forbids type: "null"',
|
|
328
|
+
"type",
|
|
329
|
+
node.type,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (Object.hasOwn(node, "nullable")) {
|
|
335
|
+
violations.push(
|
|
336
|
+
createViolation(
|
|
337
|
+
`${state.path}.nullable`,
|
|
338
|
+
"cca-nullable-key",
|
|
339
|
+
"Cloud Code Assist schema forbids nullable keyword",
|
|
340
|
+
"nullable",
|
|
341
|
+
node.nullable,
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (const key of COMBINATOR_KEYS) {
|
|
347
|
+
if (Array.isArray(node[key])) {
|
|
348
|
+
violations.push(
|
|
349
|
+
createViolation(
|
|
350
|
+
`${state.path}.${key}`,
|
|
351
|
+
"cca-combiner",
|
|
352
|
+
`Cloud Code Assist schema forbids ${key}`,
|
|
353
|
+
key,
|
|
354
|
+
node[key],
|
|
355
|
+
),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return violations;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function validateCloudCodeAssistSchema(schema: unknown): SchemaCompatibilityViolation[] {
|
|
364
|
+
if (isValidJsonSchema(schema)) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
return [
|
|
368
|
+
createViolation(
|
|
369
|
+
"root",
|
|
370
|
+
"cca-meta-schema-validation",
|
|
371
|
+
"Cloud Code Assist schema is not a structurally valid JSON Schema",
|
|
372
|
+
),
|
|
373
|
+
];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function validateSchemaCompatibility(
|
|
377
|
+
schema: unknown,
|
|
378
|
+
provider: SchemaCompatibilityProvider,
|
|
379
|
+
): SchemaCompatibilityResult {
|
|
380
|
+
const violations: SchemaCompatibilityViolation[] = [];
|
|
381
|
+
|
|
382
|
+
switch (provider) {
|
|
383
|
+
case "openai-strict": {
|
|
384
|
+
walkSchema(schema, { path: "root" }, (node, state) => {
|
|
385
|
+
violations.push(...validateStrictNode(node, state));
|
|
386
|
+
});
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case "google": {
|
|
390
|
+
walkSchema(schema, { path: "root" }, (node, state) => {
|
|
391
|
+
violations.push(...validateGoogleNode(node, state));
|
|
392
|
+
});
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
case "cloud-code-assist-claude": {
|
|
396
|
+
walkSchema(schema, { path: "root" }, (node, state) => {
|
|
397
|
+
violations.push(...validateCloudCodeAssistNode(node, state));
|
|
398
|
+
});
|
|
399
|
+
violations.push(...validateCloudCodeAssistSchema(schema));
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
provider,
|
|
406
|
+
compatible: violations.length === 0,
|
|
407
|
+
violations,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function validateStrictSchemaEnforcement(
|
|
412
|
+
originalSchema: Record<string, unknown>,
|
|
413
|
+
result: StrictSchemaEnforcementResult,
|
|
414
|
+
): SchemaCompatibilityResult {
|
|
415
|
+
if (result.strict) {
|
|
416
|
+
return validateSchemaCompatibility(result.schema, "openai-strict");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const violations: SchemaCompatibilityViolation[] = [];
|
|
420
|
+
if (result.schema !== originalSchema) {
|
|
421
|
+
violations.push(
|
|
422
|
+
createViolation(
|
|
423
|
+
"root",
|
|
424
|
+
"strict-fail-open-original-schema",
|
|
425
|
+
"Strict fail-open must return the original schema object when strict=false",
|
|
426
|
+
),
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
provider: "openai-strict",
|
|
432
|
+
compatible: violations.length === 0,
|
|
433
|
+
violations,
|
|
434
|
+
};
|
|
435
|
+
}
|