fix(models): list env-credentialed providers in /v1/models - #388
Conversation
Completion routing works for any any-llm provider once its native
credential env var is set (get_provider_kwargs returns {} for a provider
absent from config.providers, so any-llm reads the env var directly), but
model discovery only queried config.providers. Setting only
ANTHROPIC_API_KEY therefore made Anthropic completions work while its
models never appeared in GET /v1/models, so the two surfaces silently
disagreed on what a configured provider is.
Discovery now also picks up any-llm providers made callable by an env var
alone: a provider whose credential env var is present and that supports
live model listing is treated as discoverable under its implementation
name, the same selector routing resolves. This keeps GET /v1/models and
GET /v1/models/discoverable consistent with what completions can reach.
Also restore os.environ around each test in test_config_env_loading, whose
python-dotenv loads leaked provider credentials into the process and, now
that discovery is env-aware, into unrelated tests.
Fixes #221
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 36 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A blank provider credential env var (for example ANTHROPIC_API_KEY=, common from container templating) was counted as present, so the provider was added to discovery only to fail authentication, surfacing a spurious error row in GET /v1/models/discoverable. Require a non-empty value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes a mismatch between completion routing and model discovery by treating any-llm providers as “discoverable” when their native credential environment variable is set, even if they are not explicitly listed under config.providers. This makes /v1/models (and the operator-facing discovery/status endpoints) align with what completions can actually reach.
Changes:
- Extend model discovery to include env-credentialed any-llm providers (when they support live model listing) and apply this consistently to
/v1/models,/v1/models/discoverable, and provider filtering. - Add unit and integration regression tests covering env-only provider discovery (issue #221) and harden env isolation in config env-loading tests.
- Update documentation to reflect that env-only credentials affect both routing and model discovery.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/gateway/services/model_discovery_service.py |
Adds env-var-based provider detection and uses it to build the discoverable instance list for discovery and status surfaces. |
tests/unit/test_model_discovery_env_providers.py |
New unit tests validating env-only provider detection, ordering, filtering, and status inclusion. |
tests/unit/test_gateway_model_discovery.py |
Pins existing discovery tests to configured providers by stubbing env-provider detection. |
tests/integration/test_model_discovery.py |
Adds an integration regression test asserting env-only providers appear in GET /v1/models. |
tests/integration/test_config_env_loading.py |
Restores os.environ after each test to prevent dotenv-loaded provider creds from leaking across tests. |
docs/models.md |
Documents that env-only provider credentials affect both routing and discovery. |
docs/configuration.md |
Updates model_discovery setting description to include env-only discoverable providers. |
There was a problem hiding this comment.
Approving. Detailed points are inline (see the follow-up review). Themes:
_env_provider_instancesdedups by instance name, not implementation — a configured instance plus its matching credential env var can double-discover the same provider.AnyLLM.get_all_provider_metadata()is the one unguarded external call in an otherwise defensive module; a raise there 500s/v1/models.- Env edge cases (slash-alternatives, compound,
"None", blank) verified correct; no new capability exposed. Tests are solid bar the same-implementation duplication case.
There was a problem hiding this comment.
Follow-up: converting the earlier prose review into inline comments; see threads on model_discovery_service.py and the test files. No blockers.
Retraction: my earlier cross-PR concern (that this PR's Railway/PaaS promotion collides with a Secure-cookie downgrade in #384) was based on a misread of #384 against main. #384 honors X-Forwarded-Proto via request_is_https(), so the proxy case is handled and the concern does not apply. Withdrawn.
Address review on #388. Deduplicate env-based discovery against the implementations the configured instances resolve to, not their instance names. A custom instance such as my-anthropic (provider_type: anthropic) alongside ANTHROPIC_API_KEY would otherwise also yield a bare "anthropic", dialing the same credential twice and listing the same models under two keys. Guard AnyLLM.get_all_provider_metadata() so a registry hiccup falls back to configured-only discovery instead of 500-ing GET /v1/models and /v1/models/discoverable, matching the module's defensive posture. Document that keyless local providers (ollama/llamacpp/llamafile) have no credential signal to detect and stay callable-but-undiscoverable, tracked as a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Copilot review on #388. Strip the credential env var value before the truthiness check so a whitespace-only value (as unusable as a blank one, and common from container templating) does not mark a provider discoverable only for it to fail authentication. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lla-ai#384) * fix(dashboard): persist sign-in with an HttpOnly session cookie The dashboard forced operators to re-paste the master key on every new tab, closed tab, or browser restart, because the raw key lived in per-tab sessionStorage. Signing in now exchanges the key once (POST /v1/auth/session) for a server-issued opaque session held in an HttpOnly cookie, which is both more convenient (survives tabs and restarts) and more secure (no JS-readable credential, TTL, server-side revocation). Backend: - dashboard_sessions table stores only SHA-256 token hashes, so every worker and replica accepts a session and revocation is global. - POST /v1/auth/session verifies the master key and sets the cookie (HttpOnly; SameSite=Strict; Secure mirrors the request scheme so plain-HTTP LAN deployments keep working); DELETE signs out. - The master-key auth dependencies accept the cookie only when a request carries no header credentials, so API clients are untouched. A Sec-Fetch-Site check backs up SameSite as CSRF defense in depth. - Master-key rotation revokes every session and re-mints the caller's cookie on the response, so the rotating tab stays signed in. - Session TTL is configurable via dashboard_session_ttl_hours (default 7 days). Frontend: - The raw key is sent once to mint the session and never stored; a non-secret localStorage marker keeps the signed-in state synchronous, and any 401 drops it exactly like a mid-session revocation. - Sign-out revokes the session server-side before clearing local state. Fixes mozilla-ai#338 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): revoke sessions when the master key changes at startup Two follow-ups from self-review of the session-cookie change: - A dashboard session only proves possession of the master key at mint time, so it must not outlive the key. The generated key already revoked sessions inline at rotation, but a configured key rotates by changing OTARI_MASTER_KEY and restarting, which no request handler observes; pre-rotation sessions survived until their TTL. Store a hash of the effective key in runtime_settings and revoke every session at startup when it changes (covering configured-key rotation and configured/generated regime switches). The rotation endpoint keeps the marker in step so its re-minted session survives restarts. - Regenerate the Postman collection from the updated OpenAPI spec (make postman), which the openapi-spec CI job checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): keep sign-out best-effort when revocation fails Raising a 500 on a DB failure during DELETE /v1/auth/session made FastAPI discard the injected response, so the cookie was never cleared while the frontend had already dropped its local marker: the browser kept a live session cookie the operator believed was gone. Sign-out now always clears the cookie and returns 204, logging the failed revocation; the unrevoked row dies on its TTL. Flagged by Copilot review on mozilla-ai#384. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): honor X-Forwarded-Proto for the session cookie Secure flag Behind a TLS-terminating proxy, uvicorn only trusts X-Forwarded-Proto from loopback by default, so request.url.scheme reads "http" on typical PaaS ingress and the session cookie was minted without Secure despite an HTTPS browser leg. Decide the Secure attribute from the effective scheme (direct https, or the first X-Forwarded-Proto value). Honoring the header regardless of source is safe for this decision alone: a spoofed "https" over plain HTTP only denies the spoofer their own cookie. Also covers the Secure=true branch in tests (direct HTTPS and forwarded proto), and documents two review-noted asymmetries: DELETE skipping the Sec-Fetch-Site check (SameSite already covers it; worst case is a forced sign-out) and Path=/ (management routes live beside inference under /v1, and the cookie grants nothing beyond master authority). Addresses khaledosman's review on mozilla-ai#384. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dashboard): log sign-in session persistence failures The create_session 500 branch discarded the SQLAlchemyError without a trace, unlike the parallel branch in delete_session; a sign-in outage would have been invisible in the logs. Flagged by CodeRabbit on mozilla-ai#384. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(dashboard): regenerate bundle and API artifacts after rebase Rebuild the committed dashboard bundle, OpenAPI spec, and Postman collection from the tree rebased onto main, folding in main's dashboard and API changes (mozilla-ai#383, mozilla-ai#385, mozilla-ai#388). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Description
/v1/modelsdiscovery and completion routing disagreed on what a "configured provider" is. Completions work for any any-llm provider once its native credential env var is set (get_provider_kwargsreturns{}for a provider absent fromconfig.providers, so any-llm reads the env var directly), but model discovery only queriedconfig.providers. Setting onlyANTHROPIC_API_KEYtherefore made Anthropic completions work while its models never appeared inGET /v1/models, the exact quick path the Railway template recommends.Discovery now also picks up any-llm providers made callable by an env var alone: a provider whose credential env var is present and that supports live model listing is treated as discoverable under its implementation name (the same
provider:modelselector routing resolves), unless it is already a configured instance. This is wired into bothGET /v1/modelsandGET /v1/models/discoverable(and the?provider=filter), so the two surfaces agree with what completions can reach. Provider-health and the per-instance connection test are untouched; they operate on explicit or configured instances.Also restores
os.environaround each test intest_config_env_loading, whose python-dotenv loads leaked provider credentials into the process. That was harmless before, but now that discovery is env-aware it polluted an unrelated test; a pre-existing isolation gap the change surfaced.PR Type
Relevant issues
Fixes #221
Checklist
tests/unit,tests/integration).make lint,make typecheck,make test).--checkconfirms the spec is up to date).AI Usage
AI Model/Tool used:
Claude Code (Opus 4.8), driven by @njbrake.
Any additional AI details you'd like to share:
Implemented and tested end to end by Claude via back-and-forth with @njbrake; the reasoning and decisions are his.
NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)