Skip to content

Commit 7647fd1

Browse files
author
Roo
committed
feat: add configurable timeout settings for Ollama embedder
- Add timeout configuration options for Ollama embedding and validation operations - Default to 30s for embedding requests and 10s for validation requests - Add UI components in CodeIndexPopover for timeout configuration with validation - Update configuration interfaces and type definitions to support timeout settings - Integrate timeout settings into existing configuration management pipeline - Add comprehensive test coverage for timeout functionality - Add translation support for timeout settings Fixes #5733
1 parent 8a3dcfb commit 7647fd1

File tree

10 files changed

+175
-9
lines changed

10 files changed

+175
-9
lines changed

packages/types/src/codebase-index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export const codebaseIndexConfigSchema = z.object({
3434
// OpenAI Compatible specific fields
3535
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
3636
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
37+
// Ollama timeout settings for codebase indexing
38+
codebaseIndexOllamaEmbeddingTimeoutMs: z.number().int().min(1000).max(300000).optional(),
39+
codebaseIndexOllamaValidationTimeoutMs: z.number().int().min(1000).max(60000).optional(),
3740
})
3841

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

6773
export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>

src/core/webview/webviewMessageHandler.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,20 @@ export const webviewMessageHandler = async (
19661966
codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore,
19671967
}
19681968

1969+
// Save Ollama timeout settings to global state
1970+
if (settings.codebaseIndexOllamaEmbeddingTimeoutMs !== undefined) {
1971+
await updateGlobalState(
1972+
"codebaseIndexOllamaEmbeddingTimeoutMs",
1973+
settings.codebaseIndexOllamaEmbeddingTimeoutMs,
1974+
)
1975+
}
1976+
if (settings.codebaseIndexOllamaValidationTimeoutMs !== undefined) {
1977+
await updateGlobalState(
1978+
"codebaseIndexOllamaValidationTimeoutMs",
1979+
settings.codebaseIndexOllamaValidationTimeoutMs,
1980+
)
1981+
}
1982+
19691983
// Save global state first
19701984
await updateGlobalState("codebaseIndexConfig", globalStateConfig)
19711985

src/services/code-index/__tests__/service-factory.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,32 @@ describe("CodeIndexServiceFactory", () => {
143143
})
144144
})
145145

146+
it("should pass timeout parameters to Ollama embedder when configured", () => {
147+
// Arrange
148+
const testModelId = "nomic-embed-text:latest"
149+
const testConfig = {
150+
embedderProvider: "ollama",
151+
modelId: testModelId,
152+
ollamaOptions: {
153+
ollamaBaseUrl: "http://localhost:11434",
154+
embeddingTimeoutMs: 45000,
155+
validationTimeoutMs: 15000,
156+
},
157+
}
158+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
159+
160+
// Act
161+
factory.createEmbedder()
162+
163+
// Assert
164+
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
165+
ollamaBaseUrl: "http://localhost:11434",
166+
ollamaModelId: testModelId,
167+
embeddingTimeoutMs: 45000,
168+
validationTimeoutMs: 15000,
169+
})
170+
})
171+
146172
it("should throw error when OpenAI API key is missing", () => {
147173
// Arrange
148174
const testConfig = {

src/services/code-index/config-manager.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ApiHandlerOptions } from "../../shared/api"
22
import { ContextProxy } from "../../core/config/ContextProxy"
33
import { EmbedderProvider } from "./interfaces/manager"
4-
import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
4+
import { CodeIndexConfig, PreviousConfigSnapshot, OllamaConfigOptions } from "./interfaces/config"
55
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
66
import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
77

@@ -15,7 +15,7 @@ export class CodeIndexConfigManager {
1515
private modelId?: string
1616
private modelDimension?: number
1717
private openAiOptions?: ApiHandlerOptions
18-
private ollamaOptions?: ApiHandlerOptions
18+
private ollamaOptions?: OllamaConfigOptions
1919
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
2020
private geminiOptions?: { apiKey: string }
2121
private qdrantUrl?: string = "http://localhost:6333"
@@ -68,6 +68,10 @@ export class CodeIndexConfigManager {
6868
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
6969
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
7070

71+
// Get Ollama timeout settings from global state
72+
const ollamaEmbeddingTimeoutMs = this.contextProxy?.getGlobalState("codebaseIndexOllamaEmbeddingTimeoutMs")
73+
const ollamaValidationTimeoutMs = this.contextProxy?.getGlobalState("codebaseIndexOllamaValidationTimeoutMs")
74+
7175
// Update instance variables with configuration
7276
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
7377
this.qdrantUrl = codebaseIndexQdrantUrl
@@ -108,6 +112,9 @@ export class CodeIndexConfigManager {
108112

109113
this.ollamaOptions = {
110114
ollamaBaseUrl: codebaseIndexEmbedderBaseUrl,
115+
ollamaModelId: codebaseIndexEmbedderModelId,
116+
embeddingTimeoutMs: ollamaEmbeddingTimeoutMs,
117+
validationTimeoutMs: ollamaValidationTimeoutMs,
111118
}
112119

113120
this.openAiCompatibleOptions =
@@ -132,7 +139,7 @@ export class CodeIndexConfigManager {
132139
modelId?: string
133140
modelDimension?: number
134141
openAiOptions?: ApiHandlerOptions
135-
ollamaOptions?: ApiHandlerOptions
142+
ollamaOptions?: OllamaConfigOptions
136143
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
137144
geminiOptions?: { apiKey: string }
138145
qdrantUrl?: string

src/services/code-index/embedders/ollama.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@ import { t } from "../../../i18n"
66
import { withValidationErrorHandling, sanitizeErrorMessage } from "../shared/validation-helpers"
77
import { TelemetryService } from "@roo-code/telemetry"
88
import { TelemetryEventName } from "@roo-code/types"
9+
import { OllamaConfigOptions } from "../interfaces/config"
10+
11+
export interface OllamaEmbedderOptions extends ApiHandlerOptions {
12+
embeddingTimeoutMs?: number
13+
validationTimeoutMs?: number
14+
}
915

1016
/**
1117
* Implements the IEmbedder interface using a local Ollama instance.
1218
*/
1319
export class CodeIndexOllamaEmbedder implements IEmbedder {
1420
private readonly baseUrl: string
1521
private readonly defaultModelId: string
22+
private readonly embeddingTimeoutMs: number
23+
private readonly validationTimeoutMs: number
1624

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

2334
/**
@@ -61,7 +72,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
6172

6273
// Add timeout to prevent indefinite hanging
6374
const controller = new AbortController()
64-
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
75+
const timeoutId = setTimeout(() => controller.abort(), this.embeddingTimeoutMs)
6576

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

141152
// Add timeout to prevent indefinite hanging
142153
const controller = new AbortController()
143-
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
154+
const timeoutId = setTimeout(() => controller.abort(), this.validationTimeoutMs)
144155

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

198209
// Add timeout for test request too
199210
const testController = new AbortController()
200-
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
211+
const testTimeoutId = setTimeout(() => testController.abort(), this.validationTimeoutMs)
201212

202213
const testResponse = await fetch(testUrl, {
203214
method: "POST",

src/services/code-index/interfaces/config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { ApiHandlerOptions } from "../../../shared/api" // Adjust path if needed
22
import { EmbedderProvider } from "./manager"
33

4+
// Interface for Ollama-specific options including timeout configuration
5+
export interface OllamaConfigOptions {
6+
ollamaBaseUrl?: string
7+
ollamaModelId?: string
8+
embeddingTimeoutMs?: number
9+
validationTimeoutMs?: number
10+
}
11+
412
/**
513
* Configuration state for the code indexing feature
614
*/
@@ -10,7 +18,7 @@ export interface CodeIndexConfig {
1018
modelId?: string
1119
modelDimension?: number // Generic dimension property for all providers
1220
openAiOptions?: ApiHandlerOptions
13-
ollamaOptions?: ApiHandlerOptions
21+
ollamaOptions?: OllamaConfigOptions
1422
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
1523
geminiOptions?: { apiKey: string }
1624
qdrantUrl?: string

src/shared/WebviewMessage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ export interface WebviewMessage {
247247
codebaseIndexSearchMaxResults?: number
248248
codebaseIndexSearchMinScore?: number
249249

250+
// Ollama timeout settings
251+
codebaseIndexOllamaEmbeddingTimeoutMs?: number
252+
codebaseIndexOllamaValidationTimeoutMs?: number
253+
250254
// Secret settings
251255
codeIndexOpenAiKey?: string
252256
codeIndexQdrantApiKey?: string

webview-ui/src/components/chat/CodeIndexPopover.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ interface LocalCodeIndexSettings {
6262
codebaseIndexSearchMaxResults?: number
6363
codebaseIndexSearchMinScore?: number
6464

65+
// Ollama timeout settings
66+
codebaseIndexOllamaEmbeddingTimeoutMs?: number
67+
codebaseIndexOllamaValidationTimeoutMs?: number
68+
6569
// Secret settings (start empty, will be loaded separately)
6670
codeIndexOpenAiKey?: string
6771
codeIndexQdrantApiKey?: string
@@ -160,6 +164,8 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
160164
codebaseIndexEmbedderModelDimension: undefined,
161165
codebaseIndexSearchMaxResults: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS,
162166
codebaseIndexSearchMinScore: CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE,
167+
codebaseIndexOllamaEmbeddingTimeoutMs: 30000, // 30 seconds default
168+
codebaseIndexOllamaValidationTimeoutMs: 10000, // 10 seconds default
163169
codeIndexOpenAiKey: "",
164170
codeIndexQdrantApiKey: "",
165171
codebaseIndexOpenAiCompatibleBaseUrl: "",
@@ -193,6 +199,10 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
193199
codebaseIndexConfig.codebaseIndexSearchMaxResults ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_RESULTS,
194200
codebaseIndexSearchMinScore:
195201
codebaseIndexConfig.codebaseIndexSearchMinScore ?? CODEBASE_INDEX_DEFAULTS.DEFAULT_SEARCH_MIN_SCORE,
202+
codebaseIndexOllamaEmbeddingTimeoutMs:
203+
codebaseIndexConfig.codebaseIndexOllamaEmbeddingTimeoutMs ?? 30000,
204+
codebaseIndexOllamaValidationTimeoutMs:
205+
codebaseIndexConfig.codebaseIndexOllamaValidationTimeoutMs ?? 10000,
196206
codeIndexOpenAiKey: "",
197207
codeIndexQdrantApiKey: "",
198208
codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "",
@@ -743,6 +753,77 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
743753
</p>
744754
)}
745755
</div>
756+
757+
{/* Ollama Timeout Settings */}
758+
<div className="space-y-4 mt-4 p-3 border border-vscode-dropdown-border rounded">
759+
<h5 className="text-sm font-medium mb-2">
760+
{t("settings:codeIndex.ollamaTimeoutSettings")}
761+
</h5>
762+
763+
<div className="space-y-2">
764+
<div className="flex items-center gap-2">
765+
<label className="text-sm font-medium">
766+
{t("settings:codeIndex.ollamaEmbeddingTimeoutLabel")}
767+
</label>
768+
<StandardTooltip
769+
content={t(
770+
"settings:codeIndex.ollamaEmbeddingTimeoutDescription",
771+
)}>
772+
<span className="codicon codicon-info text-xs text-vscode-descriptionForeground cursor-help" />
773+
</StandardTooltip>
774+
</div>
775+
<VSCodeTextField
776+
value={
777+
currentSettings.codebaseIndexOllamaEmbeddingTimeoutMs?.toString() ||
778+
"30000"
779+
}
780+
onInput={(e: any) => {
781+
const value = parseInt(e.target.value) || 30000
782+
updateSetting(
783+
"codebaseIndexOllamaEmbeddingTimeoutMs",
784+
Math.max(1000, Math.min(300000, value)),
785+
)
786+
}}
787+
placeholder="30000"
788+
className="w-full"
789+
/>
790+
<p className="text-xs text-vscode-descriptionForeground">
791+
{t("settings:codeIndex.ollamaEmbeddingTimeoutHelp")}
792+
</p>
793+
</div>
794+
795+
<div className="space-y-2">
796+
<div className="flex items-center gap-2">
797+
<label className="text-sm font-medium">
798+
{t("settings:codeIndex.ollamaValidationTimeoutLabel")}
799+
</label>
800+
<StandardTooltip
801+
content={t(
802+
"settings:codeIndex.ollamaValidationTimeoutDescription",
803+
)}>
804+
<span className="codicon codicon-info text-xs text-vscode-descriptionForeground cursor-help" />
805+
</StandardTooltip>
806+
</div>
807+
<VSCodeTextField
808+
value={
809+
currentSettings.codebaseIndexOllamaValidationTimeoutMs?.toString() ||
810+
"10000"
811+
}
812+
onInput={(e: any) => {
813+
const value = parseInt(e.target.value) || 10000
814+
updateSetting(
815+
"codebaseIndexOllamaValidationTimeoutMs",
816+
Math.max(1000, Math.min(60000, value)),
817+
)
818+
}}
819+
placeholder="10000"
820+
className="w-full"
821+
/>
822+
<p className="text-xs text-vscode-descriptionForeground">
823+
{t("settings:codeIndex.ollamaValidationTimeoutHelp")}
824+
</p>
825+
</div>
826+
</div>
746827
</>
747828
)}
748829

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
223223
codebaseIndexEmbedderModelId: "",
224224
codebaseIndexSearchMaxResults: undefined,
225225
codebaseIndexSearchMinScore: undefined,
226+
codebaseIndexOllamaEmbeddingTimeoutMs: 30000,
227+
codebaseIndexOllamaValidationTimeoutMs: 10000,
226228
},
227229
codebaseIndexModels: { ollama: {}, openai: {} },
228230
alwaysAllowUpdateTodoList: true,

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,14 @@
119119
"ollamaBaseUrlRequired": "Ollama base URL is required",
120120
"baseUrlRequired": "Base URL is required",
121121
"modelDimensionMinValue": "Model dimension must be greater than 0"
122-
}
122+
},
123+
"ollamaTimeoutSettings": "Ollama Timeout Settings",
124+
"ollamaEmbeddingTimeoutLabel": "Embedding Timeout (ms)",
125+
"ollamaEmbeddingTimeoutDescription": "Maximum time to wait for embedding requests to complete. Increase this value if you experience timeout errors with large code blocks.",
126+
"ollamaEmbeddingTimeoutHelp": "Range: 1000-300000ms (1-300 seconds). Default: 30000ms (30 seconds)",
127+
"ollamaValidationTimeoutLabel": "Validation Timeout (ms)",
128+
"ollamaValidationTimeoutDescription": "Maximum time to wait for model validation requests to complete. This is used to test if the Ollama model is available.",
129+
"ollamaValidationTimeoutHelp": "Range: 1000-60000ms (1-60 seconds). Default: 10000ms (10 seconds)"
123130
},
124131
"autoApprove": {
125132
"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.",

0 commit comments

Comments
 (0)