Skip to content

Make every direct-API-key provider a built-in route target - #1193

Merged
edwin-zvs merged 1 commit into
mainfrom
builtin-route-targets-all-providers
Aug 3, 2026
Merged

Make every direct-API-key provider a built-in route target#1193
edwin-zvs merged 1 commit into
mainfrom
builtin-route-targets-all-providers

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Exporting DEEPSEEK_API_KEY made DeepSeek a route target with nothing to
declare (spec 0179). Exporting OPENAI_API_KEY did not do the same for
OpenAI — it made smith work immediately and silently did nothing for the
route pickers. This generalizes the built-in to every direct-API-key
provider Construct speaks to.

Key Route Default model
ANTHROPIC_API_KEY anthropic claude-opus-4-8
OPENAI_API_KEY openai gpt-5
GEMINI_API_KEY / GOOGLE_API_KEY gemini gemini-2.5-pro
META_API_KEY / MODEL_API_KEY meta muse-spark-1.1
GROK_API_KEY / XAI_API_KEY grok grok-4.5
DEEPSEEK_API_KEY deepseek deepseek-v4-pro

What actually changed

The mechanism was already right — SmithConfig::route_profiles() synthesizes
an ordinary ModelProfile, and nothing downstream knows built-ins exist. The
DeepSeek-shaped if becomes a BUILTIN_TARGETS table; everything else about
the machinery is untouched.

Two things were genuinely missing:

  • Meta had no dialect. provider_dialect() had no meta arm, so Meta was
    the one built-in-eligible provider that would have been listed as
    permanently unusable ("no translator for provider "meta""). Meta serves
    Muse Spark over the OpenAI Responses API — smith's own Meta client posts
    to /v1/responses and decodes the standard response.* events, which is
    exactly what the router's existing Responses translator emits. One arm.
  • grok: had no catalog rows. Only grok-oauth: was listed, so a direct
    Grok route would have offered its default model and nothing else.

The key each built-in reports as api_key_env is the one that actually
resolved, so a later blocker names the variable the user really set rather
than the first alias in the list.

Behavior change

This is the retrofit spec 0179 anticipated and asked to be done deliberately
per provider: pickers that were empty on machines with an API key will now
list entries. Each provider here clears both bars the spec sets — one public
endpoint, and a translator that exists — and there is now a test asserting
that for every row in the table, so a future entry cannot be added that is
listed but unselectable.

Providers reachable both ways (Claude, Codex, Grok) now show the API-key
target and the subscription login side by side. That is intended: different
billing paths, user picks which one to spend.

Unchanged: declared profiles still win the name outright, an absent key still
means an absent target (never "listed but blocked"), and the key still has to
be in the daemon's environment or [daemon.env].

Tests

New in config.rs, all table-driven so they cover future entries too:

  • every built-in is routable and self-consistent — has a translator, and
    resolves to the same endpoint/key as the equivalent hand-declared profile
  • built-in route names are unique
  • each provider becomes a target from its key alone, and only it does
  • alternate key envs (GOOGLE_API_KEY, XAI_API_KEY, MODEL_API_KEY) work
    and are reported as the resolved var
  • a declared profile beats the built-in, for any of them

The env-guard helper now clears every built-in key env rather than just
DeepSeek's — otherwise a developer's exported ANTHROPIC_API_KEY would add a
route target the fixture never asked for and the count assertions would pass
or fail depending on whose machine ran them.

In router.rs: Meta profiles resolve to openai-responses at
https://api.meta.ai/v1/responses with bearer auth. The
"untranslatable provider" test now uses Ollama's native API, which is the
honest remaining example.

Not included

azure-openai stays declaration-only — its base URL is per-resource, which
is exactly the case spec 0179 says must not be a built-in.

Exporting DEEPSEEK_API_KEY made DeepSeek a route target with nothing to
declare (spec 0179). Exporting OPENAI_API_KEY did not do the same for
OpenAI: it made smith work immediately and silently did nothing for the
route pickers, so "I set the key" and "I can route to it" came apart with
no signal explaining why — the exact asymmetry 0179 was written to remove.

Generalize the built-in to every direct-API-key provider: anthropic,
openai, gemini, meta, grok, and deepseek. The mechanism was already right
— route_profiles() synthesizes an ordinary profile and nothing downstream
knows built-ins exist — so the DeepSeek-shaped `if` becomes a
BUILTIN_TARGETS table and the machinery is untouched.

Two things were genuinely missing:

  - Meta had no dialect. provider_dialect() had no `meta` arm, so Meta was
    the one built-in-eligible provider that would have been listed as
    permanently unusable. Meta serves Muse Spark over the OpenAI Responses
    API — smith's own Meta client posts to /v1/responses and decodes the
    standard response.* events, which is what the router's existing
    Responses translator emits.
  - `grok:` had no rows in the shared model catalog, so a direct Grok route
    would have offered its default model and nothing else.

The key a built-in reports as api_key_env is the one that actually
resolved, so a later blocker names the variable the user really set rather
than the first alias in the list.

This is the retrofit 0179 anticipated and asked to be done deliberately
per provider: pickers that were empty on machines with an API key will now
list entries. Each provider clears both bars the spec sets — one public
endpoint, and a translator that exists — and a test now asserts that for
every row, so an entry cannot be added that is listed but unselectable.
azure-openai stays declaration-only: its base URL is per-resource.

The config test env guard now clears every built-in key env rather than
just DeepSeek's. Otherwise a developer's exported ANTHROPIC_API_KEY adds a
route target the fixture never asked for, and the count assertions pass or
fail depending on whose machine ran them.
@edwin-zvs
edwin-zvs merged commit 6034ade into main Aug 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant