diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index e86c17627ff..3d85d36bf73 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -36,6 +36,7 @@ export const codebaseIndexProviderSchema = z.object({ codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(), codebaseIndexOpenAiCompatibleApiKey: z.string().optional(), codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(), + codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(), }) export type CodebaseIndexProvider = z.infer diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 678cec36a12..59d66e0655f 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -15,7 +15,7 @@ export class CodeIndexConfigManager { private modelId?: string private openAiOptions?: ApiHandlerOptions private ollamaOptions?: ApiHandlerOptions - private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number } + private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string } private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string private searchMinScore?: number @@ -55,6 +55,8 @@ export class CodeIndexConfigManager { const openAiCompatibleModelDimension = this.contextProxy?.getGlobalState( "codebaseIndexOpenAiCompatibleModelDimension", ) as number | undefined + const openAiCompatibleApiVersion = + this.contextProxy?.getGlobalState("codebaseIndexOpenAiCompatibleApiVersion") ?? "" // Update instance variables with configuration this.isEnabled = codebaseIndexEnabled || false @@ -84,6 +86,7 @@ export class CodeIndexConfigManager { baseUrl: openAiCompatibleBaseUrl, apiKey: openAiCompatibleApiKey, modelDimension: openAiCompatibleModelDimension, + apiVersion: openAiCompatibleApiVersion || undefined, } : undefined } @@ -100,7 +103,7 @@ export class CodeIndexConfigManager { modelId?: string openAiOptions?: ApiHandlerOptions ollamaOptions?: ApiHandlerOptions - openAiCompatibleOptions?: { baseUrl: string; apiKey: string } + openAiCompatibleOptions?: { baseUrl: string; apiKey: string; apiVersion?: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -118,6 +121,7 @@ export class CodeIndexConfigManager { openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "", openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "", openAiCompatibleModelDimension: this.openAiCompatibleOptions?.modelDimension, + openAiCompatibleApiVersion: this.openAiCompatibleOptions?.apiVersion ?? "", qdrantUrl: this.qdrantUrl ?? "", qdrantApiKey: this.qdrantApiKey ?? "", } @@ -185,6 +189,7 @@ export class CodeIndexConfigManager { const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? "" const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? "" const prevOpenAiCompatibleModelDimension = prev?.openAiCompatibleModelDimension + const prevOpenAiCompatibleApiVersion = prev?.openAiCompatibleApiVersion ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" @@ -233,10 +238,12 @@ export class CodeIndexConfigManager { const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? "" const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? "" const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension + const currentOpenAiCompatibleApiVersion = this.openAiCompatibleOptions?.apiVersion ?? "" if ( prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl || prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey || - prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension + prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension || + prevOpenAiCompatibleApiVersion !== currentOpenAiCompatibleApiVersion ) { return true } diff --git a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts index 271d68cc205..e7c22a1d7b7 100644 --- a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts +++ b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts @@ -92,6 +92,39 @@ describe("OpenAICompatibleEmbedder", () => { "Base URL is required for OpenAI Compatible embedder", ) }) + + it("should append api-version query parameter when provided", () => { + const apiVersion = "2023-05-15" + embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId, apiVersion) + + expect(MockedOpenAI).toHaveBeenCalledWith({ + baseURL: `${testBaseUrl}?api-version=${apiVersion}`, + apiKey: testApiKey, + }) + expect(embedder).toBeDefined() + }) + + it("should handle api-version with existing query parameters", () => { + const baseUrlWithParams = "https://api.example.com/v1?existing=param" + const apiVersion = "2023-05-15" + embedder = new OpenAICompatibleEmbedder(baseUrlWithParams, testApiKey, testModelId, apiVersion) + + expect(MockedOpenAI).toHaveBeenCalledWith({ + baseURL: `${baseUrlWithParams}&api-version=${apiVersion}`, + apiKey: testApiKey, + }) + expect(embedder).toBeDefined() + }) + + it("should not modify baseURL when api-version is not provided", () => { + embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId) + + expect(MockedOpenAI).toHaveBeenCalledWith({ + baseURL: testBaseUrl, + apiKey: testApiKey, + }) + expect(embedder).toBeDefined() + }) }) describe("embedderInfo", () => { diff --git a/src/services/code-index/embedders/openai-compatible.ts b/src/services/code-index/embedders/openai-compatible.ts index 0983cc297f7..500a248c439 100644 --- a/src/services/code-index/embedders/openai-compatible.ts +++ b/src/services/code-index/embedders/openai-compatible.ts @@ -35,8 +35,9 @@ export class OpenAICompatibleEmbedder implements IEmbedder { * @param baseUrl The base URL for the OpenAI-compatible API endpoint * @param apiKey The API key for authentication * @param modelId Optional model identifier (defaults to "text-embedding-3-small") + * @param apiVersion Optional API version for Azure OpenAI compatibility */ - constructor(baseUrl: string, apiKey: string, modelId?: string) { + constructor(baseUrl: string, apiKey: string, modelId?: string, apiVersion?: string) { if (!baseUrl) { throw new Error("Base URL is required for OpenAI Compatible embedder") } @@ -44,8 +45,15 @@ export class OpenAICompatibleEmbedder implements IEmbedder { throw new Error("API key is required for OpenAI Compatible embedder") } + // For Azure OpenAI, we need to append the api-version query parameter to the baseURL + let finalBaseUrl = baseUrl + if (apiVersion) { + const separator = baseUrl.includes("?") ? "&" : "?" + finalBaseUrl = `${baseUrl}${separator}api-version=${apiVersion}` + } + this.embeddingsClient = new OpenAI({ - baseURL: baseUrl, + baseURL: finalBaseUrl, apiKey: apiKey, }) this.defaultModelId = modelId || getDefaultModelId("openai-compatible") diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index 0843120fd9f..4c4e766c52b 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -11,7 +11,7 @@ export interface CodeIndexConfig { modelId?: string openAiOptions?: ApiHandlerOptions ollamaOptions?: ApiHandlerOptions - openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number } + openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -30,6 +30,7 @@ export type PreviousConfigSnapshot = { openAiCompatibleBaseUrl?: string openAiCompatibleApiKey?: string openAiCompatibleModelDimension?: number + openAiCompatibleApiVersion?: string qdrantUrl?: string qdrantApiKey?: string } diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index afd083b2047..a98d16810a9 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -52,6 +52,7 @@ export class CodeIndexServiceFactory { config.openAiCompatibleOptions.baseUrl, config.openAiCompatibleOptions.apiKey, config.modelId, + config.openAiCompatibleOptions.apiVersion, ) } diff --git a/webview-ui/src/components/settings/CodeIndexSettings.tsx b/webview-ui/src/components/settings/CodeIndexSettings.tsx index 13c9524d9f9..129af873fbe 100644 --- a/webview-ui/src/components/settings/CodeIndexSettings.tsx +++ b/webview-ui/src/components/settings/CodeIndexSettings.tsx @@ -144,6 +144,7 @@ export const CodeIndexSettings: React.FC = ({ .int("Dimension must be an integer") .positive("Dimension must be a positive number") .optional(), + codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(), }), } @@ -161,6 +162,7 @@ export const CodeIndexSettings: React.FC = ({ codebaseIndexOpenAiCompatibleBaseUrl: apiConfig.codebaseIndexOpenAiCompatibleBaseUrl, codebaseIndexOpenAiCompatibleApiKey: apiConfig.codebaseIndexOpenAiCompatibleApiKey, codebaseIndexOpenAiCompatibleModelDimension: apiConfig.codebaseIndexOpenAiCompatibleModelDimension, + codebaseIndexOpenAiCompatibleApiVersion: apiConfig.codebaseIndexOpenAiCompatibleApiVersion, }) return true } catch { @@ -320,6 +322,24 @@ export const CodeIndexSettings: React.FC = ({ } style={{ width: "100%" }}> +
+
{t("settings:codeIndex.openaiCompatibleApiVersionLabel")}
+
+
+ + setApiConfigurationField( + "codebaseIndexOpenAiCompatibleApiVersion", + e.target.value, + ) + } + placeholder="2023-05-15" + style={{ width: "100%" }}> +

+ {t("settings:codeIndex.openaiCompatibleApiVersionDescription")} +

+
)} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 9083d4a204c..4da638d872b 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -48,6 +48,8 @@ "openaiKeyLabel": "OpenAI Key:", "openaiCompatibleBaseUrlLabel": "Base URL:", "openaiCompatibleApiKeyLabel": "API Key:", + "openaiCompatibleApiVersionLabel": "API Version:", + "openaiCompatibleApiVersionDescription": "Required for Azure OpenAI. Common values: 2023-05-15, 2024-02-01, 2024-06-01. Leave empty for other OpenAI-compatible providers.", "openaiCompatibleModelDimensionLabel": "Embedding Dimension:", "openaiCompatibleModelDimensionPlaceholder": "e.g., 1536", "openaiCompatibleModelDimensionDescription": "The embedding dimension (output size) for your model. Check your provider's documentation for this value. Common values: 384, 768, 1536, 3072.",