From a8ca2aa7d5ac0f194ce23e3a3f6f2f72daf58e58 Mon Sep 17 00:00:00 2001 From: "mirrobot-agent[bot]" <2140342+mirrobot-agent@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:42:06 +0000 Subject: [PATCH] feat(gemini): add manual project_id input when auto-discovery fails - Add _prompt_for_manual_setup() method to prompt for project_id and tier - Display link to manual setup documentation (docs/gemini-cli-manual-code-assist-setup.md) - Allow manual tier selection from known tiers list - Persist manually provided project_id and tier to credential file - Only triggers in interactive mode when auto-discovery fails after onboarding - Create placeholder documentation file for manual Code Assist setup instructions Resolves: #151 --- docs/gemini-cli-manual-code-assist-setup.md | 51 ++++++ .../providers/gemini_auth_base.py | 170 +++++++++++++++++- 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 docs/gemini-cli-manual-code-assist-setup.md diff --git a/docs/gemini-cli-manual-code-assist-setup.md b/docs/gemini-cli-manual-code-assist-setup.md new file mode 100644 index 00000000..4e8d0b4e --- /dev/null +++ b/docs/gemini-cli-manual-code-assist-setup.md @@ -0,0 +1,51 @@ +# Manual Google Cloud Code Assist Setup for Gemini CLI + +> **⚠️ PLACEHOLDER - To Be Completed by Mirrowel** +> +> This document will contain step-by-step instructions for manually enabling the Code Assist API in Google Cloud Console. + +## Overview + +Some Gemini accounts require manual creation of a Google Cloud Console project with the Code Assist API enabled. This guide will walk you through the process. + +## Prerequisites + +- A Google account with Gemini CLI access +- Access to Google Cloud Console + +## Steps + +### Step 1: Create a Google Cloud Project + +[Instructions to be added...] + +### Step 2: Enable the Code Assist API + +[Instructions to be added...] + +### Step 3: Configure API Permissions + +[Instructions to be added...] + +### Step 4: Obtain Your Project ID + +[Instructions to be added...] + +## Troubleshooting + +### Common Issues + +[To be added...] + +### Error Messages + +[To be added...] + +## Additional Resources + +- [Google Cloud Console](https://console.cloud.google.com/) +- [Gemini CLI Documentation](https://goo.gle/gemini-cli-auth-docs) + +--- + +**Note:** This document is a placeholder. Please check back later for complete instructions. diff --git a/src/rotator_library/providers/gemini_auth_base.py b/src/rotator_library/providers/gemini_auth_base.py index be0f27b3..5780b7db 100644 --- a/src/rotator_library/providers/gemini_auth_base.py +++ b/src/rotator_library/providers/gemini_auth_base.py @@ -31,6 +31,15 @@ # Service Usage API for checking enabled APIs SERVICE_USAGE_API = "https://serviceusage.googleapis.com/v1" +# Known Gemini CLI tiers for manual selection +KNOWN_GEMINI_TIERS = [ + "free-tier", + "legacy-tier", + "standard", + "premium", + "enterprise", +] + lib_logger = logging.getLogger("rotator_library") # Headers for Gemini CLI auth/discovery calls (loadCodeAssist, onboardUser, etc.) @@ -785,14 +794,173 @@ async def _discover_project_id( except httpx.RequestError as e: lib_logger.error(f"Network error while listing GCP projects: {e}") + # Auto-discovery failed - prompt for manual input (interactive mode only) + lib_logger.info( + "Auto-discovery failed. Checking if manual input is available..." + ) + + # Try to get project_id and tier from user input + try: + manual_result = await self._prompt_for_manual_setup() + if manual_result and manual_result.get("project_id"): + project_id = manual_result["project_id"] + tier = manual_result.get("tier") + + lib_logger.info( + f"Using manually provided project ID: {project_id}, tier: {tier}" + ) + + # Cache the values + self.project_id_cache[credential_path] = project_id + if tier: + self.project_tier_cache[credential_path] = tier + + # Persist to credential file + await self._persist_project_metadata(credential_path, project_id, tier) + + return project_id + except Exception as e: + lib_logger.debug(f"Manual input not available or failed: {e}") + + # If we get here, manual input was not available or failed raise ValueError( "Could not auto-discover Gemini project ID. Possible causes:\n" " 1. The cloudaicompanion.googleapis.com API is not enabled (enable it in Google Cloud Console)\n" " 2. No active GCP projects exist for this account (create one in Google Cloud Console)\n" " 3. Account lacks necessary permissions\n" - "To manually specify a project, set GEMINI_CLI_PROJECT_ID in your .env file." + "To manually specify a project, set GEMINI_CLI_PROJECT_ID in your .env file or provide it during interactive setup." + ) + + async def _prompt_for_manual_setup(self) -> Optional[Dict[str, str]]: + """ + Prompt user for manual project_id and tier selection. + + This is called when auto-discovery fails during interactive credential setup. + Only works in interactive mode (stdin available). + + Returns: + Dict with 'project_id' and optionally 'tier', or None if not available + """ + import sys + + # Check if we're in interactive mode + if not sys.stdin.isatty(): + lib_logger.debug("Not in interactive mode, skipping manual prompt") + return None + + # Import Rich for console output (deferred import to avoid circular dependency) + try: + from rich.console import Console + from rich.prompt import Prompt, Confirm + from rich.panel import Panel + from rich.text import Text + except ImportError: + lib_logger.debug("Rich not available for manual prompt") + return None + + console = Console() + + # Display instructions + console.print( + Panel( + Text.from_markup( + "[bold yellow]Automatic project discovery failed.[/bold yellow]\n\n" + "This usually means:\n" + "1. The Cloud AI Companion API is not enabled in your Google Cloud project\n" + "2. You need to manually create a project\n\n" + "[bold]Please follow the manual setup instructions:[/bold]\n" + "[cyan]docs/gemini-cli-manual-code-assist-setup.md[/cyan]\n\n" + "After following the instructions, enter your Project ID below." + ), + title="Manual Project Setup Required", + style="yellow", + ) + ) + + # Prompt for project_id + project_id = Prompt.ask( + "[bold]Enter your Google Cloud Project ID[/bold] [dim](or press Enter to skip)[/dim]", + default="", ) + if not project_id.strip(): + console.print( + "[dim]Skipping manual setup. You can set GEMINI_CLI_PROJECT_ID environment variable later.[/dim]" + ) + return None + + project_id = project_id.strip() + + # Prompt for tier selection + console.print("\n[bold]Select your tier:[/bold]") + console.print("[dim]If unsure, select 'free-tier' or 'legacy-tier'[/dim]\n") + + for i, tier in enumerate(KNOWN_GEMINI_TIERS, 1): + console.print(f" {i}. {tier}") + console.print(f" {len(KNOWN_GEMINI_TIERS) + 1}. Other (unknown)") + + tier_choice = Prompt.ask( + "\n[bold]Enter tier number[/bold]", + default="1", + ) + + try: + choice_idx = int(tier_choice) - 1 + if 0 <= choice_idx < len(KNOWN_GEMINI_TIERS): + selected_tier = KNOWN_GEMINI_TIERS[choice_idx] + else: + selected_tier = "unknown" + except ValueError: + selected_tier = "unknown" + + console.print( + Panel( + f"Project ID: [cyan]{project_id}[/cyan]\n" + f"Tier: [green]{selected_tier}[/green]", + style="green", + title="Manual Setup Complete", + ) + ) + + return {"project_id": project_id, "tier": selected_tier} + + async def _persist_project_metadata( + self, credential_path: str, project_id: str, tier: Optional[str] + ): + """Persists project ID and tier to the credential file for faster future startups.""" + # Skip persistence for env:// paths (environment-based credentials) + credential_index = self._parse_env_credential_path(credential_path) + if credential_index is not None: + lib_logger.debug( + f"Skipping project metadata persistence for env:// credential path: {credential_path}" + ) + return + + try: + # Load current credentials + with open(credential_path, "r") as f: + creds = json.load(f) + + # Update metadata + if "_proxy_metadata" not in creds: + creds["_proxy_metadata"] = {} + + creds["_proxy_metadata"]["project_id"] = project_id + if tier: + creds["_proxy_metadata"]["tier"] = tier + + # Save back using the existing save method (handles atomic writes and permissions) + await self._save_credentials(credential_path, creds) + + lib_logger.debug( + f"Persisted project_id and tier to credential file: {credential_path}" + ) + except Exception as e: + lib_logger.warning( + f"Failed to persist project metadata to credential file: {e}" + ) + # Non-fatal - just means slower startup next time + # ========================================================================= # CREDENTIAL MANAGEMENT OVERRIDES # =========================================================================