feat: add ollama provider
This commit is contained in:
@@ -54,10 +54,21 @@ describe("supported providers", () => {
|
||||
// --- Unsupported providers ---
|
||||
|
||||
describe("unsupported providers", () => {
|
||||
it("throws on ollama provider", () => {
|
||||
expect(() => createChatProvider({ chatgptMcpProvider: "ollama" })).toThrow(
|
||||
/Unsupported chat provider "ollama"/,
|
||||
);
|
||||
it("supports ollama provider", () => {
|
||||
const provider = createChatProvider({ chatgptMcpProvider: "ollama" });
|
||||
expect(provider).toBeDefined();
|
||||
expect(typeof provider.send).toBe("function");
|
||||
});
|
||||
|
||||
it("returns the same singleton instance for repeated calls with 'ollama'", () => {
|
||||
const p1 = createChatProvider({ chatgptMcpProvider: "ollama" });
|
||||
const p2 = createChatProvider({ chatgptMcpProvider: "ollama" });
|
||||
expect(p1).toBe(p2);
|
||||
});
|
||||
|
||||
it("is different from openai provider", () => {
|
||||
const ollamaP = createChatProvider({ chatgptMcpProvider: "ollama" });
|
||||
expect(ollamaP).not.toBe(openaiProvider);
|
||||
});
|
||||
|
||||
it("throws on unknown provider name", () => {
|
||||
@@ -259,3 +270,132 @@ describe("manual provider", () => {
|
||||
expect(manualExportProvider).toBe(manualP);
|
||||
});
|
||||
});
|
||||
|
||||
// --- Ollama provider send (fetch-mocked) ---
|
||||
|
||||
describe("ollama provider send", () => {
|
||||
it("builds correct request body with config values", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ model: "qwen3", message: { role: "assistant", content: "Hello!" }, done: true }),
|
||||
});
|
||||
|
||||
// Create a custom ollamaProvider with mocked fetch
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {
|
||||
chatgptMcpProvider: "ollama",
|
||||
ollamaBaseUrl: "http://test:11434",
|
||||
ollamaModel: "test-model",
|
||||
ollamaTemperature: 0.5,
|
||||
ollamaTimeout: 30,
|
||||
};
|
||||
const result = await ollamaProvider.send({ prompt: "What is AI?" }, config);
|
||||
expect(result.content).toBe("Hello!");
|
||||
const calls = fetchMock.mock.calls;
|
||||
expect(calls.length).toBe(1);
|
||||
const [url, init] = calls[0];
|
||||
expect(url).toBe("http://test:11434/api/chat");
|
||||
expect(init.method).toBe("POST");
|
||||
expect(JSON.parse(init.body)).toEqual({
|
||||
model: "test-model",
|
||||
messages: [{ role: "system", content: "What is AI?" }],
|
||||
stream: false,
|
||||
options: { temperature: 0.5 },
|
||||
});
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("uses defaults when no config ollama fields are set", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ model: "qwen3", message: { role: "assistant", content: "OK" }, done: true }),
|
||||
});
|
||||
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {};
|
||||
const result = await ollamaProvider.send({ prompt: "hi" }, config);
|
||||
expect(result.content).toBe("OK");
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
const body = JSON.parse(init.body);
|
||||
expect(url).toBe("http://localhost:11434/api/chat");
|
||||
expect(body.model).toBe("qwen3:latest");
|
||||
expect(body.options.temperature).toBe(0.2);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("handles non-2xx response with error category", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 404, text: async () => "model not found" });
|
||||
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {};
|
||||
await expect(
|
||||
ollamaProvider.send({ prompt: "hi" }, config),
|
||||
).rejects.toThrow(/Ollama API error \(OllamaModelNotFoundError\)/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("handles fetch network errors", async () => {
|
||||
const fetchMock = vi.fn().mockRejectedValue(new Error("ECONNREFUSED"));
|
||||
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {};
|
||||
await expect(
|
||||
ollamaProvider.send({ prompt: "hi" }, config),
|
||||
).rejects.toThrow(/Ollama API error \(OllamaRequestError\)/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("handles invalid JSON response", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({ ok: true, text: async () => "not json" });
|
||||
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {};
|
||||
await expect(
|
||||
ollamaProvider.send({ prompt: "hi" }, config),
|
||||
).rejects.toThrow(/invalid JSON response/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("returns empty content when message.content is missing", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ model: "test", done: true }),
|
||||
});
|
||||
|
||||
const { ollamaProvider } = await import("../../src/providers/ollama.js");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = fetchMock;
|
||||
try {
|
||||
const config = {};
|
||||
const result = await ollamaProvider.send({ prompt: "hi" }, config);
|
||||
expect(result.content).toBe("");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user