From e9217181c139b930292b5992c2602edea1e8a35f Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 9 Jan 2026 08:05:08 -0300 Subject: [PATCH] fix(agents): remove unsupported JSON Schema keywords for Cloud Code Assist API Cloud Code Assist API requires strict JSON Schema draft 2020-12 compliance and rejects keywords like patternProperties, additionalProperties, $schema, $id, $ref, $defs, and definitions. This extends cleanSchemaForGemini to: - Remove all unsupported keywords from tool schemas - Add oneOf literal flattening (matching existing anyOf behavior) - Add test to verify no unsupported keywords remain in tool schemas --- src/agents/pi-tools.test.ts | 48 +++++++++++++++++++++++++++++++++++++ src/agents/pi-tools.ts | 47 +++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts index 3242f1e7e..a32f02637 100644 --- a/src/agents/pi-tools.test.ts +++ b/src/agents/pi-tools.test.ts @@ -331,4 +331,52 @@ describe("createClawdbotCodingTools", () => { expect(tools.some((tool) => tool.name === "Bash")).toBe(true); expect(tools.some((tool) => tool.name === "browser")).toBe(false); }); + + it("removes unsupported JSON Schema keywords for Cloud Code Assist API compatibility", () => { + const tools = createClawdbotCodingTools(); + + // Helper to recursively check schema for unsupported keywords + const unsupportedKeywords = new Set([ + "patternProperties", + "additionalProperties", + "$schema", + "$id", + "$ref", + "$defs", + "definitions", + ]); + + const findUnsupportedKeywords = ( + schema: unknown, + path: string, + ): string[] => { + const found: string[] = []; + if (!schema || typeof schema !== "object") return found; + if (Array.isArray(schema)) { + schema.forEach((item, i) => { + found.push(...findUnsupportedKeywords(item, `${path}[${i}]`)); + }); + return found; + } + for (const [key, value] of Object.entries( + schema as Record, + )) { + if (unsupportedKeywords.has(key)) { + found.push(`${path}.${key}`); + } + if (value && typeof value === "object") { + found.push(...findUnsupportedKeywords(value, `${path}.${key}`)); + } + } + return found; + }; + + for (const tool of tools) { + const violations = findUnsupportedKeywords( + tool.parameters, + `${tool.name}.parameters`, + ); + expect(violations).toEqual([]); + } + }); }); diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 440a2a95f..23e8693ef 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -195,12 +195,24 @@ function tryFlattenLiteralAnyOf( return null; } +// Keywords that Cloud Code Assist API rejects (not compliant with their JSON Schema subset) +const UNSUPPORTED_SCHEMA_KEYWORDS = new Set([ + "patternProperties", + "additionalProperties", + "$schema", + "$id", + "$ref", + "$defs", + "definitions", +]); + function cleanSchemaForGemini(schema: unknown): unknown { if (!schema || typeof schema !== "object") return schema; if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini); const obj = schema as Record; const hasAnyOf = "anyOf" in obj && Array.isArray(obj.anyOf); + const hasOneOf = "oneOf" in obj && Array.isArray(obj.oneOf); // Try to flatten anyOf of literals to a single enum BEFORE processing // This handles Type.Union([Type.Literal("a"), Type.Literal("b")]) patterns @@ -221,14 +233,28 @@ function cleanSchemaForGemini(schema: unknown): unknown { } } + // Try to flatten oneOf of literals similarly + if (hasOneOf) { + const flattened = tryFlattenLiteralAnyOf(obj.oneOf as unknown[]); + if (flattened) { + const result: Record = { + type: flattened.type, + enum: flattened.enum, + }; + for (const key of ["description", "title", "default", "examples"]) { + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } + } + return result; + } + } + const cleaned: Record = {}; for (const [key, value] of Object.entries(obj)) { - // Skip unsupported schema features for Gemini: - // - patternProperties: not in OpenAPI 3.0 subset - // - const: convert to enum with single value instead - if (key === "patternProperties") { - // Gemini doesn't support patternProperties - skip it + // Skip keywords that Cloud Code Assist API doesn't support + if (UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) { continue; } @@ -238,8 +264,8 @@ function cleanSchemaForGemini(schema: unknown): unknown { continue; } - // Skip 'type' if we have 'anyOf' — Gemini doesn't allow both - if (key === "type" && hasAnyOf) { + // Skip 'type' if we have 'anyOf' or 'oneOf' — Gemini doesn't allow both + if (key === "type" && (hasAnyOf || hasOneOf)) { continue; } @@ -261,13 +287,6 @@ function cleanSchemaForGemini(schema: unknown): unknown { } else if (key === "allOf" && Array.isArray(value)) { // Clean each allOf variant cleaned[key] = value.map((variant) => cleanSchemaForGemini(variant)); - } else if ( - key === "additionalProperties" && - value && - typeof value === "object" - ) { - // Recursively clean additionalProperties schema - cleaned[key] = cleanSchemaForGemini(value); } else { cleaned[key] = value; }