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
6 changes: 6 additions & 0 deletions packages/types/src/codebase-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const codebaseIndexConfigSchema = z.object({
// OpenAI Compatible specific fields
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
// Ollama timeout settings for codebase indexing
codebaseIndexOllamaEmbeddingTimeoutMs: z.number().int().min(1000).max(300000).optional(),
codebaseIndexOllamaValidationTimeoutMs: z.number().int().min(1000).max(60000).optional(),
})

export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
Expand Down Expand Up @@ -62,6 +65,9 @@ export const codebaseIndexProviderSchema = z.object({
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
codebaseIndexGeminiApiKey: z.string().optional(),
// Ollama timeout settings for codebase indexing
codebaseIndexOllamaEmbeddingTimeoutMs: z.number().int().min(1000).max(300000).optional(),
codebaseIndexOllamaValidationTimeoutMs: z.number().int().min(1000).max(60000).optional(),
})

export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>
14 changes: 14 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,20 @@ export const webviewMessageHandler = async (
codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore,
}

// Save Ollama timeout settings to global state
if (settings.codebaseIndexOllamaEmbeddingTimeoutMs !== undefined) {
await updateGlobalState(
"codebaseIndexOllamaEmbeddingTimeoutMs",
settings.codebaseIndexOllamaEmbeddingTimeoutMs,
)
}
if (settings.codebaseIndexOllamaValidationTimeoutMs !== undefined) {
await updateGlobalState(
"codebaseIndexOllamaValidationTimeoutMs",
settings.codebaseIndexOllamaValidationTimeoutMs,
)
}

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

Expand Down
26 changes: 26 additions & 0 deletions src/services/code-index/__tests__/service-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,32 @@ describe("CodeIndexServiceFactory", () => {
})
})

it("should pass timeout parameters to Ollama embedder when configured", () => {
// Arrange
const testModelId = "nomic-embed-text:latest"
const testConfig = {
embedderProvider: "ollama",
modelId: testModelId,
ollamaOptions: {
ollamaBaseUrl: "http://localhost:11434",
embeddingTimeoutMs: 45000,
validationTimeoutMs: 15000,
},
}
mockConfigManager.getConfig.mockReturnValue(testConfig as any)

// Act
factory.createEmbedder()

// Assert
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
ollamaBaseUrl: "http://localhost:11434",
ollamaModelId: testModelId,
embeddingTimeoutMs: 45000,
validationTimeoutMs: 15000,
})
})

it("should throw error when OpenAI API key is missing", () => {
// Arrange
const testConfig = {
Expand Down
13 changes: 10 additions & 3 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiHandlerOptions } from "../../shared/api"
import { ContextProxy } from "../../core/config/ContextProxy"
import { EmbedderProvider } from "./interfaces/manager"
import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
import { CodeIndexConfig, PreviousConfigSnapshot, OllamaConfigOptions } from "./interfaces/config"
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"

Expand All @@ -15,7 +15,7 @@ export class CodeIndexConfigManager {
private modelId?: string
private modelDimension?: number
private openAiOptions?: ApiHandlerOptions
private ollamaOptions?: ApiHandlerOptions
private ollamaOptions?: OllamaConfigOptions
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
private geminiOptions?: { apiKey: string }
private qdrantUrl?: string = "http://localhost:6333"
Expand Down Expand Up @@ -68,6 +68,10 @@ export class CodeIndexConfigManager {
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""

// Get Ollama timeout settings from global state
const ollamaEmbeddingTimeoutMs = this.contextProxy?.getGlobalState("codebaseIndexOllamaEmbeddingTimeoutMs")
const ollamaValidationTimeoutMs = this.contextProxy?.getGlobalState("codebaseIndexOllamaValidationTimeoutMs")

// Update instance variables with configuration
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
this.qdrantUrl = codebaseIndexQdrantUrl
Expand Down Expand Up @@ -108,6 +112,9 @@ export class CodeIndexConfigManager {

this.ollamaOptions = {
ollamaBaseUrl: codebaseIndexEmbedderBaseUrl,
ollamaModelId: codebaseIndexEmbedderModelId,
embeddingTimeoutMs: ollamaEmbeddingTimeoutMs,
validationTimeoutMs: ollamaValidationTimeoutMs,
}

this.openAiCompatibleOptions =
Expand All @@ -132,7 +139,7 @@ export class CodeIndexConfigManager {
modelId?: string
modelDimension?: number
openAiOptions?: ApiHandlerOptions
ollamaOptions?: ApiHandlerOptions
ollamaOptions?: OllamaConfigOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
qdrantUrl?: string
Expand Down
19 changes: 15 additions & 4 deletions src/services/code-index/embedders/ollama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ import { t } from "../../../i18n"
import { withValidationErrorHandling, sanitizeErrorMessage } from "../shared/validation-helpers"
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
import { OllamaConfigOptions } from "../interfaces/config"

export interface OllamaEmbedderOptions extends ApiHandlerOptions {
embeddingTimeoutMs?: number
validationTimeoutMs?: number
}

/**
* Implements the IEmbedder interface using a local Ollama instance.
*/
export class CodeIndexOllamaEmbedder implements IEmbedder {
private readonly baseUrl: string
private readonly defaultModelId: string
private readonly embeddingTimeoutMs: number
private readonly validationTimeoutMs: number

constructor(options: ApiHandlerOptions) {
constructor(options: OllamaEmbedderOptions) {
// Ensure ollamaBaseUrl and ollamaModelId exist on ApiHandlerOptions or add defaults
this.baseUrl = options.ollamaBaseUrl || "http://localhost:11434"
this.defaultModelId = options.ollamaModelId || "nomic-embed-text:latest"
// Default timeouts: 30s for embedding (3x original), 10s for validation (2x original)
this.embeddingTimeoutMs = options.embeddingTimeoutMs || 30000
this.validationTimeoutMs = options.validationTimeoutMs || 10000
}

/**
Expand Down Expand Up @@ -61,7 +72,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {

// Add timeout to prevent indefinite hanging
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
const timeoutId = setTimeout(() => controller.abort(), this.embeddingTimeoutMs)

const response = await fetch(url, {
method: "POST",
Expand Down Expand Up @@ -140,7 +151,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {

// Add timeout to prevent indefinite hanging
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
const timeoutId = setTimeout(() => controller.abort(), this.validationTimeoutMs)

const modelsResponse = await fetch(modelsUrl, {
method: "GET",
Expand Down Expand Up @@ -197,7 +208,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {

// Add timeout for test request too
const testController = new AbortController()
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
const testTimeoutId = setTimeout(() => testController.abort(), this.validationTimeoutMs)

const testResponse = await fetch(testUrl, {
method: "POST",
Expand Down
10 changes: 9 additions & 1 deletion src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { ApiHandlerOptions } from "../../../shared/api" // Adjust path if needed
import { EmbedderProvider } from "./manager"

// Interface for Ollama-specific options including timeout configuration
export interface OllamaConfigOptions {
ollamaBaseUrl?: string
ollamaModelId?: string
embeddingTimeoutMs?: number
validationTimeoutMs?: number
}

/**
* Configuration state for the code indexing feature
*/
Expand All @@ -10,7 +18,7 @@ export interface CodeIndexConfig {
modelId?: string
modelDimension?: number // Generic dimension property for all providers
openAiOptions?: ApiHandlerOptions
ollamaOptions?: ApiHandlerOptions
ollamaOptions?: OllamaConfigOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
qdrantUrl?: string
Expand Down
4 changes: 4 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ export interface WebviewMessage {
codebaseIndexSearchMaxResults?: number
codebaseIndexSearchMinScore?: number

// Ollama timeout settings
codebaseIndexOllamaEmbeddingTimeoutMs?: number
codebaseIndexOllamaValidationTimeoutMs?: number

// Secret settings
codeIndexOpenAiKey?: string
codeIndexQdrantApiKey?: string
Expand Down
81 changes: 81 additions & 0 deletions webview-ui/src/components/chat/CodeIndexPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ interface LocalCodeIndexSettings {
codebaseIndexSearchMaxResults?: number
codebaseIndexSearchMinScore?: number

// Ollama timeout settings
codebaseIndexOllamaEmbeddingTimeoutMs?: number
codebaseIndexOllamaValidationTimeoutMs?: number

// Secret settings (start empty, will be loaded separately)
codeIndexOpenAiKey?: string
codeIndexQdrantApiKey?: string
Expand Down Expand Up @@ -160,6 +164,8 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
codebaseIndexEmbedderModelDimension: undefined,
codebaseIndexSearchMaxResults: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS,
codebaseIndexSearchMinScore: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE,
codebaseIndexOllamaEmbeddingTimeoutMs: 30000, // 30 seconds default
codebaseIndexOllamaValidationTimeoutMs: 10000, // 10 seconds default
codeIndexOpenAiKey: "",
codeIndexQdrantApiKey: "",
codebaseIndexOpenAiCompatibleBaseUrl: "",
Expand Down Expand Up @@ -193,6 +199,10 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
codebaseIndexConfig.codebaseIndexSearchMaxResults ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS,
codebaseIndexSearchMinScore:
codebaseIndexConfig.codebaseIndexSearchMinScore ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE,
codebaseIndexOllamaEmbeddingTimeoutMs:
codebaseIndexConfig.codebaseIndexOllamaEmbeddingTimeoutMs ?? 30000,
codebaseIndexOllamaValidationTimeoutMs:
codebaseIndexConfig.codebaseIndexOllamaValidationTimeoutMs ?? 10000,
codeIndexOpenAiKey: "",
codeIndexQdrantApiKey: "",
codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "",
Expand Down Expand Up @@ -743,6 +753,77 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
</p>
)}
</div>

{/* Ollama Timeout Settings */}
<div className="space-y-4 mt-4 p-3 border border-vscode-dropdown-border rounded">
<h5 className="text-sm font-medium mb-2">
{t("settings:codeIndex.ollamaTimeoutSettings")}
</h5>

<div className="space-y-2">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.ollamaEmbeddingTimeoutLabel")}
</label>
<StandardTooltip
content={t(
"settings:codeIndex.ollamaEmbeddingTimeoutDescription",
)}>
<span className="codicon codicon-info text-xs text-vscode-descriptionForeground cursor-help" />
</StandardTooltip>
</div>
<VSCodeTextField
value={
currentSettings.codebaseIndexOllamaEmbeddingTimeoutMs?.toString() ||
"30000"
}
onInput={(e: any) => {
const value = parseInt(e.target.value) || 30000
updateSetting(
"codebaseIndexOllamaEmbeddingTimeoutMs",
Math.max(1000, Math.min(300000, value)),
)
}}
placeholder="30000"
className="w-full"
/>
<p className="text-xs text-vscode-descriptionForeground">
{t("settings:codeIndex.ollamaEmbeddingTimeoutHelp")}
</p>
</div>

<div className="space-y-2">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.ollamaValidationTimeoutLabel")}
</label>
<StandardTooltip
content={t(
"settings:codeIndex.ollamaValidationTimeoutDescription",
)}>
<span className="codicon codicon-info text-xs text-vscode-descriptionForeground cursor-help" />
</StandardTooltip>
</div>
<VSCodeTextField
value={
currentSettings.codebaseIndexOllamaValidationTimeoutMs?.toString() ||
"10000"
}
onInput={(e: any) => {
const value = parseInt(e.target.value) || 10000
updateSetting(
"codebaseIndexOllamaValidationTimeoutMs",
Math.max(1000, Math.min(60000, value)),
)
}}
placeholder="10000"
className="w-full"
/>
<p className="text-xs text-vscode-descriptionForeground">
{t("settings:codeIndex.ollamaValidationTimeoutHelp")}
</p>
</div>
</div>
</>
)}

Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
codebaseIndexEmbedderModelId: "",
codebaseIndexSearchMaxResults: undefined,
codebaseIndexSearchMinScore: undefined,
codebaseIndexOllamaEmbeddingTimeoutMs: 30000,
codebaseIndexOllamaValidationTimeoutMs: 10000,
},
codebaseIndexModels: { ollama: {}, openai: {} },
alwaysAllowUpdateTodoList: true,
Expand Down
9 changes: 8 additions & 1 deletion webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,14 @@
"ollamaBaseUrlRequired": "Ollama base URL is required",
"baseUrlRequired": "Base URL is required",
"modelDimensionMinValue": "Model dimension must be greater than 0"
}
},
"ollamaTimeoutSettings": "Ollama Timeout Settings",
"ollamaEmbeddingTimeoutLabel": "Embedding Timeout (ms)",
"ollamaEmbeddingTimeoutDescription": "Maximum time to wait for embedding requests to complete. Increase this value if you experience timeout errors with large code blocks.",
"ollamaEmbeddingTimeoutHelp": "Range: 1000-300000ms (1-300 seconds). Default: 30000ms (30 seconds)",
"ollamaValidationTimeoutLabel": "Validation Timeout (ms)",
"ollamaValidationTimeoutDescription": "Maximum time to wait for model validation requests to complete. This is used to test if the Ollama model is available.",
"ollamaValidationTimeoutHelp": "Range: 1000-60000ms (1-60 seconds). Default: 10000ms (10 seconds)"
},
"autoApprove": {
"description": "Allow Roo to automatically perform operations without requiring approval. Enable these settings only if you fully trust the AI and understand the associated security risks.",
Expand Down
Loading