fix(llm): let a catalog route keep the auth its provider actually declares

pi-ai resolves a request's apiKey override only through a provider that
declares an api-key method: resolveProviderAuth short-circuits to that
method when the override is present, and otherwise falls through to the
credential store and then to ambient discovery. A provider with no
api-key method at all therefore resolves to nothing, and the request
fails with "Provider is not configured" before any network I/O.

Two routes hit that. openai-codex ships OAuth alone, so moving off the
/compat dispatch broke a profile that names a key for it — the old path
handed the token straight to the provider. And a catalog route naming an
api was being rebuilt with the harness's own auth, so `openai: {api:
openai-completions}` stopped reading OPENAI_API_KEY, contradicting the
documented promise that omitting a credential keeps provider-native
discovery.

Auth is now one decision for both constructions. A catalog route keeps
its installed provider's auth, through an api override too: which
environment a provider reads belongs to the provider, not to the wire
format its models speak. A catalog provider with no api-key method gets
the harness method beside its own, but only when the profile names a
credential — a keyless codex profile keeps the honest refusal, since
this adapter holds no OAuth store to resolve through.

Materialization now spreads the installed entry instead of enumerating
the result, so a Model field this package does not model survives a
pi-ai upgrade; headers went missing from an nvidia route exactly that
way once already. providerInfo reports the configured displayName, which
also joins the registration facts so a rename re-registers rather than
leaving the old label in every selector. A refused registration swap
gets its own diagnostic naming the route, matching the directory swap
beside it.

The README documented endpoint interrogation this layer does not
implement, and still described unknown providers as kept-last-good after
they became legal declarations refused at the write point. The Agent
Note claimed per-model reasoning configurability the schema never had,
required capacities the route now defaults, and stated an apiKey
override that short-circuits unconditionally.
This commit is contained in:
Yichen Jiang
2026-08-04 16:01:20 +08:00
parent 236b1f6d97
commit 73fce861e5
20 changed files with 172 additions and 69 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/core-data-structures/settings.md
settings.md: 08f99b5e65ac4a57cdfff3323c0d9148d379bed8
settings.zh.md: 08903810b44c293944950a8f5ae2710f45678d60
settings.md: bd01c1d28407af9cab26f624a054a010e25a3ddd
settings.zh.md: 1cb7f8b507f29f2b6876fd48b4c37284df235e53

View File

@@ -37,9 +37,12 @@ interface SettingsRegisterOptions<T> {
* configuration surface renders and what an absent section resolves through;
* folding a cross-field check into it would change both.
*
* A stored section that fails this keeps the namespace's last good value and
* warns, exactly as a schema failure does, so an externally edited document
* can never strand the owner.
* Once the owner is registered, a stored section that fails this keeps the
* namespace's last good value and warns, exactly as a schema failure does,
* so an externally edited document cannot strand a running owner. At
* registration there is no last good value yet, so a stored section that
* already fails rejects the registration itself — again exactly as a schema
* failure does.
* @param value - the resolved section, schema-valid by construction.
*/
validate?: (value: T) => void

View File

@@ -37,9 +37,12 @@ interface SettingsRegisterOptions<T> {
* configuration surface renders and what an absent section resolves through;
* folding a cross-field check into it would change both.
*
* A stored section that fails this keeps the namespace's last good value and
* warns, exactly as a schema failure does, so an externally edited document
* can never strand the owner.
* Once the owner is registered, a stored section that fails this keeps the
* namespace's last good value and warns, exactly as a schema failure does,
* so an externally edited document cannot strand a running owner. At
* registration there is no last good value yet, so a stored section that
* already fails rejects the registration itself — again exactly as a schema
* failure does.
* @param value - the resolved section, schema-valid by construction.
*/
validate?: (value: T) => void