diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index 89d5b168d7..be7778f538 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -21,7 +21,9 @@ export const CODEBASE_INDEX_DEFAULTS = { export const codebaseIndexConfigSchema = z.object({ codebaseIndexEnabled: z.boolean().optional(), codebaseIndexQdrantUrl: z.string().optional(), - codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral"]).optional(), + codebaseIndexEmbedderProvider: z + .enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway"]) + .optional(), codebaseIndexEmbedderBaseUrl: z.string().optional(), codebaseIndexEmbedderModelId: z.string().optional(), codebaseIndexEmbedderModelDimension: z.number().optional(), @@ -48,6 +50,7 @@ export const codebaseIndexModelsSchema = z.object({ "openai-compatible": z.record(z.string(), z.object({ dimension: z.number() })).optional(), gemini: z.record(z.string(), z.object({ dimension: z.number() })).optional(), mistral: z.record(z.string(), z.object({ dimension: z.number() })).optional(), + "vercel-ai-gateway": z.record(z.string(), z.object({ dimension: z.number() })).optional(), }) export type CodebaseIndexModels = z.infer @@ -64,6 +67,7 @@ export const codebaseIndexProviderSchema = z.object({ codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(), codebaseIndexGeminiApiKey: z.string().optional(), codebaseIndexMistralApiKey: z.string().optional(), + codebaseIndexVercelAiGatewayApiKey: z.string().optional(), }) export type CodebaseIndexProvider = z.infer diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 3b80bba3b2..e8dbffb62d 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -192,6 +192,7 @@ export const SECRET_STATE_KEYS = [ "codebaseIndexOpenAiCompatibleApiKey", "codebaseIndexGeminiApiKey", "codebaseIndexMistralApiKey", + "codebaseIndexVercelAiGatewayApiKey", "huggingFaceApiKey", "sambaNovaApiKey", "zaiApiKey", diff --git a/src/api/providers/fetchers/vercel-ai-gateway.ts b/src/api/providers/fetchers/vercel-ai-gateway.ts index 91456819a6..e392180d72 100644 --- a/src/api/providers/fetchers/vercel-ai-gateway.ts +++ b/src/api/providers/fetchers/vercel-ai-gateway.ts @@ -68,7 +68,8 @@ export async function getVercelAiGatewayModels(options?: ApiHandlerOptions): Pro for (const model of data) { const { id } = model - // Only include language models + // Only include language models for chat inference + // Embedding models are statically defined in embeddingModels.ts if (model.type !== "language") { continue } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index ddbc5a992e..09e97e3a7c 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2100,6 +2100,12 @@ export const webviewMessageHandler = async ( settings.codebaseIndexMistralApiKey, ) } + if (settings.codebaseIndexVercelAiGatewayApiKey !== undefined) { + await provider.contextProxy.storeSecret( + "codebaseIndexVercelAiGatewayApiKey", + settings.codebaseIndexVercelAiGatewayApiKey, + ) + } // Send success response first - settings are saved regardless of validation await provider.postMessageToWebview({ @@ -2234,6 +2240,9 @@ export const webviewMessageHandler = async ( )) const hasGeminiApiKey = !!(await provider.context.secrets.get("codebaseIndexGeminiApiKey")) const hasMistralApiKey = !!(await provider.context.secrets.get("codebaseIndexMistralApiKey")) + const hasVercelAiGatewayApiKey = !!(await provider.context.secrets.get( + "codebaseIndexVercelAiGatewayApiKey", + )) provider.postMessageToWebview({ type: "codeIndexSecretStatus", @@ -2243,6 +2252,7 @@ export const webviewMessageHandler = async ( hasOpenAiCompatibleApiKey, hasGeminiApiKey, hasMistralApiKey, + hasVercelAiGatewayApiKey, }, }) break diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 35ddca3b10..782f92cddf 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Falta la configuració compatible amb OpenAI per crear l'embedder", "geminiConfigMissing": "Falta la configuració de Gemini per crear l'embedder", "mistralConfigMissing": "Falta la configuració de Mistral per crear l'embedder", + "vercelAiGatewayConfigMissing": "Falta la configuració de Vercel AI Gateway per crear l'embedder", "invalidEmbedderType": "Tipus d'embedder configurat no vàlid: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Assegura't que la 'Dimensió d'incrustació' estigui configurada correctament als paràmetres del proveïdor compatible amb OpenAI.", "vectorDimensionNotDetermined": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Comprova els perfils del model o la configuració.", diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index f5aa7339ef..239d5d3c8a 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "OpenAI-kompatible Konfiguration fehlt für die Erstellung des Embedders", "geminiConfigMissing": "Gemini-Konfiguration fehlt für die Erstellung des Embedders", "mistralConfigMissing": "Mistral-Konfiguration fehlt für die Erstellung des Embedders", + "vercelAiGatewayConfigMissing": "Vercel AI Gateway-Konfiguration fehlt für die Erstellung des Embedders", "invalidEmbedderType": "Ungültiger Embedder-Typ konfiguriert: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Stelle sicher, dass die 'Embedding-Dimension' in den OpenAI-kompatiblen Anbietereinstellungen korrekt eingestellt ist.", "vectorDimensionNotDetermined": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Überprüfe die Modellprofile oder Konfiguration.", diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index 66465d8c35..fc902cadc1 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "OpenAI Compatible configuration missing for embedder creation", "geminiConfigMissing": "Gemini configuration missing for embedder creation", "mistralConfigMissing": "Mistral configuration missing for embedder creation", + "vercelAiGatewayConfigMissing": "Vercel AI Gateway configuration missing for embedder creation", "invalidEmbedderType": "Invalid embedder type configured: {{embedderProvider}}", "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.", diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index 51621b6d17..ac7522ab01 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Falta la configuración compatible con OpenAI para crear el incrustador", "geminiConfigMissing": "Falta la configuración de Gemini para crear el incrustador", "mistralConfigMissing": "Falta la configuración de Mistral para la creación del incrustador", + "vercelAiGatewayConfigMissing": "Falta la configuración de Vercel AI Gateway para la creación del incrustador", "invalidEmbedderType": "Tipo de incrustador configurado inválido: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Asegúrate de que la 'Dimensión de incrustación' esté configurada correctamente en los ajustes del proveedor compatible con OpenAI.", "vectorDimensionNotDetermined": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Verifica los perfiles del modelo o la configuración.", diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index e3a9227234..e4dff28012 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Configuration compatible OpenAI manquante pour la création de l'embedder", "geminiConfigMissing": "Configuration Gemini manquante pour la création de l'embedder", "mistralConfigMissing": "Configuration Mistral manquante pour la création de l'embedder", + "vercelAiGatewayConfigMissing": "Configuration Vercel AI Gateway manquante pour la création de l'embedder", "invalidEmbedderType": "Type d'embedder configuré invalide : {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Assure-toi que la 'Dimension d'embedding' est correctement définie dans les paramètres du fournisseur compatible OpenAI.", "vectorDimensionNotDetermined": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Vérifie les profils du modèle ou la configuration.", diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index 01563e833a..108d126373 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "एम्बेडर बनाने के लिए OpenAI संगत कॉन्फ़िगरेशन गायब है", "geminiConfigMissing": "एम्बेडर बनाने के लिए Gemini कॉन्फ़िगरेशन गायब है", "mistralConfigMissing": "एम्बेडर निर्माण के लिए मिस्ट्रल कॉन्फ़िगरेशन गायब है", + "vercelAiGatewayConfigMissing": "एम्बेडर निर्माण के लिए Vercel AI Gateway कॉन्फ़िगरेशन गायब है", "invalidEmbedderType": "अमान्य एम्बेडर प्रकार कॉन्फ़िगर किया गया: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। कृपया सुनिश्चित करें कि OpenAI-संगत प्रदाता सेटिंग्स में 'एम्बेडिंग आयाम' सही तरीके से सेट है।", "vectorDimensionNotDetermined": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। मॉडल प्रोफ़ाइल या कॉन्फ़िगरेशन की जांच करें।", diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index a66c1965ab..8c0fdd490f 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Konfigurasi yang kompatibel dengan OpenAI tidak ada untuk membuat embedder", "geminiConfigMissing": "Konfigurasi Gemini tidak ada untuk membuat embedder", "mistralConfigMissing": "Konfigurasi Mistral hilang untuk pembuatan embedder", + "vercelAiGatewayConfigMissing": "Konfigurasi Vercel AI Gateway hilang untuk pembuatan embedder", "invalidEmbedderType": "Tipe embedder yang dikonfigurasi tidak valid: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Pastikan 'Dimensi Embedding' diatur dengan benar di pengaturan penyedia yang kompatibel dengan OpenAI.", "vectorDimensionNotDetermined": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Periksa profil model atau konfigurasi.", diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index d59bc2c26d..0ca32f906e 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Configurazione compatibile con OpenAI mancante per la creazione dell'embedder", "geminiConfigMissing": "Configurazione Gemini mancante per la creazione dell'embedder", "mistralConfigMissing": "Configurazione di Mistral mancante per la creazione dell'embedder", + "vercelAiGatewayConfigMissing": "Configurazione di Vercel AI Gateway mancante per la creazione dell'embedder", "invalidEmbedderType": "Tipo di embedder configurato non valido: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Assicurati che la 'Dimensione di embedding' sia impostata correttamente nelle impostazioni del provider compatibile con OpenAI.", "vectorDimensionNotDetermined": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Controlla i profili del modello o la configurazione.", diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index 799c6745fa..a1a37ebffa 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "エンベッダー作成のためのOpenAI互換設定がありません", "geminiConfigMissing": "エンベッダー作成のためのGemini設定がありません", "mistralConfigMissing": "エンベッダー作成のためのMistral設定がありません", + "vercelAiGatewayConfigMissing": "エンベッダー作成のためのVercel AI Gateway設定がありません", "invalidEmbedderType": "無効なエンベッダータイプが設定されています: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。OpenAI互換プロバイダー設定で「埋め込み次元」が正しく設定されていることを確認してください。", "vectorDimensionNotDetermined": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。モデルプロファイルまたは設定を確認してください。", diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 3817135982..6c81c9d7d7 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "임베더 생성을 위한 OpenAI 호환 구성이 누락되었습니다", "geminiConfigMissing": "임베더 생성을 위한 Gemini 구성이 누락되었습니다", "mistralConfigMissing": "임베더 생성을 위한 Mistral 구성이 없습니다", + "vercelAiGatewayConfigMissing": "임베더 생성을 위한 Vercel AI Gateway 구성이 없습니다", "invalidEmbedderType": "잘못된 임베더 유형이 구성되었습니다: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. OpenAI 호환 프로바이더 설정에서 '임베딩 차원'이 올바르게 설정되어 있는지 확인하세요.", "vectorDimensionNotDetermined": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. 모델 프로필 또는 구성을 확인하세요.", diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index 52d675c890..7ae59ae02a 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "OpenAI-compatibele configuratie ontbreekt voor het maken van embedder", "geminiConfigMissing": "Gemini-configuratie ontbreekt voor het maken van embedder", "mistralConfigMissing": "Mistral-configuratie ontbreekt voor het maken van de embedder", + "vercelAiGatewayConfigMissing": "Vercel AI Gateway-configuratie ontbreekt voor het maken van de embedder", "invalidEmbedderType": "Ongeldig embedder-type geconfigureerd: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Zorg ervoor dat de 'Embedding Dimensie' correct is ingesteld in de OpenAI-compatibele provider-instellingen.", "vectorDimensionNotDetermined": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Controleer modelprofielen of configuratie.", diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 4d1ad0316c..8f75d00af8 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Brak konfiguracji kompatybilnej z OpenAI do utworzenia embeddera", "geminiConfigMissing": "Brak konfiguracji Gemini do utworzenia embeddera", "mistralConfigMissing": "Brak konfiguracji Mistral do utworzenia embeddera", + "vercelAiGatewayConfigMissing": "Brak konfiguracji Vercel AI Gateway do utworzenia embeddera", "invalidEmbedderType": "Skonfigurowano nieprawidłowy typ embeddera: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Upewnij się, że 'Wymiar osadzania' jest poprawnie ustawiony w ustawieniach dostawcy kompatybilnego z OpenAI.", "vectorDimensionNotDetermined": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Sprawdź profile modelu lub konfigurację.", diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index 875bba95dc..ee135ed8b5 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Configuração compatível com OpenAI ausente para criação do embedder", "geminiConfigMissing": "Configuração do Gemini ausente para criação do embedder", "mistralConfigMissing": "Configuração do Mistral ausente para a criação do embedder", + "vercelAiGatewayConfigMissing": "Configuração do Vercel AI Gateway ausente para a criação do embedder", "invalidEmbedderType": "Tipo de embedder configurado inválido: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Certifique-se de que a 'Dimensão de Embedding' esteja configurada corretamente nas configurações do provedor compatível com OpenAI.", "vectorDimensionNotDetermined": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Verifique os perfis do modelo ou a configuração.", diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 80dfa9a594..97301b3209 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Отсутствует конфигурация, совместимая с OpenAI, для создания эмбеддера", "geminiConfigMissing": "Отсутствует конфигурация Gemini для создания эмбеддера", "mistralConfigMissing": "Конфигурация Mistral отсутствует для создания эмбеддера", + "vercelAiGatewayConfigMissing": "Конфигурация Vercel AI Gateway отсутствует для создания эмбеддера", "invalidEmbedderType": "Настроен недопустимый тип эмбеддера: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Убедитесь, что 'Размерность эмбеддинга' правильно установлена в настройках провайдера, совместимого с OpenAI.", "vectorDimensionNotDetermined": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Проверьте профили модели или конфигурацию.", diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index ba717b7c82..279fa97516 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Gömücü oluşturmak için OpenAI uyumlu yapılandırması eksik", "geminiConfigMissing": "Gömücü oluşturmak için Gemini yapılandırması eksik", "mistralConfigMissing": "Gömücü oluşturmak için Mistral yapılandırması eksik", + "vercelAiGatewayConfigMissing": "Gömücü oluşturmak için Vercel AI Gateway yapılandırması eksik", "invalidEmbedderType": "Geçersiz gömücü türü yapılandırıldı: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. OpenAI uyumlu sağlayıcı ayarlarında 'Gömme Boyutu'nun doğru ayarlandığından emin ol.", "vectorDimensionNotDetermined": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. Model profillerini veya yapılandırmayı kontrol et.", diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index 12980b3345..3e941aafb6 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "Thiếu cấu hình tương thích OpenAI để tạo embedder", "geminiConfigMissing": "Thiếu cấu hình Gemini để tạo embedder", "mistralConfigMissing": "Thiếu cấu hình Mistral để tạo trình nhúng", + "vercelAiGatewayConfigMissing": "Thiếu cấu hình Vercel AI Gateway để tạo trình nhúng", "invalidEmbedderType": "Loại embedder được cấu hình không hợp lệ: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Hãy đảm bảo 'Kích thước Embedding' được cài đặt đúng trong cài đặt nhà cung cấp tương thích OpenAI.", "vectorDimensionNotDetermined": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Kiểm tra hồ sơ mô hình hoặc cấu hình.", diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index 1589689c06..d16f7df32b 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "创建嵌入器缺少 OpenAI 兼容配置", "geminiConfigMissing": "创建嵌入器缺少 Gemini 配置", "mistralConfigMissing": "创建嵌入器时缺少 Mistral 配置", + "vercelAiGatewayConfigMissing": "创建嵌入器时缺少 Vercel AI Gateway 配置", "invalidEmbedderType": "配置的嵌入器类型无效:{{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请确保在 OpenAI 兼容提供商设置中正确设置了「嵌入维度」。", "vectorDimensionNotDetermined": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请检查模型配置文件或配置。", diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 2dc41221f3..044de1dac2 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -47,6 +47,7 @@ "openAiCompatibleConfigMissing": "建立嵌入器缺少 OpenAI 相容設定", "geminiConfigMissing": "建立嵌入器缺少 Gemini 設定", "mistralConfigMissing": "建立嵌入器時缺少 Mistral 設定", + "vercelAiGatewayConfigMissing": "建立嵌入器時缺少 Vercel AI Gateway 設定", "invalidEmbedderType": "設定的嵌入器類型無效:{{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請確保在 OpenAI 相容提供商設定中正確設定了「嵌入維度」。", "vectorDimensionNotDetermined": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請檢查模型設定檔或設定。", diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 1723f1c2a0..2c0e8bb5c9 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -19,6 +19,7 @@ export class CodeIndexConfigManager { private openAiCompatibleOptions?: { baseUrl: string; apiKey: string } private geminiOptions?: { apiKey: string } private mistralOptions?: { apiKey: string } + private vercelAiGatewayOptions?: { apiKey: string } private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string private searchMinScore?: number @@ -69,6 +70,7 @@ export class CodeIndexConfigManager { const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? "" const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? "" const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? "" + const vercelAiGatewayApiKey = this.contextProxy?.getSecret("codebaseIndexVercelAiGatewayApiKey") ?? "" // Update instance variables with configuration this.codebaseIndexEnabled = codebaseIndexEnabled ?? true @@ -104,6 +106,8 @@ export class CodeIndexConfigManager { this.embedderProvider = "gemini" } else if (codebaseIndexEmbedderProvider === "mistral") { this.embedderProvider = "mistral" + } else if (codebaseIndexEmbedderProvider === "vercel-ai-gateway") { + this.embedderProvider = "vercel-ai-gateway" } else { this.embedderProvider = "openai" } @@ -124,6 +128,7 @@ export class CodeIndexConfigManager { this.geminiOptions = geminiApiKey ? { apiKey: geminiApiKey } : undefined this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined + this.vercelAiGatewayOptions = vercelAiGatewayApiKey ? { apiKey: vercelAiGatewayApiKey } : undefined } /** @@ -141,6 +146,7 @@ export class CodeIndexConfigManager { openAiCompatibleOptions?: { baseUrl: string; apiKey: string } geminiOptions?: { apiKey: string } mistralOptions?: { apiKey: string } + vercelAiGatewayOptions?: { apiKey: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -160,6 +166,7 @@ export class CodeIndexConfigManager { openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "", geminiApiKey: this.geminiOptions?.apiKey ?? "", mistralApiKey: this.mistralOptions?.apiKey ?? "", + vercelAiGatewayApiKey: this.vercelAiGatewayOptions?.apiKey ?? "", qdrantUrl: this.qdrantUrl ?? "", qdrantApiKey: this.qdrantApiKey ?? "", } @@ -184,6 +191,7 @@ export class CodeIndexConfigManager { openAiCompatibleOptions: this.openAiCompatibleOptions, geminiOptions: this.geminiOptions, mistralOptions: this.mistralOptions, + vercelAiGatewayOptions: this.vercelAiGatewayOptions, qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.currentSearchMinScore, @@ -221,6 +229,11 @@ export class CodeIndexConfigManager { const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured + } else if (this.embedderProvider === "vercel-ai-gateway") { + const apiKey = this.vercelAiGatewayOptions?.apiKey + const qdrantUrl = this.qdrantUrl + const isConfigured = !!(apiKey && qdrantUrl) + return isConfigured } return false // Should not happen if embedderProvider is always set correctly } @@ -255,6 +268,7 @@ export class CodeIndexConfigManager { const prevModelDimension = prev?.modelDimension const prevGeminiApiKey = prev?.geminiApiKey ?? "" const prevMistralApiKey = prev?.mistralApiKey ?? "" + const prevVercelAiGatewayApiKey = prev?.vercelAiGatewayApiKey ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" @@ -292,6 +306,7 @@ export class CodeIndexConfigManager { const currentModelDimension = this.modelDimension const currentGeminiApiKey = this.geminiOptions?.apiKey ?? "" const currentMistralApiKey = this.mistralOptions?.apiKey ?? "" + const currentVercelAiGatewayApiKey = this.vercelAiGatewayOptions?.apiKey ?? "" const currentQdrantUrl = this.qdrantUrl ?? "" const currentQdrantApiKey = this.qdrantApiKey ?? "" @@ -318,6 +333,10 @@ export class CodeIndexConfigManager { return true } + if (prevVercelAiGatewayApiKey !== currentVercelAiGatewayApiKey) { + return true + } + // Check for model dimension changes (generic for all providers) if (prevModelDimension !== currentModelDimension) { return true @@ -375,6 +394,7 @@ export class CodeIndexConfigManager { openAiCompatibleOptions: this.openAiCompatibleOptions, geminiOptions: this.geminiOptions, mistralOptions: this.mistralOptions, + vercelAiGatewayOptions: this.vercelAiGatewayOptions, qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.currentSearchMinScore, diff --git a/src/services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts b/src/services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts new file mode 100644 index 0000000000..0b819f9bbe --- /dev/null +++ b/src/services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts @@ -0,0 +1,176 @@ +// npx vitest run src/services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts + +import { describe, it, expect, vi, beforeEach } from "vitest" +import { VercelAiGatewayEmbedder } from "../vercel-ai-gateway" +import { OpenAICompatibleEmbedder } from "../openai-compatible" + +// Mock the OpenAICompatibleEmbedder +vi.mock("../openai-compatible", () => ({ + OpenAICompatibleEmbedder: vi.fn(), +})) + +// Mock the TelemetryService +vi.mock("@roo-code/telemetry", () => ({ + TelemetryService: { + instance: { + captureEvent: vi.fn(), + }, + }, +})) + +const MockedOpenAICompatibleEmbedder = vi.mocked(OpenAICompatibleEmbedder) + +describe("VercelAiGatewayEmbedder", () => { + let embedder: VercelAiGatewayEmbedder + let mockOpenAICompatibleEmbedder: any + + beforeEach(() => { + vi.clearAllMocks() + mockOpenAICompatibleEmbedder = { + createEmbeddings: vi.fn(), + validateConfiguration: vi.fn(), + } + MockedOpenAICompatibleEmbedder.mockImplementation(() => mockOpenAICompatibleEmbedder) + }) + + describe("constructor", () => { + it("should create VercelAiGatewayEmbedder with default model", () => { + // Arrange + const apiKey = "test-vercel-api-key" + + // Act + embedder = new VercelAiGatewayEmbedder(apiKey) + + // Assert + expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith( + "https://ai-gateway.vercel.sh/v1", + apiKey, + "openai/text-embedding-3-large", + 8191, + ) + }) + + it("should create VercelAiGatewayEmbedder with custom model", () => { + // Arrange + const apiKey = "test-vercel-api-key" + const modelId = "openai/text-embedding-3-small" + + // Act + embedder = new VercelAiGatewayEmbedder(apiKey, modelId) + + // Assert + expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith( + "https://ai-gateway.vercel.sh/v1", + apiKey, + "openai/text-embedding-3-small", + 8191, + ) + }) + + it("should throw error when API key is missing", () => { + // Act & Assert + expect(() => new VercelAiGatewayEmbedder("")).toThrow("validation.apiKeyRequired") + }) + }) + + describe("createEmbeddings", () => { + beforeEach(() => { + embedder = new VercelAiGatewayEmbedder("test-api-key") + }) + + it("should delegate to OpenAICompatibleEmbedder with default model", async () => { + // Arrange + const texts = ["test text 1", "test text 2"] + const expectedResponse = { + embeddings: [ + [0.1, 0.2], + [0.3, 0.4], + ], + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(expectedResponse) + + // Act + const result = await embedder.createEmbeddings(texts) + + // Assert + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalledWith( + texts, + "openai/text-embedding-3-large", + ) + expect(result).toBe(expectedResponse) + }) + + it("should delegate to OpenAICompatibleEmbedder with custom model", async () => { + // Arrange + const texts = ["test text"] + const customModel = "google/gemini-embedding-001" + const expectedResponse = { embeddings: [[0.1, 0.2, 0.3]] } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(expectedResponse) + + // Act + const result = await embedder.createEmbeddings(texts, customModel) + + // Assert + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalledWith(texts, customModel) + expect(result).toBe(expectedResponse) + }) + + it("should handle errors from OpenAICompatibleEmbedder", async () => { + // Arrange + const texts = ["test text"] + const error = new Error("API request failed") + mockOpenAICompatibleEmbedder.createEmbeddings.mockRejectedValue(error) + + // Act & Assert + await expect(embedder.createEmbeddings(texts)).rejects.toThrow("API request failed") + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalledWith( + texts, + "openai/text-embedding-3-large", + ) + }) + }) + + describe("validateConfiguration", () => { + beforeEach(() => { + embedder = new VercelAiGatewayEmbedder("test-api-key") + }) + + it("should delegate to OpenAICompatibleEmbedder", async () => { + // Arrange + const expectedResult = { valid: true } + mockOpenAICompatibleEmbedder.validateConfiguration.mockResolvedValue(expectedResult) + + // Act + const result = await embedder.validateConfiguration() + + // Assert + expect(mockOpenAICompatibleEmbedder.validateConfiguration).toHaveBeenCalled() + expect(result).toBe(expectedResult) + }) + + it("should handle validation errors", async () => { + // Arrange + const error = new Error("Validation failed") + mockOpenAICompatibleEmbedder.validateConfiguration.mockRejectedValue(error) + + // Act & Assert + await expect(embedder.validateConfiguration()).rejects.toThrow("Validation failed") + expect(mockOpenAICompatibleEmbedder.validateConfiguration).toHaveBeenCalled() + }) + }) + + describe("embedderInfo", () => { + it("should return correct embedder info", () => { + // Arrange + embedder = new VercelAiGatewayEmbedder("test-api-key") + + // Act + const info = embedder.embedderInfo + + // Assert + expect(info).toEqual({ + name: "vercel-ai-gateway", + }) + }) + }) +}) diff --git a/src/services/code-index/embedders/vercel-ai-gateway.ts b/src/services/code-index/embedders/vercel-ai-gateway.ts new file mode 100644 index 0000000000..ec0888cd8b --- /dev/null +++ b/src/services/code-index/embedders/vercel-ai-gateway.ts @@ -0,0 +1,100 @@ +import { OpenAICompatibleEmbedder } from "./openai-compatible" +import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder" +import { MAX_ITEM_TOKENS } from "../constants" +import { t } from "../../../i18n" +import { TelemetryEventName } from "@roo-code/types" +import { TelemetryService } from "@roo-code/telemetry" + +/** + * Vercel AI Gateway embedder implementation that wraps the OpenAI Compatible embedder + * with configuration for Vercel AI Gateway's embedding API. + * + * Supported models: + * - openai/text-embedding-3-small (dimension: 1536) + * - openai/text-embedding-3-large (dimension: 3072) + * - openai/text-embedding-ada-002 (dimension: 1536) + * - cohere/embed-v4.0 (dimension: 1024) + * - google/gemini-embedding-001 (dimension: 768) + * - google/text-embedding-005 (dimension: 768) + * - google/text-multilingual-embedding-002 (dimension: 768) + * - amazon/titan-embed-text-v2 (dimension: 1024) + * - mistral/codestral-embed (dimension: 1536) + * - mistral/mistral-embed (dimension: 1024) + */ +export class VercelAiGatewayEmbedder implements IEmbedder { + private readonly openAICompatibleEmbedder: OpenAICompatibleEmbedder + private static readonly VERCEL_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1" + private static readonly DEFAULT_MODEL = "openai/text-embedding-3-large" + private readonly modelId: string + + /** + * Creates a new Vercel AI Gateway embedder + * @param apiKey The Vercel AI Gateway API key for authentication + * @param modelId The model ID to use (defaults to mistral/codestral-embed) + */ + constructor(apiKey: string, modelId?: string) { + if (!apiKey) { + throw new Error(t("embeddings:validation.apiKeyRequired")) + } + + // Use provided model or default + this.modelId = modelId || VercelAiGatewayEmbedder.DEFAULT_MODEL + + // Create an OpenAI Compatible embedder with Vercel AI Gateway's configuration + this.openAICompatibleEmbedder = new OpenAICompatibleEmbedder( + VercelAiGatewayEmbedder.VERCEL_AI_GATEWAY_BASE_URL, + apiKey, + this.modelId, + MAX_ITEM_TOKENS, + ) + } + + /** + * Creates embeddings for the given texts using Vercel AI Gateway's embedding API + * @param texts Array of text strings to embed + * @param model Optional model identifier (uses constructor model if not provided) + * @returns Promise resolving to embedding response + */ + async createEmbeddings(texts: string[], model?: string): Promise { + try { + // Use the provided model or fall back to the instance's model + const modelToUse = model || this.modelId + return await this.openAICompatibleEmbedder.createEmbeddings(texts, modelToUse) + } catch (error) { + TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + location: "VercelAiGatewayEmbedder:createEmbeddings", + }) + throw error + } + } + + /** + * Validates the Vercel AI Gateway embedder configuration by delegating to the underlying OpenAI-compatible embedder + * @returns Promise resolving to validation result with success status and optional error message + */ + async validateConfiguration(): Promise<{ valid: boolean; error?: string }> { + try { + // Delegate validation to the OpenAI-compatible embedder + // The error messages will be specific to Vercel AI Gateway since we're using Vercel's base URL + return await this.openAICompatibleEmbedder.validateConfiguration() + } catch (error) { + TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + location: "VercelAiGatewayEmbedder:validateConfiguration", + }) + throw error + } + } + + /** + * Returns information about this embedder + */ + get embedderInfo(): EmbedderInfo { + return { + name: "vercel-ai-gateway", + } + } +} diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index 9098a60091..f168e26869 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -14,6 +14,7 @@ export interface CodeIndexConfig { openAiCompatibleOptions?: { baseUrl: string; apiKey: string } geminiOptions?: { apiKey: string } mistralOptions?: { apiKey: string } + vercelAiGatewayOptions?: { apiKey: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -35,6 +36,7 @@ export type PreviousConfigSnapshot = { openAiCompatibleApiKey?: string geminiApiKey?: string mistralApiKey?: string + vercelAiGatewayApiKey?: string qdrantUrl?: string qdrantApiKey?: string } diff --git a/src/services/code-index/interfaces/embedder.ts b/src/services/code-index/interfaces/embedder.ts index c5653ea2b7..1fcda3aca3 100644 --- a/src/services/code-index/interfaces/embedder.ts +++ b/src/services/code-index/interfaces/embedder.ts @@ -28,7 +28,7 @@ export interface EmbeddingResponse { } } -export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" +export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" export interface EmbedderInfo { name: AvailableEmbedders diff --git a/src/services/code-index/interfaces/manager.ts b/src/services/code-index/interfaces/manager.ts index fd3b2bfdda..527900f6d1 100644 --- a/src/services/code-index/interfaces/manager.ts +++ b/src/services/code-index/interfaces/manager.ts @@ -70,7 +70,7 @@ export interface ICodeIndexManager { } export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" -export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" +export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" export interface IndexProgressUpdate { systemStatus: IndexingState diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 5c1c366107..409c591a6b 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -4,6 +4,7 @@ import { CodeIndexOllamaEmbedder } from "./embedders/ollama" import { OpenAICompatibleEmbedder } from "./embedders/openai-compatible" import { GeminiEmbedder } from "./embedders/gemini" import { MistralEmbedder } from "./embedders/mistral" +import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway" import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels" import { QdrantVectorStore } from "./vector-store/qdrant-client" import { codeParser, DirectoryScanner, FileWatcher } from "./processors" @@ -71,6 +72,11 @@ export class CodeIndexServiceFactory { throw new Error(t("embeddings:serviceFactory.mistralConfigMissing")) } return new MistralEmbedder(config.mistralOptions.apiKey, config.modelId) + } else if (provider === "vercel-ai-gateway") { + if (!config.vercelAiGatewayOptions?.apiKey) { + throw new Error(t("embeddings:serviceFactory.vercelAiGatewayConfigMissing")) + } + return new VercelAiGatewayEmbedder(config.vercelAiGatewayOptions.apiKey, config.modelId) } throw new Error( diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d8b873e40a..57bad0e402 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -259,7 +259,13 @@ export interface WebviewMessage { // Global state settings codebaseIndexEnabled: boolean codebaseIndexQdrantUrl: string - codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" + codebaseIndexEmbedderProvider: + | "openai" + | "ollama" + | "openai-compatible" + | "gemini" + | "mistral" + | "vercel-ai-gateway" codebaseIndexEmbedderBaseUrl?: string codebaseIndexEmbedderModelId: string codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers @@ -273,6 +279,7 @@ export interface WebviewMessage { codebaseIndexOpenAiCompatibleApiKey?: string codebaseIndexGeminiApiKey?: string codebaseIndexMistralApiKey?: string + codebaseIndexVercelAiGatewayApiKey?: string } } diff --git a/src/shared/embeddingModels.ts b/src/shared/embeddingModels.ts index a3cd61e659..80c51a6b45 100644 --- a/src/shared/embeddingModels.ts +++ b/src/shared/embeddingModels.ts @@ -2,7 +2,7 @@ * Defines profiles for different embedding models, including their dimensions. */ -export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" // Add other providers as needed +export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" // Add other providers as needed export interface EmbeddingModelProfile { dimension: number @@ -53,6 +53,23 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = { mistral: { "codestral-embed-2505": { dimension: 1536, scoreThreshold: 0.4 }, }, + "vercel-ai-gateway": { + // OpenAI models + "openai/text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 }, + "openai/text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 }, + "openai/text-embedding-ada-002": { dimension: 1536, scoreThreshold: 0.4 }, + // Cohere models + "cohere/embed-v4.0": { dimension: 1024, scoreThreshold: 0.4 }, + // Google models + "google/gemini-embedding-001": { dimension: 3072, scoreThreshold: 0.4 }, + "google/text-embedding-005": { dimension: 768, scoreThreshold: 0.4 }, + "google/text-multilingual-embedding-002": { dimension: 768, scoreThreshold: 0.4 }, + // Amazon models + "amazon/titan-embed-text-v2": { dimension: 1024, scoreThreshold: 0.4 }, + // Mistral models + "mistral/codestral-embed": { dimension: 1536, scoreThreshold: 0.4 }, + "mistral/mistral-embed": { dimension: 1024, scoreThreshold: 0.4 }, + }, } /** @@ -143,6 +160,9 @@ export function getDefaultModelId(provider: EmbedderProvider): string { case "mistral": return "codestral-embed-2505" + case "vercel-ai-gateway": + return "openai/text-embedding-3-large" + default: // Fallback for unknown providers console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`) diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 4a90a60f3d..c8cba6dbed 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -70,6 +70,7 @@ interface LocalCodeIndexSettings { codebaseIndexOpenAiCompatibleApiKey?: string codebaseIndexGeminiApiKey?: string codebaseIndexMistralApiKey?: string + codebaseIndexVercelAiGatewayApiKey?: string } // Validation schema for codebase index settings @@ -136,6 +137,16 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => { .min(1, t("settings:codeIndex.validation.modelSelectionRequired")), }) + case "vercel-ai-gateway": + return baseSchema.extend({ + codebaseIndexVercelAiGatewayApiKey: z + .string() + .min(1, t("settings:codeIndex.validation.vercelAiGatewayApiKeyRequired")), + codebaseIndexEmbedderModelId: z + .string() + .min(1, t("settings:codeIndex.validation.modelSelectionRequired")), + }) + default: return baseSchema } @@ -180,6 +191,7 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexOpenAiCompatibleApiKey: "", codebaseIndexGeminiApiKey: "", codebaseIndexMistralApiKey: "", + codebaseIndexVercelAiGatewayApiKey: "", }) // Initial settings state - stores the settings when popover opens @@ -214,6 +226,7 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexOpenAiCompatibleApiKey: "", codebaseIndexGeminiApiKey: "", codebaseIndexMistralApiKey: "", + codebaseIndexVercelAiGatewayApiKey: "", } setInitialSettings(settings) setCurrentSettings(settings) @@ -322,6 +335,14 @@ export const CodeIndexPopover: React.FC = ({ if (!prev.codebaseIndexMistralApiKey || prev.codebaseIndexMistralApiKey === SECRET_PLACEHOLDER) { updated.codebaseIndexMistralApiKey = secretStatus.hasMistralApiKey ? SECRET_PLACEHOLDER : "" } + if ( + !prev.codebaseIndexVercelAiGatewayApiKey || + prev.codebaseIndexVercelAiGatewayApiKey === SECRET_PLACEHOLDER + ) { + updated.codebaseIndexVercelAiGatewayApiKey = secretStatus.hasVercelAiGatewayApiKey + ? SECRET_PLACEHOLDER + : "" + } return updated } @@ -394,7 +415,8 @@ export const CodeIndexPopover: React.FC = ({ key === "codeIndexOpenAiKey" || key === "codebaseIndexOpenAiCompatibleApiKey" || key === "codebaseIndexGeminiApiKey" || - key === "codebaseIndexMistralApiKey" + key === "codebaseIndexMistralApiKey" || + key === "codebaseIndexVercelAiGatewayApiKey" ) { dataToValidate[key] = "placeholder-valid" } @@ -642,6 +664,9 @@ export const CodeIndexPopover: React.FC = ({ {t("settings:codeIndex.mistralProvider")} + + {t("settings:codeIndex.vercelAiGatewayProvider")} + @@ -1034,6 +1059,76 @@ export const CodeIndexPopover: React.FC = ({ )} + {currentSettings.codebaseIndexEmbedderProvider === "vercel-ai-gateway" && ( + <> +
+ + + updateSetting( + "codebaseIndexVercelAiGatewayApiKey", + e.target.value, + ) + } + placeholder={t( + "settings:codeIndex.vercelAiGatewayApiKeyPlaceholder", + )} + className={cn("w-full", { + "border-red-500": formErrors.codebaseIndexVercelAiGatewayApiKey, + })} + /> + {formErrors.codebaseIndexVercelAiGatewayApiKey && ( +

+ {formErrors.codebaseIndexVercelAiGatewayApiKey} +

+ )} +
+ +
+ + + updateSetting("codebaseIndexEmbedderModelId", e.target.value) + } + className={cn("w-full", { + "border-red-500": formErrors.codebaseIndexEmbedderModelId, + })}> + + {t("settings:codeIndex.selectModel")} + + {getAvailableModels().map((modelId) => { + const model = + codebaseIndexModels?.[ + currentSettings.codebaseIndexEmbedderProvider + ]?.[modelId] + return ( + + {modelId}{" "} + {model + ? t("settings:codeIndex.modelDimensions", { + dimension: model.dimension, + }) + : ""} + + ) + })} + + {formErrors.codebaseIndexEmbedderModelId && ( +

+ {formErrors.codebaseIndexEmbedderModelId} +

+ )} +
+ + )} + {/* Qdrant Settings */}