fix: append /v1 to OpenAI Compatible base URLs for llama.cpp compatibility

- Automatically append /v1 to base URLs that are plain URLs without a path
- This fixes compatibility with llama.cpp servers that expose OpenAI-compatible endpoints
- Added tests to verify the URL handling works correctly

Fixes #7065
This commit is contained in:
Roo Code 2025-08-13 21:48:59 +00:00
parent ca6f261cce
commit 3c89c43818
2 changed files with 52 additions and 1 deletions

View file

@ -99,6 +99,41 @@ describe("OpenAiNativeHandler", () => {
})
expect(handlerWithoutKey).toBeInstanceOf(OpenAiNativeHandler)
})
it("should append /v1 to base URL when it's a plain URL without path", () => {
// Test with plain URL (like llama.cpp server)
const handler1 = new OpenAiNativeHandler({
apiModelId: "gpt-4.1",
openAiNativeApiKey: "test-api-key",
openAiNativeBaseUrl: "http://127.0.0.1:8080",
})
expect(handler1).toBeInstanceOf(OpenAiNativeHandler)
// The client should have the /v1 appended internally
// Test with URL that already has /v1
const handler2 = new OpenAiNativeHandler({
apiModelId: "gpt-4.1",
openAiNativeApiKey: "test-api-key",
openAiNativeBaseUrl: "http://localhost:8080/v1",
})
expect(handler2).toBeInstanceOf(OpenAiNativeHandler)
// Test with URL that has a different path
const handler3 = new OpenAiNativeHandler({
apiModelId: "gpt-4.1",
openAiNativeApiKey: "test-api-key",
openAiNativeBaseUrl: "https://api.openai.com/v1",
})
expect(handler3).toBeInstanceOf(OpenAiNativeHandler)
// Test with URL with trailing slash
const handler4 = new OpenAiNativeHandler({
apiModelId: "gpt-4.1",
openAiNativeApiKey: "test-api-key",
openAiNativeBaseUrl: "http://localhost:11434/",
})
expect(handler4).toBeInstanceOf(OpenAiNativeHandler)
})
})
describe("createMessage", () => {

View file

@ -57,7 +57,23 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
this.options.enableGpt5ReasoningSummary = true
}
const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
this.client = new OpenAI({ baseURL: this.options.openAiNativeBaseUrl, apiKey })
// Handle base URL - append /v1 if it's a plain URL without a path
let baseURL = this.options.openAiNativeBaseUrl
if (baseURL && !baseURL.includes("/v1")) {
// Check if it's a URL without any path (just protocol://host:port)
try {
const url = new URL(baseURL)
// If the pathname is just '/', append /v1
if (url.pathname === "/") {
baseURL = baseURL.replace(/\/$/, "") + "/v1"
}
} catch {
// If URL parsing fails, leave it as is
}
}
this.client = new OpenAI({ baseURL, apiKey })
}
private normalizeGpt5Usage(usage: any, model: OpenAiNativeModel): ApiStreamUsageChunk | undefined {