Make every direct-API-key provider a built-in route target - #1193
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exporting
DEEPSEEK_API_KEYmade DeepSeek a route target with nothing todeclare (spec 0179). Exporting
OPENAI_API_KEYdid not do the same forOpenAI — 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.
ANTHROPIC_API_KEYanthropicclaude-opus-4-8OPENAI_API_KEYopenaigpt-5GEMINI_API_KEY/GOOGLE_API_KEYgeminigemini-2.5-proMETA_API_KEY/MODEL_API_KEYmetamuse-spark-1.1GROK_API_KEY/XAI_API_KEYgrokgrok-4.5DEEPSEEK_API_KEYdeepseekdeepseek-v4-proWhat actually changed
The mechanism was already right —
SmithConfig::route_profiles()synthesizesan ordinary
ModelProfile, and nothing downstream knows built-ins exist. TheDeepSeek-shaped
ifbecomes aBUILTIN_TARGETStable; everything else aboutthe machinery is untouched.
Two things were genuinely missing:
provider_dialect()had nometaarm, so Meta wasthe 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/responsesand decodes the standardresponse.*events, which isexactly what the router's existing Responses translator emits. One arm.
grok:had no catalog rows. Onlygrok-oauth:was listed, so a directGrok route would have offered its default model and nothing else.
The key each built-in reports as
api_key_envis the one that actuallyresolved, 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:resolves to the same endpoint/key as the equivalent hand-declared profile
GOOGLE_API_KEY,XAI_API_KEY,MODEL_API_KEY) workand are reported as the resolved var
The env-guard helper now clears every built-in key env rather than just
DeepSeek's — otherwise a developer's exported
ANTHROPIC_API_KEYwould add aroute 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 toopenai-responsesathttps://api.meta.ai/v1/responseswith bearer auth. The"untranslatable provider" test now uses Ollama's native API, which is the
honest remaining example.
Not included
azure-openaistays declaration-only — its base URL is per-resource, whichis exactly the case spec 0179 says must not be a built-in.