CostGuard 0.1.0

dotnet add package CostGuard --version 0.1.0
                    
NuGet\Install-Package CostGuard -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CostGuard" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CostGuard" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="CostGuard" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CostGuard --version 0.1.0
                    
#r "nuget: CostGuard, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CostGuard@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CostGuard&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=CostGuard&version=0.1.0
                    
Install as a Cake Tool

CostGuard

Cost tracking, budgets, and automatic model fallback for Microsoft.Extensions.AI.

Microsoft.Extensions.AI tells you how many tokens a request used. CostGuard turns that into money — and lets you act on it:

  • 💵 Per-request cost in USD — computed from a built-in (and fully overridable) pricing catalog for Anthropic, OpenAI, and Google models, attached to every ChatResponse.
  • 🚧 Budgets — daily / monthly / all-time limits per tenant, per user, or globally. When a limit is hit: throw, log, or automatically degrade to a cheaper model.
  • 🏷️ Scopes — attribute spend to tenants/users/features via an ambient CostScope, a ChatOptions key, or a custom selector.
  • 📈 OpenTelemetry-friendly metrics — cost and token counters on the CostGuard meter.
  • 🔌 Plain middleware — one .UseCostGuard() call in your existing ChatClientBuilder pipeline. Works with any IChatClient provider (OpenAI, Anthropic, Azure, Ollama, …), streaming included.

Install

dotnet add package CostGuard

Quickstart

using CostGuard;
using Microsoft.Extensions.AI;

IChatClient client = new ChatClientBuilder(innerProviderClient)
    .UseCostGuard(o =>
    {
        // Each scope (tenant/user) gets $10/day; past that, requests run on a cheaper model.
        o.Budgets.Add(new Budget
        {
            Scope = CostScopes.PerScope,
            Period = BudgetPeriod.Daily,
            LimitUsd = 10m,
            Action = BudgetAction.DegradeModel,
            FallbackModelId = "claude-haiku-4-5",
        });

        // Hard stop at $500/month across the whole app.
        o.Budgets.Add(new Budget
        {
            Scope = CostScopes.Total,
            Period = BudgetPeriod.Monthly,
            LimitUsd = 500m,
            Action = BudgetAction.Throw,
        });
    })
    .Build();

// Attribute spend to a tenant for everything inside the scope:
using (CostScope.Begin($"tenant:{tenantId}"))
{
    ChatResponse response = await client.GetResponseAsync(messages);

    ChatCost? cost = response.GetCostGuardCost();
    Console.WriteLine($"This request cost ${cost?.TotalUsd:0.####}");
}

No budgets configured? CostGuard still meters every request — useful as a pure cost observability layer.

How budgets work

Budgets are checked before each request against spend already recorded in the store, so a scope can overshoot its limit by at most one request. Windows are fixed UTC calendar periods (Daily, Monthly, AllTime).

Budget.Scope Meaning
CostScopes.PerScope ("*") The limit applies to each scope value individually (every tenant gets its own allowance).
CostScopes.Total The limit applies to aggregate spend across all scopes.
"tenant:acme" The limit applies to that exact scope only.
Budget.Action Effect when the limit is reached
Throw BudgetExceededException before the provider is called.
DegradeModel ChatOptions.ModelId is rewritten to FallbackModelId; the response is marked via response.WasDegradedByCostGuard().
LogOnly A warning is logged; the request proceeds unchanged.

Scopes

Three ways to say who is spending, in priority order:

// 1. A custom selector (e.g. read your own request context):
o.ScopeSelector = options => myRequestContext.TenantId;

// 2. Per-request, on ChatOptions:
var options = new ChatOptions
{
    AdditionalProperties = new() { [CostGuardKeys.Scope] = "user:42" },
};

// 3. Ambient, flows across awaits (great for ASP.NET Core middleware):
using (CostScope.Begin("tenant:acme")) { ... }

Unscoped requests land in CostScopes.Default ("global").

Pricing catalog

PricingCatalog.CreateDefault() ships best-effort list prices (snapshot: July 2026, USD per 1M tokens) for current Anthropic (Claude), OpenAI (GPT-5.x), and Google (Gemini) models, including discounted cache-read rates where known. Model ids resolve by exact match first, then longest prefix — so claude-sonnet-5-20260115 finds the claude-sonnet-5 entry.

Provider prices change. For anything billing-critical, verify against your invoice and override:

o.Pricing.Add("my-fine-tune", inputPerMTokUsd: 4m, outputPerMTokUsd: 20m);

// Or load/override from a JSON feed you control:
o.Pricing.AddFromJson("""
{
  "claude-sonnet-5": { "inputPerMTok": 2.0, "outputPerMTok": 10.0, "cachedInputPerMTok": 0.2 },
  "anthropic.claude-opus-5": { "inputPerMTok": 5.0, "outputPerMTok": 25.0 }
}
""");

Unknown models are logged once and skipped by default (UnknownModelBehavior.Warn); set Throw to fail fast or Ignore to silence.

Reporting

CostGuardOptions? costGuard = client.GetCostGuardOptions();
decimal monthly = await costGuard!.SpendStore.GetCurrentSpendAsync("tenant:acme", BudgetPeriod.Monthly);
decimal total   = await costGuard.SpendStore.GetCurrentSpendAsync(CostScopes.Total, BudgetPeriod.AllTime);

The default InMemorySpendStore is per-process and resets on restart. For durable, multi-instance enforcement, implement the two-method ISpendStore interface over your database or Redis and assign it to o.SpendStore.

Metrics

Register the meter with OpenTelemetry:

services.AddOpenTelemetry().WithMetrics(m => m.AddMeter(CostGuardDiagnostics.MeterName));
Instrument Type Unit Tags
costguard.cost Counter USD model.id, cost.scope
costguard.request.cost Histogram USD model.id, cost.scope
costguard.tokens.input Counter tokens model.id, cost.scope
costguard.tokens.output Counter tokens model.id, cost.scope

Notes & limitations

  • Costs are estimates computed from reported token usage and configured prices — reconcile against provider invoices for accounting purposes.
  • Cached-input discounts treat cached tokens as a subset of input tokens (OpenAI-style reporting); detection reads common provider keys from UsageDetails.AdditionalCounts.
  • Budget enforcement is pre-flight: parallel requests in the same scope can each pass the check before either records spend.
  • Streaming responses are metered when the provider reports usage in the stream (most do, on the final update).

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 168 7/28/2026