Agentic.LLM.ProviderAccount (agentic v0.3.0)

Copy Markdown

Per-user, per-provider account state used by the multi-pathway router.

The same model is :pay_per_token for someone with a raw API key and :subscription_included for someone on Pro — so the cost profile lives here, not on Agentic.LLM.Model.

Worth (or any other host) is responsible for resolving these from its settings storage and pushing them into ctx.metadata[:provider_accounts] before each agent run; the router pulls them from ctx, never from disk.

Fields

  • :provider — atom id of the provider (:anthropic, :claude_code, :openrouter, …).
  • :cost_profile:free | :subscription_included | :subscription_metered | :pay_per_token. Drives the dominant term in Preference.score/4.

  • :subscription — optional %{plan: String.t(), monthly_fee: Money.t()} describing the subscription. Used by the dashboard to amortize cost across actual token usage. nil for pay-per-token accounts.
  • :credentials_status:ready | :missing | :expired. A purely informational field; routing reads :availability.

  • :availability:ready | :degraded | {:rate_limited, DateTime.t()} | :unavailable. Hard filter for :unavailable; continuous penalty for the others.

  • :quotas — optional %{tokens_used: int, tokens_limit: int, period_end: DateTime.t()}. Drives quota_pressure_score/1 so subscriptions taper toward pay-per-token alternatives as the cap is approached.
  • :account_id — opaque string identifying which configured account this is (Worth uses this when multiple keys for the same provider are configured). Surfaced on routes so spend attribution is unambiguous.

Summary

Functions

Build a sensible default account for provider — used in tests and as a fallback when the host did not supply an account in ctx.metadata.

Look up the account for provider in a list of ProviderAccount structs, falling back to default/1. Used by the router to find the matching account for each pathway.

Compute the [0.0..1.0]+ pressure that quota usage adds to scoring. Returns 0.0 when there is plenty of headroom; ramps after 70% of the cap; hard cliff after 90%. Designed so subscriptions taper toward alternative pathways as the weekly cap is approached, rather than hard-failing mid-session.

Types

availability()

@type availability() ::
  :ready | :degraded | :unavailable | {:rate_limited, DateTime.t()}

cost_profile()

@type cost_profile() ::
  :free | :subscription_included | :subscription_metered | :pay_per_token

credentials_status()

@type credentials_status() :: :ready | :missing | :expired

quotas()

@type quotas() :: %{
  tokens_used: non_neg_integer(),
  tokens_limit: non_neg_integer(),
  period_end: DateTime.t()
}

subscription()

@type subscription() :: %{plan: String.t(), monthly_fee: any()}

t()

@type t() :: %Agentic.LLM.ProviderAccount{
  account_id: String.t(),
  availability: availability(),
  cost_profile: cost_profile(),
  credentials_status: credentials_status(),
  provider: atom(),
  quotas: quotas() | nil,
  subscription: subscription() | nil
}

Functions

default(provider)

@spec default(atom()) :: t()

Build a sensible default account for provider — used in tests and as a fallback when the host did not supply an account in ctx.metadata.

Defaults to :pay_per_token + :ready, which is the right behaviour for someone who pasted in a regular API key and hasn't told us anything more.

for_provider(accounts, provider)

@spec for_provider([t()] | nil, atom()) :: t()

Look up the account for provider in a list of ProviderAccount structs, falling back to default/1. Used by the router to find the matching account for each pathway.

quota_pressure(provider_account)

@spec quota_pressure(t()) :: float()

Compute the [0.0..1.0]+ pressure that quota usage adds to scoring. Returns 0.0 when there is plenty of headroom; ramps after 70% of the cap; hard cliff after 90%. Designed so subscriptions taper toward alternative pathways as the weekly cap is approached, rather than hard-failing mid-session.