diff --git a/packages/types/src/__tests__/azure-openai.test.ts b/packages/types/src/__tests__/azure-openai.test.ts new file mode 100644 index 0000000000..7f5dde3a9a --- /dev/null +++ b/packages/types/src/__tests__/azure-openai.test.ts @@ -0,0 +1,37 @@ +import { + azureOpenAiDefaultApiVersion, + getAzureOpenAiBaseUrl, + getAzureOpenAiChatCompletionsUrl, +} from "../providers/openai.js" + +describe("Azure OpenAI URLs", () => { + it.each([ + ["https://resource.openai.azure.com", "https://resource.openai.azure.com/openai"], + ["https://resource.openai.azure.com/", "https://resource.openai.azure.com/openai"], + ["https://resource.openai.azure.com/openai", "https://resource.openai.azure.com/openai"], + ["https://resource.openai.azure.com/OpenAI/", "https://resource.openai.azure.com/openai"], + ["https://models.example.com/azure/", "https://models.example.com/azure/openai"], + ])("normalizes %s to the /openai root", (baseUrl, expected) => { + expect(getAzureOpenAiBaseUrl(baseUrl)).toBe(expected) + }) + + it.each([ + { + apiVersion: "2025-01-01-preview", + expected: + "https://resource.openai.azure.com/openai/deployments/brotherhood/chat/completions?api-version=2025-01-01-preview", + }, + { + apiVersion: undefined, + expected: `https://resource.openai.azure.com/openai/deployments/brotherhood/chat/completions?api-version=${azureOpenAiDefaultApiVersion}`, + }, + { + apiVersion: "", + expected: `https://resource.openai.azure.com/openai/deployments/brotherhood/chat/completions?api-version=${azureOpenAiDefaultApiVersion}`, + }, + ])("builds the deployment chat completions URL for API version $apiVersion", ({ apiVersion, expected }) => { + expect(getAzureOpenAiChatCompletionsUrl("https://resource.openai.azure.com/", "brotherhood", apiVersion)).toBe( + expected, + ) + }) +}) diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index 41d00c7a04..27214613d1 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -978,3 +978,20 @@ export function isAzureOpenAiBaseUrl(baseUrl?: string, useAzure?: boolean): bool return false } } + +/** + * Normalizes an Azure OpenAI resource URL to the `/openai` root the AzureOpenAI + * client expects. A trailing `/openai` the user already typed is not repeated. + */ +export function getAzureOpenAiBaseUrl(baseUrl: string): string { + return `${baseUrl.replace(/\/openai\/?$/i, "").replace(/\/$/, "")}/openai` +} + +/** + * Returns the chat completions URL the AzureOpenAI client requests: it inserts + * `/deployments/` using the configured model ID as the deployment name. + */ +export function getAzureOpenAiChatCompletionsUrl(baseUrl: string, deployment: string, apiVersion?: string): string { + const version = apiVersion || azureOpenAiDefaultApiVersion + return `${getAzureOpenAiBaseUrl(baseUrl)}/deployments/${deployment}/chat/completions?api-version=${version}` +} diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 7c45f22180..7c62baa7f0 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -20,6 +20,11 @@ vitest.mock("../utils/timeout-config", () => ({ getApiRequestTimeout: vitest.fn().mockReturnValue(300_000), })) +// Echo the key and interpolation values so tests can assert what reaches the message. +vitest.mock("../../../i18n", () => ({ + t: (key: string, options?: Record) => (options ? `${key} ${JSON.stringify(options)}` : key), +})) + const MOCK_TIMEOUT_MS = 300_000 const mockCreate = vitest.fn() @@ -81,7 +86,9 @@ vitest.mock("openai", () => { }, } }), - AzureOpenAI: mockAzureConstructor, + AzureOpenAI: mockAzureConstructor.mockImplementation(function () { + return { chat: { completions: { create: mockCreate } } } + }), } }) @@ -941,6 +948,67 @@ describe("OpenAiHandler", () => { }) }) + describe("Azure OpenAI deployment errors", () => { + const azureOptions = { + openAiApiKey: "test-api-key", + openAiBaseUrl: "https://resource.openai.azure.com/", + } + + function makeStatusError(message: string, status: number) { + return Object.assign(new Error(message), { status }) + } + + async function request( + entryPoint: "streaming" | "non-streaming" | "o3" | "o3-non-streaming" | "completePrompt", + options: Partial, + ) { + const requestHandler = new OpenAiHandler( + makeApiHandlerOptions({ + openAiModelId: entryPoint.startsWith("o3") ? "o3-mini" : "gpt-4o", + openAiStreamingEnabled: entryPoint !== "non-streaming" && entryPoint !== "o3-non-streaming", + ...options, + }), + ) + + if (entryPoint === "completePrompt") { + return requestHandler.completePrompt("Test prompt") + } + + return collectStream(requestHandler.createMessage("system prompt", [{ role: "user", content: "Hello" }])) + } + + it.each(["streaming", "non-streaming", "o3", "o3-non-streaming", "completePrompt"] as const)( + "names the missing deployment on an Azure 404 (%s)", + async (entryPoint) => { + mockCreate.mockRejectedValueOnce(makeStatusError("404 Resource not found", 404)) + + const error = await request(entryPoint, azureOptions).catch((e: unknown) => e) + const deployment = entryPoint.startsWith("o3") ? "o3-mini" : "gpt-4o" + + expect(error).toBeInstanceOf(Error) + expect(error).toMatchObject({ status: 404 }) + expect((error as Error).message).toContain( + `errors.api.azureDeploymentNotFound ${JSON.stringify({ deployment, message: "404 Resource not found" })}`, + ) + }, + ) + + it.each([ + { name: "a non-404 Azure error", options: azureOptions, status: 401 }, + { + name: "a 404 from a non-Azure endpoint", + options: { ...azureOptions, openAiBaseUrl: "https://api.openai.com/v1" }, + status: 404, + }, + ])("keeps the generic error message for $name", async ({ options, status }) => { + mockCreate.mockRejectedValueOnce(makeStatusError("Request failed", status)) + + const error = await request("streaming", options).catch((e: unknown) => e) + + expect(error).toMatchObject({ status, message: "OpenAI completion error: Request failed" }) + }) + }) + describe("completePrompt", () => { it("should complete prompt successfully", async () => { const result = await handler.completePrompt("Test prompt") diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index aaf3bab8fb..653d3e0705 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -5,6 +5,7 @@ import axios from "axios" import { type ModelInfo, azureOpenAiDefaultApiVersion, + getAzureOpenAiBaseUrl, isAzureAiInferenceBaseUrl, isAzureOpenAiBaseUrl, openAiModelInfoSaneDefaults, @@ -15,6 +16,7 @@ import { } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" +import { t } from "../../i18n" import { TagMatcher } from "../../utils/tag-matcher" @@ -27,7 +29,7 @@ import { getModelParams } from "../transform/model-params" import { DEFAULT_HEADERS, NOT_PROVIDED } from "./constants" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata, CompletePromptOptions } from "../index" -import { handleOpenAIError } from "./utils/error-handler" +import { handleOpenAIError, handleProviderError } from "./utils/error-handler" import { extractReasoningFromDelta } from "./utils/extract-reasoning" // TODO: Rename this to OpenAICompatibleHandler. Also, I think the @@ -38,6 +40,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl protected client: OpenAI private readonly providerName = "OpenAI" private readonly extraBody: Record + private readonly isAzureOpenAi: boolean constructor(options: ApiHandlerOptions) { super() @@ -48,6 +51,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const apiKey = this.options.openAiApiKey ?? NOT_PROVIDED const isAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) const isAzureOpenAi = isAzureOpenAiBaseUrl(this.options.openAiBaseUrl, options.openAiUseAzure) + this.isAzureOpenAi = isAzureOpenAi const headers = { ...DEFAULT_HEADERS, @@ -66,9 +70,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } 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 - const azureBaseURL = `${baseURL.replace(/\/openai\/?$/i, "").replace(/\/$/, "")}/openai` this.client = new AzureOpenAI({ - baseURL: azureBaseURL, + baseURL: getAzureOpenAiBaseUrl(baseURL), apiKey, apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion, defaultHeaders: headers, @@ -193,7 +196,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(isAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}), }) } catch (error) { - throw handleOpenAIError(error, this.providerName) + throw this.handleRequestError(error, modelId) } const matcher = new TagMatcher( @@ -261,7 +264,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(this._isAzureAiInference(modelUrl) ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}), }) } catch (error) { - throw handleOpenAIError(error, this.providerName) + throw this.handleRequestError(error, modelId) } const message = response.choices?.[0]?.message @@ -353,7 +356,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl isAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}, ) } catch (error) { - throw handleOpenAIError(error, this.providerName) + throw this.handleRequestError(error, model.id) } return response.choices?.[0]?.message.content || "" @@ -420,7 +423,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(methodIsAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}), }) } catch (error) { - throw handleOpenAIError(error, this.providerName) + throw this.handleRequestError(error, modelId) } yield* this.handleStreamResponse(stream) @@ -455,7 +458,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(methodIsAzureAiInference ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}), }) } catch (error) { - throw handleOpenAIError(error, this.providerName) + throw this.handleRequestError(error, modelId) } const message = response.choices?.[0]?.message @@ -561,6 +564,25 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl return urlHost === "api.x.ai" || urlHost.endsWith(".x.ai") } + /** + * Azure OpenAI routes each request to `/openai/deployments/`, so a 404 there + * almost always means the configured deployment name does not exist. Name the + * deployment in the error instead of surfacing a bare "Resource not found". + */ + private handleRequestError(error: unknown, deploymentName: string): Error { + if (this.isAzureOpenAi && isNotFoundError(error)) { + return handleProviderError(error, this.providerName, { + messageTransformer: (message) => + `${this.providerName} completion error: ${t("common:errors.api.azureDeploymentNotFound", { + deployment: deploymentName, + message, + })}`, + }) + } + + return handleOpenAIError(error, this.providerName) + } + protected _isAzureAiInference(baseUrl?: string): boolean { return isAzureAiInferenceBaseUrl(baseUrl) } @@ -589,6 +611,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } } +function isNotFoundError(error: unknown): boolean { + return typeof error === "object" && error !== null && "status" in error && error.status === 404 +} + export async function getOpenAiModels(baseUrl?: string, apiKey?: string, openAiHeaders?: Record) { try { if (!baseUrl) { diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 579cd6b7d7..fad0eeaa5b 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -144,7 +144,8 @@ }, "api": { "invalidKeyInvalidChars": "La clau API conté caràcters no vàlids.", - "apiRequestFailed": "La sol·licitud API ha fallat ({{status}})" + "apiRequestFailed": "La sol·licitud API ha fallat ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI no ha trobat el desplegament \"{{deployment}}\". Establiu \"Nom de desplegament d'Azure\" al nom exacte del desplegament d'Azure AI Foundry, no al nom del model (per exemple gpt-4o). Detalls: {{message}}" }, "manual_url_empty": "Si us plau, introdueix una URL de callback vàlida", "manual_url_no_query": "URL de callback no vàlida: falten paràmetres de consulta", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 3d8175a77e..6a40b0246f 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -141,7 +141,8 @@ }, "api": { "invalidKeyInvalidChars": "API-Schlüssel enthält ungültige Zeichen.", - "apiRequestFailed": "API-Anfrage fehlgeschlagen ({{status}})" + "apiRequestFailed": "API-Anfrage fehlgeschlagen ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI konnte die Bereitstellung \"{{deployment}}\" nicht finden. Setze \"Azure-Bereitstellungsname\" auf den exakten Bereitstellungsnamen aus Azure AI Foundry, nicht auf den Modellnamen (zum Beispiel gpt-4o). Details: {{message}}" }, "manual_url_empty": "Bitte gib eine gültige Callback-URL ein", "manual_url_no_query": "Ungültige Callback-URL: Query-Parameter fehlen", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index a7d616028f..1260574d0c 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -142,7 +142,8 @@ }, "api": { "invalidKeyInvalidChars": "API key contains invalid characters.", - "apiRequestFailed": "API request failed ({{status}})" + "apiRequestFailed": "API request failed ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI could not find the deployment \"{{deployment}}\". Set \"Azure deployment name\" to the exact deployment name from Azure AI Foundry, not the model name (for example gpt-4o). Details: {{message}}" }, "manual_url_empty": "Please enter a valid callback URL", "manual_url_no_query": "Invalid callback URL: missing query parameters", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 2eb6848f11..9580441d2a 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "La clave API contiene caracteres inválidos.", - "apiRequestFailed": "La solicitud API falló ({{status}})" + "apiRequestFailed": "La solicitud API falló ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI no encontró la implementación \"{{deployment}}\". Establece \"Nombre de implementación de Azure\" con el nombre exacto de la implementación de Azure AI Foundry, no el nombre del modelo (por ejemplo gpt-4o). Detalles: {{message}}" }, "manual_url_empty": "Por favor, introduce una URL de callback válida", "manual_url_no_query": "URL de callback inválida: faltan parámetros de consulta", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index b6b65a88de..d0201ef848 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "La clé API contient des caractères invalides.", - "apiRequestFailed": "La requête API a échoué ({{status}})" + "apiRequestFailed": "La requête API a échoué ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI n'a pas trouvé le déploiement \"{{deployment}}\". Définissez \"Nom du déploiement Azure\" sur le nom exact du déploiement dans Azure AI Foundry, et non sur le nom du modèle (par exemple gpt-4o). Détails : {{message}}" }, "manual_url_empty": "Veuillez entrer une URL de callback valide", "manual_url_no_query": "URL de callback invalide : paramètres de requête manquants", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index f69b5adccf..3ae7901997 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "API कुंजी में अमान्य वर्ण हैं।", - "apiRequestFailed": "API अनुरोध विफल ({{status}})" + "apiRequestFailed": "API अनुरोध विफल ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI को परिनियोजन \"{{deployment}}\" नहीं मिला। \"Azure परिनियोजन नाम\" को Azure AI Foundry के सटीक परिनियोजन नाम पर सेट करें, मॉडल नाम पर नहीं (उदाहरण के लिए gpt-4o)। विवरण: {{message}}" }, "manual_url_empty": "कृपया एक वैध callback URL दर्ज करें", "manual_url_no_query": "अवैध callback URL: क्वेरी पैरामीटर गुम हैं", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index e401612bf3..8884e3d515 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "Kunci API mengandung karakter tidak valid.", - "apiRequestFailed": "Permintaan API gagal ({{status}})" + "apiRequestFailed": "Permintaan API gagal ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI tidak dapat menemukan deployment \"{{deployment}}\". Atur \"Nama deployment Azure\" ke nama deployment yang persis dari Azure AI Foundry, bukan nama model (misalnya gpt-4o). Detail: {{message}}" }, "manual_url_empty": "Silakan masukkan URL callback yang valid", "manual_url_no_query": "URL callback tidak valid: parameter query hilang", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index aeb984940a..4fadd89ce4 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "La chiave API contiene caratteri non validi.", - "apiRequestFailed": "Richiesta API fallita ({{status}})" + "apiRequestFailed": "Richiesta API fallita ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI non ha trovato la distribuzione \"{{deployment}}\". Imposta \"Nome della distribuzione Azure\" sul nome esatto della distribuzione in Azure AI Foundry, non sul nome del modello (ad esempio gpt-4o). Dettagli: {{message}}" }, "manual_url_empty": "Inserisci un URL di callback valido", "manual_url_no_query": "URL di callback non valido: parametri di query mancanti", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index a7ec0c4de3..79532f8039 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "APIキーに無効な文字が含まれています。", - "apiRequestFailed": "APIリクエストが失敗しました ({{status}})" + "apiRequestFailed": "APIリクエストが失敗しました ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI でデプロイ \"{{deployment}}\" が見つかりませんでした。「Azure デプロイ名」には、モデル名 (例: gpt-4o) ではなく、Azure AI Foundry の正確なデプロイ名を設定してください。詳細: {{message}}" }, "manual_url_empty": "有効なコールバック URL を入力してください", "manual_url_no_query": "無効なコールバック URL:クエリパラメータがありません", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 906652ac14..6df3b55270 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "API 키에 유효하지 않은 문자가 포함되어 있습니다.", - "apiRequestFailed": "API 요청 실패 ({{status}})" + "apiRequestFailed": "API 요청 실패 ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI에서 배포 \"{{deployment}}\"을(를) 찾을 수 없습니다. \"Azure 배포 이름\"을 모델 이름(예: gpt-4o)이 아닌 Azure AI Foundry의 정확한 배포 이름으로 설정하세요. 세부 정보: {{message}}" }, "manual_url_empty": "유효한 콜백 URL을 입력하세요", "manual_url_no_query": "유효하지 않은 콜백 URL: 쿼리 매개변수 누락", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 34cdbbbe89..56f5399f69 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "API-sleutel bevat ongeldige karakters.", - "apiRequestFailed": "API-verzoek mislukt ({{status}})" + "apiRequestFailed": "API-verzoek mislukt ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI kon de implementatie \"{{deployment}}\" niet vinden. Stel \"Naam van Azure-implementatie\" in op de exacte implementatienaam uit Azure AI Foundry, niet op de modelnaam (bijvoorbeeld gpt-4o). Details: {{message}}" }, "manual_url_empty": "Voer een geldige callback-URL in", "manual_url_no_query": "Ongeldige callback-URL: query-parameters ontbreken", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 2a743fc009..422f504ec8 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "Klucz API zawiera nieprawidłowe znaki.", - "apiRequestFailed": "Żądanie API nie powiodło się ({{status}})" + "apiRequestFailed": "Żądanie API nie powiodło się ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI nie znalazło wdrożenia \"{{deployment}}\". Ustaw \"Nazwa wdrożenia Azure\" na dokładną nazwę wdrożenia z Azure AI Foundry, a nie na nazwę modelu (na przykład gpt-4o). Szczegóły: {{message}}" }, "manual_url_empty": "Wprowadź prawidłowy URL callback", "manual_url_no_query": "Nieprawidłowy URL callback: brak parametrów zapytania", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index eaf1cae06b..e512d002d0 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -127,7 +127,8 @@ }, "api": { "invalidKeyInvalidChars": "A chave API contém caracteres inválidos.", - "apiRequestFailed": "Solicitação API falhou ({{status}})" + "apiRequestFailed": "Solicitação API falhou ({{status}})", + "azureDeploymentNotFound": "O Azure OpenAI não encontrou a implantação \"{{deployment}}\". Defina \"Nome da implantação do Azure\" com o nome exato da implantação no Azure AI Foundry, não o nome do modelo (por exemplo gpt-4o). Detalhes: {{message}}" }, "manual_url_empty": "Por favor, insira uma URL de callback válida", "manual_url_no_query": "URL de callback inválida: parâmetros de consulta ausentes", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 6f48365ec1..9bd967f05d 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "API-ключ содержит недопустимые символы.", - "apiRequestFailed": "Запрос API не удался ({{status}})" + "apiRequestFailed": "Запрос API не удался ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI не удалось найти развёртывание \"{{deployment}}\". Укажите в поле \"Имя развёртывания Azure\" точное имя развёртывания из Azure AI Foundry, а не имя модели (например, gpt-4o). Подробности: {{message}}" }, "manual_url_empty": "Введи действительный URL обратного вызова", "manual_url_no_query": "Недействительный URL обратного вызова: отсутствуют параметры запроса", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index b5c543c405..a71a3e8079 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "API anahtarı geçersiz karakterler içeriyor.", - "apiRequestFailed": "API isteği başarısız oldu ({{status}})" + "apiRequestFailed": "API isteği başarısız oldu ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI \"{{deployment}}\" dağıtımını bulamadı. \"Azure dağıtım adı\" alanını model adına (örneğin gpt-4o) değil, Azure AI Foundry'deki tam dağıtım adına ayarlayın. Ayrıntılar: {{message}}" }, "manual_url_empty": "Lütfen geçerli bir callback URL'si girin", "manual_url_no_query": "Geçersiz callback URL'si: sorgu parametreleri eksik", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index cb2ed8e9b8..4fca68cf6a 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -123,7 +123,8 @@ }, "api": { "invalidKeyInvalidChars": "Khóa API chứa ký tự không hợp lệ.", - "apiRequestFailed": "Yêu cầu API thất bại ({{status}})" + "apiRequestFailed": "Yêu cầu API thất bại ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI không tìm thấy triển khai \"{{deployment}}\". Hãy đặt \"Tên triển khai Azure\" thành tên triển khai chính xác trong Azure AI Foundry, không phải tên mô hình (ví dụ gpt-4o). Chi tiết: {{message}}" }, "manual_url_empty": "Vui lòng nhập URL callback hợp lệ", "manual_url_no_query": "URL callback không hợp lệ: thiếu tham số truy vấn", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 08cfe736b5..bb3daf0c2a 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -128,7 +128,8 @@ }, "api": { "invalidKeyInvalidChars": "API 密钥包含无效字符.", - "apiRequestFailed": "API 请求失败 ({{status}})" + "apiRequestFailed": "API 请求失败 ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI 找不到部署 \"{{deployment}}\"。请将\"Azure 部署名称\"设置为 Azure AI Foundry 中的准确部署名称,而不是模型名称(例如 gpt-4o)。详细信息:{{message}}" }, "manual_url_empty": "请输入有效的回调 URL", "manual_url_no_query": "无效的回调 URL:缺少查询参数", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 92fdbaeb96..344c76bfac 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -122,7 +122,8 @@ }, "api": { "invalidKeyInvalidChars": "API 金鑰包含無效字元。", - "apiRequestFailed": "API 請求失敗 ({{status}})" + "apiRequestFailed": "API 請求失敗 ({{status}})", + "azureDeploymentNotFound": "Azure OpenAI 找不到部署 \"{{deployment}}\"。請將「Azure 部署名稱」設定為 Azure AI Foundry 中的確切部署名稱,而非模型名稱(例如 gpt-4o)。詳細資訊:{{message}}" }, "manual_url_empty": "請輸入有效的回呼 URL", "manual_url_no_query": "無效的回呼 URL:缺少查詢參數", diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index efb839a7fc..9cf85c4321 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -10,6 +10,7 @@ import { type OrganizationAllowList, type ExtensionMessage, azureOpenAiDefaultApiVersion, + getAzureOpenAiChatCompletionsUrl, isAzureOpenAiBaseUrl, openAiModelInfoSaneDefaults, DEFAULT_OPEN_AI_STRICT_TOOL_SCHEMAS, @@ -169,6 +170,18 @@ export const OpenAICompatible = ({ {isAzureOpenAi && (
{t("settings:providers.azureOpenAiDeploymentNameDescription")} + {apiConfiguration?.openAiBaseUrl && ( +
+ {t("settings:providers.azureOpenAiRequestUrl")}{" "} + + {getAzureOpenAiChatCompletionsUrl( + apiConfiguration.openAiBaseUrl, + apiConfiguration.openAiModelId || "", + apiConfiguration.azureApiVersion, + )} + +
+ )}
)} ({ @@ -175,6 +175,49 @@ describe("OpenAICompatible Component - includeMaxTokens checkbox", () => { expect(screen.getByText("settings:providers.azureOpenAiDeploymentNameDescription")).toBeInTheDocument() }) + it.each([ + { + name: "the configured deployment and API version", + apiConfiguration: { + openAiBaseUrl: "https://resource.openai.azure.com/openai/", + openAiModelId: "brotherhood", + azureApiVersion: "2025-01-01-preview", + }, + expectedUrl: + "https://resource.openai.azure.com/openai/deployments/brotherhood/chat/completions?api-version=2025-01-01-preview", + }, + { + name: "a placeholder deployment and the default API version", + apiConfiguration: { openAiBaseUrl: "https://resource.openai.azure.com" }, + expectedUrl: `https://resource.openai.azure.com/openai/deployments//chat/completions?api-version=${azureOpenAiDefaultApiVersion}`, + }, + ])("shows the Azure request URL with $name", ({ apiConfiguration, expectedUrl }) => { + render( + , + ) + + expect(screen.getByTestId("azure-request-url")).toHaveTextContent( + `settings:providers.azureOpenAiRequestUrl ${expectedUrl}`, + ) + }) + + it("omits the Azure request URL until a base URL is entered", () => { + render( + , + ) + + expect(screen.getByText("settings:providers.azureOpenAiDeploymentNameDescription")).toBeInTheDocument() + expect(screen.queryByTestId("azure-request-url")).not.toBeInTheDocument() + }) + it("keeps generic OpenAI-compatible guidance for non-Azure endpoints", () => { render( { expect( screen.queryByText("settings:providers.azureOpenAiDeploymentNameDescription"), ).not.toBeInTheDocument() + expect(screen.queryByTestId("azure-request-url")).not.toBeInTheDocument() }) it("keeps generic OpenAI-compatible guidance for Azure AI Inference endpoints", () => { diff --git a/webview-ui/src/components/settings/providers/__tests__/OpenAICompatible.visual.fixture.tsx b/webview-ui/src/components/settings/providers/__tests__/OpenAICompatible.visual.fixture.tsx index 9e0aeb71bc..98905523c5 100644 --- a/webview-ui/src/components/settings/providers/__tests__/OpenAICompatible.visual.fixture.tsx +++ b/webview-ui/src/components/settings/providers/__tests__/OpenAICompatible.visual.fixture.tsx @@ -27,7 +27,7 @@ const translations = flattenTranslations(enSettings as Record) const apiConfiguration: ProviderSettings = { apiProvider: providerIdentifiers.openai, - openAiBaseUrl: "", + openAiBaseUrl: "https://my-resource.openai.azure.com/", openAiModelId: "my-gpt4o-deployment", openAiUseAzure: true, } diff --git a/webview-ui/src/components/settings/providers/__tests__/__screenshots__/openai-compatible-azure-guidance-dark.png b/webview-ui/src/components/settings/providers/__tests__/__screenshots__/openai-compatible-azure-guidance-dark.png index e8d6bb3b4b..ca7ff8ca97 100644 Binary files a/webview-ui/src/components/settings/providers/__tests__/__screenshots__/openai-compatible-azure-guidance-dark.png and b/webview-ui/src/components/settings/providers/__tests__/__screenshots__/openai-compatible-azure-guidance-dark.png differ diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index d39d8edd6a..ff9340ca44 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nom de desplegament d'Azure", "azureOpenAiDeploymentNameDescription": "Introduïu el nom del desplegament d'Azure AI Studio, no el nom del model subjacent.", + "azureOpenAiRequestUrl": "Les sol·licituds s'envien a:", "getOpenAiApiKey": "Obtenir clau API d'OpenAI", "mistralApiKey": "Clau API de Mistral", "getMistralApiKey": "Obtenir clau API de Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 983db1df5d..392c937fb1 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure-Bereitstellungsname", "azureOpenAiDeploymentNameDescription": "Gib den Bereitstellungsnamen aus Azure AI Studio ein, nicht den Namen des zugrunde liegenden Modells.", + "azureOpenAiRequestUrl": "Anfragen werden gesendet an:", "getOpenAiApiKey": "OpenAI API-Schlüssel erhalten", "mistralApiKey": "Mistral API-Schlüssel", "getMistralApiKey": "Mistral / Codestral API-Schlüssel erhalten", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index ea924ce79e..8733e09e53 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -630,6 +630,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure deployment name", "azureOpenAiDeploymentNameDescription": "Enter the deployment name from Azure AI Studio, not the underlying model name.", + "azureOpenAiRequestUrl": "Requests are sent to:", "getOpenAiApiKey": "Get OpenAI API Key", "mistralApiKey": "Mistral API Key", "getMistralApiKey": "Get Mistral / Codestral API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 57612bf3e5..8b36c12ee3 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nombre de implementación de Azure", "azureOpenAiDeploymentNameDescription": "Introduce el nombre de la implementación de Azure AI Studio, no el nombre del modelo subyacente.", + "azureOpenAiRequestUrl": "Las solicitudes se envían a:", "getOpenAiApiKey": "Obtener clave API de OpenAI", "mistralApiKey": "Clave API de Mistral", "getMistralApiKey": "Obtener clave API de Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index a91a5137d3..8cc5b5d04f 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nom du déploiement Azure", "azureOpenAiDeploymentNameDescription": "Saisissez le nom du déploiement défini dans Azure AI Studio, et non le nom du modèle sous-jacent.", + "azureOpenAiRequestUrl": "Les requêtes sont envoyées à :", "getOpenAiApiKey": "Obtenir la clé API OpenAI", "mistralApiKey": "Clé API Mistral", "getMistralApiKey": "Obtenir la clé API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index bc6e973623..fc56e7a3cd 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure परिनियोजन नाम", "azureOpenAiDeploymentNameDescription": "अंतर्निहित मॉडल नाम के बजाय Azure AI Studio में दिया गया परिनियोजन नाम दर्ज करें।", + "azureOpenAiRequestUrl": "अनुरोध यहाँ भेजे जाते हैं:", "getOpenAiApiKey": "OpenAI API कुंजी प्राप्त करें", "mistralApiKey": "Mistral API कुंजी", "getMistralApiKey": "Mistral / Codestral API कुंजी प्राप्त करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 3ed57f373e..9ee6ce807a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nama deployment Azure", "azureOpenAiDeploymentNameDescription": "Masukkan nama deployment dari Azure AI Studio, bukan nama model dasarnya.", + "azureOpenAiRequestUrl": "Permintaan dikirim ke:", "getOpenAiApiKey": "Dapatkan OpenAI API Key", "mistralApiKey": "Mistral API Key", "getMistralApiKey": "Dapatkan Mistral / Codestral API Key", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index b708304e63..25af599912 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nome della distribuzione Azure", "azureOpenAiDeploymentNameDescription": "Inserisci il nome della distribuzione configurato in Azure AI Studio, non il nome del modello sottostante.", + "azureOpenAiRequestUrl": "Le richieste vengono inviate a:", "getOpenAiApiKey": "Ottieni chiave API OpenAI", "mistralApiKey": "Chiave API Mistral", "getMistralApiKey": "Ottieni chiave API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index f6959b50dc..f289deec08 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure デプロイ名", "azureOpenAiDeploymentNameDescription": "基盤モデル名ではなく、Azure AI Studio のデプロイ名を入力してください。", + "azureOpenAiRequestUrl": "リクエストの送信先:", "getOpenAiApiKey": "OpenAI APIキーを取得", "mistralApiKey": "Mistral APIキー", "getMistralApiKey": "Mistral / Codestral APIキーを取得", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 6cadbd8a3b..2f7c078f5a 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure 배포 이름", "azureOpenAiDeploymentNameDescription": "기반 모델 이름이 아닌 Azure AI Studio의 배포 이름을 입력하세요.", + "azureOpenAiRequestUrl": "요청 전송 위치:", "getOpenAiApiKey": "OpenAI API 키 받기", "mistralApiKey": "Mistral API 키", "getMistralApiKey": "Mistral / Codestral API 키 받기", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 992e9c53e0..41cf6dc4ef 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Naam van Azure-implementatie", "azureOpenAiDeploymentNameDescription": "Voer de implementatienaam uit Azure AI Studio in, niet de naam van het onderliggende model.", + "azureOpenAiRequestUrl": "Verzoeken worden verzonden naar:", "getOpenAiApiKey": "OpenAI API-sleutel ophalen", "mistralApiKey": "Mistral API-sleutel", "getMistralApiKey": "Mistral / Codestral API-sleutel ophalen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 459675b0e1..ad83a4c914 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nazwa wdrożenia Azure", "azureOpenAiDeploymentNameDescription": "Wprowadź nazwę wdrożenia z Azure AI Studio, a nie nazwę bazowego modelu.", + "azureOpenAiRequestUrl": "Żądania są wysyłane do:", "getOpenAiApiKey": "Uzyskaj klucz API OpenAI", "mistralApiKey": "Klucz API Mistral", "getMistralApiKey": "Uzyskaj klucz API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 500babaaa8..a6a2ed1226 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Nome da implantação do Azure", "azureOpenAiDeploymentNameDescription": "Insira o nome da implantação configurado no Azure AI Studio, não o nome do modelo subjacente.", + "azureOpenAiRequestUrl": "As solicitações são enviadas para:", "getOpenAiApiKey": "Obter chave de API OpenAI", "mistralApiKey": "Chave de API Mistral", "getMistralApiKey": "Obter chave de API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 053ff0684d..c63d5a4f46 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Имя развёртывания Azure", "azureOpenAiDeploymentNameDescription": "Введите имя развёртывания из Azure AI Studio, а не имя базовой модели.", + "azureOpenAiRequestUrl": "Запросы отправляются на:", "getOpenAiApiKey": "Получить OpenAI API-ключ", "mistralApiKey": "Mistral API-ключ", "getMistralApiKey": "Получить Mistral / Codestral API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index b37f28d44d..7ac3ef6495 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure dağıtım adı", "azureOpenAiDeploymentNameDescription": "Temel model adı yerine Azure AI Studio'daki dağıtım adını girin.", + "azureOpenAiRequestUrl": "İstekler şuraya gönderilir:", "getOpenAiApiKey": "OpenAI API Anahtarı Al", "mistralApiKey": "Mistral API Anahtarı", "getMistralApiKey": "Mistral / Codestral API Anahtarı Al", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 94843217ae..c4b39e9a1d 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Tên triển khai Azure", "azureOpenAiDeploymentNameDescription": "Nhập tên triển khai trong Azure AI Studio, không phải tên mô hình cơ sở.", + "azureOpenAiRequestUrl": "Yêu cầu được gửi đến:", "getOpenAiApiKey": "Lấy khóa API OpenAI", "mistralApiKey": "Khóa API Mistral", "getMistralApiKey": "Lấy khóa API Mistral / Codestral", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 8be0fe1532..e2dd9117c6 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -550,6 +550,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure 部署名称", "azureOpenAiDeploymentNameDescription": "请输入 Azure AI Studio 中的部署名称,而不是底层模型名称。", + "azureOpenAiRequestUrl": "请求发送至:", "getOpenAiApiKey": "获取 OpenAI API 密钥", "mistralApiKey": "Mistral API 密钥", "getMistralApiKey": "获取 Mistral / Codestral API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 65eb5c67a5..f07f2c9ab3 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -577,6 +577,7 @@ "azureOpenAiBaseUrlPlaceholder": "https://.openai.azure.com/openai", "azureOpenAiDeploymentName": "Azure 部署名稱", "azureOpenAiDeploymentNameDescription": "請輸入 Azure AI Studio 中的部署名稱,而不是底層模型名稱。", + "azureOpenAiRequestUrl": "請求傳送至:", "getOpenAiApiKey": "取得 OpenAI API 金鑰", "mistralApiKey": "Mistral API 金鑰", "getMistralApiKey": "取得 Mistral/Codestral API 金鑰",