CI / checks (push) Has been cancelled
Local models were burning the 8-round budget on simple event adds. Raise the limit, resolve calendars by name/default, and inject the household clock so relative dates work.
150 lines
5.5 KiB
TypeScript
150 lines
5.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { createMockLlmClient } from "../../src/lib/llm/mock";
|
|
import { getLlmConfig } from "../../src/lib/llm/config";
|
|
import { runAgentChat } from "../../src/modules/agent/server/run";
|
|
|
|
describe("mock llm provider", () => {
|
|
it("returns a plain assistant message by default", async () => {
|
|
const client = createMockLlmClient();
|
|
const result = await client.chatCompletion({
|
|
messages: [{ role: "user", content: "hello" }],
|
|
tools: [],
|
|
});
|
|
assert.equal(result.message.role, "assistant");
|
|
assert.ok(result.message.content?.includes("mock provider"));
|
|
});
|
|
|
|
it("requests add_list_item when the user mentions milk", async () => {
|
|
const client = createMockLlmClient();
|
|
const result = await client.chatCompletion({
|
|
messages: [{ role: "user", content: "add milk to shopping list" }],
|
|
tools: [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "add_list_item",
|
|
description: "add",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
},
|
|
],
|
|
});
|
|
assert.equal(result.message.tool_calls?.[0]?.function.name, "add_list_item");
|
|
});
|
|
});
|
|
|
|
describe("getLlmConfig", () => {
|
|
it("uses mock provider when base url is unset", () => {
|
|
const original = process.env.LLM_BASE_URL;
|
|
const originalProvider = process.env.LLM_PROVIDER;
|
|
delete process.env.LLM_BASE_URL;
|
|
delete process.env.LLM_PROVIDER;
|
|
|
|
const config = getLlmConfig();
|
|
assert.equal(config.provider, "mock");
|
|
|
|
if (original === undefined) delete process.env.LLM_BASE_URL;
|
|
else process.env.LLM_BASE_URL = original;
|
|
if (originalProvider === undefined) delete process.env.LLM_PROVIDER;
|
|
else process.env.LLM_PROVIDER = originalProvider;
|
|
});
|
|
});
|
|
|
|
describe("runAgentChat", () => {
|
|
it("executes tool calls and returns a final assistant message", async () => {
|
|
const executed: string[] = [];
|
|
const result = await runAgentChat({
|
|
messages: [{ role: "user", content: "add milk to the shopping list" }],
|
|
request: new Request("http://localhost:3000/api/agent/chat"),
|
|
llm: createMockLlmClient(),
|
|
executeTool: async (name) => {
|
|
executed.push(name);
|
|
return JSON.stringify({ status: 201, body: { id: "item-1", text: "milk" } });
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(executed, ["add_list_item"]);
|
|
assert.equal(result.message.role, "assistant");
|
|
assert.ok(result.message.content.length > 0);
|
|
assert.equal(result.toolCalls.length, 1);
|
|
assert.equal(result.toolCalls[0]?.name, "add_list_item");
|
|
assert.equal(result.toolCalls[0]?.status, 201);
|
|
});
|
|
|
|
it("accepts a custom system prompt override", async () => {
|
|
const result = await runAgentChat({
|
|
messages: [{ role: "user", content: "hello" }],
|
|
request: new Request("http://localhost:3000/api/agent/chat"),
|
|
systemPrompt: "You are a pirate.",
|
|
llm: createMockLlmClient(),
|
|
});
|
|
|
|
assert.equal(result.message.role, "assistant");
|
|
assert.ok(result.message.content.length > 0);
|
|
});
|
|
|
|
it("appends runtime clock context to the system prompt", async () => {
|
|
const original = process.env.HOUSEHOLD_TIMEZONE;
|
|
process.env.HOUSEHOLD_TIMEZONE = "America/Chicago";
|
|
|
|
let systemContent = "";
|
|
const result = await runAgentChat({
|
|
messages: [{ role: "user", content: "hello" }],
|
|
request: new Request("http://localhost:3000/api/agent/chat"),
|
|
systemPrompt: "You are a pirate.",
|
|
llm: {
|
|
async chatCompletion(request) {
|
|
const system = request.messages.find((message) => message.role === "system");
|
|
systemContent = typeof system?.content === "string" ? system.content : "";
|
|
return {
|
|
message: { role: "assistant", content: "Ahoy" },
|
|
finishReason: "stop",
|
|
};
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.equal(result.message.content, "Ahoy");
|
|
assert.match(systemContent, /^You are a pirate\./);
|
|
assert.match(systemContent, /Current time:/);
|
|
assert.match(systemContent, /America\/Chicago/);
|
|
|
|
if (original === undefined) delete process.env.HOUSEHOLD_TIMEZONE;
|
|
else process.env.HOUSEHOLD_TIMEZONE = original;
|
|
});
|
|
});
|
|
|
|
it("passes a model override to the OpenAI-compatible client", async () => {
|
|
const originalBaseUrl = process.env.LLM_BASE_URL;
|
|
const originalModel = process.env.LLM_MODEL;
|
|
const originalProvider = process.env.LLM_PROVIDER;
|
|
const originalFetch = globalThis.fetch;
|
|
let requestBody: unknown = null;
|
|
|
|
process.env.LLM_BASE_URL = "https://llm.example.test/v1";
|
|
process.env.LLM_MODEL = "llama3.2";
|
|
delete process.env.LLM_PROVIDER;
|
|
|
|
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
requestBody = JSON.parse(String(init?.body));
|
|
return Response.json({
|
|
choices: [{ message: { role: "assistant", content: "done" }, finish_reason: "stop" }],
|
|
});
|
|
}) as typeof fetch;
|
|
|
|
const { createLlmClient } = await import("../../src/lib/llm/index");
|
|
const client = createLlmClient({ model: "qwen2.5-coder" });
|
|
await client.chatCompletion({ messages: [{ role: "user", content: "hello" }] });
|
|
|
|
assert.equal((requestBody as { model?: string }).model, "qwen2.5-coder");
|
|
|
|
globalThis.fetch = originalFetch;
|
|
if (originalBaseUrl === undefined) delete process.env.LLM_BASE_URL;
|
|
else process.env.LLM_BASE_URL = originalBaseUrl;
|
|
if (originalModel === undefined) delete process.env.LLM_MODEL;
|
|
else process.env.LLM_MODEL = originalModel;
|
|
if (originalProvider === undefined) delete process.env.LLM_PROVIDER;
|
|
else process.env.LLM_PROVIDER = originalProvider;
|
|
});
|