diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index be7778f5387..14831c7dedf 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -22,7 +22,7 @@ export const codebaseIndexConfigSchema = z.object({ codebaseIndexEnabled: z.boolean().optional(), codebaseIndexQdrantUrl: z.string().optional(), codebaseIndexEmbedderProvider: z - .enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway"]) + .enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway", "nebius"]) .optional(), codebaseIndexEmbedderBaseUrl: z.string().optional(), codebaseIndexEmbedderModelId: z.string().optional(), @@ -51,6 +51,7 @@ export const codebaseIndexModelsSchema = z.object({ 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(), + nebius: z.record(z.string(), z.object({ dimension: z.number() })).optional(), }) export type CodebaseIndexModels = z.infer @@ -68,6 +69,7 @@ export const codebaseIndexProviderSchema = z.object({ codebaseIndexGeminiApiKey: z.string().optional(), codebaseIndexMistralApiKey: z.string().optional(), codebaseIndexVercelAiGatewayApiKey: z.string().optional(), + codebaseIndexNebiusApiKey: 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 a56a00fc355..e81a12cf231 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -199,6 +199,7 @@ export const SECRET_STATE_KEYS = [ "codebaseIndexGeminiApiKey", "codebaseIndexMistralApiKey", "codebaseIndexVercelAiGatewayApiKey", + "codebaseIndexNebiusApiKey", "huggingFaceApiKey", "sambaNovaApiKey", "zaiApiKey", diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index af5f9925c35..c6387d3067b 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2493,6 +2493,12 @@ export const webviewMessageHandler = async ( settings.codebaseIndexVercelAiGatewayApiKey, ) } + if (settings.codebaseIndexNebiusApiKey !== undefined) { + await provider.contextProxy.storeSecret( + "codebaseIndexNebiusApiKey", + settings.codebaseIndexNebiusApiKey, + ) + } // Send success response first - settings are saved regardless of validation await provider.postMessageToWebview({ @@ -2630,6 +2636,7 @@ export const webviewMessageHandler = async ( const hasVercelAiGatewayApiKey = !!(await provider.context.secrets.get( "codebaseIndexVercelAiGatewayApiKey", )) + const hasNebiusApiKey = !!(await provider.context.secrets.get("codebaseIndexNebiusApiKey")) provider.postMessageToWebview({ type: "codeIndexSecretStatus", @@ -2640,6 +2647,7 @@ export const webviewMessageHandler = async ( hasGeminiApiKey, hasMistralApiKey, hasVercelAiGatewayApiKey, + hasNebiusApiKey, }, }) break diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 782f92cddfd..ab2e9315a46 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Falta la configuració de Nebius 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ó.", "qdrantUrlMissing": "Falta l'URL de Qdrant per crear l'emmagatzematge de vectors", "codeIndexingNotConfigured": "No es poden crear serveis: La indexació de codi no està configurada correctament" }, + "nebius": { + "rateLimitExceeded": "S'ha superat el límit de Nebius per a {{type}} (límit: {{limit}} per {{window}}).", + "waitingForRateLimit": "Esperant que es restableixi el límit de Nebius (pausant {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indexació fallida: No s'ha indexat cap bloc de codi amb èxit. Això normalment indica un problema de configuració de l'embedder.", "indexingFailedCritical": "Indexació fallida: No s'ha indexat cap bloc de codi amb èxit malgrat trobar fitxers per processar. Això indica una fallida crítica de l'embedder.", diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index 239d5d3c8a0..a48b8a83318 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Nebius-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.", "qdrantUrlMissing": "Qdrant-URL fehlt für die Erstellung des Vektorspeichers", "codeIndexingNotConfigured": "Kann keine Dienste erstellen: Code-Indizierung ist nicht richtig konfiguriert" }, + "nebius": { + "rateLimitExceeded": "Nebius-Ratenlimit für {{type}} überschritten (Limit: {{limit}} pro {{window}}).", + "waitingForRateLimit": "Warte auf Zurücksetzen des Nebius-Ratenlimits (pausiere {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indizierung fehlgeschlagen: Keine Code-Blöcke wurden erfolgreich indiziert. Dies deutet normalerweise auf ein Embedder-Konfigurationsproblem hin.", "indexingFailedCritical": "Indizierung fehlgeschlagen: Keine Code-Blöcke wurden erfolgreich indiziert, obwohl zu verarbeitende Dateien gefunden wurden. Dies deutet auf einen kritischen Embedder-Fehler hin.", diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index fc902cadc16..12105f3814a 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "Gemini configuration missing for embedder creation", "mistralConfigMissing": "Mistral configuration missing for embedder creation", "vercelAiGatewayConfigMissing": "Vercel AI Gateway configuration missing for embedder creation", + "nebiusConfigMissing": "Nebius 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.", "qdrantUrlMissing": "Qdrant URL missing for vector store creation", "codeIndexingNotConfigured": "Cannot create services: Code indexing is not properly configured" }, + "nebius": { + "rateLimitExceeded": "Nebius rate limit exceeded for {{type}} (limit: {{limit}} per {{window}}).", + "waitingForRateLimit": "Waiting for Nebius rate limit to reset (sleeping {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indexing failed: No code blocks were successfully indexed. This usually indicates an embedder configuration issue.", "indexingFailedCritical": "Indexing failed: No code blocks were successfully indexed despite finding files to process. This indicates a critical embedder failure.", diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index ac7522ab014..00e1dd1c29b 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Falta la configuración de Nebius 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.", "qdrantUrlMissing": "Falta la URL de Qdrant para crear el almacén de vectores", "codeIndexingNotConfigured": "No se pueden crear servicios: La indexación de código no está configurada correctamente" }, + "nebius": { + "rateLimitExceeded": "Límite de Nebius excedido para {{type}} (límite: {{limit}} por {{window}}).", + "waitingForRateLimit": "Esperando el restablecimiento del límite de Nebius (pausando {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indexación fallida: No se indexaron exitosamente bloques de código. Esto usualmente indica un problema de configuración del incrustador.", "indexingFailedCritical": "Indexación fallida: No se indexaron exitosamente bloques de código a pesar de encontrar archivos para procesar. Esto indica una falla crítica del incrustador.", diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index e4dff280129..fce6371e189 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Configuration Nebius 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.", "qdrantUrlMissing": "URL Qdrant manquante pour la création du stockage de vecteurs", "codeIndexingNotConfigured": "Impossible de créer les services : L'indexation du code n'est pas correctement configurée" }, + "nebius": { + "rateLimitExceeded": "Limite Nebius dépassée pour {{type}} (limite : {{limit}} par {{window}}).", + "waitingForRateLimit": "En attente de la réinitialisation de la limite Nebius (pause de {{waitTimeMs}} ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Échec de l'indexation : Aucun bloc de code n'a été indexé avec succès. Cela indique généralement un problème de configuration de l'embedder.", "indexingFailedCritical": "Échec de l'indexation : Aucun bloc de code n'a été indexé avec succès malgré la découverte de fichiers à traiter. Cela indique une défaillance critique de l'embedder.", diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index 108d1263730..be0a84cf052 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "एम्बेडर बनाने के लिए Gemini कॉन्फ़िगरेशन गायब है", "mistralConfigMissing": "एम्बेडर निर्माण के लिए मिस्ट्रल कॉन्फ़िगरेशन गायब है", "vercelAiGatewayConfigMissing": "एम्बेडर निर्माण के लिए Vercel AI Gateway कॉन्फ़िगरेशन गायब है", + "nebiusConfigMissing": "एम्बेडर बनाने के लिए Nebius कॉन्फ़िगरेशन गायब है", "invalidEmbedderType": "अमान्य एम्बेडर प्रकार कॉन्फ़िगर किया गया: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। कृपया सुनिश्चित करें कि OpenAI-संगत प्रदाता सेटिंग्स में 'एम्बेडिंग आयाम' सही तरीके से सेट है।", "vectorDimensionNotDetermined": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। मॉडल प्रोफ़ाइल या कॉन्फ़िगरेशन की जांच करें।", "qdrantUrlMissing": "वेक्टर स्टोर बनाने के लिए Qdrant URL गायब है", "codeIndexingNotConfigured": "सेवाएं नहीं बना सकते: कोड इंडेक्सिंग ठीक से कॉन्फ़िगर नहीं है" }, + "nebius": { + "rateLimitExceeded": "Nebius सीमा {{type}} के लिए पार हो गई (सीमा: {{window}} में {{limit}}).", + "waitingForRateLimit": "Nebius सीमा रीसेट होने का इंतजार ({{waitTimeMs}}ms विराम)..." + }, "orchestrator": { "indexingFailedNoBlocks": "इंडेक्सिंग असफल: कोई भी कोड ब्लॉक सफलतापूर्वक इंडेक्स नहीं हुआ। यह आमतौर पर एम्बेडर कॉन्फ़िगरेशन समस्या को दर्शाता है।", "indexingFailedCritical": "इंडेक्सिंग असफल: प्रोसेस करने के लिए फाइलें मिलने के बावजूद कोई भी कोड ब्लॉक सफलतापूर्वक इंडेक्स नहीं हुआ। यह एक गंभीर एम्बेडर विफलता को दर्शाता है।", diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index 8c0fdd490fa..c3cd2e6f4cf 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "Konfigurasi Gemini tidak ada untuk membuat embedder", "mistralConfigMissing": "Konfigurasi Mistral hilang untuk pembuatan embedder", "vercelAiGatewayConfigMissing": "Konfigurasi Vercel AI Gateway hilang untuk pembuatan embedder", + "nebiusConfigMissing": "Konfigurasi Nebius 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.", "qdrantUrlMissing": "URL Qdrant tidak ada untuk membuat penyimpanan vektor", "codeIndexingNotConfigured": "Tidak dapat membuat layanan: Pengindeksan kode tidak dikonfigurasi dengan benar" }, + "nebius": { + "rateLimitExceeded": "Batas Nebius terlampaui untuk {{type}} (batas: {{limit}} per {{window}}).", + "waitingForRateLimit": "Menunggu batas Nebius disetel ulang (jeda {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Pengindeksan gagal: Tidak ada blok kode yang berhasil diindeks. Ini biasanya menunjukkan masalah konfigurasi embedder.", "indexingFailedCritical": "Pengindeksan gagal: Tidak ada blok kode yang berhasil diindeks meskipun menemukan file untuk diproses. Ini menunjukkan kegagalan kritis embedder.", diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index 0ca32f906ea..45f46b0c5f8 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Configurazione Nebius 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.", "qdrantUrlMissing": "URL Qdrant mancante per la creazione dello storage vettoriale", "codeIndexingNotConfigured": "Impossibile creare i servizi: L'indicizzazione del codice non è configurata correttamente" }, + "nebius": { + "rateLimitExceeded": "Limite Nebius superato per {{type}} (limite: {{limit}} per {{window}}).", + "waitingForRateLimit": "In attesa del reset del limite Nebius (pausa di {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indicizzazione fallita: Nessun blocco di codice è stato indicizzato con successo. Questo di solito indica un problema di configurazione dell'embedder.", "indexingFailedCritical": "Indicizzazione fallita: Nessun blocco di codice è stato indicizzato con successo nonostante siano stati trovati file da elaborare. Questo indica un errore critico dell'embedder.", diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index a1a37ebffa3..3d720db5e6e 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "エンベッダー作成のためのGemini設定がありません", "mistralConfigMissing": "エンベッダー作成のためのMistral設定がありません", "vercelAiGatewayConfigMissing": "エンベッダー作成のためのVercel AI Gateway設定がありません", + "nebiusConfigMissing": "エンベッダー作成のためのNebius設定がありません", "invalidEmbedderType": "無効なエンベッダータイプが設定されています: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。OpenAI互換プロバイダー設定で「埋め込み次元」が正しく設定されていることを確認してください。", "vectorDimensionNotDetermined": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。モデルプロファイルまたは設定を確認してください。", "qdrantUrlMissing": "ベクターストア作成のためのQdrant URLがありません", "codeIndexingNotConfigured": "サービスを作成できません: コードインデックスが正しく設定されていません" }, + "nebius": { + "rateLimitExceeded": "Nebius のレート制限を{{type}}で超過しました(上限: {{window}}あたり {{limit}})。", + "waitingForRateLimit": "Nebius のレート制限のリセットを待機中({{waitTimeMs}}ms 待機)..." + }, "orchestrator": { "indexingFailedNoBlocks": "インデックス作成に失敗しました:コードブロックが正常にインデックス化されませんでした。これは通常、エンベッダーの設定問題を示しています。", "indexingFailedCritical": "インデックス作成に失敗しました:処理するファイルが見つかったにもかかわらず、コードブロックが正常にインデックス化されませんでした。これは重大なエンベッダーの障害を示しています。", diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 6c81c9d7d75..96d1c0014c9 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "임베더 생성을 위한 Gemini 구성이 누락되었습니다", "mistralConfigMissing": "임베더 생성을 위한 Mistral 구성이 없습니다", "vercelAiGatewayConfigMissing": "임베더 생성을 위한 Vercel AI Gateway 구성이 없습니다", + "nebiusConfigMissing": "임베더 생성을 위한 Nebius 구성이 없습니다", "invalidEmbedderType": "잘못된 임베더 유형이 구성되었습니다: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. OpenAI 호환 프로바이더 설정에서 '임베딩 차원'이 올바르게 설정되어 있는지 확인하세요.", "vectorDimensionNotDetermined": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. 모델 프로필 또는 구성을 확인하세요.", "qdrantUrlMissing": "벡터 저장소 생성을 위한 Qdrant URL이 누락되었습니다", "codeIndexingNotConfigured": "서비스를 생성할 수 없습니다: 코드 인덱싱이 올바르게 구성되지 않았습니다" }, + "nebius": { + "rateLimitExceeded": "Nebius 제한을 {{type}}에 대해 초과했습니다(제한: {{window}}당 {{limit}}).", + "waitingForRateLimit": "Nebius 제한이 재설정될 때까지 대기 중({{waitTimeMs}}ms 대기)..." + }, "orchestrator": { "indexingFailedNoBlocks": "인덱싱 실패: 코드 블록이 성공적으로 인덱싱되지 않았습니다. 이는 일반적으로 임베더 구성 문제를 나타냅니다.", "indexingFailedCritical": "인덱싱 실패: 처리할 파일을 찾았음에도 불구하고 코드 블록이 성공적으로 인덱싱되지 않았습니다. 이는 중요한 임베더 오류를 나타냅니다.", diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index 7ae59ae02a6..c2af6ea7412 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Nebius-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.", "qdrantUrlMissing": "Qdrant URL ontbreekt voor het maken van vectoropslag", "codeIndexingNotConfigured": "Kan geen services maken: Code-indexering is niet correct geconfigureerd" }, + "nebius": { + "rateLimitExceeded": "Nebius-limiet overschreden voor {{type}} (limiet: {{limit}} per {{window}}).", + "waitingForRateLimit": "Wachten tot Nebius-limiet is gereset (pauze {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indexering mislukt: Geen codeblokken werden succesvol geïndexeerd. Dit duidt meestal op een embedder configuratieprobleem.", "indexingFailedCritical": "Indexering mislukt: Geen codeblokken werden succesvol geïndexeerd ondanks het vinden van bestanden om te verwerken. Dit duidt op een kritieke embedder fout.", diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 8f75d00af88..b88e661ec41 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "Brak konfiguracji Gemini do utworzenia embeddera", "mistralConfigMissing": "Brak konfiguracji Mistral do utworzenia embeddera", "vercelAiGatewayConfigMissing": "Brak konfiguracji Vercel AI Gateway do utworzenia embeddera", + "nebiusConfigMissing": "Brak konfiguracji Nebius 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ę.", "qdrantUrlMissing": "Brak adresu URL Qdrant do utworzenia magazynu wektorów", "codeIndexingNotConfigured": "Nie można utworzyć usług: Indeksowanie kodu nie jest poprawnie skonfigurowane" }, + "nebius": { + "rateLimitExceeded": "Przekroczono limit Nebius dla {{type}} (limit: {{limit}} na {{window}}).", + "waitingForRateLimit": "Oczekiwanie na reset limitu Nebius (pauza {{waitTimeMs}} ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indeksowanie nie powiodło się: Żadne bloki kodu nie zostały pomyślnie zaindeksowane. To zwykle wskazuje na problem z konfiguracją embeddera.", "indexingFailedCritical": "Indeksowanie nie powiodło się: Żadne bloki kodu nie zostały pomyślnie zaindeksowane pomimo znalezienia plików do przetworzenia. To wskazuje na krytyczny błąd embeddera.", diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index ee135ed8b52..8bea2272008 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Configuração do Nebius 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.", "qdrantUrlMissing": "URL do Qdrant ausente para criação do armazenamento de vetores", "codeIndexingNotConfigured": "Não é possível criar serviços: A indexação de código não está configurada corretamente" }, + "nebius": { + "rateLimitExceeded": "Limite do Nebius excedido para {{type}} (limite: {{limit}} por {{window}}).", + "waitingForRateLimit": "Aguardando redefinição do limite do Nebius (pausando {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Indexação falhou: Nenhum bloco de código foi indexado com sucesso. Isso geralmente indica um problema de configuração do embedder.", "indexingFailedCritical": "Indexação falhou: Nenhum bloco de código foi indexado com sucesso apesar de encontrar arquivos para processar. Isso indica uma falha crítica do embedder.", diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 97301b32097..967c932fee0 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "Отсутствует конфигурация Gemini для создания эмбеддера", "mistralConfigMissing": "Конфигурация Mistral отсутствует для создания эмбеддера", "vercelAiGatewayConfigMissing": "Конфигурация Vercel AI Gateway отсутствует для создания эмбеддера", + "nebiusConfigMissing": "Отсутствует конфигурация Nebius для создания эмбеддера", "invalidEmbedderType": "Настроен недопустимый тип эмбеддера: {{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Убедитесь, что 'Размерность эмбеддинга' правильно установлена в настройках провайдера, совместимого с OpenAI.", "vectorDimensionNotDetermined": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Проверьте профили модели или конфигурацию.", "qdrantUrlMissing": "Отсутствует URL Qdrant для создания векторного хранилища", "codeIndexingNotConfigured": "Невозможно создать сервисы: Индексация кода не настроена должным образом" }, + "nebius": { + "rateLimitExceeded": "Превышен лимит Nebius для {{type}} (лимит: {{limit}} на {{window}}).", + "waitingForRateLimit": "Ожидание сброса лимита Nebius (пауза {{waitTimeMs}} мс)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Индексация не удалась: Ни один блок кода не был успешно проиндексирован. Это обычно указывает на проблему конфигурации эмбеддера.", "indexingFailedCritical": "Индексация не удалась: Ни один блок кода не был успешно проиндексирован, несмотря на обнаружение файлов для обработки. Это указывает на критическую ошибку эмбеддера.", diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index 279fa97516a..61c3f68fb19 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Gömücü oluşturmak için Nebius 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.", "qdrantUrlMissing": "Vektör deposu oluşturmak için Qdrant URL'si eksik", "codeIndexingNotConfigured": "Hizmetler oluşturulamıyor: Kod indeksleme düzgün yapılandırılmamış" }, + "nebius": { + "rateLimitExceeded": "Nebius limiti {{type}} için aşıldı (limit: {{window}} başına {{limit}}).", + "waitingForRateLimit": "Nebius limitinin sıfırlanması bekleniyor ({{waitTimeMs}}ms bekleme)..." + }, "orchestrator": { "indexingFailedNoBlocks": "İndeksleme başarısız: Hiçbir kod bloğu başarıyla indekslenemedi. Bu genellikle bir embedder yapılandırma sorunu olduğunu gösterir.", "indexingFailedCritical": "İndeksleme başarısız: İşlenecek dosyalar bulunmasına rağmen hiçbir kod bloğu başarıyla indekslenemedi. Bu kritik bir embedder hatası olduğunu gösterir.", diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index 3e941aafb66..abc9a89c398 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -48,12 +48,17 @@ "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", + "nebiusConfigMissing": "Thiếu cấu hình Nebius để 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.", "qdrantUrlMissing": "Thiếu URL Qdrant để tạo kho lưu trữ vector", "codeIndexingNotConfigured": "Không thể tạo dịch vụ: Lập chỉ mục mã không được cấu hình đúng cách" }, + "nebius": { + "rateLimitExceeded": "Giới hạn Nebius vượt quá cho {{type}} (giới hạn: {{limit}} mỗi {{window}}).", + "waitingForRateLimit": "Đang chờ đặt lại giới hạn Nebius (tạm dừng {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "Lập chỉ mục thất bại: Không có khối mã nào được lập chỉ mục thành công. Điều này thường cho thấy vấn đề cấu hình embedder.", "indexingFailedCritical": "Lập chỉ mục thất bại: Không có khối mã nào được lập chỉ mục thành công mặc dù đã tìm thấy tệp để xử lý. Điều này cho thấy lỗi nghiêm trọng của embedder.", diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index d16f7df32b2..410fa111a87 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "创建嵌入器缺少 Gemini 配置", "mistralConfigMissing": "创建嵌入器时缺少 Mistral 配置", "vercelAiGatewayConfigMissing": "创建嵌入器时缺少 Vercel AI Gateway 配置", + "nebiusConfigMissing": "创建嵌入器时缺少 Nebius 配置", "invalidEmbedderType": "配置的嵌入器类型无效:{{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请确保在 OpenAI 兼容提供商设置中正确设置了「嵌入维度」。", "vectorDimensionNotDetermined": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请检查模型配置文件或配置。", "qdrantUrlMissing": "创建向量存储缺少 Qdrant URL", "codeIndexingNotConfigured": "无法创建服务:代码索引未正确配置" }, + "nebius": { + "rateLimitExceeded": "Nebius 限速超出:{{type}}(限制:每 {{window}} {{limit}})。", + "waitingForRateLimit": "等待 Nebius 限速重置(暂停 {{waitTimeMs}} 毫秒)..." + }, "orchestrator": { "indexingFailedNoBlocks": "索引失败:没有代码块被成功索引。这通常表示 Embedder 配置问题。", "indexingFailedCritical": "索引失败:尽管找到了要处理的文件,但没有代码块被成功索引。这表示 Embedder 出现严重故障。", diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 044de1dac22..abdcecdecc4 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -48,12 +48,17 @@ "geminiConfigMissing": "建立嵌入器缺少 Gemini 設定", "mistralConfigMissing": "建立嵌入器時缺少 Mistral 設定", "vercelAiGatewayConfigMissing": "建立嵌入器時缺少 Vercel AI Gateway 設定", + "nebiusConfigMissing": "建立嵌入器時缺少 Nebius 設定", "invalidEmbedderType": "設定的嵌入器類型無效:{{embedderProvider}}", "vectorDimensionNotDeterminedOpenAiCompatible": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請確保在 OpenAI 相容提供商設定中正確設定了「嵌入維度」。", "vectorDimensionNotDetermined": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請檢查模型設定檔或設定。", "qdrantUrlMissing": "建立向量儲存缺少 Qdrant URL", "codeIndexingNotConfigured": "無法建立服務:程式碼索引未正確設定" }, + "nebius": { + "rateLimitExceeded": "Nebius 限制超出:{{type}}(限制:每 {{window}} {{limit}})。", + "waitingForRateLimit": "等待 Nebius 限制重設(暫停 {{waitTimeMs}}ms)..." + }, "orchestrator": { "indexingFailedNoBlocks": "索引失敗:沒有程式碼區塊被成功索引。這通常表示 Embedder 設定問題。", "indexingFailedCritical": "索引失敗:儘管找到了要處理的檔案,但沒有程式碼區塊被成功索引。這表示 Embedder 出現嚴重故障。", diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 2c0e8bb5c9e..e05a341032a 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -20,6 +20,7 @@ export class CodeIndexConfigManager { private geminiOptions?: { apiKey: string } private mistralOptions?: { apiKey: string } private vercelAiGatewayOptions?: { apiKey: string } + private nebiusOptions?: { apiKey: string } private qdrantUrl?: string = "http://localhost:6333" private qdrantApiKey?: string private searchMinScore?: number @@ -71,6 +72,7 @@ export class CodeIndexConfigManager { const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? "" const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? "" const vercelAiGatewayApiKey = this.contextProxy?.getSecret("codebaseIndexVercelAiGatewayApiKey") ?? "" + const nebiusApiKey = this.contextProxy?.getSecret("codebaseIndexNebiusApiKey") ?? "" // Update instance variables with configuration this.codebaseIndexEnabled = codebaseIndexEnabled ?? true @@ -108,6 +110,8 @@ export class CodeIndexConfigManager { this.embedderProvider = "mistral" } else if (codebaseIndexEmbedderProvider === "vercel-ai-gateway") { this.embedderProvider = "vercel-ai-gateway" + } else if (codebaseIndexEmbedderProvider === "nebius") { + this.embedderProvider = "nebius" } else { this.embedderProvider = "openai" } @@ -129,6 +133,7 @@ export class CodeIndexConfigManager { this.geminiOptions = geminiApiKey ? { apiKey: geminiApiKey } : undefined this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined this.vercelAiGatewayOptions = vercelAiGatewayApiKey ? { apiKey: vercelAiGatewayApiKey } : undefined + this.nebiusOptions = nebiusApiKey ? { apiKey: nebiusApiKey } : undefined } /** @@ -147,6 +152,7 @@ export class CodeIndexConfigManager { geminiOptions?: { apiKey: string } mistralOptions?: { apiKey: string } vercelAiGatewayOptions?: { apiKey: string } + nebiusOptions?: { apiKey: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -167,6 +173,7 @@ export class CodeIndexConfigManager { geminiApiKey: this.geminiOptions?.apiKey ?? "", mistralApiKey: this.mistralOptions?.apiKey ?? "", vercelAiGatewayApiKey: this.vercelAiGatewayOptions?.apiKey ?? "", + nebiusApiKey: this.nebiusOptions?.apiKey ?? "", qdrantUrl: this.qdrantUrl ?? "", qdrantApiKey: this.qdrantApiKey ?? "", } @@ -192,6 +199,7 @@ export class CodeIndexConfigManager { geminiOptions: this.geminiOptions, mistralOptions: this.mistralOptions, vercelAiGatewayOptions: this.vercelAiGatewayOptions, + nebiusOptions: this.nebiusOptions, qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.currentSearchMinScore, @@ -234,6 +242,11 @@ export class CodeIndexConfigManager { const qdrantUrl = this.qdrantUrl const isConfigured = !!(apiKey && qdrantUrl) return isConfigured + } else if (this.embedderProvider === "nebius") { + const apiKey = this.nebiusOptions?.apiKey + const qdrantUrl = this.qdrantUrl + const isConfigured = !!(apiKey && qdrantUrl) + return isConfigured } return false // Should not happen if embedderProvider is always set correctly } @@ -269,6 +282,7 @@ export class CodeIndexConfigManager { const prevGeminiApiKey = prev?.geminiApiKey ?? "" const prevMistralApiKey = prev?.mistralApiKey ?? "" const prevVercelAiGatewayApiKey = prev?.vercelAiGatewayApiKey ?? "" + const prevNebiusApiKey = prev?.nebiusApiKey ?? "" const prevQdrantUrl = prev?.qdrantUrl ?? "" const prevQdrantApiKey = prev?.qdrantApiKey ?? "" @@ -307,6 +321,7 @@ export class CodeIndexConfigManager { const currentGeminiApiKey = this.geminiOptions?.apiKey ?? "" const currentMistralApiKey = this.mistralOptions?.apiKey ?? "" const currentVercelAiGatewayApiKey = this.vercelAiGatewayOptions?.apiKey ?? "" + const currentNebiusApiKey = this.nebiusOptions?.apiKey ?? "" const currentQdrantUrl = this.qdrantUrl ?? "" const currentQdrantApiKey = this.qdrantApiKey ?? "" @@ -337,6 +352,10 @@ export class CodeIndexConfigManager { return true } + if (prevNebiusApiKey !== currentNebiusApiKey) { + return true + } + // Check for model dimension changes (generic for all providers) if (prevModelDimension !== currentModelDimension) { return true @@ -395,6 +414,7 @@ export class CodeIndexConfigManager { geminiOptions: this.geminiOptions, mistralOptions: this.mistralOptions, vercelAiGatewayOptions: this.vercelAiGatewayOptions, + nebiusOptions: this.nebiusOptions, qdrantUrl: this.qdrantUrl, qdrantApiKey: this.qdrantApiKey, searchMinScore: this.currentSearchMinScore, diff --git a/src/services/code-index/embedders/__tests__/nebius.spec.ts b/src/services/code-index/embedders/__tests__/nebius.spec.ts new file mode 100644 index 00000000000..e765eabda90 --- /dev/null +++ b/src/services/code-index/embedders/__tests__/nebius.spec.ts @@ -0,0 +1,279 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import { NebiusEmbedder } from "../nebius" +import { OpenAICompatibleEmbedder } from "../openai-compatible" +import { TelemetryService } from "@roo-code/telemetry" +import { TelemetryEventName } from "@roo-code/types" + +// Mock the OpenAICompatibleEmbedder +vi.mock("../openai-compatible", () => ({ + OpenAICompatibleEmbedder: vi.fn().mockImplementation(() => ({ + createEmbeddings: vi.fn(), + validateConfiguration: vi.fn(), + })), +})) + +// Mock TelemetryService +vi.mock("@roo-code/telemetry", () => ({ + TelemetryService: { + instance: { + captureEvent: vi.fn(), + }, + }, +})) + +// Mock i18n +vi.mock("../../../../i18n", () => ({ + t: (key: string, params?: any) => { + if (params) { + return `${key} ${JSON.stringify(params)}` + } + return key + }, +})) + +describe("NebiusEmbedder", () => { + let embedder: NebiusEmbedder + const mockApiKey = "test-nebius-api-key" + const mockOpenAICompatibleEmbedder = { + createEmbeddings: vi.fn(), + validateConfiguration: vi.fn(), + } + + beforeEach(() => { + vi.clearAllMocks() + vi.useFakeTimers() + // Reset the mock implementation + ;(OpenAICompatibleEmbedder as any).mockImplementation(() => mockOpenAICompatibleEmbedder) + }) + + afterEach(() => { + vi.useRealTimers() + }) + + describe("constructor", () => { + it("should create an instance with default model", () => { + embedder = new NebiusEmbedder(mockApiKey) + expect(OpenAICompatibleEmbedder).toHaveBeenCalledWith( + "https://api.studio.nebius.com/v1", + mockApiKey, + "Qwen/Qwen3-Embedding-8B", + 8191, // MAX_ITEM_TOKENS + ) + }) + + it("should create an instance with custom model", () => { + const customModel = "custom-model-id" + embedder = new NebiusEmbedder(mockApiKey, customModel) + expect(OpenAICompatibleEmbedder).toHaveBeenCalledWith( + "https://api.studio.nebius.com/v1", + mockApiKey, + customModel, + 8191, + ) + }) + + it("should throw error if API key is not provided", () => { + expect(() => new NebiusEmbedder("")).toThrow("embeddings:validation.apiKeyRequired") + }) + }) + + describe("createEmbeddings", () => { + beforeEach(() => { + embedder = new NebiusEmbedder(mockApiKey) + }) + + it("should delegate to OpenAICompatibleEmbedder with rate limiting", async () => { + const texts = ["test text 1", "test text 2"] + const mockResponse = { + embeddings: [ + [0.1, 0.2], + [0.3, 0.4], + ], + usage: { promptTokens: 10, totalTokens: 10 }, + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(mockResponse) + + const result = await embedder.createEmbeddings(texts) + + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalledWith(texts, "Qwen/Qwen3-Embedding-8B") + expect(result).toEqual(mockResponse) + }) + + it("should use custom model if provided", async () => { + const customModel = "custom-model" + const texts = ["test text"] + const mockResponse = { + embeddings: [[0.1, 0.2]], + usage: { promptTokens: 5, totalTokens: 5 }, + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(mockResponse) + + const result = await embedder.createEmbeddings(texts, customModel) + + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalledWith(texts, customModel) + expect(result).toEqual(mockResponse) + }) + + it("should handle rate limiting when exceeding requests per minute", async () => { + const texts = ["test"] + const mockResponse = { + embeddings: [[0.1, 0.2]], + usage: { promptTokens: 5, totalTokens: 5 }, + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(mockResponse) + + // Make 10,000 requests to hit the RPM limit + const promises = [] + for (let i = 0; i < 10000; i++) { + promises.push(embedder.createEmbeddings(texts)) + } + await Promise.all(promises) + + // The next request should be rate limited + const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}) + const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}) + + // This request should trigger rate limiting + const rateLimitedPromise = embedder.createEmbeddings(texts) + + // Should log rate limit warning + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("embeddings:nebius.rateLimitExceeded")) + + // Fast-forward time to allow rate limit to reset + vi.advanceTimersByTime(60000) + + await rateLimitedPromise + + consoleWarnSpy.mockRestore() + consoleLogSpy.mockRestore() + }) + + it("should handle rate limiting when exceeding tokens per minute", async () => { + // Create very large texts that will exceed 600,000 TPM + const largeText = "a".repeat(150000) // ~37,500 tokens per text + const texts = Array(20).fill(largeText) // ~750,000 tokens total + const mockResponse = { + embeddings: Array(20).fill([0.1, 0.2]), + usage: { promptTokens: 750000, totalTokens: 750000 }, + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(mockResponse) + + const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}) + const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}) + + // This should trigger token rate limiting + const promise = embedder.createEmbeddings(texts) + + // Should log rate limit warning + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("embeddings:nebius.rateLimitExceeded")) + + // Fast-forward time to allow rate limit to reset + vi.advanceTimersByTime(60000) + + await promise + + consoleWarnSpy.mockRestore() + consoleLogSpy.mockRestore() + }) + + it("should capture telemetry on error", async () => { + const texts = ["test text"] + const error = new Error("Test error") + mockOpenAICompatibleEmbedder.createEmbeddings.mockRejectedValue(error) + + await expect(embedder.createEmbeddings(texts)).rejects.toThrow(error) + + expect(TelemetryService.instance.captureEvent).toHaveBeenCalledWith(TelemetryEventName.CODE_INDEX_ERROR, { + error: error.message, + stack: error.stack, + location: "NebiusEmbedder:createEmbeddings", + }) + }) + }) + + describe("validateConfiguration", () => { + beforeEach(() => { + embedder = new NebiusEmbedder(mockApiKey) + }) + + it("should delegate validation to OpenAICompatibleEmbedder", async () => { + const mockValidationResult = { valid: true } + mockOpenAICompatibleEmbedder.validateConfiguration.mockResolvedValue(mockValidationResult) + + const result = await embedder.validateConfiguration() + + expect(mockOpenAICompatibleEmbedder.validateConfiguration).toHaveBeenCalled() + expect(result).toEqual(mockValidationResult) + }) + + it("should handle validation errors", async () => { + const error = new Error("Validation failed") + mockOpenAICompatibleEmbedder.validateConfiguration.mockRejectedValue(error) + + await expect(embedder.validateConfiguration()).rejects.toThrow(error) + + expect(TelemetryService.instance.captureEvent).toHaveBeenCalledWith(TelemetryEventName.CODE_INDEX_ERROR, { + error: error.message, + stack: error.stack, + location: "NebiusEmbedder:validateConfiguration", + }) + }) + }) + + describe("embedderInfo", () => { + it("should return correct embedder info", () => { + embedder = new NebiusEmbedder(mockApiKey) + expect(embedder.embedderInfo).toEqual({ name: "nebius" }) + }) + }) + + describe("modelDimension", () => { + it("should return the correct model dimension", () => { + expect(NebiusEmbedder.modelDimension).toBe(4096) + }) + }) + + describe("rate limiting", () => { + beforeEach(() => { + embedder = new NebiusEmbedder(mockApiKey) + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue({ + embeddings: [[0.1, 0.2]], + usage: { promptTokens: 10, totalTokens: 10 }, + }) + }) + + it("should reset rate limits after window expires", async () => { + const texts = ["test"] + + // Make requests up to the limit + for (let i = 0; i < 9999; i++) { + await embedder.createEmbeddings(texts) + } + + // Advance time to reset the window + vi.advanceTimersByTime(60001) + + // Should be able to make requests again + const result = await embedder.createEmbeddings(texts) + expect(result).toBeDefined() + expect(mockOpenAICompatibleEmbedder.createEmbeddings).toHaveBeenCalled() + }) + + it("should log debug information about rate limit usage", async () => { + const consoleDebugSpy = vi.spyOn(console, "debug").mockImplementation(() => {}) + + const texts = ["test text"] + const mockResponse = { + embeddings: [[0.1, 0.2]], + usage: { promptTokens: 100, totalTokens: 100 }, + } + mockOpenAICompatibleEmbedder.createEmbeddings.mockResolvedValue(mockResponse) + + await embedder.createEmbeddings(texts) + + expect(consoleDebugSpy).toHaveBeenCalledWith(expect.stringContaining("Nebius AI embedding usage")) + + consoleDebugSpy.mockRestore() + }) + }) +}) diff --git a/src/services/code-index/embedders/nebius.ts b/src/services/code-index/embedders/nebius.ts new file mode 100644 index 00000000000..33031e4ff06 --- /dev/null +++ b/src/services/code-index/embedders/nebius.ts @@ -0,0 +1,224 @@ +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" + +/** + * Rate limiting configuration for Nebius AI + * Based on the documented limits: + * - 600,000 TPM (tokens per minute) + * - 10,000 RPM (requests per minute) + */ +interface RateLimitState { + tokensUsed: number + requestsCount: number + windowStart: number +} + +/** + * Nebius AI embedder implementation that wraps the OpenAI Compatible embedder + * with configuration for Nebius AI's embedding API and rate limiting. + * + * Supported model: + * - Qwen/Qwen3-Embedding-8B (dimension: 4096) + * + * Rate limits: + * - 600,000 tokens per minute + * - 10,000 requests per minute + * + * Pricing: $0.01 per 1M tokens + */ +export class NebiusEmbedder implements IEmbedder { + private readonly openAICompatibleEmbedder: OpenAICompatibleEmbedder + private static readonly NEBIUS_BASE_URL = "https://api.studio.nebius.com/v1" + private static readonly DEFAULT_MODEL = "Qwen/Qwen3-Embedding-8B" + private static readonly MODEL_DIMENSION = 4096 + private static readonly MAX_TOKENS_PER_MINUTE = 600000 + private static readonly MAX_REQUESTS_PER_MINUTE = 10000 + private static readonly RATE_LIMIT_WINDOW_MS = 60000 // 1 minute in milliseconds + + private readonly modelId: string + private rateLimitState: RateLimitState = { + tokensUsed: 0, + requestsCount: 0, + windowStart: Date.now(), + } + + /** + * Creates a new Nebius AI embedder + * @param apiKey The Nebius AI API key for authentication + * @param modelId The model ID to use (defaults to Qwen/Qwen3-Embedding-8B) + */ + constructor(apiKey: string, modelId?: string) { + if (!apiKey) { + throw new Error(t("embeddings:validation.apiKeyRequired")) + } + + // Use provided model or default + this.modelId = modelId || NebiusEmbedder.DEFAULT_MODEL + + // Create an OpenAI Compatible embedder with Nebius's configuration + // Note: MAX_ITEM_TOKENS is the token limit per item, not the embedding dimension + this.openAICompatibleEmbedder = new OpenAICompatibleEmbedder( + NebiusEmbedder.NEBIUS_BASE_URL, + apiKey, + this.modelId, + MAX_ITEM_TOKENS, + ) + } + + /** + * Checks and updates rate limit state, implementing a sliding window approach + * @returns true if the request can proceed, false if rate limited + */ + private checkAndUpdateRateLimit(estimatedTokens: number): boolean { + const now = Date.now() + const windowElapsed = now - this.rateLimitState.windowStart + + // Reset the window if a minute has passed + if (windowElapsed >= NebiusEmbedder.RATE_LIMIT_WINDOW_MS) { + this.rateLimitState = { + tokensUsed: 0, + requestsCount: 0, + windowStart: now, + } + } + + // Check if we would exceed rate limits + if (this.rateLimitState.requestsCount >= NebiusEmbedder.MAX_REQUESTS_PER_MINUTE) { + console.warn( + t("embeddings:nebius.rateLimitExceeded", { + type: "requests", + limit: NebiusEmbedder.MAX_REQUESTS_PER_MINUTE, + window: "minute", + }), + ) + return false + } + + if (this.rateLimitState.tokensUsed + estimatedTokens > NebiusEmbedder.MAX_TOKENS_PER_MINUTE) { + console.warn( + t("embeddings:nebius.rateLimitExceeded", { + type: "tokens", + limit: NebiusEmbedder.MAX_TOKENS_PER_MINUTE, + window: "minute", + }), + ) + return false + } + + // Update the state + this.rateLimitState.tokensUsed += estimatedTokens + this.rateLimitState.requestsCount += 1 + + return true + } + + /** + * Calculates the wait time until rate limits reset + * @returns milliseconds to wait, or 0 if no wait needed + */ + private getWaitTimeMs(): number { + const now = Date.now() + const windowElapsed = now - this.rateLimitState.windowStart + const remainingTime = NebiusEmbedder.RATE_LIMIT_WINDOW_MS - windowElapsed + + return remainingTime > 0 ? remainingTime : 0 + } + + /** + * Creates embeddings for the given texts using Nebius AI's embedding API + * with built-in rate limiting for 600k TPM and 10k RPM + * @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 + + // Estimate tokens for rate limiting (rough estimate: 1 token ≈ 4 characters) + const estimatedTokens = texts.reduce((sum, text) => sum + Math.ceil(text.length / 4), 0) + + // Check rate limits + if (!this.checkAndUpdateRateLimit(estimatedTokens)) { + // Wait for the rate limit window to reset + const waitTime = this.getWaitTimeMs() + if (waitTime > 0) { + console.log( + t("embeddings:nebius.waitingForRateLimit", { + waitTimeMs: waitTime, + }), + ) + await new Promise((resolve) => setTimeout(resolve, waitTime)) + // After waiting, reset the window and try again + this.rateLimitState = { + tokensUsed: estimatedTokens, + requestsCount: 1, + windowStart: Date.now(), + } + } + } + + // Delegate to the OpenAI-compatible embedder + const result = await this.openAICompatibleEmbedder.createEmbeddings(texts, modelToUse) + + // Log usage for monitoring (optional) + if (result.usage) { + console.debug( + `Nebius AI embedding usage - Tokens: ${result.usage.totalTokens}, ` + + `Rate limit status: ${this.rateLimitState.tokensUsed}/${NebiusEmbedder.MAX_TOKENS_PER_MINUTE} TPM, ` + + `${this.rateLimitState.requestsCount}/${NebiusEmbedder.MAX_REQUESTS_PER_MINUTE} RPM`, + ) + } + + return result + } 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: "NebiusEmbedder:createEmbeddings", + }) + throw error + } + } + + /** + * Validates the Nebius AI 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 Nebius since we're using Nebius'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: "NebiusEmbedder:validateConfiguration", + }) + throw error + } + } + + /** + * Returns information about this embedder + */ + get embedderInfo(): EmbedderInfo { + return { + name: "nebius", + } + } + + /** + * Gets the model dimension for the Nebius AI model + * @returns The embedding dimension (4096 for Qwen/Qwen3-Embedding-8B) + */ + static get modelDimension(): number { + return NebiusEmbedder.MODEL_DIMENSION + } +} diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index f168e268691..87f305d67e3 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -15,6 +15,7 @@ export interface CodeIndexConfig { geminiOptions?: { apiKey: string } mistralOptions?: { apiKey: string } vercelAiGatewayOptions?: { apiKey: string } + nebiusOptions?: { apiKey: string } qdrantUrl?: string qdrantApiKey?: string searchMinScore?: number @@ -37,6 +38,7 @@ export type PreviousConfigSnapshot = { geminiApiKey?: string mistralApiKey?: string vercelAiGatewayApiKey?: string + nebiusApiKey?: string qdrantUrl?: string qdrantApiKey?: string } diff --git a/src/services/code-index/interfaces/embedder.ts b/src/services/code-index/interfaces/embedder.ts index 1fcda3aca32..2f2ac0e9391 100644 --- a/src/services/code-index/interfaces/embedder.ts +++ b/src/services/code-index/interfaces/embedder.ts @@ -28,7 +28,14 @@ export interface EmbeddingResponse { } } -export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" +export type AvailableEmbedders = + | "openai" + | "ollama" + | "openai-compatible" + | "gemini" + | "mistral" + | "vercel-ai-gateway" + | "nebius" export interface EmbedderInfo { name: AvailableEmbedders diff --git a/src/services/code-index/interfaces/manager.ts b/src/services/code-index/interfaces/manager.ts index 527900f6d1c..219b24aa039 100644 --- a/src/services/code-index/interfaces/manager.ts +++ b/src/services/code-index/interfaces/manager.ts @@ -70,7 +70,14 @@ export interface ICodeIndexManager { } export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" -export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" +export type EmbedderProvider = + | "openai" + | "ollama" + | "openai-compatible" + | "gemini" + | "mistral" + | "vercel-ai-gateway" + | "nebius" export interface IndexProgressUpdate { systemStatus: IndexingState diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 6d69e1f0b6c..b366777a034 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -5,6 +5,7 @@ import { OpenAICompatibleEmbedder } from "./embedders/openai-compatible" import { GeminiEmbedder } from "./embedders/gemini" import { MistralEmbedder } from "./embedders/mistral" import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway" +import { NebiusEmbedder } from "./embedders/nebius" import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels" import { QdrantVectorStore } from "./vector-store/qdrant-client" import { codeParser, DirectoryScanner, FileWatcher } from "./processors" @@ -79,6 +80,11 @@ export class CodeIndexServiceFactory { throw new Error(t("embeddings:serviceFactory.vercelAiGatewayConfigMissing")) } return new VercelAiGatewayEmbedder(config.vercelAiGatewayOptions.apiKey, config.modelId) + } else if (provider === "nebius") { + if (!config.nebiusOptions?.apiKey) { + throw new Error(t("embeddings:serviceFactory.nebiusConfigMissing")) + } + return new NebiusEmbedder(config.nebiusOptions.apiKey, config.modelId) } throw new Error( diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d43a2fce043..d66b1eda00b 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -302,6 +302,7 @@ export interface WebviewMessage { codebaseIndexGeminiApiKey?: string codebaseIndexMistralApiKey?: string codebaseIndexVercelAiGatewayApiKey?: string + codebaseIndexNebiusApiKey?: string } } diff --git a/src/shared/embeddingModels.ts b/src/shared/embeddingModels.ts index 80c51a6b455..8d2aac9092f 100644 --- a/src/shared/embeddingModels.ts +++ b/src/shared/embeddingModels.ts @@ -2,7 +2,14 @@ * Defines profiles for different embedding models, including their dimensions. */ -export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" // Add other providers as needed +export type EmbedderProvider = + | "openai" + | "ollama" + | "openai-compatible" + | "gemini" + | "mistral" + | "vercel-ai-gateway" + | "nebius" // Add other providers as needed export interface EmbeddingModelProfile { dimension: number @@ -70,6 +77,9 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = { "mistral/codestral-embed": { dimension: 1536, scoreThreshold: 0.4 }, "mistral/mistral-embed": { dimension: 1024, scoreThreshold: 0.4 }, }, + nebius: { + "Qwen/Qwen3-Embedding-8B": { dimension: 4096, scoreThreshold: 0.4 }, + }, } /** @@ -163,6 +173,9 @@ export function getDefaultModelId(provider: EmbedderProvider): string { case "vercel-ai-gateway": return "openai/text-embedding-3-large" + case "nebius": + return "Qwen/Qwen3-Embedding-8B" + 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 45bf4224a12..fb4db512e83 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -73,6 +73,7 @@ interface LocalCodeIndexSettings { codebaseIndexGeminiApiKey?: string codebaseIndexMistralApiKey?: string codebaseIndexVercelAiGatewayApiKey?: string + codebaseIndexNebiusApiKey?: string } // Validation schema for codebase index settings @@ -149,6 +150,14 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => { .min(1, t("settings:codeIndex.validation.modelSelectionRequired")), }) + case "nebius": + return baseSchema.extend({ + codebaseIndexNebiusApiKey: z.string().min(1, t("settings:codeIndex.validation.apiKeyRequired")), + codebaseIndexEmbedderModelId: z + .string() + .min(1, t("settings:codeIndex.validation.modelSelectionRequired")), + }) + default: return baseSchema } @@ -194,6 +203,7 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexGeminiApiKey: "", codebaseIndexMistralApiKey: "", codebaseIndexVercelAiGatewayApiKey: "", + codebaseIndexNebiusApiKey: "", }) // Initial settings state - stores the settings when popover opens @@ -229,6 +239,7 @@ export const CodeIndexPopover: React.FC = ({ codebaseIndexGeminiApiKey: "", codebaseIndexMistralApiKey: "", codebaseIndexVercelAiGatewayApiKey: "", + codebaseIndexNebiusApiKey: "", } setInitialSettings(settings) setCurrentSettings(settings) @@ -345,6 +356,9 @@ export const CodeIndexPopover: React.FC = ({ ? SECRET_PLACEHOLDER : "" } + if (!prev.codebaseIndexNebiusApiKey || prev.codebaseIndexNebiusApiKey === SECRET_PLACEHOLDER) { + updated.codebaseIndexNebiusApiKey = secretStatus.hasNebiusApiKey ? SECRET_PLACEHOLDER : "" + } return updated } @@ -418,7 +432,8 @@ export const CodeIndexPopover: React.FC = ({ key === "codebaseIndexOpenAiCompatibleApiKey" || key === "codebaseIndexGeminiApiKey" || key === "codebaseIndexMistralApiKey" || - key === "codebaseIndexVercelAiGatewayApiKey" + key === "codebaseIndexVercelAiGatewayApiKey" || + key === "codebaseIndexNebiusApiKey" ) { dataToValidate[key] = "placeholder-valid" } @@ -669,6 +684,9 @@ export const CodeIndexPopover: React.FC = ({ {t("settings:codeIndex.vercelAiGatewayProvider")} + + {t("settings:codeIndex.nebiusProvider")} + @@ -1131,6 +1149,71 @@ export const CodeIndexPopover: React.FC = ({ )} + {currentSettings.codebaseIndexEmbedderProvider === "nebius" && ( + <> +
+ + + updateSetting("codebaseIndexNebiusApiKey", e.target.value) + } + placeholder={t("settings:codeIndex.nebiusApiKeyPlaceholder")} + className={cn("w-full", { + "border-red-500": formErrors.codebaseIndexNebiusApiKey, + })} + /> + {formErrors.codebaseIndexNebiusApiKey && ( +

+ {formErrors.codebaseIndexNebiusApiKey} +

+ )} +
+ +
+ + + 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 */}