diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 192a9f446..e4bb1d7bc 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -33,6 +33,7 @@ - Fixed an HTTP 400 error when resuming or replaying OpenAI history after an interrupted native Computer Use turn. - Fixed connection 404 errors when using Google Vertex AI in multi-region locations (eu and us) by correctly resolving regional endpoint (REP) hosts. - Fixed a resource leak in SqliteAuthCredentialStore.close() where unclosed prepared statements kept the SQLite connection alive, preventing database file cleanup (especially on Windows where files remained locked). +- Fixed Google Gemini and Vertex tool declarations carrying numeric, boolean, object-valued, or mixed `enum` arrays that the Google Schema wire type cannot represent. Unsupported enums are omitted while valid string enums remain constrained. ## [17.1.7] - 2026-07-27 @@ -58,9 +59,6 @@ - Kimi Code now sends its session-stable prompt cache key on both supported transports: `prompt_cache_key` for OpenAI-compatible requests and `metadata.user_id` for Anthropic-compatible requests. Explicit keys survive side-channel session IDs, while `cacheRetention: "none"` still disables automatic affinity ([#6049](https://github.com/can1357/oh-my-pi/issues/6049)). - Fresh encrypted auth-broker snapshot caches are revalidated within a short startup budget, so one-shot clients see newly imported or revoked credentials immediately when the broker is reachable while retaining cache fallback for transport and server failures. - Fixed custom `anthropic-messages` endpoints dropping native web-search call/result blocks in the leaked-thinking wrapper, preserving signed continuation history in source order without carrying a preceding text signature onto later unsigned blocks ([#6703](https://github.com/can1357/oh-my-pi/issues/6703)). -### Fixed - -- Fixed Google Gemini and Vertex tool declarations carrying numeric, boolean, object-valued, or mixed `enum` arrays that the Google Schema wire type cannot represent. Unsupported enums are omitted while valid string enums remain constrained. ## [17.1.4] - 2026-07-26 diff --git a/packages/ai/src/utils/schema/normalize.ts b/packages/ai/src/utils/schema/normalize.ts index d45c2ab38..7c7d2c2cb 100644 --- a/packages/ai/src/utils/schema/normalize.ts +++ b/packages/ai/src/utils/schema/normalize.ts @@ -107,6 +107,15 @@ const SUBSCHEMA_VALUE_KEYS: Record = { contentSchema: true, }; +/** + * Keywords whose value is either a boolean keyword value or an object + * subschema. Object values must be walked, while bare booleans stay literal. + */ +const BOOLEAN_OR_SCHEMA_VALUE_KEYS: Record = { + additionalProperties: true, + unevaluatedProperties: true, +}; + /** Keywords whose value is an array of subschemas. */ const SUBSCHEMA_ARRAY_KEYS: Record = { anyOf: true, @@ -367,12 +376,15 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa options.insideSchemaMap || Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) || Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key); + const childIsSubschema = + childBooleanIsSubschema || + (Object.hasOwn(BOOLEAN_OR_SCHEMA_VALUE_KEYS, key) && isJsonObject(entry)); result[key] = - childInsideSchemaMap || childBooleanIsSubschema + childInsideSchemaMap || childIsSubschema ? normalizeSchemaNode(entry, { ...options, insideSchemaMap: childInsideSchemaMap, - booleanIsSubschema: childBooleanIsSubschema, + booleanIsSubschema: childIsSubschema, }) : entry; } @@ -398,12 +410,15 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa options.insideSchemaMap || Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) || Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key); + const childIsSubschema = + childBooleanIsSubschema || + (Object.hasOwn(BOOLEAN_OR_SCHEMA_VALUE_KEYS, key) && isJsonObject(entry)); result[key] = - childInsideSchemaMap || childBooleanIsSubschema + childInsideSchemaMap || childIsSubschema ? normalizeSchemaNode(entry, { ...options, insideSchemaMap: childInsideSchemaMap, - booleanIsSubschema: childBooleanIsSubschema, + booleanIsSubschema: childIsSubschema, }) : entry; } diff --git a/packages/ai/test/schema-normalization.test.ts b/packages/ai/test/schema-normalization.test.ts index 904f21c4b..b49c2e7fb 100644 --- a/packages/ai/test/schema-normalization.test.ts +++ b/packages/ai/test/schema-normalization.test.ts @@ -1305,6 +1305,21 @@ describe("normalizeSchemaForMoonshot", () => { expect(props.limit).toEqual({ type: "integer", default: 10 }); }); + it("normalizes schema-valued additionalProperties without walking literal payload objects", () => { + const literal = { oneOf: [{ const: "literal-a" }, { const: "literal-b" }] }; + const normalized = normalizeSchemaForMoonshot({ + type: "object", + additionalProperties: { oneOf: [{ const: 1 }, { const: 2 }] }, + default: literal, + }); + + expect(normalized).toEqual({ + type: "object", + additionalProperties: { type: "number", enum: [1, 2] }, + default: literal, + }); + }); + it("coerces boolean subschemas to MFJS object forms without changing boolean keywords", () => { expect( normalizeSchemaForMoonshot({