dexcost
Rust

Configuration

All Config fields, environment variables, development mode, TaskOptions attribution fields, and the dexcost CLI.

Configuration options

Pass a Config struct to dexcost::init(). init is safe to call multiple times — only the first call takes effect and subsequent calls return DexcostError::AlreadyInitialized.

FieldTypeDefaultDescription
api_keyOption<String>None — falls back to DEXCOST_API_KEYAPI key for cloud push. Must start with dx_live_ (production) or dx_test_ (sandbox). Omit for local-only mode.
batch_sizeusize100Maximum number of events per sync batch sent to the Control Layer.
flush_interval_secsu645Seconds between background sync pushes.
redact_fieldsVec<String>vec![] (no redaction)Field names removed from event details before cloud push (e.g. vec!["prompt".into(), "completion".into()]).
hash_customer_idboolfalseSHA-256 hash customer_id before cloud push so raw identifiers never leave the process.
environmentOption<String>None — falls back to DEXCOST_ENVDeployment environment. Set to "development" to enable dev console output.
auto_instrumentVec<String>vec![]Names of providers to note for auto-instrumentation intent (e.g. vec!["openai".into()]). Wrapper clients must still be constructed explicitly.
track_httpbooltrueWhen true, the global ServiceCatalog is initialised and made available via service_catalog(). Set to false to skip HTTP cost tracking entirely.
service_catalog_urlOption<String>NoneURL to fetch an updated service catalog JSON at startup, merged in the background after init returns.
buffer_pathOption<PathBuf>None — falls back to DEXCOST_BUFFER_PATH, then ~/.dexcost/buffer.dbExplicit path for the on-disk SQLite event buffer.

init returns Err(DexcostError::InvalidApiKey) if the key format is invalid and Err(DexcostError::AlreadyInitialized) on a second call.

use dexcost::{init, Config};
use std::path::PathBuf;

init(Config {
    api_key: Some("dx_live_...".into()),
    batch_size: 200,
    flush_interval_secs: 10,
    redact_fields: vec!["prompt".into(), "completion".into()],
    hash_customer_id: true,
    track_http: true,
    service_catalog_url: Some("https://catalog.example.com/catalog.json".into()),
    buffer_path: Some(PathBuf::from("/var/lib/myapp/dexcost.db")),
    ..Default::default()
}).unwrap();

Environment variables

Environment variables are read inside Config::validate() when init is called. Explicit field values in Config take precedence over environment variables.

VariableDescription
DEXCOST_API_KEYAPI key. Read when Config::api_key is None.
DEXCOST_ENVDeployment environment. Read when Config::environment is None. Set to development to enable dev console mode.
DEXCOST_ENDPOINTOverride the Control Layer URL. Defaults to https://api.dexcost.io. Read at push time via Config::endpoint().
DEXCOST_BUFFER_PATHOverride the SQLite buffer file path. Read when Config::buffer_path is None and no home directory default applies.
DEXCOST_RATES_PATHOverride the default rates YAML path used by RateRegistry::default_path(). Defaults to ~/.dexcost/rates.yaml.

Development mode

Pass environment: Some("development".into()) in Config (or set DEXCOST_ENV=development) to enable dev mode:

dexcost::init(Config {
    environment: Some("development".into()),
    ..Default::default()
}).unwrap();

In dev mode:

  • Every llm_call, external_cost, compute_cost, and retry_marker event is printed to stderr as it is recorded, including provider, model, token counts, and cost.
  • Task completion is printed with aggregated cost totals and retry counts.
  • An API key is not required — the background pusher is not started.

Dev mode is controlled by dexcost::dev_console::enable_dev_mode(), which is called automatically when environment == "development" during Config::validate().


Attribution & context

TaskOptions

Pass a TaskOptions struct to start_task to set attribution on an individual task:

use dexcost::{start_task, TaskOptions};
use std::collections::HashMap;

let mut task = start_task("resolve_ticket", TaskOptions {
    customer_id: Some("acme-corp".into()),
    project_id: Some("support".into()),
    experiment_id: Some("exp-001".into()),
    variant: Some("v2-prompt".into()),
    metadata: Some(HashMap::from([
        ("tier".into(), serde_json::json!("enterprise")),
    ])),
    ..Default::default()
}).await?;
FieldTypeDescription
customer_idOption<String>Customer attribution. Stored on the task and included in every event within the task.
project_idOption<String>Project attribution.
experiment_idOption<String>Experiment identifier for A/B tracking.
variantOption<String>Variant label for the experiment.
metadataOption<HashMap<String, serde_json::Value>>Arbitrary key-value metadata attached to the task.
parent_task_idOption<String>Explicit parent task link. When None, the parent is inferred from task-local context (via scope).
heuristicsOption<HeuristicConfig>Enable heuristic retry detection. See Instrumentation.

DexcostContext

DexcostContext provides ambient attribution without an explicit task. Call set_context to store it in a process-wide RwLock; wrapper clients and auto-tasks read it via get_dexcost_context:

use dexcost::{set_context, DexcostContext};

set_context(DexcostContext {
    customer_id: Some("acme-corp".into()),
    project_id: Some("chatbot-v2".into()),
    agent: Some("support_bot".into()),
    ..Default::default()
}).await;
FieldTypeDescription
customer_idOption<String>Customer attribution.
project_idOption<String>Project attribution.
agentOption<String>Agent name; overrides the task_type of auto-created session tasks.
metadataOption<HashMap<String, serde_json::Value>>Arbitrary metadata propagated to auto-created tasks.

Call get_dexcost_context().await to read the current context and clear_dexcost_context().await to remove it.


CLI

The dexcost CLI operates offline against the local SQLite buffer. No API key or network connection is required.

Build from source:

cargo install --path . --bin dexcost

dexcost status

Print event and task counts from the local SQLite buffer:

dexcost status
dexcost status --db /path/to/buffer.db

Output includes: database path, task count, event count, pending sync count, and total cost (USD).

dexcost rates

Manage the persistent rate registry stored at ~/.dexcost/rates.yaml:

# List all registered rates
dexcost rates --list

# Import rates from a YAML file and merge into the persistent store
dexcost rates --import rates.yaml

# Export the persistent store to a YAML file
dexcost rates --export rates.yaml

YAML format expected by --import (canonical format shared with the SDK):

rates:
  maps.googleapis.com:
    per: request
    cost_usd: "0.005"
  stripe:
    per: per_transaction
    cost_usd: "0.029"

A legacy flat JSON array of {service, per, cost_usd} objects is also accepted on import for backward compatibility. Files exported by the CLI re-import cleanly via RateRegistry::load_from_file and vice versa.

dexcost scan

Static-analysis scan of your codebase to identify LLM call sites and untracked cost points:

dexcost scan ./src
dexcost scan ./src --generate-stubs

scan walks the target directory (default .), identifies auto-instrumented LLM call sites, and flags external API calls that likely incur cost but have no record_cost call nearby. --generate-stubs prints ready-to-paste record_cost snippets for each untracked cost point found.

On this page