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:
@@ -96,6 +96,41 @@ export interface ProviderSpec {
|
||||
baseURL?: string
|
||||
/** The route's materialized models, in configuration order. */
|
||||
models: readonly Model<Api>[]
|
||||
/**
|
||||
* Whether the profile names a credential — a literal key or a reference.
|
||||
* Only that decides whether {@link routeAuth} adds the harness's own api-key
|
||||
* method to a catalog provider that offers none; the key itself still arrives
|
||||
* per request, never at construction.
|
||||
*/
|
||||
namesCredential: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The auth one route resolves its credential through.
|
||||
*
|
||||
* A catalog route keeps the installed provider's own auth, which is what
|
||||
* preserves provider-native ambient discovery for a profile naming no
|
||||
* credential. That holds even when the profile repoints the protocol: which
|
||||
* environment a provider reads is a property of the provider, not of the wire
|
||||
* format its models speak.
|
||||
*
|
||||
* The single addition covers a catalog provider that offers no api-key method
|
||||
* at all. pi-ai resolves a request's `apiKey` override only when the provider
|
||||
* declares one (`resolveProviderAuth` checks `provider.auth.apiKey` before
|
||||
* honouring the override), so an OAuth-only provider — `openai-codex` is the
|
||||
* one the installed catalog ships — would refuse a profile's explicit key with
|
||||
* `Provider is not configured` before any request went out. Adding the harness
|
||||
* method beside the provider's own restores that route. A keyless profile adds
|
||||
* nothing and still reports the honest refusal, because this adapter resolves
|
||||
* credentials through its own seam and holds no OAuth store to fall back on.
|
||||
* @param spec - the resolved route facts.
|
||||
* @param catalog - the installed catalog provider, when pi-ai ships one.
|
||||
* @returns the auth to construct this route's provider with.
|
||||
*/
|
||||
function routeAuth(spec: ProviderSpec, catalog: Provider | undefined): Provider['auth'] {
|
||||
if (catalog === undefined) return { apiKey: harnessApiKeyAuth(spec.displayName) }
|
||||
if (catalog.auth.apiKey !== undefined || !spec.namesCredential) return catalog.auth
|
||||
return { ...catalog.auth, apiKey: harnessApiKeyAuth(spec.displayName) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +148,7 @@ function reuseCatalogProvider(base: Provider, spec: ProviderSpec): Provider {
|
||||
id: spec.provider,
|
||||
name: spec.displayName,
|
||||
...baseUrl === undefined ? {} : { baseUrl },
|
||||
auth: base.auth,
|
||||
auth: routeAuth(spec, base),
|
||||
getModels: () => spec.models,
|
||||
// Delegated rather than copied: the catalog provider stays the receiver, so
|
||||
// an implementation holding state on itself keeps working.
|
||||
@@ -149,7 +184,7 @@ export function buildProvider(spec: ProviderSpec): Provider {
|
||||
id: spec.provider,
|
||||
name: spec.displayName,
|
||||
...spec.baseURL === undefined ? {} : { baseUrl: spec.baseURL },
|
||||
auth: { apiKey: harnessApiKeyAuth(spec.displayName) },
|
||||
auth: routeAuth(spec, catalog),
|
||||
models: spec.models,
|
||||
api: factory(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user