/** * "What will this call cost, and is there budget?" — answered before it is * sent. * * Everything Trazum knows sits behind a process launch, a config walk and a * log parse. That is fine for a report and useless for a decision being made * right now: by the time a report exists, the call has been paid for. * * **This is where the temptation to merge halves is strongest**, which is why * the shape below refuses to. The budget consumed is *measured* — it comes * from the provider's own billed counts. The cost of the call being asked * about is *estimated* — nobody has sent it yet, and the token count is a * heuristic. A single "you have $38 left after this" would be a number that is * neither, handed to a caller with no way to tell. * * So the answer carries both halves separately, and the composed figure — which * callers genuinely need — arrives with its two halves broken out beside it. * The verdict names what it rests on: `measured` when the budget is already * blown without help from any estimate, and `measured+estimated` when it takes * the described call to cross. A caller reading only the verdict still cannot * mistake one for the other. */ import type { PricingCatalogue } from './pricing.js'; export type AnswerVerdict = 'within' | 'over' | 'cannot-tell'; /** Why the question cannot be answered, when it cannot. */ export type CannotTellReasonAnswer = /** No budget is configured, so "is there budget left" has no subject. */ 'no-budget-configured' /** Nothing has been measured, so the consumed half is unknown. */ | 'nothing-measured' /** The model is not in the catalogue, so the call cannot be priced. */ | 'model-unpriced'; export interface CallEstimate { model: string; inputTokens: number; outputTokens: number; estimatedUsd: number; /** Always `estimated`: this call has not happened. */ provenance: 'estimated'; /** * What the estimate rests on, so a caller can weigh it. * * `token-count` means the caller handed over counts it had already made; * `heuristic` means Trazum counted the text itself, with the ±10% band the * estimator has published since 1.9. */ basis: 'token-count' | 'heuristic'; } export interface BudgetPosition { limitUsd: number; /** Spent so far, from the provider's own billed counts. */ consumedUsd: number; remainingUsd: number; /** Always `measured`: this is a bill, not a projection. */ provenance: 'measured'; /** The period the consumed figure covers. */ window: { fromMs: number; toMs: number; } | null; } export interface CostAnswer { schemaVersion: 1; /** The call the caller described, priced. Null when none was described. */ call: CallEstimate | null; /** Where the budget stands. Null when there is no budget or nothing measured. */ budget: BudgetPosition | null; verdict: AnswerVerdict; /** * What the verdict rests on — the field that keeps this honest. * * `measured` means the budget is already past its limit and the estimate * played no part. `measured+estimated` means it takes the described call to * cross, so the verdict is only as good as the token count behind it. A * caller that reads nothing else can still tell those apart. */ restsOn: 'measured' | 'measured+estimated' | null; reason: CannotTellReasonAnswer | null; /** * Where the budget would stand after this call — the figure callers actually * want, with its halves kept visible so the composition cannot be mistaken * for a measurement. */ afterCall: { usd: number; halves: { measuredUsd: number; estimatedUsd: number; }; } | null; } export interface AnswerRequest { model?: string; inputTokens?: number; outputTokens?: number; /** Measured spend so far, when there is any. */ consumedUsd?: number; limitUsd?: number; window?: { fromMs: number; toMs: number; } | null; /** How the token counts were arrived at. */ basis?: 'token-count' | 'heuristic'; } /** * Answers the two questions, from figures the caller already holds. * * Pure and synchronous on purpose: the whole point of this release is an * answer in single-digit milliseconds, and a function that reads a file cannot * promise that. The server hands it a store total it read once and keeps. */ export declare function answerCost(request: AnswerRequest, options: { catalogue: PricingCatalogue; on?: Date; }): CostAnswer; //# sourceMappingURL=answer.d.ts.map