mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
* feat: add Ollama API key support for Turbo mode - Add ollamaApiKey field to ProviderSettings schema - Add ollamaApiKey to SECRET_STATE_KEYS for secure storage - Update Ollama and NativeOllama providers to use API key for authentication - Add UI field for Ollama API key (shown when custom base URL is provided) - Add test coverage for API key functionality This enables users to use Ollama Turbo with datacenter-grade hardware by providing an API key for authenticated Ollama instances or cloud services. * fix: use VSCodeTextField for Ollama API key field Remove non-existent ApiKeyField import and use standard VSCodeTextField with password type, matching other provider implementations * Add missing translation keys for Ollama API key support - Add providers.ollama.apiKey and providers.ollama.apiKeyHelp to all 18 language files - Support for authenticated Ollama instances and cloud services - Relates to PR #7425 * refactor: improve type safety for Ollama client configuration - Replace 'any' type with proper OllamaOptions (Config) type - Import Config type from ollama package for better type checking --------- Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Daniel Riccio <ricciodaniel98@gmail.com>
178 lines
4.5 KiB
TypeScript
178 lines
4.5 KiB
TypeScript
// npx vitest run api/providers/__tests__/ollama.spec.ts
|
|
|
|
import { Anthropic } from "@anthropic-ai/sdk"
|
|
|
|
import { OllamaHandler } from "../ollama"
|
|
import { ApiHandlerOptions } from "../../../shared/api"
|
|
|
|
const mockCreate = vitest.fn()
|
|
|
|
vitest.mock("openai", () => {
|
|
return {
|
|
__esModule: true,
|
|
default: vitest.fn().mockImplementation(() => ({
|
|
chat: {
|
|
completions: {
|
|
create: mockCreate.mockImplementation(async (options) => {
|
|
if (!options.stream) {
|
|
return {
|
|
id: "test-completion",
|
|
choices: [
|
|
{
|
|
message: { role: "assistant", content: "Test response" },
|
|
finish_reason: "stop",
|
|
index: 0,
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: 10,
|
|
completion_tokens: 5,
|
|
total_tokens: 15,
|
|
},
|
|
}
|
|
}
|
|
|
|
return {
|
|
[Symbol.asyncIterator]: async function* () {
|
|
yield {
|
|
choices: [
|
|
{
|
|
delta: { content: "Test response" },
|
|
index: 0,
|
|
},
|
|
],
|
|
usage: null,
|
|
}
|
|
yield {
|
|
choices: [
|
|
{
|
|
delta: {},
|
|
index: 0,
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: 10,
|
|
completion_tokens: 5,
|
|
total_tokens: 15,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
})),
|
|
}
|
|
})
|
|
|
|
describe("OllamaHandler", () => {
|
|
let handler: OllamaHandler
|
|
let mockOptions: ApiHandlerOptions
|
|
|
|
beforeEach(() => {
|
|
mockOptions = {
|
|
apiModelId: "llama2",
|
|
ollamaModelId: "llama2",
|
|
ollamaBaseUrl: "http://localhost:11434/v1",
|
|
}
|
|
handler = new OllamaHandler(mockOptions)
|
|
mockCreate.mockClear()
|
|
})
|
|
|
|
describe("constructor", () => {
|
|
it("should initialize with provided options", () => {
|
|
expect(handler).toBeInstanceOf(OllamaHandler)
|
|
expect(handler.getModel().id).toBe(mockOptions.ollamaModelId)
|
|
})
|
|
|
|
it("should use default base URL if not provided", () => {
|
|
const handlerWithoutUrl = new OllamaHandler({
|
|
apiModelId: "llama2",
|
|
ollamaModelId: "llama2",
|
|
})
|
|
expect(handlerWithoutUrl).toBeInstanceOf(OllamaHandler)
|
|
})
|
|
|
|
it("should use API key when provided", () => {
|
|
const handlerWithApiKey = new OllamaHandler({
|
|
apiModelId: "llama2",
|
|
ollamaModelId: "llama2",
|
|
ollamaBaseUrl: "https://ollama.com",
|
|
ollamaApiKey: "test-api-key",
|
|
})
|
|
expect(handlerWithApiKey).toBeInstanceOf(OllamaHandler)
|
|
// The API key will be used in the Authorization header
|
|
})
|
|
})
|
|
|
|
describe("createMessage", () => {
|
|
const systemPrompt = "You are a helpful assistant."
|
|
const messages: Anthropic.Messages.MessageParam[] = [
|
|
{
|
|
role: "user",
|
|
content: "Hello!",
|
|
},
|
|
]
|
|
|
|
it("should handle streaming responses", async () => {
|
|
const stream = handler.createMessage(systemPrompt, messages)
|
|
const chunks: any[] = []
|
|
for await (const chunk of stream) {
|
|
chunks.push(chunk)
|
|
}
|
|
|
|
expect(chunks.length).toBeGreaterThan(0)
|
|
const textChunks = chunks.filter((chunk) => chunk.type === "text")
|
|
expect(textChunks).toHaveLength(1)
|
|
expect(textChunks[0].text).toBe("Test response")
|
|
})
|
|
|
|
it("should handle API errors", async () => {
|
|
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
|
|
|
const stream = handler.createMessage(systemPrompt, messages)
|
|
|
|
await expect(async () => {
|
|
for await (const _chunk of stream) {
|
|
// Should not reach here
|
|
}
|
|
}).rejects.toThrow("API Error")
|
|
})
|
|
})
|
|
|
|
describe("completePrompt", () => {
|
|
it("should complete prompt successfully", async () => {
|
|
const result = await handler.completePrompt("Test prompt")
|
|
expect(result).toBe("Test response")
|
|
expect(mockCreate).toHaveBeenCalledWith({
|
|
model: mockOptions.ollamaModelId,
|
|
messages: [{ role: "user", content: "Test prompt" }],
|
|
temperature: 0,
|
|
stream: false,
|
|
})
|
|
})
|
|
|
|
it("should handle API errors", async () => {
|
|
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
|
await expect(handler.completePrompt("Test prompt")).rejects.toThrow("Ollama completion error: API Error")
|
|
})
|
|
|
|
it("should handle empty response", async () => {
|
|
mockCreate.mockResolvedValueOnce({
|
|
choices: [{ message: { content: "" } }],
|
|
})
|
|
const result = await handler.completePrompt("Test prompt")
|
|
expect(result).toBe("")
|
|
})
|
|
})
|
|
|
|
describe("getModel", () => {
|
|
it("should return model info", () => {
|
|
const modelInfo = handler.getModel()
|
|
expect(modelInfo.id).toBe(mockOptions.ollamaModelId)
|
|
expect(modelInfo.info).toBeDefined()
|
|
expect(modelInfo.info.maxTokens).toBe(-1)
|
|
expect(modelInfo.info.contextWindow).toBe(128_000)
|
|
})
|
|
})
|
|
})
|