Files
oh-my-pi/packages/ai/test/models-cost.test.ts
T
roboomp a52ed682c7 fix(ai): separated codex orchestration usage
- Added a Usage.orchestration sidecar for provider-side service tokens so Responses/Codex totals and costs stay accurate without inflating visible prompt input/cache buckets.
- Updated Codex/WebSocket usage, session/status aggregates, and usage reporting to preserve orchestration-aware totals.
- Added regressions for OpenAI Responses accounting, Codex WebSocket terminal usage, cost calculation, and session aggregation.

Fixes #4469
2026-07-03 16:44:12 +00:00

125 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import type { Usage } from "@oh-my-pi/pi-ai/types";
import { calculateCost, getBundledModel } from "@oh-my-pi/pi-catalog/models";
describe("calculateCost", () => {
it("keeps token-based calculation for GitHub Copilot models", () => {
const model = {
...getBundledModel("github-copilot", "gpt-4o"),
cost: {
input: 1000,
output: 2000,
cacheRead: 500,
cacheWrite: 800,
},
};
const usage: Usage = {
input: 1000,
output: 500,
cacheRead: 200,
cacheWrite: 100,
totalTokens: 1800,
cost: {
input: 123,
output: 456,
cacheRead: 789,
cacheWrite: 321,
total: 1689,
},
};
calculateCost(model, usage);
expect(usage.cost.input).toBeCloseTo(1, 8);
expect(usage.cost.output).toBeCloseTo(1, 8);
expect(usage.cost.cacheRead).toBeCloseTo(0.1, 8);
expect(usage.cost.cacheWrite).toBeCloseTo(0.08, 8);
expect(usage.cost.total).toBeCloseTo(2.18, 8);
});
it("keeps token-based calculation for non-Copilot providers", () => {
const model = {
...getBundledModel("openai", "gpt-4o-mini"),
cost: {
input: 1000,
output: 2000,
cacheRead: 500,
cacheWrite: 800,
},
};
const usage: Usage = {
input: 1000,
output: 500,
cacheRead: 200,
cacheWrite: 100,
totalTokens: 1800,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
};
calculateCost(model, usage);
expect(usage.cost.input).toBeCloseTo(1, 8);
expect(usage.cost.output).toBeCloseTo(1, 8);
expect(usage.cost.cacheRead).toBeCloseTo(0.1, 8);
expect(usage.cost.cacheWrite).toBeCloseTo(0.08, 8);
expect(usage.cost.total).toBeCloseTo(2.18, 8);
});
it("prices provider orchestration tokens without changing visible usage buckets", () => {
const model = {
...getBundledModel("openai", "gpt-4o-mini"),
cost: {
input: 1000,
output: 2000,
cacheRead: 500,
cacheWrite: 800,
},
};
const usage: Usage = {
input: 100,
output: 20,
cacheRead: 50,
cacheWrite: 10,
totalTokens: 250,
orchestration: { input: 25, output: 40, cacheRead: 5 },
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
calculateCost(model, usage);
expect(usage.input).toBe(100);
expect(usage.output).toBe(20);
expect(usage.cacheRead).toBe(50);
expect(usage.cost.input).toBeCloseTo(0.125, 8);
expect(usage.cost.output).toBeCloseTo(0.12, 8);
expect(usage.cost.cacheRead).toBeCloseTo(0.0275, 8);
expect(usage.cost.cacheWrite).toBeCloseTo(0.008, 8);
expect(usage.cost.total).toBeCloseTo(0.2805, 8);
});
it("prices OpenAI Codex GPT models from the matching OpenAI catalog entry", () => {
const openAIModel = getBundledModel("openai", "gpt-5.4");
const codexModel = getBundledModel("openai-codex", "gpt-5.4");
const usage: Usage = {
input: 1000,
output: 500,
cacheRead: 200,
cacheWrite: 0,
totalTokens: 1700,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
expect(codexModel.cost).toEqual(openAIModel.cost);
calculateCost(codexModel, usage);
expect(usage.cost.total).toBeCloseTo(0.01005, 8);
});
});