add: responses api support for azure (#1321)
- Use Responses API for Azure provider endpoints - Added a unit test to catch regression on the change from `/chat/completions` to `/responses` - Updated the default AOAI api version from `2025-03-01-preview` to `2025-04-01-preview` to avoid user/400 errors due to missing summary support in the March API version. - Changes have been tested locally on AOAI endpoints
This commit is contained in:
@@ -469,7 +469,7 @@ export OPENAI_API_KEY="your-api-key-here"
|
|||||||
|
|
||||||
# Azure OpenAI
|
# Azure OpenAI
|
||||||
export AZURE_OPENAI_API_KEY="your-azure-api-key-here"
|
export AZURE_OPENAI_API_KEY="your-azure-api-key-here"
|
||||||
export AZURE_OPENAI_API_VERSION="2025-03-01-preview" (Optional)
|
export AZURE_OPENAI_API_VERSION="2025-04-01-preview" (Optional)
|
||||||
|
|
||||||
# OpenRouter
|
# OpenRouter
|
||||||
export OPENROUTER_API_KEY="your-openrouter-key-here"
|
export OPENROUTER_API_KEY="your-openrouter-key-here"
|
||||||
|
|||||||
@@ -800,7 +800,8 @@ export class AgentLoop {
|
|||||||
|
|
||||||
const responseCall =
|
const responseCall =
|
||||||
!this.config.provider ||
|
!this.config.provider ||
|
||||||
this.config.provider?.toLowerCase() === "openai"
|
this.config.provider?.toLowerCase() === "openai" ||
|
||||||
|
this.config.provider?.toLowerCase() === "azure"
|
||||||
? (params: ResponseCreateParams) =>
|
? (params: ResponseCreateParams) =>
|
||||||
this.oai.responses.create(params)
|
this.oai.responses.create(params)
|
||||||
: (params: ResponseCreateParams) =>
|
: (params: ResponseCreateParams) =>
|
||||||
@@ -1188,7 +1189,8 @@ export class AgentLoop {
|
|||||||
|
|
||||||
const responseCall =
|
const responseCall =
|
||||||
!this.config.provider ||
|
!this.config.provider ||
|
||||||
this.config.provider?.toLowerCase() === "openai"
|
this.config.provider?.toLowerCase() === "openai" ||
|
||||||
|
this.config.provider?.toLowerCase() === "azure"
|
||||||
? (params: ResponseCreateParams) =>
|
? (params: ResponseCreateParams) =>
|
||||||
this.oai.responses.create(params)
|
this.oai.responses.create(params)
|
||||||
: (params: ResponseCreateParams) =>
|
: (params: ResponseCreateParams) =>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const OPENAI_BASE_URL = process.env["OPENAI_BASE_URL"] || "";
|
|||||||
export let OPENAI_API_KEY = process.env["OPENAI_API_KEY"] || "";
|
export let OPENAI_API_KEY = process.env["OPENAI_API_KEY"] || "";
|
||||||
|
|
||||||
export const AZURE_OPENAI_API_VERSION =
|
export const AZURE_OPENAI_API_VERSION =
|
||||||
process.env["AZURE_OPENAI_API_VERSION"] || "2025-03-01-preview";
|
process.env["AZURE_OPENAI_API_VERSION"] || "2025-04-01-preview";
|
||||||
|
|
||||||
export const DEFAULT_REASONING_EFFORT = "high";
|
export const DEFAULT_REASONING_EFFORT = "high";
|
||||||
export const OPENAI_ORGANIZATION = process.env["OPENAI_ORGANIZATION"] || "";
|
export const OPENAI_ORGANIZATION = process.env["OPENAI_ORGANIZATION"] || "";
|
||||||
|
|||||||
107
codex-cli/tests/agent-azure-responses-endpoint.test.ts
Normal file
107
codex-cli/tests/agent-azure-responses-endpoint.test.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* tests/agent-azure-responses-endpoint.test.ts
|
||||||
|
*
|
||||||
|
* Verifies that AgentLoop calls the `/responses` endpoint when provider is set to Azure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
// Fake stream that yields a completed response event
|
||||||
|
class FakeStream {
|
||||||
|
async *[Symbol.asyncIterator]() {
|
||||||
|
yield {
|
||||||
|
type: "response.completed",
|
||||||
|
response: { id: "azure_resp", status: "completed", output: [] },
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastCreateParams: any = null;
|
||||||
|
|
||||||
|
vi.mock("openai", () => {
|
||||||
|
class FakeDefaultClient {
|
||||||
|
public responses = {
|
||||||
|
create: async (params: any) => {
|
||||||
|
lastCreateParams = params;
|
||||||
|
return new FakeStream();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
class FakeAzureClient {
|
||||||
|
public responses = {
|
||||||
|
create: async (params: any) => {
|
||||||
|
lastCreateParams = params;
|
||||||
|
return new FakeStream();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
class APIConnectionTimeoutError extends Error {}
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
default: FakeDefaultClient,
|
||||||
|
AzureOpenAI: FakeAzureClient,
|
||||||
|
APIConnectionTimeoutError,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stub approvals to bypass command approval logic
|
||||||
|
vi.mock("../src/approvals.js", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
alwaysApprovedCommands: new Set<string>(),
|
||||||
|
canAutoApprove: () => ({ type: "auto-approve", runInSandbox: false }),
|
||||||
|
isSafeCommand: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Stub format-command to avoid formatting side effects
|
||||||
|
vi.mock("../src/format-command.js", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
formatCommandForDisplay: (cmd: Array<string>) => cmd.join(" "),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Stub internal logging to keep output clean
|
||||||
|
vi.mock("../src/utils/agent/log.js", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
log: () => {},
|
||||||
|
isLoggingEnabled: () => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { AgentLoop } from "../src/utils/agent/agent-loop.js";
|
||||||
|
|
||||||
|
describe("AgentLoop Azure provider responses endpoint", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
lastCreateParams = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls the /responses endpoint when provider is azure", async () => {
|
||||||
|
const cfg: any = {
|
||||||
|
model: "test-model",
|
||||||
|
provider: "azure",
|
||||||
|
instructions: "",
|
||||||
|
disableResponseStorage: false,
|
||||||
|
notify: false,
|
||||||
|
};
|
||||||
|
const loop = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
|
model: cfg.model,
|
||||||
|
config: cfg,
|
||||||
|
instructions: cfg.instructions,
|
||||||
|
approvalPolicy: { mode: "suggest" } as any,
|
||||||
|
onItem: () => {},
|
||||||
|
onLoading: () => {},
|
||||||
|
getCommandConfirmation: async () => ({ review: "yes" }) as any,
|
||||||
|
onLastResponseId: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await loop.run([
|
||||||
|
{
|
||||||
|
type: "message",
|
||||||
|
role: "user",
|
||||||
|
content: [{ type: "input_text", text: "hello" }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(lastCreateParams).not.toBeNull();
|
||||||
|
expect(lastCreateParams.model).toBe(cfg.model);
|
||||||
|
expect(Array.isArray(lastCreateParams.input)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user