Support modern Microsoft Foundry Responses API endpoints:

- support *.ai.azure.com endpoints
- normalize away the /responses postfix in the query path which by
  default is added on Microsoft Foundry UI
This commit is contained in:
Konstantin Gukov 2026-06-23 15:38:43 +02:00
parent 6a4813a7ca
commit e3dcb244f8
4 changed files with 23 additions and 4 deletions

View file

@ -368,7 +368,7 @@ Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` ex
|----------|------------------------|
| OpenAI | `OPENAI_API_KEY` |
| Ant Ling | `ANT_LING_API_KEY` |
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.openai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com` and `*.cognitiveservices.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. |
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.ai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com`, `*.cognitiveservices.azure.com` and `*.ai.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. |
| Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
| DeepSeek | `DEEPSEEK_API_KEY` |
| NVIDIA NIM | `NVIDIA_API_KEY` |

View file

@ -180,12 +180,20 @@ function normalizeAzureBaseUrl(baseUrl: string): string {
}
const isAzureHost =
url.hostname.endsWith(".openai.azure.com") || url.hostname.endsWith(".cognitiveservices.azure.com");
url.hostname.endsWith(".openai.azure.com") ||
url.hostname.endsWith(".cognitiveservices.azure.com") ||
url.hostname.endsWith(".ai.azure.com");
const normalizedPath = url.pathname.replace(/\/+$/, "");
// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK
// can append /deployments/<model>/... and ?api-version=v1 correctly.
if (isAzureHost && (normalizedPath === "" || normalizedPath === "/" || normalizedPath === "/openai")) {
if (
isAzureHost &&
(normalizedPath === "" ||
normalizedPath === "/" ||
normalizedPath === "/openai" ||
normalizedPath === "/openai/v1/responses")
) {
url.pathname = "/openai/v1";
url.search = "";
}

View file

@ -96,6 +96,11 @@ describe("azure-openai-responses base URL normalization", () => {
expect(baseURL).toBe("https://marc-quicktests-resource.cognitiveservices.azure.com/openai/v1");
});
it("normalizes Microsoft Foundry root endpoints to /openai/v1", async () => {
const baseURL = await captureClientBaseUrl("https://marc-quicktests-resource.ai.azure.com");
expect(baseURL).toBe("https://marc-quicktests-resource.ai.azure.com/openai/v1");
});
it("normalizes Azure OpenAI root endpoints to /openai/v1", async () => {
const baseURL = await captureClientBaseUrl("https://my-resource.openai.azure.com");
expect(baseURL).toBe("https://my-resource.openai.azure.com/openai/v1");
@ -111,6 +116,11 @@ describe("azure-openai-responses base URL normalization", () => {
expect(baseURL).toBe("https://my-resource.cognitiveservices.azure.com/openai/v1");
});
it("normalizes /openai/v1/responses to /openai/v1", async () => {
const baseURL = await captureClientBaseUrl("https://my-resource.services.ai.azure.com/openai/v1/responses");
expect(baseURL).toBe("https://my-resource.services.ai.azure.com/openai/v1");
});
it("preserves explicit non-Azure proxy paths", async () => {
const baseURL = await captureClientBaseUrl("https://my-proxy.example.com/v1");
expect(baseURL).toBe("https://my-proxy.example.com/v1");

View file

@ -156,8 +156,9 @@ OAuth credentials are also stored here after `/login` and managed automatically.
```bash
export AZURE_OPENAI_API_KEY=...
export AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com
export AZURE_OPENAI_BASE_URL=https://your-resource.ai.azure.com
# also supported: https://your-resource.cognitiveservices.azure.com
# also supported: https://your-resource.openai.azure.com
# root endpoints are auto-normalized to /openai/v1
# or use resource name instead of base URL
export AZURE_OPENAI_RESOURCE_NAME=your-resource