diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index f5e4e4c985..dd166600d6 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -13,6 +13,7 @@ import { import type { ApiHandlerOptions } from "../../shared/api" import { XmlMatcher } from "../../utils/xml-matcher" +import { extractApiVersionFromUrl } from "../../utils/azure-url-parser" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" @@ -35,12 +36,24 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl super() this.options = options - const baseURL = this.options.openAiBaseUrl ?? "https://api.openai.com/v1" + const originalBaseURL = this.options.openAiBaseUrl ?? "https://api.openai.com/v1" const apiKey = this.options.openAiApiKey ?? "not-provided" const isAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) const urlHost = this._getUrlHost(this.options.openAiBaseUrl) const isAzureOpenAi = urlHost === "azure.com" || urlHost.endsWith(".azure.com") || options.openAiUseAzure + // Extract API version from URL if present and no explicit azureApiVersion is set + let effectiveApiVersion = this.options.azureApiVersion + let baseURL = originalBaseURL + + // Extract version for both Azure OpenAI and Azure AI Inference + if ((isAzureOpenAi || isAzureAiInference) && !effectiveApiVersion) { + const extractedVersion = extractApiVersionFromUrl(originalBaseURL) + if (extractedVersion) { + effectiveApiVersion = extractedVersion + } + } + const headers = { ...DEFAULT_HEADERS, ...(this.options.openAiHeaders || {}), @@ -49,23 +62,23 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl if (isAzureAiInference) { // Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure this.client = new OpenAI({ - baseURL, + baseURL: originalBaseURL, // Keep original URL for AI Inference apiKey, defaultHeaders: headers, - defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" }, + defaultQuery: { "api-version": effectiveApiVersion || "2024-05-01-preview" }, }) } else if (isAzureOpenAi) { // Azure API shape slightly differs from the core API shape: // https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai this.client = new AzureOpenAI({ - baseURL, + baseURL: originalBaseURL, // Use original URL to maintain exact same behavior apiKey, - apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion, + apiVersion: effectiveApiVersion || azureOpenAiDefaultApiVersion, defaultHeaders: headers, }) } else { this.client = new OpenAI({ - baseURL, + baseURL: originalBaseURL, apiKey, defaultHeaders: headers, }) diff --git a/src/utils/__tests__/azure-url-parser.test.ts b/src/utils/__tests__/azure-url-parser.test.ts new file mode 100644 index 0000000000..44151c53b3 --- /dev/null +++ b/src/utils/__tests__/azure-url-parser.test.ts @@ -0,0 +1,190 @@ +import { describe, it, expect } from "vitest" +import { + extractApiVersionFromUrl, + isAzureOpenAiUrl, + removeApiVersionFromUrl, + isValidAzureApiVersion, +} from "../azure-url-parser" + +describe("azure-url-parser", () => { + describe("isValidAzureApiVersion", () => { + it("should return true for valid API version format YYYY-MM-DD", () => { + expect(isValidAzureApiVersion("2024-05-01")).toBe(true) + expect(isValidAzureApiVersion("2023-12-31")).toBe(true) + }) + + it("should return true for valid API version format YYYY-MM-DD-preview", () => { + expect(isValidAzureApiVersion("2024-05-01-preview")).toBe(true) + expect(isValidAzureApiVersion("2024-12-01-preview")).toBe(true) + }) + + it("should return false for invalid API version formats", () => { + expect(isValidAzureApiVersion("2024-5-1")).toBe(false) // Missing leading zeros + expect(isValidAzureApiVersion("24-05-01")).toBe(false) // Two-digit year + expect(isValidAzureApiVersion("2024/05/01")).toBe(false) // Wrong separator + expect(isValidAzureApiVersion("2024-05-01-alpha")).toBe(false) // Wrong suffix + expect(isValidAzureApiVersion("invalid-version")).toBe(false) + expect(isValidAzureApiVersion("")).toBe(false) + }) + }) + + describe("extractApiVersionFromUrl", () => { + it("should extract API version from Azure OpenAI URL", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview" + const result = extractApiVersionFromUrl(url) + expect(result).toBe("2024-05-01-preview") + }) + + it("should extract API version from URL with multiple query parameters", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&api-version=2024-12-01-preview&baz=qux" + const result = extractApiVersionFromUrl(url) + expect(result).toBe("2024-12-01-preview") + }) + + it("should return null when no api-version parameter exists", () => { + const url = "https://api.openai.com/v1/chat/completions" + const result = extractApiVersionFromUrl(url) + expect(result).toBeNull() + }) + + it("should return null for invalid URLs", () => { + const invalidUrl = "not-a-valid-url" + const result = extractApiVersionFromUrl(invalidUrl) + expect(result).toBeNull() + }) + + it("should handle empty api-version parameter", () => { + const url = "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=" + const result = extractApiVersionFromUrl(url) + expect(result).toBe("") + }) + + it("should handle URL without query parameters", () => { + const url = "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions" + const result = extractApiVersionFromUrl(url) + expect(result).toBeNull() + }) + + it("should handle URL with duplicate api-version parameters", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01&api-version=2024-12-01" + const result = extractApiVersionFromUrl(url) + // URL.searchParams.get returns the first value + expect(result).toBe("2024-05-01") + }) + + it("should handle URL with malformed api-version parameter", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=invalid-format" + const result = extractApiVersionFromUrl(url) + expect(result).toBe("invalid-format") // Still extracts it, validation is separate + }) + }) + + describe("isAzureOpenAiUrl", () => { + it("should return true for Azure OpenAI URLs with .openai.azure.com", () => { + const url = "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(true) + }) + + it("should return true for Azure URLs ending with .azure.com", () => { + const url = "https://myservice.azure.com/api/v1" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(true) + }) + + it("should return true for URLs with /openai/deployments/ path", () => { + const url = "https://custom-domain.com/openai/deployments/mymodel/chat/completions" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(true) + }) + + it("should return false for regular OpenAI URLs", () => { + const url = "https://api.openai.com/v1/chat/completions" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(false) + }) + + it("should return false for other API URLs", () => { + const url = "https://api.anthropic.com/v1/messages" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(false) + }) + + it("should return false for invalid URLs", () => { + const invalidUrl = "not-a-valid-url" + const result = isAzureOpenAiUrl(invalidUrl) + expect(result).toBe(false) + }) + + it("should handle case insensitive hostname matching", () => { + const url = "https://MYRESOURCE.OPENAI.AZURE.COM/openai/deployments/mymodel" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(true) + }) + + it("should return false for malicious URLs trying to include Azure domain", () => { + const maliciousUrl = "https://evil.openai.azure.com.attacker.com/api/v1" + const result = isAzureOpenAiUrl(maliciousUrl) + expect(result).toBe(false) + }) + + it("should return true for root openai.azure.com domain", () => { + const url = "https://openai.azure.com/api/v1" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(true) + }) + + it("should return false for Azure AI Inference Service URLs", () => { + const url = "https://myservice.services.ai.azure.com/models/deployments" + const result = isAzureOpenAiUrl(url) + expect(result).toBe(false) + }) + }) + + describe("removeApiVersionFromUrl", () => { + it("should remove api-version parameter from URL", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview" + const result = removeApiVersionFromUrl(url) + expect(result).toBe("https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions") + }) + + it("should remove api-version parameter while preserving other parameters", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&api-version=2024-05-01-preview&baz=qux" + const result = removeApiVersionFromUrl(url) + expect(result).toBe( + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&baz=qux", + ) + }) + + it("should return original URL when no api-version parameter exists", () => { + const url = "https://api.openai.com/v1/chat/completions?foo=bar" + const result = removeApiVersionFromUrl(url) + expect(result).toBe(url) + }) + + it("should return original URL for invalid URLs", () => { + const invalidUrl = "not-a-valid-url" + const result = removeApiVersionFromUrl(invalidUrl) + expect(result).toBe(invalidUrl) + }) + + it("should handle URL with only api-version parameter", () => { + const url = + "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview" + const result = removeApiVersionFromUrl(url) + expect(result).toBe("https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions") + }) + + it("should handle URL without query parameters", () => { + const url = "https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions" + const result = removeApiVersionFromUrl(url) + expect(result).toBe(url) + }) + }) +}) diff --git a/src/utils/azure-url-parser.ts b/src/utils/azure-url-parser.ts new file mode 100644 index 0000000000..674e11dd14 --- /dev/null +++ b/src/utils/azure-url-parser.ts @@ -0,0 +1,82 @@ +/** + * Utility functions for parsing Azure OpenAI URLs and extracting API versions + */ + +/** + * Validates if a string is a valid Azure API version format + * @param version The version string to validate + * @returns True if the version follows Azure API version format (YYYY-MM-DD or YYYY-MM-DD-preview) + */ +export function isValidAzureApiVersion(version: string): boolean { + if (!version) return false + + // Azure API versions follow the pattern: YYYY-MM-DD or YYYY-MM-DD-preview + const versionPattern = /^\d{4}-\d{2}-\d{2}(-preview)?$/ + return versionPattern.test(version) +} + +/** + * Extracts the API version from an Azure OpenAI URL query parameter + * @param url The Azure OpenAI URL that may contain an api-version query parameter + * @returns The extracted API version string, or null if not found + */ +export function extractApiVersionFromUrl(url: string): string | null { + try { + const urlObj = new URL(url) + const apiVersion = urlObj.searchParams.get("api-version") + + // Validate the extracted version format + if (apiVersion && !isValidAzureApiVersion(apiVersion)) { + console.warn(`Invalid Azure API version format: ${apiVersion}`) + } + + return apiVersion + } catch (error) { + // Invalid URL format + return null + } +} + +/** + * Checks if a URL appears to be an Azure OpenAI URL + * @param url The URL to check + * @returns True if the URL appears to be an Azure OpenAI URL + */ +export function isAzureOpenAiUrl(url: string): boolean { + try { + const urlObj = new URL(url) + const host = urlObj.host.toLowerCase() + + // Exclude Azure AI Inference Service URLs + if (host.endsWith(".services.ai.azure.com")) { + return false + } + + // Check for Azure OpenAI hostname patterns + // Use endsWith to prevent matching malicious URLs like evil.openai.azure.com.attacker.com + return ( + host.endsWith(".openai.azure.com") || + host === "openai.azure.com" || + host.endsWith(".azure.com") || + urlObj.pathname.includes("/openai/deployments/") + ) + } catch (error) { + return false + } +} + +/** + * Removes the api-version query parameter from a URL + * @param url The URL to clean + * @returns The URL without the api-version parameter + */ +export function removeApiVersionFromUrl(url: string): string { + try { + const urlObj = new URL(url) + urlObj.searchParams.delete("api-version") + return urlObj.toString() + } catch (error) { + // Return original URL if parsing fails + return url + } +} diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index 736b0253c4..654ed73220 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -12,6 +12,12 @@ import { openAiModelInfoSaneDefaults, } from "@roo-code/types" +import { + extractApiVersionFromUrl, + isAzureOpenAiUrl, + isValidAzureApiVersion, +} from "../../../../../src/utils/azure-url-parser" + import { ExtensionMessage } from "@roo/ExtensionMessage" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -41,6 +47,12 @@ export const OpenAICompatible = ({ const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat) + // Check if API version can be extracted from the base URL + const baseUrl = apiConfiguration?.openAiBaseUrl || "" + const extractedApiVersion = extractApiVersionFromUrl(baseUrl) + const isAzureUrl = isAzureOpenAiUrl(baseUrl) + const showApiVersionExtraction = isAzureUrl && extractedApiVersion && !azureApiVersionSelected + const [openAiModels, setOpenAiModels] = useState | null>(null) const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { @@ -194,12 +206,31 @@ export const OpenAICompatible = ({ }}> {t("settings:modelInfo.azureApiVersion")} + {showApiVersionExtraction && ( +
+ )} {azureApiVersionSelected && ( { + const value = apiConfiguration?.azureApiVersion + if (!value) { + return "var(--vscode-input-border)" + } + return isValidAzureApiVersion(value) + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} /> )}
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 15018e64ab..d01915fa63 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "S'ha d'activat quan s'utilitzen models R1 com el QWQ per evitar errors 400", "useAzure": "Utilitzar Azure", "azureApiVersion": "Establir versió de l'API d'Azure", + "azureApiVersionDetected": "Versió de l'API detectada a l'URL base: {{version}}
Aquesta s'utilitzarà automàticament. Activa la casella de selecció anterior per sobreescriure-la.", "gemini": { "freeRequests": "* Gratuït fins a {{count}} sol·licituds per minut. Després d'això, la facturació depèn de la mida del prompt.", "pricingDetails": "Per a més informació, consulteu els detalls de preus.", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index bb5ed1146b..8c7ef5c0bb 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Muss bei Verwendung von R1-Modellen wie QWQ aktiviert werden, um 400er-Fehler zu vermeiden", "useAzure": "Azure verwenden", "azureApiVersion": "Azure API-Version festlegen", + "azureApiVersionDetected": "In der Basis-URL erkannte API-Version: {{version}}
Diese wird automatisch verwendet. Aktiviere das obige Kontrollkästchen, um dies zu überschreiben.", "gemini": { "freeRequests": "* Kostenlos bis zu {{count}} Anfragen pro Minute. Danach hängt die Abrechnung von der Prompt-Größe ab.", "pricingDetails": "Weitere Informationen finden Sie unter Preisdetails.", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 728e856502..706a8e86b8 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Must be enabled when using R1 models such as QWQ to prevent 400 errors", "useAzure": "Use Azure", "azureApiVersion": "Set Azure API version", + "azureApiVersionDetected": "API version detected in Base URL: {{version}}
This will be used automatically. Enable the checkbox above to override.", "gemini": { "freeRequests": "* Free up to {{count}} requests per minute. After that, billing depends on prompt size.", "pricingDetails": "For more info, see pricing details.", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 5836933b46..c502e677e5 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Debe habilitarse al utilizar modelos R1 como QWQ, para evitar el error 400", "useAzure": "Usar Azure", "azureApiVersion": "Establecer versión de API de Azure", + "azureApiVersionDetected": "Versión de la API detectada en la URL base: {{version}}
Se utilizará automáticamente. Habilita la casilla de verificación anterior para anularla.", "gemini": { "freeRequests": "* Gratis hasta {{count}} solicitudes por minuto. Después de eso, la facturación depende del tamaño del prompt.", "pricingDetails": "Para más información, consulte los detalles de precios.", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 833a789e5a..6aa2302404 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Doit être activé lors de l'utilisation de modèles R1 tels que QWQ, pour éviter l'erreur 400", "useAzure": "Utiliser Azure", "azureApiVersion": "Définir la version de l'API Azure", + "azureApiVersionDetected": "Version de l'API détectée dans l'URL de base : {{version}}
Celle-ci sera utilisée automatiquement. Cochez la case ci-dessus pour la remplacer.", "gemini": { "freeRequests": "* Gratuit jusqu'à {{count}} requêtes par minute. Après cela, la facturation dépend de la taille du prompt.", "pricingDetails": "Pour plus d'informations, voir les détails de tarification.", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 0749943508..127a3a633d 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "QWQ जैसी R1 मॉडलों का उपयोग करते समय इसे सक्षम करना आवश्यक है, ताकि 400 त्रुटि से बचा जा सके", "useAzure": "Azure का उपयोग करें", "azureApiVersion": "Azure API संस्करण सेट करें", + "azureApiVersionDetected": "बेस यूआरएल में एपीआई संस्करण का पता चला: {{version}}
यह स्वचालित रूप से उपयोग किया जाएगा। ओवरराइड करने के लिए ऊपर दिए गए चेकबॉक्स को सक्षम करें।", "gemini": { "freeRequests": "* प्रति मिनट {{count}} अनुरोधों तक मुफ्त। उसके बाद, बिलिंग प्रॉम्प्ट आकार पर निर्भर करती है।", "pricingDetails": "अधिक जानकारी के लिए, मूल्य निर्धारण विवरण देखें।", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 4a0c51d39d..b809252761 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -660,6 +660,7 @@ "enableR1FormatTips": "Harus diaktifkan saat menggunakan model R1 seperti QWQ untuk mencegah error 400", "useAzure": "Gunakan Azure", "azureApiVersion": "Atur versi API Azure", + "azureApiVersionDetected": "Versi API terdeteksi di URL Dasar: {{version}}
Ini akan digunakan secara otomatis. Aktifkan kotak centang di atas untuk menimpa.", "gemini": { "freeRequests": "* Gratis hingga {{count}} permintaan per menit. Setelah itu, penagihan tergantung pada ukuran prompt.", "pricingDetails": "Untuk info lebih lanjut, lihat detail harga.", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 9d9be82868..1b463d017c 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Deve essere abilitato quando si utilizzano modelli R1 come QWQ, per evitare l'errore 400", "useAzure": "Usa Azure", "azureApiVersion": "Imposta versione API Azure", + "azureApiVersionDetected": "Versione API rilevata nell'URL di base: {{version}}
Verrà utilizzata automaticamente. Abilita la casella di controllo sopra per sovrascriverla.", "gemini": { "freeRequests": "* Gratuito fino a {{count}} richieste al minuto. Dopo, la fatturazione dipende dalla dimensione del prompt.", "pricingDetails": "Per maggiori informazioni, vedi i dettagli sui prezzi.", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 9fc03cbfb1..ee778d27b1 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "QWQなどのR1モデルを使用する際には、有効にする必要があります。400エラーを防ぐために", "useAzure": "Azureを使用", "azureApiVersion": "Azure APIバージョンを設定", + "azureApiVersionDetected": "ベースURLでAPIバージョンが検出されました: {{version}}
これは自動的に使用されます。上書きするには、上のチェックボックスを有効にしてください。", "gemini": { "freeRequests": "* 1分間あたり{{count}}リクエストまで無料。それ以降は、プロンプトサイズに応じて課金されます。", "pricingDetails": "詳細は価格情報をご覧ください。", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 219daa05a4..e6f1bd7246 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "QWQ와 같은 R1 모델을 사용할 때 활성화해야 하며, 400 오류를 방지합니다", "useAzure": "Azure 사용", "azureApiVersion": "Azure API 버전 설정", + "azureApiVersionDetected": "기본 URL에서 API 버전 감지됨: {{version}}
자동으로 사용됩니다. 재정의하려면 위의 확인란을 활성화하세요.", "gemini": { "freeRequests": "* 분당 {{count}}개의 요청까지 무료. 이후에는 프롬프트 크기에 따라 요금이 부과됩니다.", "pricingDetails": "자세한 내용은 가격 정보를 참조하세요.", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index e184f4d85e..ec9515ca32 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Moet ingeschakeld zijn bij gebruik van R1-modellen zoals QWQ om 400-fouten te voorkomen", "useAzure": "Azure gebruiken", "azureApiVersion": "Azure API-versie instellen", + "azureApiVersionDetected": "API-versie gedetecteerd in basis-URL: {{version}}
Deze wordt automatisch gebruikt. Schakel het selectievakje hierboven in om te overschrijven.", "gemini": { "freeRequests": "* Gratis tot {{count}} verzoeken per minuut. Daarna is de prijs afhankelijk van de promptgrootte.", "pricingDetails": "Zie prijsdetails voor meer info.", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 23d3ce707d..64f1a60f8e 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Należy włączyć podczas korzystania z modeli R1, takich jak QWQ, aby uniknąć błędu 400", "useAzure": "Użyj Azure", "azureApiVersion": "Ustaw wersję API Azure", + "azureApiVersionDetected": "Wykryto wersję API w bazowym adresie URL: {{version}}
Zostanie ona użyta automatycznie. Zaznacz powyższe pole wyboru, aby nadpisać.", "gemini": { "freeRequests": "* Darmowe do {{count}} zapytań na minutę. Po tym, rozliczanie zależy od rozmiaru podpowiedzi.", "pricingDetails": "Więcej informacji znajdziesz w szczegółach cennika.", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 102036622c..7630b92e60 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Deve ser ativado ao usar modelos R1 como QWQ, para evitar erro 400", "useAzure": "Usar Azure", "azureApiVersion": "Definir versão da API Azure", + "azureApiVersionDetected": "Versão da API detectada na URL base: {{version}}
Isso será usado automaticamente. Habilite a caixa de seleção acima para substituir.", "gemini": { "freeRequests": "* Gratuito até {{count}} requisições por minuto. Depois disso, a cobrança depende do tamanho do prompt.", "pricingDetails": "Para mais informações, consulte os detalhes de preços.", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 5952dd8c89..85d7c67e60 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Необходимо включить при использовании моделей R1 (например, QWQ), чтобы избежать ошибок 400", "useAzure": "Использовать Azure", "azureApiVersion": "Установить версию API Azure", + "azureApiVersionDetected": "Версия API обнаружена в базовом URL: {{version}}
Она будет использоваться автоматически. Включите флажок выше, чтобы переопределить.", "gemini": { "freeRequests": "* Бесплатно до {{count}} запросов в минуту. Далее тарификация зависит от размера подсказки.", "pricingDetails": "Подробнее о ценах.", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 625ca4d5ea..832958a7d7 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "QWQ gibi R1 modelleri kullanıldığında etkinleştirilmelidir, 400 hatası alınmaması için", "useAzure": "Azure kullan", "azureApiVersion": "Azure API sürümünü ayarla", + "azureApiVersionDetected": "Temel URL'de API sürümü algılandı: {{version}}
Bu otomatik olarak kullanılacaktır. Geçersiz kılmak için yukarıdaki onay kutusunu etkinleştirin.", "gemini": { "freeRequests": "* Dakikada {{count}} isteğe kadar ücretsiz. Bundan sonra, ücretlendirme istem boyutuna bağlıdır.", "pricingDetails": "Daha fazla bilgi için fiyatlandırma ayrıntılarına bakın.", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 52a9db5b93..500317fcc8 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "Cần kích hoạt khi sử dụng các mô hình R1 như QWQ, để tránh lỗi 400", "useAzure": "Sử dụng Azure", "azureApiVersion": "Đặt phiên bản API Azure", + "azureApiVersionDetected": "Phiên bản API được phát hiện trong URL cơ sở: {{version}}
Phiên bản này sẽ được sử dụng tự động. Bật hộp kiểm ở trên để ghi đè.", "gemini": { "freeRequests": "* Miễn phí đến {{count}} yêu cầu mỗi phút. Sau đó, thanh toán phụ thuộc vào kích thước lời nhắc.", "pricingDetails": "Để biết thêm thông tin, xem chi tiết giá.", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 151ee9e744..a7bc65d473 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "使用 QWQ 等 R1 系列模型时必须启用,避免出现 400 错误", "useAzure": "使用 Azure 服务", "azureApiVersion": "设置 Azure API 版本", + "azureApiVersionDetected": "在基本 URL 中检测到 API 版本:{{version}}
将自动使用此版本。若要覆盖,请启用上方的复选框。", "gemini": { "freeRequests": "* 每分钟免费 {{count}} 个请求。之后,计费取决于提示大小。", "pricingDetails": "有关更多信息,请参阅定价详情。", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index ab4caeea5b..4f0bbbd5da 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -631,6 +631,7 @@ "enableR1FormatTips": "使用 QWQ 等 R1 模型時必須啟用,以避免發生 400 錯誤", "useAzure": "使用 Azure", "azureApiVersion": "設定 Azure API 版本", + "azureApiVersionDetected": "在基底 URL 中偵測到 API 版本:{{version}}
將自動使用此版本。若要覆寫,請啟用上方的核取方塊。", "gemini": { "freeRequests": "* 每分鐘可免費使用 {{count}} 次請求,超過後將依提示大小計費。", "pricingDetails": "詳細資訊請參閱定價說明。",