import { Component, createSignal, onMount, For, Show } from 'solid-js' import { Rocket, CheckCircle, XCircle, Loader, Sparkles, Key, LogOut, Shield, Terminal, Copy, ExternalLink } from 'lucide-solid' import { getUserScopedKey } from '../../lib/user-storage' interface AntigravityModel { id: string name: string family?: string reasoning?: boolean tool_call?: boolean limit?: { context: number output: number } } interface AntigravityToken { access_token: string refresh_token?: string expires_in: number created_at: number } const ANTIGRAVITY_TOKEN_KEY = "antigravity_oauth_token" const AntigravitySettings: Component = () => { const [models, setModels] = createSignal([]) const [isLoading, setIsLoading] = createSignal(true) const [connectionStatus, setConnectionStatus] = createSignal<'idle' | 'testing' | 'connected' | 'failed'>('idle') const [authStatus, setAuthStatus] = createSignal<'unknown' | 'authenticated' | 'unauthenticated'>('unknown') const [error, setError] = createSignal(null) const [showTokenInput, setShowTokenInput] = createSignal(false) const [tokenInput, setTokenInput] = createSignal('') const [copied, setCopied] = createSignal(false) // Check stored token on mount onMount(async () => { checkAuthStatus() await loadModels() await testConnection() }) const getStoredToken = (): AntigravityToken | null => { if (typeof window === "undefined") return null try { const raw = window.localStorage.getItem(getUserScopedKey(ANTIGRAVITY_TOKEN_KEY)) if (!raw) return null return JSON.parse(raw) } catch { return null } } const isTokenValid = (token: AntigravityToken | null): boolean => { if (!token) return false const createdAt = token.created_at > 1e12 ? Math.floor(token.created_at / 1000) : token.created_at const expiresAt = (createdAt + token.expires_in) * 1000 - 300000 // 5 min buffer return Date.now() < expiresAt } const checkAuthStatus = () => { const token = getStoredToken() if (isTokenValid(token)) { setAuthStatus('authenticated') } else { setAuthStatus('unauthenticated') } } const loadModels = async () => { setIsLoading(true) try { const response = await fetch('/api/antigravity/models') if (response.ok) { const data = await response.json() setModels(data.models || []) setError(null) } else { throw new Error('Failed to load models') } } catch (err) { console.error('Failed to load Antigravity models:', err) setError('Failed to load models') } finally { setIsLoading(false) } } const testConnection = async () => { setConnectionStatus('testing') try { const response = await fetch('/api/antigravity/test') if (response.ok) { const data = await response.json() setConnectionStatus(data.connected ? 'connected' : 'failed') } else { setConnectionStatus('failed') } } catch (err) { setConnectionStatus('failed') } } const handleTokenSubmit = () => { const token = tokenInput().trim() if (!token) { setError('Please enter a valid token') return } try { const tokenData: AntigravityToken = { access_token: token, expires_in: 3600, // 1 hour default created_at: Date.now() } window.localStorage.setItem( getUserScopedKey(ANTIGRAVITY_TOKEN_KEY), JSON.stringify(tokenData) ) setAuthStatus('authenticated') setShowTokenInput(false) setTokenInput('') setError(null) loadModels() } catch (err) { setError('Failed to save token') } } const signOut = () => { window.localStorage.removeItem(getUserScopedKey(ANTIGRAVITY_TOKEN_KEY)) setAuthStatus('unauthenticated') } const copyCommand = async () => { const command = 'npx opencode-antigravity-auth login' await navigator.clipboard.writeText(command) setCopied(true) setTimeout(() => setCopied(false), 2000) } const formatNumber = (num: number): string => { if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M` if (num >= 1000) return `${(num / 1000).toFixed(0)}K` return num.toString() } const getModelFamily = (model: AntigravityModel): { label: string; color: string } => { if (model.id.startsWith('gemini')) return { label: 'Gemini', color: 'bg-blue-500/20 text-blue-400' } if (model.id.startsWith('claude')) return { label: 'Claude', color: 'bg-orange-500/20 text-orange-400' } if (model.id.startsWith('gpt')) return { label: 'GPT', color: 'bg-green-500/20 text-green-400' } return { label: model.family || 'Other', color: 'bg-zinc-700 text-zinc-400' } } return (
{/* Header */}

Antigravity

Premium models via Google authentication

{connectionStatus() === 'testing' && ( Testing... )} {connectionStatus() === 'connected' && ( Connected )} {connectionStatus() === 'failed' && ( Offline )}
{/* Info Banner */}

Premium AI Models

Antigravity provides access to Gemini 3 Pro/Flash, Claude Sonnet 4.5, Claude Opus 4.5, and GPT-OSS 120B through Google's infrastructure.

{/* Authentication Section */}

Authentication

{authStatus() === 'authenticated' ? 'Token configured - you can use Antigravity models' : 'Configure authentication to access premium models'}

Configured
{/* SDK Mode Instructions */}
Recommended: Use OpenCode SDK Mode

For the best experience, switch to OpenCode SDK mode in General settings. The Antigravity plugin handles authentication automatically via Google OAuth.

npx opencode-antigravity-auth login
{/* Manual Token Entry */}
setTokenInput(e.currentTarget.value)} placeholder="Paste your Google OAuth access token..." class="flex-1 px-3 py-2 bg-zinc-900 border border-zinc-700 rounded-lg text-sm text-white placeholder-zinc-500 focus:outline-none focus:border-purple-500" />

You can get an access token by running the Antigravity auth plugin in a terminal, or by extracting it from an authenticated Google Cloud session.

{/* Error Display */}
{error()}
{/* Models Grid */}

Available Models

Loading models...
0}>
{(model) => { const family = getModelFamily(model) return (

{model.name}

{model.id}

{family.label}
{model.reasoning && ( Thinking )} {model.tool_call && ( Tool Use )}
{model.limit && (
Context: {formatNumber(model.limit.context)} Output: {formatNumber(model.limit.output)}
)}
) }}

Models will be available after configuration.

{/* Usage Info */}

How to Use

  • SDK Mode (Recommended): Switch to OpenCode binary mode and install the antigravity auth plugin
  • Native Mode: Enter your access token manually above
  • • Select any Antigravity model from the model picker in chat
  • • Models include Gemini 3, Claude Sonnet/Opus 4.5, and GPT-OSS
  • • Thinking-enabled models show step-by-step reasoning
{/* Plugin Link */}
) } export default AntigravitySettings