mirror of
https://github.com/hanzo-js/kai.git
synced 2026-10-30 15:04:57 +00:00
92 lines
4.2 KiB
TypeScript
92 lines
4.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { Kai, type LogLevel, noul, VERSION } from "@hanzo/kai";
|
|
import { DECISION, json, MODELS, recorder, said, scripted } from "./fetch.ts";
|
|
|
|
const KEY = "sk-live-0123456789abcdef";
|
|
|
|
function client(logLevel: LogLevel, respond = () => json(DECISION, 200, { "x-request-id": "req_7" })) {
|
|
const log = recorder();
|
|
const { fetch } = scripted(respond);
|
|
const kai = new Kai({
|
|
apiKey: KEY,
|
|
baseURL: "https://api.test",
|
|
fetch,
|
|
logger: log,
|
|
logLevel,
|
|
defaultHeaders: { Cookie: "session=abc123", "X-Api-Key": "xk-999", "Proxy-Authorization": "Basic dXNlcjpwYXNz" },
|
|
});
|
|
return { kai, log };
|
|
}
|
|
|
|
test("the default level says nothing about a request that succeeds", async (t) => {
|
|
const spies = (["debug", "info", "warn", "error"] as const).map((m) => t.mock.method(console, m, () => {}));
|
|
const { fetch } = scripted(() => json(DECISION));
|
|
await new Kai({ apiKey: KEY, fetch }).decide({ state: "s", questions: { q: noul("q?") } });
|
|
for (const spy of spies) assert.equal(spy.mock.callCount(), 0);
|
|
});
|
|
|
|
test("the console logger prefixes each line [kai]", async (t) => {
|
|
const info = t.mock.method(console, "info", () => {});
|
|
const { fetch } = scripted(() => json(MODELS, 200, { "x-request-id": "req_7" }));
|
|
await new Kai({ apiKey: KEY, fetch, logLevel: "info" }).models.list();
|
|
assert.match(String(info.mock.calls[0]?.arguments[0]), /^\[kai\] #1 GET \/v1\/models <- 200 in \d+ ms \(request req_7\)$/);
|
|
});
|
|
|
|
test("info logs one line per attempt", async () => {
|
|
const { kai, log } = client("info");
|
|
await kai.decide({ state: "s", questions: { q: noul("q?") } });
|
|
assert.equal(said(log, "debug").length, 0);
|
|
assert.deepEqual(said(log, "info").length, 1);
|
|
assert.match(said(log, "info")[0] ?? "", /^#1 POST \/v1\/decisions <- 200 in \d+ ms \(request req_7\)$/);
|
|
});
|
|
|
|
test("debug logs headers with every credential masked, and both bodies", async () => {
|
|
const { kai, log } = client("debug");
|
|
await kai.decide({ state: "s", questions: { q: noul("q?") } });
|
|
const [level, message, detail] = log.lines[0] ?? [];
|
|
assert.equal(level, "debug");
|
|
assert.equal(message, "#1 POST /v1/decisions -> https://api.test/v1/decisions");
|
|
const { headers, body } = detail as { headers: Record<string, string>; body: unknown };
|
|
assert.equal(headers.authorization, "Bearer ***");
|
|
assert.equal(headers["proxy-authorization"], "Basic ***");
|
|
assert.equal(headers.cookie, "***");
|
|
assert.equal(headers["x-api-key"], "***");
|
|
assert.equal(headers["user-agent"], `@hanzo/kai/${VERSION}`);
|
|
assert.deepEqual(body, { model: "kai", state: "s", questions: { q: { type: "noul", instructions: "q?" } } });
|
|
assert.deepEqual(log.lines.at(-1)?.slice(0, 2), ["debug", "#1 POST /v1/decisions <- body"]);
|
|
const everything = JSON.stringify(log.lines);
|
|
for (const secret of [KEY, "abc123", "xk-999", "dXNlcjpwYXNz"]) assert.equal(everything.includes(secret), false, secret);
|
|
});
|
|
|
|
test("a failed attempt logs its error body at debug and its retry at info", async () => {
|
|
let n = 0;
|
|
const { kai, log } = client("debug", () =>
|
|
n++ === 0 ? json({ error: { code: 503, message: "busy" } }, 503) : json(DECISION),
|
|
);
|
|
await kai.decide({ state: "s", questions: { q: noul("q?") } }, { retry: { backoffInitialMs: 0 } });
|
|
assert.ok(log.lines.some((l) => l[1] === "#1 POST /v1/decisions <- error body" && JSON.stringify(l[2]).includes("busy")));
|
|
assert.ok(said(log, "info").some((m) => /^#1 POST \/v1\/decisions retrying in 0 ms \(1\/2\) after 503$/.test(m)));
|
|
});
|
|
|
|
test("off drops everything, warnings included", async () => {
|
|
const { kai, log } = client("off", () =>
|
|
json({ ...DECISION, answers: { odd: { type: "rank" }, q: { type: "noul", noul: 0.5 } } }),
|
|
);
|
|
const d = await kai.decide({ state: "s", questions: { q: noul("q?") } });
|
|
assert.deepEqual(Object.keys(d.answers), ["q"]);
|
|
assert.deepEqual(log.lines, []);
|
|
});
|
|
|
|
test("the client's logger is the configured one narrowed to its level", () => {
|
|
const { kai, log } = client("warn");
|
|
kai.logger.debug("no");
|
|
kai.logger.info("no");
|
|
kai.logger.warn("yes");
|
|
kai.logger.error("yes too");
|
|
assert.deepEqual(
|
|
log.lines.map((l) => l[1]),
|
|
["yes", "yes too"],
|
|
);
|
|
});
|