diff --git a/dist/providers/google-shared.js b/dist/providers/google-shared.js --- a/dist/providers/google-shared.js +++ b/dist/providers/google-shared.js @@ -52,19 +52,25 @@ } 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) { + // Only send thought signatures for Claude models - Gemini doesn't support them + // and will mimic tags if we include them as text. + if (block.thinkingSignature && model.id.includes("claude")) { parts.push({ thought: true, text: sanitizeSurrogates(block.thinking), thoughtSignature: block.thinkingSignature, }); } - else { - parts.push({ - text: `\n${sanitizeSurrogates(block.thinking)}\n`, - }); + else if (!model.id.includes("gemini")) { + // For non-Gemini, non-Claude models, include as text with delimiters + // Skip entirely for Gemini to avoid it mimicking the pattern + if (block.thinking && block.thinking.trim()) { + parts.push({ + text: `\n${sanitizeSurrogates(block.thinking)}\n`, + }); + } } + // For Gemini models without Claude signature: skip thinking blocks entirely } else if (block.type === "toolCall") { const part = { @@ -147,6 +153,77 @@ 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. */ export function convertTools(tools) { @@ -157,7 +234,7 @@ functionDeclarations: tools.map((tool) => ({ name: tool.name, description: tool.description, - parameters: tool.parameters, + parameters: sanitizeSchemaForGoogle(tool.parameters), })), }, ];