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
1 change: 1 addition & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
codebaseIndexEmbedderDimension: null,
}
await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig)

Expand Down
3 changes: 3 additions & 0 deletions src/exports/roo-code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type GlobalSettings = {
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down Expand Up @@ -865,6 +866,7 @@ type IpcMessage =
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down Expand Up @@ -1377,6 +1379,7 @@ type TaskCommand =
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down
3 changes: 3 additions & 0 deletions src/exports/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type GlobalSettings = {
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down Expand Up @@ -879,6 +880,7 @@ type IpcMessage =
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down Expand Up @@ -1393,6 +1395,7 @@ type TaskCommand =
codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined
codebaseIndexEmbedderBaseUrl?: string | undefined
codebaseIndexEmbedderModelId?: string | undefined
codebaseIndexEmbedderDimension?: (number | null) | undefined
}
| undefined
alwaysAllowWrite?: boolean | undefined
Expand Down
51 changes: 51 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,57 @@
"type": "string",
"default": "",
"description": "%settings.customStoragePath.description%"
},
"roo-cline.codebaseIndexEnabled": {
"type": "boolean",
"default": false,
"description": "Enable codebase indexing for semantic search.",
"scope": "resource"
},
"roo-cline.codebaseIndexEmbedderProvider": {
"type": "string",
"enum": [
"openai",
"ollama"
],
"default": "openai",
"description": "Select the embedding provider (OpenAI or Ollama).",
"scope": "resource"
},
"roo-cline.codebaseIndexEmbedderBaseUrl": {
"type": "string",
"default": "",
"description": "Optional: Base URL for the selected embedding provider API (e.g., for proxies, Azure OpenAI, or self-hosted Ollama).",
"scope": "resource"
},
"roo-cline.codebaseIndexEmbedderModelId": {
"type": "string",
"default": "",
"description": "Optional: Specify a custom embedding model ID. Leave empty to use the provider's default (e.g., text-embedding-3-small for OpenAI).",
"scope": "resource"
},
"roo-cline.codebaseIndexEmbedderDimension": {
"type": [
"number",
"null"
],
"default": null,
"description": "Optional: Specify the embedding dimension for your custom model ID if it's not automatically recognized. Required only if using an unknown model ID.",
"scope": "resource"
},
"roo-cline.codebaseIndexQdrantUrl": {
"type": "string",
"default": "http://localhost:6333",
"description": "URL for the Qdrant vector database instance.",
"scope": "resource"
},
"roo-cline.codebaseIndexSearchMinScore": {
"type": "number",
"default": 0.4,
"minimum": 0,
"maximum": 1,
"description": "Minimum similarity score (0-1) for codebase search results.",
"scope": "resource"
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export const codebaseIndexConfigSchema = z.object({
codebaseIndexEmbedderProvider: z.enum(["openai", "ollama"]).optional(),
codebaseIndexEmbedderBaseUrl: z.string().optional(),
codebaseIndexEmbedderModelId: z.string().optional(),
codebaseIndexEmbedderDimension: z.number().nullish(),
})

export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
Expand All @@ -244,7 +245,7 @@ export const codebaseIndexModelsSchema = z.object({
export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>

export const codebaseIndexProviderSchema = z.object({
codeIndexOpenAiKey: z.string().optional(),
codeIndexOpenAiKey: z.string().optional(),
codeIndexQdrantApiKey: z.string().optional(),
})

Expand Down Expand Up @@ -661,7 +662,7 @@ export const providerSettingsSchema = z.object({
...groqSchema.shape,
...chutesSchema.shape,
...litellmSchema.shape,
...codebaseIndexProviderSchema.shape
...codebaseIndexProviderSchema.shape,
})

export type ProviderSettings = z.infer<typeof providerSettingsSchema>
Expand Down
22 changes: 20 additions & 2 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class CodeIndexConfigManager {
private qdrantUrl?: string = "http://localhost:6333"
private qdrantApiKey?: string
private searchMinScore?: number
private dimension?: number // Added for custom model dimension

constructor(private readonly contextProxy: ContextProxy) {}

Expand All @@ -35,6 +36,7 @@ export class CodeIndexConfigManager {
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
dimension?: number // Add dimension to return type
}
requiresRestart: boolean
}> {
Expand All @@ -47,6 +49,7 @@ export class CodeIndexConfigManager {
ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl,
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
dimension: this.dimension, // Added dimension to snapshot
}

let codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
Expand All @@ -56,31 +59,42 @@ export class CodeIndexConfigManager {
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
codebaseIndexEmbedderDimension: null,
}

// Destructure known properties, access dimension defensively
const {
codebaseIndexEnabled,
codebaseIndexQdrantUrl,
codebaseIndexEmbedderProvider,
codebaseIndexEmbedderBaseUrl,
codebaseIndexEmbedderModelId,
} = codebaseIndexConfig
const codebaseIndexEmbedderDimension = (codebaseIndexConfig as any)?.codebaseIndexEmbedderDimension

const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? ""

this.isEnabled = codebaseIndexEnabled || false
this.qdrantUrl = codebaseIndexQdrantUrl
this.qdrantApiKey = qdrantApiKey ?? ""
this.openAiOptions = { openAiNativeApiKey: openAiKey }
this.searchMinScore = SEARCH_MIN_SCORE

this.embedderProvider = codebaseIndexEmbedderProvider === "ollama" ? "ollama" : "openai"
this.openAiOptions = {
openAiNativeApiKey: openAiKey,
openAiBaseUrl: this.embedderProvider === "openai" ? codebaseIndexEmbedderBaseUrl : undefined,
}
this.modelId = codebaseIndexEmbedderModelId || undefined

this.ollamaOptions = {
ollamaBaseUrl: codebaseIndexEmbedderBaseUrl,
}
// Parse and store dimension, ensuring it's a positive number or undefined
this.dimension =
typeof codebaseIndexEmbedderDimension === "number" && codebaseIndexEmbedderDimension > 0
? codebaseIndexEmbedderDimension
: undefined

return {
configSnapshot: previousConfigSnapshot,
Expand All @@ -94,6 +108,7 @@ export class CodeIndexConfigManager {
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.searchMinScore,
dimension: this.dimension,
},
requiresRestart: this._didConfigChangeRequireRestart(previousConfigSnapshot),
}
Expand All @@ -103,7 +118,6 @@ export class CodeIndexConfigManager {
* Checks if the service is properly configured based on the embedder type.
*/
public isConfigured(): boolean {

if (this.embedderProvider === "openai") {
const openAiKey = this.openAiOptions?.openAiNativeApiKey
const qdrantUrl = this.qdrantUrl
Expand Down Expand Up @@ -160,6 +174,9 @@ export class CodeIndexConfigManager {
if (prev.qdrantUrl !== this.qdrantUrl || prev.qdrantApiKey !== this.qdrantApiKey) {
return true
}

// Check dimension change
if (prev.dimension !== this.dimension) return true
}

return false
Expand All @@ -179,6 +196,7 @@ export class CodeIndexConfigManager {
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.searchMinScore,
dimension: this.dimension,
}
}

Expand Down
67 changes: 36 additions & 31 deletions src/services/code-index/embedders/ollama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,50 +22,55 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
*/
async createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse> {
const modelToUse = model || this.defaultModelId
const url = `${this.baseUrl}/api/embed` // Endpoint as specified
const url = `${this.baseUrl}/api/embed`

try {
// Note: Standard Ollama API uses 'prompt' for single text, not 'input' for array.
// Implementing based on user's specific request structure.
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
// Ollama API processes one text at a time using 'prompt' field
const embeddings: number[][] = []

for (const text of texts) {
const requestBody = {
model: modelToUse,
input: texts, // Using 'input' as requested
}),
})
prompt: text,
}

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
})

if (!response.ok) {
let errorBody = "Could not read error body"
try {
errorBody = await response.text()
} catch (e) {
// Ignore error reading body
if (!response.ok) {
let errorBody = "Could not read error body"
try {
errorBody = await response.text()
} catch (e) {
// Ignore error reading body
}
throw new Error(
`Ollama API request failed with status ${response.status} ${response.statusText}: ${errorBody}`,
)
}
throw new Error(
`Ollama API request failed with status ${response.status} ${response.statusText}: ${errorBody}`,
)
}

const data = await response.json()
const data = await response.json()

// Extract embedding using 'embedding' key (singular)
const embedding = data.embedding
if (!embedding || !Array.isArray(embedding)) {
throw new Error(
'Invalid response structure from Ollama API: "embedding" array not found or not an array.',
)
}

// Extract embeddings using 'embeddings' key as requested
const embeddings = data.embeddings
if (!embeddings || !Array.isArray(embeddings)) {
throw new Error(
'Invalid response structure from Ollama API: "embeddings" array not found or not an array.',
)
embeddings.push(embedding)
}

return {
embeddings: embeddings,
}
} catch (error: any) {
// Log the original error for debugging purposes
console.error("Ollama embedding failed:", error)
// Re-throw a more specific error for the caller
throw new Error(`Ollama embedding failed: ${error.message}`)
}
Expand Down
21 changes: 16 additions & 5 deletions src/services/code-index/embedders/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ import {
export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
private embeddingsClient: OpenAI
private readonly defaultModelId: string
private readonly configuredDimension?: number

/**
* Creates a new OpenAI embedder
* @param options API handler options
* @param options API handler options including optional model ID and dimension
*/
constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string }) {
constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string; embeddingDimension?: number }) {
super(options)
const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
this.embeddingsClient = new OpenAI({ apiKey })
const baseURL = this.options.openAiBaseUrl
this.configuredDimension = options.embeddingDimension
this.embeddingsClient = new OpenAI({
apiKey,
...(baseURL && { baseURL }),
})
this.defaultModelId = options.openAiEmbeddingModelId || "text-embedding-3-small"
}

Expand Down Expand Up @@ -98,10 +104,15 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
): Promise<{ embeddings: number[][]; usage: { promptTokens: number; totalTokens: number } }> {
for (let attempts = 0; attempts < MAX_RETRIES; attempts++) {
try {
const response = await this.embeddingsClient.embeddings.create({
const params: OpenAI.Embeddings.EmbeddingCreateParams = {
input: batchTexts,
model: model,
})
}
if (this.configuredDimension) {
params.dimensions = this.configuredDimension
}

const response = await this.embeddingsClient.embeddings.create(params)

return {
embeddings: response.data.map((item) => item.embedding),
Expand Down
2 changes: 2 additions & 0 deletions src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface CodeIndexConfig {
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
dimension?: number
}

/**
Expand All @@ -28,4 +29,5 @@ export type PreviousConfigSnapshot = {
ollamaBaseUrl?: string
qdrantUrl?: string
qdrantApiKey?: string
dimension?: number
}
Loading
Loading