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); }); });