Documentation
¶
Overview ¶
Package usage keeps the token count of every call the gateway serves, so magpie can show what each agent and model consumed and roughly what it cost. Records go to one JSON-lines file next to providers.json; nothing leaves the machine.
Index ¶
- Constants
- Variables
- func AgentOf(ua string) string
- func Append(r Record)
- func FormatCost(amountUSD float64, currency string, rate float64) string
- func GroupRouted(sent, served string) bool
- func LastSeen(agent string) time.Time
- func Ledger(p Period, f Filter) (rows []Row, sum Totals, agents []string)
- func NewPricer() func([]Record) Totals
- func Path() string
- func Saw(agent string)
- func Swapped(sent, served string) bool
- func Vias(since time.Time) map[string][]Via
- func WriteCSV(w io.Writer, rows []Row) error
- type Filter
- type Group
- type Known
- type Ledgered
- type Part
- type Period
- type Point
- type Record
- type RequestPage
- type Row
- type SeriesPoint
- type Share
- type Summary
- type Totals
- type Via
Constants ¶
const UnknownProvider = "session-unknown"
UnknownProvider groups local session calls without claiming a supplier. Recorded provider IDs and matched creator identities stay in their own fields.
Variables ¶
var Agents func() []Known
Agents lists the agents magpie knows. Package agent sets it from each agent's own description, so they are listed in one place only.
var CSVHeader = []string{"time", "agent", "requested_model", "provider", "host", "model", "served_model", "swapped",
"effort", "input_tokens", "output_tokens", "cache_write_tokens", "cache_read_tokens", "reasoning_tokens",
"cost_usd", "duration_ms", "ttft_ms", "status", "error", "session", "kind", "provider_key_id", "provider_key_name", "route_id",
"request_id", "endpoint", "error_message", "error_type", "source", "rejected", "session_provider", "session_account", "session_official_login", "caller_key_id", "caller_key_name"}
CSVHeader is the ledger's columns, as WriteCSV writes them.
var Dimensions = []string{"provider", "agent", "model"}
Dimensions are what calls are told apart by: the provider they went to, the agent that made them, the model.
var LogCalls func(time.Time) []sessions.Call
LogCalls reads the calls the agents' own session files record, which the ledger adds to those the gateway logged. A variable for the tests.
Periods lists the windows in order.
Functions ¶
func AgentOf ¶
AgentOf names the agent behind a client User-Agent. Known agents map to their magpie id; anything else keeps its product name.
func Append ¶
func Append(r Record)
Append writes one record. Errors are swallowed: accounting must never break a call.
func FormatCost ¶ added in v0.1.404
FormatCost renders an effective-price cost, kept in USD everywhere it's stored, as the CLI and TUI show it: at amountUSD's own price when currency isn't "cny", else converted at rate (CNY per one USD, from internal/fx; a rate of 0 or below also falls back to USD, a stale or missing rate being no reason to hide the number). Whole dollars or yuan above 100, cents above 1, else thousandths, so a fraction of a cent still shows as something.
func GroupRouted ¶ added in v0.1.563
GroupRouted reports whether sent is a routing group of another magpie (a remote magpie's "group/<id>", the one name only a magpie answers to) and served the model its reply named: the member the group routed the request to, which is the group doing its job, not the vendor serving another model than asked (莫 on Discord: group/auto-… answered by deepseek/deepseek-v4.1-flash, marked a swap).
func LastSeen ¶ added in v0.1.91
LastSeen is when a request from the agent last reached the gateway: this process's own, else the newest in the log's last stretch. Zero if none.
func Ledger ¶ added in v0.1.408
Ledger is LedgerOf's rows, sum and agents.
A row's swapped is judged by the upstream names in force now, not by the one its call went out under: the names are read from the settings here, once, so a name given or changed since a call was recorded re-judges that call — one recorded under the name magpie knows the model by, and read after a name was set for it, reads as a swap although the vendor answered with the very model that was asked for. A record keeps the canonical id and the name the vendor's own reply gave (served_model) and not the name the request was sent under, so this is the only name there is to judge by — read as the name that went out, which the effort a record keeps of makes exact: an Antigravity account is asked for the variant of the model that level picks, so a reply naming that one is the model that was asked for and not another, whatever the family it is a level of. It is how ledgerWith's loop already treats the rest of a row: a provider renamed since is put back to the id it has now, and the names in force now are what the catalog lists the models under as well.
Storing the name that was sent would judge each call by what it was really sent under, and it is the better answer, but it is not this function's to make: it is a field on every record the gateway writes, and so a change to the file's format — a column a row written by an older magpie has no value for, and a decision about whether an empty one means "no upstream name" or "unknown" — rather than a row of a report. The rows here are what a reader sets beside a bill, and a name in force today is the one whose reply a bill's model column is read against.
The names are read here, once for the lot, rather than asked for a row at a time, so a caller already holding them — a report over the provider/model it was given — reads them the same way and reaches the same judgement: what a row says is the same however the rows were asked for.
func NewPricer ¶ added in v0.1.591
NewPricer totals requests' token-bearing attempts at one snapshot of the effective prices used by the ledger. Reuse it for one page of routes; make it again on the next read so a changed tariff re-prices history.
func Swapped ¶ added in v0.1.408
Swapped reports whether served is another model than sent: not the same name, however dated, pinned or prefixed. A vendor's "auto" (Copilot's, Cursor's) asked it to pick, so whichever answers wasn't swapped in, nor is the member another magpie's routing group sent it to (GroupRouted).
func Vias ¶ added in v0.1.284
Vias is what the gateway sent each session's calls to since a time, by agent id and the session's id ("codex|<id>"), the most calls first.
Types ¶
type Filter ¶ added in v0.1.408
type Filter struct {
CallerKey string
RouteID int64
Model string // exact model selected in the ranking
Agent string
Provider string // a provider's id, as the ledger's rows have it
Failed bool
Query string
}
Filter narrows the ledger: to a route, one agent (its id, as AgentOf gives it), to one provider, to the failed calls, and to the rows whose models, provider, host or session hold Query (any case).
type Group ¶
type Group struct {
ID string `json:"id"`
Provider string `json:"provider,omitempty"` // models only
Model string `json:"model,omitempty"`
ProviderKeyID string `json:"providerKeyId,omitempty"`
ProviderKeyName string `json:"providerKeyName,omitempty"`
CallerKeyID string `json:"callerKeyId,omitempty"`
CallerKeyName string `json:"callerKeyName,omitempty"`
// Host is where the calls went, when the provider's id has gone to
// more than one place, or elsewhere than the provider goes now: its
// calls are then told apart by it, not summed under the id.
Host string `json:"host,omitempty"`
// Agent is the agent a session is of (sessions only).
Agent string `json:"agent,omitempty"`
Totals
}
Group is the share of one agent or model.
type Known ¶ added in v0.1.43
type Known struct {
ID string
Names []string // its id and aliases, which a record may already carry
UA []string // what its User-Agent begins with
}
Known is an agent as its requests name it.
type Ledgered ¶ added in v0.1.579
Ledgered is a period's calls that a filter keeps, newest first, with their sum, and the agents and providers that made any call in the period (their ids, for a filter to offer).
type Part ¶ added in v0.1.579
Part is what one provider, agent or model had of a point of the timeline.
type Record ¶
type Record struct {
RouteID int64 `json:"route_id,omitempty"` // the gateway Route, shared by its attempts
Time time.Time `json:"t"`
Agent string `json:"agent"` // magpie agent id, or the client's product name
Provider string `json:"provider"`
Host string `json:"host,omitempty"` // where the call went: provider.Where then
ProviderKeyID string `json:"providerKeyId,omitempty"` // fingerprint of the API key actually used
ProviderKeyName string `json:"providerKeyName,omitempty"`
CallerKeyID string `json:"callerKeyId,omitempty"`
CallerKeyName string `json:"callerKeyName,omitempty"`
// SessionProvider is the provider ID recorded by the agent. SessionAccount
// identifies the session's creator, not the account used for an API request.
// Neither field establishes an upstream route from today's configuration.
SessionProvider string `json:"session_provider,omitempty"`
SessionAccount string `json:"session_account,omitempty"`
// SessionOfficialLogin marks a matched account's explicit official login
// metadata, not this call's endpoint or billing authentication.
SessionOfficialLogin bool `json:"session_official_login,omitempty"`
Model string `json:"model"` // the provider's model id
// Requested is the model id the agent asked for (a magpie alias, a
// routing group, provider/model…), and Served the model the vendor's
// reply says answered, when it named one: a ledger to set beside the
// vendor's own bill. Neither is in a record written before they were.
Requested string `json:"req,omitempty"`
Served string `json:"served,omitempty"`
Input int `json:"in"`
Output int `json:"out"`
CacheRead int `json:"cache_read,omitempty"`
CacheWrite int `json:"cache_write,omitempty"`
Reasoning int `json:"reasoning,omitempty"`
// Effort is the reasoning the model was asked for — a routing group's
// pick for the turn, or the agent's own — as it takes it; "" for none
Effort string `json:"effort,omitempty"`
Millis int64 `json:"ms"`
// TTFT: ms from the request to its reply's first content — text,
// reasoning or a tool call — and FirstText to its first text, counted
// as Millis is, so Millis-TTFT is how long the reply took to write;
// none for a reply that wasn't streamed (#196)
TTFT int64 `json:"ttft_ms,omitempty"`
FirstText int64 `json:"first_text_ms,omitempty"`
Status int `json:"status"`
// Error is why a call failed, in the vendor's words and cut short;
// ErrType what its body called the error (rate_limit_error,
// usage_limit_reached); RequestID the id the vendor gave the call; and
// Endpoint the path the agent called, with the one it was sent to when
// it went out in another protocol (/v1/messages → /v1/chat/completions)
Error string `json:"err,omitempty"`
ErrType string `json:"err_type,omitempty"`
RequestID string `json:"rid,omitempty"`
Endpoint string `json:"ep,omitempty"`
// Session is the conversation the call was part of, as its agent names
// it (X-Magpie-Session, or the session header Claude Code, Codex or
// OpenCode sends): several sessions on one model told apart
Session string `json:"session,omitempty"`
// NativeSession retains the client header when X-Magpie-Session overrides it.
NativeSession string `json:"native_session,omitempty"`
// Rejected is a request refused locally before an upstream was contacted.
Rejected bool `json:"rejected,omitempty"`
// Kind is what the agent made the call for when it isn't a turn of
// the conversation: a Codex subagent's (review, compact, guardian…)
Kind string `json:"kind,omitempty"`
// Via is the computer whose magpie passed the call on to this one (a
// Remote magpie provider there), Agent being the agent's on it; "" for
// a call made on this computer
Via string `json:"via,omitempty"`
}
Record is one call.
func Load ¶
Load streams complete records from the log. Historical request bodies are not retained by a global cache after the caller finishes with this snapshot.
func (Record) Failed ¶ added in v0.1.579
Failed is whether the call failed: the agent was answered an error status, or, for a call read from a session file, which has no status, the file says an error ended it.
func (Record) IsRejected ¶ added in v0.1.579
IsRejected also recognizes local rejections written before the explicit flag.
type RequestPage ¶ added in v0.1.579
type Row ¶ added in v0.1.408
type Row struct {
Record
// Cost is the call's effective price in USD, when its model's is known
// (Priced); Swapped: the reply named another model than Model
Cost float64 `json:"cost"`
Priced bool `json:"priced"`
Swapped bool `json:"swapped,omitempty"`
// Routed marks a remote magpie group answered by one of its members.
Routed bool `json:"routed,omitempty"`
// Source is "log" for a call read from an agent's own session file,
// which the gateway never saw: no status, no provider, no model asked
// for, but what the file says of the call. "" for one the gateway logged.
Source string `json:"source,omitempty"`
}
Row is one call as the ledger lists it.
type SeriesPoint ¶ added in v0.1.579
SeriesPoint is a point of the timeline with its calls told apart, by each dimension, for a chart to stack them: the models of one point, say.
func LedgerSeries ¶ added in v0.1.579
func LedgerSeries(p Period, rows []Row) (bucket string, pts []SeriesPoint)
LedgerSeries is the rows of a period, the ledger's or a filter's, summed by hour, day or week, for the chart over them. A row is of the point its time falls in; the rows are in any order.
type Share ¶ added in v0.1.579
type Share struct {
}
Share is one provider's, agent's or model's part of some rows.
type Summary ¶
type Summary struct {
Period Period `json:"period"`
Since time.Time `json:"since"`
Totals
Bucket string `json:"bucket"` // hour | day | week
Series []Point `json:"series"`
Agents []Group `json:"agents"`
Models []Group `json:"models"`
ProviderKeys []Group `json:"providerKeys"`
CallerKeys []Group `json:"callerKeys"`
// Sessions are the calls that named their session, by session.
Sessions []Group `json:"sessions"`
}
Summary is a period, summed up.
type Totals ¶
type Totals struct {
Calls int `json:"calls"`
Errors int `json:"errors"`
Input int `json:"input"`
Output int `json:"output"`
CacheRead int `json:"cache_read"`
CacheWrite int `json:"cache_write"`
Reasoning int `json:"reasoning"`
Cost float64 `json:"cost"` // USD at the effective price, for the priced calls
Unpriced int `json:"unpriced"` // calls with tokens but no known price
// Timed: the answered calls whose first token was timed (streamed),
// TTFT the sum of their ttft_ms; DecodeMs the time from it to the end
// of those that wrote any, over which DecodeOut tokens came: their
// mean wait, and how fast they wrote (#196)
Timed int `json:"timed,omitempty"`
TTFT int64 `json:"ttft_ms,omitempty"`
DecodeMs int64 `json:"decode_ms,omitempty"`
DecodeOut int `json:"decode_out,omitempty"`
}
Totals is a sum of calls.
func (Totals) AllTokens ¶ added in v0.1.579
AllTokens is what went in and out and through the cache.
func (Totals) MeanTTFT ¶ added in v0.1.392
MeanTTFT is the mean ms to the first token of the timed calls, 0 for none.
type Via ¶ added in v0.1.284
type Via struct {
Provider string `json:"provider"`
Model string `json:"model"`
Effort string `json:"effort,omitempty"`
Calls int `json:"calls"`
Tokens int `json:"tokens"` // in and out, as Totals counts them
Last time.Time `json:"last"`
}
Via is where the gateway sent a session's calls: one model at one reasoning effort, how often and how much.