supercode-harness 0.4.17

The optional native Supercode agent and tool harness
Documentation
//! BP-7 (catalog §4a "Turn/budget caps" — the *spend* cap; "Per-turn
//! cost/usage accounting" — the *cost* half): token→dollars on the request
//! path.
//!
//! Until BP-7 the only pricing in the workspace was
//! [`crate::pricing_ref`], whose own module doc says it is "not used
//! anywhere on the request path" — which is exactly why the ledger's
//! `turn-budget-caps` row read "no spend/budget cap at all — no pricing
//! exists on the request path". This module is that missing piece, and
//! nothing more: a per-million-token price pair for a model id, resolved
//! from (1) the config's explicit override
//! ([`crate::Config::price_input_per_mtok`] /
//! [`crate::Config::price_output_per_mtok`]) or (2) a small built-in table
//! of published list prices for the model families the parity presets pin.
//!
//! **Deliberately fails closed.** [`resolve`] returns `None` for a model it
//! cannot price rather than guessing, and [`crate::Agent::new`] refuses to
//! build an agent that arms `core.max_budget_usd` against an unpriceable
//! model. A spend cap that silently never bites is worse than no cap: the
//! caller believes they are protected.

/// Per-million-token prices for one model, in US dollars.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModelPrice {
    /// Input (prompt) tokens, dollars per million.
    pub input_per_mtok: f64,
    /// Output (completion) tokens, dollars per million.
    pub output_per_mtok: f64,
}

impl ModelPrice {
    /// Dollar cost of one round-trip's token counts.
    ///
    /// Cached prompt tokens are billed at the full input rate here: the
    /// provider-reported discount varies per provider and per cache tier,
    /// and over-reporting cost is the safe direction for a *cap* (a budget
    /// that stops slightly early never overspends). Named rather than
    /// silently assumed — see the `per-turn-cost-usage-accounting` ledger
    /// row's note.
    pub fn cost_usd(&self, prompt_tokens: u64, completion_tokens: u64) -> f64 {
        (prompt_tokens as f64 / 1_000_000.0) * self.input_per_mtok
            + (completion_tokens as f64 / 1_000_000.0) * self.output_per_mtok
    }
}

/// Published list prices for the model families the parity presets pin,
/// matched by SUBSTRING on the model id so the provider-prefixed spellings
/// (`anthropic/claude-opus-4-8`, `claude-opus-4-8`,
/// `us.anthropic.claude-opus-4-8-v1:0`) all resolve to the same row.
///
/// Longest pattern first — `claude-haiku` must win over a shorter prefix
/// that would also match.
const BUILT_IN_PRICES: &[(&str, ModelPrice)] = &[
    (
        "claude-opus",
        ModelPrice {
            input_per_mtok: crate::pricing_ref::REF_INPUT_PER_MTOK,
            output_per_mtok: crate::pricing_ref::REF_OUTPUT_PER_MTOK,
        },
    ),
    (
        "claude-sonnet",
        ModelPrice {
            input_per_mtok: 3.00,
            output_per_mtok: 15.00,
        },
    ),
    (
        "claude-haiku",
        ModelPrice {
            input_per_mtok: 1.00,
            output_per_mtok: 5.00,
        },
    ),
];

/// The built-in list price for `model`, if this build knows one.
pub fn built_in(model: &str) -> Option<ModelPrice> {
    BUILT_IN_PRICES
        .iter()
        .find(|(pattern, _)| model.contains(pattern))
        .map(|(_, price)| *price)
}

/// Resolve the price to bill `model` at: the explicit config override when
/// BOTH halves are set, else the built-in table, else `None`.
///
/// Both override halves are required together on purpose — an input price
/// with no output price would silently bill completions at zero.
pub fn resolve(
    model: &str,
    price_input_per_mtok: Option<f64>,
    price_output_per_mtok: Option<f64>,
) -> Option<ModelPrice> {
    match (price_input_per_mtok, price_output_per_mtok) {
        (Some(input_per_mtok), Some(output_per_mtok)) => Some(ModelPrice {
            input_per_mtok,
            output_per_mtok,
        }),
        _ => built_in(model),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn built_in_matches_every_provider_spelling_of_the_same_family() {
        for spelling in [
            "anthropic/claude-opus-4-8",
            "claude-opus-4-8",
            "us.anthropic.claude-opus-4-8-v1:0",
        ] {
            assert_eq!(
                built_in(spelling).map(|p| p.input_per_mtok),
                Some(crate::pricing_ref::REF_INPUT_PER_MTOK),
                "{spelling}"
            );
        }
    }

    #[test]
    fn haiku_is_not_swallowed_by_a_broader_family_row() {
        let haiku = built_in("anthropic/claude-haiku-4-5").expect("haiku is priced");
        let opus = built_in("anthropic/claude-opus-4-8").expect("opus is priced");
        assert!(haiku.input_per_mtok < opus.input_per_mtok);
    }

    #[test]
    fn an_unknown_model_has_no_built_in_price() {
        assert!(built_in("someone-elses/model-1").is_none());
    }

    #[test]
    fn an_override_needs_both_halves_and_wins_over_the_table() {
        assert_eq!(
            resolve("anthropic/claude-opus-4-8", Some(1.0), Some(2.0)),
            Some(ModelPrice {
                input_per_mtok: 1.0,
                output_per_mtok: 2.0
            })
        );
        // Half an override falls back to the table rather than billing
        // completions at zero.
        assert_eq!(
            resolve("anthropic/claude-opus-4-8", Some(1.0), None).map(|p| p.output_per_mtok),
            Some(crate::pricing_ref::REF_OUTPUT_PER_MTOK)
        );
        assert!(resolve("someone-elses/model-1", None, Some(2.0)).is_none());
    }

    #[test]
    fn cost_is_the_two_rates_summed_over_a_million() {
        let price = ModelPrice {
            input_per_mtok: 10.0,
            output_per_mtok: 100.0,
        };
        // 1M input + 0.1M output = $10 + $10.
        assert!((price.cost_usd(1_000_000, 100_000) - 20.0).abs() < 1e-9);
        assert_eq!(price.cost_usd(0, 0), 0.0);
    }
}