diff --git a/dist/providers/google-shared.js b/dist/providers/google-shared.js
index 7bc0a9f5d6241f191cd607ecb37b3acac8d58267..56866774e47444b5d333961c9b20fce582363124 100644
--- a/dist/providers/google-shared.js
+++ b/dist/providers/google-shared.js
@@ -10,13 +10,27 @@ import { transformMessages } from "./transorm-messages.js";
 export function convertMessages(model, context) {
     const contents = [];
     const transformedMessages = transformMessages(context.messages, model);
+    
+    /**
+     * Helper to add content while merging consecutive messages of the same role.
+     * Gemini/Cloud Code Assist requires strict role alternation (user/model/user/model).
+     * Consecutive messages of the same role cause "function call turn" errors.
+     */
+    function addContent(role, parts) {
+        if (parts.length === 0) return;
+        const lastContent = contents[contents.length - 1];
+        if (lastContent?.role === role) {
+            // Merge into existing message of same role
+            lastContent.parts.push(...parts);
+        } else {
+            contents.push({ role, parts });
+        }
+    }
+    
     for (const msg of transformedMessages) {
         if (msg.role === "user") {
             if (typeof msg.content === "string") {
-                contents.push({
-                    role: "user",
-                    parts: [{ text: sanitizeSurrogates(msg.content) }],
-                });
+                addContent("user", [{ text: sanitizeSurrogates(msg.content) }]);
             }
             else {
                 const parts = msg.content.map((item) => {
@@ -35,10 +49,7 @@ export function convertMessages(model, context) {
                 const filteredParts = !model.input.includes("image") ? parts.filter((p) => p.text !== undefined) : parts;
                 if (filteredParts.length === 0)
                     continue;
-                contents.push({
-                    role: "user",
-                    parts: filteredParts,
-                });
+                addContent("user", filteredParts);
             }
         }
         else if (msg.role === "assistant") {
@@ -51,9 +62,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 +82,7 @@ export function convertMessages(model, context) {
                         });
                     }
                     else {
+                        // Other models: convert to text with delimiters
                         parts.push({
                             text: `<thinking>\n${sanitizeSurrogates(block.thinking)}\n</thinking>`,
                         });
@@ -85,10 +107,7 @@ export function convertMessages(model, context) {
             }
             if (parts.length === 0)
                 continue;
-            contents.push({
-                role: "model",
-                parts,
-            });
+            addContent("model", parts);
         }
         else if (msg.role === "toolResult") {
             // Extract text and image content
@@ -125,27 +144,94 @@ export function convertMessages(model, context) {
             }
             // Cloud Code Assist API requires all function responses to be in a single user turn.
             // Check if the last content is already a user turn with function responses and merge.
+            // Use addContent for proper role alternation handling.
             const lastContent = contents[contents.length - 1];
             if (lastContent?.role === "user" && lastContent.parts?.some((p) => p.functionResponse)) {
                 lastContent.parts.push(functionResponsePart);
             }
             else {
-                contents.push({
-                    role: "user",
-                    parts: [functionResponsePart],
-                });
+                addContent("user", [functionResponsePart]);
             }
             // For older models, add images in a separate user message
+            // Note: This may create consecutive user messages, but addContent will merge them
             if (hasImages && !supportsMultimodalFunctionResponse) {
-                contents.push({
-                    role: "user",
-                    parts: [{ text: "Tool result image:" }, ...imageParts],
-                });
+                addContent("user", [{ text: "Tool result image:" }, ...imageParts]);
             }
         }
     }
     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 +243,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) {
diff --git a/dist/providers/google-gemini-cli.js b/dist/providers/google-gemini-cli.js
--- a/dist/providers/google-gemini-cli.js
+++ b/dist/providers/google-gemini-cli.js
@@ -168,7 +168,12 @@ async function* streamCompletion(params, options) {
                         break; // Success, exit retry loop
                     }
                     const errorText = await response.text();
-                    // Check if retryable
+                    // PATCH: Fail immediately on 429 to let caller rotate accounts
+                    if (response.status === 429) {
+                        console.log(`[pi-ai] 429 rate limit - failing fast to rotate account`);
+                        throw new Error(`Cloud Code Assist API error (${response.status}): ${errorText}`);
+                    }
+                    // Check if retryable (non-429 errors)
                     if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
                         // Use server-provided delay or exponential backoff
                         const serverDelay = extractRetryDelay(errorText);
@@ -183,6 +188,10 @@ async function* streamCompletion(params, options) {
                     if (error instanceof Error && error.message === "Request was aborted") {
                         throw error;
                     }
+                    // PATCH: Don't retry 429 errors - let caller rotate accounts
+                    if (error instanceof Error && error.message.includes("429")) {
+                        throw error;
+                    }
                     lastError = error instanceof Error ? error : new Error(String(error));
                     // Network errors are retryable
                     if (attempt < MAX_RETRIES) {
