fix: align compaction cuts and native usage accounting

This commit is contained in:
amosblomqvist
2026-10-24 19:05:02 +03:00
parent 50a2db775d
commit 624c91a437
45 changed files with 775 additions and 2329 deletions
+8 -5
View File
@@ -87,8 +87,8 @@ export interface ChatSettings {
* billed (CallUsage per agent step). Steps, not a sum: cost and context
* folds happen at read time (core/prices.ts). */
export interface ExtractionStageUsage {
/** The model that ran this stage, provider-qualified ("provider:id" —
* model slots wave, 2026-09-20; matches PRICES' uniform keys). */
/** The model that ran this stage, provider-qualified ("provider:id")
* for key-independent Pi catalog lookup at read time. */
model: string;
steps: CallUsage[];
}
@@ -118,7 +118,7 @@ export interface ExtractionRunUsage {
* flattened to the fields we persist (undefined = provider didn't report;
* renderers show absent, never zero). */
export interface CallUsage {
/** Total input (prompt) tokens, cached portion included. */
/** Pi-normalized fresh input tokens; cache buckets are disjoint. */
inputTokens: number | undefined;
outputTokens: number | undefined;
/** Cached input tokens READ (billed at the cache-read rate, if any). */
@@ -126,6 +126,9 @@ export interface CallUsage {
/** Input tokens written to the cache this call (OpenRouter bills writes
* for some providers; undefined = not reported/not billed). */
cacheWriteTokens: number | undefined;
/** Subset of cacheWriteTokens written with one-hour retention. Missing
* on older logs; new native captures explicitly record zero if absent. */
cacheWrite1hTokens?: number;
/** The reasoning portion of the output tokens. */
reasoningTokens: number | undefined;
}
@@ -144,8 +147,8 @@ export interface TurnUsage {
* entry to the transcript (the block this turn "belongs" to). */
messageId: string;
at: string;
/** The model that ran the turn, provider-qualified ("provider:id" —
* model slots wave, 2026-09-20; matches PRICES' uniform keys). */
/** The model that ran the turn, provider-qualified ("provider:id")
* for key-independent Pi catalog lookup at read time. */
model: string;
/** One entry per agent step (model call) in this turn. */
steps: CallUsage[];
+21 -10
View File
@@ -43,7 +43,7 @@ export interface JobRecord {
* money-channel doctrine; cost renders "—", never $0). */
export interface JobUsage {
/** The model that ran the sub-agent, provider-qualified ("provider:id" —
* model slots wave, 2026-09-20; matches PRICES' uniform keys). */
* model slots wave, 2026-09-20; matches Pi catalog lookup keys). */
model: string;
/** Running totals summed over the run's model calls so far. */
inputTokens: number | undefined;
@@ -51,6 +51,10 @@ export interface JobUsage {
cacheReadTokens: number | undefined;
cacheWriteTokens: number | undefined;
reasoningTokens: number | undefined;
cacheWrite1hTokens?: number;
/** Individual calls for current-catalog per-call tier pricing. Absent on
* older jobs; never backfill them from cumulative totals. */
calls?: CallUsage[];
/** How many model calls the run has made (each bills separately). */
steps: number;
/** The MOST RECENT model call's accounting (2026-09 dock contract): the
@@ -67,7 +71,7 @@ export interface JobUsage {
* object with these fields folds, including the real thing and test fakes). */
export interface UsageStepLike {
inputTokens?: number | undefined;
inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number } | undefined;
inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number; cacheWrite1hTokens?: number } | undefined;
outputTokens?: number | undefined;
outputTokenDetails?: { reasoningTokens?: number } | undefined;
}
@@ -80,12 +84,13 @@ export function piUsageToStep(usage: {
output: number;
cacheRead: number;
cacheWrite: number;
cacheWrite1h?: number;
reasoning?: number;
}): UsageStepLike {
return {
inputTokens: usage.input,
outputTokens: usage.output,
inputTokenDetails: { cacheReadTokens: usage.cacheRead, cacheWriteTokens: usage.cacheWrite },
inputTokenDetails: { cacheReadTokens: usage.cacheRead, cacheWriteTokens: usage.cacheWrite, cacheWrite1hTokens: usage.cacheWrite1h ?? 0 },
outputTokenDetails: usage.reasoning !== undefined ? { reasoningTokens: usage.reasoning } : undefined,
};
}
@@ -101,6 +106,14 @@ export function accumulateJobUsage(
): JobUsage {
const add = (a: number | undefined, b: number | undefined): number | undefined =>
a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
const call: CallUsage = {
inputTokens: step.inputTokens,
outputTokens: step.outputTokens,
cacheReadTokens: step.inputTokenDetails?.cacheReadTokens,
cacheWriteTokens: step.inputTokenDetails?.cacheWriteTokens,
cacheWrite1hTokens: step.inputTokenDetails?.cacheWrite1hTokens,
reasoningTokens: step.outputTokenDetails?.reasoningTokens,
};
return {
model,
inputTokens: add(total?.inputTokens, step.inputTokens),
@@ -108,14 +121,12 @@ export function accumulateJobUsage(
cacheReadTokens: add(total?.cacheReadTokens, step.inputTokenDetails?.cacheReadTokens),
cacheWriteTokens: add(total?.cacheWriteTokens, step.inputTokenDetails?.cacheWriteTokens),
reasoningTokens: add(total?.reasoningTokens, step.outputTokenDetails?.reasoningTokens),
cacheWrite1hTokens: ((total?.cacheWriteTokens ?? 0) > 0 && total?.cacheWrite1hTokens === undefined) ||
((call.cacheWriteTokens ?? 0) > 0 && call.cacheWrite1hTokens === undefined)
? undefined : add(total?.cacheWrite1hTokens, call.cacheWrite1hTokens),
steps: (total?.steps ?? 0) + 1,
lastStep: {
inputTokens: step.inputTokens,
outputTokens: step.outputTokens,
cacheReadTokens: step.inputTokenDetails?.cacheReadTokens,
cacheWriteTokens: step.inputTokenDetails?.cacheWriteTokens,
reasoningTokens: step.outputTokenDetails?.reasoningTokens,
},
...(!total || total.calls ? { calls: [...(total?.calls ?? []), call] } : {}),
lastStep: call,
};
}
+1 -1
View File
@@ -10,7 +10,7 @@ import type { BackgroundModelRole, ModelSlotName } from "../settings.ts";
/** One picker row: everything the UI shows about a model, projected from
* pi-ai's catalog. Costs are USD per 1M tokens (the same catalog, the same
* units core/prices.ts's generated table carries). */
* units as Pi's server-side pricing calculator). */
export interface ModelCatalogEntry {
id: string;
name: string;