Commit Graph
10 Commits
Author SHA1 Message Date
roboomp 09ea361331 fix(schema): normalize empty JSON schema {} to true for grammar-constrained models
z.unknown() emits {} (empty JSON Schema) as the value for additionalProperties,
items, and other schema-valued positions. This is semantically equivalent to
boolean true per JSON Schema draft 2020-12 §4.3.1, but grammar-constrained
samplers (llama.cpp, etc.) interpret the object form as "generate an empty
object" rather than "any JSON value". Models using such samplers therefore
emit {} for every open-typed field, including extra.title in plan-mode resolve
calls (issue #1179). This affected all tools with open schemas, not just the
resolve tool -- MCP tools and any tool using z.unknown() in value positions
have the same problem.

Fix: normalize any {} in schema-valued positions to true in two places:
- zodToWireSchema postProcess/walk: covers Zod-based tools across all providers
- normalizeOpenAIResponsesSchemaNode: covers all tools (Zod, TypeBox, MCP)
  going to openai-responses-compatible endpoints (llama.cpp, Azure Responses)

Adds isJsonObjectEmpty() helper to schema/types.ts (for...in based, no allocation).

Fixes #1179
2026-05-19 04:39:27 +00:00
Can Bölük 4024fbe7e2 fix(ai): decontaminate leaked Zod schema instances to valid JSON Schema
Rewrites JSON-roundtripped Zod 4 schema objects that leak Zod internals as
JSON Schema keywords (e.g., `type:"enum"`, `enum:{...}`) into valid
JSON Schema 2020-12. This prevents validation failures when such schemas
are used as tool input schemas (e.g., from MCP servers).

Updates `isZodSchema` to reject deserialized Zod impostors that retain
`_zod` but lose their prototype.

Wires `toJSON` methods onto TypeBox shim schemas to ensure `JSON.stringify`
produces clean JSON Schema, preventing future leaks.

Fixes #1101
2026-05-19 10:16:40 +09:00
roboomp 74461e6f53 fix(ai): broadened openai schema sanitization coverage
Added dependencies and contentSchema to the OpenAI Responses schema-position sets so draft-04..07 dependencies maps and draft 2019-09 contentSchema nodes also get the properties-on-object normalization. Recognized array-form type declarations that include object. Preserved malformed non-array oneOf payloads instead of dropping them. Documented the cycle-safe cache seed and added a self-referential regression test.

Refs #1147
2026-05-17 10:55:29 +00:00
roboomp 03623bdf5e fix(ai): normalized openai object tool schemas
Normalized OpenAI Responses schema sanitization so object schema nodes always include properties while only traversing schema-valued positions. Wired the same sanitizer into the OpenAI Codex Responses tool conversion path and added regression coverage for no-argument MCP-style schemas and literal payload preservation.

Fixes #1147
2026-05-17 09:28:59 +00:00
can1357 54a60a7702 fix(ai/schema): hoisted description to anyOf wrapper in strict-mode unions
- Extracted `description` from type-array and nullable branches so it lives on the wrapper, not duplicated onto each variant.
- Replaced inline enum-type inference with `inferStrictPrimitiveTypeFromEnumOrConst`, covering both `enum` and `const` in sanitize and enforce paths.
- Mixed-primitive enums and non-primitive consts now fall back to non-strict instead of producing a typeless schema that OpenAI rejects on the wire.
2026-05-16 20:22:39 +02:00
can1357 e7200c2e4a feat(ai): added unified normalize flow for Google/CCA schema handling
- Implemented a unified normalization flow by switching Google/CCA handling to normalizeSchemaForGoogle/CCA.
- Added normalize.ts with recursive node normalization, nullable-union checks, and combiner collapsing.
- Removed sanitize-google.ts and normalize-cca.ts, replacing them with normalize exports in schema indexes.
- Added spill-to-description utilities with spill/paren modes and `$defs` exclusion for unsupported fields.
- Updated MCP bridge and schema tests to use normalizeSchemaFor* APIs with expanded compatibility checks.
- Documented normalization behavior changes and breaking rename in constraints and package changelog files.
2026-05-16 19:04:09 +02:00
can1357 9bd4d0099b fix(ai): resolved strict-mode local-ref inlining and single-item allOf
- Added strict-mode ref preprocessing to resolve local `#/` refs with sibling-key precedence.
- Added single-item allOf handling that inlines the sole schema entry and retains multi-item allOf.
- Added Anthropic schema normalization to retain whitelisted metadata and enforce stricter key handling.
- Added Google schema sanitizer options for snake_case renaming, null-collapse behavior, and ordering.
- Expanded schema tests for Anthropic, Google, and strict-mode parity across refs, arrays, and recursion.
2026-05-16 18:33:25 +02:00
can1357 0d00f015ec feat(ai): added 2020-12 schema migration to ai strict validation
- Added draft-07-to-2020-12 schema upgrade logic for `$schema`, `definitions`, `$ref`, tuple, and nullable mapping.
- Integrated `upgradeJsonSchemaTo202012` into strict adaptation, validation, strict-mode, sanitizer, and wire/dereference flows.
- Replaced set-based traversal helpers with record maps, `for...in` loops, and epoch/`once` cycle guards.
- Added unreleased changelog entries and tests for schema normalization, `oneOf` to `anyOf`, and tuple validation edge cases.
2026-05-15 23:46:23 +02:00
can1357 5c7c5eccb8 fix(ai): corrected AI schema normalization to draft-2020-12 prefixItems
- Adopted draft-2020-12 tuple validation with `prefixItems`, rejecting array-valued `items`.
- Expanded strict-mode handling to recurse `prefixItems` entries and infer `array` when tuple prefixes exist.
- Normalized Anthropic schemas through `prefixItems`, keeping supported tuple constraints and dropping unsupported fields.
- Updated coding-agent schema metadata and tests to draft-2020-12 `$schema` targets, including MCP/theme fixtures.
- Added `trimTrailingWhitespace()` to strip trailing spaces/tabs and keep the original line when none exist.
2026-05-15 23:46:23 +02:00
can1357 cba80c79c3 fix(schema): harden all provider schema normalizers with cycle detection, fixpoint iteration, and correctness fixes
## New: schema compatibility validation API

Add `validateSchemaCompatibility(schema, provider)` in
`packages/ai/src/utils/schema/compatibility.ts` that performs a static
audit of a JSON Schema against three provider targets:

- `openai-strict`: checks forbidden keys, required/properties symmetry,
  additionalProperties constraint, and that every node declares a type,
  combinator, or $ref
- `google`: checks unsupported keyword set and array-valued type
- `cloud-code-assist-claude`: checks forbidden keywords, array type,
  null type, nullable keyword, and combiner presence; also validates via
  AJV 2020 draft

Add `validateStrictSchemaEnforcement(original, result)` to assert the
fail-open contract: when strict enforcement succeeds the output must pass
openai-strict validation; when it fails the output must be the original
schema object (same reference).

Export both functions and their types from `./utils/schema/index.ts`.

## New: shared constants in fields.ts

Extract `COMBINATOR_KEYS` (`anyOf`, `allOf`, `oneOf`) and add
`CCA_UNSUPPORTED_SCHEMA_FIELDS` as exported constants, eliminating the
local duplicate in `strict-mode.ts` and providing a canonical field set
for Cloud Code Assist (much narrower than the Google set — CCA supports
validation keywords like `additionalProperties`, `minLength`,
`pattern`, etc.).

## Fix: cycle detection in all recursive schema traversals

All recursive walkers now carry a `WeakSet<object>` guard. Previously any
schema with a reference cycle (or a schema object that appears at two
nodes in the tree) would cause an infinite loop or a stack overflow:

- `sanitizeSchemaForStrictMode` / `enforceStrictSchema`
- `normalizeSchemaForCloudCodeAssistClaude`
- `normalizeNullablePropertiesForCloudCodeAssist`
- `stripResidualCombiners`
- `sanitizeSchemaImpl` (Google sanitizer)
- `hasResidualCloudCodeAssistIncompatibilities`

`hasResidualCloudCodeAssistIncompatibilities` previously returned `true`
for already-visited nodes, producing false positives that forced the CCA
fallback schema on valid (but multiply-referenced) schemas. It now
correctly returns `false`.

## Fix: stripResidualCombiners iterates to fixpoint

The previous single-pass approach missed chained combiner reductions
where one collapsed variant exposed another reducible combiner. The
rewriter now loops until no further reduction occurs.

## Fix: mergeObjectCombinerVariants required-field computation

The merged object schema now takes the intersection of all variants'
`required` arrays, then unions in own-level required properties that
exist in the merged schema. Previously the `required` field was silently
dropped from the flattened schema, making all properties effectively
optional.

## Fix: sanitizeSchemaForGoogle improvements

- Type inference for const-collapsed enums: type is derived from all
  variants (must unanimously agree), falling back to inference from enum
  values; mixed null/non-null infers the non-null scalar type and sets
  `nullable: true`
- Const→enum deduplication now uses deep structural equality instead of
  `Object.is`
- Recursion spreads the full options object so new fields (`unsupportedFields`,
  `seen`) are not silently dropped when descending into sub-schemas
- Array-valued `type` is filtered to strings before processing
- Removed incorrect stripping of `additionalProperties: false` (the
  field is valid and should be preserved)
- Parameterized `unsupportedFields` in `SanitizeSchemaOptions` enables
  code reuse between the Google and CCA sanitizers

## Fix: sanitizeSchemaForStrictMode / enforceStrictSchema

- `nullable: true` is now stripped during sanitization and expanded into
  `anyOf: [schema, {type: "null"}]` in the enforcer output, matching
  what OpenAI strict mode requires
- Type inference: `type: "array"` is inferred when `items` is present;
  a scalar type is inferred from uniform `enum` values
- Const→enum merge uses deep equality to avoid duplicate entries when
  both `const` and `enum` exist with the same value
- `additionalProperties` is now dropped unconditionally in sanitization
  (previously only object-valued `additionalProperties` was recursed;
  non-object values were passed through)
- `enforceStrictSchema` recurses into `$defs` and `definitions` blocks
- `enforceStrictSchema` handles tuple-style `items` arrays
- `enforceStrictSchema` skips double-wrapping: optional properties
  already expressed as `anyOf: [..., {type: "null"}]` are not wrapped again
- `tryEnforceStrictSchema` now caches results in a `WeakMap` keyed on
  the input schema object to avoid redundant work on repeated calls

## Fix: mergeCompatibleEnumSchemas deep equality

Uses `areJsonValuesEqual` instead of `Object.is` when deduplicating
enum members, so structurally equal objects are not duplicated.

## New: test coverage

- `packages/ai/test/schema-normalization.test.ts`: comprehensive unit
  tests for strict mode, Google, and Cloud Code Assist normalization
- `packages/ai/test/schema-compatibility.test.ts`: unit tests for all
  three provider targets in the new compatibility validator
- `packages/coding-agent/test/tools/provider-schema-compatibility.test.ts`:
  integration test that instantiates every builtin and hidden tool, runs
  their parameter schemas through all three provider pipelines, and
  asserts zero compatibility violations
2026-02-28 18:41:10 +01:00