Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ uses [Semantic Versioning](https://semver.org/).

### Added

- **Google provider.** `google` (Gemini API key) and `google-vertex` (Vertex AI
with Google credentials: ADC or Workload Identity Federation, plus
`--google-project` and `--google-location`) run on Gemini without a base
URL. Agent loops use pydantic-ai's native `GoogleModel`, so Gemini 3 thought
signatures survive tool-call turns, and the cost summary prices Gemini runs
from genai-prices ([#20](https://github.com/FSoft-AI4Code/CodeWiki/issues/20)).
- **Rust analyzer.** Structs, unions, enums, traits, free functions, and
impl/trait methods become components, with trait-impl, field-type,
struct-literal, and call edges. `#[cfg(test)]` modules are skipped, and
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ codewiki config set --provider claude-code \
--main-model claude-sonnet-4-6 --cluster-model claude-sonnet-4-6
```

Anthropic, Azure OpenAI, AWS Bedrock, Atlas Cloud, and Codex are also
supported. See [Providers and models](./guides/providers.md).
Anthropic, Azure OpenAI, AWS Bedrock, Google (Gemini API and Vertex AI),
Atlas Cloud, and Codex are also supported. See [Providers and models](./guides/providers.md).

**3. Generate**

Expand Down
2 changes: 2 additions & 0 deletions codewiki/cli/adapters/doc_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def generate(self) -> DocumentationJob:
fallback_model=self.config.get("fallback_model"),
provider=self.config.get("provider", "openai-compatible"),
aws_region=self.config.get("aws_region", "us-east-1"),
google_project=self.config.get("google_project", ""),
google_location=self.config.get("google_location", ""),
max_tokens=self.config.get("max_tokens", 32768),
max_token_per_module=self.config.get("max_token_per_module", 36369),
max_token_per_leaf_module=self.config.get("max_token_per_leaf_module", 16000),
Expand Down
130 changes: 113 additions & 17 deletions codewiki/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,16 @@ def config_group():
"azure-openai",
"claude-code",
"codex",
"google",
"google-vertex",
],
case_sensitive=False,
),
help=(
"LLM provider type (default: openai-compatible). "
"Use 'atlas-cloud' for Atlas Cloud (base URL auto-set; reads ATLASCLOUD_API_KEY). "
"Use 'google' for the Gemini API (reads GEMINI_API_KEY / GOOGLE_API_KEY) or "
"'google-vertex' for Vertex AI with Google credentials (no API key). "
"Use 'claude-code' or 'codex' to run on a CLI subscription instead of an API key."
),
)
Expand All @@ -83,6 +87,17 @@ def config_group():
"--api-version", type=str, help="Azure OpenAI API version (default: 2024-12-01-preview)"
)
@click.option("--azure-deployment", type=str, help="Azure OpenAI deployment name")
@click.option(
"--google-project",
type=str,
help="Google Cloud project for google-vertex (default: GOOGLE_CLOUD_PROJECT)",
)
@click.option(
"--google-location",
type=str,
help="Vertex AI location for google-vertex, e.g. global, eu, us "
"(default: GOOGLE_CLOUD_LOCATION, else global)",
)
@click.option(
"--use-gitignore/--no-gitignore",
default=None,
Expand Down Expand Up @@ -110,6 +125,8 @@ def config_set(
aws_region: Optional[str] = None,
api_version: Optional[str] = None,
azure_deployment: Optional[str] = None,
google_project: Optional[str] = None,
google_location: Optional[str] = None,
use_gitignore: Optional[bool] = None,
prompt_caching: Optional[bool] = None,
):
Expand Down Expand Up @@ -142,6 +159,16 @@ def config_set(
# Subscription mode (Codex)
$ codewiki config set --provider codex --main-model gpt-5.2-codex

\b
# Vertex AI (Google credentials, no API key or base URL)
$ codewiki config set --provider google-vertex --google-project my-proj \\
--google-location eu --main-model gemini-3.8-flash --cluster-model gemini-3.8-flash

\b
# Gemini API (key read from GEMINI_API_KEY if --api-key is omitted)
$ codewiki config set --provider google --main-model gemini-3.8-flash \\
--cluster-model gemini-3.8-flash

\b
# Update only API key
$ codewiki config set --api-key sk-new-key
Expand Down Expand Up @@ -181,6 +208,8 @@ def config_set(
aws_region,
api_version,
azure_deployment,
google_project,
google_location,
use_gitignore is not None,
prompt_caching is not None,
]
Expand All @@ -199,6 +228,20 @@ def config_set(
if not api_key:
api_key = os.getenv("ATLASCLOUD_API_KEY")

manager = ConfigManager()
manager.load() # Load existing config if present

if provider and provider.lower() == "google" and not api_key:
api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
previous = manager.get_config()
if not api_key and not (previous and previous.provider == "google"):
# The keyring has one key slot: reusing it would send another
# provider's key to Google.
raise ConfigurationError(
"--provider google needs a Gemini API key: pass --api-key or set "
"GEMINI_API_KEY (or GOOGLE_API_KEY)."
)

# Validate inputs before saving
validated_data = {}

Expand Down Expand Up @@ -265,9 +308,11 @@ def config_set(
if prompt_caching is not None:
validated_data["prompt_caching"] = prompt_caching

# Create config manager and save
manager = ConfigManager()
manager.load() # Load existing config if present
if google_project is not None:
validated_data["google_project"] = google_project

if google_location is not None:
validated_data["google_location"] = google_location

manager.save(
api_key=validated_data.get("api_key"),
Expand All @@ -285,6 +330,8 @@ def config_set(
aws_region=validated_data.get("aws_region"),
api_version=validated_data.get("api_version"),
azure_deployment=validated_data.get("azure_deployment"),
google_project=validated_data.get("google_project"),
google_location=validated_data.get("google_location"),
use_gitignore=validated_data.get("use_gitignore"),
prompt_caching=validated_data.get("prompt_caching"),
)
Expand Down Expand Up @@ -352,6 +399,12 @@ def config_set(
if azure_deployment:
click.secho(f"✓ Azure Deployment: {azure_deployment}", fg="green")

if google_project:
click.secho(f"✓ Google project: {google_project}", fg="green")

if google_location:
click.secho(f"✓ Google location: {google_location}", fg="green")

if use_gitignore is not None:
click.secho(f"✓ Use gitignore: {use_gitignore}", fg="green")

Expand Down Expand Up @@ -405,6 +458,9 @@ def config_show(output_json: bool):
"api_key": mask_api_key(api_key) if api_key else "Not set",
"api_key_storage": "keychain" if manager.keyring_available else "encrypted_file",
"base_url": config.base_url if config else "",
"provider": config.provider if config else "",
"google_project": config.google_project if config else "",
"google_location": config.google_location if config else "",
"main_model": config.main_model if config else "",
"cluster_model": config.cluster_model if config else "",
"fallback_model": config.fallback_model if config else "glm-4p5",
Expand All @@ -430,12 +486,14 @@ def config_show(output_json: bool):
click.echo("━" * 40)
click.echo()

from codewiki.src.be.backend import is_caw_provider
from codewiki.src.be.backend import is_caw_provider, needs_base_url

caw_mode = bool(config) and is_caw_provider(config.provider)

click.secho("Credentials", fg="cyan", bold=True)
if caw_mode:
if config and config.provider == "google-vertex":
click.secho(" Google ADC / Workload Identity (no API key needed)", fg="cyan")
elif caw_mode:
cli_name = "claude" if config.provider == "claude-code" else "codex"
click.secho(
f" Subscription mode: authenticate via '{cli_name} login' (no API key needed)",
Expand All @@ -453,10 +511,18 @@ def config_show(output_json: bool):
click.echo(f" Provider: {config.provider}")
click.echo(f" Main Model: {config.main_model or 'Not set'}")
if not caw_mode:
click.echo(f" Base URL: {config.base_url or 'Not set'}")
if needs_base_url(config.provider):
click.echo(f" Base URL: {config.base_url or 'Not set'}")
click.echo(f" Cluster Model: {config.cluster_model or 'Not set'}")
click.echo(f" Fallback Model: {config.fallback_model or 'Not set'}")
if config.provider == "bedrock":
if config.provider == "google-vertex":
project = config.google_project or "from GOOGLE_CLOUD_PROJECT"
location = config.google_location or (
"from GOOGLE_CLOUD_LOCATION, else global"
)
click.echo(f" Google Project: {project}")
click.echo(f" Google Location: {location}")
elif config.provider == "bedrock":
click.echo(f" AWS Region: {config.aws_region}")
elif config.provider == "azure-openai":
click.echo(f" API Version: {config.api_version}")
Expand Down Expand Up @@ -565,20 +631,29 @@ def config_validate(quick: bool, verbose: bool):

# Load config early so we know the provider for the rest of the checks.
config = manager.get_config()
from codewiki.src.be.backend import is_caw_provider
from codewiki.src.be.backend import (
is_caw_provider,
is_google_provider,
needs_api_key,
needs_base_url,
)

caw_mode = bool(config) and is_caw_provider(config.provider)
key_needed = bool(config) and needs_api_key(config.provider)
url_needed = bool(config) and needs_base_url(config.provider)
api_key = None

# Step 2: Check API key (skipped for subscription providers)
# Step 2: Check API key (skipped for subscription providers and Vertex AI)
if verbose:
click.echo()
click.echo("[2/5] Checking API key...")

if caw_mode:
if not key_needed:
reason = "subscription mode" if caw_mode else "Google credentials"
if verbose:
click.secho(" ✓ API key not required (subscription mode)", fg="green")
click.secho(f" ✓ API key not required ({reason})", fg="green")
else:
click.secho("✓ API key not required (subscription mode)", fg="green")
click.secho(f"✓ API key not required ({reason})", fg="green")
else:
if verbose:
storage = "system keychain" if manager.keyring_available else "encrypted file"
Expand All @@ -597,16 +672,17 @@ def config_validate(quick: bool, verbose: bool):
else:
click.secho("✓ API key present (stored in keychain)", fg="green")

# Step 3: Check base URL (skipped for subscription providers)
# Step 3: Check base URL (skipped for subscription and Google providers)
if verbose:
click.echo()
click.echo("[3/5] Checking base URL...")

if caw_mode:
if not url_needed:
reason = "subscription mode" if caw_mode else "Google endpoint"
if verbose:
click.secho(" ✓ Base URL not required (subscription mode)", fg="green")
click.secho(f" ✓ Base URL not required ({reason})", fg="green")
else:
click.secho("✓ Base URL not required (subscription mode)", fg="green")
click.secho(f"✓ Base URL not required ({reason})", fg="green")
else:
if verbose:
click.echo(f" URL: {config.base_url}")
Expand Down Expand Up @@ -657,7 +733,7 @@ def config_validate(quick: bool, verbose: bool):
# Warn about non-top-tier cluster model
if not is_top_tier_model(config.cluster_model):
click.secho(
"⚠️ Cluster model is not top-tier. Consider using claude-sonnet-4 or gpt-4.",
"⚠️ Cluster model is not top-tier. Consider a Claude Sonnet/Opus, GPT-5 or Gemini 3 model.",
fg="yellow",
)

Expand Down Expand Up @@ -690,6 +766,26 @@ def config_validate(quick: bool, verbose: bool):
f"✓ {cli_name} CLI available (run '{cli_name} login' if not yet authenticated)",
fg="green",
)
elif is_google_provider(config.provider) and not quick:
if verbose:
click.echo()
click.echo("[5/5] Testing Google API access...")
try:
from codewiki.src.be import google_provider

google_provider.check_access(
config.provider,
api_key if key_needed else None,
config.main_model,
config.google_project,
config.google_location,
)
click.secho("✓ API connectivity test successful", fg="green")
except Exception as e:
click.secho("✗ API connectivity test failed", fg="red")
if verbose:
click.echo(f" Error: {e}")
sys.exit(EXIT_CONFIG_ERROR)
elif not quick:
if verbose:
click.echo()
Expand Down
2 changes: 2 additions & 0 deletions codewiki/cli/commands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,8 @@ def generate_command(
"api_key": api_key,
"provider": getattr(config, "provider", "openai-compatible"),
"aws_region": getattr(config, "aws_region", "us-east-1"),
"google_project": getattr(config, "google_project", ""),
"google_location": getattr(config, "google_location", ""),
"agent_instructions": agent_instructions_dict,
# Max token settings (runtime overrides take precedence)
"max_tokens": max_tokens if max_tokens is not None else config.max_tokens,
Expand Down
Loading
Loading