mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-25 23:47:20 +00:00
fix: use azure-openai-responses for Azure custom providers (#50851) (thanks @kunalk16)
* Add azure-openai-responses * Unit tests update for updated API * Add entry for PR #50851 * Add comma to address PR comment Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Address PR comment on sanitization of output * Address review comment * Revert commits * Revert commit * Update changelog stating Azure OpenAI only Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Add references * Address PR comment on sanitization of output * Address review comment * Revert commits * Revert commit * Address PR comment on sanitization of output * Address review comment * Revert commits * Revert commit * Fix generated file * Add azure openai responses to OPENAI_RESPONSES_APIS * Add azure openai responses to createParallelToolCallsWrapper * Adding azure openai responses to attempt.ts * Add azure openai responses to google.ts * Address PR comment on sanitization of output * Revert commit * Address PR comment on sanitization of output * Revert commit * Address PR comment on sanitization of output * Revert commit * Fix changelog * Fix linting * fix: cover azure responses wrapper path (#50851) (thanks @kunalk16) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
@@ -364,6 +364,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
applyModelId: string;
|
||||
model:
|
||||
| Model<"openai-responses">
|
||||
| Model<"azure-openai-responses">
|
||||
| Model<"openai-codex-responses">
|
||||
| Model<"openai-completions">
|
||||
| Model<"anthropic-messages">;
|
||||
@@ -418,7 +419,11 @@ describe("applyExtraParamsToAgent", () => {
|
||||
function runParallelToolCallsPayloadMutationCase(params: {
|
||||
applyProvider: string;
|
||||
applyModelId: string;
|
||||
model: Model<"openai-completions"> | Model<"openai-responses"> | Model<"anthropic-messages">;
|
||||
model:
|
||||
| Model<"openai-completions">
|
||||
| Model<"openai-responses">
|
||||
| Model<"azure-openai-responses">
|
||||
| Model<"anthropic-messages">;
|
||||
cfg?: Record<string, unknown>;
|
||||
extraParamsOverride?: Record<string, unknown>;
|
||||
payload?: Record<string, unknown>;
|
||||
@@ -656,6 +661,34 @@ describe("applyExtraParamsToAgent", () => {
|
||||
expect(payload.parallel_tool_calls).toBe(true);
|
||||
});
|
||||
|
||||
it("injects parallel_tool_calls for azure-openai-responses payloads when configured", () => {
|
||||
const payload = runParallelToolCallsPayloadMutationCase({
|
||||
applyProvider: "azure-openai-responses",
|
||||
applyModelId: "gpt-5",
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"azure-openai-responses/gpt-5": {
|
||||
params: {
|
||||
parallelToolCalls: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
model: {
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-5",
|
||||
baseUrl: "https://example.openai.azure.com/openai/v1",
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
});
|
||||
|
||||
expect(payload.parallel_tool_calls).toBe(true);
|
||||
});
|
||||
|
||||
it("does not inject parallel_tool_calls for unsupported APIs", () => {
|
||||
const payload = runParallelToolCallsPayloadMutationCase({
|
||||
applyProvider: "anthropic",
|
||||
@@ -2664,11 +2697,11 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
},
|
||||
model: {
|
||||
api: "openai-responses",
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-5.4",
|
||||
baseUrl: "https://example.openai.azure.com/openai/v1",
|
||||
} as unknown as Model<"openai-responses">,
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
});
|
||||
expect(payload).not.toHaveProperty("service_tier");
|
||||
});
|
||||
@@ -2829,7 +2862,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
applyProvider: "azure-openai-responses",
|
||||
applyModelId: "gpt-4o",
|
||||
model: {
|
||||
api: "openai-responses",
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-4o",
|
||||
name: "gpt-4o",
|
||||
@@ -2840,7 +2873,7 @@ describe("applyExtraParamsToAgent", () => {
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 16_384,
|
||||
compat: { supportsStore: false },
|
||||
} as unknown as Model<"openai-responses">,
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
});
|
||||
expect(payload).not.toHaveProperty("store");
|
||||
});
|
||||
@@ -2917,11 +2950,11 @@ describe("applyExtraParamsToAgent", () => {
|
||||
applyProvider: "azure-openai-responses",
|
||||
applyModelId: "gpt-4o",
|
||||
model: {
|
||||
api: "openai-responses",
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-4o",
|
||||
baseUrl: "https://example.openai.azure.com/openai/v1",
|
||||
} as unknown as Model<"openai-responses">,
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
});
|
||||
expect(payload).not.toHaveProperty("context_management");
|
||||
});
|
||||
@@ -2945,11 +2978,11 @@ describe("applyExtraParamsToAgent", () => {
|
||||
},
|
||||
},
|
||||
model: {
|
||||
api: "openai-responses",
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-4o",
|
||||
baseUrl: "https://example.openai.azure.com/openai/v1",
|
||||
} as unknown as Model<"openai-responses">,
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
});
|
||||
expect(payload.context_management).toEqual([
|
||||
{
|
||||
@@ -3081,16 +3114,16 @@ describe("applyExtraParamsToAgent", () => {
|
||||
expect(payload.prompt_cache_retention).toBe("24h");
|
||||
});
|
||||
|
||||
it("keeps prompt cache fields for direct Azure OpenAI openai-responses endpoints", () => {
|
||||
it("keeps prompt cache fields for direct Azure OpenAI azure-openai-responses endpoints", () => {
|
||||
const payload = runResponsesPayloadMutationCase({
|
||||
applyProvider: "azure-openai-responses",
|
||||
applyModelId: "gpt-4o",
|
||||
model: {
|
||||
api: "openai-responses",
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
id: "gpt-4o",
|
||||
baseUrl: "https://example.openai.azure.com/openai/v1",
|
||||
} as unknown as Model<"openai-responses">,
|
||||
} as unknown as Model<"azure-openai-responses">,
|
||||
payload: {
|
||||
store: false,
|
||||
prompt_cache_key: "session-azure",
|
||||
|
||||
@@ -22,7 +22,7 @@ function createMockStream(): ReturnType<StreamFn> {
|
||||
}
|
||||
|
||||
type RunExtraParamsCaseParams<
|
||||
TApi extends "openai-completions" | "openai-responses",
|
||||
TApi extends "openai-completions" | "openai-responses" | "azure-openai-responses",
|
||||
TPayload extends Record<string, unknown>,
|
||||
> = {
|
||||
applyModelId?: string;
|
||||
@@ -36,7 +36,7 @@ type RunExtraParamsCaseParams<
|
||||
};
|
||||
|
||||
export function runExtraParamsCase<
|
||||
TApi extends "openai-completions" | "openai-responses",
|
||||
TApi extends "openai-completions" | "openai-responses" | "azure-openai-responses",
|
||||
TPayload extends Record<string, unknown>,
|
||||
>(params: RunExtraParamsCaseParams<TApi, TPayload>): ExtraParamsCapture<TPayload> {
|
||||
const captured: ExtraParamsCapture<TPayload> = {
|
||||
|
||||
@@ -278,7 +278,11 @@ function createParallelToolCallsWrapper(
|
||||
): StreamFn {
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
return (model, context, options) => {
|
||||
if (model.api !== "openai-completions" && model.api !== "openai-responses") {
|
||||
if (
|
||||
model.api !== "openai-completions" &&
|
||||
model.api !== "openai-responses" &&
|
||||
model.api !== "azure-openai-responses"
|
||||
) {
|
||||
return underlying(model, context, options);
|
||||
}
|
||||
log.debug(
|
||||
@@ -439,7 +443,10 @@ function applyPostPluginStreamWrappers(
|
||||
log.debug(
|
||||
`applying OpenAI text verbosity=${openAITextVerbosity} for ${ctx.provider}/${ctx.modelId}`,
|
||||
);
|
||||
ctx.agent.streamFn = createOpenAITextVerbosityWrapper(ctx.agent.streamFn, openAITextVerbosity);
|
||||
ctx.agent.streamFn = createOpenAITextVerbosityWrapper(
|
||||
ctx.agent.streamFn,
|
||||
openAITextVerbosity,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +627,9 @@ export async function sanitizeSessionHistory(params: {
|
||||
);
|
||||
|
||||
const isOpenAIResponsesApi =
|
||||
params.modelApi === "openai-responses" || params.modelApi === "openai-codex-responses";
|
||||
params.modelApi === "openai-responses" ||
|
||||
params.modelApi === "openai-codex-responses" ||
|
||||
params.modelApi === "azure-openai-responses";
|
||||
const hasSnapshot = Boolean(params.provider || params.modelApi || params.modelId);
|
||||
const priorSnapshot = hasSnapshot ? readLastModelSnapshot(params.sessionManager) : null;
|
||||
const modelChanged = priorSnapshot
|
||||
|
||||
@@ -99,6 +99,7 @@ function normalizeResolvedTransportApi(api: unknown): ModelDefinitionConfig["api
|
||||
case "openai-codex-responses":
|
||||
case "openai-completions":
|
||||
case "openai-responses":
|
||||
case "azure-openai-responses":
|
||||
return api;
|
||||
default:
|
||||
return undefined;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { streamWithPayloadPatch } from "./stream-payload-utils.js";
|
||||
type OpenAIServiceTier = "auto" | "default" | "flex" | "priority";
|
||||
type OpenAITextVerbosity = "low" | "medium" | "high";
|
||||
|
||||
const OPENAI_RESPONSES_APIS = new Set(["openai-responses"]);
|
||||
const OPENAI_RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses"]);
|
||||
const OPENAI_RESPONSES_PROVIDERS = new Set(["openai", "azure-openai", "azure-openai-responses"]);
|
||||
|
||||
function isDirectOpenAIBaseUrl(baseUrl: unknown): boolean {
|
||||
@@ -358,7 +358,9 @@ export function createOpenAIFastModeWrapper(baseStreamFn: StreamFn | undefined):
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
return (model, context, options) => {
|
||||
if (
|
||||
(model.api !== "openai-responses" && model.api !== "openai-codex-responses") ||
|
||||
(model.api !== "openai-responses" &&
|
||||
model.api !== "openai-codex-responses" &&
|
||||
model.api !== "azure-openai-responses") ||
|
||||
(model.provider !== "openai" && model.provider !== "openai-codex")
|
||||
) {
|
||||
return underlying(model, context, options);
|
||||
|
||||
@@ -992,6 +992,7 @@ export async function runEmbeddedAttempt(
|
||||
|
||||
if (
|
||||
params.model.api === "openai-responses" ||
|
||||
params.model.api === "azure-openai-responses" ||
|
||||
params.model.api === "openai-codex-responses"
|
||||
) {
|
||||
const inner = activeSession.agent.streamFn;
|
||||
|
||||
@@ -81,7 +81,9 @@ export function resolveTranscriptPolicy(params: {
|
||||
const requiresOpenAiCompatibleToolIdSanitization =
|
||||
params.modelApi === "openai-completions" ||
|
||||
(!isOpenAi &&
|
||||
(params.modelApi === "openai-responses" || params.modelApi === "openai-codex-responses"));
|
||||
(params.modelApi === "openai-responses" ||
|
||||
params.modelApi === "openai-codex-responses" ||
|
||||
params.modelApi === "azure-openai-responses"));
|
||||
|
||||
// Anthropic Claude endpoints can reject replayed `thinking` blocks unless the
|
||||
// original signatures are preserved byte-for-byte. Drop them at send-time to
|
||||
|
||||
@@ -482,7 +482,7 @@ describe("applyCustomApiConfig", () => {
|
||||
const provider = result.config.models?.providers?.[providerId];
|
||||
|
||||
expect(provider?.baseUrl).toBe("https://user123-resource.openai.azure.com/openai/v1");
|
||||
expect(provider?.api).toBe("openai-responses");
|
||||
expect(provider?.api).toBe("azure-openai-responses");
|
||||
expect(provider?.authHeader).toBe(false);
|
||||
expect(provider?.headers).toEqual({ "api-key": "abcd1234" });
|
||||
|
||||
@@ -568,7 +568,7 @@ describe("applyCustomApiConfig", () => {
|
||||
expect(result.providerIdRenamedFrom).toBeUndefined();
|
||||
const provider = result.config.models?.providers?.[oldProviderId];
|
||||
expect(provider?.baseUrl).toBe("https://my-resource.openai.azure.com/openai/v1");
|
||||
expect(provider?.api).toBe("openai-responses");
|
||||
expect(provider?.api).toBe("azure-openai-responses");
|
||||
expect(provider?.authHeader).toBe(false);
|
||||
expect(provider?.headers).toEqual({ "api-key": "key789" });
|
||||
});
|
||||
|
||||
@@ -687,7 +687,7 @@ export function applyCustomApiConfig(params: ApplyCustomApiConfigParams): Custom
|
||||
normalizeOptionalProviderApiKey(existingApiKey);
|
||||
|
||||
const providerApi = isAzureOpenAi
|
||||
? ("openai-responses" as const)
|
||||
? ("azure-openai-responses" as const)
|
||||
: resolveProviderApi(params.compatibility);
|
||||
const azureHeaders = isAzure && normalizedApiKey ? { "api-key": normalizedApiKey } : undefined;
|
||||
|
||||
|
||||
@@ -1038,6 +1038,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
|
||||
"github-copilot",
|
||||
"bedrock-converse-stream",
|
||||
"ollama",
|
||||
"azure-openai-responses",
|
||||
],
|
||||
},
|
||||
injectNumCtxForOpenAICompat: {
|
||||
@@ -1142,6 +1143,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
|
||||
"github-copilot",
|
||||
"bedrock-converse-stream",
|
||||
"ollama",
|
||||
"azure-openai-responses",
|
||||
],
|
||||
},
|
||||
reasoning: {
|
||||
|
||||
@@ -10,6 +10,7 @@ export const MODEL_APIS = [
|
||||
"github-copilot",
|
||||
"bedrock-converse-stream",
|
||||
"ollama",
|
||||
"azure-openai-responses",
|
||||
] as const;
|
||||
|
||||
export type ModelApi = (typeof MODEL_APIS)[number];
|
||||
|
||||
Reference in New Issue
Block a user