Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/types/src/codebase-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const codebaseIndexConfigSchema = z.object({
// OpenAI Compatible specific fields
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
codebaseIndexOpenAiCompatibleUseFloatEncoding: z.boolean().optional(),
})

export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
Expand Down Expand Up @@ -62,6 +63,7 @@ export const codebaseIndexProviderSchema = z.object({
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
codebaseIndexOpenAiCompatibleUseFloatEncoding: z.boolean().optional(),
codebaseIndexGeminiApiKey: z.string().optional(),
codebaseIndexMistralApiKey: z.string().optional(),
})
Expand Down
27 changes: 27 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,9 @@ export const webviewMessageHandler = async (

const settings = message.codeIndexSettings

// Debug logging for settings save
console.log("[DEBUG] saveCodeIndexSettingsAtomic called with settings:", JSON.stringify(settings, null, 2))

try {
// Check if embedder provider has changed
const currentConfig = getGlobalState("codebaseIndexConfig") || {}
Expand All @@ -2047,6 +2050,8 @@ export const webviewMessageHandler = async (
codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore,
}

console.log("[DEBUG] globalStateConfig to be saved:", JSON.stringify(globalStateConfig, null, 2))

// Save global state first
await updateGlobalState("codebaseIndexConfig", globalStateConfig)

Expand Down Expand Up @@ -2076,26 +2081,35 @@ export const webviewMessageHandler = async (
)
}

console.log("[DEBUG] Secrets saved successfully")

// Send success response first - settings are saved regardless of validation
await provider.postMessageToWebview({
type: "codeIndexSettingsSaved",
success: true,
settings: globalStateConfig,
})

console.log("[DEBUG] Success response sent to webview")

// Update webview state
await provider.postStateToWebview()

// Then handle validation and initialization for the current workspace
const currentCodeIndexManager = provider.getCurrentWorkspaceCodeIndexManager()
if (currentCodeIndexManager) {
console.log("[DEBUG] CodeIndexManager found, handling settings change")

// If embedder provider changed, perform proactive validation
if (embedderProviderChanged) {
console.log("[DEBUG] Embedder provider changed, performing validation")
try {
// Force handleSettingsChange which will trigger validation
await currentCodeIndexManager.handleSettingsChange()
console.log("[DEBUG] Settings change handled successfully")
} catch (error) {
// Validation failed - the error state is already set by handleSettingsChange
console.log("[DEBUG] Embedder validation failed:", error)
provider.log(
`Embedder validation failed after provider change: ${error instanceof Error ? error.message : String(error)}`,
)
Expand All @@ -2109,10 +2123,13 @@ export const webviewMessageHandler = async (
}
} else {
// No provider change, just handle settings normally
console.log("[DEBUG] No provider change, handling settings normally")
try {
await currentCodeIndexManager.handleSettingsChange()
console.log("[DEBUG] Settings change handled successfully")
} catch (error) {
// Log but don't fail - settings are saved
console.log("[DEBUG] Settings change handling error:", error)
provider.log(
`Settings change handling error: ${error instanceof Error ? error.message : String(error)}`,
)
Expand All @@ -2124,11 +2141,15 @@ export const webviewMessageHandler = async (

// Auto-start indexing if now enabled and configured
if (currentCodeIndexManager.isFeatureEnabled && currentCodeIndexManager.isFeatureConfigured) {
console.log("[DEBUG] Feature enabled and configured, checking initialization")
if (!currentCodeIndexManager.isInitialized) {
console.log("[DEBUG] Manager not initialized, initializing now")
try {
await currentCodeIndexManager.initialize(provider.contextProxy)
console.log("[DEBUG] Code index manager initialized successfully")
provider.log(`Code index manager initialized after settings save`)
} catch (error) {
console.log("[DEBUG] Code index initialization failed:", error)
provider.log(
`Code index initialization failed: ${error instanceof Error ? error.message : String(error)}`,
)
Expand All @@ -2138,10 +2159,15 @@ export const webviewMessageHandler = async (
values: currentCodeIndexManager.getCurrentStatus(),
})
}
} else {
console.log("[DEBUG] Manager already initialized")
}
} else {
console.log("[DEBUG] Feature not enabled or not configured")
}
} else {
// No workspace open - send error status
console.log("[DEBUG] No workspace open, cannot save code index settings")
provider.log("Cannot save code index settings: No workspace folder open")
await provider.postMessageToWebview({
type: "indexingStatusUpdate",
Expand All @@ -2155,6 +2181,7 @@ export const webviewMessageHandler = async (
})
}
} catch (error) {
console.log("[DEBUG] Error saving code index settings:", error)
provider.log(`Error saving code index settings: ${error.message || error}`)
await provider.postMessageToWebview({
type: "codeIndexSettingsSaved",
Expand Down
4 changes: 4 additions & 0 deletions src/services/code-index/__tests__/config-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ describe("CodeIndexConfigManager", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
useFloatEncoding: false,
},
qdrantUrl: "http://qdrant.local",
qdrantApiKey: "test-qdrant-key",
Expand Down Expand Up @@ -211,6 +212,7 @@ describe("CodeIndexConfigManager", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
useFloatEncoding: false,
},
qdrantUrl: "http://qdrant.local",
qdrantApiKey: "test-qdrant-key",
Expand Down Expand Up @@ -248,6 +250,7 @@ describe("CodeIndexConfigManager", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
useFloatEncoding: false,
// modelDimension is undefined when not set
},
qdrantUrl: "http://qdrant.local",
Expand Down Expand Up @@ -287,6 +290,7 @@ describe("CodeIndexConfigManager", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
useFloatEncoding: false,
},
geminiOptions: undefined,
qdrantUrl: "http://qdrant.local",
Expand Down
112 changes: 94 additions & 18 deletions src/services/code-index/__tests__/service-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,19 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith({
openAiNativeApiKey: "test-api-key",
openAiEmbeddingModelId: testModelId,
})
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith(
{
openAiNativeApiKey: "test-api-key",
openAiEmbeddingModelId: testModelId,
},
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should pass model ID to Ollama embedder when using Ollama provider", () => {
Expand All @@ -95,10 +104,19 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
ollamaBaseUrl: "http://localhost:11434",
ollamaModelId: testModelId,
})
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith(
{
ollamaBaseUrl: "http://localhost:11434",
ollamaModelId: testModelId,
},
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should handle undefined model ID for OpenAI embedder", () => {
Expand All @@ -116,10 +134,19 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith({
openAiNativeApiKey: "test-api-key",
openAiEmbeddingModelId: undefined,
})
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith(
{
openAiNativeApiKey: "test-api-key",
openAiEmbeddingModelId: undefined,
},
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should handle undefined model ID for Ollama embedder", () => {
Expand All @@ -137,10 +164,19 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
ollamaBaseUrl: "http://localhost:11434",
ollamaModelId: undefined,
})
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith(
{
ollamaBaseUrl: "http://localhost:11434",
ollamaModelId: undefined,
},
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should throw error when OpenAI API key is missing", () => {
Expand Down Expand Up @@ -182,6 +218,7 @@ describe("CodeIndexServiceFactory", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-api-key",
useFloatEncoding: false,
},
}
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
Expand All @@ -194,6 +231,15 @@ describe("CodeIndexServiceFactory", () => {
"https://api.example.com/v1",
"test-api-key",
testModelId,
undefined, // modelDimension
false, // useFloatEncoding
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

Expand All @@ -205,6 +251,7 @@ describe("CodeIndexServiceFactory", () => {
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-api-key",
useFloatEncoding: false,
},
}
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
Expand All @@ -217,6 +264,15 @@ describe("CodeIndexServiceFactory", () => {
"https://api.example.com/v1",
"test-api-key",
undefined,
undefined, // modelDimension
false, // useFloatEncoding
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

Expand Down Expand Up @@ -279,7 +335,17 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedGeminiEmbedder).toHaveBeenCalledWith("test-gemini-api-key", undefined)
expect(MockedGeminiEmbedder).toHaveBeenCalledWith(
"test-gemini-api-key",
undefined,
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should create GeminiEmbedder with specified modelId", () => {
Expand All @@ -297,7 +363,17 @@ describe("CodeIndexServiceFactory", () => {
factory.createEmbedder()

// Assert
expect(MockedGeminiEmbedder).toHaveBeenCalledWith("test-gemini-api-key", "text-embedding-004")
expect(MockedGeminiEmbedder).toHaveBeenCalledWith(
"test-gemini-api-key",
"text-embedding-004",
expect.objectContaining({
append: expect.any(Function),
appendLine: expect.any(Function),
clear: expect.any(Function),
dispose: expect.any(Function),
show: expect.any(Function),
}), // outputChannel
)
})

it("should throw error when Gemini API key is missing", () => {
Expand Down
11 changes: 9 additions & 2 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class CodeIndexConfigManager {
private modelDimension?: number
private openAiOptions?: ApiHandlerOptions
private ollamaOptions?: ApiHandlerOptions
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; useFloatEncoding?: boolean }
private geminiOptions?: { apiKey: string }
private mistralOptions?: { apiKey: string }
private qdrantUrl?: string = "http://localhost:6333"
Expand Down Expand Up @@ -67,6 +67,8 @@ export class CodeIndexConfigManager {
// Fix: Read OpenAI Compatible settings from the correct location within codebaseIndexConfig
const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
const openAiCompatibleUseFloatEncoding =
codebaseIndexConfig.codebaseIndexOpenAiCompatibleUseFloatEncoding ?? false
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""

Expand Down Expand Up @@ -119,6 +121,7 @@ export class CodeIndexConfigManager {
? {
baseUrl: openAiCompatibleBaseUrl,
apiKey: openAiCompatibleApiKey,
useFloatEncoding: openAiCompatibleUseFloatEncoding,
}
: undefined

Expand Down Expand Up @@ -158,6 +161,7 @@ export class CodeIndexConfigManager {
ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl ?? "",
openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "",
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
openAiCompatibleUseFloatEncoding: this.openAiCompatibleOptions?.useFloatEncoding ?? false,
geminiApiKey: this.geminiOptions?.apiKey ?? "",
mistralApiKey: this.mistralOptions?.apiKey ?? "",
qdrantUrl: this.qdrantUrl ?? "",
Expand Down Expand Up @@ -252,6 +256,7 @@ export class CodeIndexConfigManager {
const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? ""
const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? ""
const prevOpenAiCompatibleUseFloatEncoding = prev?.openAiCompatibleUseFloatEncoding ?? false
const prevModelDimension = prev?.modelDimension
const prevGeminiApiKey = prev?.geminiApiKey ?? ""
const prevMistralApiKey = prev?.mistralApiKey ?? ""
Expand Down Expand Up @@ -289,6 +294,7 @@ export class CodeIndexConfigManager {
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentOpenAiCompatibleUseFloatEncoding = this.openAiCompatibleOptions?.useFloatEncoding ?? false
const currentModelDimension = this.modelDimension
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
Expand All @@ -305,7 +311,8 @@ export class CodeIndexConfigManager {

if (
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey ||
prevOpenAiCompatibleUseFloatEncoding !== currentOpenAiCompatibleUseFloatEncoding
) {
return true
}
Expand Down
Loading
Loading