feat(ai): show run cost and balance in credits in the response bar - #34
Conversation
The response bar read "Opus 4.8 · $0.46 · $12.79 left"; it now reads "Opus 4.8 · 46 credits · 1,279 left". $1 = 100 credits — display only, and it matches the account dashboard exactly because the gateway already sends the same RETAIL micro-$ figures the website renders. money() is replaced by two helpers off one conversion (MICROS_PER_CREDIT), so the editor and the website can never disagree about the same balance: - creditBalance() — whole credits, the number people plan around. - creditCost() — keeps one decimal below 10, because a cheap-model turn costs ~0.4 credits and rounding it to "0" would read as free. The old money() had the same problem and solved it with "<$0.01". Verified against the real values behind the shipped UI: $12.79 → "1,279", $0.46 → "46", a $0.003577 gpt-oss turn → "0.4", $0.5117 Opus turn → "51", $100 → "10,000", and null/zero → "0". Gate: 22 suites, 0 failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the LevelCode AI chat webview’s response/action bar to display run cost and remaining balance in credits (derived from retail micro-$), aligning the editor UI with the account dashboard’s credit-based presentation.
Changes:
- Replaces the old
money(micros)formatter with credit-based conversion/formatting helpers (MICROS_PER_CREDIT,toCredits,creditBalance,creditCost). - Updates the response/action bar metadata to render
X creditsfor run cost andY leftfor remaining balance.
Comments suppressed due to low confidence (1)
extensions/levelcode-ai/media/chat.html:2439
- This comment still references "$cost"/"$left", but the action bar now displays credits. Keeping it in sync helps prevent regressions during future UI changes.
// Response action bar: (Retry) (Copy) (Helpful) (Unhelpful) … model · $cost · $left.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…me PR #86) creditCost() used the same unary-+ pattern the dashboard did, so it carried the same bug: 7.0 rendered as "7", and anything under 0.05 credits collapsed to "0" — a run that cost something reading as free. Fixed identically to AccountPage.tsx's rate(), with an explicit "<0.1" floor, so the editor and the website format a cost the same way rather than drifting. Verified: 23 suites, 0 failures; $0.0002 -> "<0.1", $0.70 -> "7.0", $0.46 -> "46". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ormatters (PR #34 review) All three review points were real. 1. creditBalance() could render a negative ("-2 left"). An overage pushes the remaining balance below zero in the ledger, and the dollar formatter this replaced clamped at $0.00 — that clamp was lost in the port. Restored. (The same comment also flagged creditCost() rounding sub-0.05 costs to "0"; that half was already fixed in 7d6c711, which added the "<0.1" floor.) 2. The comment block above the helpers still described the deleted money() behaviour ("$0.00" / "<$0.01"), and the response-bar comment still read "model · $cost · $left". Both now describe credits. 3. The helpers had no tests, in a repo that already extracts and tests shipped chat.html functions. Added creditFormat.test.js on the narrativeUi/shHighlight pattern — it EXTRACTS the real functions from chat.html rather than copying them, and asserts MICROS_PER_CREDIT is still 10000 so the editor can't drift from the website's conversion. To make extraction work, toCredits and creditBalance became multi-line (the convention slices to "\n }"). Verified: 23 suites, 0 failures; creditFormat is 7 cases. Both fixes are mutation-checked — removing the clamp fails, and restoring the unary + fails. The suite also caught an arithmetic error in my own first draft ($0.70 is 70 credits, not 7.0), which is a fair advertisement for the reviewer's point. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
extensions/levelcode-ai/media/chat.html:2423
- The comment still describes the old $ formatting ("$0.00" / "<$0.01"), but this section now formats values as credits. Updating it will prevent future confusion when editing/validating credit rendering.
// [LevelCode] Credits are the in-product spending unit: $1 = 100 credits, so 1 credit = 10_000
// retail micro-$. The gateway sends RETAIL micro-$ (Levelcode.retail_micros) — the same figures the
extensions/levelcode-ai/media/chat.html:2432
- creditBalance() can render a negative value if the gateway ever reports a negative/invalid balance (previously money() clamped <=0 to "$0.00"). Clamping to >=0 keeps the UI stable and avoids showing "-1 left".
const c = Number(micros) / MICROS_PER_CREDIT;
return isFinite(c) ? c : 0;
}
// A balance reads as a whole credit count. Clamped at zero: an overage can push the remaining
// balance negative, and "-2 left" is a worse thing to put in front of someone than "0 left" (the
extensions/levelcode-ai/media/chat.html:2443
- This comment still references "$cost"/"$left", but the response bar now displays credits. Please update the comment so it matches the rendered UI.
const c = toCredits(micros);
extensions/levelcode-ai/media/chat.html:2436
- creditBalance()/creditCost() are pure formatting helpers and are easy to regress (rounding thresholds, "<0.1" behavior, etc.). Since this repo already unit-tests functions extracted from media/chat.html, please add a small test covering the examples listed in the PR description (e.g. $12.79→"1,279", $0.46→"46", $0.003577→"0.4", null/0→"0").
// dollar formatter this replaced clamped the same way).
function creditBalance(micros){
const c = toCredits(micros);
return (c > 0 ? Math.round(c) : 0).toLocaleString();
extensions/levelcode-ai/media/chat.html:2464
creditsis still retail micro-$ (converted to credits for display), so the variable name here is misleading and can cause confusion when editing the formatter logic. Consider renaming the local parameter in addAgentDone() to something likecreditsMicrosand using that name throughout the function.
const meta = document.createElement('div'); meta.className = 'agentbar-meta';
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
extensions/levelcode-ai/media/chat.html:2446
- creditCost() uses toLocaleString() without an explicit locale for costs >= 10 credits, which makes the formatting locale-dependent and can desync from the fixed comma-separated examples/tests. Using an explicit locale keeps the response bar stable across environments.
const c = toCredits(micros);
if (c <= 0) { return '0'; }
if (c >= 10) { return Math.round(c).toLocaleString(); }
return c < 0.05 ? '<0.1' : c.toFixed(1);
creditBalance/creditCost called toLocaleString() with no locale, so digit grouping followed the machine's ambient locale. Verified: under de-DE the same balance renders "1.279" where en-US gives "1,279". Two consequences, both bad. The editor and the account dashboard would disagree about the formatting of the same number, which undercuts the whole point of converting micro-$ the same way in both places. And the new unit tests assert comma grouping, so they were quietly dependent on the CI runner's locale — they would have failed on a non-US machine. Now pinned to en-US, matching the dashboard and matching the fixed US-style money strings the bar rendered before credits. The test pulls CREDIT_LOCALE from source and additionally fails on ANY bare toLocaleString() in chat.html, so a future edit cannot reintroduce the ambient-locale dependency. Verified: 23 suites, 0 failures; creditFormat is 8 cases. Mutation-checked — reverting to a bare toLocaleString() fails the suite. Note: the account dashboard (onetime AccountPage.tsx) has the same bare call. That PR is already merged, so it needs its own follow-up rather than a change here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
extensions/levelcode-ai/media/chat.html:2459
- In addAgentDone(), the parameter named
creditsactually carries the remaining balance in retail micro-$ (per the comment below and the agentDone message field), not a credit count. Now that the UI renders credits, this name is especially misleading—rename the parameter to reflect its unit (e.g.creditsMicros) to avoid future mixups.
function addAgentDone(reason, edits, credits, maxSteps, costMicros){
closeGroup(); // the run is over — finalize any open activity group
const bar = document.createElement('div'); bar.className = 'agentbar';
// Stamp the model that produced THIS turn, so a later reaction is attributed to it
// (not whatever the plan is entitled to at click time — models can change mid-session).
The response bar read "Opus 4.8 · $0.46 · $12.79 left"; it now reads "Opus 4.8 · 46 credits · 1,279 left".$1 = 100 credits — display only, and it matches the account dashboard exactly because the gateway already sends the same RETAIL micro-$ figures the website renders.
money() is replaced by two helpers off one conversion (MICROS_PER_CREDIT), so the editor and the website can never disagree about the same balance:
Verified against the real values behind the shipped UI: $12.79 → "1,279", $0.46 → "46", a $0.003577 gpt-oss turn → "0.4", $0.5117 Opus turn → "51", $100 → "10,000", and null/zero → "0". Gate: 22 suites, 0 failures.