commits on main
2026-10-25
4d3aaca6 dawn feat(opencode-go): accept oauth credentials and sendable pipe buffer
feat(opencode-go): accept oauth credentials and sendable pipe buffer
2026-10-13
3951be35 dawn fix(antigravity): claim no window length, the feed only gives a reset time
fix(antigravity): claim no window length, the feed only gives a reset time
epochToWindowLabel printed the time left until reset, so a fixed 5h window read 3h, then 2h, then 1h as the day went on. The feed never states a window, so the limit keeps its resetsAt and drops windowLabel.
2026-09-28
1a4bff97 dawn fix(cli): end the stdin envelope at its first newline
fix(cli): end the stdin envelope at its first newline
An Erlang port cannot close stdin without closing the port, so a host there never sends the EOF `advance -` waited for. The envelope is one JSON line; reading to the first newline (or EOF) lets such a host write the line and read the answer.
333f8ab4 dawn feat(core): give limits an id, windowSeconds and scope, and read advance from stdin
feat(core): give limits an id, windowSeconds and scope, and read advance from stdin
A host tracking quota over time needs to key a limit, know its window, and know which usage counts against it; `label` is display text and says none of that reliably. ABI 4 adds three fields to every limit: - `id`, a stable machine key within the provider: anthropic's `kind`, codex's `primary`/`secondary` and `<metered_feature>.<window>`, antigravity's family, opencode's window key, zai's `<type>:<unit>:<number>` (one type can repeat with another period), alibaba's `five_hour`/`weekly`, hyper's `hypercredits`. - `windowSeconds`, when the provider states it (codex's `limit_window_seconds`, hyper's refresh endpoints) or the plan fixes it; absent when unknown, as for zai's units other than hours. - `scope`, the model family or feature a limit is restricted to: anthropic's body `scope`, or the kind's suffix (`weekly_opus` -> `opus`), codex's additional limit name, antigravity's family. Absent means the limit counts all usage. `advance -` takes the whole call as one JSON envelope on stdin, `{provider, credential, responses: [round...], nowMs?}`, so a credential never lands in argv where other local processes can read it. It stays stateless: the host keeps every round's responses and the CLI replays them through one fresh state, as the argument form does. Mirrored in the TypeScript reference in prime-agent.
2026-09-16
c7211f29 dawn fix(alibaba): quote a CLI's JSON error message, not its braces
fix(alibaba): quote a CLI's JSON error message, not its braces
bl failed with exit 3 and wrote its error as JSON on stderr: {"error": {"message": "No console access token found.", "hint": "Run `bl auth login --console`."}} and the failure reason took the body's first line, so the tray showed "bailian CLI failed (exit 3): {" — less useful than the exit code alone. A JSON body now contributes its error.message and error.hint (the action the user needs); a plain-text body keeps its first non-empty line; JSON without a message leaves the reason as the bare exit code. Mirrored in the TypeScript reference with tests on both.
ddcf76ec dawn fix(core): tell a missing command apart from a host that refuses to run commands
fix(core): tell a missing command apart from a host that refuses to run commands
The protocol reserved 127 for "this host does not run commands", and the Swift runner used the same code for "the binary is not there" (via a `/usr/bin/env` fallback) — so alibaba's feed read a missing `bl` as a refusal and told the user to capture a console session, when the actual problem was that the command did not exist. The tray surfaced exactly that wrong sentence within minutes of being wired up. - a command that cannot be found or spawned answers 126 ("found it, could not run it"), which the feed treats as an ordinary CLI failure, and the header now names both codes. - a non-zero exit with empty stdout carries stderr as the body, so the feed has something to show. - the CLI failure reason includes the host's first line of text: "bailian CLI failed (exit 126): bl is not on PATH" rather than "exit 126". Mirrored in the TypeScript reference so parity holds.
ec6efc26 dawn fix(swift): resolve libusage.a from this package, not from the builder's directory
fix(swift): resolve libusage.a from this package, not from the builder's directory
The manifest linked `../zig-out/lib/libusage.a`, which is only correct when this package is the root one being built: consumed as a dependency, SwiftPM resolves the relative path against the *consumer's* directory and the link fails with "no such file or directory". The tray is the first consumer, so the path is now absolute, computed in the manifest from `#filePath` — the manifest always knows where it lives, whatever is building it. Verified both ways: `swift run --package-path swift usage-smoke tests/fixtures` here, and a throwaway package that depends on `../usage-core/swift` and prints `abi=3 providers=8`.
7104b97b dawn fix(core): escape quotes again, keep the reference's window label, and sniff the hyper body
fix(core): escape quotes again, keep the reference's window label, and sniff the hyper body
Three divergences found by reading the ABI-3 diff and pinned by new parity cases in prime-agent. All three are cases the parity suite could not see before, because it drove Zig one round per process and never compared an HTTP request step. - writeJsonString emitted a bare `"` for a quote inside a value, so any provider string containing one produced invalid JSON: `"notes":["Team: Hyper "Squad""]`. The escape table is the boundary between provider-controlled text and the JSON contract, so it now has a test that parses its own output. - epochToWindowLabel dropped the label for a reset already in the past. The reference (prime-agent's driver.ts) floors it to `Math.max(1, …)`, and a host that shows nothing looks like a provider with no window at all. - hyper classified its round by response count alone, so a caller handing in the billing body as the only response got "no hyper teams found". The reference distinguishes by body shape (`plan` or `balances`), and the host loop is not the only caller. Two tests added for the first two; prime-agent's parity suite grows a multi-round hyper case and a past-due reset case for all three.
cf47adbb dawn fix(core): bump to ABI 3 with host-owned state, safe float casts, and pipe draining
fix(core): bump to ABI 3 with host-owned state, safe float casts, and pipe draining
e7764b5c dawn docs: stand as its own project
docs: stand as its own project
The core moved out of prime-agent's native/usage into its own repository: same toolchain, same fixtures, same ABI. The README now describes this repo on its own terms, records how the TypeScript reference and the parity suite stay in prime-agent, and replaces the stale status with what the eight providers actually answer from (including the `cli` credential kind and the command request the host runs). History is preserved: the commits below are the core's own, extracted with `git subtree split --prefix=native/usage`.
2aa3198d dawn feat(usage): read alibaba's token plan through the official CLI, not only a scraped session
feat(usage): read alibaba's token plan through the official CLI, not only a scraped session
dawn's token-plan keys are inference-only, so we reverse-engineered the console endpoint from a browser HAR. That worked, but it was the wrong shape of solution: the provider ships a supported CLI, and CodexBar's docs (MIT, read as reference only — dawn does not want us depending on it) name both it and the `quota-config` endpoint we had missed. - `UsageRequest` is now a union: `{kind:"http",...}` or `{kind:"command", command, args, timeoutMs}`. The core still spawns nothing; it asks, and the host answers. A host that will not run commands replies `status: 127` and the feed routes around it into the captured-session path. - a new credential kind, `cli`: no secret, because the provider's own CLI is already signed in. - the alibaba feed tries `bl usage token-plan --console-region <id> --console-site <site> --output json` first, over the four region variants (`intl`/`cn`/`intl-personal`/`cn-personal`), and falls back to the console session. One parser reads both payloads, because the CLI answers with the same field names the console wraps in `data.DataV2.data.data`: `per*Percentage` is a FRACTION and `per*ResetTime` is epoch MILLISECONDS. - two corrections from the reference: `sec_token` is optional (some accounts are not given one, and the console proceeds without it — ours used to fail), and `quota-config` reports per-tier allowances, so the report now says "Token plan pro: weekly 40000, 5h 12000 credits" beside the percentage. Remaining days and reset cards become notes. - the coding-agent host runs only allowlisted command names (`bl`), with the environment narrowed to PATH/HOME/locale/timezone/proxies, a timeout and a 64 KiB stdout cap; a refusal or a missing binary comes back as a status the feed treats as data. - the Zig core and the Swift driver mirror all of it, including the command request in the Swift decoder and a `runCommand` that applies the same allowlist, timeout and cap. Verified: zig unit + C ABI tests, the twelve-case parity suite, the Swift smoke test (which now shows `alibaba=4 calls`), 71 pi-ai usage tests, 45 coding-agent usage tests, and `npm run check` end to end. The child proved the new surface bites: dropping `kind` from command requests fails parity and the Swift smoke immediately. Caveat, stated rather than papered over: `bl` is not installed on this machine, so the CLI path is tested against recorded payloads and scripted hosts, not live.
113881bc dawn feat(native): every provider in the zig core, plus the swift wrapper a host can call
feat(native): every provider in the zig core, plus the swift wrapper a host can call
The core could report one provider. It now covers all eight of them from the same definitions the typescript side uses, which is what makes the tray able to drop its own six fetchers. - `usage_advance` (ABI 2) is the host-driven protocol: hand it the credential and whatever responses you already have, get back either the requests to send or the report. hyper's teams-then-billing is just another round, not a second concept. - providers/: antigravity (pools 33 model buckets into Gemini/Claude/GPT-OSS families, drops the internal `chat_*`/`tab_*` ones, and keeps the `antigravity/...` user-agent its endpoint gates on), hyper (credits allocated vs remaining, dollar balance), opencode-go, zai, deepseek (balance-only), alibaba (three console-gateway calls behind a captured session), plus the codex and anthropic ports. - `iso8601.zig`: Zig's std has no parser and four providers hand back ISO strings, with and without fractional seconds and with `Z` or `±hh:mm` offsets. Its unit tests were recomputed independently. - `packages/ai/test/usage-zig-parity.test.ts` now compares all eight providers against the typescript feeds on the recorded bodies, deeply, so a conversion that drifts on either side fails the build. The child proved the comparison bites: dropping the remaining-fraction inversion in antigravity turns 83.4% into 16.6% and fails the antigravity case. - `swift/` is a SwiftPM package: `UsageCore` (the C bridge, with strings freed and NULL turned into a thrown error), `UsageDriver` (the host loop over URLSession, where a transport failure becomes a response with status 0 so the feed reports it as data), and `usage-smoke`, which pins the things most likely to be "fixed" wrongly: codex's three windows, anthropic's ISO reset becoming epoch milliseconds, antigravity's 83.4% inversion and its user-agent requirement, and alibaba's three calls carrying the captured cookies, csrf token and region in the form body. - `npm run check:zig` builds and runs the Swift smoke test where a swift toolchain exists, and says so when it skips. Verified: zig unit + C ABI tests, the eight-provider parity suite, the Swift smoke test against the recorded fixtures, and npm run check end to end.
c54fe13f dawn feat(usage): host-driven feeds for every provider, plus the extension seam
feat(usage): host-driven feeds for every provider, plus the extension seam
The first pass had one provider and one shape. dawn wants /usage to cover what she actually pays for - antigravity, hyper, opencode-go, zai, deepseek, alibaba - and to reuse the same rules from a macOS app, so the query became a definition instead of a function. A feed is now driven by the host: `advance(state, responses)` returns either the requests to send or the finished report. The host owns HTTP, which is what lets one definition run in the CLI, in the Zig core, and in a tray. It also covers the providers that need more than one call - hyper reads /v1/teams before it can ask for that team's billing - without inventing a second concept for them. - packages/ai/docs/usage-feeds.md is the contract: report shape, credential kinds, and the unit rules (percent vs fraction vs remaining-fraction vs dollars; epoch seconds vs milliseconds vs ISO-8601) that every provider gets wrong in its own way. - seven feeds: openai-codex, anthropic (the normalised limits[] array, not the codenamed windows), google-antigravity (including the antigravity/... user-agent its endpoint gates on), hyper (plan allocation vs remaining credits, two-step, dollar balance), opencode-go, zai, and deepseek, which is balance-only and therefore has no windows at all. - reports gained `balance` and `notes`, so a provider without a percentage is not forced into one, and `skipped` became {provider, reason} - a key-only alibaba now says "needs a session credential" instead of going quiet. - credentials are provider-defined: oauth, apiKey, or a captured session. alibaba's token-plan keys are inference-only, so its quota needs a console session, and scripts/capture-alibaba-session.mjs turns a browser HAR export into the 0600 session file the bridge reads. - pi.registerProvider(..., { usage }) registers a feed from an extension into the same lookup, so a provider dawn builds herself is covered by /usage with no new wiring, and unregistering drops it. Documented in docs/custom-provider.md. Verified: 64 tests across the five usage suites (every feed driven through the host loop with scripted responses, no network in any of them), coding-agent usage 12/12 plus the CLI and interactive suites, tsgo and biome clean.
2026-09-15
6bb9da47 dawn feat(native): implement the usage query core in Zig for native hosts
feat(native): implement the usage query core in Zig for native hosts
dawn wants the usage query from a macOS app, and JavaScript will not run there. This is the query alone — the rules that turn a provider body into limits — as a C ABI library in Zig, with the TypeScript implementation left in place as the reference. The core is pure on purpose: it never opens a socket, reads a clock, or touches the filesystem. - `usage_parse(provider, body, now_ms)`, `usage_build_request(provider, token, account_id)`, `usage_credentials_from_access_token(jwt)`, `usage_supported_providers()`, `usage_abi_version()`, `usage_free()`. JSON in, JSON out, so the shapes are the contract already documented in packages/ai/docs/usage-query.md. Failures come back as `{"error":"..."}` and NULL only ever means allocation failed; a host with its own token needs no auth store. - Host owns the socket. A usage query is one GET, and doing it here would mean shipping TLS with its own trust store and proxy behaviour; a host with NSURLSession already has that, so it asks for the request, sends it, and hands back the body. - `native/usage/tests/c_smoke.c` is compiled by the toolchain and linked against the installed static library, so `zig build test` exercises the ABI the way a native caller does — including a NULL argument and a provider without a feed. - `packages/ai/test/usage-zig-parity.test.ts` computes both implementations from the same recorded bodies at test time (one captured live, redacted of user_id/account_id/email), so neither can drift from the other quietly. - Zig is pinned to 0.16.0 with a verified checksum: 0.16 moved args, stdio, fs, and clock APIs, and the pure core compiled unchanged through that while only the debug CLI needed edits, which is the boundary doing its job. `npm run check` now includes `check:zig`, and CI installs the pinned toolchain. Verified: zig unit tests plus the C ABI smoke test, byte-identical output against the TypeScript reference on both fixtures, `npm run check` clean.
full history: git clone https://git.klbr.net/ptr.pet/provide-usage