153fd78345
- Implemented agent type classification and persistence in the database, including a backfill for legacy records. - Added `AgentType` categorization and aggregated token usage metrics to the backend services. - Created an `AgentTokenShare` visualization component to display usage distribution on the overview dashboard. - Integrated agent-based tracking into existing data pipelines, view models, and testing suites.
132 lines
3.6 KiB
TypeScript
132 lines
3.6 KiB
TypeScript
import { Database } from "bun:sqlite";
|
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { closeDb, getRecentRequests, initDb, insertMessageStats } from "@oh-my-pi/omp-stats/db";
|
|
import type { MessageStats } from "@oh-my-pi/omp-stats/types";
|
|
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
|
import { getAgentDir, getStatsDbPath, setAgentDir, TempDir } from "@oh-my-pi/pi-utils";
|
|
|
|
const originalConfigDir = process.env.PI_CONFIG_DIR;
|
|
const originalAgentDir = getAgentDir();
|
|
let tempDir: TempDir | null = null;
|
|
|
|
beforeEach(() => {
|
|
tempDir = TempDir.createSync("@pi-stats-db-");
|
|
const configDir = path.relative(os.homedir(), tempDir.join("config"));
|
|
process.env.PI_CONFIG_DIR = configDir;
|
|
setAgentDir(path.join(os.homedir(), configDir, "agent"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
closeDb();
|
|
if (originalConfigDir === undefined) {
|
|
delete process.env.PI_CONFIG_DIR;
|
|
} else {
|
|
process.env.PI_CONFIG_DIR = originalConfigDir;
|
|
}
|
|
setAgentDir(originalAgentDir);
|
|
tempDir?.removeSync();
|
|
tempDir = null;
|
|
});
|
|
|
|
function createCodexGptStats(entryId: string): MessageStats {
|
|
return {
|
|
sessionFile: "/tmp/session.jsonl",
|
|
entryId,
|
|
folder: "/tmp/project",
|
|
model: "gpt-5.4",
|
|
provider: "openai-codex",
|
|
api: "openai-codex-responses",
|
|
timestamp: Date.now(),
|
|
duration: 1000,
|
|
ttft: 100,
|
|
stopReason: "stop",
|
|
errorMessage: null,
|
|
usage: {
|
|
input: 1000,
|
|
output: 500,
|
|
cacheRead: 200,
|
|
cacheWrite: 0,
|
|
totalTokens: 1700,
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
},
|
|
agentType: "main",
|
|
};
|
|
}
|
|
|
|
function expectedCodexGptCost() {
|
|
const cost = getBundledModel("openai-codex", "gpt-5.4").cost;
|
|
const input = (cost.input / 1_000_000) * 1000;
|
|
const output = (cost.output / 1_000_000) * 500;
|
|
const cacheRead = (cost.cacheRead / 1_000_000) * 200;
|
|
return {
|
|
input,
|
|
output,
|
|
cacheRead,
|
|
total: input + output + cacheRead,
|
|
};
|
|
}
|
|
|
|
describe("stats GPT cost correction", () => {
|
|
it("stores catalog-derived cost when OpenAI Codex session usage has zero cost", async () => {
|
|
await initDb();
|
|
|
|
insertMessageStats([createCodexGptStats("inserted")]);
|
|
|
|
const expected = expectedCodexGptCost();
|
|
const request = getRecentRequests(1)[0];
|
|
expect(expected.total).toBeGreaterThan(0);
|
|
expect(request?.usage.cost.input).toBeCloseTo(expected.input, 8);
|
|
expect(request?.usage.cost.output).toBeCloseTo(expected.output, 8);
|
|
expect(request?.usage.cost.cacheRead).toBeCloseTo(expected.cacheRead, 8);
|
|
expect(request?.usage.cost.total).toBeCloseTo(expected.total, 8);
|
|
});
|
|
|
|
it("backfills existing zero-cost OpenAI Codex GPT rows on database init", async () => {
|
|
await initDb();
|
|
closeDb();
|
|
|
|
const database = new Database(getStatsDbPath());
|
|
database
|
|
.prepare(`
|
|
INSERT INTO messages (
|
|
session_file, entry_id, folder, model, provider, api, timestamp,
|
|
duration, ttft, stop_reason, error_message,
|
|
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_tokens, premium_requests,
|
|
cost_input, cost_output, cost_cache_read, cost_cache_write, cost_total
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`)
|
|
.run(
|
|
"/tmp/session.jsonl",
|
|
"backfilled",
|
|
"/tmp/project",
|
|
"gpt-5.4",
|
|
"openai-codex",
|
|
"openai-codex-responses",
|
|
Date.now(),
|
|
1000,
|
|
100,
|
|
"stop",
|
|
null,
|
|
1000,
|
|
500,
|
|
200,
|
|
0,
|
|
1700,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
);
|
|
database.close();
|
|
|
|
await initDb();
|
|
|
|
const request = getRecentRequests(1)[0];
|
|
expect(request?.usage.cost.total).toBeCloseTo(expectedCodexGptCost().total, 8);
|
|
});
|
|
});
|