Merge branch 'RooCodeInc:main' into main

This commit is contained in:
Murilo Pires 2025-07-11 10:57:18 -03:00 committed by GitHub
commit fdb1f359f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 678 additions and 221 deletions

View file

@ -383,7 +383,7 @@
- Fix vscode-material-icons in the filer picker
- Fix global settings export
- Respect user-configured terminal integration timeout (thanks @KJ7LNW)
- Contex condensing enhancements (thanks @SannidhyaSah)
- Context condensing enhancements (thanks @SannidhyaSah)
## [3.18.1] - 2025-05-22
@ -895,7 +895,7 @@
## [3.10.1] - 2025-03-20
- Make the suggested responses optional to not break overriden system prompts
- Make the suggested responses optional to not break overridden system prompts
## [3.10.0] - 2025-03-20

View file

@ -224,7 +224,7 @@ function validateInput(input) {
},
text: `Use apply_diff on the file ${testFile.name} to change "Hello World" to "Hello Universe". The file already exists with this content:
${testFile.content}\nAssume the file exists and you can modify it directly.`,
}) //Temporary meassure since list_files ignores all the files inside a tmp workspace
}) //Temporary measure since list_files ignores all the files inside a tmp workspace
console.log("Task ID:", taskId)
console.log("Test filename:", testFile.name)

View file

@ -203,7 +203,7 @@ export function Evals({
</ScatterChart>
</ChartContainer>
<div className="py-4 text-xs opacity-50">
(Note: Very expensive models are exluded from the scatter plot.)
(Note: Very expensive models are excluded from the scatter plot.)
</div>
</TableCaption>
</Table>

View file

@ -29,7 +29,7 @@ Start the evals service:
docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0
```
The initial build process can take a minute or two. Upon success you should see ouput indicating that a web service is running on [localhost:3000](http://localhost:3000/):
The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on [localhost:3000](http://localhost:3000/):
<img width="1182" alt="Screenshot 2025-06-05 at 12 05 38PM" src="https://github.com/user-attachments/assets/34f25a59-1362-458c-aafa-25e13cdb2a7a" />
Additionally, you'll find in Docker Desktop that database and redis services are running:

View file

@ -14,6 +14,14 @@ export const mistralModels = {
inputPrice: 2.0,
outputPrice: 5.0,
},
"devstral-medium-latest": {
maxTokens: 131_000,
contextWindow: 131_000,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.4,
outputPrice: 2.0,
},
"mistral-medium-latest": {
maxTokens: 131_000,
contextWindow: 131_000,

View file

@ -36,7 +36,7 @@ export function getReadFileToolDescription(blockName: string, blockParams: any):
}
} catch (error) {
console.error("Failed to parse read_file args XML for description:", error)
return `[${blockName} with unparseable args]`
return `[${blockName} with unparsable args]`
}
} else if (blockParams.path) {
// Fallback for legacy single-path usage

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "No s'ha pogut processar el lot després de {{maxRetries}} intents: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}"
"qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}",
"vectorDimensionMismatch": "No s'ha pogut actualitzar l'índex de vectors per al nou model. Prova d'esborrar l'índex i tornar a començar. Detalls: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Ha fallat l'autenticació. Comproveu la vostra clau d'API a la configuració.",
@ -36,7 +37,9 @@
"invalidApiKey": "Clau d'API no vàlida. Comproveu la vostra configuració de clau d'API.",
"invalidBaseUrl": "URL base no vàlida. Comproveu la vostra configuració d'URL.",
"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",
"baseUrlRequired": "Es requereix una URL base per a aquest incrustador"
},
"serviceFactory": {
"openAiConfigMissing": "Falta la configuració d'OpenAI per crear l'embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Verarbeitung des Batches nach {{maxRetries}} Versuchen fehlgeschlagen: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}"
"qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}",
"vectorDimensionMismatch": "Aktualisierung des Vektorindex für neues Modell fehlgeschlagen. Bitte versuche, den Index zu löschen und von vorne zu beginnen. Details: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfe deinen API-Schlüssel in den Einstellungen.",
@ -36,7 +37,9 @@
"invalidApiKey": "Ungültiger API-Schlüssel. Bitte überprüfe deine API-Schlüssel-Konfiguration.",
"invalidBaseUrl": "Ungültige Basis-URL. Bitte überprüfe deine URL-Konfiguration.",
"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",
"baseUrlRequired": "Basis-URL ist für diesen Embedder erforderlich"
},
"serviceFactory": {
"openAiConfigMissing": "OpenAI-Konfiguration fehlt für die Erstellung des Embedders",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Failed to process batch after {{maxRetries}} attempts: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}"
"qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}",
"vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Authentication failed. Please check your API key in the settings.",
@ -36,7 +37,9 @@
"invalidApiKey": "Invalid API key. Please check your API key configuration.",
"invalidBaseUrl": "Invalid base URL. Please check your URL 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",
"baseUrlRequired": "Base URL is required for this embedder"
},
"serviceFactory": {
"openAiConfigMissing": "OpenAI configuration missing for embedder creation",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Error al procesar lote después de {{maxRetries}} intentos: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}"
"qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}",
"vectorDimensionMismatch": "No se pudo actualizar el índice de vectores para el nuevo modelo. Intenta borrar el índice y empezar de nuevo. Detalles: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Error de autenticación. Comprueba tu clave de API en los ajustes.",
@ -36,7 +37,9 @@
"invalidApiKey": "Clave de API no válida. Comprueba la configuración de tu clave de API.",
"invalidBaseUrl": "URL base no válida. Comprueba la configuración de tu URL.",
"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",
"baseUrlRequired": "Se requiere una URL base para este embedder"
},
"serviceFactory": {
"openAiConfigMissing": "Falta la configuración de OpenAI para crear el incrustador",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Échec du traitement du lot après {{maxRetries}} tentatives : {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}"
"qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}",
"vectorDimensionMismatch": "Échec de la mise à jour de l'index vectoriel pour le nouveau modèle. Veuillez essayer de vider l'index et de recommencer. Détails : {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Échec de l'authentification. Veuillez vérifier votre clé API dans les paramètres.",
@ -36,7 +37,9 @@
"invalidApiKey": "Clé API invalide. Veuillez vérifier votre configuration de clé API.",
"invalidBaseUrl": "URL de base invalide. Veuillez vérifier votre configuration d'URL.",
"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.",
"baseUrlRequired": "Une URL de base est requise pour cet embedder"
},
"serviceFactory": {
"openAiConfigMissing": "Configuration OpenAI manquante pour la création de l'embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "{{maxRetries}} प्रयासों के बाद बैच प्रसंस्करण विफल: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}"
"qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}",
"vectorDimensionMismatch": "नए मॉडल के लिए वेक्टर इंडेक्स को अपडेट करने में विफल। कृपया इंडेक्स को साफ़ करने और फिर से शुरू करने का प्रयास करें। विवरण: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "प्रमाणीकरण विफल। कृपया सेटिंग्स में अपनी एपीआई कुंजी जांचें।",
@ -36,7 +37,9 @@
"invalidApiKey": "अमान्य एपीआई कुंजी। कृपया अपनी एपीआई कुंजी कॉन्फ़िगरेशन जांचें।",
"invalidBaseUrl": "अमान्य बेस यूआरएल। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।",
"invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।",
"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।"
"invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।",
"apiKeyRequired": "इस एम्बेडर के लिए API कुंजी आवश्यक है।",
"baseUrlRequired": "इस एम्बेडर के लिए बेस यूआरएल आवश्यक है"
},
"serviceFactory": {
"openAiConfigMissing": "एम्बेडर बनाने के लिए OpenAI कॉन्फ़िगरेशन गायब है",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Gagal memproses batch setelah {{maxRetries}} percobaan: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}"
"qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}",
"vectorDimensionMismatch": "Gagal memperbarui indeks vektor untuk model baru. Silakan coba bersihkan indeks dan mulai lagi. Detail: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Autentikasi gagal. Silakan periksa kunci API Anda di pengaturan.",
@ -36,7 +37,9 @@
"invalidApiKey": "Kunci API tidak valid. Silakan periksa konfigurasi kunci API Anda.",
"invalidBaseUrl": "URL dasar tidak valid. Silakan periksa konfigurasi URL 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",
"baseUrlRequired": "URL dasar diperlukan untuk embedder ini"
},
"serviceFactory": {
"openAiConfigMissing": "Konfigurasi OpenAI tidak ada untuk membuat embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Elaborazione del batch fallita dopo {{maxRetries}} tentativi: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}"
"qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}",
"vectorDimensionMismatch": "Impossibile aggiornare l'indice vettoriale per il nuovo modello. Prova a cancellare l'indice e a ricominciare. Dettagli: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Autenticazione fallita. Controlla la tua chiave API nelle impostazioni.",
@ -36,7 +37,9 @@
"invalidApiKey": "Chiave API non valida. Controlla la configurazione della tua chiave API.",
"invalidBaseUrl": "URL di base non valido. Controlla la configurazione del tuo URL.",
"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",
"baseUrlRequired": "È richiesto un URL di base per questo embedder"
},
"serviceFactory": {
"openAiConfigMissing": "Configurazione OpenAI mancante per la creazione dell'embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "{{maxRetries}}回の試行後、バッチ処理に失敗しました:{{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}"
"qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}",
"vectorDimensionMismatch": "新しいモデルのベクトルインデックスの更新に失敗しました。インデックスをクリアして再試行してください。詳細:{{errorMessage}}"
},
"validation": {
"authenticationFailed": "認証に失敗しました。設定でAPIキーを確認してください。",
@ -36,7 +37,9 @@
"invalidApiKey": "無効なAPIキーです。APIキー構成を確認してください。",
"invalidBaseUrl": "無効なベースURLです。URL構成を確認してください。",
"invalidModel": "無効なモデルです。モデル構成を確認してください。",
"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。"
"invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。",
"apiKeyRequired": "このエンベッダーにはAPIキーが必要です。",
"baseUrlRequired": "このエンベッダーにはベースURLが必要です"
},
"serviceFactory": {
"openAiConfigMissing": "エンベッダー作成のためのOpenAI設定がありません",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "{{maxRetries}}번 시도 후 배치 처리 실패: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}"
"qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}",
"vectorDimensionMismatch": "새 모델의 벡터 인덱스를 업데이트하지 못했습니다. 인덱스를 지우고 다시 시작해 보세요. 세부 정보: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "인증에 실패했습니다. 설정에서 API 키를 확인하세요.",
@ -36,7 +37,9 @@
"invalidApiKey": "잘못된 API 키입니다. API 키 구성을 확인하세요.",
"invalidBaseUrl": "잘못된 기본 URL입니다. URL 구성을 확인하세요.",
"invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.",
"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요."
"invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요.",
"apiKeyRequired": "이 임베더에는 API 키가 필요합니다",
"baseUrlRequired": "이 임베더에는 기본 URL이 필요합니다"
},
"serviceFactory": {
"openAiConfigMissing": "임베더 생성을 위한 OpenAI 구성이 누락되었습니다",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Verwerken van batch mislukt na {{maxRetries}} pogingen: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}"
"qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}",
"vectorDimensionMismatch": "Kan de vectorindex voor het nieuwe model niet bijwerken. Probeer de index te wissen en opnieuw te beginnen. Details: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Authenticatie mislukt. Controleer je API-sleutel in de instellingen.",
@ -36,7 +37,9 @@
"invalidApiKey": "Ongeldige API-sleutel. Controleer je API-sleutelconfiguratie.",
"invalidBaseUrl": "Ongeldige basis-URL. Controleer je URL-configuratie.",
"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",
"baseUrlRequired": "Basis-URL is vereist voor deze embedder"
},
"serviceFactory": {
"openAiConfigMissing": "OpenAI-configuratie ontbreekt voor het maken van embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Nie udało się przetworzyć partii po {{maxRetries}} próbach: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}"
"qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}",
"vectorDimensionMismatch": "Nie udało się zaktualizować indeksu wektorowego dla nowego modelu. Spróbuj wyczyścić indeks i zacząć od nowa. Szczegóły: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Uwierzytelnianie nie powiodło się. Sprawdź swój klucz API w ustawieniach.",
@ -36,7 +37,9 @@
"invalidApiKey": "Nieprawidłowy klucz API. Sprawdź konfigurację klucza API.",
"invalidBaseUrl": "Nieprawidłowy podstawowy adres URL. Sprawdź konfigurację adresu URL.",
"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",
"baseUrlRequired": "Podstawowy adres URL jest wymagany dla tego embeddera"
},
"serviceFactory": {
"openAiConfigMissing": "Brak konfiguracji OpenAI do utworzenia embeddera",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Falha ao processar lote após {{maxRetries}} tentativas: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}"
"qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}",
"vectorDimensionMismatch": "Falha ao atualizar o índice de vetores para o novo modelo. Tente limpar o índice e começar novamente. Detalhes: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Falha na autenticação. Verifique sua chave de API nas configurações.",
@ -36,7 +37,9 @@
"invalidApiKey": "Chave de API inválida. Verifique sua configuração de chave de API.",
"invalidBaseUrl": "URL base inválida. Verifique sua configuração de URL.",
"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",
"baseUrlRequired": "A URL base é necessária para este embedder"
},
"serviceFactory": {
"openAiConfigMissing": "Configuração do OpenAI ausente para criação do embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Не удалось обработать пакет после {{maxRetries}} попыток: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}"
"qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}",
"vectorDimensionMismatch": "Не удалось обновить векторный индекс для новой модели. Попробуйте очистить индекс и начать сначала. Подробности: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Ошибка аутентификации. Проверьте свой ключ API в настройках.",
@ -36,7 +37,9 @@
"invalidApiKey": "Неверный ключ API. Проверьте конфигурацию ключа API.",
"invalidBaseUrl": "Неверный базовый URL. Проверьте конфигурацию URL.",
"invalidModel": "Неверная модель. Проверьте конфигурацию модели.",
"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию."
"invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию.",
"apiKeyRequired": "Для этого встраивателя требуется ключ API",
"baseUrlRequired": "Для этого встраивателя требуется базовый URL"
},
"serviceFactory": {
"openAiConfigMissing": "Отсутствует конфигурация OpenAI для создания эмбеддера",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "{{maxRetries}} denemeden sonra toplu işlem başarısız oldu: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}"
"qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}",
"vectorDimensionMismatch": "Yeni model için vektör dizini güncellenemedi. Lütfen dizini temizleyip yeniden başlatmayı deneyin. Detaylar: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Kimlik doğrulama başarısız oldu. Lütfen ayarlardan API anahtarınızı kontrol edin.",
@ -36,7 +37,9 @@
"invalidApiKey": "Geçersiz API anahtarı. Lütfen API anahtarı yapılandırmanızı kontrol edin.",
"invalidBaseUrl": "Geçersiz temel URL. Lütfen URL 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",
"baseUrlRequired": "Bu gömücü için temel URL gereklidir"
},
"serviceFactory": {
"openAiConfigMissing": "Gömücü oluşturmak için OpenAI yapılandırması eksik",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "Không thể xử lý lô sau {{maxRetries}} lần thử: {{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}"
"qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}",
"vectorDimensionMismatch": "Không thể cập nhật chỉ mục vector cho mô hình mới. Vui lòng thử xóa chỉ mục và bắt đầu lại. Chi tiết: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "Xác thực không thành công. Vui lòng kiểm tra khóa API của bạn trong cài đặt.",
@ -36,7 +37,9 @@
"invalidApiKey": "Khóa API không hợp lệ. Vui lòng kiểm tra cấu hình khóa API của bạn.",
"invalidBaseUrl": "URL cơ sở không hợp lệ. Vui lòng kiểm tra cấu hình URL 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",
"baseUrlRequired": "Cần có URL cơ sở cho trình nhúng này"
},
"serviceFactory": {
"openAiConfigMissing": "Thiếu cấu hình OpenAI để tạo embedder",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "尝试 {{maxRetries}} 次后批次处理失败:{{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}"
"qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}",
"vectorDimensionMismatch": "无法更新新模型的向量索引。请尝试清除索引并重新开始。详细信息:{{errorMessage}}"
},
"validation": {
"authenticationFailed": "身份验证失败。请在设置中检查您的 API 密钥。",
@ -36,7 +37,9 @@
"invalidApiKey": "API 密钥无效。请检查您的 API 密钥配置。",
"invalidBaseUrl": "基础 URL 无效。请检查您的 URL 配置。",
"invalidModel": "模型无效。请检查您的模型配置。",
"invalidResponse": "嵌入服务响应无效。请检查您的配置。"
"invalidResponse": "嵌入服务响应无效。请检查您的配置。",
"apiKeyRequired": "此嵌入器需要 API 密钥",
"baseUrlRequired": "此嵌入器需要基础 URL"
},
"serviceFactory": {
"openAiConfigMissing": "创建嵌入器缺少 OpenAI 配置",

View file

@ -23,7 +23,8 @@
"failedToProcessBatchWithError": "嘗試 {{maxRetries}} 次後批次處理失敗:{{errorMessage}}"
},
"vectorStore": {
"qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}"
"qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}",
"vectorDimensionMismatch": "無法更新新模型的向量索引。請嘗試清除索引並重新開始。詳細資訊: {{errorMessage}}"
},
"validation": {
"authenticationFailed": "驗證失敗。請在設定中檢查您的 API 金鑰。",
@ -36,7 +37,9 @@
"invalidApiKey": "無效的 API 金鑰。請檢查您的 API 金鑰組態。",
"invalidBaseUrl": "無效的基礎 URL。請檢查您的 URL 組態。",
"invalidModel": "無效的模型。請檢查您的模型組態。",
"invalidResponse": "內嵌服務回應無效。請檢查您的組態。"
"invalidResponse": "內嵌服務回應無效。請檢查您的組態。",
"apiKeyRequired": "此嵌入器需要 API 金鑰",
"baseUrlRequired": "此嵌入器需要基礎 URL"
},
"serviceFactory": {
"openAiConfigMissing": "建立嵌入器缺少 OpenAI 設定",

View file

@ -34,9 +34,9 @@ describe("GeminiEmbedder", () => {
it("should throw error when API key is not provided", () => {
// Act & Assert
expect(() => new GeminiEmbedder("")).toThrow("API key is required for Gemini embedder")
expect(() => new GeminiEmbedder(null as any)).toThrow("API key is required for Gemini embedder")
expect(() => new GeminiEmbedder(undefined as any)).toThrow("API key is required for Gemini embedder")
expect(() => new GeminiEmbedder("")).toThrow("validation.apiKeyRequired")
expect(() => new GeminiEmbedder(null as any)).toThrow("validation.apiKeyRequired")
expect(() => new GeminiEmbedder(undefined as any)).toThrow("validation.apiKeyRequired")
})
})

View file

@ -80,19 +80,19 @@ describe("OpenAICompatibleEmbedder", () => {
it("should throw error when baseUrl is missing", () => {
expect(() => new OpenAICompatibleEmbedder("", testApiKey, testModelId)).toThrow(
"Base URL is required for OpenAI Compatible embedder",
"embeddings:validation.baseUrlRequired",
)
})
it("should throw error when apiKey is missing", () => {
expect(() => new OpenAICompatibleEmbedder(testBaseUrl, "", testModelId)).toThrow(
"API key is required for OpenAI Compatible embedder",
"embeddings:validation.apiKeyRequired",
)
})
it("should throw error when both baseUrl and apiKey are missing", () => {
expect(() => new OpenAICompatibleEmbedder("", "", testModelId)).toThrow(
"Base URL is required for OpenAI Compatible embedder",
"embeddings:validation.baseUrlRequired",
)
})
})

View file

@ -1,6 +1,7 @@
import { OpenAICompatibleEmbedder } from "./openai-compatible"
import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder"
import { GEMINI_MAX_ITEM_TOKENS } from "../constants"
import { t } from "../../../i18n"
/**
* Gemini embedder implementation that wraps the OpenAI Compatible embedder
@ -23,7 +24,7 @@ export class GeminiEmbedder implements IEmbedder {
*/
constructor(apiKey: string) {
if (!apiKey) {
throw new Error("API key is required for Gemini embedder")
throw new Error(t("embeddings:validation.apiKeyRequired"))
}
// Create an OpenAI Compatible embedder with Gemini's fixed configuration

View file

@ -45,10 +45,10 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
*/
constructor(baseUrl: string, apiKey: string, modelId?: string, maxItemTokens?: number) {
if (!baseUrl) {
throw new Error("Base URL is required for OpenAI Compatible embedder")
throw new Error(t("embeddings:validation.baseUrlRequired"))
}
if (!apiKey) {
throw new Error("API key is required for OpenAI Compatible embedder")
throw new Error(t("embeddings:validation.apiKeyRequired"))
}
this.baseUrl = baseUrl

View file

@ -263,15 +263,8 @@ export class CodeIndexManager {
const validationResult = await this._serviceFactory.validateEmbedder(embedder)
if (!validationResult.valid) {
const errorMessage = validationResult.error || "Embedder configuration validation failed"
// Always attempt translation, use original as fallback
let translatedMessage = t(errorMessage)
// If translation returns a different value (stripped namespace), use original
if (translatedMessage !== errorMessage && !translatedMessage.includes(":")) {
translatedMessage = errorMessage
}
this._stateManager.setSystemState("Error", translatedMessage)
throw new Error(translatedMessage)
this._stateManager.setSystemState("Error", errorMessage)
throw new Error(errorMessage)
}
// (Re)Initialize orchestrator

View file

@ -28,16 +28,16 @@ export function getErrorMessageForStatus(status: number | undefined, embedderTyp
switch (status) {
case 401:
case 403:
return "embeddings:validation.authenticationFailed"
return t("embeddings:validation.authenticationFailed")
case 404:
return embedderType === "openai"
? "embeddings:validation.modelNotAvailable"
: "embeddings:validation.invalidEndpoint"
? t("embeddings:validation.modelNotAvailable")
: t("embeddings:validation.invalidEndpoint")
case 429:
return "embeddings:validation.serviceUnavailable"
return t("embeddings:validation.serviceUnavailable")
default:
if (status && status >= 400 && status < 600) {
return "embeddings:validation.configurationError"
return t("embeddings:validation.configurationError")
}
return undefined
}
@ -138,11 +138,11 @@ export function handleValidationError(
errorMessage.includes("HTTP 0:") ||
errorMessage === "No response"
) {
return { valid: false, error: "embeddings:validation.connectionFailed" }
return { valid: false, error: t("embeddings:validation.connectionFailed") }
}
if (errorMessage.includes("Failed to parse response JSON")) {
return { valid: false, error: "embeddings:validation.invalidResponse" }
return { valid: false, error: t("embeddings:validation.invalidResponse") }
}
}
@ -152,7 +152,7 @@ export function handleValidationError(
}
// Fallback to generic error
return { valid: false, error: "embeddings:validation.configurationError" }
return { valid: false, error: t("embeddings:validation.configurationError") }
}
/**

View file

@ -9,6 +9,9 @@ import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../../cons
vitest.mock("@qdrant/js-client-rest")
vitest.mock("crypto")
vitest.mock("../../../../utils/path")
vitest.mock("../../../../i18n", () => ({
t: (key: string) => key, // Just return the key for testing
}))
vitest.mock("path", () => ({
...vitest.importActual("path"),
sep: "/",
@ -674,7 +677,7 @@ describe("QdrantVectorStore", () => {
;(console.warn as any).mockRestore()
})
it("should re-throw error from deleteCollection when recreating collection with mismatched vectorSize", async () => {
it("should throw vectorDimensionMismatch error when deleteCollection fails during recreation", async () => {
const differentVectorSize = 768
mockQdrantClientInstance.getCollection.mockResolvedValue({
config: {
@ -691,15 +694,67 @@ describe("QdrantVectorStore", () => {
vitest.spyOn(console, "error").mockImplementation(() => {})
vitest.spyOn(console, "warn").mockImplementation(() => {})
// The actual error message includes the URL and error details
await expect(vectorStore.initialize()).rejects.toThrow(
/Failed to connect to Qdrant vector database|vectorStore\.qdrantConnectionFailed/,
)
// The error should have a cause property set to the original error
let caughtError: any
try {
await vectorStore.initialize()
} catch (error: any) {
caughtError = error
}
expect(caughtError).toBeDefined()
expect(caughtError.message).toContain("embeddings:vectorStore.vectorDimensionMismatch")
expect(caughtError.cause).toBe(deleteError)
expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(1)
expect(mockQdrantClientInstance.deleteCollection).toHaveBeenCalledTimes(1)
expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled()
expect(mockQdrantClientInstance.createPayloadIndex).not.toHaveBeenCalled()
// Should log both the warning and the critical error
expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.error).toHaveBeenCalledTimes(2) // One for the critical error, one for the outer catch
;(console.error as any).mockRestore()
;(console.warn as any).mockRestore()
})
it("should throw vectorDimensionMismatch error when createCollection fails during recreation", async () => {
const differentVectorSize = 768
mockQdrantClientInstance.getCollection.mockResolvedValue({
config: {
params: {
vectors: {
size: differentVectorSize,
},
},
},
} as any)
// Delete succeeds but create fails
mockQdrantClientInstance.deleteCollection.mockResolvedValue(true as any)
const createError = new Error("Create Collection Failed")
mockQdrantClientInstance.createCollection.mockRejectedValue(createError)
vitest.spyOn(console, "error").mockImplementation(() => {})
vitest.spyOn(console, "warn").mockImplementation(() => {})
// Should throw an error with cause property set to the original error
let caughtError: any
try {
await vectorStore.initialize()
} catch (error: any) {
caughtError = error
}
expect(caughtError).toBeDefined()
expect(caughtError.message).toContain("embeddings:vectorStore.vectorDimensionMismatch")
expect(caughtError.cause).toBe(createError)
expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(1)
expect(mockQdrantClientInstance.deleteCollection).toHaveBeenCalledTimes(1)
expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1)
expect(mockQdrantClientInstance.createPayloadIndex).not.toHaveBeenCalled()
// Should log warning, critical error, and outer error
expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.error).toHaveBeenCalledTimes(2)
;(console.error as any).mockRestore()
;(console.warn as any).mockRestore()
})

View file

@ -165,17 +165,33 @@ export class QdrantVectorStore implements IVectorStore {
created = false // Exists and correct
} else {
// Exists but wrong vector size, recreate
console.warn(
`[QdrantVectorStore] Collection ${this.collectionName} exists with vector size ${existingVectorSize}, but expected ${this.vectorSize}. Recreating collection.`,
)
await this.client.deleteCollection(this.collectionName) // Known to exist
await this.client.createCollection(this.collectionName, {
vectors: {
size: this.vectorSize,
distance: this.DISTANCE_METRIC,
},
})
created = true
try {
console.warn(
`[QdrantVectorStore] Collection ${this.collectionName} exists with vector size ${existingVectorSize}, but expected ${this.vectorSize}. Recreating collection.`,
)
await this.client.deleteCollection(this.collectionName)
await this.client.createCollection(this.collectionName, {
vectors: {
size: this.vectorSize,
distance: this.DISTANCE_METRIC,
},
})
created = true
} catch (recreationError) {
const errorMessage =
recreationError instanceof Error ? recreationError.message : String(recreationError)
console.error(
`[QdrantVectorStore] CRITICAL: Failed to recreate collection ${this.collectionName} for new vector size. Error: ${errorMessage}`,
)
const dimensionMismatchError = new Error(
t("embeddings:vectorStore.vectorDimensionMismatch", {
errorMessage,
}),
)
// Use error.cause to preserve the original error context
dimensionMismatchError.cause = recreationError
throw dimensionMismatchError
}
}
}
@ -204,7 +220,12 @@ export class QdrantVectorStore implements IVectorStore {
errorMessage,
)
// Provide a more user-friendly error message that includes the original error
// If this is already a vector dimension mismatch error (identified by cause), re-throw it as-is
if (error instanceof Error && error.cause !== undefined) {
throw error
}
// Otherwise, provide a more user-friendly error message that includes the original error
throw new Error(
t("embeddings:vectorStore.qdrantConnectionFailed", { qdrantUrl: this.qdrantUrl, errorMessage }),
)

View file

@ -1166,9 +1166,9 @@ export const ChatRowContent = ({
return <div>Error displaying search results.</div>
}
const { query = "", results = [] } = parsed?.content || {}
const { results = [] } = parsed?.content || {}
return <CodebaseSearchResultsDisplay query={query} results={results} />
return <CodebaseSearchResultsDisplay results={results} />
case "user_edit_todos":
return <UpdateTodoListToolBlock userEdited onChange={() => {}} />
default:

View file

@ -950,18 +950,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onScroll={() => updateHighlights()}
/>
{isTtsPlaying && (
<StandardTooltip content={t("chat:stopTts")}>
<Button
variant="ghost"
size="icon"
className="absolute top-0 right-0 opacity-25 hover:opacity-100 z-10"
onClick={() => vscode.postMessage({ type: "stopTts" })}>
<VolumeX className="size-4" />
</Button>
</StandardTooltip>
)}
<div className="absolute top-1 right-1 z-30">
<StandardTooltip content={t("chat:enhancePrompt")}>
<button
@ -1173,6 +1161,26 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</div>
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
{isTtsPlaying && (
<StandardTooltip content={t("chat:stopTts")}>
<button
aria-label={t("chat:stopTts")}
onClick={() => vscode.postMessage({ type: "stopTts" })}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
)}>
<VolumeX className="w-4 h-4" />
</button>
</StandardTooltip>
)}
<IndexingStatusBadge />
<StandardTooltip content={t("chat:addImages")}>
<button

View file

@ -30,15 +30,15 @@ const CodebaseSearchResult: React.FC<CodebaseSearchResultProps> = ({ filePath, s
<StandardTooltip content={t("codebaseSearch.resultTooltip", { score: score.toFixed(3) })}>
<div
onClick={handleClick}
className="mb-1 p-2 border border-primary rounded cursor-pointer hover:bg-secondary hover:text-white">
className="p-2 border border-[var(--vscode-editorGroup-border)] cursor-pointer hover:bg-secondary hover:text-white">
<div className="flex gap-2 items-center overflow-hidden">
<span className="text-primary-300 whitespace-nowrap flex-shrink-0">
{filePath.split("/").at(-1)}:{startLine}-{endLine}
{filePath.split("/").at(-1)}:{startLine === endLine ? startLine : `${startLine}-${endLine}`}
</span>
<span className="text-gray-500 truncate min-w-0 flex-1">
{filePath.split("/").slice(0, -1).join("/")}
</span>
<span className="text-xs text-vscode-descriptionForeground bg-vscode-badge-background px-2 py-1 rounded whitespace-nowrap ml-auto">
<span className="text-xs text-vscode-descriptionForeground whitespace-nowrap ml-auto opacity-60">
{score.toFixed(3)}
</span>
</div>

View file

@ -3,7 +3,6 @@ import CodebaseSearchResult from "./CodebaseSearchResult"
import { Trans } from "react-i18next"
interface CodebaseSearchResultsDisplayProps {
query: string
results: Array<{
filePath: string
score: number
@ -13,26 +12,26 @@ interface CodebaseSearchResultsDisplayProps {
}>
}
const CodebaseSearchResultsDisplay: React.FC<CodebaseSearchResultsDisplayProps> = ({ query, results }) => {
const CodebaseSearchResultsDisplay: React.FC<CodebaseSearchResultsDisplayProps> = ({ results }) => {
const [codebaseSearchResultsExpanded, setCodebaseSearchResultsExpanded] = useState(false)
return (
<div className="flex flex-col gap-2">
<div className="flex flex-col -mt-4 gap-1">
<div
onClick={() => setCodebaseSearchResultsExpanded(!codebaseSearchResultsExpanded)}
className="font-bold cursor-pointer flex items-center justify-between px-2 py-2 rounded border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)]">
className="cursor-pointer flex items-center justify-between px-2 py-2 border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)]">
<span>
<Trans
i18nKey="chat:codebaseSearch.didSearch"
components={{ code: <code></code> }}
values={{ query, count: results.length }}
count={results.length}
values={{ count: results.length }}
/>
</span>
<span className={`codicon codicon-chevron-${codebaseSearchResultsExpanded ? "up" : "down"}`}></span>
</div>
{codebaseSearchResultsExpanded && (
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
{results.map((result, idx) => (
<CodebaseSearchResult
key={idx}

View file

@ -4,7 +4,7 @@ import { McpTool } from "@roo/mcp"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { vscode } from "@src/utils/vscode"
import { StandardTooltip } from "@/components/ui"
import { StandardTooltip, ToggleSwitch } from "@/components/ui"
type McpToolRowProps = {
tool: McpTool
@ -16,6 +16,8 @@ type McpToolRowProps = {
const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatContext = false }: McpToolRowProps) => {
const { t } = useAppTranslation()
const isToolEnabled = tool.enabledForPrompt ?? true
const handleAlwaysAllowChange = () => {
if (!serverName) return
vscode.postMessage({
@ -46,17 +48,29 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatCo
onClick={(e) => e.stopPropagation()}>
{/* Tool name section */}
<div className="flex items-center min-w-0 flex-1">
<span className="codicon codicon-symbol-method mr-2 flex-shrink-0 text-vscode-symbolIcon-methodForeground"></span>
<span
className={`codicon codicon-symbol-method mr-2 flex-shrink-0 ${
isToolEnabled
? "text-vscode-symbolIcon-methodForeground"
: "text-vscode-descriptionForeground opacity-60"
}`}></span>
<StandardTooltip content={tool.name}>
<span className="font-medium truncate text-vscode-foreground">{tool.name}</span>
<span
className={`font-medium truncate ${
isToolEnabled
? "text-vscode-foreground"
: "text-vscode-descriptionForeground opacity-60"
}`}>
{tool.name}
</span>
</StandardTooltip>
</div>
{/* Controls section */}
{serverName && (
<div className="flex items-center gap-4 flex-shrink-0">
{/* Always Allow checkbox */}
{alwaysAllowMcp && (
{/* Always Allow checkbox - only show when tool is enabled */}
{alwaysAllowMcp && isToolEnabled && (
<VSCodeCheckbox
checked={tool.alwaysAllow}
onChange={handleAlwaysAllowChange}
@ -68,35 +82,31 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatCo
</VSCodeCheckbox>
)}
{/* Enabled eye button - only show in settings context */}
{/* Enabled toggle switch - only show in settings context */}
{!isInChatContext && (
<StandardTooltip content={t("mcp:tool.togglePromptInclusion")}>
<button
role="button"
aria-pressed={tool.enabledForPrompt}
<ToggleSwitch
checked={isToolEnabled}
onChange={handleEnabledForPromptChange}
size="medium"
aria-label={t("mcp:tool.togglePromptInclusion")}
className={`p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors ${
tool.enabledForPrompt
? "text-vscode-foreground"
: "text-vscode-descriptionForeground opacity-60"
}`}
onClick={handleEnabledForPromptChange}
data-tool-prompt-toggle={tool.name}>
<span
className={`codicon ${
tool.enabledForPrompt ? "codicon-eye-closed" : "codicon-eye"
} text-base`}
/>
</button>
data-testid={`tool-prompt-toggle-${tool.name}`}
/>
</StandardTooltip>
)}
</div>
)}
</div>
{tool.description && (
<div className="mt-1 text-xs text-vscode-descriptionForeground opacity-80">{tool.description}</div>
<div
className={`mt-1 text-xs text-vscode-descriptionForeground ${
isToolEnabled ? "opacity-80" : "opacity-40"
}`}>
{tool.description}
</div>
)}
{tool.inputSchema &&
{isToolEnabled &&
tool.inputSchema &&
"properties" in tool.inputSchema &&
Object.keys(tool.inputSchema.properties as Record<string, any>).length > 0 && (
<div className="mt-2 text-xs border border-vscode-panel-border rounded p-2">

View file

@ -22,6 +22,8 @@ import {
DialogTitle,
DialogDescription,
DialogFooter,
ToggleSwitch,
StandardTooltip,
} from "@src/components/ui"
import { buildDocLink } from "@src/utils/docLinks"
@ -123,13 +125,13 @@ const McpView = ({ onDone }: McpViewProps) => {
style={{
marginTop: "10px",
width: "100%",
display: "flex",
flexWrap: "wrap",
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
gap: "10px",
}}>
<Button
variant="secondary"
style={{ flex: "1 1 auto", minWidth: "120px" }}
style={{ width: "100%" }}
onClick={() => {
vscode.postMessage({ type: "openMcpSettings" })
}}>
@ -138,7 +140,7 @@ const McpView = ({ onDone }: McpViewProps) => {
</Button>
<Button
variant="secondary"
style={{ flex: "1 1 auto", minWidth: "120px" }}
style={{ width: "100%" }}
onClick={() => {
vscode.postMessage({ type: "openProjectMcpSettings" })
}}>
@ -147,13 +149,31 @@ const McpView = ({ onDone }: McpViewProps) => {
</Button>
<Button
variant="secondary"
style={{ flex: "1 1 auto", minWidth: "120px" }}
style={{ width: "100%" }}
onClick={() => {
vscode.postMessage({ type: "refreshAllMcpServers" })
}}>
<span className="codicon codicon-refresh" style={{ marginRight: "6px" }}></span>
{t("mcp:refreshMCP")}
</Button>
<StandardTooltip content={t("mcp:marketplace")}>
<Button
variant="secondary"
style={{ width: "100%" }}
onClick={() => {
window.postMessage(
{
type: "action",
action: "marketplaceButtonClicked",
values: { marketplaceTab: "mcp" },
},
"*",
)
}}>
<span className="codicon codicon-extensions" style={{ marginRight: "6px" }}></span>
{t("mcp:marketplace")}
</Button>
</StandardTooltip>
</div>
<div
style={{
@ -295,54 +315,6 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
style={{ marginRight: "8px" }}>
<span className="codicon codicon-refresh" style={{ fontSize: "14px" }}></span>
</Button>
<div
role="switch"
aria-checked={!server.disabled}
tabIndex={0}
style={{
width: "20px",
height: "10px",
backgroundColor: server.disabled
? "var(--vscode-titleBar-inactiveForeground)"
: "var(--vscode-button-background)",
borderRadius: "5px",
position: "relative",
cursor: "pointer",
transition: "background-color 0.2s",
opacity: server.disabled ? 0.4 : 0.8,
}}
onClick={() => {
vscode.postMessage({
type: "toggleMcpServer",
serverName: server.name,
source: server.source || "global",
disabled: !server.disabled,
})
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
vscode.postMessage({
type: "toggleMcpServer",
serverName: server.name,
source: server.source || "global",
disabled: !server.disabled,
})
}
}}>
<div
style={{
width: "6px",
height: "6px",
backgroundColor: "var(--vscode-titleBar-activeForeground)",
borderRadius: "50%",
position: "absolute",
top: "2px",
left: server.disabled ? "2px" : "12px",
transition: "left 0.2s",
}}
/>
</div>
</div>
<div
style={{
@ -353,6 +325,21 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
marginLeft: "8px",
}}
/>
<div style={{ marginLeft: "8px" }}>
<ToggleSwitch
checked={!server.disabled}
onChange={() => {
vscode.postMessage({
type: "toggleMcpServer",
serverName: server.name,
source: server.source || "global",
disabled: !server.disabled,
})
}}
size="medium"
aria-label={`Toggle ${server.name} server`}
/>
</div>
</div>
{server.status === "connected" ? (

View file

@ -144,40 +144,40 @@ describe("McpToolRow", () => {
expect(screen.getByText("Second parameter")).toBeInTheDocument()
})
it("shows eye button when serverName is provided and not in chat context", () => {
it("shows toggle switch when serverName is provided and not in chat context", () => {
render(<McpToolRow tool={mockTool} serverName="test-server" />)
const eyeButton = screen.getByRole("button", { name: "Toggle prompt inclusion" })
expect(eyeButton).toBeInTheDocument()
const toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" })
expect(toggleSwitch).toBeInTheDocument()
})
it("hides eye button when isInChatContext is true", () => {
it("hides toggle switch when isInChatContext is true", () => {
render(<McpToolRow tool={mockTool} serverName="test-server" isInChatContext={true} />)
const eyeButton = screen.queryByRole("button", { name: "Toggle prompt inclusion" })
expect(eyeButton).not.toBeInTheDocument()
const toggleSwitch = screen.queryByRole("switch", { name: "Toggle prompt inclusion" })
expect(toggleSwitch).not.toBeInTheDocument()
})
it("shows correct eye icon based on enabledForPrompt state", () => {
// Test when enabled (should show eye-closed icon)
it("shows correct toggle switch state based on enabledForPrompt", () => {
// Test when enabled (should be checked)
const { rerender } = render(<McpToolRow tool={mockTool} serverName="test-server" />)
let eyeIcon = screen.getByRole("button", { name: "Toggle prompt inclusion" }).querySelector("span")
expect(eyeIcon).toHaveClass("codicon-eye-closed")
let toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" })
expect(toggleSwitch).toHaveAttribute("aria-checked", "true")
// Test when disabled (should show eye icon)
// Test when disabled (should not be checked)
const disabledTool = { ...mockTool, enabledForPrompt: false }
rerender(<McpToolRow tool={disabledTool} serverName="test-server" />)
eyeIcon = screen.getByRole("button", { name: "Toggle prompt inclusion" }).querySelector("span")
expect(eyeIcon).toHaveClass("codicon-eye")
toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" })
expect(toggleSwitch).toHaveAttribute("aria-checked", "false")
})
it("sends message to toggle enabledForPrompt when eye button is clicked", () => {
it("sends message to toggle enabledForPrompt when toggle switch is clicked", () => {
render(<McpToolRow tool={mockTool} serverName="test-server" />)
const eyeButton = screen.getByRole("button", { name: "Toggle prompt inclusion" })
fireEvent.click(eyeButton)
const toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" })
fireEvent.click(toggleSwitch)
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "toggleToolEnabledForPrompt",
@ -187,4 +187,102 @@ describe("McpToolRow", () => {
isEnabled: false,
})
})
it("hides always allow checkbox when tool is disabled", () => {
const disabledTool = { ...mockTool, enabledForPrompt: false }
render(<McpToolRow tool={disabledTool} serverName="test-server" alwaysAllowMcp={true} />)
expect(screen.queryByText("Always allow")).not.toBeInTheDocument()
})
it("shows always allow checkbox when tool is enabled", () => {
const enabledTool = { ...mockTool, enabledForPrompt: true }
render(<McpToolRow tool={enabledTool} serverName="test-server" alwaysAllowMcp={true} />)
expect(screen.getByText("Always allow")).toBeInTheDocument()
})
it("hides parameters section when tool is disabled", () => {
const disabledToolWithSchema = {
...mockTool,
enabledForPrompt: false,
inputSchema: {
type: "object",
properties: {
param1: {
type: "string",
description: "First parameter",
},
},
required: ["param1"],
},
}
render(<McpToolRow tool={disabledToolWithSchema} serverName="test-server" />)
expect(screen.queryByText("Parameters")).not.toBeInTheDocument()
expect(screen.queryByText("param1")).not.toBeInTheDocument()
expect(screen.queryByText("First parameter")).not.toBeInTheDocument()
})
it("shows parameters section when tool is enabled", () => {
const enabledToolWithSchema = {
...mockTool,
enabledForPrompt: true,
inputSchema: {
type: "object",
properties: {
param1: {
type: "string",
description: "First parameter",
},
},
required: ["param1"],
},
}
render(<McpToolRow tool={enabledToolWithSchema} serverName="test-server" />)
expect(screen.getByText("Parameters")).toBeInTheDocument()
expect(screen.getByText("param1")).toBeInTheDocument()
expect(screen.getByText("First parameter")).toBeInTheDocument()
})
it("grays out tool name and description when tool is disabled", () => {
const disabledTool = {
...mockTool,
enabledForPrompt: false,
description: "A disabled tool",
}
render(<McpToolRow tool={disabledTool} serverName="test-server" />)
const toolName = screen.getByText("test-tool")
const toolDescription = screen.getByText("A disabled tool")
// Check that the tool name has the grayed out classes
expect(toolName).toHaveClass("text-vscode-descriptionForeground", "opacity-60")
// Check that the description has reduced opacity
expect(toolDescription).toHaveClass("opacity-40")
})
it("shows normal styling for tool name and description when tool is enabled", () => {
const enabledTool = {
...mockTool,
enabledForPrompt: true,
description: "An enabled tool",
}
render(<McpToolRow tool={enabledTool} serverName="test-server" />)
const toolName = screen.getByText("test-tool")
const toolDescription = screen.getByText("An enabled tool")
// Check that the tool name has normal styling
expect(toolName).toHaveClass("text-vscode-foreground")
expect(toolName).not.toHaveClass("text-vscode-descriptionForeground", "opacity-60")
// Check that the description has normal opacity
expect(toolDescription).toHaveClass("opacity-80")
expect(toolDescription).not.toHaveClass("opacity-40")
})
})

View file

@ -561,6 +561,23 @@ const ModesView = ({ onDone }: ModesViewProps) => {
</div>
)}
</div>
<StandardTooltip content={t("chat:modeSelector.marketplace")}>
<Button
variant="ghost"
size="icon"
onClick={() => {
window.postMessage(
{
type: "action",
action: "marketplaceButtonClicked",
values: { marketplaceTab: "mode" },
},
"*",
)
}}>
<span className="codicon codicon-extensions"></span>
</Button>
</StandardTooltip>
</div>
</div>

View file

@ -0,0 +1,101 @@
import React from "react"
import { render, fireEvent, screen } from "@/utils/test-utils"
import { ToggleSwitch } from "../toggle-switch"
describe("ToggleSwitch", () => {
it("renders with correct initial state", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={true} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
expect(toggle).toBeInTheDocument()
expect(toggle).toHaveAttribute("aria-checked", "true")
expect(toggle).toHaveAttribute("aria-label", "Test toggle")
})
it("renders unchecked state correctly", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
expect(toggle).toHaveAttribute("aria-checked", "false")
})
it("calls onChange when clicked", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
fireEvent.click(toggle)
expect(onChange).toHaveBeenCalledTimes(1)
})
it("calls onChange when Enter key is pressed", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
fireEvent.keyDown(toggle, { key: "Enter" })
expect(onChange).toHaveBeenCalledTimes(1)
})
it("calls onChange when Space key is pressed", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
fireEvent.keyDown(toggle, { key: " " })
expect(onChange).toHaveBeenCalledTimes(1)
})
it("does not call onChange when disabled", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} disabled={true} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
fireEvent.click(toggle)
fireEvent.keyDown(toggle, { key: "Enter" })
expect(onChange).not.toHaveBeenCalled()
})
it("has correct tabIndex when disabled", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} disabled={true} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
expect(toggle).toHaveAttribute("tabindex", "-1")
})
it("renders with custom data-testid", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} data-testid="custom-toggle" />)
const toggle = screen.getByTestId("custom-toggle")
expect(toggle).toBeInTheDocument()
})
it("supports medium size", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} size="medium" aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
expect(toggle).toBeInTheDocument()
// Medium size should be 20px x 10px
expect(toggle).toHaveStyle({ width: "20px", height: "10px" })
})
it("defaults to small size", () => {
const onChange = vi.fn()
render(<ToggleSwitch checked={false} onChange={onChange} aria-label="Test toggle" />)
const toggle = screen.getByRole("switch")
expect(toggle).toBeInTheDocument()
// Small size should be 16px x 8px
expect(toggle).toHaveStyle({ width: "16px", height: "8px" })
})
})

View file

@ -18,3 +18,4 @@ export * from "./select"
export * from "./textarea"
export * from "./tooltip"
export * from "./standard-tooltip"
export * from "./toggle-switch"

View file

@ -0,0 +1,68 @@
import React from "react"
export interface ToggleSwitchProps {
checked: boolean
onChange: () => void
disabled?: boolean
size?: "small" | "medium"
"aria-label"?: string
"data-testid"?: string
}
export const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
checked,
onChange,
disabled = false,
size = "small",
"aria-label": ariaLabel,
"data-testid": dataTestId,
}) => {
const dimensions = size === "small" ? { width: 16, height: 8, dotSize: 4 } : { width: 20, height: 10, dotSize: 6 }
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
if (!disabled) {
onChange()
}
}
}
return (
<div
role="switch"
aria-checked={checked}
aria-label={ariaLabel}
tabIndex={disabled ? -1 : 0}
data-testid={dataTestId}
style={{
width: `${dimensions.width}px`,
height: `${dimensions.height}px`,
backgroundColor: checked
? "var(--vscode-button-background)"
: "var(--vscode-titleBar-inactiveForeground)",
borderRadius: `${dimensions.height / 2}px`,
position: "relative",
cursor: disabled ? "not-allowed" : "pointer",
transition: "background-color 0.2s",
opacity: disabled ? 0.4 : checked ? 0.8 : 0.6,
}}
onClick={disabled ? undefined : onChange}
onKeyDown={handleKeyDown}>
<div
style={{
width: `${dimensions.dotSize}px`,
height: `${dimensions.dotSize}px`,
backgroundColor: "var(--vscode-titleBar-activeForeground)",
borderRadius: "50%",
position: "absolute",
top: `${(dimensions.height - dimensions.dotSize) / 2}px`,
left: checked
? `${dimensions.width - dimensions.dotSize - (dimensions.height - dimensions.dotSize) / 2}px`
: `${(dimensions.height - dimensions.dotSize) / 2}px`,
transition: "left 0.2s",
}}
/>
</div>
)
}

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo vol cercar a la base de codi <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo vol cercar a la base de codi <code>{{query}}</code> a <code>{{path}}</code>:",
"didSearch": "S'han trobat {{count}} resultat(s) per a <code>{{query}}</code>:",
"didSearch_one": "S'ha trobat 1 resultat",
"didSearch_other": "S'han trobat {{count}} resultats",
"resultTooltip": "Puntuació de similitud: {{score}} (fes clic per obrir el fitxer)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Servidors MCP",
"done": "Fet",
"marketplace": "Mercat de MCP",
"description": "El <0>Model Context Protocol</0> permet la comunicació amb servidors MCP que s'executen localment i proporcionen eines i recursos addicionals per ampliar les capacitats de Roo. Pots utilitzar <1>servidors creats per la comunitat</1> o demanar a Roo que creï noves eines específiques per al teu flux de treball (per exemple, \"afegir una eina que obtingui la documentació més recent de npm\").",
"instructions": "Instruccions",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo möchte den Codebase nach <code>{{query}}</code> durchsuchen:",
"wantsToSearchWithPath": "Roo möchte den Codebase nach <code>{{query}}</code> in <code>{{path}}</code> durchsuchen:",
"didSearch": "{{count}} Ergebnis(se) für <code>{{query}}</code> gefunden:",
"didSearch_one": "1 Ergebnis gefunden",
"didSearch_other": "{{count}} Ergebnisse gefunden",
"resultTooltip": "Ähnlichkeitswert: {{score}} (klicken zum Öffnen der Datei)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP-Server",
"done": "Fertig",
"marketplace": "MCP-Marktplatz",
"description": "Das <0>Model Context Protocol</0> ermöglicht die Kommunikation mit lokal laufenden MCP-Servern, die zusätzliche Tools und Ressourcen zur Erweiterung der Fähigkeiten von Roo bereitstellen. Du kannst <1>von der Community erstellte Server</1> verwenden oder Roo bitten, neue Tools speziell für deinen Workflow zu erstellen (z.B. \"ein Tool hinzufügen, das die neueste npm-Dokumentation abruft\").",
"instructions": "Anweisungen",
"enableToggle": {

View file

@ -202,7 +202,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo wants to search the codebase for <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo wants to search the codebase for <code>{{query}}</code> in <code>{{path}}</code>:",
"didSearch": "Found {{count}} result(s) for <code>{{query}}</code>:",
"didSearch_one": "Found 1 result",
"didSearch_other": "Found {{count}} results",
"resultTooltip": "Similarity score: {{score}} (click to open file)"
},
"commandOutput": "Command Output",

View file

@ -1,6 +1,7 @@
{
"title": "MCP Servers",
"done": "Done",
"marketplace": "MCP Marketplace",
"description": "The <0>Model Context Protocol</0> enables communication with locally running MCP servers that provide additional tools and resources to extend Roo's capabilities. You can use <1>community-made servers</1> or ask Roo to create new tools specific to your workflow (e.g., \"add a tool that gets the latest npm docs\").",
"instructions": "Instructions",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo quiere buscar en la base de código <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo quiere buscar en la base de código <code>{{query}}</code> en <code>{{path}}</code>:",
"didSearch": "Se encontraron {{count}} resultado(s) para <code>{{query}}</code>:",
"didSearch_one": "Se encontró 1 resultado",
"didSearch_other": "Se encontraron {{count}} resultados",
"resultTooltip": "Puntuación de similitud: {{score}} (haz clic para abrir el archivo)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Servidores MCP",
"done": "Listo",
"marketplace": "Mercado MCP",
"description": "El <0>Model Context Protocol</0> permite la comunicación con servidores MCP que se ejecutan localmente y proporcionan herramientas y recursos adicionales para extender las capacidades de Roo. Puedes usar <1>servidores creados por la comunidad</1> o pedir a Roo que cree nuevas herramientas específicas para tu flujo de trabajo (por ejemplo, \"añadir una herramienta que obtenga la documentación más reciente de npm\").",
"instructions": "Instrucciones",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo veut rechercher dans la base de code <code>{{query}}</code> :",
"wantsToSearchWithPath": "Roo veut rechercher dans la base de code <code>{{query}}</code> dans <code>{{path}}</code> :",
"didSearch": "{{count}} résultat(s) trouvé(s) pour <code>{{query}}</code> :",
"didSearch_one": "1 résultat trouvé",
"didSearch_other": "{{count}} résultats trouvés",
"resultTooltip": "Score de similarité : {{score}} (cliquer pour ouvrir le fichier)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Serveurs MCP",
"done": "Terminé",
"marketplace": "Marché MCP",
"description": "Le <0>Model Context Protocol</0> permet la communication avec des serveurs MCP exécutés localement qui fournissent des outils et des ressources supplémentaires pour étendre les capacités de Roo. Vous pouvez utiliser <1>des serveurs créés par la communauté</1> ou demander à Roo de créer de nouveaux outils spécifiques à votre flux de travail (par exemple, \"ajouter un outil qui récupère la dernière documentation npm\").",
"instructions": "Instructions",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo कोडबेस में <code>{{query}}</code> खोजना चाहता है:",
"wantsToSearchWithPath": "Roo <code>{{path}}</code> में कोडबेस में <code>{{query}}</code> खोजना चाहता है:",
"didSearch": "<code>{{query}}</code> के लिए {{count}} परिणाम मिले:",
"didSearch_one": "1 परिणाम मिला",
"didSearch_other": "{{count}} परिणाम मिले",
"resultTooltip": "समानता स्कोर: {{score}} (फ़ाइल खोलने के लिए क्लिक करें)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP सर्वर",
"done": "हो गया",
"marketplace": "MCP मार्केटप्लेस",
"description": "<0>मॉडल कॉन्टेक्स्ट प्रोटोकॉल</0> स्थानीय रूप से चल रहे MCP सर्वरों के साथ संचार को सक्षम बनाता है जो Roo की क्षमताओं का विस्तार करने के लिए अतिरिक्त उपकरण और संसाधन प्रदान करते हैं। आप <1>समुदाय द्वारा बनाए गए सर्वरों</1> का उपयोग कर सकते हैं या Roo से अपने कार्यप्रवाह के लिए विशिष्ट नए उपकरण बनाने के लिए कह सकते हैं (जैसे, \"नवीनतम npm दस्तावेज़ प्राप्त करने वाला उपकरण जोड़ें\")।",
"instructions": "निर्देश",
"enableToggle": {

View file

@ -208,7 +208,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo ingin mencari codebase untuk <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo ingin mencari codebase untuk <code>{{query}}</code> di <code>{{path}}</code>:",
"didSearch": "Ditemukan {{count}} hasil untuk <code>{{query}}</code>:",
"didSearch_one": "Ditemukan 1 hasil",
"didSearch_other": "Ditemukan {{count}} hasil",
"resultTooltip": "Skor kemiripan: {{score}} (klik untuk membuka file)"
},
"commandOutput": "Output Perintah",

View file

@ -1,6 +1,7 @@
{
"title": "Server MCP",
"done": "Selesai",
"marketplace": "Marketplace MCP",
"description": "<0>Model Context Protocol</0> memungkinkan komunikasi dengan server MCP yang berjalan secara lokal yang menyediakan tools dan resources tambahan untuk memperluas kemampuan Roo Code. Anda dapat menggunakan <1>server buatan komunitas</1> atau meminta Roo membuat tools baru yang spesifik untuk workflow Anda (misalnya, \"tambahkan tool yang mendapatkan dokumentasi npm terbaru\").",
"instructions": "Instruksi",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo vuole cercare nella base di codice <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo vuole cercare nella base di codice <code>{{query}}</code> in <code>{{path}}</code>:",
"didSearch": "Trovato {{count}} risultato/i per <code>{{query}}</code>:",
"didSearch_one": "Trovato 1 risultato",
"didSearch_other": "Trovati {{count}} risultati",
"resultTooltip": "Punteggio di somiglianza: {{score}} (clicca per aprire il file)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Server MCP",
"done": "Fatto",
"marketplace": "Marketplace MCP",
"description": "Il <0>Model Context Protocol</0> permette la comunicazione con server MCP in esecuzione locale che forniscono strumenti e risorse aggiuntive per estendere le capacità di Roo. Puoi utilizzare <1>server creati dalla comunità</1> o chiedere a Roo di creare nuovi strumenti specifici per il tuo flusso di lavoro (ad esempio, \"aggiungi uno strumento che ottiene la documentazione npm più recente\").",
"instructions": "Istruzioni",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Rooはコードベースで <code>{{query}}</code> を検索したい:",
"wantsToSearchWithPath": "Rooは <code>{{path}}</code> 内のコードベースで <code>{{query}}</code> を検索したい:",
"didSearch": "<code>{{query}}</code> の検索結果: {{count}} 件",
"didSearch_one": "1件の結果が見つかりました",
"didSearch_other": "{{count}}件の結果が見つかりました",
"resultTooltip": "類似度スコア: {{score}} (クリックしてファイルを開く)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCPサーバー",
"done": "完了",
"marketplace": "MCPマーケットプレイス",
"description": "<0>Model Context Protocol</0>は、ローカルで実行されているMCPサーバーとの通信を可能にし、Rooの機能を拡張するための追加ツールやリソースを提供します。<1>コミュニティによって作成されたサーバー</1>を使用したり、Rooにワークフロー専用の新しいツールを作成するよう依頼したりできます「最新のnpmドキュメントを取得するツールを追加する」。",
"instructions": "手順",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo가 코드베이스에서 <code>{{query}}</code>을(를) 검색하고 싶어합니다:",
"wantsToSearchWithPath": "Roo가 <code>{{path}}</code>에서 <code>{{query}}</code>을(를) 검색하고 싶어합니다:",
"didSearch": "<code>{{query}}</code>에 대한 검색 결과 {{count}}개 찾음:",
"didSearch_one": "1개의 결과를 찾았습니다",
"didSearch_other": "{{count}}개의 결과를 찾았습니다",
"resultTooltip": "유사도 점수: {{score}} (클릭하여 파일 열기)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP 서버",
"done": "완료",
"marketplace": "MCP 마켓플레이스",
"description": "<0>Model Context Protocol</0>은 로컬에서 실행되는 MCP 서버와 통신하여 Roo의 기능을 확장하는 추가 도구 및 리소스를 제공합니다. <1>커뮤니티에서 만든 서버</1>를 사용하거나 Roo에게 작업 흐름에 맞는 새로운 도구를 만들도록 요청할 수 있습니다 (예: \"최신 npm 문서를 가져오는 도구 추가\").",
"instructions": "지침",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo wil de codebase doorzoeken op <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo wil de codebase doorzoeken op <code>{{query}}</code> in <code>{{path}}</code>:",
"didSearch": "{{count}} resultaat/resultaten gevonden voor <code>{{query}}</code>:",
"didSearch_one": "1 resultaat gevonden",
"didSearch_other": "{{count}} resultaten gevonden",
"resultTooltip": "Gelijkenisscore: {{score}} (klik om bestand te openen)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP-servers",
"done": "Gereed",
"marketplace": "MCP-marktplaats",
"description": "Het <0>Model Context Protocol</0> maakt communicatie mogelijk met lokaal draaiende MCP-servers die extra tools en bronnen bieden om Roo's mogelijkheden uit te breiden. Je kunt <1>community-servers</1> gebruiken of Roo vragen om nieuwe tools te maken die specifiek zijn voor jouw workflow (bijv. 'voeg een tool toe die de nieuwste npm-documentatie ophaalt').",
"instructions": "Instructies",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo chce przeszukać bazę kodu w poszukiwaniu <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo chce przeszukać bazę kodu w poszukiwaniu <code>{{query}}</code> w <code>{{path}}</code>:",
"didSearch": "Znaleziono {{count}} wynik(ów) dla <code>{{query}}</code>:",
"didSearch_one": "Znaleziono 1 wynik",
"didSearch_other": "Znaleziono {{count}} wyników",
"resultTooltip": "Wynik podobieństwa: {{score}} (kliknij, aby otworzyć plik)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Serwery MCP",
"done": "Gotowe",
"marketplace": "Marketplace MCP",
"description": "<0>Model Context Protocol</0> umożliwia komunikację z lokalnie uruchomionymi serwerami MCP, które zapewniają dodatkowe narzędzia i zasoby rozszerzające możliwości Roo. Możesz korzystać z <1>serwerów stworzonych przez społeczność</1> lub poprosić Roo o utworzenie nowych narzędzi specyficznych dla twojego przepływu pracy (np. \"dodaj narzędzie, które pobiera najnowszą dokumentację npm\").",
"instructions": "Instrukcje",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo quer pesquisar na base de código por <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo quer pesquisar na base de código por <code>{{query}}</code> em <code>{{path}}</code>:",
"didSearch": "Encontrado {{count}} resultado(s) para <code>{{query}}</code>:",
"didSearch_one": "Encontrado 1 resultado",
"didSearch_other": "Encontrados {{count}} resultados",
"resultTooltip": "Pontuação de similaridade: {{score}} (clique para abrir o arquivo)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Servidores MCP",
"done": "Concluído",
"marketplace": "Marketplace MCP",
"description": "O <0>Model Context Protocol</0> permite a comunicação com servidores MCP em execução localmente que fornecem ferramentas e recursos adicionais para estender as capacidades do Roo. Você pode usar <1>servidores criados pela comunidade</1> ou pedir ao Roo para criar novas ferramentas específicas para seu fluxo de trabalho (por exemplo, \"adicionar uma ferramenta que obtém a documentação mais recente do npm\").",
"instructions": "Instruções",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo хочет выполнить поиск в кодовой базе по <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo хочет выполнить поиск в кодовой базе по <code>{{query}}</code> в <code>{{path}}</code>:",
"didSearch": "Найдено {{count}} результат(ов) для <code>{{query}}</code>:",
"didSearch_one": "Найден 1 результат",
"didSearch_other": "Найдено {{count}} результатов",
"resultTooltip": "Оценка схожести: {{score}} (нажмите, чтобы открыть файл)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Серверы MCP",
"done": "Готово",
"marketplace": "Маркетплейс MCP",
"description": "<0>Model Context Protocol</0> обеспечивает связь с локально запущенными MCP-серверами, которые предоставляют дополнительные инструменты и ресурсы для расширения возможностей Roo. Вы можете использовать <1>серверы, созданные сообществом</1>, или попросить Roo создать новые инструменты, специфичные для вашего рабочего процесса (например, «добавь инструмент, который получает последние npm-документацию»).",
"instructions": "Инструкции",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo kod tabanında <code>{{query}}</code> aramak istiyor:",
"wantsToSearchWithPath": "Roo <code>{{path}}</code> içinde kod tabanında <code>{{query}}</code> aramak istiyor:",
"didSearch": "<code>{{query}}</code> için {{count}} sonuç bulundu:",
"didSearch_one": "1 sonuç bulundu",
"didSearch_other": "{{count}} sonuç bulundu",
"resultTooltip": "Benzerlik puanı: {{score}} (dosyayı açmak için tıklayın)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP Sunucuları",
"done": "Tamam",
"marketplace": "MCP Pazaryeri",
"description": "<0>Model Context Protocol</0>, Roo'nun yeteneklerini genişletmek için ek araçlar ve kaynaklar sağlayan yerel olarak çalışan MCP sunucularıyla iletişim kurmanızı sağlar. <1>Topluluk tarafından oluşturulan sunucuları</1> kullanabilir veya Roo'dan iş akışınıza özel yeni araçlar oluşturmasını isteyebilirsiniz (örneğin, \"en son npm belgelerini alan bir araç ekle\").",
"instructions": "Talimatlar",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo muốn tìm kiếm trong cơ sở mã cho <code>{{query}}</code>:",
"wantsToSearchWithPath": "Roo muốn tìm kiếm trong cơ sở mã cho <code>{{query}}</code> trong <code>{{path}}</code>:",
"didSearch": "Đã tìm thấy {{count}} kết quả cho <code>{{query}}</code>:",
"didSearch_one": "Đã tìm thấy 1 kết quả",
"didSearch_other": "Đã tìm thấy {{count}} kết quả",
"resultTooltip": "Điểm tương tự: {{score}} (nhấp để mở tệp)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "Máy chủ MCP",
"done": "Hoàn thành",
"marketplace": "Chợ MCP",
"description": "<0>Giao thức ngữ cảnh mô hình</0> cho phép giao tiếp với các máy chủ MCP đang chạy cục bộ, cung cấp các công cụ và tài nguyên bổ sung để mở rộng khả năng của Roo. Bạn có thể sử dụng <1>các máy chủ do cộng đồng tạo</1> hoặc yêu cầu Roo tạo các công cụ mới dành riêng cho quy trình làm việc của bạn (ví dụ: \"thêm công cụ lấy tài liệu npm mới nhất\").",
"instructions": "Hướng dẫn",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo 需要搜索代码库: <code>{{query}}</code>",
"wantsToSearchWithPath": "Roo 需要在 <code>{{path}}</code> 中搜索: <code>{{query}}</code>",
"didSearch": "找到 {{count}} 个结果: <code>{{query}}</code>",
"didSearch_one": "找到 1 个结果",
"didSearch_other": "找到 {{count}} 个结果",
"resultTooltip": "相似度评分: {{score}} (点击打开文件)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP 服务器",
"done": "完成",
"marketplace": "MCP 市场",
"description": "<0>Model Context Protocol</0> 支持与本地MCP服务通信提供扩展功能。您可以使用<1>社区服务器</1>,或通过指令创建定制工具(例如:\"新增获取最新npm文档的工具\")。",
"instructions": "使用说明",
"enableToggle": {

View file

@ -297,7 +297,8 @@
"codebaseSearch": {
"wantsToSearch": "Roo 想要搜尋程式碼庫:<code>{{query}}</code>",
"wantsToSearchWithPath": "Roo 想要在 <code>{{path}}</code> 中搜尋:<code>{{query}}</code>",
"didSearch": "找到 {{count}} 個結果:<code>{{query}}</code>",
"didSearch_one": "找到 1 個結果",
"didSearch_other": "找到 {{count}} 個結果",
"resultTooltip": "相似度評分:{{score}} (點擊開啟檔案)"
},
"read-batch": {

View file

@ -1,6 +1,7 @@
{
"title": "MCP 伺服器",
"done": "完成",
"marketplace": "MCP 市集",
"description": "<0>Model Context Protocol</0> 能與本機執行的 MCP 伺服器通訊,提供額外的工具和資源來擴展 Roo 的功能。您可以使用<1>社群開發的伺服器</1>,或請 Roo 為您的工作流程建立新工具(例如「新增一個取得最新 npm 文件的工具」)。",
"instructions": "使用說明",
"enableToggle": {

View file

@ -28,7 +28,7 @@ class VSCodeAPIWrapper {
* @remarks When running webview code inside a web browser, postMessage will instead
* log the given message to the console.
*
* @param message Abitrary data (must be JSON serializable) to send to the extension context.
* @param message Arbitrary data (must be JSON serializable) to send to the extension context.
*/
public postMessage(message: WebviewMessage) {
if (this.vsCodeApi) {