From 5a01505abbf3a158cac503b6e93a03794c637a1d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 12 Sep 2025 22:18:22 +0000 Subject: [PATCH] fix: exclude undefined max token fields from settings export - Modified ProviderSettingsManager export method to exclude modelMaxTokens and modelMaxThinkingTokens when undefined - Prevents these fields from appearing in exported settings for models that do not support reasoning budgets - Added test to verify undefined fields are properly excluded from export Fixes #7944 --- src/core/config/ProviderSettingsManager.ts | 16 ++++- .../config/__tests__/importExport.spec.ts | 71 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 21a7a060c1..67cee1eb1a 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -471,7 +471,21 @@ export class ProviderSettingsManager { const configs = profiles.apiConfigs for (const name in configs) { // Avoid leaking properties from other providers. - configs[name] = discriminatedProviderSettingsWithIdSchema.parse(configs[name]) + let config = discriminatedProviderSettingsWithIdSchema.parse(configs[name]) + + // Remove max token fields if they are undefined + // These fields should only be included for models that support reasoning budgets + // and when the user has explicitly set values + // Use type assertion to access these optional fields + const configAny = config as any + if (configAny.modelMaxTokens === undefined) { + delete configAny.modelMaxTokens + } + if (configAny.modelMaxThinkingTokens === undefined) { + delete configAny.modelMaxThinkingTokens + } + + configs[name] = config } return profiles }) diff --git a/src/core/config/__tests__/importExport.spec.ts b/src/core/config/__tests__/importExport.spec.ts index 361d6b23b0..fd23f162f5 100644 --- a/src/core/config/__tests__/importExport.spec.ts +++ b/src/core/config/__tests__/importExport.spec.ts @@ -1608,5 +1608,76 @@ describe("importExport", () => { "https://custom-api.example.com/v1", ) }) + + it("should exclude undefined modelMaxTokens and modelMaxThinkingTokens from export", async () => { + // This test verifies that undefined max token fields are not included in the export + // to prevent them from appearing for models that don't support reasoning budgets + + ;(vscode.window.showSaveDialog as Mock).mockResolvedValue({ + fsPath: "/mock/path/roo-code-settings.json", + }) + + const mockProviderProfiles = { + currentApiConfigName: "test-provider", + apiConfigs: { + "test-provider": { + apiProvider: "openai" as ProviderName, + id: "test-id", + // modelMaxTokens and modelMaxThinkingTokens are undefined + apiKey: "test-key", + openAiBaseUrl: "https://api.openai.com/v1", + }, + "anthropic-provider": { + apiProvider: "anthropic" as ProviderName, + id: "anthropic-id", + apiKey: "anthropic-key", + modelMaxTokens: 4096, // This one has a value set + // modelMaxThinkingTokens is undefined + }, + "reasoning-provider": { + apiProvider: "openrouter" as ProviderName, + id: "reasoning-id", + openRouterApiKey: "reasoning-key", + modelMaxTokens: 8192, + modelMaxThinkingTokens: 4096, // Both values set for reasoning model + }, + }, + modeApiConfigs: {}, + } + + const mockGlobalSettings = { + mode: "code", + autoApprovalEnabled: true, + } + + mockProviderSettingsManager.export.mockResolvedValue(mockProviderProfiles) + mockContextProxy.export.mockResolvedValue(mockGlobalSettings) + ;(fs.mkdir as Mock).mockResolvedValue(undefined) + + await exportSettings({ + providerSettingsManager: mockProviderSettingsManager, + contextProxy: mockContextProxy, + }) + + // Get the exported data + const exportedData = (safeWriteJson as Mock).mock.calls[0][1] + + // Verify that undefined fields are not included in the export + const testProvider = exportedData.providerProfiles.apiConfigs["test-provider"] + expect(testProvider.apiKey).toBe("test-key") + expect(testProvider.openAiBaseUrl).toBe("https://api.openai.com/v1") + expect("modelMaxTokens" in testProvider).toBe(false) // Should not be present + expect("modelMaxThinkingTokens" in testProvider).toBe(false) // Should not be present + + // Verify that defined modelMaxTokens is included but undefined modelMaxThinkingTokens is not + const anthropicProvider = exportedData.providerProfiles.apiConfigs["anthropic-provider"] + expect(anthropicProvider.modelMaxTokens).toBe(4096) // Should be present with value + expect("modelMaxThinkingTokens" in anthropicProvider).toBe(false) // Should not be present + + // Verify that both fields are included when they have values + const reasoningProvider = exportedData.providerProfiles.apiConfigs["reasoning-provider"] + expect(reasoningProvider.modelMaxTokens).toBe(8192) // Should be present with value + expect(reasoningProvider.modelMaxThinkingTokens).toBe(4096) // Should be present with value + }) }) })