Skip to content

Commit f212176

Browse files
committed
fix(azure): preserve equals in deployment maps
1 parent 611adb2 commit f212176

4 files changed

Lines changed: 69 additions & 33 deletions

File tree

src/agents/openai-transport-stream.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
import type { ModelCompatConfig } from "../config/types.models.js";
1616
import { getEnvApiKey } from "../llm/env-api-keys.js";
1717
import { calculateCost } from "../llm/model-utils.js";
18+
import { resolveAzureDeploymentNameFromMap } from "../llm/providers/azure-deployment-map.js";
1819
import { convertMessages } from "../llm/providers/openai-completions.js";
1920
import { clampOpenAIPromptCacheKey } from "../llm/providers/openai-prompt-cache.js";
2021
import type { Api, Context, Model } from "../llm/types.js";
@@ -2269,16 +2270,10 @@ function normalizeAzureBaseUrl(baseUrl: string): string {
22692270
}
22702271

22712272
function resolveAzureDeploymentName(model: Model): string {
2272-
const deploymentMap = process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP;
2273-
if (deploymentMap) {
2274-
for (const entry of deploymentMap.split(",")) {
2275-
const [modelId, deploymentName] = entry.split("=", 2).map((value) => value?.trim());
2276-
if (modelId === model.id && deploymentName) {
2277-
return deploymentName;
2278-
}
2279-
}
2280-
}
2281-
return model.id;
2273+
return resolveAzureDeploymentNameFromMap({
2274+
modelId: model.id,
2275+
deploymentMap: process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP,
2276+
});
22822277
}
22832278

22842279
function createAzureOpenAIClient(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
parseAzureDeploymentNameMap,
4+
resolveAzureDeploymentNameFromMap,
5+
} from "./azure-deployment-map.js";
6+
7+
describe("Azure deployment name map", () => {
8+
it("preserves equals signs inside deployment names", () => {
9+
const map = parseAzureDeploymentNameMap("gpt-5=deployment=blue, ignored, gpt-4 = prod = east ");
10+
11+
expect(map.get("gpt-5")).toBe("deployment=blue");
12+
expect(map.get("gpt-4")).toBe("prod = east");
13+
expect(
14+
resolveAzureDeploymentNameFromMap({
15+
modelId: "gpt-5",
16+
deploymentMap: "gpt-5=deployment=blue",
17+
}),
18+
).toBe("deployment=blue");
19+
});
20+
21+
it("falls back to the model id when the map has no usable entry", () => {
22+
expect(
23+
resolveAzureDeploymentNameFromMap({
24+
modelId: "gpt-5",
25+
deploymentMap: "other=deployment,missing-value=",
26+
}),
27+
).toBe("gpt-5");
28+
});
29+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export function parseAzureDeploymentNameMap(value: string | undefined): Map<string, string> {
2+
const map = new Map<string, string>();
3+
if (!value) {
4+
return map;
5+
}
6+
for (const entry of value.split(",")) {
7+
const trimmed = entry.trim();
8+
if (!trimmed) {
9+
continue;
10+
}
11+
const separator = trimmed.indexOf("=");
12+
if (separator <= 0) {
13+
continue;
14+
}
15+
const modelId = trimmed.slice(0, separator).trim();
16+
const deploymentName = trimmed.slice(separator + 1).trim();
17+
if (!modelId || !deploymentName) {
18+
continue;
19+
}
20+
map.set(modelId, deploymentName);
21+
}
22+
return map;
23+
}
24+
25+
export function resolveAzureDeploymentNameFromMap(params: {
26+
modelId: string;
27+
deploymentMap?: string;
28+
}): string {
29+
return parseAzureDeploymentNameMap(params.deploymentMap).get(params.modelId) || params.modelId;
30+
}

src/llm/providers/azure-openai-responses.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from "../types.js";
1414
import { AssistantMessageEventStream } from "../utils/event-stream.js";
1515
import { headersToRecord } from "../utils/headers.js";
16+
import { resolveAzureDeploymentNameFromMap } from "./azure-deployment-map.js";
1617
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
1718
import {
1819
convertResponsesMessages,
@@ -29,36 +30,17 @@ const AZURE_TOOL_CALL_PROVIDERS = new Set([
2930
"azure-openai-responses",
3031
]);
3132

32-
function parseDeploymentNameMap(value: string | undefined): Map<string, string> {
33-
const map = new Map<string, string>();
34-
if (!value) {
35-
return map;
36-
}
37-
for (const entry of value.split(",")) {
38-
const trimmed = entry.trim();
39-
if (!trimmed) {
40-
continue;
41-
}
42-
const [modelId, deploymentName] = trimmed.split("=", 2);
43-
if (!modelId || !deploymentName) {
44-
continue;
45-
}
46-
map.set(modelId.trim(), deploymentName.trim());
47-
}
48-
return map;
49-
}
50-
5133
function resolveDeploymentName(
5234
model: Model<"azure-openai-responses">,
5335
options?: AzureOpenAIResponsesOptions,
5436
): string {
5537
if (options?.azureDeploymentName) {
5638
return options.azureDeploymentName;
5739
}
58-
const mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(
59-
model.id,
60-
);
61-
return mappedDeployment || model.id;
40+
return resolveAzureDeploymentNameFromMap({
41+
modelId: model.id,
42+
deploymentMap: process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP,
43+
});
6244
}
6345

6446
function formatAzureOpenAIError(error: unknown): string {

0 commit comments

Comments
 (0)