@sema-agent/core
Version:
Stateless, task-oriented AI agent core
72 lines (71 loc) • 4.03 kB
TypeScript
/**
* 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;