alishahryar1--free-claude-code
a0f62c598c
## Problem | Before | After | | --- | --- | | FCC supported Google AI Studio API keys but could not route coding agents through a Google Cloud Vertex AI project. | `vertex/...` routes through Google's [documented OpenAI-compatible Chat Completions endpoint](https://cloud.google.com/vertex-ai/generative-ai/docs/start/openai), using the global endpoint by default or an explicitly configured region. | | A pasted Vertex access token would expire, while Application Default Credentials were not part of provider construction. | FCC loads [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials), supplies a renewable credential callback to the OpenAI transport, coalesces concurrent refreshes, and returns typed authentication or transient failures. | | Vertex does not expose its model catalog through the compatible OpenAI `/models` route. | FCC translates its generic discovery operation to Google's paginated [publisher-model list API](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/publishers.models/list) and converts resource names into the model IDs accepted by Chat Completions. | | Google thought signatures were owned by the AI Studio adapter even though Vertex shares the same protocol behavior. | A neutral Google OpenAI family owns shared thought-signature and request behavior; AI Studio and Vertex retain separate endpoint and authentication ownership. | ## Changes - Added the Vertex provider, `VERTEX_PROJECT_ID`, optional `VERTEX_LOCATION` and `VERTEX_PROXY`, Admin UI configuration, model-picker discovery, smoke metadata, and customer setup documentation. - Added renewable ADC access tokens with refresh coalescing, proxy-aware refresh, sanitized failure classification, and project quota headers. - Added global/regional endpoint composition plus native model-catalog pagination, strict response validation, response cleanup, and repeated-page protection. - Generalized provider readiness around declared configuration fields so project-based and multi-field providers no longer pretend every remote provider is configured by one API key. - Moved shared Google request quirks out of the Gemini adapter, preserved AI Studio behavior, and bumped the package to `4.11.0`. <!-- greptile_comment --> <details open><summary><h3>Greptile Summary</h3></summary> This PR adds Google Vertex AI as a new provider using Application Default Credentials. The main changes are: - New `vertex` provider with project/location endpoint construction. - Renewable ADC access-token loading with refresh coalescing and proxy-aware refresh. - Native Vertex publisher-model discovery with pagination and response validation. - Shared Google OpenAI-compatible request behavior for Gemini and Vertex. - Admin UI, settings, smoke config, docs, version, lockfile, and tests for the new provider. </details> <h3>Confidence Score: 5/5</h3> Safe to merge with low risk. No blocking correctness or security issues were identified. The new provider follows the existing provider-runtime and Admin configuration patterns. Endpoint, auth, model parsing, readiness, docs, version, lockfile, and tests are updated together. No files require special attention. <details><summary><h3><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="20" align="absmiddle"></a> T-Rex Logs</h3></summary> **What T-Rex did** - The T-Rex test suite was executed to validate the code-execution proof-of-work, generating a full verbose pytest log and recording the run metadata, and the run completed with EXIT\_CODE: 0. <a href="https://app.greptile.com/trex/runs/14991235/artifacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img alt="View all artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a> <sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub> </details> <details open><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | src/free_claude_code/providers/vertex/client.py | Adds the Vertex provider with OpenAI-compatible chat routing and native paginated model discovery. | | src/free_claude_code/providers/vertex/auth.py | Implements renewable ADC token loading, proxy-aware refresh, coalescing, and sanitized auth failures. | | src/free_claude_code/providers/vertex/endpoint.py | Builds validated Vertex global/regional service, chat, and model-list endpoints. | | src/free_claude_code/providers/vertex/models.py | Parses Vertex publisher-model pages into OpenAI-compatible model IDs with malformed-response checks. | | src/free_claude_code/providers/google_openai/provider.py | Adds shared Google thought-signature caching and thinking-budget request body handling. | | src/free_claude_code/providers/google_openai/quirks.py | Renames Gemini-specific quirks to shared Google quirks and exposes model-neutral thinking config helpers. | | src/free_claude_code/providers/openai_chat/provider.py | Allows OpenAI-chat providers to pass an async API-key callback into the OpenAI SDK. | | src/free_claude_code/providers/runtime/discovery.py | Uses descriptor-defined readiness to choose providers eligible for model cache/discovery. | | src/free_claude_code/config/provider_catalog.py | Adds the Vertex descriptor and required settings metadata, and makes Cloudflare readiness require both token and account ID. | | src/free_claude_code/config/admin/status.py | Generalizes Admin provider readiness status to use each descriptor's configuration attributes. | | src/free_claude_code/config/admin/provider_manifest.py | Adds Admin UI fields for Vertex project and location alongside generated provider fields. | | tests/providers/test_vertex.py | Adds targeted tests for Vertex endpoints, ADC token refresh, reasoning mapping, and model discovery pagination. | </details> <details open><summary><h3>Sequence Diagram</h3></summary> <a href="#gh-light-mode-only"> ```mermaid %%{init: {'theme': 'neutral'}}%% sequenceDiagram participant User as User / Admin UI participant Settings as Settings + Provider Catalog participant Runtime as Provider Runtime participant Vertex as VertexProvider participant ADC as Google ADC participant OpenAI as OpenAI-compatible Chat Endpoint participant Models as Vertex Publisher Models API User->>Settings: Set VERTEX_PROJECT_ID / VERTEX_LOCATION / VERTEX_PROXY Settings->>Runtime: Descriptor reports vertex configured by project id Runtime->>Vertex: Construct with project, location, proxy, rate limiter Vertex->>ADC: Load/refresh Application Default Credentials ADC-->>Vertex: Renewable access token Vertex->>OpenAI: Stream chat completion with bearer token + x-goog-user-project OpenAI-->>Vertex: Streaming chat chunks Vertex-->>Runtime: Normalized provider stream Runtime->>Vertex: Refresh model list Vertex->>Models: GET paginated publishers/google/models Models-->>Vertex: publisherModels + nextPageToken Vertex-->>Runtime: Prefixed model IDs for cache/model picker ``` </a> <a href="#gh-dark-mode-only"> ```mermaid %%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant User as User / Admin UI participant Settings as Settings + Provider Catalog participant Runtime as Provider Runtime participant Vertex as VertexProvider participant ADC as Google ADC participant OpenAI as OpenAI-compatible Chat Endpoint participant Models as Vertex Publisher Models API User->>Settings: Set VERTEX_PROJECT_ID / VERTEX_LOCATION / VERTEX_PROXY Settings->>Runtime: Descriptor reports vertex configured by project id Runtime->>Vertex: Construct with project, location, proxy, rate limiter Vertex->>ADC: Load/refresh Application Default Credentials ADC-->>Vertex: Renewable access token Vertex->>OpenAI: Stream chat completion with bearer token + x-goog-user-project OpenAI-->>Vertex: Streaming chat chunks Vertex-->>Runtime: Normalized provider stream Runtime->>Vertex: Refresh model list Vertex->>Models: GET paginated publishers/google/models Models-->>Vertex: publisherModels + nextPageToken Vertex-->>Runtime: Prefixed model IDs for cache/model picker ``` </a> </details> <sub>Reviews (1): Last reviewed commit: ["feat: add Google Vertex AI provider"](https://github.com/alishahryar1/free-claude-code/commit/97e753f0772e60377865876ca59b2fd8888d922e) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=45405432)</sub> <!-- /greptile_comment -->
60 行
1.8 KiB
Python
60 行
1.8 KiB
Python
from dataclasses import FrozenInstanceError
|
|
|
|
import pytest
|
|
|
|
from free_claude_code.config.provider_catalog import (
|
|
PROVIDER_CATALOG,
|
|
ProviderDescriptor,
|
|
)
|
|
|
|
|
|
def test_provider_descriptors_are_immutable_values() -> None:
|
|
descriptor = ProviderDescriptor(
|
|
provider_id="local",
|
|
display_name="Local",
|
|
local=True,
|
|
)
|
|
|
|
assert descriptor.local is True
|
|
assert not hasattr(descriptor, "__dict__")
|
|
with pytest.raises(FrozenInstanceError):
|
|
descriptor.__setattr__("local", False)
|
|
|
|
|
|
def test_catalog_has_no_transport_metadata() -> None:
|
|
assert "transport_type" not in ProviderDescriptor.__slots__
|
|
assert "capabilities" not in ProviderDescriptor.__slots__
|
|
|
|
|
|
def test_catalog_local_assignments_are_exact() -> None:
|
|
assert {
|
|
provider_id
|
|
for provider_id, descriptor in PROVIDER_CATALOG.items()
|
|
if descriptor.local
|
|
} == {"lmstudio", "llamacpp", "ollama"}
|
|
|
|
|
|
def test_ollama_cloud_is_remote_and_distinct_from_local_ollama() -> None:
|
|
cloud = PROVIDER_CATALOG["ollama_cloud"]
|
|
local = PROVIDER_CATALOG["ollama"]
|
|
|
|
assert cloud.local is False
|
|
assert cloud.credential_env == "OLLAMA_API_KEY"
|
|
assert local.local is True
|
|
assert local.credential_env is None
|
|
|
|
|
|
def test_provider_configuration_attrs_cover_multi_field_and_adc_providers() -> None:
|
|
assert PROVIDER_CATALOG["cloudflare"].configuration_attrs() == (
|
|
"cloudflare_api_token",
|
|
"cloudflare_account_id",
|
|
)
|
|
assert PROVIDER_CATALOG["vertex"].configuration_attrs() == ("vertex_project_id",)
|
|
|
|
|
|
def test_every_provider_declares_its_configuration_boundary() -> None:
|
|
assert all(
|
|
descriptor.configuration_attrs() or descriptor.static_credential is not None
|
|
for descriptor in PROVIDER_CATALOG.values()
|
|
)
|