Skip to content

Commit 7e4761f

Browse files
authored
Merge pull request #1394 from RooVetGit/fix_context_proxy
Revert "Better encapsulation for API config"
2 parents 957d022 + f683e45 commit 7e4761f

File tree

4 files changed

+59
-222
lines changed

4 files changed

+59
-222
lines changed

src/core/__tests__/contextProxy.test.ts

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,11 @@ import * as vscode from "vscode"
22
import { ContextProxy } from "../contextProxy"
33
import { logger } from "../../utils/logging"
44
import { GLOBAL_STATE_KEYS, SECRET_KEYS } from "../../shared/globalState"
5-
import { ApiConfiguration } from "../../shared/api"
65

76
// Mock shared/globalState
87
jest.mock("../../shared/globalState", () => ({
98
GLOBAL_STATE_KEYS: ["apiProvider", "apiModelId", "mode"],
109
SECRET_KEYS: ["apiKey", "openAiApiKey"],
11-
GlobalStateKey: {},
12-
SecretKey: {},
13-
}))
14-
15-
// Mock shared/api
16-
jest.mock("../../shared/api", () => ({
17-
API_CONFIG_KEYS: ["apiProvider", "apiModelId"],
18-
ApiConfiguration: {},
1910
}))
2011

2112
// Mock VSCode API
@@ -162,82 +153,5 @@ describe("ContextProxy", () => {
162153
const storedValue = await proxy.getSecret("api-key")
163154
expect(storedValue).toBeUndefined()
164155
})
165-
166-
describe("getApiConfiguration", () => {
167-
it("should combine global state and secrets into a single ApiConfiguration object", async () => {
168-
// Mock data in state cache
169-
await proxy.updateGlobalState("apiProvider", "anthropic")
170-
await proxy.updateGlobalState("apiModelId", "test-model")
171-
// Mock data in secrets cache
172-
await proxy.storeSecret("apiKey", "test-api-key")
173-
174-
const config = proxy.getApiConfiguration()
175-
176-
// Should contain values from global state
177-
expect(config.apiProvider).toBe("anthropic")
178-
expect(config.apiModelId).toBe("test-model")
179-
// Should contain values from secrets
180-
expect(config.apiKey).toBe("test-api-key")
181-
})
182-
183-
it("should handle special case for apiProvider defaulting", async () => {
184-
// Clear apiProvider but set apiKey
185-
await proxy.updateGlobalState("apiProvider", undefined)
186-
await proxy.storeSecret("apiKey", "test-api-key")
187-
188-
const config = proxy.getApiConfiguration()
189-
190-
// Should default to anthropic when apiKey exists
191-
expect(config.apiProvider).toBe("anthropic")
192-
193-
// Clear both apiProvider and apiKey
194-
await proxy.updateGlobalState("apiProvider", undefined)
195-
await proxy.storeSecret("apiKey", undefined)
196-
197-
const configWithoutKey = proxy.getApiConfiguration()
198-
199-
// Should default to openrouter when no apiKey exists
200-
expect(configWithoutKey.apiProvider).toBe("openrouter")
201-
})
202-
})
203-
204-
describe("updateApiConfiguration", () => {
205-
it("should update both global state and secrets", async () => {
206-
const apiConfig: ApiConfiguration = {
207-
apiProvider: "anthropic",
208-
apiModelId: "claude-latest",
209-
apiKey: "test-api-key",
210-
}
211-
212-
await proxy.updateApiConfiguration(apiConfig)
213-
214-
// Should update global state
215-
expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "anthropic")
216-
expect(mockGlobalState.update).toHaveBeenCalledWith("apiModelId", "claude-latest")
217-
// Should update secrets
218-
expect(mockSecrets.store).toHaveBeenCalledWith("apiKey", "test-api-key")
219-
220-
// Check that values are in cache
221-
expect(proxy.getGlobalState("apiProvider")).toBe("anthropic")
222-
expect(proxy.getGlobalState("apiModelId")).toBe("claude-latest")
223-
expect(proxy.getSecret("apiKey")).toBe("test-api-key")
224-
})
225-
226-
it("should ignore keys that aren't in either GLOBAL_STATE_KEYS or SECRET_KEYS", async () => {
227-
// Use type assertion to add an invalid key
228-
const apiConfig = {
229-
apiProvider: "anthropic",
230-
invalidKey: "should be ignored",
231-
} as ApiConfiguration & { invalidKey: string }
232-
233-
await proxy.updateApiConfiguration(apiConfig)
234-
235-
// Should update keys in GLOBAL_STATE_KEYS
236-
expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "anthropic")
237-
// Should not call update/store for invalid keys
238-
expect(mockGlobalState.update).not.toHaveBeenCalledWith("invalidKey", expect.anything())
239-
expect(mockSecrets.store).not.toHaveBeenCalledWith("invalidKey", expect.anything())
240-
})
241-
})
242156
})
243157
})

src/core/contextProxy.ts

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as vscode from "vscode"
22
import { logger } from "../utils/logging"
3-
import { ApiConfiguration, API_CONFIG_KEYS } from "../shared/api"
4-
import { GLOBAL_STATE_KEYS, SECRET_KEYS, GlobalStateKey, SecretKey } from "../shared/globalState"
3+
import { GLOBAL_STATE_KEYS, SECRET_KEYS } from "../shared/globalState"
54

65
export class ContextProxy {
76
private readonly originalContext: vscode.ExtensionContext
@@ -83,6 +82,7 @@ export class ContextProxy {
8382
getSecret(key: string): string | undefined {
8483
return this.secretCache.get(key)
8584
}
85+
8686
storeSecret(key: string, value?: string): Thenable<void> {
8787
// Update cache
8888
this.secretCache.set(key, value)
@@ -93,62 +93,4 @@ export class ContextProxy {
9393
return this.originalContext.secrets.store(key, value)
9494
}
9595
}
96-
97-
/**
98-
* Gets a complete ApiConfiguration object by fetching values
99-
* from both global state and secrets storage
100-
*/
101-
getApiConfiguration(): ApiConfiguration {
102-
// Create an empty ApiConfiguration object
103-
const config: ApiConfiguration = {}
104-
105-
// Add all API-related keys from global state
106-
for (const key of API_CONFIG_KEYS) {
107-
const value = this.getGlobalState(key)
108-
if (value !== undefined) {
109-
// Use type assertion to avoid TypeScript error
110-
;(config as any)[key] = value
111-
}
112-
}
113-
114-
// Add all secret values
115-
for (const key of SECRET_KEYS) {
116-
const value = this.getSecret(key)
117-
if (value !== undefined) {
118-
// Use type assertion to avoid TypeScript error
119-
;(config as any)[key] = value
120-
}
121-
}
122-
123-
// Handle special case for apiProvider if needed (same logic as current implementation)
124-
if (!config.apiProvider) {
125-
if (config.apiKey) {
126-
config.apiProvider = "anthropic"
127-
} else {
128-
config.apiProvider = "openrouter"
129-
}
130-
}
131-
132-
return config
133-
}
134-
135-
/**
136-
* Updates an ApiConfiguration by persisting each property
137-
* to the appropriate storage (global state or secrets)
138-
*/
139-
async updateApiConfiguration(apiConfiguration: ApiConfiguration): Promise<void> {
140-
const promises: Array<Thenable<void>> = []
141-
142-
// For each property, update the appropriate storage
143-
Object.entries(apiConfiguration).forEach(([key, value]) => {
144-
if (SECRET_KEYS.includes(key as SecretKey)) {
145-
promises.push(this.storeSecret(key, value))
146-
} else if (API_CONFIG_KEYS.includes(key as GlobalStateKey)) {
147-
promises.push(this.updateGlobalState(key, value))
148-
}
149-
// Ignore keys that aren't in either list
150-
})
151-
152-
await Promise.all(promises)
153-
}
15496
}

src/core/webview/ClineProvider.ts

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,8 +1659,20 @@ export class ClineProvider implements vscode.WebviewViewProvider {
16591659
}
16601660
}
16611661

1662-
// Update all configuration values through the contextProxy
1663-
await this.contextProxy.updateApiConfiguration(apiConfiguration)
1662+
// Create an array of promises to update state
1663+
const promises: Promise<any>[] = []
1664+
1665+
// For each property in apiConfiguration, update the appropriate state
1666+
Object.entries(apiConfiguration).forEach(([key, value]) => {
1667+
// Check if this key is a secret
1668+
if (SECRET_KEYS.includes(key as SecretKey)) {
1669+
promises.push(this.storeSecret(key as SecretKey, value))
1670+
} else {
1671+
promises.push(this.updateGlobalState(key as GlobalStateKey, value))
1672+
}
1673+
})
1674+
1675+
await Promise.all(promises)
16641676

16651677
if (this.cline) {
16661678
this.cline.api = buildApiHandler(apiConfiguration)
@@ -2061,32 +2073,62 @@ export class ClineProvider implements vscode.WebviewViewProvider {
20612073
*/
20622074

20632075
async getState() {
2064-
// Get ApiConfiguration directly from contextProxy
2065-
const apiConfiguration = this.contextProxy.getApiConfiguration()
2066-
2067-
// Create an object to store all fetched values (excluding API config which we already have)
2068-
const stateValues: Record<GlobalStateKey, any> = {} as Record<GlobalStateKey, any>
2076+
// Create an object to store all fetched values
2077+
const stateValues: Record<GlobalStateKey | SecretKey, any> = {} as Record<GlobalStateKey | SecretKey, any>
2078+
const secretValues: Record<SecretKey, any> = {} as Record<SecretKey, any>
20692079

2070-
// Create promise arrays for global state
2071-
const statePromises = GLOBAL_STATE_KEYS
2072-
// Filter out API config keys since we already have them
2073-
.filter((key) => !API_CONFIG_KEYS.includes(key))
2074-
.map((key) => this.getGlobalState(key))
2080+
// Create promise arrays for global state and secrets
2081+
const statePromises = GLOBAL_STATE_KEYS.map((key) => this.getGlobalState(key))
2082+
const secretPromises = SECRET_KEYS.map((key) => this.getSecret(key))
20752083

20762084
// Add promise for custom modes which is handled separately
20772085
const customModesPromise = this.customModesManager.getCustomModes()
20782086

20792087
let idx = 0
2080-
const valuePromises = await Promise.all([...statePromises, customModesPromise])
2088+
const valuePromises = await Promise.all([...statePromises, ...secretPromises, customModesPromise])
20812089

2082-
// Populate stateValues
2083-
GLOBAL_STATE_KEYS.filter((key) => !API_CONFIG_KEYS.includes(key)).forEach((key) => {
2090+
// Populate stateValues and secretValues
2091+
GLOBAL_STATE_KEYS.forEach((key, _) => {
20842092
stateValues[key] = valuePromises[idx]
20852093
idx = idx + 1
20862094
})
20872095

2096+
SECRET_KEYS.forEach((key, index) => {
2097+
secretValues[key] = valuePromises[idx]
2098+
idx = idx + 1
2099+
})
2100+
20882101
let customModes = valuePromises[idx] as ModeConfig[] | undefined
20892102

2103+
// Determine apiProvider with the same logic as before
2104+
let apiProvider: ApiProvider
2105+
if (stateValues.apiProvider) {
2106+
apiProvider = stateValues.apiProvider
2107+
} else {
2108+
// Either new user or legacy user that doesn't have the apiProvider stored in state
2109+
// (If they're using OpenRouter or Bedrock, then apiProvider state will exist)
2110+
if (secretValues.apiKey) {
2111+
apiProvider = "anthropic"
2112+
} else {
2113+
// New users should default to openrouter
2114+
apiProvider = "openrouter"
2115+
}
2116+
}
2117+
2118+
// Build the apiConfiguration object combining state values and secrets
2119+
// Using the dynamic approach with API_CONFIG_KEYS
2120+
const apiConfiguration: ApiConfiguration = {
2121+
// Dynamically add all API-related keys from stateValues
2122+
...Object.fromEntries(API_CONFIG_KEYS.map((key) => [key, stateValues[key]])),
2123+
// Add all secrets
2124+
...secretValues,
2125+
}
2126+
2127+
// Ensure apiProvider is set properly if not already in state
2128+
if (!apiConfiguration.apiProvider) {
2129+
apiConfiguration.apiProvider = apiProvider
2130+
}
2131+
20902132
// Return the same structure as before
20912133
return {
20922134
apiConfiguration,

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,6 @@ jest.mock("../../contextProxy", () => {
3434
.mockImplementation((key, value) =>
3535
value ? context.secrets.store(key, value) : context.secrets.delete(key),
3636
),
37-
getApiConfiguration: jest.fn().mockImplementation(() => ({
38-
apiProvider: "openrouter",
39-
// Add other common properties
40-
})),
41-
updateApiConfiguration: jest.fn().mockImplementation(async (apiConfiguration) => {
42-
// Mock implementation that simulates updating state and secrets
43-
for (const [key, value] of Object.entries(apiConfiguration)) {
44-
if (key === "apiKey" || key === "openAiApiKey") {
45-
context.secrets.store(key, value)
46-
} else {
47-
context.globalState.update(key, value)
48-
}
49-
}
50-
return Promise.resolve()
51-
}),
5237
saveChanges: jest.fn().mockResolvedValue(undefined),
5338
dispose: jest.fn().mockResolvedValue(undefined),
5439
hasPendingChanges: jest.fn().mockReturnValue(false),
@@ -1594,51 +1579,5 @@ describe("ContextProxy integration", () => {
15941579
expect(mockContextProxy.getGlobalState).toBeDefined()
15951580
expect(mockContextProxy.updateGlobalState).toBeDefined()
15961581
expect(mockContextProxy.storeSecret).toBeDefined()
1597-
expect(mockContextProxy.getApiConfiguration).toBeDefined()
1598-
expect(mockContextProxy.updateApiConfiguration).toBeDefined()
1599-
})
1600-
1601-
test("getState uses contextProxy.getApiConfiguration", async () => {
1602-
// Setup mock API configuration
1603-
const mockApiConfig = {
1604-
apiProvider: "anthropic",
1605-
apiModelId: "claude-latest",
1606-
apiKey: "test-api-key",
1607-
}
1608-
mockContextProxy.getApiConfiguration.mockReturnValue(mockApiConfig)
1609-
1610-
// Get state
1611-
const state = await provider.getState()
1612-
1613-
// Verify getApiConfiguration was called
1614-
expect(mockContextProxy.getApiConfiguration).toHaveBeenCalled()
1615-
// Verify state has the API configuration from contextProxy
1616-
expect(state.apiConfiguration).toBe(mockApiConfig)
1617-
})
1618-
1619-
test("updateApiConfiguration uses contextProxy.updateApiConfiguration", async () => {
1620-
// Setup test config
1621-
const testApiConfig = {
1622-
apiProvider: "anthropic",
1623-
apiModelId: "claude-latest",
1624-
apiKey: "test-api-key",
1625-
}
1626-
1627-
// Mock methods needed for the test
1628-
provider.configManager = {
1629-
listConfig: jest.fn().mockResolvedValue([]),
1630-
setModeConfig: jest.fn(),
1631-
} as any
1632-
1633-
// Mock getState for mode
1634-
jest.spyOn(provider, "getState").mockResolvedValue({
1635-
mode: "code",
1636-
} as any)
1637-
1638-
// Call the private method - need to use any to access it
1639-
await (provider as any).updateApiConfiguration(testApiConfig)
1640-
1641-
// Verify contextProxy.updateApiConfiguration was called with the right config
1642-
expect(mockContextProxy.updateApiConfiguration).toHaveBeenCalledWith(testApiConfig)
16431582
})
16441583
})

0 commit comments

Comments
 (0)