fix(llm-pi-ai): withhold OAuth-only providers from the configurable directory
pi-ai resolves an OAuth provider from a stored OAuth credential alone, and this adapter builds its Models collection with no credential store and runs no login flow. `openai-codex` — the one installed provider declaring `auth.oauth` with no `auth.apiKey` — was therefore offered on the Models page with the keyless placeholder every pi-ai route carries, and every request on it failed `Provider is not configured` before going out. `catalogProviderTakesApiKey()` answers whether pi-ai's installed provider for a route declares the one method this adapter can supply, and the directory skips the catalog routes that fail it. Catalog membership is unchanged, so `declared` still answers what pi-ai ships; the profile half of the union stays unconditional, so a route a settings document already names keeps its entry and can be edited or deleted. Resolution is untouched: a profile naming `apiKeyEnv` on such a route still builds a working provider.
This commit is contained in:
@@ -141,6 +141,26 @@ export function catalogProviderIds(): readonly string[] {
|
||||
return getBuiltinProviders()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the installed catalog provider for one route declares an api-key
|
||||
* method — the only authentication this adapter obtains on its own.
|
||||
*
|
||||
* A key is what the harness resolves through its own credential seam and hands
|
||||
* pi-ai per request. pi-ai's other method, OAuth, resolves from a *stored*
|
||||
* OAuth credential alone: `resolveProviderAuth` has no ambient path for it,
|
||||
* this adapter builds its `Models` collection with no credential store, and
|
||||
* nothing here runs a login flow. So a provider offering OAuth by itself
|
||||
* leaves nothing for this adapter to authenticate with, and the posture such a
|
||||
* provider invites — no key configured, credentials discovered by the provider
|
||||
* — fails every request with `Provider is not configured`.
|
||||
* @param provider - provider route key.
|
||||
* @returns whether the catalog provider takes an api key; false for a route
|
||||
* pi-ai does not ship, which the caller answers for separately.
|
||||
*/
|
||||
export function catalogProviderTakesApiKey(provider: string): boolean {
|
||||
return catalogProvider(provider)?.auth.apiKey !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* The installed catalog models for one route, indexed by model id.
|
||||
* @param provider - provider route key.
|
||||
|
||||
@@ -61,7 +61,7 @@ import { assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm'
|
||||
import type { AdapterRegistrationHandle, DirectoryRegistrationHandle, LlmConfigurableProvider } from '@deepseek-ai/dsh-llm'
|
||||
import { deepEqualJson, installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
import { PiAiAdapter } from './adapter.ts'
|
||||
import { catalogProviderIds } from './catalog.ts'
|
||||
import { catalogProviderIds, catalogProviderTakesApiKey } from './catalog.ts'
|
||||
import { assertServiceable, Config, resolveProfiles } from './config.ts'
|
||||
import type { ResolvedPiAiProviderProfile } from './config.ts'
|
||||
import { discoverModels } from './discovery.ts'
|
||||
@@ -105,10 +105,15 @@ function registrationFacts(profiles: ReadonlyMap<string, ResolvedPiAiProviderPro
|
||||
}
|
||||
|
||||
/**
|
||||
* The configurable-provider directory: every installed catalog route, plus
|
||||
* every route the current profiles declare. A hand-declared route has no
|
||||
* catalog entry, so without this union it would have no settings address and
|
||||
* configuration surfaces could neither show nor edit it.
|
||||
* The configurable-provider directory: every installed catalog route this
|
||||
* adapter can authenticate, plus every route the current profiles declare. A
|
||||
* hand-declared route has no catalog entry, so without this union it would
|
||||
* have no settings address and configuration surfaces could neither show nor
|
||||
* edit it.
|
||||
*
|
||||
* The profile half is unconditional, which is what keeps a route already
|
||||
* stored against a withheld provider editable and deletable rather than
|
||||
* stranded in the settings document with nothing on the page to remove it.
|
||||
* @param profiles - the currently resolved provider profiles.
|
||||
* @returns the directory entries in catalog order, declared routes last.
|
||||
*/
|
||||
@@ -129,7 +134,14 @@ function directoryEntries(
|
||||
declared: !catalog.has(provider),
|
||||
})
|
||||
}
|
||||
for (const provider of catalog) declare(provider, provider)
|
||||
// A provider whose only native method is OAuth leaves this adapter nothing
|
||||
// to authenticate with, so offering it would put a card on the settings page
|
||||
// whose own posture — no key, credentials discovered by the provider — fails
|
||||
// every request. Catalog *membership* is unaffected, so `declare` above still
|
||||
// answers what pi-ai ships.
|
||||
for (const provider of catalog) {
|
||||
if (catalogProviderTakesApiKey(provider)) declare(provider, provider)
|
||||
}
|
||||
for (const [provider, profile] of profiles) declare(provider, profile.displayName)
|
||||
return [...entries.values()]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user