Add Google Gemini CLI and Antigravity OAuth providers

- Add google-gemini-cli provider: free Gemini 2.0/2.5 via Cloud Code Assist
- Add google-antigravity provider: free Gemini 3, Claude, GPT-OSS via sandbox
- Move OAuth infrastructure from coding-agent to ai package
- Fix thinking signature handling for cross-model handoff
- Fix OpenAI message ID length limit (max 64 chars)
- Add GitHub Copilot overflow pattern detection
- Add OAuth provider tests for context overflow and streaming
This commit is contained in:
Mario Zechner
2025-12-20 18:21:32 +01:00
parent 3266cac0f1
commit c359023c3f
25 changed files with 1392 additions and 413 deletions

View File

@@ -1,54 +1,70 @@
import { loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
import { loginGitHubCopilot, refreshGitHubCopilotToken } from "./github-copilot.js";
import { loginGoogleCloud, refreshGoogleCloudToken } from "./google-cloud.js";
/**
* OAuth management for coding-agent.
* Re-exports from @mariozechner/pi-ai and adds convenience wrappers.
*/
import {
listOAuthProviders as listOAuthProvidersFromStorage,
getOAuthApiKey,
listOAuthProviders as listOAuthProvidersFromAi,
loadOAuthCredentials,
loginAnthropic,
loginAntigravity,
loginGeminiCli,
loginGitHubCopilot,
type OAuthCredentials,
type OAuthProvider,
refreshToken as refreshTokenFromAi,
removeOAuthCredentials,
saveOAuthCredentials,
} from "./storage.js";
} from "@mariozechner/pi-ai";
// Re-export for convenience
export { listOAuthProvidersFromStorage as listOAuthProviders };
// Re-export types and functions
export type { OAuthCredentials, OAuthProvider };
export { listOAuthProvidersFromAi as listOAuthProviders };
export { getOAuthApiKey, loadOAuthCredentials, removeOAuthCredentials, saveOAuthCredentials };
export type SupportedOAuthProvider = "anthropic" | "github-copilot" | "google-cloud-code-assist";
export interface OAuthProviderInfo {
id: SupportedOAuthProvider;
name: string;
available: boolean;
}
export type OAuthPrompt = {
message: string;
placeholder?: string;
allowEmpty?: boolean;
};
export type OAuthAuthInfo = {
// Types for OAuth flow
export interface OAuthAuthInfo {
url: string;
instructions?: string;
}
export interface OAuthPrompt {
message: string;
placeholder?: string;
}
export type OAuthProviderInfo = {
id: OAuthProvider;
name: string;
description: string;
available: boolean;
};
/**
* Get list of OAuth providers
*/
export function getOAuthProviders(): OAuthProviderInfo[] {
return [
{
id: "anthropic",
name: "Anthropic (Claude Pro/Max)",
description: "Use Claude with your Pro/Max subscription",
available: true,
},
{
id: "github-copilot",
name: "GitHub Copilot",
description: "Use models via GitHub Copilot subscription",
available: true,
},
{
id: "google-cloud-code-assist",
name: "Google Cloud Code Assist (Gemini CLI)",
id: "google-gemini-cli",
name: "Google Gemini CLI",
description: "Free Gemini 2.0/2.5 models via Google Cloud",
available: true,
},
{
id: "google-antigravity",
name: "Antigravity",
description: "Free Gemini 3, Claude, GPT-OSS via Google Cloud",
available: true,
},
];
@@ -58,7 +74,7 @@ export function getOAuthProviders(): OAuthProviderInfo[] {
* Login with OAuth provider
*/
export async function login(
provider: SupportedOAuthProvider,
provider: OAuthProvider,
onAuth: (info: OAuthAuthInfo) => void,
onPrompt: (prompt: OAuthPrompt) => Promise<string>,
onProgress?: (message: string) => void,
@@ -79,8 +95,12 @@ export async function login(
saveOAuthCredentials("github-copilot", creds);
break;
}
case "google-cloud-code-assist": {
await loginGoogleCloud(onAuth, onProgress);
case "google-gemini-cli": {
await loginGeminiCli((info) => onAuth({ url: info.url, instructions: info.instructions }), onProgress);
break;
}
case "google-antigravity": {
await loginAntigravity((info) => onAuth({ url: info.url, instructions: info.instructions }), onProgress);
break;
}
default:
@@ -91,62 +111,21 @@ export async function login(
/**
* Logout from OAuth provider
*/
export async function logout(provider: SupportedOAuthProvider): Promise<void> {
export async function logout(provider: OAuthProvider): Promise<void> {
removeOAuthCredentials(provider);
}
/**
* Refresh OAuth token for provider
* Refresh OAuth token for provider.
* Delegates to the ai package implementation.
*/
export async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {
const credentials = loadOAuthCredentials(provider);
if (!credentials) {
throw new Error(`No OAuth credentials found for ${provider}`);
}
let newCredentials: OAuthCredentials;
switch (provider) {
case "anthropic":
newCredentials = await refreshAnthropicToken(credentials.refresh);
break;
case "github-copilot":
newCredentials = await refreshGitHubCopilotToken(credentials.refresh, credentials.enterpriseUrl);
break;
case "google-cloud-code-assist":
newCredentials = await refreshGoogleCloudToken(credentials.refresh, credentials.projectId);
break;
default:
throw new Error(`Unknown OAuth provider: ${provider}`);
}
// Save new credentials
saveOAuthCredentials(provider, newCredentials);
return newCredentials.access;
export async function refreshToken(provider: OAuthProvider): Promise<string> {
return refreshTokenFromAi(provider);
}
/**
* Get OAuth token for provider (auto-refreshes if expired)
* Get OAuth token for provider (auto-refreshes if expired).
*/
export async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {
const credentials = loadOAuthCredentials(provider);
if (!credentials) {
return null;
}
// Check if token is expired (with 5 min buffer already applied)
if (Date.now() >= credentials.expires) {
// Token expired - refresh it
try {
return await refreshToken(provider);
} catch (error) {
console.error(`Failed to refresh OAuth token for ${provider}:`, error);
// Remove invalid credentials
removeOAuthCredentials(provider);
return null;
}
}
return credentials.access;
export async function getOAuthToken(provider: OAuthProvider): Promise<string | null> {
return getOAuthApiKey(provider);
}