diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 35ee6709a0a..212a673b953 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -1,7 +1,7 @@ import { ExtensionContext } from "vscode" import { z, ZodError } from "zod" -import { providerSettingsSchema, ApiConfigMeta } from "../../schemas" +import { providerSettingsSchema, ApiConfigMeta, ProviderSettings } from "../../schemas" import { Mode, modes } from "../../shared/modes" import { telemetryService } from "../../services/telemetry/TelemetryService" @@ -115,20 +115,15 @@ export class ProviderSettingsManager { } if (rateLimitSeconds === undefined) { - // Failed to get the existing value, use the default + // Failed to get the existing value, use the default. rateLimitSeconds = 0 } for (const [name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { if (apiConfig.rateLimitSeconds === undefined) { - console.log( - `[MigrateRateLimitSeconds] Applying rate limit ${rateLimitSeconds}s to profile: ${name}`, - ) apiConfig.rateLimitSeconds = rateLimitSeconds } } - - console.log(`[MigrateRateLimitSeconds] migration complete`) } catch (error) { console.error(`[MigrateRateLimitSeconds] Failed to migrate rate limit settings:`, error) } @@ -321,7 +316,31 @@ export class ProviderSettingsManager { private async load(): Promise { try { const content = await this.context.secrets.get(this.secretsKey) - return content ? providerProfilesSchema.parse(JSON.parse(content)) : this.defaultProviderProfiles + + if (!content) { + return this.defaultProviderProfiles + } + + const providerProfiles = providerProfilesSchema + .extend({ + apiConfigs: z.record(z.string(), z.any()), + }) + .parse(JSON.parse(content)) + + const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce( + (acc, [key, apiConfig]) => { + const result = providerSettingsWithIdSchema.safeParse(apiConfig) + return result.success ? { ...acc, [key]: result.data } : acc + }, + {} as Record, + ) + + return { + ...providerProfiles, + apiConfigs: Object.fromEntries( + Object.entries(apiConfigs).filter(([_, apiConfig]) => apiConfig !== null), + ), + } } catch (error) { if (error instanceof ZodError) { telemetryService.captureSchemaValidationError({ schemaName: "ProviderProfiles", error }) diff --git a/src/core/config/__tests__/ProviderSettingsManager.test.ts b/src/core/config/__tests__/ProviderSettingsManager.test.ts index b1a85075464..91f5adbdf92 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.test.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.test.ts @@ -437,6 +437,45 @@ describe("ProviderSettingsManager", () => { "Failed to load config: Error: Failed to write provider profiles to secrets: Error: Storage failed", ) }) + + it("should remove invalid profiles during load", async () => { + const invalidConfig = { + currentApiConfigName: "valid", + apiConfigs: { + valid: { + apiProvider: "anthropic", + apiKey: "valid-key", + apiModelId: "claude-3-opus-20240229", + rateLimitSeconds: 0, + }, + invalid: { + // Invalid API provider. + id: "x.ai", + apiProvider: "x.ai", + }, + // Incorrect type. + anotherInvalid: "not an object", + }, + migrations: { + rateLimitSecondsMigrated: true, + }, + } + + mockSecrets.get.mockResolvedValue(JSON.stringify(invalidConfig)) + + await providerSettingsManager.initialize() + + const storeCalls = mockSecrets.store.mock.calls + expect(storeCalls.length).toBeGreaterThan(0) // Ensure store was called at least once. + const finalStoredConfigJson = storeCalls[storeCalls.length - 1][1] + + const storedConfig = JSON.parse(finalStoredConfigJson) + expect(storedConfig.apiConfigs.valid).toBeDefined() + expect(storedConfig.apiConfigs.invalid).toBeUndefined() + expect(storedConfig.apiConfigs.anotherInvalid).toBeUndefined() + expect(Object.keys(storedConfig.apiConfigs)).toEqual(["valid"]) + expect(storedConfig.currentApiConfigName).toBe("valid") + }) }) describe("ResetAllConfigs", () => {