/** * kosha-discovery — Cost primitives shared by the proxy and the CLI. * * Three pieces, all pure synthesis over the model + provider catalog: * - Pre-flight cost estimation (input + expected-output tokens × rates). * - JSONL spend ledger persisted under `~/.kosha/`, partitioned one file * per calendar month (`ledger-YYYY-MM.jsonl`) so the budget gate only * ever scans the month it cares about — not the full history. The legacy * append-only `ledger.jsonl` is still read for backward compat so * existing installs keep working until it stops growing. * - Monthly budget gate driven by `KOSHA_MONTHLY_BUDGET_USD`. * * No tokenizer dependency — request bodies travel through the proxy * untouched, so we approximate input tokens with a coarse char→token ratio. * @module */ import type { ModelCard } from "./types.js"; /** A single completion record written to the ledger. */ export interface LedgerEntry { ts: number; provider: string; modelId: string; requested: string; tenant: string | null; /** Estimated total cost in USD. */ estimatedUsd: number; /** Input tokens estimated from the forwarded request body. */ estimatedInputTokens: number; /** Output tokens the caller asked for (or our fallback). */ estimatedOutputTokens: number; upstreamStatus: number; /** Reconciled cost from the provider's `usage` block, when it returned one. */ actualUsd?: number; /** Uncached input tokens the provider billed. */ actualInputTokens?: number; /** Output tokens the provider billed. */ actualOutputTokens?: number; /** Cache-read (hit) input tokens the provider reported. */ cacheReadTokens?: number; /** Cache-write (creation) input tokens the provider reported. */ cacheWriteTokens?: number; /** `upstream` when the actual* fields came from the provider; `estimate` when only the pre-flight numbers exist. */ usageSource?: "upstream" | "estimate"; /** * `request` (default) for the row written when a request completes; * `adjustment` for a follow-up row that corrects an earlier estimate once * the upstream usage arrived (streaming responses). Adjustment rows carry * deltas, so summing every row's {@link ledgerRowUsd} yields actual spend. */ kind?: "request" | "adjustment"; /** Correlates an adjustment with the request row it amends. */ requestId?: string; /** Adjustment rows only: actual − estimated, in USD (may be negative). */ adjustmentUsd?: number; /** Adjustment rows only: actual − estimated input tokens. */ adjustmentInputTokens?: number; /** Adjustment rows only: actual − estimated output tokens. */ adjustmentOutputTokens?: number; } /** * The USD figure a ledger row contributes to spend: an adjustment row's * delta; otherwise the reconciled upstream cost when the row has one, the * pre-flight estimate if not. Every reader (budget gate, `kosha spend`, * /metrics) goes through this so they agree. */ export declare function ledgerRowUsd(row: Pick): number; /** True for rows that represent a completed request (not a later correction). */ export declare function isRequestRow(row: Pick): boolean; /** Reconciled cost derived from a provider's `usage` block. */ export interface ActualUsageCost { usd: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; } /** * Which accounting convention the usage block follows: * - `openai` — `prompt_tokens` already includes cached tokens * (`prompt_tokens_details.cached_tokens` is a subset). * - `anthropic` — `input_tokens` excludes cache reads / writes, which are * reported separately and billed at their own rates. */ export type UsageShape = "openai" | "anthropic"; /** * Turn a provider `usage` block into a reconciled USD cost against the * model's pricing. Returns null when the model has no usable pricing or the * block carries no token counts. Cache rates fall back to the provider's * published ratios (Anthropic: reads 0.1×, writes 1.25× input; OpenAI: reads * 0.5× input) when the catalog entry lacks explicit cache pricing, so a * cached request is never priced as if it were fully uncached. */ export declare function actualCostFromUsage(model: ModelCard, rawUsage: unknown, shape: UsageShape): ActualUsageCost | null; export declare const DEFAULT_LEDGER_PATH: string; export interface CostEstimate { estimatedUsd: number; inputTokens: number; expectedOutputTokens: number; } /** * Estimate the USD cost of forwarding `requestBody` to `model`. Returns null * when the model has no usable pricing — the caller should treat that as * "cost unknown" rather than free. */ export declare function estimateRequestCost(model: ModelCard, requestBody: Record): CostEstimate | null; /** * Read `KOSHA_MONTHLY_BUDGET_USD` (if any). Returns null when no budget is * configured — the proxy's budget gate then becomes a no-op. This cap is * global: it is always compared against total spend, never a tenant's slice, * so a caller cannot escape it by inventing a fresh tenant tag. */ export declare function readMonthlyBudgetUsd(): number | null; /** * Read `KOSHA_TENANT_BUDGET_USD` (if any): an additional per-tenant monthly * cap applied on top of the global one to requests that carry a tenant tag. */ export declare function readTenantBudgetUsd(): number | null; /** * Sum estimated USD spend for entries falling in the calendar month that * contains `nowMs`. Tenant scopes the sum when given. * * Reads only the month's partition (`ledger-YYYY-MM.jsonl` next to * `ledgerPath`) plus, for backward compat, the legacy append-only * `ledgerPath` itself — so existing installs whose history still lives in * the un-partitioned file keep counting until that file stops growing. At * most two files are read, never the full history. */ export declare function readSpendForMonth(nowMs: number, tenant?: string | null, ledgerPath?: string): Promise; /** * Drop monthly partition files older than the retention window. Best-effort: * errors are swallowed because retention is housekeeping, not correctness. * The legacy `ledgerPath` file itself is never touched — only * `ledger-YYYY-MM.jsonl` siblings are eligible. Returns the count of * partitions removed (handy for tests / a future `kosha ledger trim`). * * The window is "current month plus `retentionMonths - 1` prior months", so * the default of 12 keeps a rolling year. Exported so the CLI can run an * explicit sweep without waiting for an append. */ export declare function trimLedgerPartitions(ledgerPath: string, nowMs: number, retentionMonths?: number): Promise; /** * Append one ledger entry atomically. The entry is written to the monthly * partition for its own `ts` (`ledger-YYYY-MM.jsonl`), keeping each file * bounded to a single month so budget reads don't scan the full history. * * Writes are append-only so concurrent processes don't clobber each other. * A best-effort atomic create-and-rename is used if the first append hits * ENOENT, so a fresh install comes up cleanly. After the write we run a * best-effort retention trim so old partitions age out without a cron job. */ export declare function appendLedgerEntry(entry: LedgerEntry, ledgerPath?: string): Promise; //# sourceMappingURL=cost.d.ts.map