Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/azure-responses-v1-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Fix OpenAI Responses Azure URL normalization so Azure v1 endpoints avoid unsupported `api-version` parameters.
153 changes: 153 additions & 0 deletions src/api/providers/__tests__/openai-responses.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// npx vitest run api/providers/__tests__/openai-responses.spec.ts

import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI, { AzureOpenAI } from "openai"

import { OpenAiCompatibleResponsesHandler } from "../openai-responses"
import { ApiHandlerOptions } from "../../../shared/api"
Expand Down Expand Up @@ -100,4 +101,156 @@ describe("OpenAiCompatibleResponsesHandler", () => {
}),
)
})

it("normalizes fallback URL without duplicating /v1", async () => {
const handler = new OpenAiCompatibleResponsesHandler({
openAiApiKey: "test-key",
openAiBaseUrl: "https://api.example.com/v1",
openAiModelId: "gpt-4o",
} satisfies ApiHandlerOptions)

const mockFetch = vi.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))

const stream = handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}

expect(mockFetch).toHaveBeenCalledWith(
"https://api.example.com/v1/responses",
expect.objectContaining({
method: "POST",
}),
)
})

it("rejects Azure AI Inference endpoints for Responses API", async () => {
const handler = new OpenAiCompatibleResponsesHandler({
openAiApiKey: "test-key",
openAiBaseUrl: "https://myresource.services.ai.azure.com/models",
openAiModelId: "gpt-5.2-codex",
} satisfies ApiHandlerOptions)

const stream = handler.createMessage(systemPrompt, messages)

await expect(async () => {
for await (const _chunk of stream) {
}
}).rejects.toThrow("Azure AI Inference endpoints")

await expect(handler.completePrompt("Test prompt")).rejects.toThrow("Azure AI Inference endpoints")
})

Comment on lines +105 to +152
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Unmarked responses tests additions 📘 Rule violation ⛯ Reliability

New/modified tests in a shared upstream src/ test file were added without kilocode_change
markers even though they validate newly introduced Kilo-specific behavior. This can cause avoidable
conflicts during upstream syncs.
Agent Prompt
## Issue description
Kilo-specific edits in shared upstream files under `src/` must include `kilocode_change` markers. The PR adds new test cases for Responses/Azure endpoint normalization and rejection without markers.

## Issue Context
Other modified shared files in this PR already use `kilocode_change` markers, so leaving related tests unmarked is inconsistent and increases upstream sync friction.

## Fix Focus Areas
- src/api/providers/__tests__/openai-responses.spec.ts[105-152]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

it("does not pass chat-completions path override for Azure OpenAI Responses calls", async () => {
const handler = new OpenAiCompatibleResponsesHandler({
openAiApiKey: "test-key",
openAiBaseUrl: "https://myresource.openai.azure.com/openai/v1",
openAiUseAzure: true,
openAiModelId: "my-deployment",
} satisfies ApiHandlerOptions)

mockResponsesCreate.mockResolvedValueOnce({
[Symbol.asyncIterator]: async function* () {
yield { type: "response.text.delta", delta: "hello" }
yield {
type: "response.done",
response: {
usage: {
prompt_tokens: 1,
completion_tokens: 1,
},
},
}
},
})

const stream = handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}

expect(mockResponsesCreate).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({
signal: expect.any(AbortSignal),
}),
)
const options = mockResponsesCreate.mock.calls[0][1]
expect(options.path).toBeUndefined()
})

it("uses Azure fallback auth and normalizes Azure deployment chat URL to /openai/v1/responses without api-version", async () => {
const handler = new OpenAiCompatibleResponsesHandler({
openAiApiKey: "test-key",
openAiBaseUrl:
"https://myresource.openai.azure.com/openai/deployments/my-deployment/chat/completions?api-version=2024-05-01-preview",
openAiUseAzure: true,
azureApiVersion: "2024-08-01-preview",
openAiModelId: "my-deployment",
} satisfies ApiHandlerOptions)

const mockFetch = vi.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))

const stream = handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}

expect(mockFetch).toHaveBeenCalledTimes(1)
const [requestUrl, requestOptions] = mockFetch.mock.calls[0]
expect(requestUrl).toBe("https://myresource.openai.azure.com/openai/v1/responses")
expect(requestUrl).not.toContain("api-version=")
expect(requestOptions.headers["api-key"]).toBe("test-key")
expect(requestOptions.headers.Authorization).toBeUndefined()
})

it("normalizes cognitiveservices Azure endpoint to /openai/v1/responses without api-version", async () => {
const handler = new OpenAiCompatibleResponsesHandler({
openAiApiKey: "test-key",
openAiBaseUrl: "https://myresource.cognitiveservices.azure.com",
openAiUseAzure: true,
azureApiVersion: "2024-08-01-preview",
openAiModelId: "my-deployment",
} satisfies ApiHandlerOptions)

const mockFetch = vi.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))

const stream = handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}

expect(mockFetch).toHaveBeenCalledTimes(1)
const [requestUrl, requestOptions] = mockFetch.mock.calls[0]
expect(requestUrl).toBe("https://myresource.cognitiveservices.azure.com/openai/v1/responses")
expect(requestUrl).not.toContain("api-version=")
expect(requestOptions.headers["api-key"]).toBe("test-key")
expect(requestOptions.headers.Authorization).toBeUndefined()
})
})
Loading
Loading