From 64babcac7a63658af9c75dcc0e8a71ec341df76a Mon Sep 17 00:00:00 2001 From: Sebastian Barrios Date: Sat, 10 Jan 2026 11:02:06 -0500 Subject: [PATCH] fix(agents): harden Cloud Code Assist compatibility - Expand schema scrubber to strip additional constraint keywords rejected by Cloud Code Assist (examples, minLength, maxLength, minimum, maximum, multipleOf, pattern, format, minItems, maxItems, uniqueItems, minProperties, maxProperties) - Extend tool call ID sanitization to cover toolUse and toolCall block types (previously only functionCall was sanitized) - Update pi-tools test to include 'examples' in unsupported keywords Fixes 400 errors when using google-antigravity/claude-opus-4-5-thinking: - tools.N.custom.input_schema: JSON schema is invalid - messages.N.content.N.tool_use.id: String should match pattern --- src/agents/pi-embedded-helpers.ts | 20 +++++++++++--------- src/agents/pi-tools.test.ts | 1 + src/agents/schema/clean-for-gemini.ts | 25 +++++++++++++++++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/agents/pi-embedded-helpers.ts b/src/agents/pi-embedded-helpers.ts index cae4ba51f..049647ac5 100644 --- a/src/agents/pi-embedded-helpers.ts +++ b/src/agents/pi-embedded-helpers.ts @@ -146,18 +146,20 @@ export async function sanitizeSessionMessagesImages( // Also sanitize tool call IDs in assistant messages (function call blocks) const sanitizedContent = await Promise.all( filteredContent.map(async (block) => { - if ( - block && - typeof block === "object" && - (block as { type?: unknown }).type === "functionCall" && - (block as { id?: unknown }).id - ) { - const functionBlock = block as { type: string; id: string }; + if (!block || typeof block !== "object") return block; + + const type = (block as { type?: unknown }).type; + const id = (block as { id?: unknown }).id; + if (typeof id !== "string" || !id) return block; + + // Cloud Code Assist tool blocks require ids matching ^[a-zA-Z0-9_-]+$. + if (type === "functionCall" || type === "toolUse" || type === "toolCall") { return { - ...functionBlock, - id: sanitizeToolCallId(functionBlock.id), + ...((block as unknown) as Record), + id: sanitizeToolCallId(id), }; } + return block; }), ); diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts index c3c207c70..a4eabc81f 100644 --- a/src/agents/pi-tools.test.ts +++ b/src/agents/pi-tools.test.ts @@ -384,6 +384,7 @@ describe("createClawdbotCodingTools", () => { "$ref", "$defs", "definitions", + "examples", ]); const findUnsupportedKeywords = ( diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index e84729f8a..219319fb8 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -10,6 +10,23 @@ const UNSUPPORTED_SCHEMA_KEYWORDS = new Set([ "$ref", "$defs", "definitions", + // Non-standard (OpenAPI) keyword; Claude validators reject it. + "examples", + + // Cloud Code Assist appears to validate tool schemas more strictly/quirkily than + // draft 2020-12 in practice; these constraints frequently trigger 400s. + "minLength", + "maxLength", + "minimum", + "maximum", + "multipleOf", + "pattern", + "format", + "minItems", + "maxItems", + "uniqueItems", + "minProperties", + "maxProperties", ]); // Check if an anyOf/oneOf array contains only literal values that can be flattened. @@ -134,14 +151,14 @@ function cleanSchemaForGeminiWithDefs( const result: Record = { ...(cleaned as Record), }; - for (const key of ["description", "title", "default", "examples"]) { + for (const key of ["description", "title", "default"]) { if (key in obj && obj[key] !== undefined) result[key] = obj[key]; } return result; } const result: Record = {}; - for (const key of ["description", "title", "default", "examples"]) { + for (const key of ["description", "title", "default"]) { if (key in obj && obj[key] !== undefined) result[key] = obj[key]; } return result; @@ -157,7 +174,7 @@ function cleanSchemaForGeminiWithDefs( type: flattened.type, enum: flattened.enum, }; - for (const key of ["description", "title", "default", "examples"]) { + for (const key of ["description", "title", "default"]) { if (key in obj && obj[key] !== undefined) result[key] = obj[key]; } return result; @@ -171,7 +188,7 @@ function cleanSchemaForGeminiWithDefs( type: flattened.type, enum: flattened.enum, }; - for (const key of ["description", "title", "default", "examples"]) { + for (const key of ["description", "title", "default"]) { if (key in obj && obj[key] !== undefined) result[key] = obj[key]; } return result;