Răsfoiți Sursa

Add support for Roo Code Cloud as an embeddings provider (#9543)

Matt Rubens 1 lună în urmă
părinte
comite
fb4f23537a
47 a modificat fișierele cu 971 adăugiri și 40 ștergeri
  1. 2 1
      packages/types/src/codebase-index.ts
  2. 2 1
      src/i18n/locales/ca/embeddings.json
  3. 2 1
      src/i18n/locales/de/embeddings.json
  4. 2 1
      src/i18n/locales/en/embeddings.json
  5. 2 1
      src/i18n/locales/es/embeddings.json
  6. 2 1
      src/i18n/locales/fr/embeddings.json
  7. 2 1
      src/i18n/locales/hi/embeddings.json
  8. 2 1
      src/i18n/locales/id/embeddings.json
  9. 2 1
      src/i18n/locales/it/embeddings.json
  10. 2 1
      src/i18n/locales/ja/embeddings.json
  11. 2 1
      src/i18n/locales/ko/embeddings.json
  12. 2 1
      src/i18n/locales/nl/embeddings.json
  13. 2 1
      src/i18n/locales/pl/embeddings.json
  14. 2 1
      src/i18n/locales/pt-BR/embeddings.json
  15. 2 1
      src/i18n/locales/ru/embeddings.json
  16. 2 1
      src/i18n/locales/tr/embeddings.json
  17. 2 1
      src/i18n/locales/vi/embeddings.json
  18. 2 1
      src/i18n/locales/zh-CN/embeddings.json
  19. 2 1
      src/i18n/locales/zh-TW/embeddings.json
  20. 52 12
      src/services/code-index/__tests__/config-manager.spec.ts
  21. 19 5
      src/services/code-index/config-manager.ts
  22. 319 0
      src/services/code-index/embedders/__tests__/roo.spec.ts
  23. 415 0
      src/services/code-index/embedders/roo.ts
  24. 1 0
      src/services/code-index/interfaces/embedder.ts
  25. 1 0
      src/services/code-index/interfaces/manager.ts
  26. 4 0
      src/services/code-index/service-factory.ts
  27. 1 0
      src/shared/WebviewMessage.ts
  28. 22 1
      src/shared/embeddingModels.ts
  29. 63 3
      webview-ui/src/components/chat/CodeIndexPopover.tsx
  30. 2 0
      webview-ui/src/i18n/locales/ca/settings.json
  31. 2 0
      webview-ui/src/i18n/locales/de/settings.json
  32. 2 0
      webview-ui/src/i18n/locales/en/settings.json
  33. 2 0
      webview-ui/src/i18n/locales/es/settings.json
  34. 2 0
      webview-ui/src/i18n/locales/fr/settings.json
  35. 2 0
      webview-ui/src/i18n/locales/hi/settings.json
  36. 2 0
      webview-ui/src/i18n/locales/id/settings.json
  37. 2 0
      webview-ui/src/i18n/locales/it/settings.json
  38. 2 0
      webview-ui/src/i18n/locales/ja/settings.json
  39. 2 0
      webview-ui/src/i18n/locales/ko/settings.json
  40. 2 0
      webview-ui/src/i18n/locales/nl/settings.json
  41. 2 0
      webview-ui/src/i18n/locales/pl/settings.json
  42. 2 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  43. 2 0
      webview-ui/src/i18n/locales/ru/settings.json
  44. 2 0
      webview-ui/src/i18n/locales/tr/settings.json
  45. 2 0
      webview-ui/src/i18n/locales/vi/settings.json
  46. 2 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  47. 2 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 2 - 1
packages/types/src/codebase-index.ts

@@ -22,7 +22,7 @@ export const codebaseIndexConfigSchema = z.object({
 	codebaseIndexEnabled: z.boolean().optional(),
 	codebaseIndexEnabled: z.boolean().optional(),
 	codebaseIndexQdrantUrl: z.string().optional(),
 	codebaseIndexQdrantUrl: z.string().optional(),
 	codebaseIndexEmbedderProvider: z
 	codebaseIndexEmbedderProvider: z
-		.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway", "openrouter"])
+		.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway", "openrouter", "roo"])
 		.optional(),
 		.optional(),
 	codebaseIndexEmbedderBaseUrl: z.string().optional(),
 	codebaseIndexEmbedderBaseUrl: z.string().optional(),
 	codebaseIndexEmbedderModelId: z.string().optional(),
 	codebaseIndexEmbedderModelId: z.string().optional(),
@@ -52,6 +52,7 @@ export const codebaseIndexModelsSchema = z.object({
 	mistral: 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(),
 	"vercel-ai-gateway": z.record(z.string(), z.object({ dimension: z.number() })).optional(),
 	openrouter: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
 	openrouter: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
+	roo: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
 })
 })
 
 
 export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>
 export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>

+ 2 - 1
src/i18n/locales/ca/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Model no vàlid. Comproveu la vostra configuració de model.",
 		"invalidModel": "Model no vàlid. Comproveu la vostra configuració de model.",
 		"invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració.",
 		"invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració.",
 		"apiKeyRequired": "Es requereix una clau d'API per a aquest incrustador",
 		"apiKeyRequired": "Es requereix una clau d'API per a aquest incrustador",
-		"baseUrlRequired": "Es requereix una URL base per a aquest incrustador"
+		"baseUrlRequired": "Es requereix una URL base per a aquest incrustador",
+		"rooAuthenticationRequired": "Es requereix autenticació de Roo Code Cloud. Si us plau, inicieu sessió per utilitzar el proveïdor d'incrustacions Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Falta la configuració d'OpenAI per crear l'embedder",
 		"openAiConfigMissing": "Falta la configuració d'OpenAI per crear l'embedder",

+ 2 - 1
src/i18n/locales/de/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Ungültiges Modell. Bitte überprüfe deine Modellkonfiguration.",
 		"invalidModel": "Ungültiges Modell. Bitte überprüfe deine Modellkonfiguration.",
 		"invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration.",
 		"invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration.",
 		"apiKeyRequired": "API-Schlüssel ist für diesen Embedder erforderlich",
 		"apiKeyRequired": "API-Schlüssel ist für diesen Embedder erforderlich",
-		"baseUrlRequired": "Basis-URL ist für diesen Embedder erforderlich"
+		"baseUrlRequired": "Basis-URL ist für diesen Embedder erforderlich",
+		"rooAuthenticationRequired": "Roo Code Cloud-Authentifizierung erforderlich. Bitte melde dich an, um den Roo Embeddings-Anbieter zu verwenden."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "OpenAI-Konfiguration fehlt für die Erstellung des Embedders",
 		"openAiConfigMissing": "OpenAI-Konfiguration fehlt für die Erstellung des Embedders",

+ 2 - 1
src/i18n/locales/en/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Invalid model. Please check your model configuration.",
 		"invalidModel": "Invalid model. Please check your model configuration.",
 		"invalidResponse": "Invalid response from embedder service. Please check your configuration.",
 		"invalidResponse": "Invalid response from embedder service. Please check your configuration.",
 		"apiKeyRequired": "API key is required for this embedder",
 		"apiKeyRequired": "API key is required for this embedder",
-		"baseUrlRequired": "Base URL is required for this embedder"
+		"baseUrlRequired": "Base URL is required for this embedder",
+		"rooAuthenticationRequired": "Roo Code Cloud authentication required. Please sign in to use the Roo embeddings provider."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "OpenAI configuration missing for embedder creation",
 		"openAiConfigMissing": "OpenAI configuration missing for embedder creation",

+ 2 - 1
src/i18n/locales/es/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Modelo no válido. Comprueba la configuración de tu modelo.",
 		"invalidModel": "Modelo no válido. Comprueba la configuración de tu modelo.",
 		"invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración.",
 		"invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración.",
 		"apiKeyRequired": "Se requiere una clave de API para este embedder",
 		"apiKeyRequired": "Se requiere una clave de API para este embedder",
-		"baseUrlRequired": "Se requiere una URL base para este embedder"
+		"baseUrlRequired": "Se requiere una URL base para este embedder",
+		"rooAuthenticationRequired": "Se requiere autenticación de Roo Code Cloud. Inicia sesión para usar el proveedor de embeddings de Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Falta la configuración de OpenAI para crear el incrustador",
 		"openAiConfigMissing": "Falta la configuración de OpenAI para crear el incrustador",

+ 2 - 1
src/i18n/locales/fr/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Modèle invalide. Veuillez vérifier votre configuration de modèle.",
 		"invalidModel": "Modèle invalide. Veuillez vérifier votre configuration de modèle.",
 		"invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration.",
 		"invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration.",
 		"apiKeyRequired": "Une clé API est requise pour cet embedder.",
 		"apiKeyRequired": "Une clé API est requise pour cet embedder.",
-		"baseUrlRequired": "Une URL de base est requise pour cet embedder"
+		"baseUrlRequired": "Une URL de base est requise pour cet embedder",
+		"rooAuthenticationRequired": "Authentification Roo Code Cloud requise. Connecte-toi pour utiliser le fournisseur d'embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Configuration OpenAI manquante pour la création de l'embedder",
 		"openAiConfigMissing": "Configuration OpenAI manquante pour la création de l'embedder",

+ 2 - 1
src/i18n/locales/hi/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
 		"invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
 		"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।",
 		"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।",
 		"apiKeyRequired": "इस एम्बेडर के लिए API कुंजी आवश्यक है।",
 		"apiKeyRequired": "इस एम्बेडर के लिए API कुंजी आवश्यक है।",
-		"baseUrlRequired": "इस एम्बेडर के लिए बेस यूआरएल आवश्यक है"
+		"baseUrlRequired": "इस एम्बेडर के लिए बेस यूआरएल आवश्यक है",
+		"rooAuthenticationRequired": "Roo Code Cloud प्रमाणीकरण आवश्यक है। Roo एम्बेडिंग प्रदाता का उपयोग करने के लिए कृपया साइन इन करें।"
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "एम्बेडर बनाने के लिए OpenAI कॉन्फ़िगरेशन गायब है",
 		"openAiConfigMissing": "एम्बेडर बनाने के लिए OpenAI कॉन्फ़िगरेशन गायब है",

+ 2 - 1
src/i18n/locales/id/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Model tidak valid. Silakan periksa konfigurasi model Anda.",
 		"invalidModel": "Model tidak valid. Silakan periksa konfigurasi model Anda.",
 		"invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda.",
 		"invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda.",
 		"apiKeyRequired": "Kunci API diperlukan untuk embedder ini",
 		"apiKeyRequired": "Kunci API diperlukan untuk embedder ini",
-		"baseUrlRequired": "URL dasar diperlukan untuk embedder ini"
+		"baseUrlRequired": "URL dasar diperlukan untuk embedder ini",
+		"rooAuthenticationRequired": "Autentikasi Roo Code Cloud diperlukan. Silakan masuk untuk menggunakan penyedia embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Konfigurasi OpenAI tidak ada untuk membuat embedder",
 		"openAiConfigMissing": "Konfigurasi OpenAI tidak ada untuk membuat embedder",

+ 2 - 1
src/i18n/locales/it/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Modello non valido. Controlla la configurazione del tuo modello.",
 		"invalidModel": "Modello non valido. Controlla la configurazione del tuo modello.",
 		"invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione.",
 		"invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione.",
 		"apiKeyRequired": "È richiesta una chiave API per questo embedder",
 		"apiKeyRequired": "È richiesta una chiave API per questo embedder",
-		"baseUrlRequired": "È richiesto un URL di base per questo embedder"
+		"baseUrlRequired": "È richiesto un URL di base per questo embedder",
+		"rooAuthenticationRequired": "È richiesta l'autenticazione Roo Code Cloud. Accedi per utilizzare il provider di embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Configurazione OpenAI mancante per la creazione dell'embedder",
 		"openAiConfigMissing": "Configurazione OpenAI mancante per la creazione dell'embedder",

+ 2 - 1
src/i18n/locales/ja/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "無効なモデルです。モデル構成を確認してください。",
 		"invalidModel": "無効なモデルです。モデル構成を確認してください。",
 		"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。",
 		"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。",
 		"apiKeyRequired": "このエンベッダーにはAPIキーが必要です。",
 		"apiKeyRequired": "このエンベッダーにはAPIキーが必要です。",
-		"baseUrlRequired": "このエンベッダーにはベースURLが必要です"
+		"baseUrlRequired": "このエンベッダーにはベースURLが必要です",
+		"rooAuthenticationRequired": "Roo Code Cloud認証が必要です。Roo埋め込みプロバイダーを使用するにはサインインしてください。"
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "エンベッダー作成のためのOpenAI設定がありません",
 		"openAiConfigMissing": "エンベッダー作成のためのOpenAI設定がありません",

+ 2 - 1
src/i18n/locales/ko/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.",
 		"invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.",
 		"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요.",
 		"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요.",
 		"apiKeyRequired": "이 임베더에는 API 키가 필요합니다",
 		"apiKeyRequired": "이 임베더에는 API 키가 필요합니다",
-		"baseUrlRequired": "이 임베더에는 기본 URL이 필요합니다"
+		"baseUrlRequired": "이 임베더에는 기본 URL이 필요합니다",
+		"rooAuthenticationRequired": "Roo Code Cloud 인증이 필요합니다. Roo 임베딩 제공업체를 사용하려면 로그인하세요."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "임베더 생성을 위한 OpenAI 구성이 누락되었습니다",
 		"openAiConfigMissing": "임베더 생성을 위한 OpenAI 구성이 누락되었습니다",

+ 2 - 1
src/i18n/locales/nl/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Ongeldig model. Controleer je modelconfiguratie.",
 		"invalidModel": "Ongeldig model. Controleer je modelconfiguratie.",
 		"invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie.",
 		"invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie.",
 		"apiKeyRequired": "API-sleutel is vereist voor deze embedder",
 		"apiKeyRequired": "API-sleutel is vereist voor deze embedder",
-		"baseUrlRequired": "Basis-URL is vereist voor deze embedder"
+		"baseUrlRequired": "Basis-URL is vereist voor deze embedder",
+		"rooAuthenticationRequired": "Roo Code Cloud-authenticatie is vereist. Meld je aan om de Roo embeddings-provider te gebruiken."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "OpenAI-configuratie ontbreekt voor het maken van embedder",
 		"openAiConfigMissing": "OpenAI-configuratie ontbreekt voor het maken van embedder",

+ 2 - 1
src/i18n/locales/pl/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Nieprawidłowy model. Sprawdź konfigurację modelu.",
 		"invalidModel": "Nieprawidłowy model. Sprawdź konfigurację modelu.",
 		"invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację.",
 		"invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację.",
 		"apiKeyRequired": "Klucz API jest wymagany dla tego embeddera",
 		"apiKeyRequired": "Klucz API jest wymagany dla tego embeddera",
-		"baseUrlRequired": "Podstawowy adres URL jest wymagany dla tego embeddera"
+		"baseUrlRequired": "Podstawowy adres URL jest wymagany dla tego embeddera",
+		"rooAuthenticationRequired": "Wymagana jest autentykacja Roo Code Cloud. Zaloguj się, aby używać dostawcy embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Brak konfiguracji OpenAI do utworzenia embeddera",
 		"openAiConfigMissing": "Brak konfiguracji OpenAI do utworzenia embeddera",

+ 2 - 1
src/i18n/locales/pt-BR/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Modelo inválido. Verifique a configuração do seu modelo.",
 		"invalidModel": "Modelo inválido. Verifique a configuração do seu modelo.",
 		"invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração.",
 		"invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração.",
 		"apiKeyRequired": "A chave de API é necessária para este embedder",
 		"apiKeyRequired": "A chave de API é necessária para este embedder",
-		"baseUrlRequired": "A URL base é necessária para este embedder"
+		"baseUrlRequired": "A URL base é necessária para este embedder",
+		"rooAuthenticationRequired": "Autenticação Roo Code Cloud necessária. Faça login para usar o provedor de embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Configuração do OpenAI ausente para criação do embedder",
 		"openAiConfigMissing": "Configuração do OpenAI ausente para criação do embedder",

+ 2 - 1
src/i18n/locales/ru/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Неверная модель. Проверьте конфигурацию модели.",
 		"invalidModel": "Неверная модель. Проверьте конфигурацию модели.",
 		"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию.",
 		"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию.",
 		"apiKeyRequired": "Для этого встраивателя требуется ключ API",
 		"apiKeyRequired": "Для этого встраивателя требуется ключ API",
-		"baseUrlRequired": "Для этого встраивателя требуется базовый URL"
+		"baseUrlRequired": "Для этого встраивателя требуется базовый URL",
+		"rooAuthenticationRequired": "Требуется аутентификация Roo Code Cloud. Войдите в систему, чтобы использовать провайдер embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Отсутствует конфигурация OpenAI для создания эмбеддера",
 		"openAiConfigMissing": "Отсутствует конфигурация OpenAI для создания эмбеддера",

+ 2 - 1
src/i18n/locales/tr/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Geçersiz model. Lütfen model yapılandırmanızı kontrol edin.",
 		"invalidModel": "Geçersiz model. Lütfen model yapılandırmanızı kontrol edin.",
 		"invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin.",
 		"invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin.",
 		"apiKeyRequired": "Bu gömücü için API anahtarı gereklidir",
 		"apiKeyRequired": "Bu gömücü için API anahtarı gereklidir",
-		"baseUrlRequired": "Bu gömücü için temel URL gereklidir"
+		"baseUrlRequired": "Bu gömücü için temel URL gereklidir",
+		"rooAuthenticationRequired": "Roo Code Cloud kimlik doğrulaması gerekli. Roo embeddings sağlayıcısını kullanmak için lütfen giriş yap."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Gömücü oluşturmak için OpenAI yapılandırması eksik",
 		"openAiConfigMissing": "Gömücü oluşturmak için OpenAI yapılandırması eksik",

+ 2 - 1
src/i18n/locales/vi/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "Mô hình không hợp lệ. Vui lòng kiểm tra cấu hình mô hình của bạn.",
 		"invalidModel": "Mô hình không hợp lệ. Vui lòng kiểm tra cấu hình mô hình của bạn.",
 		"invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn.",
 		"invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn.",
 		"apiKeyRequired": "Cần có khóa API cho trình nhúng này",
 		"apiKeyRequired": "Cần có khóa API cho trình nhúng này",
-		"baseUrlRequired": "Cần có URL cơ sở cho trình nhúng này"
+		"baseUrlRequired": "Cần có URL cơ sở cho trình nhúng này",
+		"rooAuthenticationRequired": "Yêu cầu xác thực Roo Code Cloud. Vui lòng đăng nhập để sử dụng nhà cung cấp embeddings Roo."
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "Thiếu cấu hình OpenAI để tạo embedder",
 		"openAiConfigMissing": "Thiếu cấu hình OpenAI để tạo embedder",

+ 2 - 1
src/i18n/locales/zh-CN/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "模型无效。请检查您的模型配置。",
 		"invalidModel": "模型无效。请检查您的模型配置。",
 		"invalidResponse": "嵌入服务响应无效。请检查您的配置。",
 		"invalidResponse": "嵌入服务响应无效。请检查您的配置。",
 		"apiKeyRequired": "此嵌入器需要 API 密钥",
 		"apiKeyRequired": "此嵌入器需要 API 密钥",
-		"baseUrlRequired": "此嵌入器需要基础 URL"
+		"baseUrlRequired": "此嵌入器需要基础 URL",
+		"rooAuthenticationRequired": "需要 Roo Code Cloud 身份验证。请登录以使用 Roo 嵌入提供商。"
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "创建嵌入器缺少 OpenAI 配置",
 		"openAiConfigMissing": "创建嵌入器缺少 OpenAI 配置",

+ 2 - 1
src/i18n/locales/zh-TW/embeddings.json

@@ -39,7 +39,8 @@
 		"invalidModel": "無效的模型。請檢查您的模型組態。",
 		"invalidModel": "無效的模型。請檢查您的模型組態。",
 		"invalidResponse": "內嵌服務回應無效。請檢查您的組態。",
 		"invalidResponse": "內嵌服務回應無效。請檢查您的組態。",
 		"apiKeyRequired": "此嵌入器需要 API 金鑰",
 		"apiKeyRequired": "此嵌入器需要 API 金鑰",
-		"baseUrlRequired": "此嵌入器需要基礎 URL"
+		"baseUrlRequired": "此嵌入器需要基礎 URL",
+		"rooAuthenticationRequired": "需要 Roo Code Cloud 身份驗證。請登入以使用 Roo 嵌入提供商。"
 	},
 	},
 	"serviceFactory": {
 	"serviceFactory": {
 		"openAiConfigMissing": "建立嵌入器缺少 OpenAI 設定",
 		"openAiConfigMissing": "建立嵌入器缺少 OpenAI 設定",

+ 52 - 12
src/services/code-index/__tests__/config-manager.spec.ts

@@ -9,6 +9,21 @@ vi.mock("../../../core/config/ContextProxy")
 // Mock embeddingModels module
 // Mock embeddingModels module
 vi.mock("../../../shared/embeddingModels")
 vi.mock("../../../shared/embeddingModels")
 
 
+// Mock CloudService
+vi.mock("@roo-code/cloud", () => ({
+	CloudService: {
+		hasInstance: vi.fn(() => false),
+		instance: {
+			authService: {
+				getSessionToken: vi.fn(() => undefined),
+			},
+		},
+	},
+}))
+
+import { CloudService } from "@roo-code/cloud"
+const mockedCloudService = vi.mocked(CloudService)
+
 // Import mocked functions
 // Import mocked functions
 import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../../shared/embeddingModels"
 import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../../shared/embeddingModels"
 
 
@@ -54,7 +69,7 @@ describe("CodeIndexConfigManager", () => {
 		it("should initialize with ContextProxy", () => {
 		it("should initialize with ContextProxy", () => {
 			expect(configManager).toBeDefined()
 			expect(configManager).toBeDefined()
 			expect(configManager.isFeatureEnabled).toBe(true)
 			expect(configManager.isFeatureEnabled).toBe(true)
-			expect(configManager.currentEmbedderProvider).toBe("openai")
+			expect(configManager.currentEmbedderProvider).toBe("roo")
 		})
 		})
 	})
 	})
 
 
@@ -98,19 +113,43 @@ describe("CodeIndexConfigManager", () => {
 
 
 			const result = await configManager.loadConfiguration()
 			const result = await configManager.loadConfiguration()
 
 
-			expect(result.currentConfig).toEqual({
-				isConfigured: false,
-				embedderProvider: "openai",
-				modelId: undefined,
-				openAiOptions: { openAiNativeApiKey: "" },
-				ollamaOptions: { ollamaBaseUrl: "" },
-				qdrantUrl: "http://localhost:6333",
-				qdrantApiKey: "",
-				searchMinScore: 0.4,
-			})
+			// Roo is the default provider but requires authentication to be configured
+			// Since there's no session token in the test environment, isConfigured is false
+			expect(result.currentConfig.isConfigured).toBe(false)
+			expect(result.currentConfig.embedderProvider).toBe("roo")
+			expect(result.currentConfig.modelId).toBeUndefined()
+			expect(result.currentConfig.openAiOptions).toEqual({ openAiNativeApiKey: "" })
+			expect(result.currentConfig.ollamaOptions).toEqual({ ollamaBaseUrl: "" })
+			expect(result.currentConfig.qdrantUrl).toBe("http://localhost:6333")
+			expect(result.currentConfig.qdrantApiKey).toBe("")
+			expect(result.currentConfig.searchMinScore).toBe(0.4)
 			expect(result.requiresRestart).toBe(false)
 			expect(result.requiresRestart).toBe(false)
 		})
 		})
 
 
+		it("should return isConfigured=true for Roo provider when authenticated", async () => {
+			// Mock CloudService to return an authenticated session
+			mockedCloudService.hasInstance.mockReturnValue(true)
+			;(mockedCloudService.instance.authService?.getSessionToken as ReturnType<typeof vi.fn>).mockReturnValue(
+				"valid-session-token",
+			)
+
+			mockContextProxy.getGlobalState.mockReturnValue({
+				codebaseIndexEnabled: true,
+				codebaseIndexQdrantUrl: "http://localhost:6333",
+				codebaseIndexEmbedderProvider: "roo",
+			})
+			mockContextProxy.getSecret.mockReturnValue(undefined)
+
+			configManager = new CodeIndexConfigManager(mockContextProxy)
+			const result = await configManager.loadConfiguration()
+
+			expect(result.currentConfig.isConfigured).toBe(true)
+			expect(result.currentConfig.embedderProvider).toBe("roo")
+
+			// Reset the mock
+			mockedCloudService.hasInstance.mockReturnValue(false)
+		})
+
 		it("should load configuration from globalState and secrets", async () => {
 		it("should load configuration from globalState and secrets", async () => {
 			const mockGlobalState = {
 			const mockGlobalState = {
 				codebaseIndexEnabled: true,
 				codebaseIndexEnabled: true,
@@ -1624,6 +1663,7 @@ describe("CodeIndexConfigManager", () => {
 
 
 			expect(config).toHaveProperty("isConfigured")
 			expect(config).toHaveProperty("isConfigured")
 			expect(config).toHaveProperty("embedderProvider")
 			expect(config).toHaveProperty("embedderProvider")
+			// Provider is "openai" as set in the mock, not "roo"
 			expect(config.embedderProvider).toBe("openai")
 			expect(config.embedderProvider).toBe("openai")
 		})
 		})
 	})
 	})
@@ -1779,7 +1819,7 @@ describe("CodeIndexConfigManager", () => {
 				configManager = new CodeIndexConfigManager(mockContextProxy)
 				configManager = new CodeIndexConfigManager(mockContextProxy)
 				await configManager.loadConfiguration()
 				await configManager.loadConfiguration()
 
 
-				// Should use default model ID
+				// Should use default model ID for the configured provider (openai)
 				expect(configManager.currentModelDimension).toBe(1536)
 				expect(configManager.currentModelDimension).toBe(1536)
 				expect(mockedGetDefaultModelId).toHaveBeenCalledWith("openai")
 				expect(mockedGetDefaultModelId).toHaveBeenCalledWith("openai")
 				expect(mockedGetModelDimension).toHaveBeenCalledWith("openai", "text-embedding-3-small")
 				expect(mockedGetModelDimension).toHaveBeenCalledWith("openai", "text-embedding-3-small")

+ 19 - 5
src/services/code-index/config-manager.ts

@@ -4,6 +4,7 @@ import { EmbedderProvider } from "./interfaces/manager"
 import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
 import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
 import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
 import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
 import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
 import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
+import { CloudService } from "@roo-code/cloud"
 
 
 /**
 /**
  * Manages configuration state and validation for the code indexing feature.
  * Manages configuration state and validation for the code indexing feature.
@@ -11,7 +12,7 @@ import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "..
  */
  */
 export class CodeIndexConfigManager {
 export class CodeIndexConfigManager {
 	private codebaseIndexEnabled: boolean = true
 	private codebaseIndexEnabled: boolean = true
-	private embedderProvider: EmbedderProvider = "openai"
+	private embedderProvider: EmbedderProvider = "roo"
 	private modelId?: string
 	private modelId?: string
 	private modelDimension?: number
 	private modelDimension?: number
 	private openAiOptions?: ApiHandlerOptions
 	private openAiOptions?: ApiHandlerOptions
@@ -47,7 +48,7 @@ export class CodeIndexConfigManager {
 		const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
 		const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
 			codebaseIndexEnabled: true,
 			codebaseIndexEnabled: true,
 			codebaseIndexQdrantUrl: "http://localhost:6333",
 			codebaseIndexQdrantUrl: "http://localhost:6333",
-			codebaseIndexEmbedderProvider: "openai",
+			codebaseIndexEmbedderProvider: "roo",
 			codebaseIndexEmbedderBaseUrl: "",
 			codebaseIndexEmbedderBaseUrl: "",
 			codebaseIndexEmbedderModelId: "",
 			codebaseIndexEmbedderModelId: "",
 			codebaseIndexSearchMinScore: undefined,
 			codebaseIndexSearchMinScore: undefined,
@@ -100,7 +101,9 @@ export class CodeIndexConfigManager {
 		this.openAiOptions = { openAiNativeApiKey: openAiKey }
 		this.openAiOptions = { openAiNativeApiKey: openAiKey }
 
 
 		// Set embedder provider with support for openai-compatible
 		// Set embedder provider with support for openai-compatible
-		if (codebaseIndexEmbedderProvider === "ollama") {
+		if (codebaseIndexEmbedderProvider === "openai") {
+			this.embedderProvider = "openai"
+		} else if (codebaseIndexEmbedderProvider === "ollama") {
 			this.embedderProvider = "ollama"
 			this.embedderProvider = "ollama"
 		} else if (codebaseIndexEmbedderProvider === "openai-compatible") {
 		} else if (codebaseIndexEmbedderProvider === "openai-compatible") {
 			this.embedderProvider = "openai-compatible"
 			this.embedderProvider = "openai-compatible"
@@ -112,8 +115,10 @@ export class CodeIndexConfigManager {
 			this.embedderProvider = "vercel-ai-gateway"
 			this.embedderProvider = "vercel-ai-gateway"
 		} else if (codebaseIndexEmbedderProvider === "openrouter") {
 		} else if (codebaseIndexEmbedderProvider === "openrouter") {
 			this.embedderProvider = "openrouter"
 			this.embedderProvider = "openrouter"
+		} else if (codebaseIndexEmbedderProvider === "roo") {
+			this.embedderProvider = "roo"
 		} else {
 		} else {
-			this.embedderProvider = "openai"
+			this.embedderProvider = "roo"
 		}
 		}
 
 
 		this.modelId = codebaseIndexEmbedderModelId || undefined
 		this.modelId = codebaseIndexEmbedderModelId || undefined
@@ -247,6 +252,15 @@ export class CodeIndexConfigManager {
 			const qdrantUrl = this.qdrantUrl
 			const qdrantUrl = this.qdrantUrl
 			const isConfigured = !!(apiKey && qdrantUrl)
 			const isConfigured = !!(apiKey && qdrantUrl)
 			return isConfigured
 			return isConfigured
+		} else if (this.embedderProvider === "roo") {
+			// Roo Code Cloud uses CloudService session token, so we need to check authentication
+			const qdrantUrl = this.qdrantUrl
+			const sessionToken = CloudService.hasInstance()
+				? CloudService.instance.authService?.getSessionToken()
+				: undefined
+			const isAuthenticated = sessionToken && sessionToken !== "unauthenticated"
+			const isConfigured = !!(qdrantUrl && isAuthenticated)
+			return isConfigured
 		}
 		}
 		return false // Should not happen if embedderProvider is always set correctly
 		return false // Should not happen if embedderProvider is always set correctly
 	}
 	}
@@ -273,7 +287,7 @@ export class CodeIndexConfigManager {
 		// Handle null/undefined values safely
 		// Handle null/undefined values safely
 		const prevEnabled = prev?.enabled ?? false
 		const prevEnabled = prev?.enabled ?? false
 		const prevConfigured = prev?.configured ?? false
 		const prevConfigured = prev?.configured ?? false
-		const prevProvider = prev?.embedderProvider ?? "openai"
+		const prevProvider = prev?.embedderProvider ?? "roo"
 		const prevOpenAiKey = prev?.openAiKey ?? ""
 		const prevOpenAiKey = prev?.openAiKey ?? ""
 		const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? ""
 		const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? ""
 		const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
 		const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""

+ 319 - 0
src/services/code-index/embedders/__tests__/roo.spec.ts

@@ -0,0 +1,319 @@
+// npx vitest run src/services/code-index/embedders/__tests__/roo.spec.ts
+
+import { RooEmbedder } from "../roo"
+import { OpenAI } from "openai"
+import { CloudService } from "@roo-code/cloud"
+
+// Mock OpenAI
+vi.mock("openai", () => ({
+	OpenAI: vi.fn(),
+}))
+
+// Mock CloudService
+vi.mock("@roo-code/cloud", () => ({
+	CloudService: {
+		hasInstance: vi.fn(),
+		instance: {
+			authService: {
+				getSessionToken: vi.fn(),
+			},
+		},
+	},
+}))
+
+// Mock the TelemetryService
+vi.mock("@roo-code/telemetry", () => ({
+	TelemetryService: {
+		instance: {
+			captureEvent: vi.fn(),
+		},
+	},
+}))
+
+// Mock handleOpenAIError
+vi.mock("../../../../api/providers/utils/openai-error-handler", () => ({
+	handleOpenAIError: vi.fn((error) => error),
+}))
+
+const MockedOpenAI = vi.mocked(OpenAI)
+const MockedCloudService = vi.mocked(CloudService)
+
+describe("RooEmbedder", () => {
+	let embedder: RooEmbedder
+	let mockEmbeddingsCreate: ReturnType<typeof vi.fn>
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		// Set up CloudService mock to return a valid session token
+		MockedCloudService.hasInstance.mockReturnValue(true)
+		;(MockedCloudService.instance.authService!.getSessionToken as ReturnType<typeof vi.fn>).mockReturnValue(
+			"test-session-token",
+		)
+
+		// Set up OpenAI mock
+		mockEmbeddingsCreate = vi.fn()
+		MockedOpenAI.mockImplementation(
+			() =>
+				({
+					embeddings: {
+						create: mockEmbeddingsCreate,
+					},
+					apiKey: "test-session-token",
+				}) as any,
+		)
+	})
+
+	describe("constructor", () => {
+		it("should create RooEmbedder with default model", () => {
+			// Act
+			embedder = new RooEmbedder()
+
+			// Assert
+			expect(MockedOpenAI).toHaveBeenCalledWith(
+				expect.objectContaining({
+					baseURL: "https://api.roocode.com/proxy/v1",
+					apiKey: "test-session-token",
+					defaultHeaders: {
+						"HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code",
+						"X-Title": "Roo Code",
+					},
+				}),
+			)
+		})
+
+		it("should create RooEmbedder with custom model", () => {
+			// Arrange
+			const customModel = "openai/text-embedding-3-small"
+
+			// Act
+			embedder = new RooEmbedder(customModel)
+
+			// Assert
+			expect(MockedOpenAI).toHaveBeenCalled()
+			// The embedder should store the custom model
+			expect(embedder.embedderInfo.name).toBe("roo")
+		})
+
+		it("should handle unauthenticated state", () => {
+			// Arrange
+			MockedCloudService.hasInstance.mockReturnValue(false)
+
+			// Act
+			embedder = new RooEmbedder()
+
+			// Assert - Should use "unauthenticated" as apiKey
+			expect(MockedOpenAI).toHaveBeenCalledWith(
+				expect.objectContaining({
+					apiKey: "unauthenticated",
+				}),
+			)
+		})
+	})
+
+	describe("createEmbeddings", () => {
+		beforeEach(() => {
+			embedder = new RooEmbedder()
+		})
+
+		it("should create embeddings for text input", async () => {
+			// Arrange
+			const texts = ["test text 1", "test text 2"]
+			const base64Embedding1 = Buffer.from(new Float32Array([0.1, 0.2, 0.3]).buffer).toString("base64")
+			const base64Embedding2 = Buffer.from(new Float32Array([0.4, 0.5, 0.6]).buffer).toString("base64")
+
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: [{ embedding: base64Embedding1 }, { embedding: base64Embedding2 }],
+				usage: { prompt_tokens: 10, total_tokens: 10 },
+			})
+
+			// Act
+			const result = await embedder.createEmbeddings(texts)
+
+			// Assert
+			expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
+				input: texts,
+				model: "openai/text-embedding-3-large",
+				encoding_format: "base64",
+			})
+			expect(result.embeddings).toHaveLength(2)
+			expect(result.usage?.promptTokens).toBe(10)
+			expect(result.usage?.totalTokens).toBe(10)
+		})
+
+		it("should use custom model when provided", async () => {
+			// Arrange
+			const texts = ["test text"]
+			const customModel = "google/gemini-embedding-001"
+			const base64Embedding = Buffer.from(new Float32Array([0.1, 0.2]).buffer).toString("base64")
+
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: [{ embedding: base64Embedding }],
+				usage: { prompt_tokens: 5, total_tokens: 5 },
+			})
+
+			// Act
+			const result = await embedder.createEmbeddings(texts, customModel)
+
+			// Assert
+			expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
+				input: texts,
+				model: customModel,
+				encoding_format: "base64",
+			})
+			expect(result.embeddings).toHaveLength(1)
+		})
+
+		it("should handle batch processing for large inputs", async () => {
+			// Arrange
+			// Create texts that would exceed batch limits
+			const texts = Array(100).fill("test text")
+			const base64Embedding = Buffer.from(new Float32Array([0.1, 0.2]).buffer).toString("base64")
+
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: texts.map(() => ({ embedding: base64Embedding })),
+				usage: { prompt_tokens: 500, total_tokens: 500 },
+			})
+
+			// Act
+			const result = await embedder.createEmbeddings(texts)
+
+			// Assert
+			expect(result.embeddings).toHaveLength(100)
+		})
+
+		it("should skip texts exceeding token limit", async () => {
+			// Arrange
+			// Create a very long text that exceeds MAX_ITEM_TOKENS
+			const longText = "a".repeat(100000) // Way more than 8191 tokens
+			const normalText = "normal text"
+			const texts = [longText, normalText]
+			const base64Embedding = Buffer.from(new Float32Array([0.1, 0.2]).buffer).toString("base64")
+
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: [{ embedding: base64Embedding }],
+				usage: { prompt_tokens: 5, total_tokens: 5 },
+			})
+
+			// Act
+			const result = await embedder.createEmbeddings(texts)
+
+			// Assert - Only the normal text should be processed
+			expect(mockEmbeddingsCreate).toHaveBeenCalled()
+			expect(result.embeddings).toHaveLength(1)
+		})
+
+		it("should handle API errors", async () => {
+			// Arrange
+			const texts = ["test text"]
+			mockEmbeddingsCreate.mockRejectedValue(new Error("API error"))
+
+			// Act & Assert
+			await expect(embedder.createEmbeddings(texts)).rejects.toThrow()
+		})
+	})
+
+	describe("validateConfiguration", () => {
+		beforeEach(() => {
+			embedder = new RooEmbedder()
+		})
+
+		it("should return valid when authenticated and API works", async () => {
+			// Arrange
+			const base64Embedding = Buffer.from(new Float32Array([0.1]).buffer).toString("base64")
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: [{ embedding: base64Embedding }],
+				usage: { prompt_tokens: 1, total_tokens: 1 },
+			})
+
+			// Act
+			const result = await embedder.validateConfiguration()
+
+			// Assert
+			expect(result.valid).toBe(true)
+			expect(result.error).toBeUndefined()
+		})
+
+		it("should return invalid when not authenticated", async () => {
+			// Arrange - Reset and set up unauthenticated state
+			MockedCloudService.hasInstance.mockReturnValue(false)
+			embedder = new RooEmbedder()
+
+			// Act
+			const result = await embedder.validateConfiguration()
+
+			// Assert
+			expect(result.valid).toBe(false)
+			expect(result.error).toBe("embeddings:validation.rooAuthenticationRequired")
+		})
+
+		it("should return invalid when API call fails", async () => {
+			// Arrange
+			mockEmbeddingsCreate.mockRejectedValue(new Error("API error"))
+
+			// Act
+			const result = await embedder.validateConfiguration()
+
+			// Assert
+			expect(result.valid).toBe(false)
+		})
+
+		it("should return invalid when response is empty", async () => {
+			// Arrange
+			mockEmbeddingsCreate.mockResolvedValue({
+				data: [],
+				usage: { prompt_tokens: 0, total_tokens: 0 },
+			})
+
+			// Act
+			const result = await embedder.validateConfiguration()
+
+			// Assert
+			expect(result.valid).toBe(false)
+			expect(result.error).toBe("embeddings:validation.invalidResponse")
+		})
+	})
+
+	describe("embedderInfo", () => {
+		it("should return correct embedder info", () => {
+			// Arrange
+			embedder = new RooEmbedder()
+
+			// Act
+			const info = embedder.embedderInfo
+
+			// Assert
+			expect(info).toEqual({
+				name: "roo",
+			})
+		})
+	})
+
+	describe("rate limiting", () => {
+		beforeEach(() => {
+			embedder = new RooEmbedder()
+		})
+
+		it("should handle 429 rate limit errors with retry", async () => {
+			// Arrange
+			const texts = ["test text"]
+			const rateLimitError = new Error("Rate limited") as any
+			rateLimitError.status = 429
+
+			const base64Embedding = Buffer.from(new Float32Array([0.1]).buffer).toString("base64")
+
+			// First call fails with 429, second succeeds
+			mockEmbeddingsCreate.mockRejectedValueOnce(rateLimitError).mockResolvedValueOnce({
+				data: [{ embedding: base64Embedding }],
+				usage: { prompt_tokens: 1, total_tokens: 1 },
+			})
+
+			// Act
+			const result = await embedder.createEmbeddings(texts)
+
+			// Assert
+			expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(2)
+			expect(result.embeddings).toHaveLength(1)
+		})
+	})
+})

+ 415 - 0
src/services/code-index/embedders/roo.ts

@@ -0,0 +1,415 @@
+import { OpenAI } from "openai"
+import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder"
+import {
+	MAX_BATCH_TOKENS,
+	MAX_ITEM_TOKENS,
+	MAX_BATCH_RETRIES as MAX_RETRIES,
+	INITIAL_RETRY_DELAY_MS as INITIAL_DELAY_MS,
+} from "../constants"
+import { getDefaultModelId, getModelQueryPrefix } from "../../../shared/embeddingModels"
+import { t } from "../../../i18n"
+import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "../shared/validation-helpers"
+import { TelemetryEventName } from "@roo-code/types"
+import { TelemetryService } from "@roo-code/telemetry"
+import { Mutex } from "async-mutex"
+import { handleOpenAIError } from "../../../api/providers/utils/openai-error-handler"
+import { CloudService } from "@roo-code/cloud"
+
+interface EmbeddingItem {
+	embedding: string | number[]
+	[key: string]: any
+}
+
+interface RooEmbeddingResponse {
+	data: EmbeddingItem[]
+	usage?: {
+		prompt_tokens?: number
+		total_tokens?: number
+	}
+}
+
+function getSessionToken(): string {
+	const token = CloudService.hasInstance() ? CloudService.instance.authService?.getSessionToken() : undefined
+	return token ?? "unauthenticated"
+}
+
+/**
+ * Roo Code Cloud implementation of the embedder interface with batching and rate limiting.
+ * Roo Code Cloud provides access to embedding models through a unified proxy endpoint.
+ */
+export class RooEmbedder implements IEmbedder {
+	private embeddingsClient: OpenAI
+	private readonly defaultModelId: string
+	private readonly maxItemTokens: number
+	private readonly baseUrl: string
+
+	// Global rate limiting state shared across all instances
+	private static globalRateLimitState = {
+		isRateLimited: false,
+		rateLimitResetTime: 0,
+		consecutiveRateLimitErrors: 0,
+		lastRateLimitError: 0,
+		// Mutex to ensure thread-safe access to rate limit state
+		mutex: new Mutex(),
+	}
+
+	/**
+	 * Creates a new Roo Code Cloud embedder
+	 * @param modelId Optional model identifier (defaults to "openai/text-embedding-3-large")
+	 * @param maxItemTokens Optional maximum tokens per item (defaults to MAX_ITEM_TOKENS)
+	 */
+	constructor(modelId?: string, maxItemTokens?: number) {
+		const sessionToken = getSessionToken()
+
+		this.baseUrl = process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy"
+
+		// Ensure baseURL ends with /v1 for OpenAI client, but don't duplicate it
+		const baseURL = !this.baseUrl.endsWith("/v1") ? `${this.baseUrl}/v1` : this.baseUrl
+
+		// Wrap OpenAI client creation to handle invalid API key characters
+		try {
+			this.embeddingsClient = new OpenAI({
+				baseURL,
+				apiKey: sessionToken,
+				defaultHeaders: {
+					"HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code",
+					"X-Title": "Roo Code",
+				},
+			})
+		} catch (error) {
+			// Use the error handler to transform ByteString conversion errors
+			throw handleOpenAIError(error, "Roo Code Cloud")
+		}
+
+		this.defaultModelId = modelId || getDefaultModelId("roo")
+		this.maxItemTokens = maxItemTokens || MAX_ITEM_TOKENS
+	}
+
+	/**
+	 * Creates embeddings for the given texts with batching and rate limiting
+	 * @param texts Array of text strings to embed
+	 * @param model Optional model identifier
+	 * @returns Promise resolving to embedding response
+	 */
+	async createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse> {
+		const modelToUse = model || this.defaultModelId
+
+		// Apply model-specific query prefix if required
+		const queryPrefix = getModelQueryPrefix("roo", modelToUse)
+		const processedTexts = queryPrefix
+			? texts.map((text, index) => {
+					// Prevent double-prefixing
+					if (text.startsWith(queryPrefix)) {
+						return text
+					}
+					const prefixedText = `${queryPrefix}${text}`
+					const estimatedTokens = Math.ceil(prefixedText.length / 4)
+					if (estimatedTokens > MAX_ITEM_TOKENS) {
+						console.warn(
+							t("embeddings:textWithPrefixExceedsTokenLimit", {
+								index,
+								estimatedTokens,
+								maxTokens: MAX_ITEM_TOKENS,
+							}),
+						)
+						// Return original text if adding prefix would exceed limit
+						return text
+					}
+					return prefixedText
+				})
+			: texts
+
+		const allEmbeddings: number[][] = []
+		const usage = { promptTokens: 0, totalTokens: 0 }
+		const remainingTexts = [...processedTexts]
+
+		while (remainingTexts.length > 0) {
+			const currentBatch: string[] = []
+			let currentBatchTokens = 0
+			const processedIndices: number[] = []
+
+			for (let i = 0; i < remainingTexts.length; i++) {
+				const text = remainingTexts[i]
+				const itemTokens = Math.ceil(text.length / 4)
+
+				if (itemTokens > this.maxItemTokens) {
+					console.warn(
+						t("embeddings:textExceedsTokenLimit", {
+							index: i,
+							itemTokens,
+							maxTokens: this.maxItemTokens,
+						}),
+					)
+					processedIndices.push(i)
+					continue
+				}
+
+				if (currentBatchTokens + itemTokens <= MAX_BATCH_TOKENS) {
+					currentBatch.push(text)
+					currentBatchTokens += itemTokens
+					processedIndices.push(i)
+				} else {
+					break
+				}
+			}
+
+			// Remove processed items from remainingTexts (in reverse order to maintain correct indices)
+			for (let i = processedIndices.length - 1; i >= 0; i--) {
+				remainingTexts.splice(processedIndices[i], 1)
+			}
+
+			if (currentBatch.length > 0) {
+				const batchResult = await this._embedBatchWithRetries(currentBatch, modelToUse)
+				allEmbeddings.push(...batchResult.embeddings)
+				usage.promptTokens += batchResult.usage.promptTokens
+				usage.totalTokens += batchResult.usage.totalTokens
+			}
+		}
+
+		return { embeddings: allEmbeddings, usage }
+	}
+
+	/**
+	 * Helper method to handle batch embedding with retries and exponential backoff
+	 * @param batchTexts Array of texts to embed in this batch
+	 * @param model Model identifier to use
+	 * @returns Promise resolving to embeddings and usage statistics
+	 */
+	private async _embedBatchWithRetries(
+		batchTexts: string[],
+		model: string,
+	): Promise<{ embeddings: number[][]; usage: { promptTokens: number; totalTokens: number } }> {
+		for (let attempts = 0; attempts < MAX_RETRIES; attempts++) {
+			// Check global rate limit before attempting request
+			await this.waitForGlobalRateLimit()
+
+			// Update API key before each request to ensure we use the latest session token
+			this.embeddingsClient.apiKey = getSessionToken()
+
+			try {
+				const response = (await this.embeddingsClient.embeddings.create({
+					input: batchTexts,
+					model: model,
+					// OpenAI package (as of v4.78.1) has a parsing issue that truncates embedding dimensions to 256
+					// when processing numeric arrays, which breaks compatibility with models using larger dimensions.
+					// By requesting base64 encoding, we bypass the package's parser and handle decoding ourselves.
+					encoding_format: "base64",
+				})) as RooEmbeddingResponse
+
+				// Convert base64 embeddings to float32 arrays
+				const processedEmbeddings = response.data.map((item: EmbeddingItem) => {
+					if (typeof item.embedding === "string") {
+						const buffer = Buffer.from(item.embedding, "base64")
+
+						// Create Float32Array view over the buffer
+						const float32Array = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4)
+
+						return {
+							...item,
+							embedding: Array.from(float32Array),
+						}
+					}
+					return item
+				})
+
+				// Replace the original data with processed embeddings
+				response.data = processedEmbeddings
+
+				const embeddings = response.data.map((item) => item.embedding as number[])
+
+				return {
+					embeddings: embeddings,
+					usage: {
+						promptTokens: response.usage?.prompt_tokens || 0,
+						totalTokens: response.usage?.total_tokens || 0,
+					},
+				}
+			} catch (error) {
+				// Capture telemetry before error is reformatted
+				TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
+					error: error instanceof Error ? error.message : String(error),
+					stack: error instanceof Error ? error.stack : undefined,
+					location: "RooEmbedder:_embedBatchWithRetries",
+					attempt: attempts + 1,
+				})
+
+				const hasMoreAttempts = attempts < MAX_RETRIES - 1
+
+				// Check if it's a rate limit error
+				const httpError = error as HttpError
+				if (httpError?.status === 429) {
+					// Update global rate limit state
+					await this.updateGlobalRateLimitState(httpError)
+
+					if (hasMoreAttempts) {
+						// Calculate delay based on global rate limit state
+						const baseDelay = INITIAL_DELAY_MS * Math.pow(2, attempts)
+						const globalDelay = await this.getGlobalRateLimitDelay()
+						const delayMs = Math.max(baseDelay, globalDelay)
+
+						console.warn(
+							t("embeddings:rateLimitRetry", {
+								delayMs,
+								attempt: attempts + 1,
+								maxRetries: MAX_RETRIES,
+							}),
+						)
+						await new Promise((resolve) => setTimeout(resolve, delayMs))
+						continue
+					}
+				}
+
+				// Log the error for debugging
+				console.error(`Roo Code Cloud embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
+
+				// Format and throw the error
+				throw formatEmbeddingError(error, MAX_RETRIES)
+			}
+		}
+
+		throw new Error(t("embeddings:failedMaxAttempts", { attempts: MAX_RETRIES }))
+	}
+
+	/**
+	 * Validates the Roo Code Cloud embedder configuration by testing API connectivity
+	 * @returns Promise resolving to validation result with success status and optional error message
+	 */
+	async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
+		return withValidationErrorHandling(async () => {
+			// Check if we have a valid session token
+			const sessionToken = getSessionToken()
+			if (!sessionToken || sessionToken === "unauthenticated") {
+				return {
+					valid: false,
+					error: "embeddings:validation.rooAuthenticationRequired",
+				}
+			}
+
+			try {
+				// Update API key before validation
+				this.embeddingsClient.apiKey = sessionToken
+
+				// Test with a minimal embedding request
+				const testTexts = ["test"]
+				const modelToUse = this.defaultModelId
+
+				const response = (await this.embeddingsClient.embeddings.create({
+					input: testTexts,
+					model: modelToUse,
+					encoding_format: "base64",
+				})) as RooEmbeddingResponse
+
+				// Check if we got a valid response
+				if (!response?.data || response.data.length === 0) {
+					return {
+						valid: false,
+						error: "embeddings:validation.invalidResponse",
+					}
+				}
+
+				return { valid: true }
+			} catch (error) {
+				// Capture telemetry for validation errors
+				TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
+					error: error instanceof Error ? error.message : String(error),
+					stack: error instanceof Error ? error.stack : undefined,
+					location: "RooEmbedder:validateConfiguration",
+				})
+				throw error
+			}
+		}, "roo")
+	}
+
+	/**
+	 * Returns information about this embedder
+	 */
+	get embedderInfo(): EmbedderInfo {
+		return {
+			name: "roo",
+		}
+	}
+
+	/**
+	 * Waits if there's an active global rate limit
+	 */
+	private async waitForGlobalRateLimit(): Promise<void> {
+		const release = await RooEmbedder.globalRateLimitState.mutex.acquire()
+		let mutexReleased = false
+
+		try {
+			const state = RooEmbedder.globalRateLimitState
+
+			if (state.isRateLimited && state.rateLimitResetTime > Date.now()) {
+				const waitTime = state.rateLimitResetTime - Date.now()
+				// Silent wait - no logging to prevent flooding
+				release()
+				mutexReleased = true
+				await new Promise((resolve) => setTimeout(resolve, waitTime))
+				return
+			}
+
+			// Reset rate limit if time has passed
+			if (state.isRateLimited && state.rateLimitResetTime <= Date.now()) {
+				state.isRateLimited = false
+				state.consecutiveRateLimitErrors = 0
+			}
+		} finally {
+			// Only release if we haven't already
+			if (!mutexReleased) {
+				release()
+			}
+		}
+	}
+
+	/**
+	 * Updates global rate limit state when a 429 error occurs
+	 */
+	private async updateGlobalRateLimitState(error: HttpError): Promise<void> {
+		const release = await RooEmbedder.globalRateLimitState.mutex.acquire()
+		try {
+			const state = RooEmbedder.globalRateLimitState
+			const now = Date.now()
+
+			// Increment consecutive rate limit errors
+			if (now - state.lastRateLimitError < 60000) {
+				// Within 1 minute
+				state.consecutiveRateLimitErrors++
+			} else {
+				state.consecutiveRateLimitErrors = 1
+			}
+
+			state.lastRateLimitError = now
+
+			// Calculate exponential backoff based on consecutive errors
+			const baseDelay = 5000 // 5 seconds base
+			const maxDelay = 300000 // 5 minutes max
+			const exponentialDelay = Math.min(baseDelay * Math.pow(2, state.consecutiveRateLimitErrors - 1), maxDelay)
+
+			// Set global rate limit
+			state.isRateLimited = true
+			state.rateLimitResetTime = now + exponentialDelay
+
+			// Silent rate limit activation - no logging to prevent flooding
+		} finally {
+			release()
+		}
+	}
+
+	/**
+	 * Gets the current global rate limit delay
+	 */
+	private async getGlobalRateLimitDelay(): Promise<number> {
+		const release = await RooEmbedder.globalRateLimitState.mutex.acquire()
+		try {
+			const state = RooEmbedder.globalRateLimitState
+
+			if (state.isRateLimited && state.rateLimitResetTime > Date.now()) {
+				return state.rateLimitResetTime - Date.now()
+			}
+
+			return 0
+		} finally {
+			release()
+		}
+	}
+}

+ 1 - 0
src/services/code-index/interfaces/embedder.ts

@@ -36,6 +36,7 @@ export type AvailableEmbedders =
 	| "mistral"
 	| "mistral"
 	| "vercel-ai-gateway"
 	| "vercel-ai-gateway"
 	| "openrouter"
 	| "openrouter"
+	| "roo"
 
 
 export interface EmbedderInfo {
 export interface EmbedderInfo {
 	name: AvailableEmbedders
 	name: AvailableEmbedders

+ 1 - 0
src/services/code-index/interfaces/manager.ts

@@ -78,6 +78,7 @@ export type EmbedderProvider =
 	| "mistral"
 	| "mistral"
 	| "vercel-ai-gateway"
 	| "vercel-ai-gateway"
 	| "openrouter"
 	| "openrouter"
+	| "roo"
 
 
 export interface IndexProgressUpdate {
 export interface IndexProgressUpdate {
 	systemStatus: IndexingState
 	systemStatus: IndexingState

+ 4 - 0
src/services/code-index/service-factory.ts

@@ -6,6 +6,7 @@ import { GeminiEmbedder } from "./embedders/gemini"
 import { MistralEmbedder } from "./embedders/mistral"
 import { MistralEmbedder } from "./embedders/mistral"
 import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway"
 import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway"
 import { OpenRouterEmbedder } from "./embedders/openrouter"
 import { OpenRouterEmbedder } from "./embedders/openrouter"
+import { RooEmbedder } from "./embedders/roo"
 import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
 import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
 import { QdrantVectorStore } from "./vector-store/qdrant-client"
 import { QdrantVectorStore } from "./vector-store/qdrant-client"
 import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
 import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
@@ -85,6 +86,9 @@ export class CodeIndexServiceFactory {
 				throw new Error(t("embeddings:serviceFactory.openRouterConfigMissing"))
 				throw new Error(t("embeddings:serviceFactory.openRouterConfigMissing"))
 			}
 			}
 			return new OpenRouterEmbedder(config.openRouterOptions.apiKey, config.modelId)
 			return new OpenRouterEmbedder(config.openRouterOptions.apiKey, config.modelId)
+		} else if (provider === "roo") {
+			// Roo Code Cloud uses session token from CloudService, no API key required
+			return new RooEmbedder(config.modelId)
 		}
 		}
 
 
 		throw new Error(
 		throw new Error(

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -237,6 +237,7 @@ export interface WebviewMessage {
 			| "mistral"
 			| "mistral"
 			| "vercel-ai-gateway"
 			| "vercel-ai-gateway"
 			| "openrouter"
 			| "openrouter"
+			| "roo"
 		codebaseIndexEmbedderBaseUrl?: string
 		codebaseIndexEmbedderBaseUrl?: string
 		codebaseIndexEmbedderModelId: string
 		codebaseIndexEmbedderModelId: string
 		codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers
 		codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers

+ 22 - 1
src/shared/embeddingModels.ts

@@ -9,7 +9,8 @@ export type EmbedderProvider =
 	| "gemini"
 	| "gemini"
 	| "mistral"
 	| "mistral"
 	| "vercel-ai-gateway"
 	| "vercel-ai-gateway"
-	| "openrouter" // Add other providers as needed
+	| "openrouter"
+	| "roo" // Add other providers as needed
 
 
 export interface EmbeddingModelProfile {
 export interface EmbeddingModelProfile {
 	dimension: number
 	dimension: number
@@ -92,6 +93,23 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
 		"qwen/qwen3-embedding-4b": { dimension: 2560, scoreThreshold: 0.4 },
 		"qwen/qwen3-embedding-4b": { dimension: 2560, scoreThreshold: 0.4 },
 		"qwen/qwen3-embedding-8b": { dimension: 4096, scoreThreshold: 0.4 },
 		"qwen/qwen3-embedding-8b": { dimension: 4096, scoreThreshold: 0.4 },
 	},
 	},
+	roo: {
+		// OpenAI models via Roo Code Cloud
+		"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 via Roo Code Cloud
+		"cohere/embed-v4.0": { dimension: 1024, scoreThreshold: 0.4 },
+		// Google models via Roo Code Cloud
+		"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 via Roo Code Cloud
+		"amazon/titan-embed-text-v2": { dimension: 1024, scoreThreshold: 0.4 },
+		// Mistral models via Roo Code Cloud
+		"mistral/codestral-embed": { dimension: 1536, scoreThreshold: 0.4 },
+		"mistral/mistral-embed": { dimension: 1024, scoreThreshold: 0.4 },
+	},
 }
 }
 
 
 /**
 /**
@@ -188,6 +206,9 @@ export function getDefaultModelId(provider: EmbedderProvider): string {
 		case "openrouter":
 		case "openrouter":
 			return "openai/text-embedding-3-large"
 			return "openai/text-embedding-3-large"
 
 
+		case "roo":
+			return "openai/text-embedding-3-large"
+
 		default:
 		default:
 			// Fallback for unknown providers
 			// Fallback for unknown providers
 			console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`)
 			console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`)

+ 63 - 3
webview-ui/src/components/chat/CodeIndexPopover.tsx

@@ -161,6 +161,14 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => {
 					.min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
 					.min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
 			})
 			})
 
 
+		case "roo":
+			// Roo Code Cloud uses session token from CloudService - no API key required
+			return baseSchema.extend({
+				codebaseIndexEmbedderModelId: z
+					.string()
+					.min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
+			})
+
 		default:
 		default:
 			return baseSchema
 			return baseSchema
 	}
 	}
@@ -172,7 +180,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 }) => {
 }) => {
 	const SECRET_PLACEHOLDER = "••••••••••••••••"
 	const SECRET_PLACEHOLDER = "••••••••••••••••"
 	const { t } = useAppTranslation()
 	const { t } = useAppTranslation()
-	const { codebaseIndexConfig, codebaseIndexModels, cwd } = useExtensionState()
+	const { codebaseIndexConfig, codebaseIndexModels, cwd, cloudIsAuthenticated } = useExtensionState()
 	const [open, setOpen] = useState(false)
 	const [open, setOpen] = useState(false)
 	const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false)
 	const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false)
 	const [isSetupSettingsOpen, setIsSetupSettingsOpen] = useState(false)
 	const [isSetupSettingsOpen, setIsSetupSettingsOpen] = useState(false)
@@ -193,7 +201,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 	const getDefaultSettings = (): LocalCodeIndexSettings => ({
 	const getDefaultSettings = (): LocalCodeIndexSettings => ({
 		codebaseIndexEnabled: true,
 		codebaseIndexEnabled: true,
 		codebaseIndexQdrantUrl: "",
 		codebaseIndexQdrantUrl: "",
-		codebaseIndexEmbedderProvider: "openai",
+		codebaseIndexEmbedderProvider: "roo",
 		codebaseIndexEmbedderBaseUrl: "",
 		codebaseIndexEmbedderBaseUrl: "",
 		codebaseIndexEmbedderModelId: "",
 		codebaseIndexEmbedderModelId: "",
 		codebaseIndexEmbedderModelDimension: undefined,
 		codebaseIndexEmbedderModelDimension: undefined,
@@ -226,7 +234,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 			const settings = {
 			const settings = {
 				codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true,
 				codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true,
 				codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "",
 				codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "",
-				codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai",
+				codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "roo",
 				codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "",
 				codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "",
 				codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "",
 				codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "",
 				codebaseIndexEmbedderModelDimension:
 				codebaseIndexEmbedderModelDimension:
@@ -695,6 +703,9 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 												<SelectItem value="openrouter">
 												<SelectItem value="openrouter">
 													{t("settings:codeIndex.openRouterProvider")}
 													{t("settings:codeIndex.openRouterProvider")}
 												</SelectItem>
 												</SelectItem>
+												<SelectItem value="roo">
+													{t("settings:codeIndex.rooProvider")}
+												</SelectItem>
 											</SelectContent>
 											</SelectContent>
 										</Select>
 										</Select>
 									</div>
 									</div>
@@ -1222,6 +1233,55 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 										</>
 										</>
 									)}
 									)}
 
 
+									{currentSettings.codebaseIndexEmbedderProvider === "roo" && (
+										<>
+											{!cloudIsAuthenticated && (
+												<div className="p-3 mb-2 bg-vscode-editorWarning-background text-vscode-editorWarning-foreground rounded text-sm">
+													{t("settings:codeIndex.rooCloudAuthNote")}
+												</div>
+											)}
+
+											<div className="space-y-2">
+												<label className="text-sm font-medium">
+													{t("settings:codeIndex.modelLabel")}
+												</label>
+												<VSCodeDropdown
+													value={currentSettings.codebaseIndexEmbedderModelId}
+													onChange={(e: any) =>
+														updateSetting("codebaseIndexEmbedderModelId", e.target.value)
+													}
+													className={cn("w-full", {
+														"border-red-500": formErrors.codebaseIndexEmbedderModelId,
+													})}>
+													<VSCodeOption value="" className="p-2">
+														{t("settings:codeIndex.selectModel")}
+													</VSCodeOption>
+													{getAvailableModels().map((modelId) => {
+														const model =
+															codebaseIndexModels?.[
+																currentSettings.codebaseIndexEmbedderProvider
+															]?.[modelId]
+														return (
+															<VSCodeOption key={modelId} value={modelId} className="p-2">
+																{modelId}{" "}
+																{model
+																	? t("settings:codeIndex.modelDimensions", {
+																			dimension: model.dimension,
+																		})
+																	: ""}
+															</VSCodeOption>
+														)
+													})}
+												</VSCodeDropdown>
+												{formErrors.codebaseIndexEmbedderModelId && (
+													<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
+														{formErrors.codebaseIndexEmbedderModelId}
+													</p>
+												)}
+											</div>
+										</>
+									)}
+
 									{/* Qdrant Settings */}
 									{/* Qdrant Settings */}
 									<div className="space-y-2">
 									<div className="space-y-2">
 										<label className="text-sm font-medium">
 										<label className="text-sm font-medium">

+ 2 - 0
webview-ui/src/i18n/locales/ca/settings.json

@@ -61,6 +61,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Clau de l'API d'OpenRouter",
 		"openRouterApiKeyLabel": "Clau de l'API d'OpenRouter",
 		"openRouterApiKeyPlaceholder": "Introduïu la vostra clau de l'API d'OpenRouter",
 		"openRouterApiKeyPlaceholder": "Introduïu la vostra clau de l'API d'OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud utilitza l'autenticació del teu compte. Inicia sessió a Roo Code Cloud per utilitzar aquest proveïdor.",
 		"openaiCompatibleProvider": "Compatible amb OpenAI",
 		"openaiCompatibleProvider": "Compatible amb OpenAI",
 		"openAiKeyLabel": "Clau API OpenAI",
 		"openAiKeyLabel": "Clau API OpenAI",
 		"openAiKeyPlaceholder": "Introduïu la vostra clau API OpenAI",
 		"openAiKeyPlaceholder": "Introduïu la vostra clau API OpenAI",

+ 2 - 0
webview-ui/src/i18n/locales/de/settings.json

@@ -63,6 +63,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API-Schlüssel",
 		"openRouterApiKeyLabel": "OpenRouter API-Schlüssel",
 		"openRouterApiKeyPlaceholder": "Gib deinen OpenRouter API-Schlüssel ein",
 		"openRouterApiKeyPlaceholder": "Gib deinen OpenRouter API-Schlüssel ein",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud verwendet deine Kontoauthentifizierung. Melde dich bei Roo Code Cloud an, um diesen Anbieter zu verwenden.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "API-Schlüssel:",
 		"mistralApiKeyLabel": "API-Schlüssel:",
 		"mistralApiKeyPlaceholder": "Gib deinen Mistral-API-Schlüssel ein",
 		"mistralApiKeyPlaceholder": "Gib deinen Mistral-API-Schlüssel ein",

+ 2 - 0
webview-ui/src/i18n/locales/en/settings.json

@@ -72,6 +72,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API Key",
 		"openRouterApiKeyLabel": "OpenRouter API Key",
 		"openRouterApiKeyPlaceholder": "Enter your OpenRouter API key",
 		"openRouterApiKeyPlaceholder": "Enter your OpenRouter API key",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud uses your account authentication. Sign in to Roo Code Cloud to use this provider.",
 		"openaiCompatibleProvider": "OpenAI Compatible",
 		"openaiCompatibleProvider": "OpenAI Compatible",
 		"openAiKeyLabel": "OpenAI API Key",
 		"openAiKeyLabel": "OpenAI API Key",
 		"openAiKeyPlaceholder": "Enter your OpenAI API key",
 		"openAiKeyPlaceholder": "Enter your OpenAI API key",

+ 2 - 0
webview-ui/src/i18n/locales/es/settings.json

@@ -63,6 +63,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Clave de API de OpenRouter",
 		"openRouterApiKeyLabel": "Clave de API de OpenRouter",
 		"openRouterApiKeyPlaceholder": "Introduce tu clave de API de OpenRouter",
 		"openRouterApiKeyPlaceholder": "Introduce tu clave de API de OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud utiliza la autenticación de tu cuenta. Inicia sesión en Roo Code Cloud para usar este proveedor.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Clave API:",
 		"mistralApiKeyLabel": "Clave API:",
 		"mistralApiKeyPlaceholder": "Introduce tu clave de API de Mistral",
 		"mistralApiKeyPlaceholder": "Introduce tu clave de API de Mistral",

+ 2 - 0
webview-ui/src/i18n/locales/fr/settings.json

@@ -63,6 +63,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Clé d'API OpenRouter",
 		"openRouterApiKeyLabel": "Clé d'API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Entrez votre clé d'API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Entrez votre clé d'API OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud utilise l'authentification de ton compte. Connecte-toi à Roo Code Cloud pour utiliser ce fournisseur.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Clé d'API:",
 		"mistralApiKeyLabel": "Clé d'API:",
 		"mistralApiKeyPlaceholder": "Entrez votre clé d'API Mistral",
 		"mistralApiKeyPlaceholder": "Entrez votre clé d'API Mistral",

+ 2 - 0
webview-ui/src/i18n/locales/hi/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "ओपनराउटर",
 		"openRouterProvider": "ओपनराउटर",
 		"openRouterApiKeyLabel": "ओपनराउटर एपीआई कुंजी",
 		"openRouterApiKeyLabel": "ओपनराउटर एपीआई कुंजी",
 		"openRouterApiKeyPlaceholder": "अपनी ओपनराउटर एपीआई कुंजी दर्ज करें",
 		"openRouterApiKeyPlaceholder": "अपनी ओपनराउटर एपीआई कुंजी दर्ज करें",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud आपके खाते के प्रमाणीकरण का उपयोग करता है। इस प्रदाता का उपयोग करने के लिए Roo Code Cloud में साइन इन करें।",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "API कुंजी:",
 		"mistralApiKeyLabel": "API कुंजी:",
 		"mistralApiKeyPlaceholder": "अपनी मिस्ट्रल एपीआई कुंजी दर्ज करें",
 		"mistralApiKeyPlaceholder": "अपनी मिस्ट्रल एपीआई कुंजी दर्ज करें",

+ 2 - 0
webview-ui/src/i18n/locales/id/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Kunci API OpenRouter",
 		"openRouterApiKeyLabel": "Kunci API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Masukkan kunci API OpenRouter Anda",
 		"openRouterApiKeyPlaceholder": "Masukkan kunci API OpenRouter Anda",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud menggunakan autentikasi akun Anda. Masuk ke Roo Code Cloud untuk menggunakan penyedia ini.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Kunci API:",
 		"mistralApiKeyLabel": "Kunci API:",
 		"mistralApiKeyPlaceholder": "Masukkan kunci API Mistral Anda",
 		"mistralApiKeyPlaceholder": "Masukkan kunci API Mistral Anda",

+ 2 - 0
webview-ui/src/i18n/locales/it/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Chiave API OpenRouter",
 		"openRouterApiKeyLabel": "Chiave API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Inserisci la tua chiave API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Inserisci la tua chiave API OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud utilizza l'autenticazione del tuo account. Accedi a Roo Code Cloud per utilizzare questo provider.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Chiave API:",
 		"mistralApiKeyLabel": "Chiave API:",
 		"mistralApiKeyPlaceholder": "Inserisci la tua chiave API Mistral",
 		"mistralApiKeyPlaceholder": "Inserisci la tua chiave API Mistral",

+ 2 - 0
webview-ui/src/i18n/locales/ja/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter APIキー",
 		"openRouterApiKeyLabel": "OpenRouter APIキー",
 		"openRouterApiKeyPlaceholder": "OpenRouter APIキーを入力してください",
 		"openRouterApiKeyPlaceholder": "OpenRouter APIキーを入力してください",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloudはアカウント認証を使用します。このプロバイダーを使用するにはRoo Code Cloudにサインインしてください。",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "APIキー:",
 		"mistralApiKeyLabel": "APIキー:",
 		"mistralApiKeyPlaceholder": "Mistral APIキーを入力してください",
 		"mistralApiKeyPlaceholder": "Mistral APIキーを入力してください",

+ 2 - 0
webview-ui/src/i18n/locales/ko/settings.json

@@ -61,6 +61,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API 키",
 		"openRouterApiKeyLabel": "OpenRouter API 키",
 		"openRouterApiKeyPlaceholder": "OpenRouter API 키를 입력하세요",
 		"openRouterApiKeyPlaceholder": "OpenRouter API 키를 입력하세요",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud는 계정 인증을 사용합니다. 이 제공업체를 사용하려면 Roo Code Cloud에 로그인하세요.",
 		"openaiCompatibleProvider": "OpenAI 호환",
 		"openaiCompatibleProvider": "OpenAI 호환",
 		"openAiKeyLabel": "OpenAI API 키",
 		"openAiKeyLabel": "OpenAI API 키",
 		"openAiKeyPlaceholder": "OpenAI API 키를 입력하세요",
 		"openAiKeyPlaceholder": "OpenAI API 키를 입력하세요",

+ 2 - 0
webview-ui/src/i18n/locales/nl/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API-sleutel",
 		"openRouterApiKeyLabel": "OpenRouter API-sleutel",
 		"openRouterApiKeyPlaceholder": "Voer uw OpenRouter API-sleutel in",
 		"openRouterApiKeyPlaceholder": "Voer uw OpenRouter API-sleutel in",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud gebruikt je accountauthenticatie. Meld je aan bij Roo Code Cloud om deze provider te gebruiken.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "API-sleutel:",
 		"mistralApiKeyLabel": "API-sleutel:",
 		"mistralApiKeyPlaceholder": "Voer uw Mistral API-sleutel in",
 		"mistralApiKeyPlaceholder": "Voer uw Mistral API-sleutel in",

+ 2 - 0
webview-ui/src/i18n/locales/pl/settings.json

@@ -61,6 +61,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Klucz API OpenRouter",
 		"openRouterApiKeyLabel": "Klucz API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Wprowadź swój klucz API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Wprowadź swój klucz API OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud używa uwierzytelnienia twojego konta. Zaloguj się do Roo Code Cloud, aby używać tego dostawcy.",
 		"openaiCompatibleProvider": "Kompatybilny z OpenAI",
 		"openaiCompatibleProvider": "Kompatybilny z OpenAI",
 		"openAiKeyLabel": "Klucz API OpenAI",
 		"openAiKeyLabel": "Klucz API OpenAI",
 		"openAiKeyPlaceholder": "Wprowadź swój klucz API OpenAI",
 		"openAiKeyPlaceholder": "Wprowadź swój klucz API OpenAI",

+ 2 - 0
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Chave de API do OpenRouter",
 		"openRouterApiKeyLabel": "Chave de API do OpenRouter",
 		"openRouterApiKeyPlaceholder": "Digite sua chave de API do OpenRouter",
 		"openRouterApiKeyPlaceholder": "Digite sua chave de API do OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud usa a autenticação da sua conta. Faça login no Roo Code Cloud para usar este provedor.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Chave de API:",
 		"mistralApiKeyLabel": "Chave de API:",
 		"mistralApiKeyPlaceholder": "Digite sua chave de API da Mistral",
 		"mistralApiKeyPlaceholder": "Digite sua chave de API da Mistral",

+ 2 - 0
webview-ui/src/i18n/locales/ru/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Ключ API OpenRouter",
 		"openRouterApiKeyLabel": "Ключ API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Введите свой ключ API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Введите свой ключ API OpenRouter",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud использует аутентификацию твоего аккаунта. Войди в Roo Code Cloud, чтобы использовать этого провайдера.",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "Ключ API:",
 		"mistralApiKeyLabel": "Ключ API:",
 		"mistralApiKeyPlaceholder": "Введите свой API-ключ Mistral",
 		"mistralApiKeyPlaceholder": "Введите свой API-ключ Mistral",

+ 2 - 0
webview-ui/src/i18n/locales/tr/settings.json

@@ -61,6 +61,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API Anahtarı",
 		"openRouterApiKeyLabel": "OpenRouter API Anahtarı",
 		"openRouterApiKeyPlaceholder": "OpenRouter API anahtarınızı girin",
 		"openRouterApiKeyPlaceholder": "OpenRouter API anahtarınızı girin",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud hesap kimlik doğrulamanı kullanır. Bu sağlayıcıyı kullanmak için Roo Code Cloud'a giriş yap.",
 		"openaiCompatibleProvider": "OpenAI Uyumlu",
 		"openaiCompatibleProvider": "OpenAI Uyumlu",
 		"openAiKeyLabel": "OpenAI API Anahtarı",
 		"openAiKeyLabel": "OpenAI API Anahtarı",
 		"openAiKeyPlaceholder": "OpenAI API anahtarınızı girin",
 		"openAiKeyPlaceholder": "OpenAI API anahtarınızı girin",

+ 2 - 0
webview-ui/src/i18n/locales/vi/settings.json

@@ -61,6 +61,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "Khóa API OpenRouter",
 		"openRouterApiKeyLabel": "Khóa API OpenRouter",
 		"openRouterApiKeyPlaceholder": "Nhập khóa API OpenRouter của bạn",
 		"openRouterApiKeyPlaceholder": "Nhập khóa API OpenRouter của bạn",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud sử dụng xác thực tài khoản của bạn. Đăng nhập vào Roo Code Cloud để sử dụng nhà cung cấp này.",
 		"openaiCompatibleProvider": "Tương thích OpenAI",
 		"openaiCompatibleProvider": "Tương thích OpenAI",
 		"openAiKeyLabel": "Khóa API OpenAI",
 		"openAiKeyLabel": "Khóa API OpenAI",
 		"openAiKeyPlaceholder": "Nhập khóa API OpenAI của bạn",
 		"openAiKeyPlaceholder": "Nhập khóa API OpenAI của bạn",

+ 2 - 0
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -63,6 +63,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API 密钥",
 		"openRouterApiKeyLabel": "OpenRouter API 密钥",
 		"openRouterApiKeyPlaceholder": "输入您的 OpenRouter API 密钥",
 		"openRouterApiKeyPlaceholder": "输入您的 OpenRouter API 密钥",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud 使用您的帐户认证。请登录 Roo Code Cloud 以使用此提供商。",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "API 密钥:",
 		"mistralApiKeyLabel": "API 密钥:",
 		"mistralApiKeyPlaceholder": "输入您的 Mistral API 密钥",
 		"mistralApiKeyPlaceholder": "输入您的 Mistral API 密钥",

+ 2 - 0
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -58,6 +58,8 @@
 		"openRouterProvider": "OpenRouter",
 		"openRouterProvider": "OpenRouter",
 		"openRouterApiKeyLabel": "OpenRouter API 金鑰",
 		"openRouterApiKeyLabel": "OpenRouter API 金鑰",
 		"openRouterApiKeyPlaceholder": "輸入您的 OpenRouter API 金鑰",
 		"openRouterApiKeyPlaceholder": "輸入您的 OpenRouter API 金鑰",
+		"rooProvider": "Roo Code Cloud",
+		"rooCloudAuthNote": "Roo Code Cloud 使用您的帳戶認證。請登入 Roo Code Cloud 以使用此提供者。",
 		"mistralProvider": "Mistral",
 		"mistralProvider": "Mistral",
 		"mistralApiKeyLabel": "API 金鑰:",
 		"mistralApiKeyLabel": "API 金鑰:",
 		"mistralApiKeyPlaceholder": "輸入您的 Mistral API 金鑰",
 		"mistralApiKeyPlaceholder": "輸入您的 Mistral API 金鑰",