evOo-local/packages/adapters/src/pi-runtime-limits.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

92 lines
3.2 KiB
TypeScript

import { DEFAULT_MODEL_MAX_TOKENS } from "@rakazo/contracts";
import { describe, expect, it } from "vitest";
import {
billedPromptTokens,
clipToolResultContent,
clipToolResultText,
resolveCompletionMaxTokens,
TOOL_RESULT_TEXT_LIMIT,
} from "./pi-runtime-limits.js";
describe("billedPromptTokens", () => {
it("adds cache read and write onto uncached input so the meter matches provider cost", () => {
expect(
billedPromptTokens({
input: 12,
output: 40,
cacheRead: 8_000,
cacheWrite: 200,
}),
).toEqual({ inputTokens: 8_212, outputTokens: 40 });
});
it("keeps uncached-only usage unchanged when cache fields are absent", () => {
expect(billedPromptTokens({ input: 100, output: 20 })).toEqual({
inputTokens: 100,
outputTokens: 20,
});
});
it("treats a fully cached prompt as billed input rather than zero", () => {
expect(
billedPromptTokens({
input: 0,
output: 15,
cacheRead: 12_500,
cacheWrite: 0,
}),
).toEqual({ inputTokens: 12_500, outputTokens: 15 });
});
});
describe("resolveCompletionMaxTokens", () => {
it("uses the shared default instead of a model-card ceiling", () => {
expect(resolveCompletionMaxTokens(128_000)).toBe(DEFAULT_MODEL_MAX_TOKENS);
});
it("honors a configured escape hatch up to the model cap", () => {
expect(resolveCompletionMaxTokens(128_000, 8_192)).toBe(8_192);
expect(resolveCompletionMaxTokens(2_048, 8_192)).toBe(2_048);
});
it("clamps an agent-supplied options maxTokens to the user cap", () => {
expect(resolveCompletionMaxTokens(128_000, undefined, 128_000)).toBe(DEFAULT_MODEL_MAX_TOKENS);
expect(resolveCompletionMaxTokens(128_000, 16_384, 128_000)).toBe(16_384);
});
});
describe("clipToolResultText", () => {
it("bounds oversized tool output for the in-turn transcript", () => {
const text = "x".repeat(TOOL_RESULT_TEXT_LIMIT + 50);
const clipped = clipToolResultText(text);
expect(clipped.endsWith("…")).toBe(true);
expect(clipped.length).toBe(TOOL_RESULT_TEXT_LIMIT + 1);
});
});
describe("clipToolResultContent", () => {
it("shares one character budget across all text parts and leaves images", () => {
const first = "a".repeat(TOOL_RESULT_TEXT_LIMIT - 10);
const second = "b".repeat(40);
const image = { type: "image" as const, data: "iVBORw0KGgo=", mimeType: "image/png" as const };
const clipped = clipToolResultContent([
{ type: "text" as const, text: first },
image,
{ type: "text" as const, text: second },
{ type: "text" as const, text: "later omitted" },
]);
expect(clipped).toHaveLength(3);
expect(clipped[0]).toEqual({ type: "text", text: first });
expect(clipped[1]).toEqual(image);
const overflow = clipped[2] as { type: "text"; text: string };
expect(overflow.type).toBe("text");
expect(overflow.text.endsWith("…")).toBe(true);
expect(overflow.text.startsWith("b")).toBe(true);
expect(overflow.text.length).toBe(11);
const textChars = clipped
.filter((part) => part.type === "text")
.reduce((sum, part) => sum + part.text.replace(/…$/, "").length, 0);
expect(textChars).toBe(TOOL_RESULT_TEXT_LIMIT);
});
});