feat(ai): sync model reads, explicit async refresh

Provider.getModels() is sync-only (last-known list; must not throw) with
an optional refreshModels() where dynamic providers fetch. The
sync-or-async union invited latent sync assumptions that would detonate
on the first dynamic provider; async-only reads would force sync
consumer surfaces (extension find/getAll) through Promises. Sync reads
plus an explicit refresh verb keeps the contract single and the
staleness visible.

Models.getModels()/getModel() are sync best-effort reads;
Models.refresh(provider?) rejects with ModelsError(model_source) for a
single provider and is concurrent best-effort across all providers.
createProvider() takes a models array plus an optional refreshModels
fetcher (stored on success, in-flight calls deduped, list unchanged on
rejection). forceRefresh options are gone.

Also finishes the in-progress AuthStorage fallbackResolver removal
(drops the now-unused includeFallback option from getApiKey).
This commit is contained in:
Mario Zechner
2026-06-10 23:30:04 +02:00
Unverified
parent 9ab1292679
commit 6e98573f24
10 changed files with 237 additions and 139 deletions
+1 -21
View File
@@ -198,7 +198,6 @@ export class InMemoryAuthStorageBackend implements AuthStorageBackend {
export class AuthStorage {
private data: AuthStorageData = {};
private runtimeOverrides: Map<string, string> = new Map();
private fallbackResolver?: (provider: string) => string | undefined;
private loadError: Error | null = null;
private errors: Error[] = [];
private storage: AuthStorageBackend;
@@ -237,14 +236,6 @@ export class AuthStorage {
this.runtimeOverrides.delete(provider);
}
/**
* Set a fallback resolver for API keys not found in auth.json or env vars.
* Used for custom provider keys from models.json.
*/
setFallbackResolver(resolver: (provider: string) => string | undefined): void {
this.fallbackResolver = resolver;
}
private recordError(error: unknown): void {
const normalizedError = error instanceof Error ? error : new Error(String(error));
this.errors.push(normalizedError);
@@ -341,7 +332,6 @@ export class AuthStorage {
if (this.runtimeOverrides.has(provider)) return true;
if (this.data[provider]) return true;
if (getEnvApiKey(provider)) return true;
if (this.fallbackResolver?.(provider)) return true;
return false;
}
@@ -362,10 +352,6 @@ export class AuthStorage {
return { configured: false, source: "environment", label: envKeys[0] };
}
if (this.fallbackResolver?.(provider)) {
return { configured: false, source: "fallback", label: "custom provider config" };
}
return { configured: false };
}
@@ -459,9 +445,8 @@ export class AuthStorage {
* 2. API key from auth.json
* 3. OAuth token from auth.json (auto-refreshed with locking)
* 4. Environment variable
* 5. Fallback resolver (models.json custom providers)
*/
async getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {
async getApiKey(providerId: string): Promise<string | undefined> {
// Runtime override takes highest priority
const runtimeKey = this.runtimeOverrides.get(providerId);
if (runtimeKey) {
@@ -516,11 +501,6 @@ export class AuthStorage {
const envKey = getEnvApiKey(providerId);
if (envKey) return envKey;
// Fall back to custom resolver (e.g., models.json custom providers)
if (options?.includeFallback !== false) {
return this.fallbackResolver?.(providerId) ?? undefined;
}
return undefined;
}
@@ -757,7 +757,7 @@ export class ModelRegistry {
async getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth> {
try {
const providerConfig = this.providerRequestConfigs.get(model.provider);
const apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider, { includeFallback: false });
const apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider);
const apiKey =
apiKeyFromAuthStorage ??
(providerConfig?.apiKey
@@ -844,7 +844,7 @@ export class ModelRegistry {
* Get API key for a provider.
*/
async getApiKeyForProvider(provider: string): Promise<string | undefined> {
const apiKey = await this.authStorage.getApiKey(provider, { includeFallback: false });
const apiKey = await this.authStorage.getApiKey(provider);
if (apiKey !== undefined) {
return apiKey;
}