Browse Source

Merge pull request #1235 from samhvw8/feat/context-proxy

Feat ContextProxy to improve state management
Matt Rubens 10 months ago
parent
commit
fcf09260b8

+ 243 - 0
src/core/__tests__/contextProxy.test.ts

@@ -0,0 +1,243 @@
+import * as vscode from "vscode"
+import { ContextProxy } from "../contextProxy"
+import { logger } from "../../utils/logging"
+import { GLOBAL_STATE_KEYS, SECRET_KEYS } from "../../shared/globalState"
+import { ApiConfiguration } from "../../shared/api"
+
+// Mock shared/globalState
+jest.mock("../../shared/globalState", () => ({
+	GLOBAL_STATE_KEYS: ["apiProvider", "apiModelId", "mode"],
+	SECRET_KEYS: ["apiKey", "openAiApiKey"],
+	GlobalStateKey: {},
+	SecretKey: {},
+}))
+
+// Mock shared/api
+jest.mock("../../shared/api", () => ({
+	API_CONFIG_KEYS: ["apiProvider", "apiModelId"],
+	ApiConfiguration: {},
+}))
+
+// Mock VSCode API
+jest.mock("vscode", () => ({
+	Uri: {
+		file: jest.fn((path) => ({ path })),
+	},
+	ExtensionMode: {
+		Development: 1,
+		Production: 2,
+		Test: 3,
+	},
+}))
+
+describe("ContextProxy", () => {
+	let proxy: ContextProxy
+	let mockContext: any
+	let mockGlobalState: any
+	let mockSecrets: any
+
+	beforeEach(() => {
+		// Reset mocks
+		jest.clearAllMocks()
+
+		// Mock globalState
+		mockGlobalState = {
+			get: jest.fn(),
+			update: jest.fn().mockResolvedValue(undefined),
+		}
+
+		// Mock secrets
+		mockSecrets = {
+			get: jest.fn().mockResolvedValue("test-secret"),
+			store: jest.fn().mockResolvedValue(undefined),
+			delete: jest.fn().mockResolvedValue(undefined),
+		}
+
+		// Mock the extension context
+		mockContext = {
+			globalState: mockGlobalState,
+			secrets: mockSecrets,
+			extensionUri: { path: "/test/extension" },
+			extensionPath: "/test/extension",
+			globalStorageUri: { path: "/test/storage" },
+			logUri: { path: "/test/logs" },
+			extension: { packageJSON: { version: "1.0.0" } },
+			extensionMode: vscode.ExtensionMode.Development,
+		}
+
+		// Create proxy instance
+		proxy = new ContextProxy(mockContext)
+	})
+
+	describe("read-only pass-through properties", () => {
+		it("should return extension properties from the original context", () => {
+			expect(proxy.extensionUri).toBe(mockContext.extensionUri)
+			expect(proxy.extensionPath).toBe(mockContext.extensionPath)
+			expect(proxy.globalStorageUri).toBe(mockContext.globalStorageUri)
+			expect(proxy.logUri).toBe(mockContext.logUri)
+			expect(proxy.extension).toBe(mockContext.extension)
+			expect(proxy.extensionMode).toBe(mockContext.extensionMode)
+		})
+	})
+
+	describe("constructor", () => {
+		it("should initialize state cache with all global state keys", () => {
+			expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length)
+			for (const key of GLOBAL_STATE_KEYS) {
+				expect(mockGlobalState.get).toHaveBeenCalledWith(key)
+			}
+		})
+
+		it("should initialize secret cache with all secret keys", () => {
+			expect(mockSecrets.get).toHaveBeenCalledTimes(SECRET_KEYS.length)
+			for (const key of SECRET_KEYS) {
+				expect(mockSecrets.get).toHaveBeenCalledWith(key)
+			}
+		})
+	})
+
+	describe("getGlobalState", () => {
+		it("should return value from cache when it exists", async () => {
+			// Manually set a value in the cache
+			await proxy.updateGlobalState("test-key", "cached-value")
+
+			// Should return the cached value
+			const result = proxy.getGlobalState("test-key")
+			expect(result).toBe("cached-value")
+
+			// Original context should be called once during updateGlobalState
+			expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length) // Only from initialization
+		})
+
+		it("should handle default values correctly", async () => {
+			// No value in cache
+			const result = proxy.getGlobalState("unknown-key", "default-value")
+			expect(result).toBe("default-value")
+		})
+	})
+
+	describe("updateGlobalState", () => {
+		it("should update state directly in original context", async () => {
+			await proxy.updateGlobalState("test-key", "new-value")
+
+			// Should have called original context
+			expect(mockGlobalState.update).toHaveBeenCalledWith("test-key", "new-value")
+
+			// Should have stored the value in cache
+			const storedValue = await proxy.getGlobalState("test-key")
+			expect(storedValue).toBe("new-value")
+		})
+	})
+
+	describe("getSecret", () => {
+		it("should return value from cache when it exists", async () => {
+			// Manually set a value in the cache
+			await proxy.storeSecret("api-key", "cached-secret")
+
+			// Should return the cached value
+			const result = proxy.getSecret("api-key")
+			expect(result).toBe("cached-secret")
+		})
+	})
+
+	describe("storeSecret", () => {
+		it("should store secret directly in original context", async () => {
+			await proxy.storeSecret("api-key", "new-secret")
+
+			// Should have called original context
+			expect(mockSecrets.store).toHaveBeenCalledWith("api-key", "new-secret")
+
+			// Should have stored the value in cache
+			const storedValue = await proxy.getSecret("api-key")
+			expect(storedValue).toBe("new-secret")
+		})
+
+		it("should handle undefined value for secret deletion", async () => {
+			await proxy.storeSecret("api-key", undefined)
+
+			// Should have called delete on original context
+			expect(mockSecrets.delete).toHaveBeenCalledWith("api-key")
+
+			// Should have stored undefined in cache
+			const storedValue = await proxy.getSecret("api-key")
+			expect(storedValue).toBeUndefined()
+		})
+
+		describe("getApiConfiguration", () => {
+			it("should combine global state and secrets into a single ApiConfiguration object", async () => {
+				// Mock data in state cache
+				await proxy.updateGlobalState("apiProvider", "anthropic")
+				await proxy.updateGlobalState("apiModelId", "test-model")
+				// Mock data in secrets cache
+				await proxy.storeSecret("apiKey", "test-api-key")
+
+				const config = proxy.getApiConfiguration()
+
+				// Should contain values from global state
+				expect(config.apiProvider).toBe("anthropic")
+				expect(config.apiModelId).toBe("test-model")
+				// Should contain values from secrets
+				expect(config.apiKey).toBe("test-api-key")
+			})
+
+			it("should handle special case for apiProvider defaulting", async () => {
+				// Clear apiProvider but set apiKey
+				await proxy.updateGlobalState("apiProvider", undefined)
+				await proxy.storeSecret("apiKey", "test-api-key")
+
+				const config = proxy.getApiConfiguration()
+
+				// Should default to anthropic when apiKey exists
+				expect(config.apiProvider).toBe("anthropic")
+
+				// Clear both apiProvider and apiKey
+				await proxy.updateGlobalState("apiProvider", undefined)
+				await proxy.storeSecret("apiKey", undefined)
+
+				const configWithoutKey = proxy.getApiConfiguration()
+
+				// Should default to openrouter when no apiKey exists
+				expect(configWithoutKey.apiProvider).toBe("openrouter")
+			})
+		})
+
+		describe("updateApiConfiguration", () => {
+			it("should update both global state and secrets", async () => {
+				const apiConfig: ApiConfiguration = {
+					apiProvider: "anthropic",
+					apiModelId: "claude-latest",
+					apiKey: "test-api-key",
+				}
+
+				await proxy.updateApiConfiguration(apiConfig)
+
+				// Should update global state
+				expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "anthropic")
+				expect(mockGlobalState.update).toHaveBeenCalledWith("apiModelId", "claude-latest")
+				// Should update secrets
+				expect(mockSecrets.store).toHaveBeenCalledWith("apiKey", "test-api-key")
+
+				// Check that values are in cache
+				expect(proxy.getGlobalState("apiProvider")).toBe("anthropic")
+				expect(proxy.getGlobalState("apiModelId")).toBe("claude-latest")
+				expect(proxy.getSecret("apiKey")).toBe("test-api-key")
+			})
+
+			it("should ignore keys that aren't in either GLOBAL_STATE_KEYS or SECRET_KEYS", async () => {
+				// Use type assertion to add an invalid key
+				const apiConfig = {
+					apiProvider: "anthropic",
+					invalidKey: "should be ignored",
+				} as ApiConfiguration & { invalidKey: string }
+
+				await proxy.updateApiConfiguration(apiConfig)
+
+				// Should update keys in GLOBAL_STATE_KEYS
+				expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "anthropic")
+				// Should not call update/store for invalid keys
+				expect(mockGlobalState.update).not.toHaveBeenCalledWith("invalidKey", expect.anything())
+				expect(mockSecrets.store).not.toHaveBeenCalledWith("invalidKey", expect.anything())
+			})
+		})
+	})
+})

+ 154 - 0
src/core/contextProxy.ts

@@ -0,0 +1,154 @@
+import * as vscode from "vscode"
+import { logger } from "../utils/logging"
+import { ApiConfiguration, API_CONFIG_KEYS } from "../shared/api"
+import { GLOBAL_STATE_KEYS, SECRET_KEYS, GlobalStateKey, SecretKey } from "../shared/globalState"
+
+export class ContextProxy {
+	private readonly originalContext: vscode.ExtensionContext
+	private stateCache: Map<string, any>
+	private secretCache: Map<string, string | undefined>
+
+	constructor(context: vscode.ExtensionContext) {
+		// Initialize properties first
+		this.originalContext = context
+		this.stateCache = new Map()
+		this.secretCache = new Map()
+
+		// Initialize state cache with all defined global state keys
+		this.initializeStateCache()
+
+		// Initialize secret cache with all defined secret keys
+		this.initializeSecretCache()
+
+		logger.debug("ContextProxy created")
+	}
+
+	// Helper method to initialize state cache
+	private initializeStateCache(): void {
+		for (const key of GLOBAL_STATE_KEYS) {
+			try {
+				const value = this.originalContext.globalState.get(key)
+				this.stateCache.set(key, value)
+			} catch (error) {
+				logger.error(`Error loading global ${key}: ${error instanceof Error ? error.message : String(error)}`)
+			}
+		}
+	}
+
+	// Helper method to initialize secret cache
+	private initializeSecretCache(): void {
+		for (const key of SECRET_KEYS) {
+			// Get actual value and update cache when promise resolves
+			;(this.originalContext.secrets.get(key) as Promise<string | undefined>)
+				.then((value) => {
+					this.secretCache.set(key, value)
+				})
+				.catch((error: Error) => {
+					logger.error(`Error loading secret ${key}: ${error.message}`)
+				})
+		}
+	}
+
+	get extensionUri(): vscode.Uri {
+		return this.originalContext.extensionUri
+	}
+	get extensionPath(): string {
+		return this.originalContext.extensionPath
+	}
+	get globalStorageUri(): vscode.Uri {
+		return this.originalContext.globalStorageUri
+	}
+	get logUri(): vscode.Uri {
+		return this.originalContext.logUri
+	}
+	get extension(): vscode.Extension<any> | undefined {
+		return this.originalContext.extension
+	}
+	get extensionMode(): vscode.ExtensionMode {
+		return this.originalContext.extensionMode
+	}
+
+	getGlobalState<T>(key: string): T | undefined
+	getGlobalState<T>(key: string, defaultValue: T): T
+	getGlobalState<T>(key: string, defaultValue?: T): T | undefined {
+		const value = this.stateCache.get(key) as T | undefined
+		return value !== undefined ? value : (defaultValue as T | undefined)
+	}
+
+	updateGlobalState<T>(key: string, value: T): Thenable<void> {
+		this.stateCache.set(key, value)
+		return this.originalContext.globalState.update(key, value)
+	}
+
+	getSecret(key: string): string | undefined {
+		return this.secretCache.get(key)
+	}
+	storeSecret(key: string, value?: string): Thenable<void> {
+		// Update cache
+		this.secretCache.set(key, value)
+		// Write directly to context
+		if (value === undefined) {
+			return this.originalContext.secrets.delete(key)
+		} else {
+			return this.originalContext.secrets.store(key, value)
+		}
+	}
+
+	/**
+	 * Gets a complete ApiConfiguration object by fetching values
+	 * from both global state and secrets storage
+	 */
+	getApiConfiguration(): ApiConfiguration {
+		// Create an empty ApiConfiguration object
+		const config: ApiConfiguration = {}
+
+		// Add all API-related keys from global state
+		for (const key of API_CONFIG_KEYS) {
+			const value = this.getGlobalState(key)
+			if (value !== undefined) {
+				// Use type assertion to avoid TypeScript error
+				;(config as any)[key] = value
+			}
+		}
+
+		// Add all secret values
+		for (const key of SECRET_KEYS) {
+			const value = this.getSecret(key)
+			if (value !== undefined) {
+				// Use type assertion to avoid TypeScript error
+				;(config as any)[key] = value
+			}
+		}
+
+		// Handle special case for apiProvider if needed (same logic as current implementation)
+		if (!config.apiProvider) {
+			if (config.apiKey) {
+				config.apiProvider = "anthropic"
+			} else {
+				config.apiProvider = "openrouter"
+			}
+		}
+
+		return config
+	}
+
+	/**
+	 * Updates an ApiConfiguration by persisting each property
+	 * to the appropriate storage (global state or secrets)
+	 */
+	async updateApiConfiguration(apiConfiguration: ApiConfiguration): Promise<void> {
+		const promises: Array<Thenable<void>> = []
+
+		// For each property, update the appropriate storage
+		Object.entries(apiConfiguration).forEach(([key, value]) => {
+			if (SECRET_KEYS.includes(key as SecretKey)) {
+				promises.push(this.storeSecret(key, value))
+			} else if (API_CONFIG_KEYS.includes(key as GlobalStateKey)) {
+				promises.push(this.updateGlobalState(key, value))
+			}
+			// Ignore keys that aren't in either list
+		})
+
+		await Promise.all(promises)
+	}
+}

+ 105 - 421
src/core/webview/ClineProvider.ts

@@ -8,16 +8,16 @@ import * as path from "path"
 import * as vscode from "vscode"
 import * as vscode from "vscode"
 import simpleGit from "simple-git"
 import simpleGit from "simple-git"
 
 
-import { ApiConfiguration, ApiProvider, ModelInfo } from "../../shared/api"
+import { ApiConfiguration, ApiProvider, ModelInfo, API_CONFIG_KEYS } from "../../shared/api"
 import { CheckpointStorage } from "../../shared/checkpoints"
 import { CheckpointStorage } from "../../shared/checkpoints"
 import { findLast } from "../../shared/array"
 import { findLast } from "../../shared/array"
 import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
 import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
 import { GlobalFileNames } from "../../shared/globalFileNames"
 import { GlobalFileNames } from "../../shared/globalFileNames"
-import type { SecretKey, GlobalStateKey } from "../../shared/globalState"
+import { SecretKey, GlobalStateKey, SECRET_KEYS, GLOBAL_STATE_KEYS } from "../../shared/globalState"
 import { HistoryItem } from "../../shared/HistoryItem"
 import { HistoryItem } from "../../shared/HistoryItem"
 import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
 import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
 import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
 import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
-import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
+import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug, ModeConfig } from "../../shared/modes"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
 import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
 import { downloadTask } from "../../integrations/misc/export-markdown"
 import { downloadTask } from "../../integrations/misc/export-markdown"
@@ -35,6 +35,7 @@ import { getDiffStrategy } from "../diff/DiffStrategy"
 import { SYSTEM_PROMPT } from "../prompts/system"
 import { SYSTEM_PROMPT } from "../prompts/system"
 import { ConfigManager } from "../config/ConfigManager"
 import { ConfigManager } from "../config/ConfigManager"
 import { CustomModesManager } from "../config/CustomModesManager"
 import { CustomModesManager } from "../config/CustomModesManager"
+import { ContextProxy } from "../contextProxy"
 import { buildApiHandler } from "../../api"
 import { buildApiHandler } from "../../api"
 import { getOpenRouterModels } from "../../api/providers/openrouter"
 import { getOpenRouterModels } from "../../api/providers/openrouter"
 import { getGlamaModels } from "../../api/providers/glama"
 import { getGlamaModels } from "../../api/providers/glama"
@@ -66,6 +67,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 	private workspaceTracker?: WorkspaceTracker
 	private workspaceTracker?: WorkspaceTracker
 	protected mcpHub?: McpHub // Change from private to protected
 	protected mcpHub?: McpHub // Change from private to protected
 	private latestAnnouncementId = "feb-27-2025-automatic-checkpoints" // update to some unique identifier when we add a new announcement
 	private latestAnnouncementId = "feb-27-2025-automatic-checkpoints" // update to some unique identifier when we add a new announcement
+	private contextProxy: ContextProxy
 	configManager: ConfigManager
 	configManager: ConfigManager
 	customModesManager: CustomModesManager
 	customModesManager: CustomModesManager
 
 
@@ -74,6 +76,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		private readonly outputChannel: vscode.OutputChannel,
 		private readonly outputChannel: vscode.OutputChannel,
 	) {
 	) {
 		this.outputChannel.appendLine("ClineProvider instantiated")
 		this.outputChannel.appendLine("ClineProvider instantiated")
+		this.contextProxy = new ContextProxy(context)
 		ClineProvider.activeInstances.add(this)
 		ClineProvider.activeInstances.add(this)
 		this.workspaceTracker = new WorkspaceTracker(this)
 		this.workspaceTracker = new WorkspaceTracker(this)
 		this.configManager = new ConfigManager(this.context)
 		this.configManager = new ConfigManager(this.context)
@@ -242,11 +245,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		webviewView.webview.options = {
 		webviewView.webview.options = {
 			// Allow scripts in the webview
 			// Allow scripts in the webview
 			enableScripts: true,
 			enableScripts: true,
-			localResourceRoots: [this.context.extensionUri],
+			localResourceRoots: [this.contextProxy.extensionUri],
 		}
 		}
 
 
 		webviewView.webview.html =
 		webviewView.webview.html =
-			this.context.extensionMode === vscode.ExtensionMode.Development
+			this.contextProxy.extensionMode === vscode.ExtensionMode.Development
 				? await this.getHMRHtmlContent(webviewView.webview)
 				? await this.getHMRHtmlContent(webviewView.webview)
 				: this.getHtmlContent(webviewView.webview)
 				: this.getHtmlContent(webviewView.webview)
 
 
@@ -399,8 +402,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		}
 		}
 
 
 		const nonce = getNonce()
 		const nonce = getNonce()
-		const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
-		const codiconsUri = getUri(webview, this.context.extensionUri, [
+		const stylesUri = getUri(webview, this.contextProxy.extensionUri, [
+			"webview-ui",
+			"build",
+			"assets",
+			"index.css",
+		])
+		const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [
 			"node_modules",
 			"node_modules",
 			"@vscode",
 			"@vscode",
 			"codicons",
 			"codicons",
@@ -466,15 +474,20 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		// then convert it to a uri we can use in the webview.
 		// then convert it to a uri we can use in the webview.
 
 
 		// The CSS file from the React build output
 		// The CSS file from the React build output
-		const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
+		const stylesUri = getUri(webview, this.contextProxy.extensionUri, [
+			"webview-ui",
+			"build",
+			"assets",
+			"index.css",
+		])
 		// The JS file from the React build output
 		// The JS file from the React build output
-		const scriptUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.js"])
+		const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"])
 
 
 		// The codicon font from the React build output
 		// The codicon font from the React build output
 		// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts
 		// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts
 		// we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it
 		// we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it
 		// don't forget to add font-src ${webview.cspSource};
 		// don't forget to add font-src ${webview.cspSource};
-		const codiconsUri = getUri(webview, this.context.extensionUri, [
+		const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [
 			"node_modules",
 			"node_modules",
 			"@vscode",
 			"@vscode",
 			"codicons",
 			"codicons",
@@ -1265,7 +1278,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 								// Try to get enhancement config first, fall back to current config
 								// Try to get enhancement config first, fall back to current config
 								let configToUse: ApiConfiguration = apiConfiguration
 								let configToUse: ApiConfiguration = apiConfiguration
 								if (enhancementApiConfigId) {
 								if (enhancementApiConfigId) {
-									const config = listApiConfigMeta?.find((c) => c.id === enhancementApiConfigId)
+									const config = listApiConfigMeta?.find(
+										(c: ApiConfigMeta) => c.id === enhancementApiConfigId,
+									)
 									if (config?.name) {
 									if (config?.name) {
 										const loadedConfig = await this.configManager.loadConfig(config.name)
 										const loadedConfig = await this.configManager.loadConfig(config.name)
 										if (loadedConfig.apiProvider) {
 										if (loadedConfig.apiProvider) {
@@ -1644,108 +1659,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			}
 			}
 		}
 		}
 
 
-		const {
-			apiProvider,
-			apiModelId,
-			apiKey,
-			glamaModelId,
-			glamaModelInfo,
-			glamaApiKey,
-			openRouterApiKey,
-			awsAccessKey,
-			awsSecretKey,
-			awsSessionToken,
-			awsRegion,
-			awsUseCrossRegionInference,
-			awsProfile,
-			awsUseProfile,
-			vertexProjectId,
-			vertexRegion,
-			openAiBaseUrl,
-			openAiApiKey,
-			openAiModelId,
-			openAiCustomModelInfo,
-			openAiUseAzure,
-			ollamaModelId,
-			ollamaBaseUrl,
-			lmStudioModelId,
-			lmStudioBaseUrl,
-			anthropicBaseUrl,
-			geminiApiKey,
-			openAiNativeApiKey,
-			deepSeekApiKey,
-			azureApiVersion,
-			openAiStreamingEnabled,
-			openRouterModelId,
-			openRouterBaseUrl,
-			openRouterModelInfo,
-			openRouterUseMiddleOutTransform,
-			vsCodeLmModelSelector,
-			mistralApiKey,
-			mistralCodestralUrl,
-			unboundApiKey,
-			unboundModelId,
-			unboundModelInfo,
-			requestyApiKey,
-			requestyModelId,
-			requestyModelInfo,
-			modelTemperature,
-			modelMaxTokens,
-			modelMaxThinkingTokens,
-			lmStudioDraftModelId,
-			lmStudioSpeculativeDecodingEnabled,
-		} = apiConfiguration
-		await Promise.all([
-			this.updateGlobalState("apiProvider", apiProvider),
-			this.updateGlobalState("apiModelId", apiModelId),
-			this.storeSecret("apiKey", apiKey),
-			this.updateGlobalState("glamaModelId", glamaModelId),
-			this.updateGlobalState("glamaModelInfo", glamaModelInfo),
-			this.storeSecret("glamaApiKey", glamaApiKey),
-			this.storeSecret("openRouterApiKey", openRouterApiKey),
-			this.storeSecret("awsAccessKey", awsAccessKey),
-			this.storeSecret("awsSecretKey", awsSecretKey),
-			this.storeSecret("awsSessionToken", awsSessionToken),
-			this.updateGlobalState("awsRegion", awsRegion),
-			this.updateGlobalState("awsUseCrossRegionInference", awsUseCrossRegionInference),
-			this.updateGlobalState("awsProfile", awsProfile),
-			this.updateGlobalState("awsUseProfile", awsUseProfile),
-			this.updateGlobalState("vertexProjectId", vertexProjectId),
-			this.updateGlobalState("vertexRegion", vertexRegion),
-			this.updateGlobalState("openAiBaseUrl", openAiBaseUrl),
-			this.storeSecret("openAiApiKey", openAiApiKey),
-			this.updateGlobalState("openAiModelId", openAiModelId),
-			this.updateGlobalState("openAiCustomModelInfo", openAiCustomModelInfo),
-			this.updateGlobalState("openAiUseAzure", openAiUseAzure),
-			this.updateGlobalState("ollamaModelId", ollamaModelId),
-			this.updateGlobalState("ollamaBaseUrl", ollamaBaseUrl),
-			this.updateGlobalState("lmStudioModelId", lmStudioModelId),
-			this.updateGlobalState("lmStudioBaseUrl", lmStudioBaseUrl),
-			this.updateGlobalState("anthropicBaseUrl", anthropicBaseUrl),
-			this.storeSecret("geminiApiKey", geminiApiKey),
-			this.storeSecret("openAiNativeApiKey", openAiNativeApiKey),
-			this.storeSecret("deepSeekApiKey", deepSeekApiKey),
-			this.updateGlobalState("azureApiVersion", azureApiVersion),
-			this.updateGlobalState("openAiStreamingEnabled", openAiStreamingEnabled),
-			this.updateGlobalState("openRouterModelId", openRouterModelId),
-			this.updateGlobalState("openRouterModelInfo", openRouterModelInfo),
-			this.updateGlobalState("openRouterBaseUrl", openRouterBaseUrl),
-			this.updateGlobalState("openRouterUseMiddleOutTransform", openRouterUseMiddleOutTransform),
-			this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector),
-			this.storeSecret("mistralApiKey", mistralApiKey),
-			this.updateGlobalState("mistralCodestralUrl", mistralCodestralUrl),
-			this.storeSecret("unboundApiKey", unboundApiKey),
-			this.updateGlobalState("unboundModelId", unboundModelId),
-			this.updateGlobalState("unboundModelInfo", unboundModelInfo),
-			this.storeSecret("requestyApiKey", requestyApiKey),
-			this.updateGlobalState("requestyModelId", requestyModelId),
-			this.updateGlobalState("requestyModelInfo", requestyModelInfo),
-			this.updateGlobalState("modelTemperature", modelTemperature),
-			this.updateGlobalState("modelMaxTokens", modelMaxTokens),
-			this.updateGlobalState("anthropicThinking", modelMaxThinkingTokens),
-			this.updateGlobalState("lmStudioDraftModelId", lmStudioDraftModelId),
-			this.updateGlobalState("lmStudioSpeculativeDecodingEnabled", lmStudioSpeculativeDecodingEnabled),
-		])
+		// Update all configuration values through the contextProxy
+		await this.contextProxy.updateApiConfiguration(apiConfiguration)
+
 		if (this.cline) {
 		if (this.cline) {
 			this.cline.api = buildApiHandler(apiConfiguration)
 			this.cline.api = buildApiHandler(apiConfiguration)
 		}
 		}
@@ -1806,13 +1722,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 	}
 	}
 
 
 	async ensureSettingsDirectoryExists(): Promise<string> {
 	async ensureSettingsDirectoryExists(): Promise<string> {
-		const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings")
+		const settingsDir = path.join(this.contextProxy.globalStorageUri.fsPath, "settings")
 		await fs.mkdir(settingsDir, { recursive: true })
 		await fs.mkdir(settingsDir, { recursive: true })
 		return settingsDir
 		return settingsDir
 	}
 	}
 
 
 	private async ensureCacheDirectoryExists() {
 	private async ensureCacheDirectoryExists() {
-		const cacheDir = path.join(this.context.globalStorageUri.fsPath, "cache")
+		const cacheDir = path.join(this.contextProxy.globalStorageUri.fsPath, "cache")
 		await fs.mkdir(cacheDir, { recursive: true })
 		await fs.mkdir(cacheDir, { recursive: true })
 		return cacheDir
 		return cacheDir
 	}
 	}
@@ -1900,7 +1816,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
 		const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
 		const historyItem = history.find((item) => item.id === id)
 		const historyItem = history.find((item) => item.id === id)
 		if (historyItem) {
 		if (historyItem) {
-			const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
+			const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id)
 			const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
 			const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
 			const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
 			const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
 			const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
 			const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
@@ -2053,7 +1969,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
 			alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
 			uriScheme: vscode.env.uriScheme,
 			uriScheme: vscode.env.uriScheme,
 			currentTaskItem: this.cline?.taskId
 			currentTaskItem: this.cline?.taskId
-				? (taskHistory || []).find((item) => item.id === this.cline?.taskId)
+				? (taskHistory || []).find((item: HistoryItem) => item.id === this.cline?.taskId)
 				: undefined,
 				: undefined,
 			clineMessages: this.cline?.clineMessages || [],
 			clineMessages: this.cline?.clineMessages || [],
 			taskHistory: (taskHistory || [])
 			taskHistory: (taskHistory || [])
@@ -2145,273 +2061,58 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 	*/
 	*/
 
 
 	async getState() {
 	async getState() {
-		const [
-			storedApiProvider,
-			apiModelId,
-			apiKey,
-			glamaApiKey,
-			glamaModelId,
-			glamaModelInfo,
-			openRouterApiKey,
-			awsAccessKey,
-			awsSecretKey,
-			awsSessionToken,
-			awsRegion,
-			awsUseCrossRegionInference,
-			awsProfile,
-			awsUseProfile,
-			vertexProjectId,
-			vertexRegion,
-			openAiBaseUrl,
-			openAiApiKey,
-			openAiModelId,
-			openAiCustomModelInfo,
-			openAiUseAzure,
-			ollamaModelId,
-			ollamaBaseUrl,
-			lmStudioModelId,
-			lmStudioBaseUrl,
-			anthropicBaseUrl,
-			geminiApiKey,
-			openAiNativeApiKey,
-			deepSeekApiKey,
-			mistralApiKey,
-			mistralCodestralUrl,
-			azureApiVersion,
-			openAiStreamingEnabled,
-			openRouterModelId,
-			openRouterModelInfo,
-			openRouterBaseUrl,
-			openRouterUseMiddleOutTransform,
-			lastShownAnnouncementId,
-			customInstructions,
-			alwaysAllowReadOnly,
-			alwaysAllowWrite,
-			alwaysAllowExecute,
-			alwaysAllowBrowser,
-			alwaysAllowMcp,
-			alwaysAllowModeSwitch,
-			taskHistory,
-			allowedCommands,
-			soundEnabled,
-			diffEnabled,
-			enableCheckpoints,
-			checkpointStorage,
-			soundVolume,
-			browserViewportSize,
-			fuzzyMatchThreshold,
-			preferredLanguage,
-			writeDelayMs,
-			screenshotQuality,
-			terminalOutputLineLimit,
-			mcpEnabled,
-			enableMcpServerCreation,
-			alwaysApproveResubmit,
-			requestDelaySeconds,
-			rateLimitSeconds,
-			currentApiConfigName,
-			listApiConfigMeta,
-			vsCodeLmModelSelector,
-			mode,
-			modeApiConfigs,
-			customModePrompts,
-			customSupportPrompts,
-			enhancementApiConfigId,
-			autoApprovalEnabled,
-			customModes,
-			experiments,
-			unboundApiKey,
-			unboundModelId,
-			unboundModelInfo,
-			requestyApiKey,
-			requestyModelId,
-			requestyModelInfo,
-			modelTemperature,
-			modelMaxTokens,
-			modelMaxThinkingTokens,
-			maxOpenTabsContext,
-			browserToolEnabled,
-			lmStudioSpeculativeDecodingEnabled,
-			lmStudioDraftModelId,
-		] = await Promise.all([
-			this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
-			this.getGlobalState("apiModelId") as Promise<string | undefined>,
-			this.getSecret("apiKey") as Promise<string | undefined>,
-			this.getSecret("glamaApiKey") as Promise<string | undefined>,
-			this.getGlobalState("glamaModelId") as Promise<string | undefined>,
-			this.getGlobalState("glamaModelInfo") as Promise<ModelInfo | undefined>,
-			this.getSecret("openRouterApiKey") as Promise<string | undefined>,
-			this.getSecret("awsAccessKey") as Promise<string | undefined>,
-			this.getSecret("awsSecretKey") as Promise<string | undefined>,
-			this.getSecret("awsSessionToken") as Promise<string | undefined>,
-			this.getGlobalState("awsRegion") as Promise<string | undefined>,
-			this.getGlobalState("awsUseCrossRegionInference") as Promise<boolean | undefined>,
-			this.getGlobalState("awsProfile") as Promise<string | undefined>,
-			this.getGlobalState("awsUseProfile") as Promise<boolean | undefined>,
-			this.getGlobalState("vertexProjectId") as Promise<string | undefined>,
-			this.getGlobalState("vertexRegion") as Promise<string | undefined>,
-			this.getGlobalState("openAiBaseUrl") as Promise<string | undefined>,
-			this.getSecret("openAiApiKey") as Promise<string | undefined>,
-			this.getGlobalState("openAiModelId") as Promise<string | undefined>,
-			this.getGlobalState("openAiCustomModelInfo") as Promise<ModelInfo | undefined>,
-			this.getGlobalState("openAiUseAzure") as Promise<boolean | undefined>,
-			this.getGlobalState("ollamaModelId") as Promise<string | undefined>,
-			this.getGlobalState("ollamaBaseUrl") as Promise<string | undefined>,
-			this.getGlobalState("lmStudioModelId") as Promise<string | undefined>,
-			this.getGlobalState("lmStudioBaseUrl") as Promise<string | undefined>,
-			this.getGlobalState("anthropicBaseUrl") as Promise<string | undefined>,
-			this.getSecret("geminiApiKey") as Promise<string | undefined>,
-			this.getSecret("openAiNativeApiKey") as Promise<string | undefined>,
-			this.getSecret("deepSeekApiKey") as Promise<string | undefined>,
-			this.getSecret("mistralApiKey") as Promise<string | undefined>,
-			this.getGlobalState("mistralCodestralUrl") as Promise<string | undefined>,
-			this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
-			this.getGlobalState("openAiStreamingEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("openRouterModelId") as Promise<string | undefined>,
-			this.getGlobalState("openRouterModelInfo") as Promise<ModelInfo | undefined>,
-			this.getGlobalState("openRouterBaseUrl") as Promise<string | undefined>,
-			this.getGlobalState("openRouterUseMiddleOutTransform") as Promise<boolean | undefined>,
-			this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
-			this.getGlobalState("customInstructions") as Promise<string | undefined>,
-			this.getGlobalState("alwaysAllowReadOnly") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysAllowWrite") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysAllowExecute") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysAllowBrowser") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysAllowMcp") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysAllowModeSwitch") as Promise<boolean | undefined>,
-			this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
-			this.getGlobalState("allowedCommands") as Promise<string[] | undefined>,
-			this.getGlobalState("soundEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("diffEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("enableCheckpoints") as Promise<boolean | undefined>,
-			this.getGlobalState("checkpointStorage") as Promise<CheckpointStorage | undefined>,
-			this.getGlobalState("soundVolume") as Promise<number | undefined>,
-			this.getGlobalState("browserViewportSize") as Promise<string | undefined>,
-			this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
-			this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
-			this.getGlobalState("writeDelayMs") as Promise<number | undefined>,
-			this.getGlobalState("screenshotQuality") as Promise<number | undefined>,
-			this.getGlobalState("terminalOutputLineLimit") as Promise<number | undefined>,
-			this.getGlobalState("mcpEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("enableMcpServerCreation") as Promise<boolean | undefined>,
-			this.getGlobalState("alwaysApproveResubmit") as Promise<boolean | undefined>,
-			this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
-			this.getGlobalState("rateLimitSeconds") as Promise<number | undefined>,
-			this.getGlobalState("currentApiConfigName") as Promise<string | undefined>,
-			this.getGlobalState("listApiConfigMeta") as Promise<ApiConfigMeta[] | undefined>,
-			this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
-			this.getGlobalState("mode") as Promise<Mode | undefined>,
-			this.getGlobalState("modeApiConfigs") as Promise<Record<Mode, string> | undefined>,
-			this.getGlobalState("customModePrompts") as Promise<CustomModePrompts | undefined>,
-			this.getGlobalState("customSupportPrompts") as Promise<CustomSupportPrompts | undefined>,
-			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
-			this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
-			this.customModesManager.getCustomModes(),
-			this.getGlobalState("experiments") as Promise<Record<ExperimentId, boolean> | undefined>,
-			this.getSecret("unboundApiKey") as Promise<string | undefined>,
-			this.getGlobalState("unboundModelId") as Promise<string | undefined>,
-			this.getGlobalState("unboundModelInfo") as Promise<ModelInfo | undefined>,
-			this.getSecret("requestyApiKey") as Promise<string | undefined>,
-			this.getGlobalState("requestyModelId") as Promise<string | undefined>,
-			this.getGlobalState("requestyModelInfo") as Promise<ModelInfo | undefined>,
-			this.getGlobalState("modelTemperature") as Promise<number | undefined>,
-			this.getGlobalState("modelMaxTokens") as Promise<number | undefined>,
-			this.getGlobalState("anthropicThinking") as Promise<number | undefined>,
-			this.getGlobalState("maxOpenTabsContext") as Promise<number | undefined>,
-			this.getGlobalState("browserToolEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("lmStudioSpeculativeDecodingEnabled") as Promise<boolean | undefined>,
-			this.getGlobalState("lmStudioDraftModelId") as Promise<string | undefined>,
-		])
+		// Get ApiConfiguration directly from contextProxy
+		const apiConfiguration = this.contextProxy.getApiConfiguration()
 
 
-		let apiProvider: ApiProvider
-		if (storedApiProvider) {
-			apiProvider = storedApiProvider
-		} else {
-			// Either new user or legacy user that doesn't have the apiProvider stored in state
-			// (If they're using OpenRouter or Bedrock, then apiProvider state will exist)
-			if (apiKey) {
-				apiProvider = "anthropic"
-			} else {
-				// New users should default to openrouter
-				apiProvider = "openrouter"
-			}
-		}
+		// Create an object to store all fetched values (excluding API config which we already have)
+		const stateValues: Record<GlobalStateKey, any> = {} as Record<GlobalStateKey, any>
 
 
+		// Create promise arrays for global state
+		const statePromises = GLOBAL_STATE_KEYS
+			// Filter out API config keys since we already have them
+			.filter((key) => !API_CONFIG_KEYS.includes(key))
+			.map((key) => this.getGlobalState(key))
+
+		// Add promise for custom modes which is handled separately
+		const customModesPromise = this.customModesManager.getCustomModes()
+
+		let idx = 0
+		const valuePromises = await Promise.all([...statePromises, customModesPromise])
+
+		// Populate stateValues
+		GLOBAL_STATE_KEYS.filter((key) => !API_CONFIG_KEYS.includes(key)).forEach((key) => {
+			stateValues[key] = valuePromises[idx]
+			idx = idx + 1
+		})
+
+		let customModes = valuePromises[idx] as ModeConfig[] | undefined
+
+		// Return the same structure as before
 		return {
 		return {
-			apiConfiguration: {
-				apiProvider,
-				apiModelId,
-				apiKey,
-				glamaApiKey,
-				glamaModelId,
-				glamaModelInfo,
-				openRouterApiKey,
-				awsAccessKey,
-				awsSecretKey,
-				awsSessionToken,
-				awsRegion,
-				awsUseCrossRegionInference,
-				awsProfile,
-				awsUseProfile,
-				vertexProjectId,
-				vertexRegion,
-				openAiBaseUrl,
-				openAiApiKey,
-				openAiModelId,
-				openAiCustomModelInfo,
-				openAiUseAzure,
-				ollamaModelId,
-				ollamaBaseUrl,
-				lmStudioModelId,
-				lmStudioBaseUrl,
-				anthropicBaseUrl,
-				geminiApiKey,
-				openAiNativeApiKey,
-				deepSeekApiKey,
-				mistralApiKey,
-				mistralCodestralUrl,
-				azureApiVersion,
-				openAiStreamingEnabled,
-				openRouterModelId,
-				openRouterModelInfo,
-				openRouterBaseUrl,
-				openRouterUseMiddleOutTransform,
-				vsCodeLmModelSelector,
-				unboundApiKey,
-				unboundModelId,
-				unboundModelInfo,
-				requestyApiKey,
-				requestyModelId,
-				requestyModelInfo,
-				modelTemperature,
-				modelMaxTokens,
-				modelMaxThinkingTokens,
-				lmStudioSpeculativeDecodingEnabled,
-				lmStudioDraftModelId,
-			},
-			lastShownAnnouncementId,
-			customInstructions,
-			alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
-			alwaysAllowWrite: alwaysAllowWrite ?? false,
-			alwaysAllowExecute: alwaysAllowExecute ?? false,
-			alwaysAllowBrowser: alwaysAllowBrowser ?? false,
-			alwaysAllowMcp: alwaysAllowMcp ?? false,
-			alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
-			taskHistory,
-			allowedCommands,
-			soundEnabled: soundEnabled ?? false,
-			diffEnabled: diffEnabled ?? true,
-			enableCheckpoints: enableCheckpoints ?? true,
-			checkpointStorage: checkpointStorage ?? "task",
-			soundVolume,
-			browserViewportSize: browserViewportSize ?? "900x600",
-			screenshotQuality: screenshotQuality ?? 75,
-			fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
-			writeDelayMs: writeDelayMs ?? 1000,
-			terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
-			mode: mode ?? defaultModeSlug,
+			apiConfiguration,
+			lastShownAnnouncementId: stateValues.lastShownAnnouncementId,
+			customInstructions: stateValues.customInstructions,
+			alwaysAllowReadOnly: stateValues.alwaysAllowReadOnly ?? false,
+			alwaysAllowWrite: stateValues.alwaysAllowWrite ?? false,
+			alwaysAllowExecute: stateValues.alwaysAllowExecute ?? false,
+			alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
+			alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
+			alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
+			taskHistory: stateValues.taskHistory,
+			allowedCommands: stateValues.allowedCommands,
+			soundEnabled: stateValues.soundEnabled ?? false,
+			diffEnabled: stateValues.diffEnabled ?? true,
+			enableCheckpoints: stateValues.enableCheckpoints ?? false,
+			checkpointStorage: stateValues.checkpointStorage ?? "task",
+			soundVolume: stateValues.soundVolume,
+			browserViewportSize: stateValues.browserViewportSize ?? "900x600",
+			screenshotQuality: stateValues.screenshotQuality ?? 75,
+			fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0,
+			writeDelayMs: stateValues.writeDelayMs ?? 1000,
+			terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
+			mode: stateValues.mode ?? defaultModeSlug,
 			preferredLanguage:
 			preferredLanguage:
-				preferredLanguage ??
+				stateValues.preferredLanguage ??
 				(() => {
 				(() => {
 					// Get VSCode's locale setting
 					// Get VSCode's locale setting
 					const vscodeLang = vscode.env.language
 					const vscodeLang = vscode.env.language
@@ -2441,23 +2142,23 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 					// Return mapped language or default to English
 					// Return mapped language or default to English
 					return langMap[vscodeLang] ?? langMap[vscodeLang.split("-")[0]] ?? "English"
 					return langMap[vscodeLang] ?? langMap[vscodeLang.split("-")[0]] ?? "English"
 				})(),
 				})(),
-			mcpEnabled: mcpEnabled ?? true,
-			enableMcpServerCreation: enableMcpServerCreation ?? true,
-			alwaysApproveResubmit: alwaysApproveResubmit ?? false,
-			requestDelaySeconds: Math.max(5, requestDelaySeconds ?? 10),
-			rateLimitSeconds: rateLimitSeconds ?? 0,
-			currentApiConfigName: currentApiConfigName ?? "default",
-			listApiConfigMeta: listApiConfigMeta ?? [],
-			modeApiConfigs: modeApiConfigs ?? ({} as Record<Mode, string>),
-			customModePrompts: customModePrompts ?? {},
-			customSupportPrompts: customSupportPrompts ?? {},
-			enhancementApiConfigId,
-			experiments: experiments ?? experimentDefault,
-			autoApprovalEnabled: autoApprovalEnabled ?? false,
+			mcpEnabled: stateValues.mcpEnabled ?? true,
+			enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
+			alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false,
+			requestDelaySeconds: Math.max(5, stateValues.requestDelaySeconds ?? 10),
+			rateLimitSeconds: stateValues.rateLimitSeconds ?? 0,
+			currentApiConfigName: stateValues.currentApiConfigName ?? "default",
+			listApiConfigMeta: stateValues.listApiConfigMeta ?? [],
+			modeApiConfigs: stateValues.modeApiConfigs ?? ({} as Record<Mode, string>),
+			customModePrompts: stateValues.customModePrompts ?? {},
+			customSupportPrompts: stateValues.customSupportPrompts ?? {},
+			enhancementApiConfigId: stateValues.enhancementApiConfigId,
+			experiments: stateValues.experiments ?? experimentDefault,
+			autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false,
 			customModes,
 			customModes,
-			maxOpenTabsContext: maxOpenTabsContext ?? 20,
-			openRouterUseMiddleOutTransform: openRouterUseMiddleOutTransform ?? true,
-			browserToolEnabled: browserToolEnabled ?? true,
+			maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20,
+			openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true,
+			browserToolEnabled: stateValues.browserToolEnabled ?? true,
 		}
 		}
 	}
 	}
 
 
@@ -2477,25 +2178,21 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 	// global
 	// global
 
 
 	async updateGlobalState(key: GlobalStateKey, value: any) {
 	async updateGlobalState(key: GlobalStateKey, value: any) {
-		await this.context.globalState.update(key, value)
+		await this.contextProxy.updateGlobalState(key, value)
 	}
 	}
 
 
 	async getGlobalState(key: GlobalStateKey) {
 	async getGlobalState(key: GlobalStateKey) {
-		return await this.context.globalState.get(key)
+		return await this.contextProxy.getGlobalState(key)
 	}
 	}
 
 
 	// secrets
 	// secrets
 
 
 	public async storeSecret(key: SecretKey, value?: string) {
 	public async storeSecret(key: SecretKey, value?: string) {
-		if (value) {
-			await this.context.secrets.store(key, value)
-		} else {
-			await this.context.secrets.delete(key)
-		}
+		await this.contextProxy.storeSecret(key, value)
 	}
 	}
 
 
 	private async getSecret(key: SecretKey) {
 	private async getSecret(key: SecretKey) {
-		return await this.context.secrets.get(key)
+		return await this.contextProxy.getSecret(key)
 	}
 	}
 
 
 	// dev
 	// dev
@@ -2512,26 +2209,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		}
 		}
 
 
 		for (const key of this.context.globalState.keys()) {
 		for (const key of this.context.globalState.keys()) {
-			await this.context.globalState.update(key, undefined)
+			await this.contextProxy.updateGlobalState(key, undefined)
 		}
 		}
-		const secretKeys: SecretKey[] = [
-			"apiKey",
-			"glamaApiKey",
-			"openRouterApiKey",
-			"awsAccessKey",
-			"awsSecretKey",
-			"awsSessionToken",
-			"openAiApiKey",
-			"geminiApiKey",
-			"openAiNativeApiKey",
-			"deepSeekApiKey",
-			"mistralApiKey",
-			"unboundApiKey",
-			"requestyApiKey",
-		]
-		for (const key of secretKeys) {
+
+		for (const key of SECRET_KEYS) {
 			await this.storeSecret(key, undefined)
 			await this.storeSecret(key, undefined)
 		}
 		}
+
 		await this.configManager.resetAllConfigs()
 		await this.configManager.resetAllConfigs()
 		await this.customModesManager.resetCustomModes()
 		await this.customModesManager.resetCustomModes()
 		if (this.cline) {
 		if (this.cline) {

+ 155 - 0
src/core/webview/__tests__/ClineProvider.test.ts

@@ -5,6 +5,7 @@ import axios from "axios"
 
 
 import { ClineProvider } from "../ClineProvider"
 import { ClineProvider } from "../ClineProvider"
 import { ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
 import { ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
+import { GlobalStateKey, SecretKey } from "../../../shared/globalState"
 import { setSoundEnabled } from "../../../utils/sound"
 import { setSoundEnabled } from "../../../utils/sound"
 import { defaultModeSlug } from "../../../shared/modes"
 import { defaultModeSlug } from "../../../shared/modes"
 import { experimentDefault } from "../../../shared/experiments"
 import { experimentDefault } from "../../../shared/experiments"
@@ -12,6 +13,49 @@ import { experimentDefault } from "../../../shared/experiments"
 // Mock setup must come before imports
 // Mock setup must come before imports
 jest.mock("../../prompts/sections/custom-instructions")
 jest.mock("../../prompts/sections/custom-instructions")
 
 
+// Mock ContextProxy
+jest.mock("../../contextProxy", () => {
+	return {
+		ContextProxy: jest.fn().mockImplementation((context) => ({
+			originalContext: context,
+			extensionUri: context.extensionUri,
+			extensionPath: context.extensionPath,
+			globalStorageUri: context.globalStorageUri,
+			logUri: context.logUri,
+			extension: context.extension,
+			extensionMode: context.extensionMode,
+			getGlobalState: jest
+				.fn()
+				.mockImplementation((key, defaultValue) => context.globalState.get(key, defaultValue)),
+			updateGlobalState: jest.fn().mockImplementation((key, value) => context.globalState.update(key, value)),
+			getSecret: jest.fn().mockImplementation((key) => context.secrets.get(key)),
+			storeSecret: jest
+				.fn()
+				.mockImplementation((key, value) =>
+					value ? context.secrets.store(key, value) : context.secrets.delete(key),
+				),
+			getApiConfiguration: jest.fn().mockImplementation(() => ({
+				apiProvider: "openrouter",
+				// Add other common properties
+			})),
+			updateApiConfiguration: jest.fn().mockImplementation(async (apiConfiguration) => {
+				// Mock implementation that simulates updating state and secrets
+				for (const [key, value] of Object.entries(apiConfiguration)) {
+					if (key === "apiKey" || key === "openAiApiKey") {
+						context.secrets.store(key, value)
+					} else {
+						context.globalState.update(key, value)
+					}
+				}
+				return Promise.resolve()
+			}),
+			saveChanges: jest.fn().mockResolvedValue(undefined),
+			dispose: jest.fn().mockResolvedValue(undefined),
+			hasPendingChanges: jest.fn().mockReturnValue(false),
+		})),
+	}
+})
+
 // Mock dependencies
 // Mock dependencies
 jest.mock("vscode")
 jest.mock("vscode")
 jest.mock("delay")
 jest.mock("delay")
@@ -235,6 +279,12 @@ describe("ClineProvider", () => {
 	let mockOutputChannel: vscode.OutputChannel
 	let mockOutputChannel: vscode.OutputChannel
 	let mockWebviewView: vscode.WebviewView
 	let mockWebviewView: vscode.WebviewView
 	let mockPostMessage: jest.Mock
 	let mockPostMessage: jest.Mock
+	let mockContextProxy: {
+		updateGlobalState: jest.Mock
+		getGlobalState: jest.Mock
+		storeSecret: jest.Mock
+		dispose: jest.Mock
+	}
 
 
 	beforeEach(() => {
 	beforeEach(() => {
 		// Reset mocks
 		// Reset mocks
@@ -307,6 +357,8 @@ describe("ClineProvider", () => {
 		} as unknown as vscode.WebviewView
 		} as unknown as vscode.WebviewView
 
 
 		provider = new ClineProvider(mockContext, mockOutputChannel)
 		provider = new ClineProvider(mockContext, mockOutputChannel)
+		// @ts-ignore - Access private property for testing
+		mockContextProxy = provider.contextProxy
 
 
 		// @ts-ignore - Accessing private property for testing.
 		// @ts-ignore - Accessing private property for testing.
 		provider.customModesManager = mockCustomModesManager
 		provider.customModesManager = mockCustomModesManager
@@ -479,6 +531,7 @@ describe("ClineProvider", () => {
 
 
 		await messageHandler({ type: "writeDelayMs", value: 2000 })
 		await messageHandler({ type: "writeDelayMs", value: 2000 })
 
 
+		expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("writeDelayMs", 2000)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("writeDelayMs", 2000)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("writeDelayMs", 2000)
 		expect(mockPostMessage).toHaveBeenCalled()
 		expect(mockPostMessage).toHaveBeenCalled()
 	})
 	})
@@ -492,6 +545,7 @@ describe("ClineProvider", () => {
 		// Simulate setting sound to enabled
 		// Simulate setting sound to enabled
 		await messageHandler({ type: "soundEnabled", bool: true })
 		await messageHandler({ type: "soundEnabled", bool: true })
 		expect(setSoundEnabled).toHaveBeenCalledWith(true)
 		expect(setSoundEnabled).toHaveBeenCalledWith(true)
+		expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("soundEnabled", true)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", true)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", true)
 		expect(mockPostMessage).toHaveBeenCalled()
 		expect(mockPostMessage).toHaveBeenCalled()
 
 
@@ -614,6 +668,7 @@ describe("ClineProvider", () => {
 
 
 		// Test alwaysApproveResubmit
 		// Test alwaysApproveResubmit
 		await messageHandler({ type: "alwaysApproveResubmit", bool: true })
 		await messageHandler({ type: "alwaysApproveResubmit", bool: true })
+		expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("alwaysApproveResubmit", true)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("alwaysApproveResubmit", true)
 		expect(mockContext.globalState.update).toHaveBeenCalledWith("alwaysApproveResubmit", true)
 		expect(mockPostMessage).toHaveBeenCalled()
 		expect(mockPostMessage).toHaveBeenCalled()
 
 
@@ -1484,6 +1539,106 @@ describe("ClineProvider", () => {
 			expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
 			expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
 				{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
 				{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
 			])
 			])
+			expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("listApiConfigMeta", [
+				{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
+			])
 		})
 		})
 	})
 	})
 })
 })
+
+describe("ContextProxy integration", () => {
+	let provider: ClineProvider
+	let mockContext: vscode.ExtensionContext
+	let mockOutputChannel: vscode.OutputChannel
+	let mockContextProxy: any
+
+	beforeEach(() => {
+		// Reset mocks
+		jest.clearAllMocks()
+
+		// Setup basic mocks
+		mockContext = {
+			globalState: { get: jest.fn(), update: jest.fn(), keys: jest.fn().mockReturnValue([]) },
+			secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() },
+			extensionUri: {} as vscode.Uri,
+			globalStorageUri: { fsPath: "/test/path" },
+			extension: { packageJSON: { version: "1.0.0" } },
+		} as unknown as vscode.ExtensionContext
+
+		mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel
+		provider = new ClineProvider(mockContext, mockOutputChannel)
+
+		// @ts-ignore - accessing private property for testing
+		mockContextProxy = provider.contextProxy
+	})
+
+	test("updateGlobalState uses contextProxy", async () => {
+		await provider.updateGlobalState("currentApiConfigName" as GlobalStateKey, "testValue")
+		expect(mockContextProxy.updateGlobalState).toHaveBeenCalledWith("currentApiConfigName", "testValue")
+	})
+
+	test("getGlobalState uses contextProxy", async () => {
+		mockContextProxy.getGlobalState.mockResolvedValueOnce("testValue")
+		const result = await provider.getGlobalState("currentApiConfigName" as GlobalStateKey)
+		expect(mockContextProxy.getGlobalState).toHaveBeenCalledWith("currentApiConfigName")
+		expect(result).toBe("testValue")
+	})
+
+	test("storeSecret uses contextProxy", async () => {
+		await provider.storeSecret("apiKey" as SecretKey, "test-secret")
+		expect(mockContextProxy.storeSecret).toHaveBeenCalledWith("apiKey", "test-secret")
+	})
+
+	test("contextProxy methods are available", () => {
+		// Verify the contextProxy has all the required methods
+		expect(mockContextProxy.getGlobalState).toBeDefined()
+		expect(mockContextProxy.updateGlobalState).toBeDefined()
+		expect(mockContextProxy.storeSecret).toBeDefined()
+		expect(mockContextProxy.getApiConfiguration).toBeDefined()
+		expect(mockContextProxy.updateApiConfiguration).toBeDefined()
+	})
+
+	test("getState uses contextProxy.getApiConfiguration", async () => {
+		// Setup mock API configuration
+		const mockApiConfig = {
+			apiProvider: "anthropic",
+			apiModelId: "claude-latest",
+			apiKey: "test-api-key",
+		}
+		mockContextProxy.getApiConfiguration.mockReturnValue(mockApiConfig)
+
+		// Get state
+		const state = await provider.getState()
+
+		// Verify getApiConfiguration was called
+		expect(mockContextProxy.getApiConfiguration).toHaveBeenCalled()
+		// Verify state has the API configuration from contextProxy
+		expect(state.apiConfiguration).toBe(mockApiConfig)
+	})
+
+	test("updateApiConfiguration uses contextProxy.updateApiConfiguration", async () => {
+		// Setup test config
+		const testApiConfig = {
+			apiProvider: "anthropic",
+			apiModelId: "claude-latest",
+			apiKey: "test-api-key",
+		}
+
+		// Mock methods needed for the test
+		provider.configManager = {
+			listConfig: jest.fn().mockResolvedValue([]),
+			setModeConfig: jest.fn(),
+		} as any
+
+		// Mock getState for mode
+		jest.spyOn(provider, "getState").mockResolvedValue({
+			mode: "code",
+		} as any)
+
+		// Call the private method - need to use any to access it
+		await (provider as any).updateApiConfiguration(testApiConfig)
+
+		// Verify contextProxy.updateApiConfiguration was called with the right config
+		expect(mockContextProxy.updateApiConfiguration).toHaveBeenCalledWith(testApiConfig)
+	})
+})

+ 46 - 1
src/shared/api.ts

@@ -58,7 +58,6 @@ export interface ApiHandlerOptions {
 	azureApiVersion?: string
 	azureApiVersion?: string
 	openRouterUseMiddleOutTransform?: boolean
 	openRouterUseMiddleOutTransform?: boolean
 	openAiStreamingEnabled?: boolean
 	openAiStreamingEnabled?: boolean
-	setAzureApiVersion?: boolean
 	deepSeekBaseUrl?: string
 	deepSeekBaseUrl?: string
 	deepSeekApiKey?: string
 	deepSeekApiKey?: string
 	includeMaxTokens?: boolean
 	includeMaxTokens?: boolean
@@ -78,6 +77,52 @@ export type ApiConfiguration = ApiHandlerOptions & {
 	id?: string // stable unique identifier
 	id?: string // stable unique identifier
 }
 }
 
 
+// Import GlobalStateKey type from globalState.ts
+import { GlobalStateKey } from "./globalState"
+
+// Define API configuration keys for dynamic object building
+export const API_CONFIG_KEYS: GlobalStateKey[] = [
+	"apiModelId",
+	"anthropicBaseUrl",
+	"vsCodeLmModelSelector",
+	"glamaModelId",
+	"glamaModelInfo",
+	"openRouterModelId",
+	"openRouterModelInfo",
+	"openRouterBaseUrl",
+	"awsRegion",
+	"awsUseCrossRegionInference",
+	// "awsUsePromptCache", // NOT exist on GlobalStateKey
+	// "awspromptCacheId", // NOT exist on GlobalStateKey
+	"awsProfile",
+	"awsUseProfile",
+	"vertexProjectId",
+	"vertexRegion",
+	"openAiBaseUrl",
+	"openAiModelId",
+	"openAiCustomModelInfo",
+	"openAiUseAzure",
+	"ollamaModelId",
+	"ollamaBaseUrl",
+	"lmStudioModelId",
+	"lmStudioBaseUrl",
+	"lmStudioDraftModelId",
+	"lmStudioSpeculativeDecodingEnabled",
+	"mistralCodestralUrl",
+	"azureApiVersion",
+	"openRouterUseMiddleOutTransform",
+	"openAiStreamingEnabled",
+	// "deepSeekBaseUrl", //  not exist on GlobalStateKey
+	// "includeMaxTokens", // not exist on GlobalStateKey
+	"unboundModelId",
+	"unboundModelInfo",
+	"requestyModelId",
+	"requestyModelInfo",
+	"modelTemperature",
+	"modelMaxTokens",
+	"modelMaxThinkingTokens",
+]
+
 // Models
 // Models
 
 
 export interface ModelInfo {
 export interface ModelInfo {

+ 16 - 19
src/shared/checkExistApiConfig.ts

@@ -1,23 +1,20 @@
 import { ApiConfiguration } from "../shared/api"
 import { ApiConfiguration } from "../shared/api"
+import { SECRET_KEYS } from "./globalState"
 
 
 export function checkExistKey(config: ApiConfiguration | undefined) {
 export function checkExistKey(config: ApiConfiguration | undefined) {
-	return config
-		? [
-				config.apiKey,
-				config.glamaApiKey,
-				config.openRouterApiKey,
-				config.awsRegion,
-				config.vertexProjectId,
-				config.openAiApiKey,
-				config.ollamaModelId,
-				config.lmStudioModelId,
-				config.geminiApiKey,
-				config.openAiNativeApiKey,
-				config.deepSeekApiKey,
-				config.mistralApiKey,
-				config.vsCodeLmModelSelector,
-				config.requestyApiKey,
-				config.unboundApiKey,
-			].some((key) => key !== undefined)
-		: false
+	if (!config) return false
+
+	// Check all secret keys from the centralized SECRET_KEYS array
+	const hasSecretKey = SECRET_KEYS.some((key) => config[key as keyof ApiConfiguration] !== undefined)
+
+	// Check additional non-secret configuration properties
+	const hasOtherConfig = [
+		config.awsRegion,
+		config.vertexProjectId,
+		config.ollamaModelId,
+		config.lmStudioModelId,
+		config.vsCodeLmModelSelector,
+	].some((value) => value !== undefined)
+
+	return hasSecretKey || hasOtherConfig
 }
 }

+ 99 - 89
src/shared/globalState.ts

@@ -1,90 +1,100 @@
-export type SecretKey =
-	| "apiKey"
-	| "glamaApiKey"
-	| "openRouterApiKey"
-	| "awsAccessKey"
-	| "awsSecretKey"
-	| "awsSessionToken"
-	| "openAiApiKey"
-	| "geminiApiKey"
-	| "openAiNativeApiKey"
-	| "deepSeekApiKey"
-	| "mistralApiKey"
-	| "unboundApiKey"
-	| "requestyApiKey"
+// Define the array first with 'as const' to create a readonly tuple type
+export const SECRET_KEYS = [
+	"apiKey",
+	"glamaApiKey",
+	"openRouterApiKey",
+	"awsAccessKey",
+	"awsSecretKey",
+	"awsSessionToken",
+	"openAiApiKey",
+	"geminiApiKey",
+	"openAiNativeApiKey",
+	"deepSeekApiKey",
+	"mistralApiKey",
+	"unboundApiKey",
+	"requestyApiKey",
+] as const
 
 
-export type GlobalStateKey =
-	| "apiProvider"
-	| "apiModelId"
-	| "glamaModelId"
-	| "glamaModelInfo"
-	| "awsRegion"
-	| "awsUseCrossRegionInference"
-	| "awsProfile"
-	| "awsUseProfile"
-	| "vertexProjectId"
-	| "vertexRegion"
-	| "lastShownAnnouncementId"
-	| "customInstructions"
-	| "alwaysAllowReadOnly"
-	| "alwaysAllowWrite"
-	| "alwaysAllowExecute"
-	| "alwaysAllowBrowser"
-	| "alwaysAllowMcp"
-	| "alwaysAllowModeSwitch"
-	| "taskHistory"
-	| "openAiBaseUrl"
-	| "openAiModelId"
-	| "openAiCustomModelInfo"
-	| "openAiUseAzure"
-	| "ollamaModelId"
-	| "ollamaBaseUrl"
-	| "lmStudioModelId"
-	| "lmStudioBaseUrl"
-	| "lmStudioDraftModelId"
-	| "lmStudioSpeculativeDecodingEnabled"
-	| "anthropicBaseUrl"
-	| "azureApiVersion"
-	| "openAiStreamingEnabled"
-	| "openRouterModelId"
-	| "openRouterModelInfo"
-	| "openRouterBaseUrl"
-	| "openRouterUseMiddleOutTransform"
-	| "allowedCommands"
-	| "soundEnabled"
-	| "soundVolume"
-	| "diffEnabled"
-	| "enableCheckpoints"
-	| "checkpointStorage"
-	| "browserViewportSize"
-	| "screenshotQuality"
-	| "fuzzyMatchThreshold"
-	| "preferredLanguage" // Language setting for Cline's communication
-	| "writeDelayMs"
-	| "terminalOutputLineLimit"
-	| "mcpEnabled"
-	| "enableMcpServerCreation"
-	| "alwaysApproveResubmit"
-	| "requestDelaySeconds"
-	| "rateLimitSeconds"
-	| "currentApiConfigName"
-	| "listApiConfigMeta"
-	| "vsCodeLmModelSelector"
-	| "mode"
-	| "modeApiConfigs"
-	| "customModePrompts"
-	| "customSupportPrompts"
-	| "enhancementApiConfigId"
-	| "experiments" // Map of experiment IDs to their enabled state
-	| "autoApprovalEnabled"
-	| "customModes" // Array of custom modes
-	| "unboundModelId"
-	| "requestyModelId"
-	| "requestyModelInfo"
-	| "unboundModelInfo"
-	| "modelTemperature"
-	| "modelMaxTokens"
-	| "anthropicThinking" // TODO: Rename to `modelMaxThinkingTokens`.
-	| "mistralCodestralUrl"
-	| "maxOpenTabsContext"
-	| "browserToolEnabled" // Setting to enable/disable the browser tool
+// Derive the type from the array - creates a union of string literals
+export type SecretKey = (typeof SECRET_KEYS)[number]
+
+// Define the array first with 'as const' to create a readonly tuple type
+export const GLOBAL_STATE_KEYS = [
+	"apiProvider",
+	"apiModelId",
+	"glamaModelId",
+	"glamaModelInfo",
+	"awsRegion",
+	"awsUseCrossRegionInference",
+	"awsProfile",
+	"awsUseProfile",
+	"vertexProjectId",
+	"vertexRegion",
+	"lastShownAnnouncementId",
+	"customInstructions",
+	"alwaysAllowReadOnly",
+	"alwaysAllowWrite",
+	"alwaysAllowExecute",
+	"alwaysAllowBrowser",
+	"alwaysAllowMcp",
+	"alwaysAllowModeSwitch",
+	"taskHistory",
+	"openAiBaseUrl",
+	"openAiModelId",
+	"openAiCustomModelInfo",
+	"openAiUseAzure",
+	"ollamaModelId",
+	"ollamaBaseUrl",
+	"lmStudioModelId",
+	"lmStudioBaseUrl",
+	"anthropicBaseUrl",
+	"modelMaxThinkingTokens",
+	"azureApiVersion",
+	"openAiStreamingEnabled",
+	"openRouterModelId",
+	"openRouterModelInfo",
+	"openRouterBaseUrl",
+	"openRouterUseMiddleOutTransform",
+	"allowedCommands",
+	"soundEnabled",
+	"soundVolume",
+	"diffEnabled",
+	"enableCheckpoints",
+	"checkpointStorage",
+	"browserViewportSize",
+	"screenshotQuality",
+	"fuzzyMatchThreshold",
+	"preferredLanguage", // Language setting for Cline's communication
+	"writeDelayMs",
+	"terminalOutputLineLimit",
+	"mcpEnabled",
+	"enableMcpServerCreation",
+	"alwaysApproveResubmit",
+	"requestDelaySeconds",
+	"rateLimitSeconds",
+	"currentApiConfigName",
+	"listApiConfigMeta",
+	"vsCodeLmModelSelector",
+	"mode",
+	"modeApiConfigs",
+	"customModePrompts",
+	"customSupportPrompts",
+	"enhancementApiConfigId",
+	"experiments", // Map of experiment IDs to their enabled state
+	"autoApprovalEnabled",
+	"customModes", // Array of custom modes
+	"unboundModelId",
+	"requestyModelId",
+	"requestyModelInfo",
+	"unboundModelInfo",
+	"modelTemperature",
+	"modelMaxTokens",
+	"mistralCodestralUrl",
+	"maxOpenTabsContext",
+	"browserToolEnabled",
+	"lmStudioSpeculativeDecodingEnabled",
+	"lmStudioDraftModelId",
+] as const
+
+// Derive the type from the array - creates a union of string literals
+export type GlobalStateKey = (typeof GLOBAL_STATE_KEYS)[number]