Repository navigation
Conversation
Each turn now ends with one structured pi.usage log line: model, steps, uncached input, cache reads and writes, output, reasoning, web searches, estimated USD and wall time, with the student as a keyed pseudonym instead of a NetID. This is the data for the PI cost analysis and the basis for a per-student usage limit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First step of the cost analysis from the Oct 8 meeting. Before we can say what PI costs at N users, we need real cost per chat instead of guesses. This PR logs it for every turn.
What gets logged
After each turn, the chat object writes one JSON line to Workers Logs (observability is already on in
wrangler.jsonc):{"event":"pi.usage","status":"completed","user":"3f9a1c0e7b2d4a55","chat":"a1b2c3d4-e5f6","model":"claude-opus-5", "steps":3,"input":2140,"cacheRead":0,"cacheWrite5m":0,"cacheWrite1h":0,"output":612,"reasoning":180, "webSearches":0,"costUsd":0.026,"ms":14210}stepscounts the model calls in the turn. Every tool call adds a step, and each step resends the system prompt, the tool definitions and the history, so the turn's bill is the sum over all of them.useris an HMAC of the NetID keyed withSESSION_SECRET. That lets us see how usage spreads across students without NetIDs in the logs.msis wall time for the turn. The chat object is billed for that whole time, so this is what turns into Durable Object GB-s.costUsd: nullwith a warning, so it shows up as a gap and not as free.Code
src/server/usage.ts(new) holds the price table (Opus 5, Sonnet 5, Opus 5.5, Sonnet 5.5) and the pure functionsaddStep,costUsdandpseudonym.src/server/pi.tsresets the counter inbeforeTurn, adds each step inonStepEnd, and logs once inonChatResponseoronChatError, whichever comes first. Logging is wrapped so it can never fail a turn.tests/usage.test.tschecks the buckets, the price math against hand-computed values, the fallbacks, unpriced models and the pseudonyms.Testing
npm run typecheck,npm test(45 passing) andnpm run buildall pass.streamTextinto these functions. The counts matched (1,000 uncached, 8,000 cache read, 1,500 + 500 cache write, 300 output, 2 searches), and the cost came out to $0.050875, the same as by hand.event = "pi.usage".Next
The analysis doc (cost at 100, 500, 1k and 3k users, and free-tier breakpoints) uses these numbers. After that comes the rolling per-student usage limit on the desk object.
🤖 Generated with Claude Code