link

package
v1.801.464 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package link is the unified AI login manager's registry: the org+user-scoped record of WHICH provider accounts (Claude Max, ChatGPT Plus, a Hanzo API key, a raw provider key) a developer has signed into, ON WHICH MACHINES, with each account's latest usage snapshot. It is the cross-machine view console renders as "AI Providers / Accounts" and the source the redundancy route policy reads.

THE ATOM is a Link: one (user, device, provider, account) binding plus its kind, status, last-seen, and the latest usage snapshot. A device is the (machine, host, os) projection shared by a machine's Links — not a separate stored entity, so there is no device/link join and no orphan-device GC. A user's accounts across every machine are just that user's Links.

NO SECRET LIVES HERE. The provider's OAuth token / API key stays device-local (exactly as @hanzo/usage keeps it, reading each provider's own login to meter usage); the registry holds LINK METADATA + usage snapshots only. The collector (@hanzo/usage's reporter) registers a Link and pushes its usage using the SAME IAM bearer it already carries — never a provider secret.

BILLING FOLLOWS THE ACCOUNT, not the registry. This store never touches commerce — it holds no metering client, so it is structurally incapable of creating a charge. A subscription account's inference bills the user's monthly plan (metered here for visibility only); an api-key / hanzo account's inference bills via commerce on the existing gateway path, unchanged. Each Link carries the mode that says which (BillingMode), so a usage event's billing is explicit.

ISOLATION: org is the tenant key and user is the owner key; every read and write leads with `org=? AND user=?` bound predicates, so a caller sees and mutates only their OWN accounts within their OWN org — fail-closed.

Index

Constants

View Source
const (
	// KindSubscription is a provider account signed in with the user's own
	// subscription login (Claude Max, ChatGPT Plus). Its inference bills the
	// user's monthly plan; the registry meters it for visibility only.
	KindSubscription = "subscription"
	// KindAPIKey is an account credentialed by an API key (a raw provider key,
	// or a Hanzo sk- key). Its inference bills via commerce on the gateway path.
	KindAPIKey = "apikey"
)

Link kinds — how an account is credentialed, which decides how its usage bills.

View Source
const (
	StatusLinked  = "linked"
	StatusRevoked = "revoked"
)

Link statuses. linked is active; revoked is a logged-out account whose sessions were stopped. A revoked Link is retained (not deleted) so its usage history and the audit trail survive a log-out.

View Source
const (
	// BillingPlan: the user's own subscription pays; NO commerce charge.
	BillingPlan = "plan"
	// BillingCommerce: usage bills via commerce (the gateway meter), as today.
	BillingCommerce = "commerce"
)

Billing modes — the value a Link and a route candidate carry so "how does this account's usage bill" is explicit, never inferred at the charge site.

View Source
const (
	Window6h    = "6h"
	WindowDay   = "day"
	WindowWeek  = "week"
	WindowMonth = "month"
)

The CANONICAL window value: the ONE closed vocabulary that rate limits, quotas, and usage rollups all share, platform-wide. Lowercase, no case variants, no synonyms — a window is one of exactly these four values.

This declaration is the single source of the vocabulary. (hanzoai/commerce today carries an ad-hoc set — weekly/daily/monthly/hourly plus capitalized variants, and no sub-day window at all; a later commerce pass adopts THESE values. Do not copy the old strings back in.)

View Source
const (
	ConfidenceExact       = "exact"
	ConfidenceEstimated   = "estimated"
	ConfidencePercentOnly = "percentOnly"
	ConfidenceUnknown     = "unknown"
)

Confidence values — how real a sample's numbers are. These mirror @hanzo/usage's UsageDataConfidence EXACTLY (they are the meter's own wire values, already carried on Usage.Confidence), so a value crosses the whole system unmapped.

This is the ANTI-FABRICATION FLAG, and the reason it is a column: a Claude sample is `percentOnly`, so its token counters are 0 because they are UNKNOWN — not because the account consumed nothing. Without this value a reader cannot tell "0 tokens" from "no idea", and would render a fabricated zero. With it, the console renders "—".

View Source
const (
	SourceAccount = "account"
	SourceHanzo   = "hanzo"
)

Sources for the global view: which plane a usage row came from. They are NOT interchangeable and are never added together —

  • SourceAccount the provider's OWN plan consumption, metered from the user's own login. Its cost is what the PROVIDER says it charged (0 for a flat subscription); its percent is plan quota. NOT a Hanzo charge.
  • SourceHanzo hanzo.cloud_usage — Hanzo-routed inference. COST OF RECORD.
View Source
const (
	ScopeUser = "user" // the caller's OWN linked accounts (org+subject)
	ScopeOrg  = "org"  // the whole org's Hanzo-routed usage
)

Scopes for a global-view row: the tenancy the row's numbers cover. The two sources answer at different scopes and the row says which, so a reader never silently compares a user's plan usage against an org's whole spend.

View Source
const (
	Range1h  = "1h"
	Range24h = "24h"
	Range7d  = "7d"
	Range30d = "30d"
)

Ranges — the closed allowlist for a read window. ONE resolver serves both sides of the global view, so the account rows and the Hanzo rows always cover the SAME period; two resolvers could drift and turn the union into a lie.

Variables

View Source
var (
	// ErrNoPrincipal — the caller passed a blank org/subject. Fail-closed: an
	// unvalidated request routes nothing.
	ErrNoPrincipal = errors.New("link route: no validated principal")
	// ErrNoLinkedAccount — the caller has no linked account to route through.
	ErrNoLinkedAccount = errors.New("link route: no linked account to route through")
	// ErrAccountUnavailable — the specifically-pinned account is not linked, or its
	// credential could not be resolved. Distinct from "you have none".
	ErrAccountUnavailable = errors.New("link route: the selected account is unavailable")
	// ErrAllExhausted — every candidate was tried and each was rate-limited or
	// unavailable. Maps to HTTP 429.
	ErrAllExhausted = errors.New("link route: all linked accounts are rate-limited or unavailable")
)

Sentinels the router returns. None carries a credential.

View Source
var ErrNoCredential = errors.New("link: no sealed credential for account")

ErrNoCredential is the sentinel a Resolver returns when an account has no sealed credential in KMS (never linked, or unsealed). The router treats it as "this account is unavailable" and cycles past — it NEVER falls back to a platform key.

Functions

func BillingMode

func BillingMode(kind string) string

BillingMode returns how an account of this kind bills its usage: a subscription bills the user's plan (no commerce charge), everything else bills via commerce. It is the ONE place the subscription-vs-api-key distinction is decided, so a usage event's billing is a pure function of the account, never re-derived.

func IsQuota

func IsQuota(err error) bool

IsQuota reports whether err is a quota / rate-limit / 429 outcome — the ONE predicate the cycle decision uses. It recognises a *QuotaError, an error with a StatusCode()/Status() of 429, and, as a fallback for opaque provider SDK errors, a message naming a quota condition. Everything else is a terminal error the router returns without cycling (a non-quota failure is not fixed by another account).

func KMSRef

func KMSRef(org string, a Account) string

KMSRef is the org-scoped KMS coordinate a linked account's credential is sealed at — the ONE convention shared by the connector (which WRITES the secret here) and this router (which READS it). It folds org, provider, and profile into the path exactly as clients/platform's kmsAuthRef folds org+field, so a credential can only ever be ADDRESSED within its own org's namespace. The org segment is the first path component, so no provider/profile value a caller might influence can escape the org prefix.

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the /v1/links surface. The sessions seam is set from the agents in-process adapter (adapters.go) so a revoke can stop the affected sessions.

func Shutdown

func Shutdown(context.Context) error

Shutdown closes the store. Idempotent.

func WithAccount

func WithAccount(ctx context.Context, a Account) context.Context

WithAccount carries the NON-SECRET account identity beside the credential, so the egress can attribute/audit by account without a second parameter.

func WithCredential

func WithCredential(ctx context.Context, cred Credential) context.Context

WithCredential returns a context carrying cred for an account-aware upstream to read at egress. Process-local; never serialized.

func ZeroPrice

func ZeroPrice(Result) int64

ZeroPrice charges nothing. It is the honest default for bring-your-own-account routing: the customer pays the provider directly on their own key, so absent an operator-set platform fee, Hanzo records the USAGE without inventing a charge.

Types

type AIClientUpstream

type AIClientUpstream struct{ AI types.AIClient }

AIClientUpstream adapts a platform types.AIClient into an Upstream. It threads the resolved credential + account to the client through the request context (WithCredential / WithAccount) — never a header, body, or log — so an ACCOUNT-AWARE client (the in-process ai egress that reads CredentialFrom at dial time) authenticates with the caller's OWN credential. The request Payload must be a *types.ChatRequest.

func (AIClientUpstream) Call

func (u AIClientUpstream) Call(ctx context.Context, cred Credential, a Account, req Request) (Result, error)

Call dials the wrapped AIClient with the credential attached to the context. A provider quota/429 surfaces as an error the router classifies via IsQuota and cycles on; any other error is terminal.

type Account

type Account struct {
	Provider string
	Profile  string
}

Account is the identity of ONE linked provider account: a provider and a profile within it. It is the openclaw `@anthropic:work` selector — provider "anthropic", profile "work" — and a NON-SECRET identifier: it NAMES an account, never carries its credential. An empty Profile denotes the provider's default account.

func AccountFrom

func AccountFrom(ctx context.Context) (Account, bool)

AccountFrom returns the routed account identity, if any.

func ParseAccountRef

func ParseAccountRef(s string) (Account, bool)

ParseAccountRef parses a bare account selector "provider:profile" or "provider". The profile after the first ':' is optional; a leading/trailing-blank provider yields ok=false (nothing to pin). Only the first ':' splits, so a profile may contain ':' if a provider ever needs it. Pure + total.

func (Account) String

func (a Account) String() string

String renders the account as the wire selector "provider:profile" (or the bare "provider" for the default profile). It is the audit + metering label — always safe to log, because it is an identifier and never a secret.

type AccountsTotal

type AccountsTotal struct {
	Accounts         int   `json:"accounts"`
	Requests         int64 `json:"requests"`
	PromptTokens     int64 `json:"promptTokens"`
	CompletionTokens int64 `json:"completionTokens"`
	TotalTokens      int64 `json:"totalTokens"`
	CostCents        int64 `json:"costCents"`
}

AccountsTotal is the summed usage across a caller's linked accounts.

type AccountsUsage

type AccountsUsage struct {
	Scope    string        `json:"scope"`  // user
	Source   string        `json:"source"` // routed
	Total    AccountsTotal `json:"total"`
	Accounts []RoutedUsage `json:"accounts"`
}

AccountsUsage is the per-account breakdown response. Source is always "routed" — this is the gateway's own routed ledger, distinct from the device collector's plan snapshots (/v1/links/usage/summary) and from the org money ledger (/v1/billing/usage). Scope is always "user": the caller's own linked accounts.

func RoutedBreakdown

func RoutedBreakdown(ctx context.Context, org, subject string) (AccountsUsage, bool)

RoutedBreakdown is the package-level read the billing surface (clients/billing) calls to answer GET /v1/billing/usage/accounts without reaching into link's store. It resolves through the mounted link subsystem; (zero, false) when link is not mounted (a split deploy), so the caller can answer an honest "unavailable" rather than a fabricated empty breakdown. org+subject are the caller's validated principal — the billing handler passes principal.Org(c) + c.User(), never a client value — so this can only ever read the caller's OWN accounts.

type Config

type Config struct {
	Links    Links
	Resolver Resolver
	Upstream Upstream
	Meter    Meter
	Logger   luxlog.Logger
	Policy   Policy
	Cooldown time.Duration
	Now      func() time.Time
}

Config constructs a Router. Links, Resolver, and Upstream are required; Meter and Logger are optional; Now defaults to time.Now; Cooldown defaults to 60s.

type Credential

type Credential struct {
	Token  string            // bearer / api-key / OAuth access token — the secret
	Header string            // auth header to carry it; "" ⟹ Authorization
	Scheme string            // "Bearer" | "" (raw); how Token is presented
	Extra  map[string]string // multi-field providers (region, project, sign-key)
	Expiry time.Time         // token expiry; zero ⟹ non-expiring (a raw api key)
}

Credential is a resolved upstream provider credential, held in memory for the life of ONE request and then discarded. It is NEVER persisted, and it never appears in a log line, a response, an error, or an argv: String and GoString redact, so even an accidental %v / %+v / %#v of a Credential — or of any struct that embeds one — cannot leak the token. The router treats it as opaque: it hands the value to Upstream.Call and retains nothing.

func CredentialFrom

func CredentialFrom(ctx context.Context) (Credential, bool)

CredentialFrom returns the routed credential the router attached, if any. The provider egress calls this to authenticate its upstream call with the caller's own account, then discards the value. Absent ⟹ the request was not account-routed (the platform's own path), so the egress uses its normal credential.

func (Credential) GoString

func (Credential) GoString() string

GoString redacts the %#v form too, so a %+v of an enclosing struct is safe.

func (Credential) String

func (Credential) String() string

String redacts. A Credential must never render its token, on any code path.

type Link struct {
	ID       string
	Org      string
	User     string // the owning subject (validated principal); a user sees only their own
	Machine  string // stable machine id (the device key)
	Host     string // hostname (device label)
	OS       string // platform label (darwin|linux|windows|…)
	Provider string // matches @hanzo/usage providerRegistry id (claude|codex|hanzo|openai|…)
	Account  string // the subscription/account id or label (e.g. an account email)
	Plan     string // the plan name from the provider identity (e.g. "Claude Max"); display only
	Kind     string // subscription | apikey
	Status   string // linked | revoked
	LastSeen int64  // unix; bumped on every usage report
	Usage    string // JSON of the latest Usage projection ("" until first report)

	CreatedAt int64
	UpdatedAt int64
}

Link is one (user, device, provider, account) binding. Tenant isolation is the (org, user) pair, enforced on every query. It never stores a secret.

type Links interface {
	ListLinked(ctx context.Context, org, subject string) ([]Link, error)
}

Links is the read seam over the linked-account registry the router routes across. *Store satisfies it; a unit test supplies a fake. The router depends on THIS, not the concrete SQLite store, so its selection + cycling policy is proven without a database or KMS — and the isolation guarantee is a deterministic assertion.

type Meter

type Meter interface {
	RecordRouted(ctx context.Context, org, subject string, a Account, kind string, res Result)
}

Meter records a served routed call to the per-account usage ledger. A nil Meter disables metering (routing still works). It is a separate seam so the router's policy is tested without a warehouse or a billing client.

func NewMeter

func NewMeter(store *Store, billing *metering.Client, price Pricer, log luxlog.Logger) Meter

NewMeter builds the reference Meter. A nil billing client disables the money debit (usage is still counted); a nil price defaults to ZeroPrice; a nil clock defaults to time.Now.

type Policy

type Policy int

Policy is the cycle order over a caller's candidate accounts.

const (
	// PolicyPlan is route.go's redundancy order: subscription accounts first (the
	// flat-rate pool, most-headroom first), then api-key accounts (the metered
	// backstop). The sensible default; it reuses the ONE ordering Plan already owns.
	PolicyPlan Policy = iota
	// PolicyMostRemaining orders purely by remaining rate-limit headroom, highest
	// first — "route to whichever account has the most quota left", regardless of kind.
	PolicyMostRemaining
	// PolicyRoundRobin spreads load evenly across a provider's accounts by rotating
	// the Plan order per (org, subject) on each request.
	PolicyRoundRobin
)

type Pricer

type Pricer func(res Result) int64

Pricer returns the retail charge, in whole USD cents, for one served routed call. It is injected so the router never fabricates a price: the operator sets the BYO platform-fee policy, and the default (ZeroPrice) charges nothing — usage is still metered, but no money is invented.

type QuotaError

type QuotaError struct {
	Provider string
	Status   int
	Err      error
}

QuotaError marks an upstream outcome as a quota / rate-limit / 429 the router should CYCLE on. Upstream adapters wrap their 429s in it (or return HTTP 429, which IsQuota also recognises). It carries the provider + status for the audit trail, never any credential.

func (*QuotaError) Error

func (e *QuotaError) Error() string

func (*QuotaError) Unwrap

func (e *QuotaError) Unwrap() error

type Request

type Request struct {
	Model   string
	Payload any
}

Request is the provider-agnostic inference the Upstream executes. The router owns SELECTION and CYCLING, not prompt shaping, so Payload is opaque: it is never inspected, mutated, or logged here.

type Resolver

type Resolver interface {
	Resolve(ctx context.Context, org, subject string, a Account) (Credential, error)
}

Resolver is the credential-fetch contract this package CONSUMES. Given an account it returns the KMS-stored credential, refreshed if the connector keeps it fresh. It is scoped to (org, subject): the key is bound HERE from the validated principal the router passes, never taken from a request, so a foreign org's or user's account is unreachable — resolving across the tenant boundary is unrepresentable, not merely refused.

func NewKMSResolver

func NewKMSResolver(kms cloud.KMSClient) Resolver

NewKMSResolver builds the live Resolver over cloud KMS. A nil KMS yields a resolver that reports every account unavailable (fail-closed), never a panic.

type Result

type Result struct {
	Model            string
	PromptTokens     int64
	CompletionTokens int64
	TotalTokens      int64
	Response         any
}

Result is a served call's outcome the meter records. A routed call's usage is EXACT — the upstream returns real token counts — unlike a plan-percent snapshot.

type RouteCandidate

type RouteCandidate struct {
	Provider    string  `json:"provider"`
	Account     string  `json:"account,omitempty"`
	Plan        string  `json:"plan,omitempty"`
	Kind        string  `json:"kind"`    // subscription | apikey
	Billing     string  `json:"billing"` // plan | commerce (BillingMode(Kind))
	Available   bool    `json:"available"`
	HeadroomPct float64 `json:"headroomPct"` // remaining capacity 0..100
	Machine     string  `json:"machine,omitempty"`
	Host        string  `json:"host,omitempty"`
	LinkID      string  `json:"linkId"`
	Reason      string  `json:"reason,omitempty"` // why unavailable, when Available=false
}

RouteCandidate is one account the policy would route to, in preference order, annotated with how it bills and whether it currently has rate-limit headroom.

type RoutePlan

type RoutePlan struct {
	Candidates  []RouteCandidate `json:"candidates"`
	Primary     *RouteCandidate  `json:"primary,omitempty"`
	GeneratedAt string           `json:"generatedAt"`
}

RoutePlan is the ordered redundancy plan for a user's accounts: the candidates in preference order plus the primary the caller would try first.

func Plan

func Plan(links []Link, now time.Time) RoutePlan

Plan builds the redundancy route plan from a user's LINKED accounts. Ordering: subscription accounts first (the flat-rate pool the user already pays for — used first, and failed over between for redundancy: two Claude Max accounts), then api-key/hanzo accounts (the metered API route). Within each group the order is (available first, then most headroom, then most-recently seen). The primary is the first available candidate; if every account is rate-limited it falls back to the first api-key account (the pay-per-call backstop is always usable) — else nil, an honest "nothing routable right now".

Only linked accounts should be passed (Store.ListLinked); a revoked account is never a candidate. The function is pure and total: any Links in, one plan out.

type RoutedUsage

type RoutedUsage struct {
	Provider         string `json:"provider"`
	Account          string `json:"account,omitempty"`
	Kind             string `json:"kind"`
	Billing          string `json:"billing"` // BillingMode(Kind): plan | commerce
	Requests         int64  `json:"requests"`
	PromptTokens     int64  `json:"promptTokens"`
	CompletionTokens int64  `json:"completionTokens"`
	TotalTokens      int64  `json:"totalTokens"`
	CostCents        int64  `json:"costCents"`
	FirstAt          int64  `json:"-"`
	LastAt           int64  `json:"-"`
}

RoutedUsage is one account's summed server-routed usage — a row of the per-account breakdown the dashboard reads.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router runs the failover: it selects a caller's OWN linked accounts, orders them by policy, and tries each — resolving its credential and dialing the upstream — cycling on a live quota error until one serves or all are spent.

func NewRouter

func NewRouter(cfg Config) (*Router, error)

NewRouter builds a Router from cfg. It returns an error only for a missing required seam, so a misconfiguration fails at wiring time, never at request time.

func NewRouterFromDeps

func NewRouterFromDeps(deps cloud.Deps, up Upstream) (*Router, bool, error)

NewRouterFromDeps builds the live Router from cloud.Deps and a caller-supplied Upstream. The account registry is the mounted link Store. It returns (nil, false, nil) when link is not mounted — so a co-resident caller can cleanly skip routing — and an error only for a bad configuration.

func (*Router) Route

func (r *Router) Route(ctx context.Context, org, subject string, sel Selection, req Request) (Result, Account, error)

Route runs the failover for one request. org and subject are the VALIDATED principal's (the caller passes principal.Org(c) + c.User()) — never a request field. sel is the parsed, non-secret account selection. It returns the served Result and the Account it was served through, or a sentinel error.

type Sample

type Sample struct {
	Provider string // matches Link.Provider / @hanzo/usage providerRegistry id
	Account  string // the account label (an identifier, never a secret)
	Plan     string // the plan name from the provider identity; display only
	Kind     string // subscription | apikey (validKind) — mirrors Link.Kind
	Machine  string // the machine that OBSERVED this (an attribute, not a key)

	Lane          string    // the meter's lane id (five_hour|seven_day_opus|…) — the identity
	Window        string    // the canonical class (6h|day|week|month)
	WindowMinutes int32     // the duration the meter reported (300 for Claude's 5h)
	WindowStart   time.Time // WHICH instance of the window this measures — the dedup key
	ResetsAt      time.Time // when the window resets (RateWindow.resetsAt); zero = unknown

	// UsedPct is the lane's used percent, 0..100 — RateWindow.usedPercent. For a
	// subscription account this is THE quota signal and often the ONLY one.
	//
	// QUOTA GAP (deliberate, reported, not faked): there is NO absolute
	// used/limit token quota column, because nothing can populate one. The meter
	// reports a percent, not a limit (Claude: dataConfidence `percentOnly`). The
	// hanzoai/plans catalog expresses `ai.requests_per_min` / `ai.tokens_per_min`
	// — per-MINUTE rate limits, a different concept from a window quota — and in
	// any case it is HANZO's own plan catalog, which cannot know what Anthropic
	// grants a subscription plan. An always-zero quota_limit would read as "the
	// limit is zero"; absent is honest. It lands as an additive column when a
	// source for it exists.
	UsedPct    float64
	Confidence string // exact|estimated|percentOnly|unknown — see the const block
	// Synthetic marks a lane the meter FABRICATED (RateWindow.isSyntheticPlaceholder
	// — set when a provider returned null for a lane and the adapter filled it in).
	// Carried so a made-up lane is never rendered as observed truth.
	Synthetic bool

	// Absolute counters — UsageTotals. Present only when the source really reports
	// them (`exact`); zero otherwise, which Confidence disambiguates.
	Requests          int64
	InputTokens       int64
	OutputTokens      int64
	TotalTokens       int64
	CachedInputTokens int64

	// Money — ProviderCostSnapshot, in minor units (cents). CostCents is what the
	// PROVIDER says this account spent (Claude's extra_usage overage; 0 for a flat
	// subscription — the plan is the charge). CostLimitCents is that meter's budget
	// (ProviderCostSnapshot.limit) — a MONEY cap, never a token quota. Neither is a
	// Hanzo charge: this plane holds no metering client.
	CostCents      int64
	CostLimitCents int64
	Currency       string // ProviderCostSnapshot.currencyCode; "" = unknown
}

Sample is one metering lane's consumption of one provider account at one observation.

TENANCY IS NOT ON THE VALUE. Org and subject are bound by the server from the validated principal at the boundary, so a client cannot assert whose usage this is — the shape makes cross-tenant writes unrepresentable rather than merely refused.

func (Sample) Sanitize

func (s Sample) Sanitize(now time.Time) Sample

Sanitize bounds every field so a warehouse row stays small, finite, and well-formed no matter what a client sends: strings trimmed and length-capped, counters non-negative and clamped, percents coerced into [0,100] even for NaN/Inf, instants bounded to a sane window around `now`, and the two enums defaulted rather than trusted. It is TOTAL (never errors), so the write path always has a safe value — validation of the CLOSED vocabularies (window, kind) is the boundary's job and is a 400, because silently rewriting a caller's window class would corrupt their dash.

It does NOT set the observation clock: the server stamps that, so a client can never backdate a sample or pin a stale one as newest.

type Selection

type Selection struct {
	Account Account
	Pinned  bool
}

Selection is the per-request account choice. A zero Account means "route across ALL of my linked accounts" (auto-failover); a set Account with Pinned=true means "use THIS account (and, for cycling, its provider's other profiles)".

func ParseModelRef

func ParseModelRef(ref string) (model string, sel Selection)

ParseModelRef splits an openclaw-style model reference "model@provider:profile" into the bare model and the account it pins. It mirrors openclaw's `Opus@anthropic:work`:

"gpt-4o"              ⟹ model "gpt-4o",  no pin
"Opus@anthropic:work" ⟹ model "Opus",   pin anthropic:work
"Opus@anthropic"      ⟹ model "Opus",   pin anthropic (default profile)
"Opus@"               ⟹ model "Opus",   no pin (empty account is not a pin)

Only the FIRST '@' splits, so a model id that itself contains '@' keeps everything after the provider:profile intact is not a concern here — provider and profile are single path-ish labels. Pure + total.

func SelectionFrom

func SelectionFrom(header, modelRef, sessionPin string) (model string, sel Selection)

SelectionFrom resolves a request's account selection from its non-secret selectors, in precedence order:

  1. an explicit X-Provider-Account header ("provider:profile") — the per-request override,
  2. the model reference's "@provider:profile" suffix — the openclaw inline form,
  3. a session pin ("provider:profile") the caller set earlier — the sticky default that mirrors openclaw's `Opus@anthropic:work` staying selected.

It returns the BARE model (the "@…" stripped) and the Selection. None of the three inputs is org/subject; the router still binds tenancy from the principal.

type SessionMatch

type SessionMatch struct {
	Subject  string
	Host     string
	Provider string
	Account  string
}

SessionMatch selects the live sessions a revoke stops. Fields are ANDed with the org; an empty field is "any". A link revoke matches {Host,Provider,Account} (the device+account the sessions ran under); a device revoke matches {Host}. Subject is the REVOKING user; the adapter turns it into the session Actor so a stop only ever reaches that user's OWN sessions — Host/Provider/Account (attacker-set at link upsert) can then only narrow WITHIN them, never widen to a co-tenant's.

type Sessions

type Sessions interface {
	Stop(ctx context.Context, org string, m SessionMatch) (int, error)
	CountActive(ctx context.Context, org string, m SessionMatch) (int, error)
}

Sessions is the seam to the agent-session control plane (clients/agents, in-process). Revoke stops the sessions that ran under a revoked account/device; the device view counts a machine's active sessions. A nil seam (unit test / no agents mounted) makes revoke skip the stop and the count report 0 — the registry truth (the revoked row) is unaffected.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the login-manager database. ONE SQLite file — the deployment's own "link" — holds every org's Links; tenancy is the (org, subject) pair. It holds NO metering client — it is structurally incapable of charging commerce.

It also owns the account-usage SERIES in the datastore (datastore.go): the Link row is an account's latest state, the series is its history. Both are the same (org, subject) tenancy, so they live behind one store rather than two seams that could drift apart on isolation. dsReady latches the idempotent warehouse DDL — on success only, so a datastore still connecting at boot is retried, not permanently written off.

func (*Store) AccountTotals

func (s *Store) AccountTotals(ctx context.Context, org, subject string, from, to time.Time) ([]Total, bool)

AccountTotals reads the caller's own account usage per (provider, window).

func (*Store) AddRouted

func (s *Store) AddRouted(ctx context.Context, org, subject string, a Account, kind string, res Result, costCents, now int64) error

AddRouted sums one served routed call into the caller's per-account counter. Org and subject are the SERVER's values (the validated principal the router passed), bound positionally — the account cannot carry them, so a row can only ever be the caller's OWN within their OWN org. costCents is the charge recorded for the call (0 for a subscription account, whose plan pays the provider directly).

It does NOT attempt idempotency: a routed call is a fresh event, and each is one unit of usage. At-most-once for the CHARGE is the money ledger's job (its RequestID key), not this visibility counter's.

func (*Store) Close

func (s *Store) Close() error

func (*Store) Get

func (s *Store) Get(ctx context.Context, org, subject, id string) (Link, error)

Get returns one Link by id within the caller's (org, subject) scope, or errNotFound. The (org, subject, id) triple is the key, so another tenant's or another user's id resolves to errNotFound — never a cross-scope read.

func (*Store) HanzoTotals

func (s *Store) HanzoTotals(ctx context.Context, org string, from, to time.Time) ([]Total, bool)

HanzoTotals reads the org's Hanzo-routed usage per provider over the same window. Its rows are always `exact` — cloud_usage counts real calls we billed.

func (*Store) List

func (s *Store) List(ctx context.Context, org, subject string) ([]Link, error)

List returns every Link the (org, subject) owns, most-recently-updated first (revoked rows included, so the dashboard can show recent log-outs).

func (*Store) ListDevice

func (s *Store) ListDevice(ctx context.Context, org, subject, machine string) ([]Link, error)

ListDevice returns the accounts on one machine within the caller's scope, most-recently-updated first.

func (*Store) ListLinked

func (s *Store) ListLinked(ctx context.Context, org, subject string) ([]Link, error)

ListLinked returns only the ACTIVE (status=linked) accounts the (org, subject) owns — the set the route policy considers. Revoked accounts are excluded so a logged-out account is never a routing candidate.

func (*Store) Revoke

func (s *Store) Revoke(ctx context.Context, org, subject, id string, now int64) (Link, bool, error)

Revoke marks one Link revoked within the caller's scope and returns it (so the handler can stop the sessions that ran under that account). ok=false when no such Link exists in this (org, subject) — a cross-scope id can neither revoke nor probe. An already-revoked Link is returned as-is (idempotent).

func (*Store) RevokeDevice

func (s *Store) RevokeDevice(ctx context.Context, org, subject, machine string, now int64) ([]Link, error)

RevokeDevice marks EVERY still-linked account on one machine revoked within the caller's scope and returns the rows it revoked (so their sessions can be stopped). A device with no linked accounts revokes nothing (empty slice).

func (*Store) RoutedTotals

func (s *Store) RoutedTotals(ctx context.Context, org, subject string) ([]RoutedUsage, error)

RoutedTotals returns the caller's per-account server-routed usage, most-used first. Every query leads with `org = ? AND subject = ?` as bound parameters, so a caller reads ONLY their OWN accounts within their OWN org — the same fail-closed tenancy every other read in this package uses.

func (*Store) Series

func (s *Store) Series(ctx context.Context, org, subject, provider, account, window string, from, to time.Time) ([]Sample, bool)

Series reads one provider account's window instances. It returns (rows, true) when the warehouse answered and (nil, false) when it is unavailable, so the caller can say "unavailable" instead of showing zeros that read as "you used nothing".

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, x Link) (Link, error)

Upsert registers a Link, keyed by its (org, subject, machine, provider, account) identity. A first report INSERTs (using x.ID / x.CreatedAt); a repeat UPDATEs in place — bumping last_seen, refreshing the device labels/plan/kind, re-activating a revoked account (status→linked), and replacing the usage snapshot ONLY when a fresh one is supplied (an empty usage on a heartbeat keeps the last good snapshot, mirroring @hanzo/usage's keep-stale-over-flapping rule). It returns the stored row (with its authoritative id, which on a repeat is the original, not x.ID). Org+subject are part of the identity, so a caller can only ever write within their OWN (org, subject) scope.

func (*Store) WriteSamples

func (s *Store) WriteSamples(ctx context.Context, org, subject string, samples []Sample, now time.Time) error

WriteSamples appends the caller's sanitized samples to the warehouse, stamping each with the server's observation clock. Org and subject are the SERVER's values (from the validated principal), bound positionally — a sample cannot carry them, so it cannot forge them.

It is FAIL-SOFT by contract: an absent or blocked datastore is not an error the caller sees. The Link row that the same request refreshes is the durable truth; this series is history beside it, and losing a poll of it must never fail a report or block a device.

type Total

type Total struct {
	Source     string
	Scope      string
	Provider   string
	Window     string
	Requests   int64
	Tokens     int64
	CostCents  int64
	UsedPct    float64
	Confidence string
	Windows    int64
}

Total is one provider's usage in the global view, from ONE source at ONE scope.

type Upstream

type Upstream interface {
	Call(ctx context.Context, cred Credential, a Account, req Request) (Result, error)
}

Upstream dials ONE provider account with its resolved credential and returns the served usage, or a classified error. A quota / rate-limit / 429 outcome MUST be returned as an error satisfying IsQuota, so the router knows to CYCLE; any other error is terminal for the request (cycling cannot fix a bad request and might double-serve). cred is for this one call — the Upstream must not retain or log it.

type Usage

type Usage struct {
	SessionPct   float64 `json:"sessionPct"`             // primary window used %, 0..100
	WeeklyPct    float64 `json:"weeklyPct"`              // secondary window used %, 0..100
	ResetsAt     string  `json:"resetsAt,omitempty"`     // primary window reset, RFC3339
	Tokens       int64   `json:"tokens"`                 // absolute token total when known
	InputTokens  int64   `json:"inputTokens,omitempty"`  //
	OutputTokens int64   `json:"outputTokens,omitempty"` //
	SpendCents   int64   `json:"spendCents"`             // provider spend (0 for a subscription)
	Currency     string  `json:"currency,omitempty"`     //
	Confidence   string  `json:"confidence,omitempty"`   // exact|estimated|percentOnly|unknown
	UpdatedAt    string  `json:"updatedAt,omitempty"`    // snapshot time, RFC3339
}

Usage is the projection of a provider's @hanzo/usage UsageSnapshot the collector pushes and the dashboard renders: the rate-limit windows, token totals, and spend. It is stored as JSON on the Link and parsed for the route policy's headroom. Money is USD cents end-to-end. Spend is always 0 for a pure subscription account (there is no per-call charge — the plan is flat).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL