Files
clawdbot/patches/@mariozechner__pi-ai.patch
Muhammed Mukhthar CM 9958283ced fix: Antigravity API compatibility and Gemini thinking tag leakage (#167)
* fix: ensure type:object in sanitized tool schemas for Antigravity API

The sanitizeSchemaForGoogle function strips unsupported JSON Schema
keywords like anyOf, but this can leave schemas with 'properties' and
'required' fields without a 'type' field. Both Google's Gemini API and
Anthropic via Antigravity require 'type: object' when these fields exist.

This fix adds a post-sanitization check that ensures type is set to
'object' when properties or required fields are present.

Fixes errors like:
- Gemini: 'parameters.properties: only allowed for OBJECT type'
- Anthropic: 'tools.6.custom.input_schema.type: Field required'

* fix: regenerate pi-ai patch with proper pnpm format

The patch now correctly applies via pnpm patch-commit, fixing:
- Thinking blocks: skip for Gemini, send with signature for Claude
- Schema sanitization: ensure type:object after removing anyOf
- Remove strict:null for LM Studio/Antigravity compatibility

Tested with all Antigravity models (Gemini and Claude).

* fix: strip thinking tags from block streaming output to prevent Gemini tag leakage
2026-01-04 12:44:19 +01:00

135 lines
5.6 KiB
Diff

diff --git a/dist/providers/google-shared.js b/dist/providers/google-shared.js
index 7bc0a9f5d6241f191cd607ecb37b3acac8d58267..76166a34784cbc0718d4b9bd1fa6336a6dd394ec 100644
--- a/dist/providers/google-shared.js
+++ b/dist/providers/google-shared.js
@@ -51,9 +51,19 @@ export function convertMessages(model, context) {
parts.push({ text: sanitizeSurrogates(block.text) });
}
else if (block.type === "thinking") {
- // Thinking blocks require signatures for Claude via Antigravity.
- // If signature is missing (e.g. from GPT-OSS), convert to regular text with delimiters.
- if (block.thinkingSignature) {
+ // Thinking blocks handling varies by model:
+ // - Claude via Antigravity: requires thinkingSignature
+ // - Gemini: skip entirely (doesn't understand thoughtSignature, and mimics <thinking> tags)
+ // - Other models: convert to text with delimiters
+ const isGemini = model.id.toLowerCase().includes("gemini");
+ const isClaude = model.id.toLowerCase().includes("claude");
+ if (isGemini) {
+ // Skip thinking blocks entirely for Gemini - it doesn't support them
+ // and will mimic <thinking> tags if we convert to text
+ continue;
+ }
+ else if (block.thinkingSignature && isClaude) {
+ // Claude via Antigravity requires the signature
parts.push({
thought: true,
text: sanitizeSurrogates(block.thinking),
@@ -61,6 +71,7 @@ export function convertMessages(model, context) {
});
}
else {
+ // Other models: convert to text with delimiters
parts.push({
text: `<thinking>\n${sanitizeSurrogates(block.thinking)}\n</thinking>`,
});
@@ -146,6 +157,77 @@ export function convertMessages(model, context) {
}
return contents;
}
+/**
+ * Sanitize JSON Schema for Google Cloud Code Assist API.
+ * Removes unsupported keywords like patternProperties, const, anyOf, etc.
+ * and converts to a format compatible with Google's function declarations.
+ */
+function sanitizeSchemaForGoogle(schema) {
+ if (!schema || typeof schema !== 'object') {
+ return schema;
+ }
+ // If it's an array, sanitize each element
+ if (Array.isArray(schema)) {
+ return schema.map(item => sanitizeSchemaForGoogle(item));
+ }
+ const sanitized = {};
+ // List of unsupported JSON Schema keywords that Google's API doesn't understand
+ const unsupportedKeywords = [
+ 'patternProperties',
+ 'const',
+ 'anyOf',
+ 'oneOf',
+ 'allOf',
+ 'not',
+ '$schema',
+ '$id',
+ '$ref',
+ '$defs',
+ 'definitions',
+ 'if',
+ 'then',
+ 'else',
+ 'dependentSchemas',
+ 'dependentRequired',
+ 'unevaluatedProperties',
+ 'unevaluatedItems',
+ 'contentEncoding',
+ 'contentMediaType',
+ 'contentSchema',
+ 'deprecated',
+ 'readOnly',
+ 'writeOnly',
+ 'examples',
+ '$comment',
+ 'additionalProperties',
+ ];
+ // TODO(steipete): lossy schema scrub; revisit when Google supports these keywords.
+ for (const [key, value] of Object.entries(schema)) {
+ // Skip unsupported keywords
+ if (unsupportedKeywords.includes(key)) {
+ continue;
+ }
+ // Recursively sanitize nested objects
+ if (key === 'properties' && typeof value === 'object' && value !== null) {
+ sanitized[key] = {};
+ for (const [propKey, propValue] of Object.entries(value)) {
+ sanitized[key][propKey] = sanitizeSchemaForGoogle(propValue);
+ }
+ } else if (key === 'items' && typeof value === 'object') {
+ sanitized[key] = sanitizeSchemaForGoogle(value);
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
+ sanitized[key] = sanitizeSchemaForGoogle(value);
+ } else {
+ sanitized[key] = value;
+ }
+ }
+ // Ensure type: "object" is present when properties or required exist
+ // Google API requires type to be set when these fields are present
+ if (('properties' in sanitized || 'required' in sanitized) && !('type' in sanitized)) {
+ sanitized.type = 'object';
+ }
+ return sanitized;
+}
/**
* Convert tools to Gemini function declarations format.
*/
@@ -157,7 +239,7 @@ export function convertTools(tools) {
functionDeclarations: tools.map((tool) => ({
name: tool.name,
description: tool.description,
- parameters: tool.parameters,
+ parameters: sanitizeSchemaForGoogle(tool.parameters),
})),
},
];
diff --git a/dist/providers/openai-responses.js b/dist/providers/openai-responses.js
index 20fb0a22aaa28f7ff7c2f44a8b628fa1d9d7d936..31bae0aface1319487ce62d35f1f3b6ed334863e 100644
--- a/dist/providers/openai-responses.js
+++ b/dist/providers/openai-responses.js
@@ -486,7 +486,6 @@ function convertTools(tools) {
name: tool.name,
description: tool.description,
parameters: tool.parameters, // TypeBox already generates JSON Schema
- strict: null,
}));
}
function mapStopReason(status) {