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.
92 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|