fix: make Qdrant configuration workspace-specific (fixes #9063)

- Added workspace state management to ContextProxy for Qdrant config
- Modified CodeIndexConfigManager to use workspace-specific storage
- Updated webview message handler to read/write workspace state
- Added fallback from workspace to global state for backward compatibility
- Updated tests to reflect workspace-specific configuration

This allows different VS Code workspaces to connect to different Qdrant
servers without manual reconfiguration when switching between them.
This commit is contained in:
Roo Code 2025-11-05 23:45:50 +00:00
parent 65230f1f5c
commit 2748b753c1
4 changed files with 321 additions and 117 deletions

View file

@ -39,6 +39,7 @@ export class ContextProxy {
private stateCache: GlobalState
private secretCache: SecretState
private workspaceStateCache: Map<string, any> = new Map()
private _isInitialized = false
constructor(context: vscode.ExtensionContext) {
@ -187,6 +188,73 @@ export class ContextProxy {
return Object.fromEntries(GLOBAL_STATE_KEYS.map((key) => [key, this.getGlobalState(key)]))
}
/**
* Workspace State Management
* These methods handle workspace-specific state storage
*/
getWorkspaceState<T>(key: string): T | undefined
getWorkspaceState<T>(key: string, defaultValue: T): T
getWorkspaceState<T>(key: string, defaultValue?: T): T | undefined {
// Check cache first
if (this.workspaceStateCache.has(key)) {
return this.workspaceStateCache.get(key) as T
}
// Get from VS Code workspace state
const value = this.originalContext.workspaceState.get<T>(key)
// Update cache
if (value !== undefined) {
this.workspaceStateCache.set(key, value)
}
return value !== undefined ? value : defaultValue
}
async updateWorkspaceState<T>(key: string, value: T | undefined): Promise<void> {
// Update cache
if (value === undefined) {
this.workspaceStateCache.delete(key)
} else {
this.workspaceStateCache.set(key, value)
}
// Persist to VS Code workspace state
await this.originalContext.workspaceState.update(key, value)
}
/**
* Get Qdrant configuration from workspace state with fallback to global state
* This allows migration from global to workspace-specific configuration
*/
getQdrantConfig(): { url: string; apiKey: string } {
// Try workspace state first
let url = this.getWorkspaceState<string>("codebaseIndexQdrantUrl")
let apiKey = this.getWorkspaceState<string>("codeIndexQdrantApiKey")
// Fallback to global state if not in workspace state
if (!url) {
const globalConfig = this.getGlobalState("codebaseIndexConfig") || {}
url = globalConfig.codebaseIndexQdrantUrl || "http://localhost:6333"
}
// API key from secrets if not in workspace state
if (!apiKey) {
apiKey = this.getSecret("codeIndexQdrantApiKey" as SecretStateKey) || ""
}
return { url, apiKey }
}
/**
* Set Qdrant configuration in workspace state
*/
async setQdrantConfig(url: string, apiKey: string): Promise<void> {
await this.updateWorkspaceState("codebaseIndexQdrantUrl", url)
await this.updateWorkspaceState("codeIndexQdrantApiKey", apiKey)
}
/**
* ExtensionContext.secrets
* https://code.visualstudio.com/api/references/vscode-api#ExtensionContext.secrets

View file

@ -2508,11 +2508,18 @@ export const webviewMessageHandler = async (
const embedderProviderChanged =
currentConfig.codebaseIndexEmbedderProvider !== settings.codebaseIndexEmbedderProvider
// Save global state settings atomically
// Save Qdrant URL and API key to workspace state for workspace-specific configuration
if (settings.codebaseIndexQdrantUrl !== undefined || settings.codeIndexQdrantApiKey !== undefined) {
const qdrantUrl = settings.codebaseIndexQdrantUrl ?? "http://localhost:6333"
const qdrantApiKey = settings.codeIndexQdrantApiKey ?? ""
await provider.contextProxy.setQdrantConfig(qdrantUrl, qdrantApiKey)
}
// Save global state settings atomically (but without Qdrant config)
const globalStateConfig = {
...currentConfig,
codebaseIndexEnabled: settings.codebaseIndexEnabled,
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
// Don't save Qdrant config to global state anymore
codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider,
codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl,
codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId,
@ -2525,13 +2532,10 @@ export const webviewMessageHandler = async (
// Save global state first
await updateGlobalState("codebaseIndexConfig", globalStateConfig)
// Save secrets directly using context proxy
// Save secrets directly using context proxy (except Qdrant API key which is now workspace-specific)
if (settings.codeIndexOpenAiKey !== undefined) {
await provider.contextProxy.storeSecret("codeIndexOpenAiKey", settings.codeIndexOpenAiKey)
}
if (settings.codeIndexQdrantApiKey !== undefined) {
await provider.contextProxy.storeSecret("codeIndexQdrantApiKey", settings.codeIndexQdrantApiKey)
}
if (settings.codebaseIndexOpenAiCompatibleApiKey !== undefined) {
await provider.contextProxy.storeSecret(
"codebaseIndexOpenAiCompatibleApiKey",
@ -2690,7 +2694,11 @@ export const webviewMessageHandler = async (
case "requestCodeIndexSecretStatus": {
// Check if secrets are set using the VSCode context directly for async access
const hasOpenAiKey = !!(await provider.context.secrets.get("codeIndexOpenAiKey"))
const hasQdrantApiKey = !!(await provider.context.secrets.get("codeIndexQdrantApiKey"))
// Check Qdrant API key from workspace state
const qdrantConfig = provider.contextProxy.getQdrantConfig()
const hasQdrantApiKey = !!qdrantConfig.apiKey
const hasOpenAiCompatibleApiKey = !!(await provider.context.secrets.get(
"codebaseIndexOpenAiCompatibleApiKey",
))

File diff suppressed because it is too large Load diff

View file

@ -56,7 +56,6 @@ export class CodeIndexConfigManager {
const {
codebaseIndexEnabled,
codebaseIndexQdrantUrl,
codebaseIndexEmbedderProvider,
codebaseIndexEmbedderBaseUrl,
codebaseIndexEmbedderModelId,
@ -64,8 +63,10 @@ export class CodeIndexConfigManager {
codebaseIndexSearchMaxResults,
} = codebaseIndexConfig
// Get Qdrant configuration from workspace state (with fallback to global state)
const qdrantConfig = this.contextProxy?.getQdrantConfig() ?? { url: "http://localhost:6333", apiKey: "" }
const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? ""
// Fix: Read OpenAI Compatible settings from the correct location within codebaseIndexConfig
const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
@ -76,8 +77,8 @@ export class CodeIndexConfigManager {
// Update instance variables with configuration
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
this.qdrantUrl = codebaseIndexQdrantUrl
this.qdrantApiKey = qdrantApiKey ?? ""
this.qdrantUrl = qdrantConfig.url
this.qdrantApiKey = qdrantConfig.apiKey
this.searchMinScore = codebaseIndexSearchMinScore
this.searchMaxResults = codebaseIndexSearchMaxResults
@ -500,4 +501,13 @@ export class CodeIndexConfigManager {
public get currentSearchMaxResults(): number {
return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS
}
/**
* Save Qdrant configuration to workspace state
*/
public async saveQdrantConfig(url: string, apiKey: string): Promise<void> {
await this.contextProxy.setQdrantConfig(url, apiKey)
this.qdrantUrl = url
this.qdrantApiKey = apiKey
}
}