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
14 changes: 12 additions & 2 deletions src/i18n/locales/en/embeddings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
},
"vectorStore": {
"qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}",
"vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}"
"vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}",
"lancedbNotInstalled": "LanceDB is not installed. Please install it with: npm install @lancedb/lancedb. Error: {{errorMessage}}",
"lancedbInitFailed": "Failed to initialize LanceDB. Error: {{errorMessage}}",
"lancedbConnectionFailed": "Failed to connect to LanceDB. Error: {{errorMessage}}",
"chromadbNotInstalled": "ChromaDB is not installed. Please install it with: npm install chromadb. Error: {{errorMessage}}",
"chromadbInitFailed": "Failed to initialize ChromaDB at {{chromaUrl}}. Error: {{errorMessage}}",
"chromadbConnectionFailed": "Failed to connect to ChromaDB at {{chromaUrl}}. Please ensure ChromaDB is running. Error: {{errorMessage}}",
"sqliteNotInstalled": "SQLite is not installed. Please install it with: npm install better-sqlite3. Error: {{errorMessage}}",
"sqliteVssNotInstalled": "SQLite VSS extension is not installed. Please install it with: npm install sqlite-vss. Error: {{errorMessage}}",
"sqliteInitFailed": "Failed to initialize SQLite vector database. Error: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Authentication failed. Please check your API key in the settings.",
Expand All @@ -51,6 +60,7 @@
"vectorDimensionNotDeterminedOpenAiCompatible": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.",
"vectorDimensionNotDetermined": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Check model profiles or configuration.",
"qdrantUrlMissing": "Qdrant URL missing for vector store creation",
"codeIndexingNotConfigured": "Cannot create services: Code indexing is not properly configured"
"codeIndexingNotConfigured": "Cannot create services: Code indexing is not properly configured",
"invalidVectorDBProvider": "Invalid vector database provider: {{provider}}"
}
}
113 changes: 88 additions & 25 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ export class CodeIndexConfigManager {
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
private geminiOptions?: { apiKey: string }
private mistralOptions?: { apiKey: string }
// Vector database configuration
private vectorDBProvider: "qdrant" | "lancedb" | "chromadb" | "sqlite-vector" = "qdrant"
private qdrantUrl?: string = "http://localhost:6333"
private qdrantApiKey?: string
private chromadbUrl?: string = "http://localhost:8000"
private chromadbApiKey?: string
// Search configuration
private searchMinScore?: number
private searchMaxResults?: number

Expand All @@ -44,7 +49,9 @@ export class CodeIndexConfigManager {
// Load configuration from storage
const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
codebaseIndexEnabled: true,
codebaseIndexVectorDBProvider: "qdrant",
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexChromadbUrl: "http://localhost:8000",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
Expand All @@ -62,23 +69,32 @@ export class CodeIndexConfigManager {
codebaseIndexSearchMaxResults,
} = codebaseIndexConfig

// Extract new properties with optional chaining
const codebaseIndexVectorDBProvider = (codebaseIndexConfig as any).codebaseIndexVectorDBProvider
const codebaseIndexChromadbUrl = (codebaseIndexConfig as any).codebaseIndexChromadbUrl

const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? ""
// ChromaDB API key is not in the secret keys type yet, so we'll handle it differently
const chromadbApiKey = ""
// Fix: Read OpenAI Compatible settings from the correct location within codebaseIndexConfig
const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleBaseUrl = (codebaseIndexConfig as any).codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""

// Update instance variables with configuration
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
this.vectorDBProvider = codebaseIndexVectorDBProvider ?? "qdrant"
this.qdrantUrl = codebaseIndexQdrantUrl
this.qdrantApiKey = qdrantApiKey ?? ""
this.chromadbUrl = codebaseIndexChromadbUrl ?? "http://localhost:8000"
this.chromadbApiKey = chromadbApiKey ?? ""
this.searchMinScore = codebaseIndexSearchMinScore
this.searchMaxResults = codebaseIndexSearchMaxResults

// Validate and set model dimension
const rawDimension = codebaseIndexConfig.codebaseIndexEmbedderModelDimension
const rawDimension = (codebaseIndexConfig as any).codebaseIndexEmbedderModelDimension
if (rawDimension !== undefined && rawDimension !== null) {
const dimension = Number(rawDimension)
if (!isNaN(dimension) && dimension > 0) {
Expand Down Expand Up @@ -141,8 +157,11 @@ export class CodeIndexConfigManager {
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
mistralOptions?: { apiKey: string }
vectorDBProvider?: "qdrant" | "lancedb" | "chromadb" | "sqlite-vector"
qdrantUrl?: string
qdrantApiKey?: string
chromadbUrl?: string
chromadbApiKey?: string
searchMinScore?: number
}
requiresRestart: boolean
Expand All @@ -160,8 +179,11 @@ export class CodeIndexConfigManager {
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
geminiApiKey: this.geminiOptions?.apiKey ?? "",
mistralApiKey: this.mistralOptions?.apiKey ?? "",
vectorDBProvider: this.vectorDBProvider,
qdrantUrl: this.qdrantUrl ?? "",
qdrantApiKey: this.qdrantApiKey ?? "",
chromadbUrl: this.chromadbUrl ?? "",
chromadbApiKey: this.chromadbApiKey ?? "",
}

// Refresh secrets from VSCode storage to ensure we have the latest values
Expand All @@ -184,45 +206,64 @@ export class CodeIndexConfigManager {
openAiCompatibleOptions: this.openAiCompatibleOptions,
geminiOptions: this.geminiOptions,
mistralOptions: this.mistralOptions,
vectorDBProvider: this.vectorDBProvider,
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
chromadbUrl: this.chromadbUrl,
chromadbApiKey: this.chromadbApiKey,
searchMinScore: this.currentSearchMinScore,
},
requiresRestart,
}
}

/**
* Checks if the service is properly configured based on the embedder type.
* Checks if the service is properly configured based on the embedder type and vector DB provider.
*/
public isConfigured(): boolean {
// First check embedder configuration
let embedderConfigured = false

if (this.embedderProvider === "openai") {
const openAiKey = this.openAiOptions?.openAiNativeApiKey
const qdrantUrl = this.qdrantUrl
return !!(openAiKey && qdrantUrl)
embedderConfigured = !!openAiKey
} else if (this.embedderProvider === "ollama") {
// Ollama model ID has a default, so only base URL is strictly required for config
const ollamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl
const qdrantUrl = this.qdrantUrl
return !!(ollamaBaseUrl && qdrantUrl)
embedderConfigured = !!ollamaBaseUrl
} else if (this.embedderProvider === "openai-compatible") {
const baseUrl = this.openAiCompatibleOptions?.baseUrl
const apiKey = this.openAiCompatibleOptions?.apiKey
const qdrantUrl = this.qdrantUrl
const isConfigured = !!(baseUrl && apiKey && qdrantUrl)
return isConfigured
embedderConfigured = !!(baseUrl && apiKey)
} else if (this.embedderProvider === "gemini") {
const apiKey = this.geminiOptions?.apiKey
const qdrantUrl = this.qdrantUrl
const isConfigured = !!(apiKey && qdrantUrl)
return isConfigured
embedderConfigured = !!apiKey
} else if (this.embedderProvider === "mistral") {
const apiKey = this.mistralOptions?.apiKey
const qdrantUrl = this.qdrantUrl
const isConfigured = !!(apiKey && qdrantUrl)
return isConfigured
embedderConfigured = !!apiKey
}

// Then check vector database configuration
let vectorDBConfigured = false

switch (this.vectorDBProvider) {
case "qdrant":
vectorDBConfigured = !!this.qdrantUrl
break
case "chromadb":
vectorDBConfigured = !!this.chromadbUrl
break
case "lancedb":
case "sqlite-vector":
// These are embedded databases, no URL needed
vectorDBConfigured = true
break
default:
// Default to qdrant for backward compatibility
vectorDBConfigured = !!this.qdrantUrl
}
return false // Should not happen if embedderProvider is always set correctly

return embedderConfigured && vectorDBConfigured
}

/**
Expand Down Expand Up @@ -255,8 +296,11 @@ export class CodeIndexConfigManager {
const prevModelDimension = prev?.modelDimension
const prevGeminiApiKey = prev?.geminiApiKey ?? ""
const prevMistralApiKey = prev?.mistralApiKey ?? ""
const prevVectorDBProvider = prev?.vectorDBProvider ?? "qdrant"
const prevQdrantUrl = prev?.qdrantUrl ?? ""
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
const prevChromadbUrl = prev?.chromadbUrl ?? ""
const prevChromadbApiKey = prev?.chromadbApiKey ?? ""

// 1. Transition from disabled/unconfigured to enabled/configured
if ((!prevEnabled || !prevConfigured) && this.codebaseIndexEnabled && nowConfigured) {
Expand All @@ -279,21 +323,28 @@ export class CodeIndexConfigManager {
return false
}

// Provider change
if (prevProvider !== this.embedderProvider) {
return true
}

// Authentication changes (API keys)
// Get current values
const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentModelDimension = this.modelDimension
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
const currentVectorDBProvider = this.vectorDBProvider ?? "qdrant"
const currentQdrantUrl = this.qdrantUrl ?? ""
const currentQdrantApiKey = this.qdrantApiKey ?? ""
const currentChromadbUrl = this.chromadbUrl ?? ""
const currentChromadbApiKey = this.chromadbApiKey ?? ""

// Provider change (embedder or vector DB)
if (prevProvider !== this.embedderProvider) {
return true
}

if (prevVectorDBProvider !== currentVectorDBProvider) {
return true
}

if (prevOpenAiKey !== currentOpenAiKey) {
return true
Expand Down Expand Up @@ -323,8 +374,17 @@ export class CodeIndexConfigManager {
return true
}

if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
return true
// Vector database connection changes
if (prevVectorDBProvider === "qdrant" && currentVectorDBProvider === "qdrant") {
if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
return true
}
}

if (prevVectorDBProvider === "chromadb" && currentVectorDBProvider === "chromadb") {
if (prevChromadbUrl !== currentChromadbUrl || prevChromadbApiKey !== currentChromadbApiKey) {
return true
}
}

// Vector dimension changes (still important for compatibility)
Expand Down Expand Up @@ -375,8 +435,11 @@ export class CodeIndexConfigManager {
openAiCompatibleOptions: this.openAiCompatibleOptions,
geminiOptions: this.geminiOptions,
mistralOptions: this.mistralOptions,
vectorDBProvider: this.vectorDBProvider,
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
chromadbUrl: this.chromadbUrl,
chromadbApiKey: this.chromadbApiKey,
searchMinScore: this.currentSearchMinScore,
searchMaxResults: this.currentSearchMaxResults,
}
Expand Down
9 changes: 9 additions & 0 deletions src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ export interface CodeIndexConfig {
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
mistralOptions?: { apiKey: string }
// Vector database configuration
vectorDBProvider?: "qdrant" | "lancedb" | "chromadb" | "sqlite-vector"
qdrantUrl?: string
qdrantApiKey?: string
chromadbUrl?: string
chromadbApiKey?: string
// Search configuration
searchMinScore?: number
searchMaxResults?: number
}
Expand All @@ -35,6 +40,10 @@ export type PreviousConfigSnapshot = {
openAiCompatibleApiKey?: string
geminiApiKey?: string
mistralApiKey?: string
// Vector database configuration
vectorDBProvider?: "qdrant" | "lancedb" | "chromadb" | "sqlite-vector"
qdrantUrl?: string
qdrantApiKey?: string
chromadbUrl?: string
chromadbApiKey?: string
}
46 changes: 41 additions & 5 deletions src/services/code-index/service-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GeminiEmbedder } from "./embedders/gemini"
import { MistralEmbedder } from "./embedders/mistral"
import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
import { QdrantVectorStore } from "./vector-store/qdrant-client"
import { QdrantAdapter, LanceDBAdapter, ChromaDBAdapter, SQLiteVectorAdapter } from "./vector-store/adapters"
import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces"
import { CodeIndexConfigManager } from "./config-manager"
Expand All @@ -15,6 +16,8 @@ import { t } from "../../i18n"
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"

export type VectorDBProvider = "qdrant" | "lancedb" | "chromadb" | "sqlite-vector"

/**
* Factory class responsible for creating and configuring code indexing service dependencies.
*/
Expand Down Expand Up @@ -132,12 +135,45 @@ export class CodeIndexServiceFactory {
}
}

if (!config.qdrantUrl) {
throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing"))
// Get vector database provider from config (default to qdrant for backward compatibility)
const vectorDBProvider = (config.vectorDBProvider as VectorDBProvider) || "qdrant"

// Create appropriate vector store based on provider
switch (vectorDBProvider) {
case "qdrant":
if (!config.qdrantUrl) {
throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing"))
}
return new QdrantAdapter({
workspacePath: this.workspacePath,
url: config.qdrantUrl,
vectorSize,
apiKey: config.qdrantApiKey,
})

case "lancedb":
return new LanceDBAdapter({
workspacePath: this.workspacePath,
vectorSize,
})

case "chromadb":
return new ChromaDBAdapter({
workspacePath: this.workspacePath,
url: config.chromadbUrl || "http://localhost:8000",
vectorSize,
apiKey: config.chromadbApiKey,
})

case "sqlite-vector":
return new SQLiteVectorAdapter({
workspacePath: this.workspacePath,
vectorSize,
})

default:
throw new Error(t("embeddings:serviceFactory.invalidVectorDBProvider", { provider: vectorDBProvider }))
}

// Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?)
return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, vectorSize, config.qdrantApiKey)
}

/**
Expand Down
Loading
Loading