fix(providers): treat options.apiKey as a credential in credential signal
The previous review flagged that providerHasCredentials misclassifies working config-defined providers: Provider.key is only set by upstream when exactly one declared env var resolves or an api-type auth.json entry exists. Config providers only get options, so provider.<id>. options.apiKey never reaches key. Result: a provider whose key is embedded in opencode.json showed 'Credentials missing', lost the Models section, and forced the auth panel open. Add optionsApiKey to ProviderCredentialInput and check it in providerHasCredentials alongside key and authSourceExists. This matches what main's requiresProviderAuth helper used to do and honors the OpenChamber docs contract that options.apiKey counts as a usable login (walkthrough/DOCUMENTATION.md:134). Wire the new field through ProvidersPage.tsx using a typed indirection: the SDK Provider type does not yet expose options publicly, so the read site casts the object to the known shape. This keeps the call site type-safe without waiting for an SDK update.
This commit is contained in:
@@ -89,6 +89,15 @@ describe('provider credential state helpers', () => {
|
||||
expect(providerHasCredentials({ key: undefined, authSourceExists: true })).toBe(true);
|
||||
});
|
||||
|
||||
test('providerHasCredentials treats options.apiKey as a usable credential', () => {
|
||||
// Config-defined providers ship provider.options to the client but never
|
||||
// reach Provider.key, so the only authoritative signal is options.apiKey.
|
||||
expect(providerHasCredentials({ key: undefined, authSourceExists: false, optionsApiKey: 'sk-config' })).toBe(true);
|
||||
expect(providerHasCredentials({ key: undefined, authSourceExists: false, optionsApiKey: '' })).toBe(false);
|
||||
expect(providerHasCredentials({ key: undefined, authSourceExists: false, optionsApiKey: ' ' })).toBe(false);
|
||||
expect(providerHasCredentials({ key: undefined, authSourceExists: false, optionsApiKey: null })).toBe(false);
|
||||
});
|
||||
|
||||
test('env-less OAuth-only provider without credentials opens panel and hides models', () => {
|
||||
const hasCredentials = providerHasCredentials({
|
||||
key: undefined,
|
||||
|
||||
@@ -860,6 +860,7 @@ export const ProvidersPage: React.FC = () => {
|
||||
const hasCredentials = providerHasCredentials({
|
||||
key: selectedProvider.key,
|
||||
authSourceExists: selectedSources?.auth.exists,
|
||||
optionsApiKey: (selectedProvider as { options?: { apiKey?: string | null } }).options?.apiKey ?? null,
|
||||
});
|
||||
const authStatusIncomplete = sourcesLoaded && !hasCredentials;
|
||||
const showModelsSection = shouldShowModelsSection({
|
||||
|
||||
@@ -66,6 +66,14 @@ export interface ProviderCredentialInput {
|
||||
key?: string | null;
|
||||
/** OpenChamber auth.json provenance for this provider. */
|
||||
authSourceExists?: boolean | null;
|
||||
/**
|
||||
* Provider.options is shipped to the client for config-defined providers
|
||||
* but never reaches `Provider.key` (upstream only sets `key` from a single
|
||||
* resolved env var or an api-type auth.json entry). Treat a non-empty
|
||||
* `options.apiKey` as a usable login, per
|
||||
* `packages/web/server/lib/walkthrough/DOCUMENTATION.md:134`.
|
||||
*/
|
||||
optionsApiKey?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,6 +84,9 @@ export const providerHasCredentials = (input: ProviderCredentialInput): boolean
|
||||
if (typeof input.key === 'string' && input.key.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (typeof input.optionsApiKey === 'string' && input.optionsApiKey.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
return input.authSourceExists === true;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user