/** * Anthropic's own usage report, read as a usage log. * * The fifth converter in the pattern `from-claude-code` started, and the first * one that reads a **provider** rather than a tool sitting in front of one. * Every other converter answers *what did the calls my proxy saw cost*; this * one answers *what does the provider say my organisation used*, which is the * question a finance team asks and the one no local log can answer, because a * log only knows the machines it was written on. * * ## Why this takes a file and not a key * * The Usage & Cost API needs an **admin** credential: an `sk-ant-admin01-…` * key, an `org:admin` OAuth token, or an unscoped personal or service account * key. Workspace keys do not work. That credential can read every workspace's * usage and manage the organisation's members and keys, which makes it exactly * the kind of secret this project has spent its whole design not holding. * * So this function takes the **response**, not the credential. The operator * runs one `curl` with their own key, in their own shell, and pipes the JSON * here. Trazum never sees the key, never makes the request, and stays a pure * function of text — which is also why this file can be tested without a * network and why the deterministic core is still deterministic. * * ## The format is derived, not guessed * * Every field below is from the published response schema of * `GET /v1/organizations/usage_report/messages`: `data[]` of time buckets with * `starting_at`, `ending_at` and `results[]`, and each result carrying * `uncached_input_tokens`, `cache_read_input_tokens`, `cache_creation` * (`ephemeral_5m_input_tokens`, `ephemeral_1h_input_tokens`), `output_tokens`, * `model`, `service_tier` and `server_tool_use.web_search_requests`. * * The cache shape needs no translation at all: `parseUsageLine` already reads * `cache_creation.ephemeral_5m_input_tokens` and its 1-hour sibling, because * that is the shape the Messages API itself returns. The one rename is * `uncached_input_tokens` to `input_tokens`, and it is a rename rather than a * sum: this report reports cached reads **beside** the uncached input rather * than folded into it, which is the Anthropic shape and not the OpenAI one. * * ## What it refuses to price, and counts instead * * **A result with no model.** `model` is `null` unless the caller passed * `group_by[]=model`. Nothing on a row without it says what answered, so * nothing can price it. Refused, counted, and the count names the missing * parameter — a converter that quietly returned an empty log would send * somebody looking for a bug in their key. * * **A non-standard service tier.** Batch is billed at a discount and priority * is not billed like either; a catalogue rate is the standard rate. Pricing a * batch row from it would overstate a bill by half and look right doing it, * which is this product's one unforgivable failure. Those rows are left out * and counted, the same way an unpriceable line becomes a named gap rather * than a guess. * * **Web search requests.** `server_tool_use.web_search_requests` is billed per * request, not per token, so no token rate reaches it. Counted and named, so a * total that is missing them says so. * * **A truncated report.** `has_more: true` means the answer is one page of * several, and a bill built from one page is understated by however many pages * were not asked for. Said out loud rather than left for somebody to notice in * a figure. * * ## What deliberately does not cross * * `account_id`, `service_account_id` and `api_key_id` are read by nothing * here. The first two name people; the third names a credential. None of them * is a project name and none of them reaches the output. * * `workspace_id` is read only when the operator hands over a mapping, because * a workspace id is not a project name and turning one into the other is a * decision rather than an inference. `labelByWorkspace` is that decision, * written down: the same shape `--label-by-cwd` established for transcripts, * exact rather than prefixed, because an opaque identifier has no hierarchy * to take the longest match of. * * ## The `null` that means two things, and how it is told apart * * The report's own schema says `workspace_id` is `null` **both** when the * caller did not group by workspace **and** for the default workspace. One * value, two meanings, and a mapping that guessed between them would put the * default workspace's money on a label nobody chose or leave it off one they * did. * * It is derived instead: if any result in the report carries a workspace id, * the report was grouped, and a `null` there is the default workspace — so a * rule written for `null` applies to it. If no result carries one, nothing * distinguishes the two readings, `null` rules do not apply, and the * conversion says the split was asked for and could not be made. * * ## The instant, and why it is the bucket's * * This report has no calls in it. A bucket is an interval and its usage is a * sum over that interval, so every record made from it carries the bucket's * `starting_at` — a day's usage at that day's start, an hour's at the hour's. * Anything finer would be a precision the source does not have, and * `bucket_width=1h` is there for an operator who wants it. */ /** * One workspace, and the project name the operator gives it. * * Matched exactly, never by prefix: a workspace id is opaque and two of them * sharing leading characters share nothing at all. `null` names the default * workspace, which is the only workspace the report identifies by absence. */ export interface WorkspaceLabel { workspace: string | null; label: string; } /** One converted record, shaped exactly as `parseUsageLine` reads it. */ export interface AnthropicUsageRecord { model: string; ts: string; label?: string; usage: { input_tokens: number; output_tokens: number; cache_read_input_tokens?: number; cache_creation?: { ephemeral_5m_input_tokens: number; ephemeral_1h_input_tokens: number; }; }; } export interface AnthropicUsageConversion { records: AnthropicUsageRecord[]; /** Time buckets in the report, including intervals with no usage. */ buckets: number; /** Results that became records. */ rows: number; /** Results with `model: null`, which nothing can price. */ unnamedModel: number; /** Results at a tier a standard rate would misprice. */ nonStandardTier: number; /** * Whether any result named its tier at all. * * `false` means the report was not grouped by `service_tier`, so a batch row * and a standard row are indistinguishable here and the discount, if any, * is invisible. Not an error and not a refusal: most organisations run * standard only. It is said because the alternative is a reader assuming a * question was answered that was never asked. */ tierNamed: boolean; /** Server-side tool calls billed per request, which no token rate prices. */ webSearchRequests: number; /** `has_more`: this is one page and the bill from it is understated. */ truncated: boolean; /** Records whose label came from a workspace rule rather than `--label`. */ labelledByWorkspace: number; /** * Results carrying a workspace no rule named. * * They keep `--label` if there is one and go unattributed if there is not, * which is the same fallback `--label-by-cwd` makes: a workspace nobody * wrote a rule for is unattributed rather than attributed to a neighbour. */ unruledWorkspace: number; /** * Rules were given and no result carried a workspace id. * * Either the report was not grouped by workspace or this organisation uses * only the default one, and nothing here can tell those apart. The split * asked for was not made, and that is said rather than left to be noticed. */ workspaceNotGrouped: boolean; /** The input was not the JSON this endpoint returns. */ unparseable: number; } /** * @param label the project this usage belongs to, chosen by the operator. * * Optional, and absent means the records carry none — the unattributed bucket * every other door already understands. The provider does not know this * organisation's project names and neither does this file. */ export declare function anthropicUsageRecords(text: string, options?: { label?: string; labelByWorkspace?: readonly WorkspaceLabel[]; }): AnthropicUsageConversion; /** * Whether this text is an Anthropic usage report rather than some other JSON. * * Three fields together, because each alone is too common to decide on: * `uncached_input_tokens` is this endpoint's own name for a count every other * shape calls something else, and a report always carries buckets with a * `starting_at`. A sniffer that matched on `output_tokens` alone would claim * every Messages response ever saved to a file. */ export declare function looksLikeAnthropicUsage(text: string, prefixBytes?: number): boolean; //# sourceMappingURL=anthropic-usage.d.ts.map