diff --git a/patches/@mariozechner__pi-ai.patch b/patches/@mariozechner__pi-ai.patch index 8ac6d7d8eb..956f0b2be7 100644 --- a/patches/@mariozechner__pi-ai.patch +++ b/patches/@mariozechner__pi-ai.patch @@ -1,5 +1,90 @@ +diff --git a/dist/providers/google-shared.js b/dist/providers/google-shared.js +index ff9cbcfebfac6b4370d85dc838f5cacf2a60ed64..42096c82aec925b412258348a36ba4a7025b283b 100644 +--- a/dist/providers/google-shared.js ++++ b/dist/providers/google-shared.js +@@ -140,6 +140,71 @@ 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', ++ ]; ++ 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; ++ } ++ } ++ return sanitized; ++} + /** + * Convert tools to Gemini function declarations format. + */ +@@ -151,7 +216,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..c2bc63f483f3285b00755901ba97db810221cea6 100644 +index 20fb0a22aaa28f7ff7c2f44a8b628fa1d9d7d936..31bae0aface1319487ce62d35f1f3b6ed334863e 100644 --- a/dist/providers/openai-responses.js +++ b/dist/providers/openai-responses.js @@ -486,7 +486,6 @@ function convertTools(tools) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd37feca70..cf95c3d718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ overrides: patchedDependencies: '@mariozechner/pi-ai': - hash: bf3e904ebaad236b8c3bb48c7d1150a1463735e783acaab6d15d6cd381b43832 + hash: 9d828603572332a8eba73e7d08d3a32408bc1d87a5c1f27b3f9f8d35c3d2ffb0 path: patches/@mariozechner__pi-ai.patch '@mariozechner/pi-coding-agent@0.31.1': hash: d0d5ffa1bfda8a0f9d14a5e73a074014346d3edbdb2ffc91444d3be5119f5745 diff --git a/src/commands/configure.ts b/src/commands/configure.ts index c0096344f6..ac4d6abb0d 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -10,7 +10,11 @@ import { spinner, text, } from "@clack/prompts"; -import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai"; +import { + loginAnthropic, + loginAntigravity, + type OAuthCredentials, +} from "@mariozechner/pi-ai"; import type { ClawdisConfig } from "../config/config.js"; import { @@ -223,13 +227,17 @@ async function promptAuthConfig( message: "Model/auth choice", options: [ { value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" }, + { + value: "antigravity", + label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)", + }, { value: "apiKey", label: "Anthropic API key" }, { value: "minimax", label: "Minimax M2.1 (LM Studio)" }, { value: "skip", label: "Skip for now" }, ], }), runtime, - ) as "oauth" | "apiKey" | "minimax" | "skip"; + ) as "oauth" | "antigravity" | "apiKey" | "minimax" | "skip"; let next = cfg; @@ -266,6 +274,47 @@ async function promptAuthConfig( spin.stop("OAuth failed"); runtime.error(String(err)); } + } else if (authChoice === "antigravity") { + note( + [ + "Browser will open for Google authentication.", + "Sign in with your Google account that has Antigravity access.", + "The callback will be captured automatically on localhost:51121.", + ].join("\n"), + "Google Antigravity OAuth", + ); + const spin = spinner(); + spin.start("Starting OAuth flow…"); + let oauthCreds: OAuthCredentials | null = null; + try { + oauthCreds = await loginAntigravity( + async ({ url, instructions }) => { + spin.message(instructions ?? "Complete sign-in in browser…"); + await openUrl(url); + runtime.log(`Open: ${url}`); + }, + (msg) => spin.message(msg), + ); + spin.stop("Antigravity OAuth complete"); + if (oauthCreds) { + await writeOAuthCredentials("google-antigravity", oauthCreds); + // Set default model to Claude Opus 4.5 via Antigravity + next = { + ...next, + agent: { + ...next.agent, + model: "google-antigravity/claude-opus-4-5", + }, + }; + note( + "Default model set to google-antigravity/claude-opus-4-5", + "Model configured", + ); + } + } catch (err) { + spin.stop("Antigravity OAuth failed"); + runtime.error(String(err)); + } } else if (authChoice === "apiKey") { const key = guardCancel( await text({ diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index 23e35d018e..747a77212d 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; -import type { OAuthCredentials } from "@mariozechner/pi-ai"; +import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai"; import { discoverAuthStorage } from "@mariozechner/pi-coding-agent"; import { resolveClawdisAgentDir } from "../agents/agent-paths.js"; @@ -9,7 +9,7 @@ import type { ClawdisConfig } from "../config/config.js"; import { CONFIG_DIR } from "../utils.js"; export async function writeOAuthCredentials( - provider: "anthropic", + provider: OAuthProvider, creds: OAuthCredentials, ): Promise { const dir = path.join(CONFIG_DIR, "credentials"); diff --git a/src/commands/onboard-interactive.ts b/src/commands/onboard-interactive.ts index 4b14de7f38..bc3975b147 100644 --- a/src/commands/onboard-interactive.ts +++ b/src/commands/onboard-interactive.ts @@ -9,7 +9,11 @@ import { spinner, text, } from "@clack/prompts"; -import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai"; +import { + loginAnthropic, + loginAntigravity, + type OAuthCredentials, +} from "@mariozechner/pi-ai"; import type { ClawdisConfig } from "../config/config.js"; import { @@ -198,6 +202,10 @@ export async function runInteractiveOnboarding( message: "Model/auth choice", options: [ { value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" }, + { + value: "antigravity", + label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)", + }, { value: "apiKey", label: "Anthropic API key" }, { value: "minimax", label: "Minimax M2.1 (LM Studio)" }, { value: "skip", label: "Skip for now" }, @@ -239,6 +247,47 @@ export async function runInteractiveOnboarding( spin.stop("OAuth failed"); runtime.error(String(err)); } + } else if (authChoice === "antigravity") { + note( + [ + "Browser will open for Google authentication.", + "Sign in with your Google account that has Antigravity access.", + "The callback will be captured automatically on localhost:51121.", + ].join("\n"), + "Google Antigravity OAuth", + ); + const spin = spinner(); + spin.start("Starting OAuth flow…"); + let oauthCreds: OAuthCredentials | null = null; + try { + oauthCreds = await loginAntigravity( + async ({ url, instructions }) => { + spin.message(instructions ?? "Complete sign-in in browser…"); + await openUrl(url); + runtime.log(`Open: ${url}`); + }, + (msg) => spin.message(msg), + ); + spin.stop("Antigravity OAuth complete"); + if (oauthCreds) { + await writeOAuthCredentials("google-antigravity", oauthCreds); + // Set default model to Claude Opus 4.5 via Antigravity + nextConfig = { + ...nextConfig, + agent: { + ...nextConfig.agent, + model: "google-antigravity/claude-opus-4-5", + }, + }; + note( + "Default model set to google-antigravity/claude-opus-4-5", + "Model configured", + ); + } + } catch (err) { + spin.stop("Antigravity OAuth failed"); + runtime.error(String(err)); + } } else if (authChoice === "apiKey") { const key = guardCancel( await text({ diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 5baf8b0fad..312af24c1a 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -1,5 +1,5 @@ export type OnboardMode = "local" | "remote"; -export type AuthChoice = "oauth" | "apiKey" | "minimax" | "skip"; +export type AuthChoice = "oauth" | "antigravity" | "apiKey" | "minimax" | "skip"; export type GatewayAuthChoice = "off" | "token" | "password"; export type ResetScope = "config" | "config+creds+sessions" | "full"; export type GatewayBind = "loopback" | "lan" | "tailnet" | "auto";