UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

72 lines (71 loc) • 4.03 kB
/** * Cost computation (1.37). core never hardcodes provider pricing — a deployment injects it via * `RunnerDeps.pricing` or each `Model.cost`. All prices are **absolute per-1M-token USD**, not * multipliers: each provider's adapter precomputes its discount into an absolute value * (OpenAI/DeepSeek: the cache-hit price; Anthropic: base×0.1 read / ×1.25 5min-write / ×2.0 1h-write). */ /** Per-1M-token absolute prices (USD) for a model. */ export interface ModelPricing { inputPer1M: number; outputPer1M: number; /** Cache-read (hit) absolute price per 1M. Default 0 (treated as full-price input if absent). */ cacheReadPer1M?: number; /** Cache-write 5-min-TTL absolute price per 1M (Anthropic ~1.25× base). 0 elsewhere. */ cacheWritePer1M?: number; /** Cache-write 1-hour-TTL absolute price per 1M (Anthropic ~2.0× base). 0 elsewhere. */ cacheWriteLongPer1M?: number; } /** * Unified token counts for one task/turn, **already normalized** so `totalInputTokens` INCLUDES all * cache tokens (matching OTel `gen_ai.usage.input_tokens`). `cacheReadTokens`/`cacheWriteTokens`/ * `cacheWriteTokensLong` are mutually exclusive subsets of `totalInputTokens`. */ export interface TokenCounts { totalInputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cacheWriteTokensLong: number; outputTokens: number; } /** * Cost in **integer micro-USD** (1e-6 USD) — integer to avoid float-accumulation error in billing * reconcile (Stripe/AWS-style). Convergent across providers: full-price input = `totalInputTokens` * minus all cache tokens; cache read/write priced at their own absolute rates; output at its rate. * * DeepSeek (hit price as `cacheReadPer1M`, no cache-write) and OpenAI (no 1h write) fall out by their * zero token counts; Anthropic uses all fields. This fixes P2 (cached charged at full price) by * pricing the cache subset separately instead of trusting the brain's possibly-overstated cost total. */ export declare function computeCostMicroUsd(counts: TokenCounts, pricing: ModelPricing): number; /** * RB-368's predicate, single-sourced: does a price table EXIST for this model — an id-keyed * `RunnerDeps.pricing` entry, or the model's own `cost` declaration? When neither does, * `modelCostToPricing(undefined)` yields an all-zero table and every computed cost is a fabricated 0, * indistinguishable from "declared free". Every consumer that must keep those apart (the disclosure faces * that go absent rather than lie; the deployment $ governance window that refuses rather than charge a * fabricated 0) asks HERE, so the two can never drift into two different answers. */ export declare function isModelPriced(model: { id: string; cost?: unknown; }, pricing: Record<string, ModelPricing> | undefined): boolean; /** * The first UNEVALUABLE member of a price table (`"inputPer1M"`, …), or `undefined` when every declared * price is a finite, non-negative number. * * {@link isModelPriced} answers "does a table EXIST", which is the RB-368 question. It is deliberately not * the same question as "can this table price anything": a `NaN` rate silently computes a cost of ZERO (the * `price && tokens` guard treats NaN as absent) and a negative rate computes a negative one, so a table of * garbage looks priced and prices everything at nothing. Wherever a number from this table becomes a * CEILING rather than a report, the caller must ask this too — a bad value has to be loud, never folded * into a silent default. */ export declare function malformedPricingField(pricing: ModelPricing): string | undefined; /** Map a vendor `Model.cost` (already per-1M absolute) to `ModelPricing`. We don't emit 1h cache * writes, so `cacheWriteLongPer1M` defaults to the 5-min write price (irrelevant while that count is 0). */ export declare function modelCostToPricing(cost: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; } | undefined): ModelPricing;