From 8a6cda91c14ac4df7f819b44c208f43562d786d7 Mon Sep 17 00:00:00 2001 From: Roza Jiang Date: Thu, 29 Oct 2026 00:02:46 +1100 Subject: [PATCH 1/5] Add the AI Credits Breakdown canvas extension A detailed, private view of a user's own GitHub Copilot AI credit usage: dollars by day, repository, model, surface, call type and prompt-cache state, plus heaviest sessions and spend by automation. The ledger is built locally and read-only from the telemetry the Copilot CLI and app already keep in COPILOT_HOME. Nothing is sent over the network, and prompts, titles and file paths are never read. The canvas is served from a loopback-only server behind a per-open secret path with Host/Origin checks and a strict CSP. Includes unit tests that run on fictional sample telemetry. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/plugin/marketplace.json | 6 + docs/README.plugins.md | 1 + extensions/ai-credits-breakdown/LICENSE | 21 + extensions/ai-credits-breakdown/README.md | 81 ++ .../ai-credits-breakdown/assets/preview.png | Bin 0 -> 263263 bytes extensions/ai-credits-breakdown/collector.mjs | 701 ++++++++++++++++ .../ai-credits-breakdown/collector.test.mjs | 317 ++++++++ .../copilot-extension.json | 1 + extensions/ai-credits-breakdown/extension.mjs | 73 ++ .../ai-credits-breakdown/sample-data.mjs | 199 +++++ extensions/ai-credits-breakdown/server.mjs | 72 ++ .../ai-credits-breakdown/server.test.mjs | 87 ++ extensions/ai-credits-breakdown/ui/app.js | 761 ++++++++++++++++++ extensions/ai-credits-breakdown/ui/index.html | 94 +++ extensions/ai-credits-breakdown/ui/styles.css | 331 ++++++++ plugins/ai-credits-breakdown/README.md | 22 + plugins/ai-credits-breakdown/plugin.json | 30 + 17 files changed, 2797 insertions(+) create mode 100644 extensions/ai-credits-breakdown/LICENSE create mode 100644 extensions/ai-credits-breakdown/README.md create mode 100644 extensions/ai-credits-breakdown/assets/preview.png create mode 100644 extensions/ai-credits-breakdown/collector.mjs create mode 100644 extensions/ai-credits-breakdown/collector.test.mjs create mode 100644 extensions/ai-credits-breakdown/copilot-extension.json create mode 100644 extensions/ai-credits-breakdown/extension.mjs create mode 100644 extensions/ai-credits-breakdown/sample-data.mjs create mode 100644 extensions/ai-credits-breakdown/server.mjs create mode 100644 extensions/ai-credits-breakdown/server.test.mjs create mode 100644 extensions/ai-credits-breakdown/ui/app.js create mode 100644 extensions/ai-credits-breakdown/ui/index.html create mode 100644 extensions/ai-credits-breakdown/ui/styles.css create mode 100644 plugins/ai-credits-breakdown/README.md create mode 100644 plugins/ai-credits-breakdown/plugin.json diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index d2201b8bf..0a716043f 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -83,6 +83,12 @@ "sha": "abad48ddd3117056bbda12f2f7e87142d8c43369" } }, + { + "name": "ai-credits-breakdown", + "source": "plugins/ai-credits-breakdown", + "description": "See exactly where your own GitHub Copilot AI credits go: dollars by day, repository, model, call type and prompt-cache state, built privately on your computer from your Copilot telemetry.", + "version": "1.0.0" + }, { "name": "ai-ready", "description": "Analyze any repository and generate AI-ready configuration — AGENTS.md, copilot-instructions.md, CI workflows, issue templates, and more. Mines your PR review patterns and creates files customized to your stack.", diff --git a/docs/README.plugins.md b/docs/README.plugins.md index 848417f22..8a815165a 100644 --- a/docs/README.plugins.md +++ b/docs/README.plugins.md @@ -30,6 +30,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t | [accessibility-kanban](../plugins/accessibility-kanban/README.md) | Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work. | 1 items | accessibility, github-issues, issue-triage, kanban-board, planning-workflow, status-tracking | | [acreadiness-cockpit](../plugins/acreadiness-cockpit/README.md) | Drive Microsoft AgentRC from Copilot chat: assess AI readiness, generate Copilot instructions (flat or nested with applyTo globs for monorepos), and manage policies. Produces a self-contained static HTML dashboard at reports/index.html. | 4 items | agentrc, ai-readiness, copilot-instructions, readiness-report, monorepo, policy, dashboard | | [agent-radar](../plugins/agent-radar/README.md) | See a live map of the Copilot agents on your computer, their sub-agents, their commands, and where their data goes. | 1 items | agents, canvas, copilot-extension, data-flow, mcp, observability, subagents, token-usage | +| [ai-credits-breakdown](../plugins/ai-credits-breakdown/README.md) | See exactly where your own GitHub Copilot AI credits go: dollars by day, repository, model, call type and prompt-cache state, built privately on your computer from your Copilot telemetry. | 1 items | ai-credits, canvas, copilot-extension, cost-tracking, token-usage, usage-analytics | | [ai-team-orchestration](../plugins/ai-team-orchestration/README.md) | Run a lightweight, role-separated AI development team with flexible tools, developer-selected models, proportionate planning, and optional QA. | 4 items | ai-team, multi-agent, sprint-planning, brainstorm, project-management, orchestration, developer-workflow | | [apng-studio](../plugins/apng-studio/README.md) | Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png. | 1 items | animated-png, apng, copilot-canvas, frame-animation, image-export, interactive-canvas | | [arcade-canvas](../plugins/arcade-canvas/README.md) | Play six retro Phaser mini-games in a Copilot canvas while agents work. | 1 items | arcade-games, copilot-canvas, interactive-canvas, phaser, retro-games, session-breaks | diff --git a/extensions/ai-credits-breakdown/LICENSE b/extensions/ai-credits-breakdown/LICENSE new file mode 100644 index 000000000..8544dab82 --- /dev/null +++ b/extensions/ai-credits-breakdown/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Roza Jiang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/ai-credits-breakdown/README.md b/extensions/ai-credits-breakdown/README.md new file mode 100644 index 000000000..09d73c61d --- /dev/null +++ b/extensions/ai-credits-breakdown/README.md @@ -0,0 +1,81 @@ +# AI Credits Breakdown + +AI Credits Breakdown is a canvas extension for the GitHub Copilot app. It gives you a detailed, private view of your **own** GitHub Copilot AI credit usage (1 AI credit = 1 AIU = $0.01), across every repository, organization and Copilot surface that leaves telemetry on your computer. + +![AI Credits Breakdown showing sample data](assets/preview.png) + +The screenshot shows fictional sample data. + +## What it shows + +One analytics page with a sticky filter bar. Every panel follows every filter. + +- **Filters:** period presets (this month to date, last 7/30/90 days, all time, any single month) or an exact from/to date range, plus repository, owner, model, surface, call type, basis and prompt cache. Each dropdown lists its options with their spend under the other filters. Download CSV exports the filtered rows. Filters are kept in the URL hash. +- **Headline:** dollars spent in the range, daily average, pace to month end, the previous comparable period and the busiest day. +- **Daily credits** as a hanging bar chart over a running-total curve. Drag across it to zoom the date range. +- **Spend by repository, model, owner, surface and call type.** Click any row to filter the whole page to it. +- **What credits pay for:** cache reads, cache writes, output and fresh input. +- **When it happens:** an hour-of-week heat map in your local time zone. +- **Prompt cache: reused or rebuilt.** Every metered model call by what it found in the prompt cache (reused, expired after a pause, rebuilt mid-conversation, or a new conversation), with spend, calls, cost per call and what the credits paid for. This shows how much goes to rewriting a conversation into the cache after a break. +- **Heaviest sessions,** and **spend by automation** (Copilot app automations) as a daily line graph. + +## Requirements + +- GitHub Copilot app with canvas support +- Node 22.13+ (uses `node:sqlite`) +- Copilot CLI or Copilot app usage on this computer. Without any, the panel tells you no usage was found yet. + +## Usage + +1. Install the plugin: + + ```bash + copilot plugin install ai-credits-breakdown@awesome-copilot + ``` + +2. In the Copilot app, open **Canvases**. +3. Open **AI Credits Breakdown**. The canvas takes an optional `period` (`mtd`, `7d`, `30d`, `90d` or `all`) to choose the first range. +4. Use **Refresh** to read your latest telemetry. + +## Canvas action + +The canvas has one action, `get_summary`, so you can ask Copilot questions such as "how much did I spend on Opus this month?". It takes an optional `from` and `to` (`YYYY-MM-DD`, UTC days) and returns totals in credits and USD, the top models, repositories, surfaces and call types, the prompt-cache states and the busiest days. It does not return session ids, prompts or titles. + +## How the numbers are built + +The extension reads, read-only, from `COPILOT_HOME` (default `~/.copilot`): + +| Source | Gives | +| --- | --- | +| `session-store.db` `assistant_usage_events` | Exact nano-AIU per model call, with model, call type, token counts and the per-token rates in effect | +| `session-state/*/events.jsonl` (`session.shutdown`) | Cumulative per-session, per-model credits and tokens (running maximum; repeats ignored) | +| `session-state/*/events.jsonl` (`session.usage_checkpoint`) | Prompt-cache lifetime per model | +| `data.db` (Copilot app) | Per-session totals, automation names and which sessions an automation ran, and each project's repository | + +Each session takes the largest total among the sources, and per-call rows win where they exist. Credits = `total_nano_aiu / 1e9`. Days and months are UTC (as GitHub bills); the hour-of-week grid uses your local time zone. + +A call counts as having **reused the cache** when its cache-read tokens are at least half of the previous call's prompt in the same conversation and model. Otherwise it is **expired after a pause** when the previous call went out longer ago than the model's cache lifetime (as recorded by the Copilot CLI, with a default of 5 minutes for Claude and 30 minutes for other models), and **rebuilt mid-conversation** when it was more recent or there was none on that model (a model switch, compaction or a changed prompt). The first call of a conversation is a **new conversation**. + +These are consumption figures at list per-token rates, not an invoice. Not captured: Copilot coding agent and code review sessions (they run in the cloud), IDE chat, and other computers. + +## Privacy and security + +- Everything is computed on your computer. The extension makes no network requests and sends nothing anywhere. +- Prompts, session titles, file paths and URLs are never read into the view. Session ids are shortened to 8 characters, and only repository names (`owner/name`), model ids and automation names are shown. +- The canvas serves its page from a loopback-only HTTP server (`127.0.0.1`). The page and its data are behind a random secret path that changes every time the canvas opens, and the server also checks the `Host` and `Origin` headers, so other local users and web pages cannot read your usage. +- The page loads no scripts or styles from other servers (its Content-Security-Policy allows only itself). +- The tool uses no paid services. + +## Development + +```bash +cd extensions/ai-credits-breakdown +node --test collector.test.mjs server.test.mjs +``` + +The tests use only fictional telemetry from `sample-data.mjs`, which writes a fake `COPILOT_HOME`. To try the ledger builder on it: + +```bash +node -e 'import("./sample-data.mjs").then((m) => m.writeSampleHome("/tmp/sample-copilot-home", { days: 220 }))' +COPILOT_HOME=/tmp/sample-copilot-home node collector.mjs # prints a one-line summary; add a path to also write the ledger as JSON +``` diff --git a/extensions/ai-credits-breakdown/assets/preview.png b/extensions/ai-credits-breakdown/assets/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..f87d4bf57083bec8015cb2333fd6473b49ea5073 GIT binary patch literal 263263 zcmd43bx>Pv_$^9lsi1AK7A>^6mE!JDptwuW;_eQGA}thmhd^+GYX}r~3GQ0lH4q4# z^!t9_%)K-BuRG_=ocE8+-pORkn|D9Yde&M`ekv=<;9!zqqM@PT$bOboMMHZkiiY-N z_BlH0oxZLNI2zhZG+9Y8bYY{L?ek!T%XKW$JwYZKKhI zq7nVKL(lc3^1p4-VybT#|Lw@(JYxQDTiN=#(WC#_`KBs?hV|bE2_)#+|83_?zWn|7 zX8swV{~f1zM|^`3I-3P0BF@g~8(LF=Me7^Lbay%bJ;OU{k$-N^8B}QTE3+XgKyPT) zNEu!al1pQ(E}M~Qqhnj74~#VN#AC)IXNKu{At49($Ex=3HnV<+^XH*-Po3B?5xyrg?t>J z;K0plLdBJ9MkcM_&z(7((C|i9X^cs!ggiVes9eyz>4n zE8T`3a+&0zp`kTDfu^>HlM4{UJU7VnnLCS}O3-(aFx(s5;r)qk@Lo28-zJ@1BJ_V| zL(&b=gD$ab8m#mioB%esXlVX8BrK0nKhU3Fy8dE>M&8n)qjUk7pNmVk9cIdwEgo{X z1ftE&%xFH)(0%x@atd zUhz9cWaf&k#T7^$-+4y7y?cGrpvcZjZ;NvIe8ndxfo0{03Xcp?d z@j4Rmu`&e&Ce6DH9U*(!+)A~6MJVxlw`V=xlxUlo&+KLJo+|UlAtapIAa-c>_H;o* zJFyu<|GTt{yh%%;Z&}2J9=trD$kJ+4)zNR4*2nW zv&x}0r(-WHBvMvR@f3MhXB{#w;q8{gJCiX5$%eWfFEk~VLX_IkNw8>fK#eJfb2dr_ zEt{(kGiNMev!Y(5aTC&)rk%dlsStBwn$WkVh;22E;l4i4yE_~ZKlkV=#bFs!Qm(q> z+KAQrTM{DJIUY97bKDKl8_$3H)OmaWjx==r6OXl{OS)%7}6kn9%Clgy2xqM`(5gUP8* z0U9nAM%wyXsTb2Fg99DdkIEh#2#UvHFkm3?srTJy;ksa%9_`fhE&Qtkdwbnq=-P(g zTS4^5jv#*|R#7R;kY!jr$QYy9=(`B@JKhBRyP%+K4Lx}^2|#A#BeZXZf&avyZ{h=q zpGX%=q2Bj*{PViDzjFPJSJ(`BSkm&-*eFTWL?w_{a9S-BOLGM2tDAB$1<$XX80DOk zQ}i#1NGQ6cLxZIqPXfw~53PZIMn7Rw)8RkD%{tdQ@=b~=h<~V`Hr%*Z%5-a|5X#Fd zp=Qd=6b)@v>)k(5F6X72o7-b3rChGYpdKH@JEO(7(8+FpA!>wTIa_mxjK;g zPtgCqXR*xF)wL`@*)s7=le0C$tUuXdg_73|6f6r{h}P%54%1+@kY)IDktkr^jqy`V z0n-Hg-2rkOaK)GF#ip@{;K8Hgy=z?_QHs~DFfb4vsWb)~SN}L2lM$Duq@kg}(l-0qt?sU*#8aHpad-Ev)$T3^tb1yO zO_)lhMp;af>Jgeh*8ekY2(QsMJDnty(t&KR&! zg@I$kc3YAW0inMhT(>|%=>31oAt3(ywr#r)X5)zu)J8Op>O5{c{^%CtY$ z95;mP4L%e+RLE+LH1xpIeEboW0}PAe&r#owiJT4opnn!bmLOOSrNb!RaY#61i^#ro z$nSf^wCiwJzUe#hu#wNSueV`2tRdo|y-A?^zcI(s092g^Zt)CJc?>nK2U&EgKP<4H z@N}?YohbE>29EUz^*;XISzCw6lFH6c!&UAMXgedS8^^}NszMPVk=PfF zD<4>e*M_Db(}@GqMfyqTKeWgx^b+VbORc?7DPomv?fyP5yu`Zu6Vc+av_(%L+p?!; z0Hdk#bV*fY$zFh4%(1Yq|MgIv_hvO$X#zeu8v_fA-&g{O$s)Rrlc^{0?B;s2G$Nf2 za5+^4wzO3brp%5>IXLN~VWq0FoA8#paFrxJy|`(ypJ87Z#xfgDO-Z=>b3-e`n#Wtmb(=dMhNB7&m+cc6~RXo2HCotWf~=4onUq@ zuI`sNe+Rpklp~8=RYm%W63Xl}|BvjSXHAkWlfH9s)}U<<1=r4=AtwZ}?5W1o(du zRo)TE9#z=W)Ka^xctZS|$SUALM@7PqgiX`nrQTyRf@pw&4H5HLnZE*TlEwz7daE#K z+*GbWID3w51j!e0mz=m!SEyv^NkGb4R;oaUaRqsZyn8Hh9ZHUYX{kG=e>qbt$ zJlEvZRYf)(ch(stf@+6ZRL3QfaZ{2bb&``mX{E*QK}AWc)O*u8aX{C(6ADrkrKO(&rY&u((tJfim=}D}UOK zj#E+ZajZ4oF4jQra87PXoO@xQBI8%xkF^tgqYaxAV`93N$3s44HY41v5|u8s^v;{S zuX!QfzIU(RVH?p*gz9K9=6y!ncM(-Kmo5PH)e)c~DrMEuRZFw({o!Dti^yi|;66ZR zBzwL|-`3aGSG?hs%OX^mySBLA8e(U+FGqY;K9Vg$sW4xAJPE|PC7G$ps`AyVz#Yc3 z(|u=we4Co8bu5@AWdH8jz;@%(FuTk2xDpv5zWfZg7Oq2Ov1TB?K5NgkC5N_xf>kJv$YvB91d z2jX*6zr&H+k{A+Izja}z7i|w%EVsi~)PAxjYz?j{g}8NBb7rYtk1dm1`hL+D@1>g9 zbzw?o;&K>1s`}nsZul{_-2M1ep3@?8FA-?EUcfr%3m+O;e15=a@^Fu222yd2=jP_J zdmQPr@+j!EYz|Ecu#nE%&ijnFNLJa%a9a~Giq_Sm?sI)R_mk-icVx7kUPm;a+itz3 z)9K`M-X)jZuMY3$?T+a&7hgG0p6TV^Oh0&TS{PP~gT; z{n2i2`q{QeGLwet2+7xBB`=lXdI{5w4Tjti0MrJRNz;?Rtlh_m+z$>^8IQi;FO%$@ zwKtf|^HV$Wm~DBOyo4x4+W|6sR0vggD0vkC)l#lQ!Bj$;!*7`iTZ5e+BM_!f-B~Ve zFgV+O$Dkq?EiJ8+7x?%Z;JZv+l#p!4yB3DRgtxnGB_4Rktb5wI zyT?zS%?G99j?)0Qk!l2PDCBe5c(Y$42}V*tTI}^_bVo916e5@GDK`u^!Y(R5@tCzg z-I3oDHmNaD*_RwR|6nu+vLbPRYt^=L3PEiG+evtaaI z?3t2blbZ?CVRmowl~BOpP2XH(4_h{0_=5@sU{OAChg@o)CxPPNSfo^|2Gd8tVoDJc zJ-ohWJs5|vcLX4x`=l4kXIUT%#6GEF_~$<^z{Z0-zAhBQ25zs-`}0$bjJwaUf`a1g zQe4~9;`8{B{j0a6y8%wEU*dP<4@JCgki8<)y93!sGeRf-<2@e>kCDueDWPFs07)?^ zLr7dy%3}Bso6%tC2uoOlj~)V7>riNW06`(>lE`t6N;{mSG*mFlTf?x{j_$`c@QUmi zRQ(!$3s0}y!7I4Rt}*Xbkz-VkH45;>qLC_T;Pu^_hn-(>{zTs)fbOl7M;@y|dWqqx z!BMPq^p71TYFQQPAWkA29P#CU2JFO&GBwQUzqeo+sc?JmjVs)j=f&H6)_zi0w$rd! z?`*#5tf_yL^F)l!8B=SjNZ)>Noq?bTs5?Q(Art*}HnJeA)m1<$g+@nR{6rnd~2ZC(gHY>XSt*7jZ_q60A4eab66b1$HVrt(E2aNbe7c@ zUr*>xXW4V*s<-F)y63Ff5&cvCjerMvWbSE=*3lO^MLxf7xM%Nw%U*R*NNvpK@v_bXFBR zL!@ETa1!{-6-Aq*>GfOb5&b%UxKlgf;Bq`C-te5yK~P1z@nTz;-0u1G zf&H9^>IvMZ)$-f2s&*jek~es^^ff=%<6f9W|EWwu~(0PHlM%9QOrYx z0L>GXX}b5Q%k_KlJg(-c&iMpIp3!2Etb~<$Ni*26RPJ{~sV0p=lgKvSeRX(|Zu>xi zZpcl1Xl3Tj*3vV@G1VUE{*SqYJnlW=t;VH>@v4Rj5mH%;C;Wz8J#>|)oR5d4H>^rf z*fBD-$wYvBmvhXi4;vO^W?xhNv0S50kWhD9k)UhC0O~jT!lmOChbHVML{Tq-`=F5Q zr}VU~Krp(HaV%^~7pKK*Wj87h3ukODxb=Ca+*cN?6|MR>W4sH;O3#_>*ya=(l&LW#8Pp*CTJOxYGg^8y!~szHU%9wU|@N z2*fB#ySAtc>BD(zG=VH8Up(Bd`8P^y6_7U{GE=a^}`qXuA4-kZ4^w9ve9cexBNcR zx!X+(YjTfsj3p_5I-=jIad97jD&y=%;cwU@3ZgQqIu>S>E^6rcN2X<&@*A;no`hhs z&UC!bvr4J!1pcX;xU!uw<_OoYkzjhuZ?@ZM!AAJNZz$1m_gx$(Vh87VLB&-t)0||o zBrYlI=nrFGuA)IRm%OKn&*@etp`M-9ujC4m$hljnDy!bbl%>B+?2f9q>ZeG)8|2CR z)VHb0_I!#u&1M%Fb^GuP&wE7Xqy`_vYh1%NzIjT@VLuPi{K}6X_PhL8qqX+5wr-G{ zGQW;xu`)X9<-9-l&nO{;CEl@8A?r$3ttI|&<>l&I@4>ln=$WZOCo^8DWyyA2(;A;v{Donx|4kx`u_}NW4IDgju07XS5-84cJiAMn!AYjz~ z)*<8?>yD}w%AbvpL5!4OPZX~u#x7mFN*!Ouakf%W#P$}B^{Izc zX49T1EOneH$nhy?^b?V}UoYEa>1?%jByI%H0aFobGMX4g!kLvQm!fe*{O|ccekf~? zMoLRbcH7VT1cgW4oFTPXUA#9{3cYDsXYw)P!_o-Z80*bZG!p}4^|qM7!R&rcgr1K^ z!x`to<0!aY3g?o~m_L_Z14YmNDHEH%W2Qj#i~PVGl47g_dIqEDOqdbxtqT%ZMNNnfdr3#`FU#Gn?+M1ZyHf_!sy6OqxrlGYc?c9{ z*zG@``A+-bp|{f`MAuwvrC*ZIz*48Qrh0PBW!3oZVvCwNHyi4n>`xp{U7mrefLQ~% z6W3ig%jCvqT#1&;JP>E(B&~<^^yF%b;h{`PRbOw7kM;nU)|$n3fc~okw-S<9w+Y4a z4a$+PLgEXe_hk572L17ok0nrr*#d5gc;Ju72-=w!|b7P^$ol%Uxg8&X=8qeqs@FMVOTihT~0{oB{Hf)F_dfGk< zZ+FX)7jkgL3;1$8@kK+x%yhZ@MB&Cq@C z-0s>7OEpL^9Uy>B1)VN`Z?JH6xl?6YW z?Mh?Ji=$eosGD$YCPmF@wEHN?hFD(WX3G zNpj9$@#G|D8VMo!`+9%Aede58MW63Uz&T4%DGa5lCunFOiP-1fAP`9HTOd%q?6ojI zcNBOd4`^D>l<3xWdnnB^Jq-F&8V1@ z&yQm}4Gm{2xD(~RDE#_b9(YE^Sb?I!z=p}wb6_!N|7oMIw`(;r4J^5p#32;?xT*J7 zN3t;e=aP}BkSIAORxpLS(n_6zDGFrj;bs)Px4%EjMKNj0@4j0Tj{qPGO&SvXUG#l= zML9i1_W(NF!){-mxiu1{(sjG~(wmL4o!)g0tUop&jb0QDkS6>4U{qayDCEyh%kAG0L$L_& z`R1yt!*0WwuCS+i@JDK;HdldbrO+Lixn^s~o+lB304#79FB#90j^Zt5i~h`TR@cH~ zBQf`wRE<=BC6qZ*PO4$Oe0-o0HY{H_{@FrHiCbFylI@N*h#x~5E?t_wbe~^$<(Cka zftS68=A{9z-K*XAgI75@YdvlUhHoS6&gqPQx8LD^T`O-U5nZ2iS z%{m+4qqEtwJiGHr4=T03yZ|UP1jZJDzyDxN1 zpN*}Yfre26m!X?y$LJSWDc{C3)>M9z%M7QoXJh1Hn{HH87Mbb~qCOI8HSYl7u-}uh zet-F78VQ<6>80Ks4O^XJ9m55enVMjL#s~>d4Dcy|BBMb~=O3x`JkyQMvOw!-RIv^3 z7qQ$5KXeVs;;&{4iMRCqxh^Gjyo-eyNq@OFtqUVydWo9*|rzu={7Z)eAYd@iZ!>DQ@e4&zoY!9$_fa?RLA7UW`a% z4b#5Z+xp1@j5fbzA(;YS{TUZ7p(^~{o-akpC)QgQw9hJxE7eVb5R*^yhkWxrSf6ej zX~3=gGur>+$dOO>ksM}jxc@q;4y-HDT#euyI?8iThi3yV309;y0ZteZu1K*!?DN+T z<*DI0Leis4P;VkYMK!zhQ$%+-2{?l*RPI3CGhM*=IiSM%p>pg;5w1jm8kOV7wHR>Y zyLiX0ht4Ov;GGH5U89!UYrYk~L}v5ua8>1BZ6+2}e=DZy>6ol6vCXx8;iMsSZdTC3fmn4~YbF*cMTOAa4aj*s!mX;TTld^$Y1gCt>ZtRh# zC$A-4LKXE4%BMszdmTHr3CAniJR;v|FQN+0xH(^7$28I>#_ z;9)zKR(r(L#+jmXlnY;~(&3cCE*2fF9tHlzt7qYwuG-NNcZVlFXvMcKvL^n zZMx0) ze?n!9LxQ5L_!;-~m~wYgW@E;F3~ZJgEb?Ei&C%S(FYt9gdBKMMOUn8%Dg)B`_59Jj z8}bMrLrcmS5PlrqYS4PQ?hG_}J6N*Wc6lWQGx3Hwy}mP50%hbhM0wWT-E?Pp(r;yc zlqZ2Ta2Pi6lsH)U3r^%{65gLR8$=Gc9_4e<_4%I#QSYNtzp^eqv3Vyk>}5g2A}4x&xos!hM2gpq&9OC_YKfk~w}(oK#H9hp)?tkC#r7HCwqqf$ zetYVipnX(IMK?5XX?A|c#o~8;5m5Vzi0;SBw@boJkpnE|-H{C5-7B7#dSgm0*0-;w ziLnzU5N>t~B9Aa~i^e|7Zr<1^aW62faZ!{GfZlay%CKHq`$KM(Mlv!^+9yy5lPyzN zj#F8jf)8@)_nTuIyp+ew;5RX_`LOTT8HWIrR^^*s8V0X>Pl~l~X$C>jcFFX#mZ_i;#39p|M{X@* zo3ahmH9bNaV7u^||FeiMt`Rg#??owo2AJa4d9zG~rP8woy&(TG`eWqvMaMa5nAQ-9 z0(<7}U_GqQS#zs4%!(q7;|jS%A1#nhq_m}#6&u^XnH!4~P>bW2HWXHsC+RkIb!qD!W${_4lUuQARn9(IVw3F}3 zMsiv{wn|JrzL3anaxOF-d>Xm`d|m|nDr>IBPNBbYA@~LyDg6AHg~`m|ZZEUfCkj9| zR}pcnCg9E{qE>9nfGMgLI>asE>;c1MKq@mYXn9f+(jDZD%{G&7&T@XS2$mwfnut}@ z5t<5Dz+c|IyY#{HD{C5BoPt=^At4MfUPZi)~!x4lp16Dr>u)DUSnR?w+Zo!Ond>4@;KT zY!=%xCb=Hw%_40ufxs1tHZF*!pQ*g<+hsa97D{ZPR3_5=-rnqFM*l=!gX{t``o|3k z0nwWhOFVc)0k$E#OYP2I0YP7JERYJ37X5K|GL_|(6Ej2xjfLZz5Z5T|r$hEw7j_=+l z>UN+m4UFjgmZQuFR@VP6^wx2pueG#+(kb8})<2}l#{Keeaa`2%?e6A29k#0asus14 zqM~+`wWV6Q6_j#7B>~M|fPaSAs!&2IGj}4XB1<3&tgeG@&R)0X$8XSbGln9;?3RI- zxAY$eNc2=}YnQ$iAEr&)EH&b$XS)e6wb|G(u9mO(39>WHO)dFsWZ`G(a;@LXG89`O z>6n3O^C$!ZWB&~K5kP`3f5;ao`rWUYqF@Ju2rvxCRFxKq~c^O|G7y{cvlaFC{9H2@@QVJvy24uO zbomU6kU8ZZ8C!b5>R0eBG<03XKzHnD6#=N;{3mEN4J5fj)-rj)sZo-AQ{^lR04UT3CWg-TzUYjys!spTSgrUM|B7wiMetCb zIY=xX%&?@)I&^g4yYmdor_@?%GkwrkHWskbb_gd(D5CLviP@dogF|#dsJhThjnlBl zW7Qk}R>B)e9KJVQ@G~9aG1wZk^#_F>Dduax0bZ)L^t%rNPtRu@_#D%~amC*HXX0Li zQiFx=z<3sX=(HYHOv7ox#)w$X)0Ri#hs)qyl%AHD zhP7wTlvpQN=}!T7Fy;Am=RThna+EPI>^#jltstC89reP2mwMxw2mIu!d_wl+R?h2J zG<4j{yC4C=BFTz3X2UmYF27;tJYCRQFHulucK{PyozMc6|<*vAuA`=7)-_)vmnVRrFa zv|qDJ2d4A1uf$OFOh*tci$IbRC9jv&ZfKBr*UlrrjkKeQP0mN^5E-i+6#Z|DO=Aj& z(3zE^*gH;9vya+ayjo)#X1sH%Rp&@y=kH{k=n`^tRN zjk*RywQOg?#F0)-?k}?!YWST|ETqcFM^IYqU9l=T)F|G1Q^^qpzvtN&YGxP+?5&bK z0ObtSZ0OqX?AFz=n@~mH+MVXky#9_})d5wIuhsM#0mkhdbTRvJ5O~Qwf3d`Dqgj=> zShEUS@9RV8O#$ut=)v-s+ZfCRiF&)U`-OC>;w!r;rkrYqypmR!f`Yt!;kEtG z<4iHkY05YHh0-)tN`+~`UHJrxBvR8a(9m+QufVAaY*n%9(m393nR~Ol^T{H+TC9e9 zAy7Mgaq6k?Q$iJ2sJ}Tp{Ne(;N)K!)2%sL#=1gD=-c?n-7zhmKSFgM~pp;a3S{(g2 z5BLg@sA>mH(-lUPS>?3?Toovn9L6gRe)cn%xS1Qrvb)wzgXbFLnUNj*+gD&0=Nbf;)XX~@MJAUCPX`8o zGGE(S&iMO)?{8wUoloicxl0VUf5$k&$->#j4I zo1TVq56nL;wu#{okm*8W_J(~X=@jvc!+%o#bTsqhkB%-xGrupc;o|JcS*{z(d0TBl-8S>1HFKxC zja;CJ6gvAcMC&MfL3TU&;ukhd?dQUw>D@<+-LAO!Nl;PHpMH>^dQL&CZ$Yi&z{mSF zJm)ww`{(XZDvQ0v7vC9DNMDiLygPHnV)Te$<#MuAY7N1ptD?LHd_a)zzRCaUV!07? z-SxqrZj4x@&V{c#*Akf^I6Xu<+N0(@TW7tM1X9~M_;OyBDPpi!fO|XbGY{LgXUsUN*^D%FG<`wST zlS)S4{Vc2H5^{*D;e#eZDDzT8T)vv#BVXlS<@%ECvE z3WS5gV$k+2(8OT!{neY*)WLdMxKgYFqSmdG zVDXlOwi7%%1$m_gArB#D>Jodm_BTveYM9)aP%y3qgtN+BadCzHTx!&d1Vzi z=bGAtX4KG7qP}R1RoDJO?H+8X`D>CRXfoCn5i>ueaNz_Dm>!iX0}0D2_$tA6QWS8U?%#&2qq13#-o3HB8N&%Lb+|7gqqVf8b8|Gk88 zu;uFH3VAj)F{q_j3L?U>@vvLC03!Vy4BLpHy{8)s4VI$GHpigLKTEa#!}m+`8I=&P z{dNxT(+#GC`W)E=+NjL7)5YQUf}gw(GNx=_+Z@x`kb1jW-&{dgQ#0dh9`xm=6fQ%Kz$PT11l_>=@4Nn*7 zdhz3C0)!aeOqQBr^_Ol_dpD~|Ul_D)cLl0xftUPLp{jF#5)zUfS}fyT>*~^`@FySD z+ghFKq|;)qtCduC54 z!s+zI{uNmRSXuhw<`)XD#k?oi<02EumqD2od6n(ozD=#yBSNk(Ntix!-)R3=*#u-7KJ^}4)5FDhe>}3{f-ifjal?PQCGa}^}SJVU``jd7S*ab zb`vJBB@wDz08a%KMxJ4XKh0%$S%a#&PbGXJxbJosK$pc!rSzV%VgpFhEf3qNA0eI$ z@9F|N(Q8b4fDsXP5)JMO0=2e%TUvt^(h-8KzLkL=gS!yFfwK5v@VR=yXXOVd0YP(= zTyGvI>G^dCW%9OP^pS%xg+Eashc$?mGp;lQBjI+Xq3~Vpj;@`|(d}nS81V5@Ou8aD z+wzidM!K^fF>|KR_ICFqrF&;r0;JtON%%An-$pGhaIxqO9P~#tL}$0D%qZK;lo*R5T2!vT%ZvP?`4eUUO!k}ZFLo|@381f7%IgIcnm@->SRrp=^PyOU@Y|^JS(mLGf{Fai~1*=Ata{!7Z}|)iP6i z;fix9a@UAz@VajMy1goxKOus6!=)T^NZ4!7Vm+ApsCb8s<{`Oz6p=h|^)2xt`F^^( zb@#zzJQ4BN#a^GyTjtEjhWL$$#Kv*H%fRA`C@S7WnKQPX#mip3m5Low2&jAL$N+yR z%w>{RtI46IJ1$s9C)vXkE=yRKQ8%P(8;IIspoW82gD^N}J97!?0#FSFB|i-Q`utJj z&QLX}b>-h})N4LsP}iv4uv|8CZ<=tB2qse)_dlP-JhlwP@RCuhF=97;ytsqvvI-z4 z9}a>!y&(IBognC1?Sm$$3R|IH`0ImT`5cS#!LMBF4c-(y^vY^Y|MkI_`X5ye6#Ket zv#tI;rf5Ag>mbiNbwe4o_Iv8JY;mo_ie*1;_Q{EvsW=W&5K!q+t~bh;kB9Q*f5rX( z@a3mE{_D%XuP%J~x5Mo273E!!Z~#ukKdl0iZX;XWk!3@_YBKSQcaDLpe{J^1z-yGv z{uohSgDiMRmesoddtLJWo0S8v{$Gs?c>l+H+*nZ7)z_+j8*=^~ClB&!Lf;1!P?P-M zTmKid+Wm7We|tB=F|nc-O??L1PZ{8lenS^?^Q~Sb|G@U#xcKVo>bth5QqsMSb3cDY zU7jMpl&<^7r!-!BEK{N$h!1qI&j{xEdNP*8tgO@JZBu<;bhyBW!u;`hRDP?@jK4Av zHF8v>LlRk8?bgOtIkh1Y@yJ91E{14WN0H0Ojjj8vDD-)%MF(oSDu$@2ir+aek9cBT znB&|4PJ87+ zzMGe4V$!ri#lYZIak!ffr}%gJPmcRX8NxJ$LjBLMJdcdDv}Mw7b<_*^{Z6HG#W9*| z?b?y{xvyT4dXVoe7Dz=%Uw$2ykb*#>GnnPlrpt9A)lxFJ454lTYiYwTUdVFl0~`CZ za0UkwK5Yl*mP*jE*@`$OWY^lm%F?D2KWO)(Qeu74NsEpz`y&X|?y8r0&Z;2VK&d^m^W%<2oRg z;il|r_XPul*|WYX=C5ph=* zxP6E+EeF9J`j03eqSZ_QxA{uLJ-?P$$U=}lH`{jttBW^z&rsiJHJWSFK=C9j)Tv{m zRBhiJUj}y|S;?Tw@P3VLUz@hJn2F~&_az|?Pm!9-aL}$)B=+ow@SVu?x`cJhjFW3X zjiY(GInG@ESmlqKQf?8?^&ymrSHSgRRrny~wUkp;6$f#Ht!lQv_k71wa)hck#MhQs zMB+GB%>a7U=+b^w9I?M%r$^I};l4WbqSNa4)kSywc7?L^Ft@q(3dKekUQC-3>%t0j z|GL&J{^{HnUpT#X<+qtIB~o|t7OyQM1}qL#&kJhY(+s@+ZIaKuphAYhR)i)^GrsuT zh+*AD^qrk(w4$90!SA^4%R`72mX=CYfOgx_BQYunZsr?K`z^Iw4s~g$^Zs&Lxli#s z{;~jgN(wKdEu38Fvj1{II#fz>M%0NcEiydqvA6s5%!J?GS$NPZ14`fM1KmPHZLJZR zg{*29|I22TrL?%ysND!nH7_5}2MWz3n&?bu_3@6CyvxK9x$Z>%CaH(=MTsnO-pxiQ zyB;D9qcN5EP87M{Z)iQ0v_!C*^I5ItsoT=Bw?VZ>U*FJTm3eH&a73hh4&j-UhvJ7h zGJdbDR=0EMOI_|_c;D{k&NNp(J(!}I!_H6_Mm?vMU zp(Dht{-BCn$ge$d!)GX*IG#h+mGn>HRG<(fKmYmsJ(;kXOaMc3g3=ouWi>S{Y|LE& zYZ58xG_E2ws9bkNJ5}KUxNe{0tY;S~Lf$!$ltHPUgnH(Oby^KRVHd968}A4tF`wGC z1hxt%)1ekw%{r}V=*6Z5R>Y5H$IF60xUvfJ=~b3EAu_vTc`?f7n$yE5-w|Hsb4(mV zJ_l=5XYE%%a7L%$NYxjo(%ks1aWQ_s#mb!3v3U0x+94fxTX-lc5zET<`$;Q+y0^bj zl!L*oPBVc&U6PiB17?H#xNQDZAr z5(t>#&793!`SV;q2<%(K@iyH^1)~yJJBZ_+|bHJKZX5RI?9 z6)dkeECMixTP`nvKSYu9Tw7l#2=PgI%$wisiRA{?LuG!woo{eS-@5a5rxfjR)l~tPzY0EhS0gQ&Sn{l7eMK*rh6nMp9xjGFtq5@gltiTZG5%=URMO?Ih9*BI&3E{hr6R{f~>b9}E3vW^*)bUdPa z20Jty39TP(mK!!YO0+w%r(J#jgU7gE`P%IGq|$d+wUp?K!<1>!&NbO#wgF&)%RyVq z_78~EgZ_)`&!&2}sX!<{D(s%ORS*#hn8N0JN7!~6-%LW7y4xDb;^JrNQ-9k3DH(?B z9-llC>Y6?o>P`e|C4?&;t_+a`kHePS#yl}!Umnbc*0~6J%-uJ4K1N`IScYz0ZIqaj zy|2&r6I__puH$-!(msa27QuNE`h5HfYlf#*Z>N!)Q8nKI9>okMAR%h)y_y`HIjWuP zv=~LTbT{aQfm~=O^6Obh_@ItV%&-9lVXD{;p^J@bUnOCL^}#}Bn|D0AwgTy7DzC}A zbOIFlglaG2ZMArOC{6N4s7R?aOJdZpic+L3+lt-PpT_miQN57c;j|xOCs0Tr0-POW zzko{yy*<&flzc){e%kqry9nTS)UDdM0j+~E6GY!LFecT`Q2+iEgrT7i^|*ZpX( z+~;0QtTtW-duL8JWhRlyPL@=(eP?I3(a;L~?;k1zkb`iIRCFwNy7;p1&`}YTk}_K~ zmN*;5vjGItva(=)JJ-R%`D;j@Cr?6@1L^o_-A{*JsGEFkSWBp9F3!8@1eoTo#d0#R z^QA(OByS`gtb}w@Q#nQ17+-0Ws%Ob1K8wm!P*lvVLxDK#Y|dyPp88!L^=WNL=}jE~ zf;3tq5mT#3irf=%_O9|Cq%eK|nB2~8!bFOJJGZ=8iZlldme%97Mo=rUQO@6Y<>#Xy z&-JsX$NG>I^q1As%+{Z?!51c-L5gL6N2T>XIOe%}Kf`WTn&@ckN z5;K5~>nU8gwS$kedWWz-S;_6s@8w!jLmyA4j^{Hc{pULnH8dKI_wks`N5@cyEFtac z;9Y_2=-N}7FADToG|eO8A(%gwF}C*ZqwcGN5Eg9}7Y$Cxd1i5~I#oUt)Kc#$Fv`vV z3EyDZ%K&%QtRVYv3Xuuexb<|AeLc<&y`oJ2w=0T((svGn;a+zq=^jB9vbis>+=n~f zRtQ1UUTiS58s71(;e^_%exIMV;T!3NN8HJ_b(1Q!Yo4|-Tde?pCN`xuH#bv9Oo*}D z^z(;1jU91uj*+sv35)#fP(?~B<((!lFRk2W)Ith?!0NA8AF3$Tr;TZ@#PXiP3+TUp zeRE0-#|!yj&Z;BR-{>|W-Ebr@UYIS^INbcdXnV`BD%)@ES4BiXP)TVeq(P97?(Rvq zba%J3bWOUuyIVj&y1To(`@MXg^7V`?if0Pu84qfu3KzLUnghx;Jn9narDY}4E3^%gEZr4DLT#2DI3`ThR&F@A zR&K2?rJn_5sMih==&$X3vvyk(f!QPxwe))3Lhw+XuMvGYUC5kr9-uy3Q=nX&G3}5p zjr~xpq3r4^=>&holT-FFNF(C@<_vGHnv;T6P;Ae?(QPMOH=U+ea_Z>*Q(cquo<`uL z3|WYmG#Vi;tJP&kU$;@5w#Rw2^0Od%o8AbrJm=e0B$cF!oW}*Wu|M!OF((0@f}`xT zYD*Q9$^V{ZVbZ(L6d;h^I<1FuL`?ncjX{`hnJ;9-4D6hA0itVi+zBjN#ISl~m(YlK zBgnYr*{KuyVRg3lO3ehK&8;#VQ)yPlTI`Qs7z?imxE!1=vt-_;ev=K(2Z4?YGfk%U z{kjunURtLdr#3V6C)X}{^;na0j;`_rc+nl3M``G@CS;S5{++eho>RxuR_;+9!7BI znl;wC=__-m;BHkXdwFfMvVGIe`vR-OQkv7OzuzfHC=VMv77RELaJp?hXbrUQJlFJ2 z(rngXHHkEwL!gtS)wGy%fO0lSG4u3ANIiPh`So12xcGDkRy{5cTizP!(p)H)jqBYt z%AMv`&~;Mf>EdAEvsizpm1GK}(w)2Z8ij{-HvHax%wsA}7nK3NtEoOKH5qt9pfqH!e(N~@A0*~!i2xN@@EB$Xy zUIF>)AAaZ4sY=?Y)epa{ZrSYmrDt!nqozG|E_C{X78XRbcy1f#F3}!1nk^7vHC!(kpY6AZ>pE{?VmK{mwegj*Jx$F$)M`Fq5m?-#zbSbG*j|ujYZc zA#>EkWsm*EFI8?R$2r6}noLcst+aaXN><)8vHOl`07pjmc#$Z#obRr@WtW^&0EyAeWdt2!YwkUEmz>+dV61)+FE;LfIU|2 zCN}xncY);hW^RSDwI~I86O0${G+!?YO7{8m0Zr4$(M-@((VpSt4IO&eMv*c^L1D6; zQI%YOq(bCj1;)7|LeXL&cN<-R&e_%&6%R*DZz`pORGqWNza~{vx=@;@?NC${LtQoU zxHz;-)<(*LhbQ?6)p?^S|9lngY;kKX4$F(y&a%%KLrlA~4_YPXo&uF?EA#X7OUkCF zhg~?8KHt5~f6x<+cX7Ot7B=5XUqicyvO(0U(Oz8@5|5WEOlH#y)~!TanMB~l&j;O1 zjPl3tm7Uu&a=tNW$wM~LyO7~`C8@r4*3k1S2or<`P*L>?MTdFfCe%FnB-Z8 zH5etivfN@j*UQ!InLfI5udNmmQL&f`WJ*aomRT;I^z{3Vqg7PYwN=zLhNW{mOX#b9?qSG=bOiO?g=OEFv2zb$NjJX|v-46KvmXwINmg z_w*NDN0CYA9czfFkuO~!fR`bv0`_gHesR#r1`neuSvzPI7~xEXNg)^BK0|I~v>znf z#p|JL8sxAtTO87e98G3kp|yj{^qENZ-Ji@OyR~z7GwK0Z`zg%g8SDLhlJaK?{LqhJ zfM8<7JtW6+uEH%#$JP|r4Re-dUs41iOQA&C@2Mw&vB+j`aSe&>t&g8T3o55MJso*~ zeu;DqgWe`6AWNmTW<(T8s3ogR(9Ey}$Fa8BUliDx745`ku*^75mtv*=8dLm7Ib41U zfWVdUb`a*hNEPLmBel?ImyUZzTbLl#^!d?(9x0mSEVTANlx&uEgrP@z0X|w z`qg85uD>mX zP5Ls%o-z8&2y@3{i3<`-kM5_xy`>|in_0@&dQmuq|D2}{!Un78ljgCvgSg~zj7L{C zRp!2Mb8|mLAqCmly_XB}z>oJ=r({=fon8X0@g|RY-ArYRmR%BSYUTKbwjqa4*UTIQ$JU+R% zkkXX!eePfNxwRnu!Dvxul(z)ov&>E7zq{s|R4uQ?aoFT`QO_x2Xx(mdU<8z|x5TX5E#tmJSPH8c z+GH}*ryo+Zv+(<}sYOR4cpD=+^}KxVDxo(X2d#&k&P)a~uL2baW@~1-HN!%5-D2!a z$HY|~;WT4w2FQPGobHB9lUCS9rlbrmR(NyJQ>fUcDlljxrf@?L{$=?}?U>3ZM586vm~ z&PtEI1mCfnEngW94>|ES=x(Jc^DaOR#z^m$q$O-s{L@YHTa5ElXtnwGg@LS`?0|)I zzSMSvyej=kgkKf90Tu1xfoo%1rYZhQcP~hbmNwvGrA$?urK<`ZKhvG4Mn#Ekyd24P zt}dWW{2H&JClmY1wX%8!_TVm>08IyJ(=q z%rHWClF=7~a;d;a(xrNZUrq-UOcS36am7L^$08-)3a{6(L1m;C)|;yr@j+AWXp3Xr z!e$DBD`DxVTf_Ks4aPh{f=?Gb9ftvpE63pv@4$&*)$VS&PAjicN4egZmce)q}kl zOBBC+$;a0(7Mt#V3JB~7)sxQ;$y3B^tZRQQnk!FbiVk71zdWr|76WNMA!}km!LDxy z8*XXJI~Va30TCWHv~B}hh;IbcfTrX=%cK@I(fRvXueYj#1m0Kv)6AK_*(4;~>rRNm zo6s5UXS!#7l+f7wa@)%+b37HJMOl?|!X1_yJ8eG?quO603Xi6-)#5I2(zGuR$Kd9nWrj&fg zd#Yu@<%&|(mpPm`?O)-jDK1XsBBm-CQMx?*r9~bJunG?-Ozq$O0|B{CzlU zTKAM{^%*gcGpS~)lt%DOFgRMxnBA2;m^y01FgCy3-nPybz4>Q>KhpHd2XJ_7d~TtW z>u6vEW3dAa=8JX=>NrJaQ~BvL<}_bxOkXQPoGeOw_e(uY$mNU!~SZG!Au>%48<1C0|75t})E85@$SrrqD(;A>F)q zS8kTw_LNs&U!ldd08T0?x0Z4fvEe9RX)sVUC1}zdZ@N;g86g(HU?j1=>h|H!y_el&w2(WRju&Gv+TBh+jio0y7WNdLMeq1LU^Lfs zV&@N6_0(8pmQG`eQFXM9MUgR-@(tp?qcT2gc6Z3i3HF1AES5gziU`Equ*@$5<8*uk z?Doc=O_yXq82B(dk@~GQq2_yaMEc{3tUc7n*};U?Ba)25XlyEd2%EuNVZbY>45-hFxcPd?&ot?}-8 zdo*$k$gybRIRYfICy$6ZnPmDd8g>9UXgk@nR=7yCc^bo#$tKOJ|-D;x{e!hhHZaLzH<2Iubl*3KG~# zaq*)D=w#AqRoOS5ItGT2xy_K@EkOZa-rYZTcyK`N_J)o37KwnWvOQZ8T~N^LYf@{d zdlhQ-t)6VP#^|OA$mU3?^k6hyzpKkn(Kl==ORKzOtfAUM;Q5SuW^S#q%G0BcfqPTS zmkB55n_(V2R2-?E&DIbqW2HuCd5reG=>{yEdb1^&K!c0z@fwk4l_J##gpC&8t(NY( zkpJRn#jYo^c|4Cz9HmDyUKh|y*^X0M8hQkIQ#mHOwu^%i0UqAj^vy;`E)6I{h@mm( zpe&>?_H${9yW3!>FSfdXViY3>S|5Le z>4BgQV*5-;o`zO(T6o#YgR`Mw-cbz)$u?51UQ`DuEhtG#yBc(ivcMvu4Nr`esFf7m zxf}s8uY-t?H2A~u_=fz=XiRYe6PaqS7w75>BJ)%&{zD@WfoGFBL%sNY5it5wX`iOj z-pwx49H_)4#!pt4yC1E&%sd|I<;YV(=!~a`Z>*hH1Xq55HFivy)qX#U#P)4vA>#hC zfIv6$_DldO=RRmgNOFI#QU=hAXStz9G^Xo>9^^QC1?lf2z)?6tlTDCqlZZjNDW%7( zM6FcaxzOjb8>dxq+mS&lz*YmKq5T};ilaaArR^F$i+8T;TqcIChgBTVjO}h};`>9i zraalP+vY2wS2A2D z`Q$u-&i&=Vb3E$e_b@KX$}zTA=Q+9}Ts%{k#fBT_dP1)oJ1`%3gpl`1K>A!n`Jk^f zd2pK*v2@$6X!$hUYjnn1W7OrK{(HQlBq=FUG~m*&1BPIU7A-x^HX|oyY2e|3C>ImN zL-rn9XWKJ~qN8J7NPEO1O?0ZKsEHuHaS2N*(WqVxKIzyf^5Go<2}tTg%jk3AAXy)C z;U#lazqjA%XcKqkBO@bITO0*U5RrmD(3Ne)wYlv$yl1;d3c7c&$V}3SkI5JgzGJiS z_m?80lSWZ*G!*=#m8y;Am@$n(YTEmoR(^guPisNt+$v;uX86(L`u9e_nb?FY#aP!H zoZ-sKX-v8=!NJe$eYI+;?KPB(C_V$BarITmG&aT&|5st7uqj$Aoe* z<&!9m!g${64QfeRa;pX(KcOn4#h=<^Py+=r9g_QrQF7)?PkF>#*~LD1)d2VVRfD7B z@E59@_b@xJv8OA)y6qJe6=m-^42Lzk4z8!xJz)KFE?b8=w@}eA=l>=qn{_%H5)mHg ze!!+U%*oC+db%#N_gj^~Hr`E0Sj7{T{SmeTI zoClyMd=b#!sZy{qNeYR{();nQRU@EB-tuVUaHl3Fq~wsk{|Iy?SeY+ggq(ohRyJ3$ z{xdi}>h4HC;#UERaLNCd^NpKCN#Rh@^WJ_HA0>)-JU z?iQlwFemQii&RtmRq)5m0Q0OV##RsP+3;a2)~YgB|d|QdfH}~26V!JhliRuT-oD=>X-5HaZooaro9vKI?mPA*F)N$sMktPzaW%ME#X_M@S}8AuTlf!H->i$6J3WRQ8>_&TkVXPJb~|01EEEfbuV z*8Vp>`G+QSj=Idf;)>+&@BTi1H2up<{pTP5x0uZ_seO70yI6oy{O{w}5HI5Y_5PUu zJ_UnFS`guL!CLMiMJEuOX_^dp0hhufHV6%-ulGVyB z(hG=qVBNBRky)tV-%V3p5JM|J3cYj7+FU%g=*`4X@)J0}wKt!r&?=6b_5mz8Sbssw zdT&ncyVD(oO)r?PksIvoJ@6;ngTLxhLV(Q1DLpNwy4Ms#aWp*(*LR&3Sd4$+;u zo#XlyW87wZg>9ct+d^@_SQ5B$hw=){Cs=2Tf*y1kA=N0iB#DVrb9{m-1avYp<@T8Y zn@68EG;`4Mpi;jkyf!} zced)EBm3j?iCPT;#X;O+K*6XOfV;S`ZLBrpHOk=$p(tf<%G6l})6e|*5!G`U zK|7?hOF21NGSa5PLzg#aTbZ5BZVzBA?P9nmq}Lt2Ps#GxjEI<&$HSqwT_-)m{`Niv zD>*rfFG_Uo6{)B?(*(u&(X5JqfN$4Q-PQReQS5nD@Fn$b(4gz`IJyI^CSaos;EbEj zRnfqO+WJV{6M>`H@HnqnhIK==gw+T4)+z3qK{hch1U z1bKkFGlNV4Oi&Uvr`ZituUrUH5H73D!Giw~7?s&^PLTXNT+doRx0ADEjdr+%k1p*G21iacm3|%+u41yzxa7>_tvPd z#;P4hom_-Ah2C^0hHnyG^zGqHiXc9V!@|Uv!A22XF6~l!rBgF@be@WwSfz9=Q;4z+ zHL=_~oixR`><|D<6&f^O8KFx^faQXFiO1};p5|sP1HSnFPGgz(id^q;-vw8Lt8hEPG#!K$k>;pOmPrt^tP|-cE2hz@6m(S4= zUPxDCpr?UvvpJ+<&{x)M|wz9G& z*!mPLxe*exR{&NB7M9n#@cY85;5VWbwRq}*Xp_`N#^O^i{fE@nd@zS?1Huy$B&Xm2 ze;wyCB{(=Bdo8fUIS7+63voMdyDqr~({L~=OIL-@+1a@|FncG7r>8kWqPm@x73^+H z*0wapX;Z^F6nK)L1w1!?rW@czdbu^H8X)li)wfez=>Jy2`s_Ge3qi~B`03Bo6Xk=M zTrR7Nn9PqZyCS3ID9yC`e7c8ojdtFtKIS8%I2zR*+Kp?dyHY}WmE7z+R8$%2;BxDl z!&18B#=BFKl&#z`D^EV{ync^_jfILMG|?_u%5E%2p6mT;B`pr+v*6p5mJCnOAWS;6 z6o*dPD>Z9<+;1X9kZ8O?EsEx{z3zP5i-#>TQUWFl_4RcjISv<>MDCjh{zEe2F(qFD zad1ZUe8$8akUwq#*3W!sgK1wI7}fx%79?BJY%ehB_?DiPDP;f^3mpdwkMaXKJM35z zdT*R;K%q)@#eIO1l;YiZG#mb6i!DB_=Is5(+?4}<{V*YbouF;dEI9Gh#2 zDP?JddwZqWb_zQYfs*d#g^8&K!%TzqOxd694ucX*s=N_E-}s%S+fdVA69&f20OLK- zUZyvv6_#-vH1=2e7^ZzmZbHQPZgAEpr7=9i&iUzNIlfTSyW<^yP?`Nx`R>_e*`?m_p-QsJr6lw^dW( zM^hh+sin_lRT8}gXGRgEEf7IRHUInNA>Rzd>xT?LB7?Do90m@L1r2Nw9WqIA4};)C z1Kl(O<&5b?)L6jxMSrjGV$4^7r7&_2Q(X9|+-Am^v0&cqo}P@PNpFoP3NoR^NcW|t*7$fo33+~6IiLEI z_JylU*bxu+1GbiqdPHR45b4wM@K4ZuE?NSOuC#RTtXQJ3#u+c*ov3`ylP<fTfAvhja5Vjr+Mdsmv2-LBklgXQ*q1NKOMk42a63or>l?duFdOiCu@2Z|kD7eb z0d<;NF>Y+tc;iYF!IKVuP?io(V=wphADpjbn?m}!|!T?xXT(dn#|* zt6(MiN=Ya>9Nz~3nPfYax(;ABnf&SM8xCgr_$A9dL=;_Awa(dbEC|JR2zdaf_mDDzuv%d==f>yY?SMqkNQ4NF8i&^F zqV=oAP;y}{_44z+T8$p^d|N$rz;d6|c>nl>hRVc>6!w#D_zt53Ap}kLcM+X&XGILY zMhk$rr)$fDBUhu|MYi)r2(6hGHt~MTz#s0^M{FDfeMu3C%vPxM&QVjf%XQe#FsXGP z#fhI9JfYKah1}1nC?*}{&s?uRCh?A^s#f^9O7#sE;Opy^K6t_^qVousYYU_doDWS7+7y?sK=c@Nk2w$oi) zoy9>7J^f3!X3z=+ts!e0tl1a6U5kB&oR6tXS(G{8#&rv{;7s+JNJO-Few_8)9u zou$T^bUwWw0GLEq0yF5hzG(U=S)T|l^pCk}Y8>00@ZS8gLgy>pXQU=9ow}E?qKD_P z6R2hM?{iO!txSwoqH}9vJ~w;u66u-og@19;4f~K0G>wm!P<|3AAIjSM+HiGU}UPjn|O-T%;h1bcUf_MwF2 zZp9BRIpklH4Qb!>77b^8zvbp$kIIQ+WnUWpl6m{AwOkD(_Po8P7(nx>b=%%ok(~Y$ z`r18rf`F@2qmRJER*pjJ{)+CG)&wq)X^vN?9E1T1@Dx~f&+pBCZ;CfkuagL=NGmS175_U zNpkGzr#1t|8^Er{uGU1@x>XJow@n{rVas}0MJ);9nosMcJKjgk6bl$xiR$oKJx%dH0<-#23AiSc{~QNSfD z!-O|g>JczKv>0PAjOAJ!&psiY8!n04GWF^wj^~?uXb$5b;=%sW=YfD2K@Sk|1K#>h zWav@%L&jha&dIMbe?EmYN==)d27Q^quzY6iQxJO1CYc2(i=&#@>2rWUAet`DvY*8T zC?uz|KR&pfo=OC03-q}ih4|BGlMm@)mY0`X0m~`#nX;IWGBJtrOupTuiiT{#zpE$r zITh&g0MnCYjVHez7c0TG6M{4U@aG0c75O#(mWX`gg^7ZGvNi;FWfF$8;o~H25u3fV zBDQQH7|S$nT-)YNr$NAfd$bUd@%EK~ZL&tZ%zBUpnguq+$^3JKs2e!Hlvb-!{*#P{ zh8h6iXuVT9KDJOf$V%rR@vciMyDSonwjNqw-=RGuxaSfS(d)`{pEDa8vyh;bUm+^* zdb8h~0|}vw9#~yP&C?aIfusmOr^1+s^hHt9wM|NXfWZc4N8jwZ6_J+>WblO*-|C6Q z!|(R;TNi-S)D(=Z+64%tPeimwLWmLz771F1FG`j+=Hw1D#0;3u`pX}fkGPtTev?Q} z)42jt&bJ>)dsEf&a^mx5LYU50UTpbd=eyHMugC=OfCJZI*MoM~E9j11BR-HrOy<@B z%*b{WEWo5YjcC?1)x^B%Z~j6ul!8NLW;EO0ly6yZdT|#f z^6Blt4-W>mD$KN6t9KiP<3SvB$*Rl^&RZiZU#IpIO2yAW${7G#W~}<;@RYVJd63~p zO1qHbX+N+l!ye1W3k&brwz7XB3#Ek6Wy#>RnT?>SxE<`A0TOTWyf4r5Xk&CSkUjoM zML3=#R}zouZGd3Kyp64;n90|Jk(}O=0y36n^98HRgx1LT%6(F0_k+WGu_=^;#3@?2 zvLEXspX?-AR6|dvxP1ZmoDRe9>aJC>qoU@ou}IP*$jJ1L|pQ{$xAXvLm;J0 zyA@+$VZjIVu+GvGoCuzL=R^Y&VmwxK$_Jz~U8XFKF&XG*q)2p6i_94J1exdWFz_Yp z9$QH?MBu4dSYT^4_)0{95GTzx0rxXB3z`%kpAAqhOg0c}1kxtz2$*YFXm~r3&YK`K zjOGwu$`{VWI};*UfQ(3wQ%!CMG)a{xX9_yveJJduTS%H zmXzAtw~mP12K36tP>nQ%r+A&)N|D6NOlJo2hV zS9?od5KL>SKV}fN_Ulsb8iI6j*j8AQER@dR--4q%jJdv)6gOfLQSFsBJUwqWo~@&} zVSGB;x-EI-^YkArfLkjz2>9Vy+uDywD<08~!hVu6z6|>N2ceO$K-}Mdo_m4(>xiT^ z?j99U)U1mnUQ3jNwJu4wn9SqBH9 z|EsA3x%vN_VIUZ$yyLuu^|-~xP7MG3e4QQ)rebu4h{j_Xu#rZRtYZ<2N%JCn7RwU( zBPJY^W@`Bgj>Jb7UR3x*_a?~tf!xP-n!l@Sip)YoNi_=Qj~*Cm1*51mMVUr=`h*5d z#A5$E%?C{+MmQ?GJDiGVX88@W=<6O7Xt-C;>D0z8wGY~PrxuKpA4KKibThTpuO}6$ z2jvtcl7#G&KqSd1@j4y21*UJ_-YC2kFG;e#SKQmxyCZ6u@}eN){1JmLR+y0m>B2j` z0sp0RO%I9@$|*j0E>2P#5Bw)(FBAx|@*}J5>c0bH;BEQe91Yz-_zqfw=B;euk-}EH zUGGn;kFI^eG&}>lX2Fr@G0D5u?*zVxe zXtF}UxcJ&;ac*U$;pUrre-4a@a?0nR1V zG1se_qFi$Ao~0z6O44Ni((LyXAdg%+!b%XK<~pD)Z>9w(Zz*)kDRL2TbNFKD@>^x;9#!crPP3CGwo zvpmB({if{fcJ6X985FsZt@`%gpJ=pf2pdIui8_suT|D>?@i|u?4QI3zP`ps`h{ z?jcj$j1-w$McHpNPC$}~&t%paVZ%I4+%wSE-#=e>r3*|n3^x~bFkO*=%VEs}DeaJI zWuxAbNL%`Q(>F-Na{xd+D%HR7Ldc*Ns8eoxo6|E~X@P36v612*RNtl~*I5!Jvg+K_ zM0`~&U-8|$DhBH`dlb+i$4%LOXGkhy14FQW{c!QGLmCZZrY83I*%45FomG^-^u3O% zWsnFM$>XjUH%Y}9l(I5dNSn+rn}ec^qUHPESh8ekd;XpZ;M@RBmyt(DlI_~wJsjAK zNJy6nOaK-jBwu=S1~pr|OT*~}Me`UUHO{6E-Zvr$4PJI{xK(6*U5N8kR|Vj%^ZaGP zNK&<43!^xxFHjDeIqRQM-^vwC7>5`79DuC?!!LLXn{7^G8i@?zTI)n=60SoRljO1^RVa*6CzK%??wleWB$j83)c~8F~q8`crwPl-5(%D~<+YMwoSx z9LpXQU|m2WG{MJpn^Gf*o~ zZ?bwCDGt(DdJp*d&14Qt@N}F5yH&ENBl8O1x`vA;EQ%zVGPU&*R0ymNv5!s}PmbU<_k8$e89W!{ZFlSr<%_SbKF0kvhON)iq@ zxa7mcuK&J}vWB_8bGkg*X6Nqt>0B8XEZ&4uQn7?wu*Czb)u}Mk*$UfU6l7P-1aDF#ro0KQ1z)I6;JC;0I-tmf~C06 zXlgCH4>(1x1R;BGj^v?|`%MfHo0aU0d*>t{r9p_3{SEhfjs=-Y1si&LhJ0m$i656A zI2`y}2Q@+sR2d66>U`EEC}xcz~q*xO%~sZv;_j5hcq5 zh;e^lu2hh}fy3;;LCW^evM;}9qWNTXeU6^2%4l8hUgzS%v+&G!ecLNt(SNfzaXj5g zH15O7&ue(6R4P3{AwZ%~1M5T(nCj4zP0J?LKsKi}Q(zX<+O;zNhbEN;bkG}4SzN8!>- zC_mhlY3u<65;a-CqLZ)x3s`wr8yDo4%V?<|kPd0a(wgCaKoK-RtofK7h%MhM134US zY6JS%!Yorng8p>BYOK`QB-L;Dg45MVS;&8U2V5e(*<-tVP5}{7PHeJ9nWHyiSFY6d zvY^AOhYwvjTwfo)PIGD!K0qWB&Nf}EK_kA!mXefc0kX<&b1?`!2$TnF>~_XKb99$i zhUgMFtWDV8_J z93mN{3ap60up+GGH5;DhG-(~bq_`;4IIUWIoI2_!j3Y1O1N}6ik&W`za z1YAqWKI-l~^rS{2zy^@jNm(2vFGR=1{T30^F-w??sKhU)6a-L8l|VvFIgN_eD`y64hgL^9)7*FIlGpj6gG>M;m{hUIj61l!CZazKihWL81WV2MzUF7j(j26_K@y^ zR{$!e`E;EjO7f()x&-fnl4Mx6K6p4~&O^ z)7D`WCRA4Dxsc_8Qiy8SCRyJw&$7 z&|Qp^MO}BPP--iNvUbMV?VcAdNJ9(H9Uu2S*QX;Ju%naw*x19oKJT3C@25)b5gQwY zl`jP~Im&4}Gnc_;|$eP5WeYD<>Kree; zr5bFFCJL;~kV&;k($;%MyQji7b+f4;=~XN^;o>@K$Vht&Rzq}b2;XItLIQuf>Qg#- z-X3-s+^V+pc=+>ie}7GS;K&1K*(#us(&mxfBs;r0M_vb*k-bIjnc8F#QW{k|fb$GB zL^L+EY`>Gt(l>-uwtz|?m~#IXT|lZ|AX zUt}h>v^3Qi?v??>^vsgS=e;#>%nK#Pa(T4)%KE{!G;IQ_Ok9S5Ht|h z(i3g10V;h_Sx!(!g6S``qidHvczxvcB@0eaZ>~=kktE5*Qqv0njE$Hr;w+>5Q*ua3 z^OzL!t9D@}Ga97gAWl4RLmY9xpVgBGJw3b7xjyn;j$xqQ!^v zhjCiNXOzcje7^tD0`@y_NVWCS`gfIKQ>l?~Q-8*tsg zIxlib1n3^kvX~Z(zmb@{&;_~~Fl0QlW>;<1j|Kt#u^X%xz4YlR@`QN{s?)buumsyuZ^5ys5YbnpJ527hN+hk z%of7P^ORw#TS~*iMyFX<;jQSUGf z>*egiF;j<}>3x$$PHl#Tr*O8}|4ufO+m-@v=g9@|e5&?M{m!%dL?Cw&P3BBm`UY91bOLG(Etcj@g5 zsU&}k@)||yu~6E8I=d`}Mb0k{b!Mi0rp@(iRmhl94FCtTtPmCF8#1`w_5$~=0C$so z%)B8@mqQkNIzoyVjMDL@rG@UH%4Tj~O(>kvDP|wr*I0|p51#&4xgbrmHyz;?5Fk_Vs1*Q=asLg82NX1l$d*5q|7ciY;Se zL_`45s^U6N1yqXMJsP|hGO%)(5qCHF_CYAO;C}M^X<2g-atZ6NuX2KL@vLpXpC^`u zL%yiLv`}Xn;FgMAn4kx)?2`|qV~#3kz7Hm3W3-9|>oXjbTg5ZL1=Kvj>EIr|D0(>w z-kX$4iOVHBEs4oYj;s6?EofRpSRH%#e4s?o0Mqc*~pCNtaL=9OX8L&k!q?Ip1%UAC(>NQ~`PeK5(^UR^CO5A_`A zij-3XlA!}%@30-<1xfM)cy?J4)`t*}xCU4p4frYJSSat<0}OSQd*Y>6=riCGD4@t< zdO|Ih6C7U|6H=;@e*SpmggN(W-BgsMHj1Qk71&1%wNj_0FE;A*$;Mh-9gGH|_I&Cb zB^~&D;nV1Hu@7+Q_#7VIqr{yC^71ct6!w4j!tZ@ohGeZb*xvrh7poZ#dVyhAps2mJ zlNML^T?gbb-?W~T{h8ov-jQl=L=^T!5=sD}ul(z`?83qt9AK=e-XcQaJXdSe7r^IY zghBCGc$+f8KVFgP%-1>6lkPKZw^b_YVP9q~?}w)x%$&{sbMkSzsUMV> zB~%eGgrMqY)g)r>xF2aegZiIB9ezX0skCZ6kOUTE%{hh1EpPf|kjx4a&?zb6|E^9` zl1LGC<5y&?-s5OA2}_rU;~Ahaq-bbTdF^Ewg+7-3)uqG%@QR(a{v~!t^o`J6#++2_ zMot?gvVQ&_xS%W%&0CoVL9p>_;ZfT=@jR!>EHFz|=afzl7T|?MhQ>JZ1_y|RizlOn zW=B-8Vx|pSgJG%rji7NxvO1^Wk19ziJpQb3t=|8^6M}+vdv-WK9B`ByB7r%|a?XP+fRQ!MjWV@K0dN@`Xq zMa0CU#3dud8=(B;?*kbnIs=q?$oVR=#KRCQj0rcGm5szXM<~019F#+~xPCVtO7h2) zR(fZ67)1XR&HO^W_1wN{8N>$1&~eZrcJd~~`fD*SZ0BeII8TfxyKm*EJyBR7_}mr=zOiM-C=;(;Aa}OQF_N^iYX)_ZJdz# zlDZm7a-qWSUVzRPV{OX_Qv8=v9K*}YPyI&-bA6(t8P}W(&)p-&Z3~r!Z<{I5Mk9lb zPr@rb35m~`DU!C%XCq34?6TES#UwJmz2~bT{>t(94|)u=nyx}OC|y`+(hb!06rF@E z7|(pxS>O?0mI7hti@M#$|7RZyizGlnzengnBiplrRM33Rj!dP>1~-k;P{b+E-(m2L z@DcL##B2|_ZE?4a0_J}I=)fO`>`W&t@5AvK`47k1U?~7mQS#vlt}KtR7RU_$T(T^+ zO)+}*-WyR4w2CLSQ=k7=2lunI@pu2-RsR2?v2Qp(6BDZZZmaeJE?kkUS*9S0>n)Je z|7zL1GyllxF8>cXJwo?p?{px!^+%tCvRaGIekMz#RI?6{$JWU7^fh%so(e39NsF` zDu)-r<)+C@D|NCWqWrpMy2THk?fqBC2Tx^ycY+7C>Wt0g-JTTct|avz>#~OOv-f^_5xyR)wWF~DId)%N73#vUn?AQ z{Ix>PXN2;wbsL>f`9H7naj8}b4QEsC@$QbpywferSVXXN+v4$Pr0_{w$@JTv>a{3w zB#CQB0Bo_`|J*0+NjmxCLGAl_4^PUG?Pw)Lsv0GXRw@%`k`EFcxQOZ}164ki88_(v zc@;8lVARv?pZ9I#ON`@oAi*En+s*DAC0v3o?Lt(nr!}E}#xVX~y&kRv4GjBwf>e6V za(0$Zc?d3Zt=qj*J^6-}p!uK6eyqw7&+-O@yhAjyili>P{(wQMu24e&SnK~j(!K@U zA!N4>(6Uf83S<1|r@t&dGCm_=p>HjU|9$w5pZ#Bs68NokbkQ?8_y5D(TLsjyZd-#S zSb!kGodChz9fG^NySw{>K!D)E-QC?Cg1fuByIWWGIs5*%`>Ef$frr4V)T&kW$(&=( zF-9-O_X5>_kG*IiU(=!FE1+lY{wpH-_sh4nVCdfq03&DkKSSty5uxw@FXcJ^bKQUd z{r|gp{r~Z&5OclSDx(~Iny{Q++x~Zmzi>Qqfc+*VVx$DpWGI2|?0*O0gX6@<|9OD1 z*cy5tcCryOJ{oLqH|eS@O-X6i{3ddIdU#s*c_N}`XQuzQ!bks&iC9rux~woOy(;R5 zb6$1$u@W2e#J0m$U68AY3F*D1lCn~<5_wtilPA%hy1F{hO#pE79+8n=NWNZ7CsZ6_ zVWFG!0^S{oXlzx~<{BL;ae$VQJs%Yz!{XztW`XJ%AgHDPMV>J0%cZzDl!r$x4*bpS zh*T(Rv0@wGzqNkr{GUhiRg;ndW)K(b6HsDn^|~Hgo~PqxJUYw40Cf3sqKv6W0d-LY zp$a>o)hBK^VNBjAcRaBvLZ*C$PP0XCsuTZoX9Ni+!|t;0bvnUTu{AiRM5QhSFrYD? z%S12m4GSAVW?8cD=xMw51mxLwK=7~M*$B9oK^r%KlFrVEiHQZbx!XDr_MuP6?K^A% ztpeZxuI#-NeVPgsY60>WvyE8fR-ed!AHohJFxDaPc|F}Xm)cSg(*Y*kV3M?Cjoz?` z^OLisW`_!z%m*5eh})aTKd+3BAE*lwZ2QNxF;KDlC`#>Dv4>5=>#FJF_%xNAta?e? z8rL`H!PRb93aHl(Fpx?&UQi7+SZoD?(BV;&9$$m#fKKX;_Z`RtK}FG90;a|mztipi z+_jDU4Rp+h`NxMR0PxN{Uw8y!@Hm1tBIX`pVD%Cm!{#=xn(wF6auFbQXaIk>xd}tl zL~zF8WfGmFlEJLBWYfzIEeU#A%r@9!-~FCN@{}ARS|R3UbW?~$1^C0nou<~ZCEsGc zqD)d%yRQf1_m~2=6OO

|Q35wNcSA**STlJ}P5a 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%0D=73`8hv)7(`d1?7;P2L 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 literal 0 HcmV?d00001 diff --git a/extensions/ai-credits-breakdown/collector.mjs b/extensions/ai-credits-breakdown/collector.mjs new file mode 100644 index 000000000..abd636dca --- /dev/null +++ b/extensions/ai-credits-breakdown/collector.mjs @@ -0,0 +1,701 @@ +// Builds a per-day / per-session / per-model ledger of GitHub Copilot AI credits +// from the telemetry the Copilot CLI and Copilot app already keep on this machine. +// +// Everything here is read-only and local. Sources (all optional): +// 1. /session-store.db assistant_usage_events per-call metered nano-AIU +// 2. /session-state/*/events.jsonl session.shutdown cumulative per-session metrics +// session.usage_checkpoint prompt-cache lifetimes +// 3. /data.db Copilot app per-session totals, automations, projects +// +// 1 AI credit = 1 AIU = $0.01. total_nano_aiu / 1e9 = credits. +// No prompts, session titles, file paths or URLs are read into the ledger. +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import { DatabaseSync } from "node:sqlite"; + +export const CREDIT_USD = 0.01; +const EV_MIN_RESIDUAL = 1.0; // credits; ignore smaller disagreements between sources + +// $/1M tokens (input, cached input, cache write, output). Used only where per-call telemetry +// has not revealed a model's own rates, and to split session totals into what they paid for. +// Published table: https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing +const FALLBACK_RATES = { + "claude-opus-4.8": [5, 0.5, 6.25, 25], + "claude-opus-5": [5, 0.5, 6.25, 25], + "claude-opus-5.5": [4, 0.2, 5, 20], + "claude-sonnet-5": [2, 0.2, 2.5, 10], + "claude-sonnet-5.5": [2, 0.2, 2.5, 10], + "claude-sonnet-4.6": [3, 0.3, 3.75, 15], + "claude-haiku-4.5": [1, 0.1, 1.25, 5], + "gpt-5.3-codex": [1.75, 0.175, 0, 14], + "gpt-5.4": [2.5, 0.25, 0, 15], + "gpt-5.4-mini": [0.75, 0.075, 0, 4.5], + "gpt-5.5": [5, 0.5, 0, 30], + "gpt-5.6-sol": [4, 0.4, 5, 20], + "gpt-5.6-terra": [2, 0.2, 2.5, 12], + "gpt-5.6-luna": [0.2, 0.02, 0.25, 1.2], + "gpt-6-astra": [10, 1, 12.5, 50], + "gpt-6-sol": [2, 0.2, 2.5, 10], + "gpt-6.1-sol": [2, 0.1, 2.5, 10], + "mai-code-1.1-flash": [0.2, 0.02, 0, 1.2], + "kimi-k2.7-code": [3, 0.3, 0, 15], + // Not in the published table: priced like the nearest published sibling. + "claude-opus-4.6": [5, 0.5, 6.25, 25], + "claude-opus-4.6-1m": [5, 0.5, 6.25, 25], + "claude-opus-4.7": [5, 0.5, 6.25, 25], + "claude-opus-4.7-xhigh": [5, 0.5, 6.25, 25], + "gpt-5.2": [1.75, 0.175, 0, 14], + "gpt-5-mini": [0.25, 0.025, 0, 2], +}; +const ASSUMED_RATE_MODELS = [ + "claude-opus-4.6", "claude-opus-4.6-1m", "claude-opus-4.7", "claude-opus-4.7-xhigh", "gpt-5.2", +]; + +const SURFACE_BY_APP_TYPE = { + project: "Copilot app · project session", + workspace: "Copilot app · project session", + general_chat: "Copilot app · chat", + workflow: "Copilot app · automation", + workflow_workspace: "Copilot app · automation", + cli_session: "Copilot CLI", +}; +const KIND_BY_INITIATOR = { + user: "Your prompt", + agent: "Agent turn", + "sub-agent": "Sub-agent", + compaction: "Context compaction", +}; + +// What each per-call metered model call found in the prompt cache (see cacheStates()); display order. +export const CACHE_STATES = [ + ["warm", "Cache reused"], + ["expired", "Expired after a pause"], + ["rebuilt", "Rebuilt mid-conversation"], + ["new", "New conversation"], + ["none", "Not itemised"], +]; +const CACHE_IDX = Object.fromEntries(CACHE_STATES.map(([id], i) => [id, i])); +const CACHE_REUSE_MIN = 0.5; // share of the previous call's prompt that must come back as cache reads +// Prompt-cache lifetimes (seconds) used when the CLI has recorded none for a model's family. +const DEFAULT_CACHE_TTL = { claude: 300, other: 1800 }; +const TOKEN_TYPES = ["input", "cache_read", "cache_write", "output"]; + +export function copilotHome() { + return process.env.COPILOT_HOME || path.join(os.homedir(), ".copilot"); +} + +export function localTimeZone() { + return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; +} + +export function family(model) { + const m = model.toLowerCase(); + if (m.includes("opus")) return "Claude Opus"; + if (m.includes("sonnet") || m.includes("haiku")) return "Claude Sonnet & Haiku"; + if (m.startsWith("gpt")) return "GPT"; + return "Other"; +} + +const PRETTY = { gpt: "GPT", mai: "MAI", kimi: "Kimi", claude: "Claude" }; +export function prettyModel(id) { + return id.replace("-1m", " (1M)").split("-").map((part) => { + const lower = part.toLowerCase(); + if (PRETTY[lower]) return PRETTY[lower]; + if (/^\d/.test(part) || part.startsWith("(")) return part; + return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); + }).join(" "); +} + +const isoDay = (ts) => ts.slice(0, 10); +const round3 = (x) => Math.round(x * 1000) / 1000; +const zeros = () => [0, 0, 0, 0]; + +// Most frequent key of a Map; the first one seen wins a tie. +function mode(counter) { + let best, bestCount = -1; + for (const [key, count] of counter) { + if (count > bestCount) { best = key; bestCount = count; } + } + return best; +} +function bump(map, key, by = 1) { + map.set(key, (map.get(key) || 0) + by); +} +function nested(map, key, make) { + let value = map.get(key); + if (value === undefined) { value = make(); map.set(key, value); } + return value; +} + +// Weekday (Mon=0) and hour of a timestamp in a time zone. The UTC offset is looked up once per +// 15-minute bucket, because zone transitions never fall inside one. +export function createLocalClock(timeZone) { + const format = new Intl.DateTimeFormat("en-US", { + timeZone, hourCycle: "h23", year: "numeric", month: "numeric", day: "numeric", + hour: "numeric", minute: "numeric", second: "numeric", + }); + const offsets = new Map(); + const BUCKET = 15 * 60 * 1000; + const offsetFor = (ms) => { + const bucket = Math.floor(ms / BUCKET); + let offset = offsets.get(bucket); + if (offset === undefined) { + const start = bucket * BUCKET; + const p = Object.fromEntries(format.formatToParts(new Date(start)).map((x) => [x.type, Number(x.value)])); + offset = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - start; + offsets.set(bucket, offset); + } + return offset; + }; + return (ms) => { + const local = new Date(ms + offsetFor(ms)); + return { weekday: (local.getUTCDay() + 6) % 7, hour: local.getUTCHours() }; + }; +} + +function openReadOnly(file) { + if (!fs.existsSync(file)) return null; + try { + const db = new DatabaseSync(file, { readOnly: true }); + // The Copilot CLI and app may be writing right now; wait briefly rather than fail. + db.exec("PRAGMA busy_timeout = 3000"); + return db; + } catch { + return null; + } +} + +function tableColumns(db, table) { + try { + return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name)); + } catch { + return new Set(); + } +} + +// Per-call events grouped by session, and the per-token rates each model's calls revealed. +export function loadEvents(db) { + const bySession = new Map(); + const ratesSeen = new Map(); + const have = tableColumns(db, "assistant_usage_events"); + const required = ["session_id", "model", "total_nano_aiu", "created_at"]; + if (required.some((column) => !have.has(column))) return { bySession, ratesSeen }; + const wanted = ["session_id", "model", "input_tokens", "output_tokens", "cache_read_tokens", "cache_write_tokens", + "total_nano_aiu", "initiator", "token_details_json", "created_at", "id", "agent_id", "duration_ms"]; + const select = wanted.map((column) => (have.has(column) ? column : `NULL AS ${column}`)).join(", "); + for (const row of db.prepare(`SELECT ${select} FROM assistant_usage_events`).iterate()) { + const startMs = Date.parse(row.created_at); + if (!row.session_id || !row.model || Number.isNaN(startMs)) continue; + const comps = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; + const tok = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; + try { + for (const t of JSON.parse(row.token_details_json || "[]")) { + if (!(t.tokenType in comps)) continue; + const rate = t.costPerBatch / t.batchSize / 1e9; // credits per token + if (!Number.isFinite(rate)) continue; + comps[t.tokenType] += t.tokenCount * rate; + tok[t.tokenType] += t.tokenCount; + if (t.tokenCount > 0) { + const byType = nested(ratesSeen, row.model, () => new Map()); + bump(nested(byType, t.tokenType, () => new Map()), Math.round(rate * 1e10) / 1e4); + } + } + } catch { /* malformed token details: keep the call's credits without a split */ } + nested(bySession, row.session_id, () => []).push({ + model: row.model, + credits: (row.total_nano_aiu || 0) / 1e9, + comps, tok, + kind: KIND_BY_INITIATOR[row.initiator || ""] || "Background", + ts: row.created_at, + // created_at is when the call finished; the request went out duration_ms earlier. + start: startMs / 1000 - (row.duration_ms || 0) / 1000, + id: row.id, + // The main agent, each sub-agent and the background calls hold separate conversations. + stream: `${row.agent_id || ""}\u0000${row.initiator ? "main" : "background"}`, + prompt: row.input_tokens ?? tok.input + tok.cache_read + tok.cache_write, + cached: row.cache_read_tokens ?? tok.cache_read, + }); + } + return { bySession, ratesSeen }; +} + +// Prompt-cache lifetime in seconds per model: the CLI's own record, else what it recorded for the +// model's provider family (Claude or the others), else that family's default. +export function cacheTtlFor(recorded) { + const ttl = new Map([...recorded].map(([model, counter]) => [model, mode(counter)])); + const group = (model) => (model.startsWith("claude") ? "claude" : "other"); + const summary = {}; + for (const g of Object.keys(DEFAULT_CACHE_TTL)) { + const seen = new Map(); + for (const [model, seconds] of ttl) if (group(model) === g) bump(seen, seconds); + summary[g] = seen.size ? mode(seen) : DEFAULT_CACHE_TTL[g]; + } + return { ttlFor: (model) => ttl.get(model) || summary[group(model)], summary }; +} + +// Tag every per-call event with what it found in the prompt cache (event.cs, an index into CACHE_STATES). +// Calls are taken in request order within a conversation. A call reused the cache when at least half of +// the previous call's prompt (same conversation and model) came back as cache reads. Otherwise the prompt +// was written to the cache again: "expired" when that previous call went out longer ago than the model's +// cache lifetime, "rebuilt" when it was recent or there was none on this model (a model switch, +// compaction or a changed prompt). The first call of a conversation is "new". +export function cacheStates(bySession, ttlFor) { + for (const events of bySession.values()) { + const seen = new Set(); + const last = new Map(); + const ordered = [...events].sort((a, b) => a.start - b.start || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); + for (const e of ordered) { + const key = `${e.stream}\u0001${e.model}`; + const prev = last.get(key); + let state; + if (!seen.has(e.stream)) state = "new"; + else if (prev && e.cached >= CACHE_REUSE_MIN * prev.prompt) state = "warm"; + else if (prev && e.start - prev.start >= ttlFor(e.model)) state = "expired"; + else state = "rebuilt"; + e.cs = CACHE_IDX[state]; + seen.add(e.stream); + last.set(key, { start: e.start, prompt: e.prompt }); + } + } +} + +// Credits per 1M tokens (input, cache_read, cache_write, output): the modal rate each model's calls +// revealed, else the published table. +export function buildRates(ratesSeen) { + const rates = new Map(); + for (const [model, byType] of ratesSeen) { + const fallback = FALLBACK_RATES[model]; + rates.set(model, TOKEN_TYPES.map((type, i) => { + const counter = byType.get(type); + if (counter) return mode(counter); + return fallback ? fallback[i] * 100 : 0; + })); + } + for (const [model, fallback] of Object.entries(FALLBACK_RATES)) { + if (!rates.has(model)) rates.set(model, fallback.map((x) => x * 100)); + } + return rates; +} + +function price(model, tok, rates) { + const r = rates.get(model); + if (!r) return null; + return { input: tok.input * r[0] / 1e6, cache_read: tok.cache_read * r[1] / 1e6, cache_write: tok.cache_write * r[2] / 1e6, output: tok.output * r[3] / 1e6 }; +} + +// Scan one events.jsonl buffer for lines that start with a given JSON prefix, in file order. +function* linesStartingWith(buffer, needle) { + let pos = 0; + while ((pos = buffer.indexOf(needle, pos)) !== -1) { + if (pos === 0 || buffer[pos - 1] === 10) { + let end = buffer.indexOf(10, pos); + if (end === -1) end = buffer.length; + yield buffer.toString("utf8", pos, end); + pos = end; + } else { + pos += needle.length; + } + } +} +const SHUTDOWN_LINE = Buffer.from('{"type":"session.shutdown"'); +const CHECKPOINT_LINE = Buffer.from('{"type":"session.usage_checkpoint"'); + +// Per session: [timestamp, totalCredits, {model: {credits, tok, calls}}] in file order, from the CLI's +// shutdown records. Also the prompt-cache lifetimes the CLI recorded: Map>. +export async function loadShutdowns(home) { + const out = new Map(); + const cacheTtls = new Map(); + const root = path.join(home, "session-state"); + let dirs = []; + try { dirs = fs.readdirSync(root); } catch { return { shutdowns: out, cacheTtls }; } + let processed = 0; + for (const dir of dirs) { + if (++processed % 64 === 0) await new Promise((resolve) => setImmediate(resolve)); + let buffer; + try { buffer = fs.readFileSync(path.join(root, dir, "events.jsonl")); } catch { continue; } + for (const line of linesStartingWith(buffer, CHECKPOINT_LINE)) { + try { + for (const state of JSON.parse(line).data.modelCacheState || []) { + if (state.modelId && state.cacheTtlSeconds) { + bump(nested(cacheTtls, state.modelId, () => new Map()), Math.trunc(Number(state.cacheTtlSeconds))); + } + } + } catch { /* skip malformed checkpoint */ } + } + const seq = []; + for (const line of linesStartingWith(buffer, SHUTDOWN_LINE)) { + let event; + try { event = JSON.parse(line); } catch { continue; } + const d = event.data || {}; + const models = {}; + for (const [model, mm] of Object.entries(d.modelMetrics || {})) { + const td = mm.tokenDetails || {}; + const u = mm.usage || {}; + const tok = { + input: td.input?.tokenCount ?? 0, + cache_read: td.cache_read?.tokenCount ?? u.cacheReadTokens ?? 0, + cache_write: td.cache_write?.tokenCount ?? u.cacheWriteTokens ?? 0, + output: td.output?.tokenCount ?? u.outputTokens ?? 0, + }; + if (Object.keys(td).length === 0) { + tok.input = Math.max(0, (u.inputTokens || 0) - tok.cache_read - tok.cache_write); + } + models[model] = { credits: (mm.totalNanoAiu || 0) / 1e9, tok, calls: mm.requests?.count ?? 0 }; + } + seq.push([event.timestamp, (d.totalNanoAiu || 0) / 1e9, models]); + } + if (seq.length) out.set(dir, seq); + } + return { shutdowns: out, cacheTtls }; +} + +// Dated increments from cumulative shutdown metrics. +// Shutdown totals are cumulative across resumes; repeats are ignored and a lower value (seen in some +// older CLI builds) never subtracts: the running maximum is what counts. This matched the Copilot app's +// own per-session totals to within 4% on the sessions where both exist (a per-segment reading overshot +// by about 30%). Returns [[timestamp, credits, {model: {credits, tok, calls}}]]. +export function shutdownIncrements(seq) { + const out = []; + let prevTotal = 0; + const best = new Map(); + for (const [ts, total, models] of seq) { + if (total <= prevTotal + 1e-9) continue; + const delta = {}; + for (const [model, cur] of Object.entries(models)) { + const base = best.get(model); + const dc = cur.credits - (base ? base.credits : 0); + if (dc <= 1e-9) continue; + delta[model] = { + credits: dc, + tok: Object.fromEntries(TOKEN_TYPES.map((k) => [k, Math.max(0, cur.tok[k] - (base ? base.tok[k] : 0))])), + calls: Math.max(0, cur.calls - (base ? base.calls : 0)), + }; + } + const sd = total - prevTotal; + const msum = Object.values(delta).reduce((a, v) => a + v.credits, 0); + if (msum > 0) { + const scale = sd / msum; + for (const v of Object.values(delta)) v.credits *= scale; + } + out.push([ts, sd, delta]); + prevTotal = total; + for (const [model, cur] of Object.entries(models)) { + const b = best.get(model); + if (!b || cur.credits > b.credits) best.set(model, cur); + } + } + return out; +} + +// Automation names and run-session links only; never read automation prompts or session titles. +export function loadAutomations(db) { + const names = new Map(); + for (const row of db.prepare("SELECT id, name FROM workflows").all()) { + names.set(row.id, row.name || "Unnamed automation"); + } + const bySession = new Map(); + const runs = db.prepare( + "SELECT r.task_id AS task_id, COALESCE(NULLIF(r.session_id, ''), w.session_id) AS session_id " + + "FROM workflow_runs r LEFT JOIN workspaces w ON w.id = r.workspace_id").all(); + for (const { task_id: id, session_id: sid } of runs) { + if (!names.has(id)) names.set(id, `Deleted automation (${id.slice(0, 8)})`); + if (!sid) continue; + if (bySession.has(sid) && bySession.get(sid) !== id) { + throw new Error("An automation run session is linked to more than one automation"); + } + bySession.set(sid, id); + } + return { names, bySession }; +} + +function loadAppSessions(db) { + const app = new Map(); + const have = tableColumns(db, "sessions"); + if (!["id", "session_type", "created_at", "updated_at", "total_nano_aiu"].every((c) => have.has(c))) return app; + const col = (name) => (have.has(name) ? name : `NULL AS ${name}`); + const rows = db.prepare(`SELECT id, session_type, ${col("model")}, created_at, updated_at, total_nano_aiu FROM sessions`).all(); + for (const r of rows) { + app.set(r.id, { + type: r.session_type, model: r.model || "", created: r.created_at, updated: r.updated_at, + credits: (r.total_nano_aiu || 0) / 1e9, + }); + } + return app; +} + +function loadProjectRepos(db) { + const repos = new Map(); + try { + const rows = db.prepare( + "SELECT w.session_id AS session_id, p.github_owner AS owner, p.github_repo AS repo " + + "FROM workspaces w JOIN projects p ON p.id = w.project_id WHERE w.session_id IS NOT NULL").all(); + for (const r of rows) if (r.owner && r.repo) repos.set(r.session_id, `${r.owner}/${r.repo}`); + } catch { /* older Copilot app schema */ } + return repos; +} + +const emptyBreakdown = () => ({ credits: 0, comps: zeros(), tok: zeros(), calls: 0 }); + +// Build the whole ledger. Returns a compact, column-oriented object that the dashboard filters in the browser. +export async function buildLedger({ home = copilotHome(), timeZone = localTimeZone(), now = new Date() } = {}) { + const store = openReadOnly(path.join(home, "session-store.db")); + const appDb = openReadOnly(path.join(home, "data.db")); + try { + let automationNames = new Map(); + let automationBySession = new Map(); + let automationsUnavailable = null; + if (appDb) { + try { + ({ names: automationNames, bySession: automationBySession } = loadAutomations(appDb)); + } catch (error) { + automationsUnavailable = error.message; + automationNames = new Map(); + automationBySession = new Map(); + } + } + + const { bySession: eventsBySession, ratesSeen } = store ? loadEvents(store) : { bySession: new Map(), ratesSeen: new Map() }; + const rates = buildRates(ratesSeen); + const { shutdowns, cacheTtls } = await loadShutdowns(home); + const { ttlFor, summary: cacheTtl } = cacheTtlFor(cacheTtls); + cacheStates(eventsBySession, ttlFor); + + // Session metadata ---------------------------------------------------------------------- + const meta = new Map(); + const newMeta = (extra = {}) => ({ repo: "", created: null, updated: null, surface: "Copilot CLI", ...extra }); + if (store && tableColumns(store, "sessions").has("repository")) { + for (const r of store.prepare("SELECT id, repository, created_at, updated_at FROM sessions").all()) { + meta.set(r.id, newMeta({ repo: r.repository || "", created: r.created_at, updated: r.updated_at })); + } + } + const app = appDb ? loadAppSessions(appDb) : new Map(); + const projectRepo = appDb ? loadProjectRepos(appDb) : new Map(); + for (const [sid, a] of app) { + const m = nested(meta, sid, () => newMeta({ created: a.created, updated: a.updated })); + m.surface = SURFACE_BY_APP_TYPE[a.type] || "Copilot app · project session"; + if (!m.repo && projectRepo.has(sid)) m.repo = projectRepo.get(sid); + if (!m.created) { m.created = a.created; m.updated = a.updated; } + } + + // Fact rows: [day, session, model, kind, tier, credits, comps[4], tok[4], calls, cache state] + // tier 0 = metered per call, 1 = metered per session total. + const facts = []; + const hourly = new Map(); // day|session|model|kind|cache|weekday|hour -> credits + const sourcesTotal = { events: 0, shutdown: 0, app: 0 }; + const tierTotal = [0, 0]; + const clock = createLocalClock(timeZone); + + const add = (day, sid, model, kind, tier, credits, comps, tok, calls, cs = CACHE_IDX.none) => { + if (credits <= 0) return; + facts.push([day, sid, model, kind, tier, credits, comps, tok, calls, cs]); + tierTotal[tier] += credits; + }; + + const allIds = new Set([...eventsBySession.keys(), ...shutdowns.keys()]); + for (const [sid, a] of app) if (a.credits > 0) allIds.add(sid); + + for (const sid of [...allIds].sort()) { + const evs = eventsBySession.get(sid) || []; + const m = nested(meta, sid, () => newMeta()); + if (!m.created) { + const first = evs.length ? evs[0].ts : (shutdowns.has(sid) ? shutdowns.get(sid)[0][0] : null); + m.created = m.updated = first; + } + const evTotal = evs.reduce((a, e) => a + e.credits, 0); + const incs = shutdowns.has(sid) ? shutdownIncrements(shutdowns.get(sid)) : []; + const shTotal = incs.reduce((a, i) => a + i[1], 0); + const appTotal = app.has(sid) ? app.get(sid).credits : 0; + sourcesTotal.events += evTotal; + sourcesTotal.shutdown += shTotal; + sourcesTotal.app += appTotal; + const best = Math.max(evTotal, shTotal, appTotal); + const createdDay = (m.created || "").slice(0, 10) || (m.updated || "").slice(0, 10); + if (!createdDay) continue; + const appModel = app.get(sid)?.model || "unknown"; + + // (1) per-call events: exact day, model, call type, cache state + const agg = new Map(); + for (const e of evs) { + const day = isoDay(e.ts); + const key = [day, e.model, e.kind, e.cs].join("\u0000"); + const a = nested(agg, key, () => ({ day, model: e.model, kind: e.kind, cs: e.cs, ...emptyBreakdown() })); + a.credits += e.credits; + a.calls += 1; + TOKEN_TYPES.forEach((type, i) => { a.comps[i] += e.comps[type]; a.tok[i] += e.tok[type]; }); + const local = clock(Date.parse(e.ts)); + bump(hourly, [day, sid, e.model, e.kind, e.cs, local.weekday, local.hour].join("\u0000"), e.credits); + } + for (const a of agg.values()) add(a.day, sid, a.model, a.kind, 0, a.credits, a.comps, a.tok, a.calls, a.cs); + + // (2) residual not covered by per-call events + const residual = best - evTotal; + if (residual <= Math.max(EV_MIN_RESIDUAL, 0.02 * best)) continue; + if (evs.length) { + // Per-call events start where the CLI began recording them; anything older is dated at session creation. + const evModels = new Map(); + for (const e of evs) bump(evModels, e.model, e.credits); + const resModels = new Map(); + if (incs.length) { + const final = new Map(); + for (const [, , byModel] of incs) { + for (const [model, v] of Object.entries(byModel)) { + nested(final, model, () => ({ credits: 0 })).credits += v.credits; + } + } + for (const [model, v] of final) { + const extra = v.credits - (evModels.get(model) || 0); + if (extra > 0) resModels.set(model, extra); + } + } + if (resModels.size) { + const tot = [...resModels.values()].reduce((a, v) => a + v, 0); + for (const [model, extra] of resModels) { + add(createdDay, sid, model, "Earlier usage", 1, residual * extra / tot, zeros(), zeros(), 0); + } + } else { + add(createdDay, sid, appModel, "Earlier usage", 1, residual, zeros(), zeros(), 0); + } + } else { + let placed = 0; + for (const [ts, sd, byModel] of incs) { + for (const [model, v] of Object.entries(byModel)) { + const tok = TOKEN_TYPES.map((t) => v.tok[t]); + const est = price(model, v.tok, rates); + let comps = est ? TOKEN_TYPES.map((t) => est[t]) : zeros(); + const esum = comps.reduce((a, c) => a + c, 0); + if (esum > 0) comps = comps.map((c) => c * v.credits / esum); + add(isoDay(ts), sid, model, "Session total", 1, v.credits, comps, tok, v.calls); + placed += v.credits; + } + if (!Object.keys(byModel).length) { + add(isoDay(ts), sid, appModel, "Session total", 1, sd, zeros(), zeros(), 0); + placed += sd; + } + } + const gap = best - placed; + if (gap > Math.max(EV_MIN_RESIDUAL, 0.02 * best)) { + add(createdDay, sid, appModel, "Session total", 1, gap, zeros(), zeros(), 0); + } + } + } + + // Dimensions ----------------------------------------------------------------------------- + const models = [...new Set(facts.map((f) => f[2]))].sort(); + const kinds = [...new Set(facts.map((f) => f[3]))].sort(); + const surfaces = [...new Set(facts.map((f) => meta.get(f[1]).surface))].sort(); + const repos = [...new Set(facts.map((f) => meta.get(f[1]).repo))].sort(); + const sessionIds = [...new Set(facts.map((f) => f[1]))].sort(); + for (const sid of sessionIds) { + if (!automationBySession.has(sid) && meta.get(sid).surface === "Copilot app · automation") { + automationBySession.set(sid, "unattributed"); + automationNames.set("unattributed", "Unattributed automation sessions"); + } + } + const automationIds = [...automationNames.keys()].sort(); + const index = (list) => new Map(list.map((v, i) => [v, i])); + const ai = index(automationIds), mi = index(models), ki = index(kinds), si = index(surfaces); + const ri = index(repos), sx = index(sessionIds); + const days = [...new Set(facts.map((f) => f[0]))].sort(); + const di = index(days); + + const outFacts = facts.map(([day, sid, model, kind, tier, credits, comps, tok, calls, cs]) => + [di.get(day), sx.get(sid), mi.get(model), ki.get(kind), tier, round3(credits), ...comps.map(round3), ...tok, calls, cs]); + const sessions = sessionIds.map((sid) => ({ + id: sid.slice(0, 8), + repo: ri.get(meta.get(sid).repo), + surface: si.get(meta.get(sid).surface), + automation: automationBySession.has(sid) ? ai.get(automationBySession.get(sid)) ?? null : null, + })); + const hourlyRows = []; + for (const [key, credits] of hourly) { + const [day, sid, model, kind, cs, weekday, hour] = key.split("\u0000"); + if (credits > 0 && di.has(day) && sx.has(sid)) { + hourlyRows.push([di.get(day), sx.get(sid), mi.get(model), ki.get(kind), Number(weekday), Number(hour), round3(credits), Number(cs)]); + } + } + let eventsStart = null; + for (const evs of eventsBySession.values()) for (const e of evs) if (!eventsStart || e.ts < eventsStart) eventsStart = e.ts; + + return { + meta: { + generated: now.toISOString(), + timeZone, + creditUsd: CREDIT_USD, + total: round3(facts.reduce((a, f) => a + f[5], 0)), + periodStart: days[0] ?? null, + periodEnd: days[days.length - 1] ?? null, + tierTotals: { 0: round3(tierTotal[0]), 1: round3(tierTotal[1]) }, + sourceTotals: Object.fromEntries(Object.entries(sourcesTotal).map(([k, v]) => [k, round3(v)])), + assumedRateModels: ASSUMED_RATE_MODELS.filter((m) => models.includes(m)), + eventsStart, + cacheTtl, + cacheReuseMin: CACHE_REUSE_MIN, + automationsUnavailable, + sources: { sessionStore: Boolean(store), appDatabase: Boolean(appDb), sessionLogs: shutdowns.size }, + }, + dims: { + models: models.map((id) => ({ id, name: prettyModel(id), family: family(id) })), + kinds, surfaces, repos, days, + caches: CACHE_STATES.map(([id, name]) => ({ id, name })), + automations: automationIds.map((id) => ({ id, name: automationNames.get(id) })), + }, + sessions, + factColumns: ["day", "session", "model", "kind", "tier", "credits", "c_input", "c_cache_read", "c_cache_write", + "c_output", "t_input", "t_cache_read", "t_cache_write", "t_output", "calls", "cache"], + facts: outFacts, + hourlyColumns: ["day", "session", "model", "kind", "weekday", "hour", "credits", "cache"], + hourly: hourlyRows, + }; + } finally { + store?.close(); + appDb?.close(); + } +} + +// A compact, privacy-light summary of a ledger for the agent: totals and top entries, no session ids. +export function summarizeLedger(ledger, { from, to } = {}) { + const { dims, facts } = ledger; + const start = from || ledger.meta.periodStart; + const end = to || ledger.meta.periodEnd; + const rows = facts.filter((f) => { + const day = dims.days[f[0]]; + return day >= start && day <= end; + }); + const group = (pick) => { + const totals = new Map(); + for (const f of rows) bump(totals, pick(f), f[5]); + return [...totals].sort((a, b) => b[1] - a[1]); + }; + const total = rows.reduce((a, f) => a + f[5], 0); + const top = (entries, name, limit = 10) => entries.slice(0, limit).map(([key, credits]) => ({ + [name]: key, credits: round3(credits), usd: round3(credits * CREDIT_USD), share: total ? round3(credits / total) : 0, + })); + const byDay = group((f) => dims.days[f[0]]); + const calls = new Map(); + for (const f of rows) bump(calls, f[15], f[14]); + return { + period: { from: start, to: end, basis: "UTC days" }, + totalCredits: round3(total), + totalUsd: round3(total * CREDIT_USD), + activeDays: byDay.length, + sessions: new Set(rows.map((f) => f[1])).size, + models: top(group((f) => dims.models[f[2]].name), "model"), + repositories: top(group((f) => dims.repos[ledger.sessions[f[1]].repo] || "(no repository)"), "repository"), + surfaces: top(group((f) => dims.surfaces[ledger.sessions[f[1]].surface]), "surface"), + callTypes: top(group((f) => dims.kinds[f[3]]), "callType"), + promptCache: group((f) => f[15]).map(([i, credits]) => ({ + state: dims.caches[i].name, credits: round3(credits), usd: round3(credits * CREDIT_USD), calls: calls.get(i) || 0, + })), + busiestDays: top(byDay, "day", 5), + generated: ledger.meta.generated, + }; +} + +// `node collector.mjs [out.json]` prints a one-line summary, and writes the full ledger when given a path. +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + const ledger = await buildLedger(); + if (process.argv[2]) fs.writeFileSync(process.argv[2], JSON.stringify(ledger)); + console.log(`${ledger.facts.length} fact rows, ${ledger.sessions.length} sessions, ${ledger.meta.total} credits, ${ledger.meta.periodStart ?? "no data"} to ${ledger.meta.periodEnd ?? "no data"}`); +} diff --git a/extensions/ai-credits-breakdown/collector.test.mjs b/extensions/ai-credits-breakdown/collector.test.mjs new file mode 100644 index 000000000..57f18487a --- /dev/null +++ b/extensions/ai-credits-breakdown/collector.test.mjs @@ -0,0 +1,317 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { DatabaseSync } from "node:sqlite"; +import { + CACHE_STATES, buildLedger, buildRates, cacheStates, cacheTtlFor, createLocalClock, family, + loadAutomations, prettyModel, shutdownIncrements, summarizeLedger, +} from "./collector.mjs"; +import { SENTINELS, writeSampleHome } from "./sample-data.mjs"; + +const state = (e) => CACHE_STATES[e.cs][0]; +const call = (id, startSeconds, prompt, cached, extra = {}) => ({ + id, model: "claude-sonnet-5.5", stream: "main", start: startSeconds, prompt, cached, ...extra, +}); + +function tempHome() { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "ai-credits-")); + return { home, cleanup: () => fs.rmSync(home, { recursive: true, force: true }) }; +} + +// --------------------------------------------------------------------------------------------- +// Automation attribution (names and run-session links only) +// --------------------------------------------------------------------------------------------- +function automationDb() { + const db = new DatabaseSync(":memory:"); + db.exec(` + CREATE TABLE workflows (id TEXT, name TEXT, prompt TEXT); + CREATE TABLE workflow_runs (task_id TEXT, session_id TEXT, workspace_id TEXT); + CREATE TABLE workspaces (id TEXT, session_id TEXT); + INSERT INTO workflows VALUES ('daily', 'Daily report', 'PRIVATE PROMPT'); + INSERT INTO workflows VALUES ('idle', 'Not run yet', 'PRIVATE PROMPT'); + INSERT INTO workspaces VALUES ('workspace', 'workspace-session');`); + return db; +} + +test("includes every automation without reading prompts", () => { + const db = automationDb(); + const { names, bySession } = loadAutomations(db); + assert.deepEqual(Object.fromEntries(names), { daily: "Daily report", idle: "Not run yet" }); + assert.equal(bySession.size, 0); +}); + +test("repeated runs are attributed once per session", () => { + const db = automationDb(); + db.exec(`INSERT INTO workflow_runs VALUES ('daily', 'run-1', NULL); + INSERT INTO workflow_runs VALUES ('daily', 'run-1', NULL); + INSERT INTO workflow_runs VALUES ('daily', 'run-2', NULL);`); + assert.deepEqual(Object.fromEntries(loadAutomations(db).bySession), { "run-1": "daily", "run-2": "daily" }); +}); + +test("a workspace is only a fallback for the run's own session", () => { + const db = automationDb(); + db.exec(`INSERT INTO workflow_runs VALUES ('daily', NULL, 'workspace'); + INSERT INTO workflow_runs VALUES ('daily', '', 'workspace'); + INSERT INTO workflow_runs VALUES ('daily', 'explicit-session', 'workspace'); + INSERT INTO workflow_runs VALUES ('daily', NULL, NULL);`); + assert.deepEqual(Object.fromEntries(loadAutomations(db).bySession), { "workspace-session": "daily", "explicit-session": "daily" }); +}); + +test("keeps a deleted automation without guessing a title", () => { + const db = automationDb(); + db.exec("INSERT INTO workflow_runs VALUES ('deleted-1234', 'old-session', NULL)"); + const { names, bySession } = loadAutomations(db); + assert.equal(names.get("deleted-1234"), "Deleted automation (deleted-)"); + assert.equal(bySession.get("old-session"), "deleted-1234"); +}); + +test("conflicting links fail instead of misattributing spend", () => { + const db = automationDb(); + db.exec(`INSERT INTO workflow_runs VALUES ('daily', 'shared-session', NULL); + INSERT INTO workflow_runs VALUES ('idle', 'shared-session', NULL);`); + assert.throws(() => loadAutomations(db), /more than one automation/); +}); + +// --------------------------------------------------------------------------------------------- +// Session-level totals +// --------------------------------------------------------------------------------------------- +const tok = (input, cache_read = 0, cache_write = 0, output = 0) => ({ input, cache_read, cache_write, output }); + +test("shutdown totals are cumulative: repeats are ignored and a lower value never subtracts", () => { + const m = (credits, t, calls) => ({ "claude-sonnet-5.5": { credits, tok: t, calls } }); + const seq = [ + ["2026-10-01T01:00:00Z", 10, m(10, tok(100, 1000), 5)], + ["2026-10-01T02:00:00Z", 10, m(10, tok(100, 1000), 5)], // repeat + ["2026-10-02T01:00:00Z", 25, m(25, tok(250, 2500), 12)], + ["2026-10-03T01:00:00Z", 20, m(20, tok(200, 2000), 9)], // dips: ignored + ["2026-10-04T01:00:00Z", 30, m(30, tok(300, 3000), 15)], + ]; + const incs = shutdownIncrements(seq); + assert.deepEqual(incs.map((i) => i[0]), ["2026-10-01T01:00:00Z", "2026-10-02T01:00:00Z", "2026-10-04T01:00:00Z"]); + assert.deepEqual(incs.map((i) => i[1]), [10, 15, 5]); + assert.equal(incs.reduce((a, i) => a + i[1], 0), 30); + assert.equal(incs[1][2]["claude-sonnet-5.5"].calls, 7); +}); + +test("per-model credits are scaled to the session increment", () => { + const seq = [["2026-10-01T01:00:00Z", 10, { + a: { credits: 6, tok: tok(1), calls: 1 }, b: { credits: 6, tok: tok(1), calls: 1 }, + }]]; + const [, total, byModel] = shutdownIncrements(seq)[0]; + assert.equal(total, 10); + assert.equal(byModel.a.credits + byModel.b.credits, 10); +}); + +// --------------------------------------------------------------------------------------------- +// Prompt cache +// --------------------------------------------------------------------------------------------- +test("classifies every call by what it found in the prompt cache", () => { + const ttl = () => 300; + const events = [ + call("1", 0, 20000, 0), // first call of the conversation + call("2", 10, 21000, 20000), // read the previous prompt back + call("3", 20, 22000, 21000), // read it back again + call("4", 1000, 23000, 0), // long pause, whole prompt written again + call("5", 1010, 40000, 1000), // recent, but most of the prompt was rewritten + call("6", 1020, 5000, 0, { stream: "sub-1" }), // a sub-agent is a separate conversation + ]; + cacheStates(new Map([["s", events]]), ttl); + assert.deepEqual(events.map(state), ["new", "warm", "warm", "expired", "rebuilt", "new"]); +}); + +test("the same model is tracked per conversation, and a model switch is rebuilt", () => { + const events = [ + call("1", 0, 20000, 0), + call("2", 10, 21000, 0, { model: "gpt-5.6-sol" }), + ]; + cacheStates(new Map([["s", events]]), () => 300); + assert.deepEqual(events.map(state), ["new", "rebuilt"]); +}); + +test("cache lifetimes: the CLI's record, then its family, then a default", () => { + const { ttlFor, summary } = cacheTtlFor(new Map([ + ["claude-sonnet-5.5", new Map([[300, 5]])], + ["gpt-5.6-sol", new Map([[3600, 2], [1800, 1]])], + ])); + assert.equal(ttlFor("claude-sonnet-5.5"), 300); + assert.equal(ttlFor("claude-opus-5.5"), 300); + assert.equal(ttlFor("gpt-5.6-sol"), 3600); + assert.equal(ttlFor("mai-code-1.1-flash"), 3600); + assert.deepEqual(summary, { claude: 300, other: 3600 }); + assert.deepEqual(cacheTtlFor(new Map()).summary, { claude: 300, other: 1800 }); +}); + +test("rates come from what calls revealed, else the published table", () => { + const rates = buildRates(new Map([["claude-sonnet-5.5", new Map([["input", new Map([[123.4, 9]])]])]])); + assert.equal(rates.get("claude-sonnet-5.5")[0], 123.4); + assert.equal(rates.get("claude-sonnet-5.5")[3], 1000); + assert.equal(rates.get("gpt-5.4")[0], 250); + assert.equal(rates.has("no-such-model"), false); +}); + +// --------------------------------------------------------------------------------------------- +// Names and time +// --------------------------------------------------------------------------------------------- +test("model names and families", () => { + assert.equal(prettyModel("claude-sonnet-5.5"), "Claude Sonnet 5.5"); + assert.equal(prettyModel("gpt-5.4-mini"), "GPT 5.4 Mini"); + assert.equal(prettyModel("claude-opus-4.6-1m"), "Claude Opus 4.6 (1M)"); + assert.equal(family("claude-opus-5"), "Claude Opus"); + assert.equal(family("claude-haiku-4.5"), "Claude Sonnet & Haiku"); + assert.equal(family("gpt-6-sol"), "GPT"); + assert.equal(family("kimi-k2.7-code"), "Other"); +}); + +test("weekday (Mon=0) and hour follow the local time zone, including daylight saving", () => { + const utc = createLocalClock("UTC"); + assert.deepEqual(utc(Date.parse("2026-10-28T12:34:00Z")), { weekday: 2, hour: 12 }); + const sydney = createLocalClock("Australia/Sydney"); + assert.deepEqual(sydney(Date.parse("2026-10-28T12:00:00Z")), { weekday: 2, hour: 23 }); // AEDT, UTC+11 + assert.deepEqual(sydney(Date.parse("2026-10-28T13:00:00Z")), { weekday: 3, hour: 0 }); + assert.deepEqual(sydney(Date.parse("2026-06-10T12:00:00Z")), { weekday: 2, hour: 22 }); // AEST, UTC+10 + const newYork = createLocalClock("America/New_York"); + assert.deepEqual(newYork(Date.parse("2026-07-01T03:00:00Z")), { weekday: 1, hour: 23 }); // Tuesday 23:00 EDT, UTC-4 +}); + +// --------------------------------------------------------------------------------------------- +// Whole ledger, built from fictional telemetry +// --------------------------------------------------------------------------------------------- +test("builds a consistent ledger from local telemetry", async () => { + const { home, cleanup } = tempHome(); + try { + const sample = writeSampleHome(home); + const ledger = await buildLedger({ home, timeZone: "UTC", now: new Date("2026-10-28T12:00:00Z") }); + const { dims, meta } = ledger; + + assert.ok(ledger.facts.length > 100); + assert.equal(ledger.facts[0].length, ledger.factColumns.length); + assert.equal(ledger.hourly[0].length, ledger.hourlyColumns.length); + assert.equal(meta.timeZone, "UTC"); + assert.equal(meta.generated, "2026-10-28T12:00:00.000Z"); + + // Per-call credits are exactly what the fictional telemetry recorded. + assert.ok(Math.abs(meta.tierTotals[0] - sample.eventCredits) < 1); + assert.ok(meta.tierTotals[1] > 0, "older sessions are placed from their shutdown records"); + const total = ledger.facts.reduce((a, f) => a + f[5], 0); + assert.ok(Math.abs(total - meta.total) < 1); + assert.ok(Math.abs(meta.tierTotals[0] + meta.tierTotals[1] - meta.total) < 1); + + // Every dimension index resolves. + for (const f of ledger.facts) { + assert.ok(dims.days[f[0]] && ledger.sessions[f[1]] && dims.models[f[2]] && dims.kinds[f[3]] && dims.caches[f[15]]); + } + assert.deepEqual(dims.caches.map((c) => c.id), CACHE_STATES.map(([id]) => id)); + assert.deepEqual(dims.automations.map((a) => a.name).sort(), ["Daily standup summary", "Docs link audit", "Weekly dependency check"]); + assert.ok(dims.surfaces.includes("Copilot app · automation")); + assert.ok(ledger.sessions.some((s) => s.automation !== null)); + assert.ok(dims.repos.includes("contoso/storefront")); + + // The per-call prompt-cache states all show up in the sample, and cost components add up. + const cacheCredits = Object.fromEntries(dims.caches.map((c) => [c.id, 0])); + for (const f of ledger.facts) cacheCredits[dims.caches[f[15]].id] += f[5]; + for (const id of ["warm", "expired", "rebuilt", "new"]) assert.ok(cacheCredits[id] > 0, id); + for (const f of ledger.facts.filter((row) => row[4] === 0)) { + assert.ok(Math.abs(f[6] + f[7] + f[8] + f[9] - f[5]) < 0.05, "per-call components add up to the call credits"); + } + assert.ok(meta.cacheTtl.claude === 300 && meta.cacheTtl.other === 1800); + } finally { + cleanup(); + } +}); + +test("never reads prompts, titles or file paths into the ledger", async () => { + const { home, cleanup } = tempHome(); + try { + writeSampleHome(home); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + const text = JSON.stringify(ledger); + for (const secret of SENTINELS) assert.equal(text.includes(secret), false, secret); + assert.ok(ledger.sessions.every((s) => s.id.length <= 8), "session ids are shortened"); + } finally { + cleanup(); + } +}); + +test("summaries are privacy-light and consistent with the ledger", async () => { + const { home, cleanup } = tempHome(); + try { + writeSampleHome(home); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + const all = summarizeLedger(ledger); + assert.ok(Math.abs(all.totalCredits - ledger.meta.total) < 1); + assert.equal(all.totalUsd, Math.round(all.totalCredits * 10) / 1000); + assert.ok(all.models.length > 0 && all.repositories.length > 0 && all.busiestDays.length === 5); + assert.deepEqual(all.models.map((m) => m.credits), [...all.models.map((m) => m.credits)].sort((a, b) => b - a)); + assert.equal(JSON.stringify(all).includes(ledger.sessions[0].id), false, "no session ids"); + + const oneDay = ledger.dims.days[ledger.dims.days.length - 1]; + const day = summarizeLedger(ledger, { from: oneDay, to: oneDay }); + assert.equal(day.period.from, oneDay); + assert.ok(day.totalCredits > 0 && day.totalCredits < all.totalCredits); + } finally { + cleanup(); + } +}); + +test("an empty or missing Copilot home gives an empty ledger instead of an error", async () => { + const { home, cleanup } = tempHome(); + try { + const ledger = await buildLedger({ home: path.join(home, "nothing-here"), timeZone: "UTC" }); + assert.equal(ledger.facts.length, 0); + assert.equal(ledger.meta.periodStart, null); + assert.equal(ledger.meta.total, 0); + assert.deepEqual(ledger.meta.sources, { sessionStore: false, appDatabase: false, sessionLogs: 0 }); + assert.equal(summarizeLedger(ledger).totalCredits, 0); + } finally { + cleanup(); + } +}); + +test("works without the Copilot app database and degrades on conflicting automation links", async () => { + const { home, cleanup } = tempHome(); + try { + writeSampleHome(home); + const appPath = path.join(home, "data.db"); + const app = new DatabaseSync(appPath); + const [session] = app.prepare("SELECT session_id FROM workflow_runs LIMIT 1").all().map((r) => r.session_id); + app.prepare("INSERT INTO workflow_runs VALUES ('conflict', 'wf-deps', NULL, ?)").run(session); + app.prepare("INSERT INTO workflow_runs VALUES ('conflict2', 'wf-docs', NULL, ?)").run(session); + app.close(); + const degraded = await buildLedger({ home, timeZone: "UTC" }); + assert.match(degraded.meta.automationsUnavailable, /more than one automation/); + assert.ok(degraded.facts.length > 0); + + fs.rmSync(appPath); + const noApp = await buildLedger({ home, timeZone: "UTC" }); + assert.equal(noApp.meta.sources.appDatabase, false); + assert.ok(noApp.facts.length > 0); + assert.equal(noApp.dims.automations.length, 0); + } finally { + cleanup(); + } +}); + +test("tolerates an older per-call schema and malformed log lines", async () => { + const { home, cleanup } = tempHome(); + try { + fs.mkdirSync(path.join(home, "session-state", "old-1"), { recursive: true }); + const store = new DatabaseSync(path.join(home, "session-store.db")); + store.exec(` + CREATE TABLE sessions (id TEXT, repository TEXT, created_at TEXT, updated_at TEXT); + CREATE TABLE assistant_usage_events (id INTEGER, session_id TEXT, model TEXT, total_nano_aiu INTEGER, created_at TEXT); + INSERT INTO sessions VALUES ('old-1', 'contoso/app', '2026-10-01T10:00:00Z', '2026-10-01T11:00:00Z'); + INSERT INTO assistant_usage_events VALUES (1, 'old-1', 'claude-sonnet-5.5', 5000000000, '2026-10-01T10:05:00Z'); + INSERT INTO assistant_usage_events VALUES (2, 'old-1', 'claude-sonnet-5.5', 1000000000, 'not a date');`); + store.close(); + fs.writeFileSync(path.join(home, "session-state", "old-1", "events.jsonl"), + '{"type":"session.shutdown","data":{broken\n{"type":"session.usage_checkpoint","data":{"modelCacheState":null}}\n'); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + assert.equal(ledger.meta.total, 5); + assert.equal(ledger.facts.length, 1); + assert.equal(ledger.dims.repos[0], "contoso/app"); + } finally { + cleanup(); + } +}); diff --git a/extensions/ai-credits-breakdown/copilot-extension.json b/extensions/ai-credits-breakdown/copilot-extension.json new file mode 100644 index 000000000..d784da6b0 --- /dev/null +++ b/extensions/ai-credits-breakdown/copilot-extension.json @@ -0,0 +1 @@ +{ "name": "ai-credits-breakdown", "version": 1 } diff --git a/extensions/ai-credits-breakdown/extension.mjs b/extensions/ai-credits-breakdown/extension.mjs new file mode 100644 index 000000000..7fb35ccdd --- /dev/null +++ b/extensions/ai-credits-breakdown/extension.mjs @@ -0,0 +1,73 @@ +// Extension: ai-credits-breakdown +// A detailed, private view of your own GitHub Copilot AI credit usage, built on this computer +// from the telemetry the Copilot CLI and Copilot app already keep in ~/.copilot. +import { joinSession, createCanvas, CanvasError } from "@github/copilot-sdk/extension"; +import { buildLedger, summarizeLedger } from "./collector.mjs"; +import { startServer, stopServer } from "./server.mjs"; + +const PERIODS = ["mtd", "7d", "30d", "90d", "all"]; +const LEDGER_TTL_MS = 3_000; + +// Concurrent requests share one build, and a build is reused for a few seconds (for example when the +// agent asks for a summary right after the panel loads). Anything older is read afresh. +let ledger = null, ledgerAt = 0, building = null; +function getLedger() { + if (ledger && Date.now() - ledgerAt < LEDGER_TTL_MS) return Promise.resolve(ledger); + building ||= buildLedger().then((built) => { + ledger = built; + ledgerAt = Date.now(); + return built; + }).finally(() => { building = null; }); + return building; +} + +const servers = new Map(); // instanceId -> { server, url } +const day = { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$" }; + +const session = await joinSession({ + canvases: [ + createCanvas({ + id: "ai-credits-breakdown", + displayName: "AI Credits Breakdown", + description: "A detailed view of your own GitHub Copilot AI credit usage: dollars by day, repository, model, surface, call type and prompt-cache state, built locally from your Copilot telemetry.", + inputSchema: { + type: "object", + properties: { period: { type: "string", enum: PERIODS, description: "Initial period: this month to date (default), last 7/30/90 days, or all time." } }, + }, + actions: [ + { + name: "get_summary", + description: "Summarize the user's own AI credit usage for a UTC day range: totals in credits and USD, top models, repositories, surfaces, call types, prompt-cache states and busiest days. Defaults to all recorded usage. Session ids, prompts and titles are not included.", + inputSchema: { type: "object", properties: { from: day, to: day } }, + handler: async (ctx) => { + try { + const { from, to } = ctx.input || {}; + return summarizeLedger(await getLedger(), { from, to }); + } catch (error) { + throw new CanvasError("usage_unavailable", error.message); + } + }, + }, + ], + open: async (ctx) => { + let entry = servers.get(ctx.instanceId); + if (!entry) { + entry = await startServer({ + getLedger, + onError: (error) => session?.log(`ai-credits-breakdown could not read usage: ${error.message}`, { level: "warning", ephemeral: true }), + }); + servers.set(ctx.instanceId, entry); + } + const period = ctx.input?.period; + const url = PERIODS.includes(period) && period !== "mtd" ? `${entry.url}#p=${period}` : entry.url; + return { title: "AI Credits Breakdown", url }; + }, + onClose: async (ctx) => { + const entry = servers.get(ctx.instanceId); + if (!entry) return; + servers.delete(ctx.instanceId); + await stopServer(entry); + }, + }), + ], +}); diff --git a/extensions/ai-credits-breakdown/sample-data.mjs b/extensions/ai-credits-breakdown/sample-data.mjs new file mode 100644 index 000000000..129d4616d --- /dev/null +++ b/extensions/ai-credits-breakdown/sample-data.mjs @@ -0,0 +1,199 @@ +// Deterministic, entirely fictional Copilot telemetry for the tests and the preview screenshot. +// writeSampleHome() lays out a fake COPILOT_HOME with the same files and tables the collector reads. +import fs from "node:fs"; +import path from "node:path"; +import { DatabaseSync } from "node:sqlite"; + +// Credits per 1M tokens: input, cache read, cache write, output. +const MODELS = { + "claude-sonnet-5.5": { rates: [200, 20, 250, 1000], weight: 5, ttl: 300 }, + "claude-opus-5.5": { rates: [400, 20, 500, 2000], weight: 2, ttl: 300 }, + "gpt-5.6-sol": { rates: [400, 40, 500, 2000], weight: 2, ttl: 1800 }, + "gpt-5.6-luna": { rates: [20, 2, 25, 120], weight: 1, ttl: 1800 }, + "claude-haiku-4.5": { rates: [100, 10, 125, 500], weight: 1, ttl: 300 }, +}; +const TYPES = ["input", "cache_read", "cache_write", "output"]; +const REPOS = [ + "contoso/storefront", "contoso/payments-api", "contoso/design-system", "fabrikam/data-pipeline", + "fabrikam/mobile-app", "sample-user/dotfiles", "sample-user/notes", "", +]; +const AUTOMATIONS = [ + ["wf-daily", "Daily standup summary"], ["wf-deps", "Weekly dependency check"], ["wf-docs", "Docs link audit"], +]; +export const SENTINELS = ["SECRET-PROMPT-TEXT", "SECRET-SESSION-TITLE", "/Users/someone/secret/path"]; + +function rng(seed) { + let a = seed >>> 0; + return () => { + a = (a + 0x6d2b79f5) >>> 0; + let t = a; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +export function writeSampleHome(home, { seed = 7, end = new Date("2026-10-28T12:00:00Z"), days = 100, oldSessions = 8 } = {}) { + const rand = rng(seed); + const between = (lo, hi) => lo + rand() * (hi - lo); + const int = (lo, hi) => Math.floor(between(lo, hi + 1)); + const pick = (list) => list[Math.floor(rand() * list.length)]; + const weighted = () => { + const total = Object.values(MODELS).reduce((a, m) => a + m.weight, 0); + let x = rand() * total; + for (const [id, m] of Object.entries(MODELS)) if ((x -= m.weight) < 0) return id; + return "claude-sonnet-5.5"; + }; + const uuid = () => [8, 4, 4, 4, 12].map((n) => Array.from({ length: n }, () => int(0, 15).toString(16)).join("")).join("-"); + const iso = (ms) => new Date(ms).toISOString(); + + fs.mkdirSync(path.join(home, "session-state"), { recursive: true }); + const store = new DatabaseSync(path.join(home, "session-store.db")); + const app = new DatabaseSync(path.join(home, "data.db")); + store.exec(` + CREATE TABLE sessions (id TEXT PRIMARY KEY, repository TEXT, summary TEXT, created_at TEXT, updated_at TEXT); + CREATE TABLE assistant_usage_events (id INTEGER PRIMARY KEY, session_id TEXT, model TEXT, input_tokens INTEGER, + output_tokens INTEGER, cache_read_tokens INTEGER, cache_write_tokens INTEGER, total_nano_aiu INTEGER, + initiator TEXT, token_details_json TEXT, created_at TEXT, agent_id TEXT, duration_ms INTEGER);`); + app.exec(` + CREATE TABLE projects (id TEXT PRIMARY KEY, name TEXT, github_owner TEXT, github_repo TEXT); + CREATE TABLE workspaces (id TEXT PRIMARY KEY, project_id TEXT, session_id TEXT); + CREATE TABLE sessions (id TEXT PRIMARY KEY, title TEXT, session_type TEXT, model TEXT, created_at TEXT, + updated_at TEXT, total_nano_aiu INTEGER); + CREATE TABLE workflows (id TEXT PRIMARY KEY, name TEXT, prompt TEXT); + CREATE TABLE workflow_runs (id TEXT PRIMARY KEY, task_id TEXT, workspace_id TEXT, session_id TEXT);`); + + const insertSession = store.prepare("INSERT INTO sessions VALUES (?, ?, ?, ?, ?)"); + const insertEvent = store.prepare("INSERT INTO assistant_usage_events (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_nano_aiu, initiator, token_details_json, created_at, agent_id, duration_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + const insertApp = app.prepare("INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?, ?)"); + const insertProject = app.prepare("INSERT INTO projects VALUES (?, ?, ?, ?)"); + const insertWorkspace = app.prepare("INSERT INTO workspaces VALUES (?, ?, ?)"); + const insertRun = app.prepare("INSERT INTO workflow_runs VALUES (?, ?, ?, ?)"); + for (const [id, name] of AUTOMATIONS) app.prepare("INSERT INTO workflows VALUES (?, ?, ?)").run(id, name, SENTINELS[0]); + store.exec("BEGIN"); + app.exec("BEGIN"); + REPOS.filter(Boolean).forEach((repo, i) => { + const [owner, name] = repo.split("/"); + insertProject.run(`project-${i}`, name, owner, name); + }); + + const cost = (model, tok) => TYPES.reduce((sum, t, i) => sum + tok[t] * MODELS[model].rates[i] / 1e6, 0); + const details = (model, tok) => JSON.stringify(TYPES.filter((t) => tok[t] > 0).map((t) => ({ + tokenType: t, tokenCount: tok[t], batchSize: 1000000, costPerBatch: MODELS[model].rates[TYPES.indexOf(t)] * 1e9, + }))); + + let eventCredits = 0; + const eventsStart = end.getTime() - days * 864e5; + const sessionsMeta = []; + + // One session of metered per-call events across a main agent, a sub-agent and background calls. + const generateSession = (sid, model, startMs) => { + let when = startMs; + let credits = 0; + const streams = [{ agent: "", prompt: int(18000, 30000), calls: int(15, 70) }]; + if (rand() < 0.5) streams.push({ agent: "sub-1", prompt: int(8000, 14000), calls: int(4, 14) }); + for (const stream of streams) { + let t = when + (stream.agent ? int(20, 120) * 1000 : 0); + let prevPrompt = 0; + for (let n = 0; n < stream.calls; n++) { + const duration = int(3000, 40000); + let initiator = n === 0 ? "user" : "agent"; + if (stream.agent) initiator = "sub-agent"; + else if (n > 0 && rand() < 0.06) initiator = "compaction"; + else if (n > 0 && rand() < 0.05) initiator = "user"; + // A pause longer than the cache lifetime means the next call writes the whole prompt again. + const paused = n > 0 && rand() < 0.08; + const gap = paused ? int(MODELS[model].ttl + 30, MODELS[model].ttl + 900) * 1000 : int(500, 15000); + if (n > 0) t += gap; + const delta = int(800, 9000); + const fresh = int(150, 1800); + const first = n === 0; + const prompt = first ? stream.prompt : initiator === "compaction" ? Math.round(stream.prompt * 0.4) : prevPrompt + delta; + const rebuilt = initiator === "compaction"; + const tok = { input: fresh, cache_read: 0, cache_write: 0, output: int(150, 4200) }; + if (first || paused || rebuilt) tok.cache_write = Math.max(0, prompt - fresh); + else { tok.cache_read = prevPrompt; tok.cache_write = Math.max(0, prompt - fresh - prevPrompt); } + const finish = t + duration; + const nano = Math.round(cost(model, tok) * 1e9); + insertEvent.run(sid, model, tok.input + tok.cache_read + tok.cache_write, tok.output, tok.cache_read, tok.cache_write, + nano, initiator, details(model, tok), iso(finish), stream.agent, duration); + credits += nano / 1e9; + prevPrompt = prompt; + t = finish; + } + } + // Title generation and similar background calls carry no initiator. + const tok = { input: int(300, 900), cache_read: 0, cache_write: 0, output: int(20, 80) }; + const nano = Math.round(cost("claude-haiku-4.5", tok) * 1e9); + insertEvent.run(sid, "claude-haiku-4.5", tok.input, tok.output, 0, 0, nano, null, details("claude-haiku-4.5", tok), iso(startMs + 2000), "", 900); + credits += nano / 1e9; + return credits; + }; + + // Metered per-call sessions, a few busy days among many quiet ones. + for (let d = 0; d < days; d++) { + const dayMs = eventsStart + d * 864e5; + const weekday = new Date(dayMs).getUTCDay(); + if ((weekday === 0 || weekday === 6) && rand() < 0.7) continue; + const busy = rand() < 0.12 ? 3 : 1; + const count = int(1, 3) * busy; + for (let s = 0; s < count; s++) { + const sid = uuid(); + const repo = pick(REPOS); + const model = weighted(); + const startMs = dayMs + int(9, 17) * 36e5 + int(0, 59) * 6e4; + const automationIndex = rand() < 0.18 ? int(0, AUTOMATIONS.length - 1) : -1; + const total = generateSession(sid, model, startMs); + eventCredits += total; + insertSession.run(sid, repo, SENTINELS[1], iso(startMs), iso(startMs + 36e5)); + const type = automationIndex >= 0 ? "workflow_workspace" : repo && rand() < 0.5 ? "workspace" : "cli_session"; + if (type !== "cli_session") { + insertApp.run(sid, SENTINELS[1], type, model, iso(startMs), iso(startMs + 36e5), Math.round(total * 1e9)); + if (type === "workspace" && repo) insertWorkspace.run(`ws-${sid}`, `project-${REPOS.filter(Boolean).indexOf(repo)}`, sid); + } + if (automationIndex >= 0) insertRun.run(uuid(), AUTOMATIONS[automationIndex][0], `ws-${sid}`, sid); + sessionsMeta.push({ sid, repo, startMs }); + } + } + + // Older sessions from before per-call telemetry: only shutdown records and Copilot app totals. + for (let i = 0; i < oldSessions; i++) { + const sid = uuid(); + const model = weighted(); + const startMs = eventsStart - int(1, 40) * 864e5 + int(9, 17) * 36e5; + const repo = pick(REPOS); + const dir = path.join(home, "session-state", sid); + fs.mkdirSync(dir, { recursive: true }); + const segments = [int(30, 200), int(10, 120)]; + let tokens = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; + let requests = 0; + const lines = [JSON.stringify({ type: "session.start", data: { title: SENTINELS[1], cwd: SENTINELS[2] } })]; + segments.forEach((calls, n) => { + tokens = { input: tokens.input + calls * 400, cache_read: tokens.cache_read + calls * 60000, cache_write: tokens.cache_write + calls * 3000, output: tokens.output + calls * 900 }; + requests += calls; + const nano = Math.round(cost(model, tokens) * 1e9); + lines.push(JSON.stringify({ + type: "session.shutdown", timestamp: iso(startMs + (n + 1) * 36e5), + data: { + totalNanoAiu: nano, + modelMetrics: { + [model]: { + totalNanoAiu: nano, requests: { count: requests }, + tokenDetails: Object.fromEntries(TYPES.map((t) => [t, { tokenCount: tokens[t] }])), + }, + }, + codeChanges: { filesModified: [SENTINELS[2]] }, + }, + })); + }); + lines.push(JSON.stringify({ type: "session.usage_checkpoint", data: { modelCacheState: [{ modelId: model, cacheTtlSeconds: MODELS[model].ttl }] } })); + fs.writeFileSync(path.join(dir, "events.jsonl"), lines.join("\n") + "\n"); + insertSession.run(sid, repo, SENTINELS[1], iso(startMs), iso(startMs + 72e5)); + sessionsMeta.push({ sid, repo, startMs }); + } + store.exec("COMMIT"); + app.exec("COMMIT"); + store.close(); + app.close(); + return { sentinels: SENTINELS, eventCredits, sessions: sessionsMeta.length }; +} diff --git a/extensions/ai-credits-breakdown/server.mjs b/extensions/ai-credits-breakdown/server.mjs new file mode 100644 index 000000000..a578b59ad --- /dev/null +++ b/extensions/ai-credits-breakdown/server.mjs @@ -0,0 +1,72 @@ +// Loopback-only HTTP server that serves the dashboard UI and the locally built usage ledger. +import { createServer } from "node:http"; +import { readFileSync } from "node:fs"; +import { randomBytes, timingSafeEqual } from "node:crypto"; + +const read = (name) => readFileSync(new URL(`./ui/${name}`, import.meta.url), "utf8"); +const ASSETS = { + "": { type: "text/html; charset=utf-8", body: read("index.html") }, + "app.js": { type: "text/javascript; charset=utf-8", body: read("app.js") }, + "styles.css": { type: "text/css; charset=utf-8", body: read("styles.css") }, +}; + +const SECURITY_HEADERS = { + "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer", +}; +const CSP = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'"; +const PREFERRED_PORTS = [47820, 47821, 47822, 47823, 47824, 47825, 47826, 47827, 47828, 47829]; + +// Every server has its own secret path prefix and only answers on 127.0.0.1. The page and /data.json +// both need the prefix, so other local users and web pages (DNS rebinding) cannot read your usage. +export async function startServer({ getLedger, onError = () => {}, ports = PREFERRED_PORTS }) { + const token = randomBytes(24).toString("base64url"); + const prefix = `/${token}/`; + let origin = ""; + const authorized = (req, url) => { + if (!origin || req.headers.host !== new URL(origin).host) return false; + if (req.headers.origin && req.headers.origin !== origin) return false; + const got = Buffer.from(url.pathname.slice(0, prefix.length)), want = Buffer.from(prefix); + return got.length === want.length && timingSafeEqual(got, want); + }; + const reply = (res, status, type, body, extra = {}) => { + res.writeHead(status, { "Content-Type": type, ...SECURITY_HEADERS, ...extra }); + res.end(body); + }; + const server = createServer(async (req, res) => { + let url; + try { url = new URL(req.url, "http://127.0.0.1"); } catch { return reply(res, 400, "text/plain; charset=utf-8", "Bad request"); } + if (req.method !== "GET" || !authorized(req, url)) return reply(res, 403, "text/plain; charset=utf-8", "Forbidden"); + const route = url.pathname.slice(prefix.length); + if (route === "data.json") { + try { + return reply(res, 200, "application/json", JSON.stringify(await getLedger())); + } catch (error) { + onError(error); + return reply(res, 500, "application/json", JSON.stringify({ error: error.message })); + } + } + const asset = Object.hasOwn(ASSETS, route) ? ASSETS[route] : null; + if (!asset) return reply(res, 404, "text/plain; charset=utf-8", "Not found"); + reply(res, 200, asset.type, asset.body, route === "" ? { "Content-Security-Policy": CSP } : {}); + }); + const listen = (port) => new Promise((resolve, reject) => { + const onFail = (e) => { server.off("listening", onListening); reject(e); }; + const onListening = () => { server.off("error", onFail); resolve(); }; + server.once("error", onFail); + server.once("listening", onListening); + server.listen(port, "127.0.0.1"); + }); + // Prefer a stable port so a restored panel keeps its address; otherwise let the OS choose. + let bound = false; + for (const port of ports) { + try { await listen(port); bound = true; break; } catch { /* port in use */ } + } + if (!bound) await listen(0); + origin = `http://127.0.0.1:${server.address().port}`; + return { server, url: origin + prefix, origin, prefix }; +} + +export async function stopServer(entry) { + entry.server.closeAllConnections?.(); + await new Promise((resolve) => entry.server.close(() => resolve())); +} diff --git a/extensions/ai-credits-breakdown/server.test.mjs b/extensions/ai-credits-breakdown/server.test.mjs new file mode 100644 index 000000000..c6d609dae --- /dev/null +++ b/extensions/ai-credits-breakdown/server.test.mjs @@ -0,0 +1,87 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import { startServer, stopServer } from "./server.mjs"; + +const ledger = { meta: { total: 1 }, facts: [] }; + +function request(base, pathname, { method = "GET", headers = {} } = {}) { + return new Promise((resolve, reject) => { + const url = new URL(pathname, base); + const req = http.request(url, { method, headers }, (res) => { + let body = ""; + res.setEncoding("utf8"); + res.on("data", (chunk) => { body += chunk; }); + res.on("end", () => resolve({ status: res.statusCode, headers: res.headers, body })); + }); + req.on("error", reject); + req.end(); + }); +} + +async function withServer(options, run) { + const entry = await startServer({ ports: [], ...options }); + try { + await run(entry); + } finally { + await stopServer(entry); + } +} + +test("serves the page, script, styles and the ledger only under the secret prefix", async () => { + await withServer({ getLedger: async () => ledger }, async ({ url, origin, prefix }) => { + assert.match(origin, /^http:\/\/127\.0\.0\.1:\d+$/); + assert.ok(prefix.length > 20); + + const page = await request(url, url); + assert.equal(page.status, 200); + assert.match(page.body, /AI credits/); + assert.match(page.headers["content-security-policy"], /default-src 'none'/); + assert.match(page.headers["content-security-policy"], /connect-src 'self'/); + assert.equal(page.headers["cache-control"], "no-store"); + + for (const [name, type] of [["app.js", /javascript/], ["styles.css", /css/]]) { + const asset = await request(url, `${prefix}${name}`); + assert.equal(asset.status, 200, name); + assert.match(asset.headers["content-type"], type); + } + + const data = await request(url, `${prefix}data.json`); + assert.equal(data.status, 200); + assert.deepEqual(JSON.parse(data.body), ledger); + + assert.equal((await request(url, `${prefix}nope`)).status, 404); + assert.equal((await request(url, `${prefix}__proto__`)).status, 404); + }); +}); + +test("rejects requests without the prefix, with another host or origin, or that are not GET", async () => { + await withServer({ getLedger: async () => ledger }, async ({ url, prefix, origin }) => { + assert.equal((await request(url, "/")).status, 403); + assert.equal((await request(url, "/wrong-token/data.json")).status, 403); + assert.equal((await request(url, `${prefix}data.json`, { headers: { Host: "evil.example" } })).status, 403); + assert.equal((await request(url, `${prefix}data.json`, { headers: { Origin: "https://evil.example" } })).status, 403); + assert.equal((await request(url, `${prefix}data.json`, { method: "POST" })).status, 403); + assert.equal((await request(url, `${prefix}data.json`, { headers: { Origin: origin } })).status, 200); + }); +}); + +test("reports a failed build as JSON without crashing", async () => { + const seen = []; + await withServer({ getLedger: async () => { throw new Error("database is locked"); }, onError: (e) => seen.push(e.message) }, + async ({ url, prefix }) => { + const res = await request(url, `${prefix}data.json`); + assert.equal(res.status, 500); + assert.deepEqual(JSON.parse(res.body), { error: "database is locked" }); + assert.deepEqual(seen, ["database is locked"]); + }); +}); + +test("each server gets its own secret prefix", async () => { + await withServer({ getLedger: async () => ledger }, async (a) => { + await withServer({ getLedger: async () => ledger }, async (b) => { + assert.notEqual(a.prefix, b.prefix); + assert.notEqual(a.origin, b.origin); + }); + }); +}); diff --git a/extensions/ai-credits-breakdown/ui/app.js b/extensions/ai-credits-breakdown/ui/app.js new file mode 100644 index 000000000..3424bc09c --- /dev/null +++ b/extensions/ai-credits-breakdown/ui/app.js @@ -0,0 +1,761 @@ +(function () { + 'use strict'; + + const $ = (id) => document.getElementById(id); + const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); + const nf = new Intl.NumberFormat('en-US'); + const fmt = (n) => nf.format(Math.round(n)); + const usd = (c) => { + const v = c * 0.01; + return '$' + v.toLocaleString('en-US', { minimumFractionDigits: v < 1000 ? 2 : 0, maximumFractionDigits: v < 1000 ? 2 : 0 }); + }; + const plural = (n, one, many) => fmt(n) + ' ' + (n === 1 ? one : (many || one + 's')); + const cu = (c) => fmt(c) + ' (' + usd(c) + ')'; + const credTxt = (c) => fmt(c) + ' credits (' + usd(c) + ')'; + const pct = (x) => (x >= 0.1 ? Math.round(x * 100) + '%' : x >= 0.01 ? (x * 100).toFixed(1) + '%' : x > 0 ? '<1%' : '0%'); + const mtok = (n) => (n >= 1e9 ? (n / 1e9).toFixed(2) + 'B' : n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? Math.round(n / 1e3) + 'K' : String(n)); + const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const monthName = (mo) => MONTHS[parseInt(mo.slice(5, 7), 10) - 1]; + const longDate = (iso) => { const [y, m, d] = iso.split('-').map(Number); return d + ' ' + MONTHS[m - 1] + ' ' + y; }; + const shortDate = (iso) => { const [, m, d] = iso.split('-').map(Number); return d + ' ' + MONTHS[m - 1]; }; + const FAMILY_COLOR = { 'Claude Opus': 'var(--opus)', 'Claude Sonnet & Haiku': 'var(--sonnet)', GPT: 'var(--gpt)', Other: 'var(--other)' }; + const TIER_NAME = ['Metered per call', 'Metered per session']; + + fetch('data.json', { cache: 'no-store' }) + .then((r) => r.json().then((body) => { if (!r.ok) throw new Error(body.error || r.status); return body; })) + .then(init) + .catch((e) => { + $('loading').textContent = 'Could not read your Copilot usage: ' + e.message; + }); + + function init(data) { + if (!data.facts.length) { + $('loading').textContent = 'No AI credit usage was found in your local Copilot telemetry yet. Run a Copilot session, then reload this panel.'; + return; + } + const D = data.dims, M = data.meta; + const AUTOMATIONS = D.automations || []; + const CACHES = D.caches; + const cacheOf = (v) => v; + const CACHE_TTL = M.cacheTtl; + const ownerOf = (repo) => (!repo ? '(no repository)' : repo.split('/')[0]); + const repoLabel = (repo) => repo || '(no repository)'; + const sessInfo = data.sessions.map((s) => { + const repo = D.repos[s.repo]; + return { repo, owner: ownerOf(repo), surf: D.surfaces[s.surface], automation: s.automation }; + }); + + const facts = data.facts.map((r) => { + const day = D.days[r[0]]; const si = sessInfo[r[1]]; + return { + day, mo: day.slice(0, 7), s: r[1], m: r[2], k: r[3], tier: r[4], c: r[5], + ci: r[6], cr: r[7], cw: r[8], co: r[9], ti: r[10], tr: r[11], tw: r[12], to: r[13], calls: r[14], cache: cacheOf(r[15]), + repo: si.repo, owner: si.owner, surf: si.surf, automation: si.automation, + }; + }); + const hourly = data.hourly.map((r) => { + const si = sessInfo[r[1]]; + return { day: D.days[r[0]], s: r[1], m: r[2], k: r[3], tier: 0, dow: r[4], h: r[5], c: r[6], cache: cacheOf(r[7]), repo: si.repo, owner: si.owner, surf: si.surf }; + }); + const modelName = (i) => D.models[i].name; + const modelFam = (i) => D.models[i].family; + const months = Array.from(new Set(facts.map((f) => f.mo))).sort(); + const lastMonth = months[months.length - 1]; + const START = M.periodStart, END = M.periodEnd; + const EVENTS_FROM = M.eventsStart ? longDate(M.eventsStart.slice(0, 10)) : 'the day the Copilot CLI began recording them'; + const TZ = M.timeZone || 'UTC'; + + /* ---------- Dates ---------- */ + const addDays = (iso, n) => { const d = new Date(iso + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() + n); return d.toISOString().slice(0, 10); }; + const spanDays = (a, b) => Math.round((Date.parse(b + 'T00:00:00Z') - Date.parse(a + 'T00:00:00Z')) / 864e5) + 1; + const daysInMonth = (mo) => { const [y, m] = mo.split('-').map(Number); return new Date(Date.UTC(y, m, 0)).getUTCDate(); }; + const monthEnd = (mo) => mo + '-' + String(daysInMonth(mo)).padStart(2, '0'); + const prevMonth = (mo) => { const [y, m] = mo.split('-').map(Number); const d = new Date(Date.UTC(y, m - 2, 1)); return d.toISOString().slice(0, 7); }; + const clampDay = (d) => (d < START ? START : d > END ? END : d); + const fullName = (mo) => ({ Jan: 'January', Feb: 'February', Mar: 'March', Apr: 'April', May: 'May', Jun: 'June', Jul: 'July', Aug: 'August', Sep: 'September', Oct: 'October', Nov: 'November', Dec: 'December' })[monthName(mo)]; + const rangeText = (a, b) => (a === b ? longDate(a) : a.slice(0, 4) === b.slice(0, 4) ? shortDate(a) + ' – ' + longDate(b) : longDate(a) + ' – ' + longDate(b)); + + const PRESETS = [ + { id: 'mtd', label: 'This month to date', range: () => [lastMonth + '-01', END] }, + { id: '7d', label: 'Last 7 days', range: () => [addDays(END, -6), END] }, + { id: '30d', label: 'Last 30 days', range: () => [addDays(END, -29), END] }, + { id: '90d', label: 'Last 90 days', range: () => [addDays(END, -89), END] }, + { id: 'all', label: 'All time', range: () => [START, END] }, + ]; + months.slice().reverse().forEach((mo) => PRESETS.push({ id: 'mo:' + mo, group: 'Month', label: fullName(mo) + ' ' + mo.slice(0, 4) + (mo === lastMonth ? ' (to date)' : ''), range: () => [clampDay(mo + '-01'), clampDay(monthEnd(mo))] })); + const presetOf = () => { + const p = PRESETS.find((x) => { const r = x.range(); return r[0] === F.from && r[1] === F.to; }); + return p ? p.id : 'custom'; + }; + + /* ---------- Filter state ---------- */ + const DIMS = [ + { key: 'repo', label: 'Repository', all: 'All repositories', name: repoLabel, enc: (v) => v, dec: (s) => s }, + { key: 'owner', label: 'Owner', all: 'All owners', name: (v) => v, enc: (v) => v, dec: (s) => s }, + { key: 'm', label: 'Model', all: 'All models', num: true, name: modelName, enc: (v) => D.models[v].id, dec: (s) => { const i = D.models.findIndex((x) => x.id === s); return i >= 0 ? i : null; } }, + { key: 'surf', label: 'Surface', all: 'All surfaces', name: (v) => v, enc: (v) => v, dec: (s) => (D.surfaces.indexOf(s) >= 0 ? s : null) }, + { key: 'k', label: 'Call type', all: 'All call types', num: true, name: (v) => D.kinds[v], enc: (v) => D.kinds[v], dec: (s) => { const i = D.kinds.indexOf(s); return i >= 0 ? i : null; } }, + { key: 'tier', label: 'Basis', all: 'All bases', num: true, name: (v) => TIER_NAME[v], enc: String, dec: (s) => (['0', '1'].indexOf(s) >= 0 ? Number(s) : null) }, + { key: 'cache', label: 'Prompt cache', all: 'All cache states', num: true, name: (v) => CACHES[v].name, enc: (v) => CACHES[v].id, dec: (s) => { const i = CACHES.findIndex((x) => x.id === s); return i >= 0 ? i : null; } }, + ]; + const DIM = {}; DIMS.forEach((d) => { DIM[d.key] = d; }); + const DEFAULT = PRESETS[0].range(); + const F = { from: DEFAULT[0], to: DEFAULT[1], repo: null, owner: null, m: null, surf: null, k: null, tier: null, cache: null }; + const match = (f, exclude) => (exclude === 'date' || (f.day >= F.from && f.day <= F.to)) && + DIMS.every((d) => d.key === exclude || F[d.key] === null || f[d.key] === F[d.key]); + const rowsFor = (exclude) => facts.filter((f) => match(f, exclude)); + const rowsBetween = (a, b, exclude) => facts.filter((f) => f.day >= a && f.day <= b && DIMS.every((d) => d.key === exclude || F[d.key] === null || f[d.key] === F[d.key])); + const sum = (rows, key) => rows.reduce((a, f) => a + f[key], 0); + const anyDim = () => DIMS.some((d) => F[d.key] !== null); + + function readHash() { + const q = new URLSearchParams(location.hash.slice(1)); + const isDay = (v) => /^\d{4}-\d{2}-\d{2}$/.test(v || ''); + if (isDay(q.get('from')) && isDay(q.get('to'))) { + let a = clampDay(q.get('from')), b = clampDay(q.get('to')); if (a > b) [a, b] = [b, a]; + F.from = a; F.to = b; + } else if (q.get('p')) { + const p = PRESETS.find((x) => x.id === q.get('p')); if (p) [F.from, F.to] = p.range(); + } + DIMS.forEach((d) => { F[d.key] = q.has(d.key) ? d.dec(q.get(d.key)) : null; }); + } + function writeHash() { + const q = new URLSearchParams(); const p = presetOf(); + if (p === 'custom') { q.set('from', F.from); q.set('to', F.to); } else if (p !== 'mtd') q.set('p', p); + DIMS.forEach((d) => { if (F[d.key] !== null) q.set(d.key, d.enc(F[d.key])); }); + const h = q.toString(); const url = location.pathname + location.search + (h ? '#' + h : ''); + if (url !== location.pathname + location.search + location.hash) history.replaceState(null, '', url); + } + readHash(); + window.addEventListener('hashchange', () => { readHash(); render(); }); + + const updated = new Date(M.generated).toLocaleString(undefined, { + day: 'numeric', month: 'short', year: 'numeric', + hour: 'numeric', minute: '2-digit', timeZoneName: 'short', + }); + $('stamp').textContent = 'Last updated ' + updated + ' · data ' + shortDate(START) + ' – ' + longDate(END) + ' (UTC days)'; + + /* ---------- Filter bar ---------- */ + const preset = $('preset'), fromEl = $('from'), toEl = $('to'); + preset.innerHTML = PRESETS.filter((p) => !p.group).map((p) => '').join('') + + '' + PRESETS.filter((p) => p.group).map((p) => '').join('') + '' + + ''; + [fromEl, toEl].forEach((el) => { el.min = START; el.max = END; }); + preset.addEventListener('change', () => { const p = PRESETS.find((x) => x.id === preset.value); if (p) { [F.from, F.to] = p.range(); render(); } }); + function onDate(e) { + if (!fromEl.value || !toEl.value) return; + let a = clampDay(fromEl.value), b = clampDay(toEl.value); + if (a > b) { if (e.target === fromEl) b = a; else a = b; } + F.from = a; F.to = b; render(); + } + fromEl.addEventListener('change', onDate); toEl.addEventListener('change', onDate); + $('dimbar').innerHTML = DIMS.map((d) => '

').join(''); + $('more').addEventListener('click', () => { + const open = $('more').getAttribute('aria-expanded') !== 'true'; + $('more').setAttribute('aria-expanded', String(open)); $('dimbar').classList.toggle('open', open); + }); + $('dimbar').addEventListener('change', (e) => { + const sel = e.target.closest('select[data-dim]'); if (!sel) return; + const d = DIM[sel.dataset.dim]; const v = sel.value; + F[d.key] = v === '__all' ? null : (d.num ? Number(v) : v); + render(); + }); + $('reset').addEventListener('click', () => { [F.from, F.to] = DEFAULT; clearFilters(); }); + $('csv').addEventListener('click', downloadCsv); + $('refresh').addEventListener('click', () => location.reload()); + + function renderFilterBar() { + const pid = presetOf(); preset.value = pid; + fromEl.value = F.from; toEl.value = F.to; + DIMS.forEach((d) => { + const g = {}; rowsFor(d.key).forEach((f) => { g[f[d.key]] = (g[f[d.key]] || 0) + f.c; }); + const raw = (v) => (d.num ? Number(v) : v); + const opts = Object.entries(g).map(([v, c]) => ({ v: raw(v), c })).sort((a, b) => b.c - a.c); + if (F[d.key] !== null && !opts.some((o) => o.v === F[d.key])) opts.unshift({ v: F[d.key], c: 0 }); + const sel = $('f-' + d.key); + sel.innerHTML = '' + + opts.map((o) => '').join(''); + sel.value = F[d.key] === null ? '__all' : String(F[d.key]); + sel.closest('.fld').classList.toggle('set', F[d.key] !== null); + }); + $('reset').disabled = !anyDim() && F.from === DEFAULT[0] && F.to === DEFAULT[1]; + const n = DIMS.filter((d) => F[d.key] !== null).length; + $('more').textContent = n ? 'Filters (' + n + ')' : 'Filters'; + } + + // Delegated click-filters from tables and lists + document.addEventListener('click', (e) => { + const b = e.target.closest('[data-f]'); if (!b) return; + const key = b.dataset.f; const val = DIM[key].num ? Number(b.dataset.v) : b.dataset.v; + F[key] = F[key] === val ? null : val; render(); + }); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && !e.target.closest('select,input')) clearFilters(); + }); + function clearFilters() { DIMS.forEach((d) => { F[d.key] = null; }); render(); } + + function downloadCsv() { + const rows = rowsFor(null).slice().sort((a, b) => (a.day < b.day ? -1 : a.day > b.day ? 1 : b.c - a.c)); + const q = (v) => { const s = String(v); return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; }; + const head = ['day_utc', 'session', 'repository', 'owner', 'surface', 'model', 'call_type', 'basis', 'prompt_cache', 'credits', 'usd', 'calls', 'input_tokens', 'cache_read_tokens', 'cache_write_tokens', 'output_tokens']; + const lines = [head.join(',')].concat(rows.map((f) => [f.day, data.sessions[f.s].id, f.repo, f.owner, f.surf, D.models[f.m].id, D.kinds[f.k], TIER_NAME[f.tier], CACHES[f.cache].name, f.c.toFixed(3), (f.c * 0.01).toFixed(4), f.calls, f.ti, f.tr, f.tw, f.to].map(q).join(','))); + const blob = new Blob([lines.join('\n') + '\n'], { type: 'text/csv' }); + const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'ai-credits_' + F.from + '_' + F.to + '.csv'; + document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 0); + } + + const SECTIONS = ['daterange', 'dimbar', 'stamp', 'statement', 'repos-sheet', 'rain-sheet', 'models-sheet', 'side-stack', 'kinds-sheet', 'anat-sheet', 'hours-sheet', 'cache-sheet', 'sessions-sheet', 'provenance', 'automations-sheet']; + function render() { + $('loading').hidden = true; + SECTIONS.forEach((id) => { $(id).hidden = false; }); + writeHash(); + renderFilterBar(); + renderStatement(); + renderRepoSpend(); + renderRain(); + renderModels(); + renderOwners(); + renderSurfaces(); + renderKinds(); + renderAnatomy(); + renderHours(); + renderCache(); + renderSessions(); + renderAutomations(); + } + + /* ---------- Comparison period ---------- */ + const delta = (now, before) => { + if (!before) return now ? 'new' : ''; + const d = (now - before) / before; const r = Math.round(Math.abs(d) * 100); + if (now / before >= 3) return '▲ ×' + (now / before >= 10 ? Math.round(now / before) : (now / before).toFixed(1)); + return r === 0 ? 'level' : (d > 0 ? '▲ ' : '▼ ') + r + '%'; + }; + // The period the selected range is compared against, or null when it falls before the data starts. + function comparison() { + const mo = F.from.slice(0, 7); const len = spanDays(F.from, F.to); + const wholeMonth = F.from === mo + '-01' && (F.to === monthEnd(mo) || (F.to === END && mo === lastMonth)); + let a, b, label; + if (wholeMonth && F.to.slice(0, 7) === mo) { + const pm = prevMonth(mo); + if (F.to === END && mo === lastMonth && F.to !== monthEnd(mo)) { + const dd = Math.min(Number(F.to.slice(8)), daysInMonth(pm)); + a = pm + '-01'; b = pm + '-' + String(dd).padStart(2, '0'); label = monthName(pm) + ' 1–' + dd; + } else { a = pm + '-01'; b = monthEnd(pm); label = fullName(pm); } + } else { + b = addDays(F.from, -1); a = addDays(F.from, -len); + label = len === 1 ? 'Day before' : 'Previous ' + len + ' days'; + } + if (b < START) return null; + return { a, b, label, partial: a < START }; + } + + /* ---------- Statement (period headline) ---------- */ + function renderStatement() { + const rows = rowsFor(null); + const c = sum(rows, 'c'); + const sess = new Set(rows.map((f) => f.s)).size; + const repos = new Set(rows.map((f) => f.repo).filter(Boolean)).size; + const tiers = [0, 1].map((t) => sum(rows.filter((f) => f.tier === t), 'c')); + const amt = '' + usd(c) + ''; + const len = spanDays(F.from, F.to); + const byDay = {}; rows.forEach((f) => { byDay[f.day] = (byDay[f.day] || 0) + f.c; }); + const active = Object.keys(byDay).length; + const peak = Object.entries(byDay).sort((a, b) => b[1] - a[1])[0]; + const counts = '' + credTxt(c) + ' · ' + plural(sess, 'session') + ' · ' + plural(repos, 'repository', 'repositories') + ' · ' + plural(active, 'active day') + ' of ' + len + ''; + const pid = presetOf(); const mo = F.from.slice(0, 7); + let lead; + if (pid === 'mtd') lead = 'You have spent ' + amt + ' in ' + fullName(mo) + ' so far'; + else if (pid === 'all') lead = 'You have spent ' + amt + ' since ' + longDate(START); + else if (pid.indexOf('mo:') === 0) lead = 'You spent ' + amt + ' in ' + fullName(mo) + ' ' + mo.slice(0, 4); + else if (/^\d+d$/.test(pid)) lead = 'You spent ' + amt + ' in the last ' + parseInt(pid, 10) + ' days'; + else lead = 'You spent ' + amt + (len === 1 ? ' on ' + longDate(F.from) : ' from ' + (F.from.slice(0, 4) === F.to.slice(0, 4) ? shortDate(F.from) : longDate(F.from)) + ' to ' + longDate(F.to)); + const facts2 = []; + facts2.push(['Range (UTC)', rangeText(F.from, F.to) + ' · ' + plural(len, 'day')]); + facts2.push(['Daily average', usd(c / len) + ' a day']); + if (pid === 'mtd') { + const dim = daysInMonth(mo); + facts2.push(['At this pace', '≈ ' + usd((c / len) * dim) + ' by ' + dim + ' ' + monthName(mo)]); + } + const cmp = comparison(); + if (cmp) { + const pv = sum(rowsBetween(cmp.a, cmp.b), 'c'); const d = delta(c, pv); + facts2.push([cmp.label + (cmp.partial ? ' (partial data)' : ''), (pv ? usd(pv) : 'Nothing') + (d ? ' ' + d + '' : '')]); + } + if (peak && len > 1) facts2.push(['Busiest day', shortDate(peak[0]) + ' · ' + usd(peak[1])]); + const parts = []; + DIMS.forEach((d) => { + if (F[d.key] === null) return; + parts.push('' + esc(d.label) + ': ' + esc(d.name(F[d.key])) + ''); + }); + $('statement').innerHTML = + '

' + lead + '.

' + counts + + (parts.length ? '

Only showing' + parts.join('') + '

' : '') + + '
' + facts2.map((x) => '
' + esc(x[0]) + '
' + x[1] + '
').join('') + '
' + + '

Basis of these numbers

' + + '
' + tiers.map((v, i) => '').join('') + '
' + + '

These are list-price amounts from local telemetry, not an invoice; see where these numbers come from.

' + + '
'; + const clr = $('clear'); if (clr) clr.addEventListener('click', clearFilters); + const pl = $('prov-link'); if (pl) pl.addEventListener('click', () => { $('provenance').open = true; }); + } + + /* ---------- Repository spend ---------- */ + let allRepos = false; + function renderRepoSpend() { + const rows = rowsFor('repo'); + const by = {}; + rows.forEach((f) => { + const k = f.repo || ''; + const b = by[k] || (by[k] = { c: 0, sess: new Set(), models: {} }); + b.c += f.c; b.sess.add(f.s); b.models[f.m] = (b.models[f.m] || 0) + f.c; + }); + const cmpP = comparison(); const cmpLabel = cmpP ? cmpP.label : null; const cmp = {}; + if (cmpP) rowsBetween(cmpP.a, cmpP.b, 'repo').forEach((f) => { cmp[f.repo || ''] = (cmp[f.repo || ''] || 0) + f.c; }); + const items = Object.entries(by).sort((a, b) => b[1].c - a[1].c); + const total = items.reduce((a, [, b]) => a + b.c, 0); const max = items.length ? items[0][1].c : 1; + $('repos-h').textContent = 'Spend by repository, ' + rangeText(F.from, F.to); + const nameCell = (repo) => { + if (!repo) return '(no repository)'; + const [o, n] = repo.split('/'); + return '' + esc(o) + '/' + esc(n); + }; + $('repos-table').innerHTML = + 'RepositorySpentShare of ' + usd(total) + '' + + (cmpLabel ? '' + esc(cmpLabel) + '' : '') + 'CreditsSessionsMain model' + + (items.length ? items.filter(([repo], i) => allRepos || i < 12 || repo === F.repo).map(([repo, b]) => { + const on = F.repo !== null && F.repo === repo; + const mm = Object.entries(b.models).sort((a, c) => c[1] - a[1])[0]; + const pv = cmp[repo] || 0; const d = cmpLabel ? delta(b.c, pv) : ''; + return '' + + '' + usd(b.c) + '' + + '
' + pct(total ? b.c / total : 0) + '
' + + (cmpLabel ? '' + (pv ? usd(pv) : '–') + (d ? ' ' + d + '' : '') + '' : '') + + '' + cu(b.c) + '' + b.sess.size + '' + esc(modelName(Number(mm[0]))) + ''; + }).join('') + (items.length > 12 ? '' : '') : 'No spend under these filters. Widen the date range or clear a filter.') + + '' + (items.length > 1 ? 'Total' + usd(total) + '' + (cmpLabel ? '' + usd(Object.values(cmp).reduce((a, v) => a + v, 0)) + '' : '') + '' + cu(total) + '' : ''); + } + document.addEventListener('click', (e) => { if (e.target.id === 'repos-more') { allRepos = !allRepos; renderRepoSpend(); } }); + function tierFill(t) { + return t === 0 ? 'background:var(--rain)' : 'background:var(--rain-soft)'; + } + + /* ---------- Rain chart ---------- */ + let rainState = { days: [], per: [], cum: [], active: null, geom: null }; + function dayList(a, b) { + const out = []; const d = new Date(a + 'T00:00:00Z'); const end = new Date(b + 'T00:00:00Z'); + while (d <= end) { out.push(d.toISOString().slice(0, 10)); d.setUTCDate(d.getUTCDate() + 1); } + return out; + } + function niceMax(v) { + if (v <= 0) return 1; + const p = Math.pow(10, Math.floor(Math.log10(v))); const n = v / p; + return (n <= 1 ? 1 : n <= 2 ? 2 : n <= 2.5 ? 2.5 : n <= 5 ? 5 : 10) * p; + } + function renderRain() { + const rows = rowsFor(null); + const days = dayList(F.from, F.to); + const idx = {}; days.forEach((d, i) => { idx[d] = i; }); + const per = days.map(() => [0, 0]); + const topModel = days.map(() => ({})); + const sessSet = days.map(() => new Set()); + rows.forEach((f) => { + const i = idx[f.day]; if (i === undefined) return; + per[i][f.tier] += f.c; topModel[i][f.m] = (topModel[i][f.m] || 0) + f.c; sessSet[i].add(f.s); + }); + const tot = per.map((p) => p[0] + p[1]); + let run = 0; const cum = tot.map((t) => (run += t)); + rainState = { days, per, tot, cum, topModel, sessSet, active: rainState.active, geom: null }; + + const el = $('rain'); const W = Math.max(320, el.clientWidth || 900); + const narrow = W < 560; + const L = narrow ? 50 : 56, R = narrow ? 56 : 66, T = 22; + const H1 = narrow ? 120 : 150, GAP = 46, H2 = narrow ? 110 : 140, B = 18; + const H = T + H1 + GAP + H2 + B; + const pw = W - L - R; const n = days.length; const step = pw / n; const bw = Math.max(1, Math.min(step - (step > 4 ? 1 : 0), 24)); + const maxDay = niceMax(Math.max(...tot, 1)); const maxCum = niceMax(Math.max(run, 1)); + const x = (i) => L + i * step + (step - bw) / 2; + const xc = (i) => L + i * step + step / 2; + const y1 = (v) => T + (v / maxDay) * H1; // hangs from the top + const y2 = (v) => T + H1 + GAP + H2 - (v / maxCum) * H2; // rises from the bottom + const top2 = T + H1 + GAP; + let s = ''; + // gridlines & axis labels + [0, 0.5, 1].forEach((f) => { + const yy = T + f * H1; s += '' + fmt(f * maxDay) + '' + (f ? '(' + usd(f * maxDay) + ')' : ''); + const y2v = top2 + H2 - f * H2; s += '' + fmt(f * maxCum) + '' + (f ? '(' + usd(f * maxCum) + ')' : ''); + }); + s += ''; + s += 'Daily credits, hanging from the topRunning total, rising'; + s += ''; + // month boundaries + labels (drop a label when the next one is too close to read) + const marks = []; + days.forEach((d, i) => { if (d.slice(8) === '01' || i === 0) marks.push({ d, i, xx: L + i * step }); }); + marks.forEach((mk, j) => { + if (mk.i !== 0) s += ''; + const next = marks[j + 1]; + if (next && next.xx - mk.xx < 44) return; + const label = monthName(mk.d.slice(0, 7)) + (mk.d.slice(5, 7) === '01' || mk.i === 0 ? ' ' + mk.d.slice(0, 4) : ''); + s += '' + label + ''; + }); + // hanging bars (stacked by basis) + for (let i = 0; i < n; i++) { + let acc = 0; + [0, 1].forEach((t) => { + const v = per[i][t]; if (v <= 0) return; + const h = (v / maxDay) * H1; s += ''; acc += h; + }); + } + // cumulative curve + if (n > 0) { + let d = 'M' + xc(0).toFixed(2) + ' ' + y2(cum[0]).toFixed(2); + for (let i = 1; i < n; i++) d += ' L' + xc(i).toFixed(2) + ' ' + y2(cum[i]).toFixed(2); + s += ''; + s += ''; + s += '' + fmt(run) + '(' + usd(run) + ')'; + } + s += ''; + s += ''; + s += ''; + el.innerHTML = s; + rainState.geom = { L, step, n, T, top2, H2, W }; + $('rain-legend').innerHTML = 'Metered per callMetered per session (day = when the session reported)Peak day ' + credTxt(Math.max(...tot, 0)) + ''; + if (rainState.active !== null && rainState.active < n) showDay(rainState.active, false); else rainState.active = null; + } + function showDay(i, fromKey) { + const g = rainState.geom; if (!g) return; + rainState.active = i; + const cross = $('cross'); const xx = g.L + i * g.step + g.step / 2; + cross.setAttribute('x1', xx); cross.setAttribute('x2', xx); cross.removeAttribute('hidden'); + const per = rainState.per[i]; const tot = rainState.tot[i]; + const tm = Object.entries(rainState.topModel[i]).sort((a, b) => b[1] - a[1])[0]; + const tip = $('tip'); + tip.innerHTML = '' + longDate(rainState.days[i]) + '' + + '
Credits' + cu(tot) + '
' + + '
Running total' + cu(rainState.cum[i]) + '
' + + '
Sessions' + rainState.sessSet[i].size + '
' + + (tm ? '
Top model' + esc(modelName(Number(tm[0]))) + '
' : '') + + (per[1] ? '
Basis' + [per[0] && 'call ' + cu(per[0]), 'session ' + cu(per[1])].filter(Boolean).join(' · ') + '
' : ''); + tip.hidden = false; + const svg = $('rain').querySelector('svg'); const r = svg.getBoundingClientRect(); + const px = r.left + (xx / g.W) * r.width; const py = r.top + 20; + const tw = tip.offsetWidth; const left = Math.min(Math.max(8, px + 14), window.innerWidth - tw - 8); + tip.style.left = (px + 14 + tw > window.innerWidth ? px - tw - 14 : left) + 'px'; tip.style.top = Math.max(8, py) + 'px'; + } + function hideDay() { $('tip').hidden = true; const c = $('cross'); if (c) c.setAttribute('hidden', ''); rainState.active = null; } + const rainEl = $('rain'); + let brush = null; + const dayAt = (e) => { + const g = rainState.geom; if (!g) return -1; const svg = rainEl.querySelector('svg'); const r = svg.getBoundingClientRect(); + const xx = ((e.clientX - r.left) / r.width) * g.W; return Math.floor((xx - g.L) / g.step); + }; + function drawBrush(a, b) { + const g = rainState.geom; const el = $('brush'); if (!g || !el) return; + const lo = Math.min(a, b), hi = Math.max(a, b); + el.setAttribute('x', g.L + lo * g.step); el.setAttribute('width', (hi - lo + 1) * g.step); el.removeAttribute('hidden'); + } + rainEl.addEventListener('pointerdown', (e) => { + if (e.pointerType !== 'mouse' || e.button !== 0) return; + const i = dayAt(e); if (i < 0 || i >= rainState.days.length) return; + brush = { a: i, b: i }; rainEl.setPointerCapture(e.pointerId); e.preventDefault(); + }); + rainEl.addEventListener('pointermove', (e) => { + const g = rainState.geom; if (!g) return; + let i = dayAt(e); + if (brush) { i = Math.max(0, Math.min(g.n - 1, i)); brush.b = i; drawBrush(brush.a, brush.b); } + if (i >= 0 && i < g.n) showDay(i); else hideDay(); + }); + rainEl.addEventListener('pointerup', () => { + if (!brush) return; const lo = Math.min(brush.a, brush.b), hi = Math.max(brush.a, brush.b); brush = null; + if (hi > lo) { F.from = rainState.days[lo]; F.to = rainState.days[hi]; hideDay(); render(); } + else { const el = $('brush'); if (el) el.setAttribute('hidden', ''); } + }); + rainEl.addEventListener('pointercancel', () => { brush = null; const el = $('brush'); if (el) el.setAttribute('hidden', ''); }); + rainEl.addEventListener('pointerleave', () => { if (!brush) hideDay(); }); + rainEl.addEventListener('blur', hideDay); + rainEl.addEventListener('keydown', (e) => { + if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; e.preventDefault(); + const n = rainState.days.length; let i = rainState.active === null ? (e.key === 'ArrowLeft' ? n - 1 : 0) : rainState.active + (e.key === 'ArrowLeft' ? -1 : 1); + i = Math.max(0, Math.min(n - 1, i)); showDay(i, true); + }); + let rt; window.addEventListener('resize', () => { clearTimeout(rt); rt = setTimeout(() => { renderRain(); renderAutomations(); }, 120); }); + + /* ---------- Automation daily spend ---------- */ + const automationEl = $('automations-chart'); + let automationState = { days: [], series: [], active: null, selected: null, geom: null }; + const automationStyle = (i) => ({ + color: ['var(--rain)', 'var(--ink)', 'var(--other)'][i % 3], + dash: ['', '7 3', '2 3', '10 3 2 3'][Math.floor(i / 3) % 4], + }); + function renderAutomations() { + const days = dayList(F.from, F.to); + const indices = new Map(days.map((day, i) => [day, i])); + const series = AUTOMATIONS.map((a, i) => ({ name: a.name, i, values: days.map(() => 0), total: 0, style: automationStyle(i) })); + rowsFor(null).forEach((f) => { + const a = series[f.automation]; + if (!a) return; + a.values[indices.get(f.day)] += f.c; + a.total += f.c; + }); + const previousDay = automationState.days[automationState.active]; + automationState = { days, series, active: indices.has(previousDay) ? indices.get(previousDay) : days.length - 1, selected: automationState.selected, geom: null }; + const legend = $('automations-legend'); + if (!series.length) { + $('automations-sheet').hidden = !M.automationsUnavailable; + $('automations-summary').textContent = M.automationsUnavailable ? 'Automation attribution is unavailable: ' + M.automationsUnavailable + '.' : ''; + automationEl.innerHTML = ''; + automationEl.removeAttribute('tabindex'); + legend.innerHTML = ''; + return; + } + automationEl.setAttribute('tabindex', '0'); + const total = series.reduce((v, a) => v + a.total, 0); + $('automations-summary').textContent = plural(series.length, 'series', 'series') + ' · ' + credTxt(total) + ' recorded · ' + rangeText(F.from, F.to) + + (total ? '' : '. No automation spend under these filters; widen the date range or clear a filter.'); + const W = Math.max(240, automationEl.clientWidth), L = 72, R = 16, T = 26, PH = 240, H = T + PH + 34; + const pw = W - L - R, n = days.length; + const x = (i) => L + (n === 1 ? pw / 2 : i * pw / (n - 1)); + const max = niceMax(series.reduce((v, a) => Math.max(v, ...a.values), 1)); + const y = (v) => T + PH - v / max * PH; + let h = '' + + 'Daily spend (USD)'; + [0, 0.25, 0.5, 0.75, 1].forEach((f) => { + const yy = y(f * max); + h += '' + usd(f * max) + ''; + }); + const ticks = Math.min(n, Math.max(2, Math.floor(pw / 95))); + for (let t = 0; t < ticks; t++) { + const i = ticks === 1 ? 0 : Math.round(t * (n - 1) / (ticks - 1)); + h += '' + shortDate(days[i]) + ''; + } + series.forEach((a) => { + const path = a.values.map((v, i) => (i ? 'L' : 'M') + x(i).toFixed(2) + ' ' + y(v).toFixed(2)).join(' '); + h += '' + esc(a.name + ' · ' + credTxt(a.total)) + '' + + '' + + a.values.map((v, i) => v > 0 || n === 1 ? '' : '').join('') + ''; + }); + h += ''; + automationEl.innerHTML = h; + automationState.geom = { W, L, pw, n, x }; + legend.innerHTML = 'Automation legend, selected day spend and period totalsAutomationPeriod total' + + series.slice().sort((a, b) => b.total - a.total || a.name.localeCompare(b.name)).map((a) => '' + usd(a.total) + '').join('') + ''; + highlightAutomation(); + showAutomationDay(automationState.active); + } + function showAutomationDay(i) { + const g = automationState.geom; if (!g) return; + automationState.active = i; + $('automations-cross').setAttribute('x1', g.x(i)); + $('automations-cross').setAttribute('x2', g.x(i)); + $('automations-day').textContent = shortDate(automationState.days[i]); + automationState.series.forEach((a) => { $('automation-day-' + a.i).textContent = usd(a.values[i]); }); + automationEl.setAttribute('aria-label', 'Daily spend by automation. ' + longDate(automationState.days[i]) + '. ' + + automationState.series.map((a) => a.name + ': ' + credTxt(a.values[i])).join('; ')); + } + function highlightAutomation() { + automationEl.querySelectorAll('[data-series]').forEach((el) => { + el.classList.toggle('highlighted', Number(el.dataset.series) === automationState.selected); + }); + $('automations-legend').querySelectorAll('[data-automation]').forEach((el) => { + el.setAttribute('aria-pressed', String(Number(el.dataset.automation) === automationState.selected)); + }); + } + $('automations-legend').addEventListener('click', (e) => { + const el = e.target.closest('[data-automation]'); if (!el) return; + const i = Number(el.dataset.automation); + automationState.selected = automationState.selected === i ? null : i; + highlightAutomation(); + }); + function readAutomationPointer(e) { + const g = automationState.geom; if (!g) return; + const r = automationEl.querySelector('svg').getBoundingClientRect(); + const xx = (e.clientX - r.left) * g.W / r.width; + const i = g.n === 1 ? 0 : Math.round((xx - g.L) / g.pw * (g.n - 1)); + showAutomationDay(Math.max(0, Math.min(g.n - 1, i))); + } + automationEl.addEventListener('pointermove', readAutomationPointer); + automationEl.addEventListener('pointerdown', readAutomationPointer); + automationEl.addEventListener('keydown', (e) => { + if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key) || !automationState.geom) return; + e.preventDefault(); + const last = automationState.days.length - 1; + const i = e.key === 'Home' ? 0 : e.key === 'End' ? last : automationState.active + (e.key === 'ArrowLeft' ? -1 : 1); + showAutomationDay(Math.max(0, Math.min(last, i))); + }); + + /* ---------- Ranked list helper ---------- */ + function rankList(items, key, opts) { + opts = opts || {}; + const total = items.reduce((a, i) => a + i.c, 0); const max = Math.max(...items.map((i) => i.c), 1); + if (!items.length) return '

No spend under these filters.

'; + const shown = items.slice(0, opts.limit || items.length); + let h = '
    ' + shown.map((i) => { + const on = F[key] !== null && String(F[key]) === String(i.v); + return '
  • '; + }).join('') + '
'; + const rest = items.slice(shown.length); + if (rest.length) h += '

+ ' + rest.length + ' more, ' + credTxt(rest.reduce((a, i) => a + i.c, 0)) + ' combined.

'; + return h; + } + const groupSum = (rows, keyFn) => { const o = {}; rows.forEach((f) => { const k = keyFn(f); o[k] = (o[k] || 0) + f.c; }); return o; }; + + function renderModels() { + const rows = rowsFor('m'); const g = groupSum(rows, (f) => f.m); + const items = Object.entries(g).map(([m, c]) => ({ v: m, label: modelName(Number(m)), c, color: FAMILY_COLOR[modelFam(Number(m))] })).sort((a, b) => b.c - a.c); + const tok = {}; rows.forEach((f) => { tok[f.m] = (tok[f.m] || 0) + f.ti + f.tr + f.tw + f.to; }); + $('models').innerHTML = rankList(items, 'm', { limit: 10 }) + + '

' + Object.keys(FAMILY_COLOR).map((k) => '' + esc(k) + '').join('') + '

' + + (items.length ? '

' + esc(items[0].label) + ' alone accounts for ' + pct(items[0].c / items.reduce((a, i) => a + i.c, 0)) + ' of the credits shown.

' : ''); + } + function renderOwners() { + const ro = rowsFor('owner'); const go = groupSum(ro, (f) => f.owner); + $('owners').innerHTML = rankList(Object.entries(go).map(([v, c]) => ({ v, label: v, c })).sort((a, b) => b.c - a.c), 'owner', { limit: 8 }); + } + function renderSurfaces() { + const rows = rowsFor('surf'); const g = groupSum(rows, (f) => f.surf); + $('surfaces').innerHTML = rankList(Object.entries(g).map(([v, c]) => ({ v, label: v, c })).sort((a, b) => b.c - a.c), 'surf') + + '

Coding agent and code review sessions are not metered per token; see “Where these numbers come from”.

'; + } + function renderKinds() { + const rows = rowsFor('k'); const g = groupSum(rows, (f) => f.k); + const UNITEMISED = ['Session total']; + const items = Object.entries(g).map(([v, c]) => { + const name = D.kinds[Number(v)]; + return { v: Number(v), label: UNITEMISED.indexOf(name) >= 0 ? name + ' (not itemised)' : name, c, color: UNITEMISED.indexOf(name) >= 0 ? 'var(--rule-strong)' : null }; + }).sort((a, b) => (a.color ? 1 : 0) - (b.color ? 1 : 0) || b.c - a.c); + $('kinds').innerHTML = rankList(items, 'k', { noDot: true }) + + '

Call types come from per-call telemetry, available from ' + EVENTS_FROM + '. “Your prompt” is the first call after you hit enter; “Agent turn” covers the tool loop that follows.

'; + } + // What credits pay for, shared by the anatomy panel and the prompt-cache table. + const TOKEN_PARTS = [ + { k: 'cr', label: 'Cache reads', color: '#1b64c8', why: 'Re-reading the conversation so far on every model call.' }, + { k: 'cw', label: 'Cache writes', color: '#6fa2ea', why: 'Storing new context for later calls to reuse.' }, + { k: 'co', label: 'Output', color: '#0b2540', why: 'Tokens the model generates, including reasoning.' }, + { k: 'ci', label: 'Fresh input', color: '#b7d1f4', why: 'New, uncached input tokens.' }, + ]; + const UNITEMISED_PART = { label: 'Not itemised', color: '#d9e2ea', why: 'Session-level totals and early-period residuals carry no token split.' }; + function renderAnatomy() { + const rows = rowsFor(null); const c = sum(rows, 'c'); + const parts = TOKEN_PARTS.map((p) => Object.assign({}, p, { c: sum(rows, p.k) })); + const itemised = parts.reduce((a, p) => a + p.c, 0); + parts.push(Object.assign({}, UNITEMISED_PART, { c: Math.max(0, c - itemised) })); + $('anatomy').innerHTML = '' + + '
    ' + parts.map((p) => '
  • ' + p.label + '' + cu(p.c) + '' + pct(c ? p.c / c : 0) + '' + p.why + '
  • ').join('') + '
'; + } + function renderHours() { + const grid = Array.from({ length: 7 }, () => new Array(24).fill(0)); + hourly.forEach((r) => { if (match(r, null)) grid[r.dow][r.h] += r.c; }); + const total = grid.flat().reduce((a, v) => a + v, 0); + const max = Math.max(...grid.flat(), 1); const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + const shade = (v) => { if (v <= 0) return 'var(--grid)'; const t = Math.sqrt(v / max); return 'color-mix(in srgb, var(--rain) ' + Math.round(12 + t * 88) + '%, #fff)'; }; + let h = '' + + (peak ? '

Busiest slot: ' + days[peak.d] + ' ' + String(peak.i).padStart(2, '0') + ':00, ' + credTxt(peak.v) + '. Grid covers ' + credTxt(total) + ' of per-call telemetry.

' : '

No per-call telemetry under these filters.

') + + '

' + esc(TZ) + ' time, per-call telemetry only (from ' + EVENTS_FROM + '). Darker = more credits. Follows every filter.

'; + } + + /* ---------- Prompt cache ---------- */ + const mins = (s) => (s >= 3600 && s % 3600 === 0 ? s / 3600 + (s === 3600 ? ' hour' : ' hours') : Math.round(s / 60) + ' min'); + const CACHE_DESC = { + warm: 'Read the conversation so far back from the cache.', + expired: 'Came back after the cache lifetime (' + mins(CACHE_TTL.claude) + ' for Claude, ' + mins(CACHE_TTL.other) + ' for other models), so the whole conversation was written again.', + rebuilt: 'Within the cache lifetime, but most of the prompt was written again: a model switch, a compaction or a changed prompt.', + new: 'First call of a session, sub-agent or background task: instructions and tools are written to the cache.', + none: 'Session totals have no per-call record.', + }; + function paidBar(b) { + const parts = TOKEN_PARTS.map((p) => ({ label: p.label, color: p.color, v: b[p.k] })); + parts.push({ label: UNITEMISED_PART.label, color: UNITEMISED_PART.color, v: Math.max(0, b.c - parts.reduce((a, p) => a + p.v, 0)) }); + const said = parts.filter((p) => b.c && p.v / b.c >= 0.005).map((p) => p.label + ' ' + pct(p.v / b.c)).join(', '); + return '
' + + parts.map((p) => '').join('') + '' + + '' + (b.c && b.cw ? pct(b.cw / b.c) + ' writes' : '') + '
'; + } + function renderCache() { + const rows = rowsFor('cache'); + const by = CACHES.map(() => ({ c: 0, calls: 0, cr: 0, cw: 0, co: 0, ci: 0 })); + rows.forEach((f) => { const b = by[f.cache]; b.c += f.c; b.calls += f.calls; b.cr += f.cr; b.cw += f.cw; b.co += f.co; b.ci += f.ci; }); + const total = by.reduce((a, b) => a + b.c, 0); const max = Math.max(...by.map((b) => b.c), 1); + const idOf = (id) => CACHES.findIndex((x) => x.id === id); + const shown = CACHES.map((s, i) => i).filter((i) => by[i].c > 0 || F.cache === i); + const metered = shown.filter((i) => CACHES[i].id !== 'none'); + const mCredits = metered.reduce((a, i) => a + by[i].c, 0); const mCalls = metered.reduce((a, i) => a + by[i].calls, 0); + $('cache-h').textContent = 'Prompt cache: reused or rebuilt, ' + rangeText(F.from, F.to); + $('cache-table').innerHTML = + 'Prompt cacheSpentShare of ' + usd(total) + 'CallsPer callWhat it paid forCredits' + + (shown.length ? shown.map((i) => { + const s = CACHES[i], b = by[i], on = F.cache === i, none = s.id === 'none', perCall = !none && b.calls; + return '' + esc(CACHE_DESC[s.id] || '') + '' + + '' + usd(b.c) + '' + + '
' + pct(total ? b.c / total : 0) + '
' + + '' + (perCall ? fmt(b.calls) : '–') + '' + (perCall ? cu(b.c / b.calls) : '–') + '' + + '' + paidBar(b) + '' + cu(b.c) + ''; + }).join('') : 'No spend under these filters. Widen the date range or clear a filter.') + + '' + (shown.length > 1 ? 'Total' + usd(total) + '' + (mCalls ? fmt(mCalls) : '') + '' + cu(total) + '' : ''); + const W = by[idOf('warm')], X = by[idOf('expired')]; + let insight = ''; + if (W && X && W.calls && X.calls) { + const pw = W.c / W.calls, px = X.c / X.calls, r = px / pw; + insight = 'After a pause, a call cost ' + cu(px) + ' on average' + + (r >= 1.5 ? ': ' + (r >= 10 ? Math.round(r) : r.toFixed(1)) + ' times the ' + cu(pw) + ' of a call that reused the cache' : '') + + '. Those ' + plural(X.calls, 'call') + ' came to ' + credTxt(X.c) + ', ' + pct(mCredits ? X.c / mCredits : 0) + ' of the per-call metered credits shown.'; + } + $('cache-notes').innerHTML = + '
What it paid for:' + TOKEN_PARTS.concat([UNITEMISED_PART]).map((p) => '' + esc(p.label) + '').join('') + '
' + + (insight ? '

' + insight + '

' : '') + + '

Per-call telemetry, available from ' + EVENTS_FROM + '. How each call is classed is under “Where these numbers come from”.

'; + } + + /* ---------- Sessions ---------- */ + function renderSessions() { + const rows = rowsFor(null); const by = {}; + rows.forEach((f) => { + const b = by[f.s] || (by[f.s] = { c: 0, calls: 0, tok: 0, first: f.day, models: {}, t: [0, 0] }); + b.c += f.c; b.calls += f.calls; b.tok += f.ti + f.tr + f.tw + f.to; if (f.day < b.first) b.first = f.day; b.models[f.m] = (b.models[f.m] || 0) + f.c; b.t[f.tier] += f.c; + }); + const top = Object.entries(by).sort((a, b) => b[1].c - a[1].c).slice(0, 25); + const all = rows.length ? sum(rows, 'c') : 0; + $('sessions-table').innerHTML = 'SessionStartedRepositorySurfaceMain modelCredits (USD)ShareCallsTokensBasis' + + top.map(([s, b]) => { + const sess = data.sessions[s]; const mm = Object.entries(b.models).sort((a, c) => c[1] - a[1])[0]; + return '' + esc(sess.id) + '' + shortDate(b.first) + '' + esc(repoLabel(D.repos[sess.repo])) + '' + esc(D.surfaces[sess.surface]) + '' + esc(modelName(Number(mm[0]))) + (Object.keys(b.models).length > 1 ? ' +' + (Object.keys(b.models).length - 1) + '' : '') + '' + + '' + cu(b.c) + '' + pct(all ? b.c / all : 0) + '' + (b.calls ? fmt(b.calls) : '–') + '' + (b.tok ? mtok(b.tok) : '–') + '' + + '
' + b.t.map((v, i) => '').join('') + '
'; + }).join('') + (top.length ? '' : 'No sessions under these filters.') + ''; + $('sess-h').textContent = 'Heaviest sessions, ' + rangeText(F.from, F.to) + (Object.keys(by).length > top.length ? ' (top ' + top.length + ' of ' + Object.keys(by).length + ')' : ''); + } + + /* ---------- Provenance ---------- */ + (function provenance() { + const t = M.tierTotals; + $('prov-body').innerHTML = + '

Consumption, not an invoice. These totals are your usage priced at list per-token rates, taken from the telemetry the Copilot CLI and Copilot app keep on this machine. GitHub’s own billing is the source of truth; usage on other machines, in the cloud or in IDEs is not in this ledger.

' + + '
' + + '

Two kinds of number

    ' + + '
  • Metered per call (' + cu(t['0'] || 0) + '). Exact nano-AIU recorded for every model call since ' + EVENTS_FROM + ', with day, model and call type.
  • ' + + '
  • Metered per session (' + cu(t['1'] || 0) + '). Exact credits, but only as a session total from the CLI shutdown log or the Copilot app. Placed on the day the session reported (or started), split by model where the log says so.
' + + '

Where sources overlap

A session can appear in several sources. Each session takes the largest total among them, and per-call rows win where they exist. Source totals before merging: per-call ' + cu(M.sourceTotals.events) + ', CLI shutdown logs ' + cu(M.sourceTotals.shutdown) + ', Copilot app ' + cu(M.sourceTotals.app) + '.

' + + '
' + + '

Not captured

    ' + + '
  • Cloud sessions. Copilot coding agent and code review sessions run in the cloud and leave no local per-token telemetry. Per GitHub’s billing rules their credits go to the requester or the organization.
  • ' + + '
  • Other surfaces and machines. Chat in IDEs, and any machine whose sessions are not in this computer’s telemetry. Code completions do not use credits.
' + + '

Assumptions

    ' + + (M.assumedRateModels.length ? '
  • Where a session has only a total, its “what credits pay for” split uses published per-token rates; models missing from the published table (' + M.assumedRateModels.map(esc).join(', ') + ') use their nearest sibling’s rate.
  • ' : '') + + '
  • Where per-session log totals dip between resumes, the running maximum is used. That matched the Copilot app’s own totals to within 4% on sessions that have both.
  • ' + + '
  • Prompt cache: a call reused the cache when at least ' + pct(M.cacheReuseMin) + ' of the previous call’s prompt (same session, agent and model) came back as cache reads. Otherwise its prompt was written again: “Expired after a pause” when that previous call went out longer ago than the model’s cache lifetime as recorded by the Copilot CLI (' + mins(CACHE_TTL.claude) + ' for Claude, ' + mins(CACHE_TTL.other) + ' for other models), else “Rebuilt mid-conversation”. The main agent, each sub-agent and the background calls are separate conversations.
  • ' + + '
  • Everything is read locally and read-only; prompts, session titles and file paths are never read into this view.
' + + '
'; + })(); + + render(); + } +})(); diff --git a/extensions/ai-credits-breakdown/ui/index.html b/extensions/ai-credits-breakdown/ui/index.html new file mode 100644 index 000000000..9b20fb29a --- /dev/null +++ b/extensions/ai-credits-breakdown/ui/index.html @@ -0,0 +1,94 @@ + + + + + + +AI Credits Breakdown + + + + + +
+
+

AI credits GitHub Copilot

+ +
+ + +
+ +
+

Reading your local Copilot usage…

+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+

1 AI credit = 1 AIU = $0.01. Months are UTC (as GitHub bills); the hour-of-week grid uses your local time zone. Built on this computer from your local Copilot telemetry; nothing leaves it, and no prompts, session titles or file paths are read.

+
+ + + + diff --git a/extensions/ai-credits-breakdown/ui/styles.css b/extensions/ai-credits-breakdown/ui/styles.css new file mode 100644 index 000000000..3aaac33ee --- /dev/null +++ b/extensions/ai-credits-breakdown/ui/styles.css @@ -0,0 +1,331 @@ +:root { + --paper: #f1f5f8; + --sheet: #ffffff; + --ink: #0b2540; + --ink-2: #3a4f64; + --ink-3: #566b7f; + --rule: #d3dce4; + --rule-strong: #9fb1c1; + --grid: #e6edf3; + --rain: #1b64c8; + --rain-soft: #8db7ee; + --rain-wash: #e6effb; + --opus: #5e35b1; + --sonnet: #b45a00; + --gpt: #00796b; + --other: #5f7284; + --focus: #0b57d0; + --warn: #8a4b00; + --warn-wash: #fff4e0; + --radius: 3px; + --font: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; + --step--1: 0.8125rem; + --step-0: 0.875rem; + --step-1: 1rem; + --step-2: 1.125rem; + --step-3: 1.5rem; +} + +* { box-sizing: border-box; } +html { -webkit-text-size-adjust: 100%; } +body { + margin: 0; + background: var(--paper); + color: var(--ink); + font: 400 var(--step-0)/1.5 var(--font); + font-variant-numeric: tabular-nums; +} +code { font: 0.92em ui-monospace, SFMono-Regular, Menlo, monospace; background: var(--rain-wash); padding: 0.05em 0.35em; border-radius: 2px; } +a { color: var(--rain); } +button { font: inherit; color: inherit; } +:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 2px; } + +.skip { position: absolute; left: -999px; top: 0; background: var(--ink); color: #fff; padding: 0.5rem 0.75rem; z-index: 20; } +.skip:focus { left: 0.5rem; top: 0.5rem; } + +/* Masthead */ +.masthead { + background: var(--paper); + border-bottom: 2px solid var(--ink); + padding: 0.75rem clamp(1rem, 3vw, 2rem) 0; +} +@media (min-width: 901px) { + .masthead { position: sticky; top: 0; z-index: 10; box-shadow: 0 4px 12px -8px rgba(11, 37, 64, 0.3); } +} +.masthead-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } +h1 { margin: 0; font-size: var(--step-2); font-weight: 700; letter-spacing: -0.01em; } +h1 .who { font-weight: 400; color: var(--ink-3); margin-left: 0.5rem; } +.stamp { + margin: 0 0 0.75rem; + padding: 0.5rem 0.75rem; + background: var(--rain-wash); + color: var(--ink); + font-size: var(--step-0); + font-weight: 600; +} + +/* Filter bar */ +.masthead-row { align-items: flex-end; padding-bottom: 0.55rem; } +.daterange { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.5rem 0.6rem; } +.dimbar { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 0.6rem; padding: 0 0 0.7rem; } +.fld { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; } +.fld label { font-size: 0.75rem; font-weight: 600; color: var(--ink-3); } +.fld select, .fld input { + font: inherit; font-size: var(--step--1); color: var(--ink); background: var(--sheet); + border: 1px solid var(--rule-strong); border-radius: var(--radius); height: 2rem; padding: 0 0.45rem; min-width: 0; width: 100%; +} +.fld select { padding-right: 1.4rem; text-overflow: ellipsis; } +.fld-period { width: 11.5rem; } +.fld-date { width: 9.25rem; } +.fld select:hover, .fld input:hover { border-color: var(--ink-3); } +.fld.set select { border-color: var(--rain); background: var(--rain-wash); font-weight: 600; } +.btn-more { display: none; } +.factions { display: flex; gap: 0.4rem; margin-left: 0.4rem; } +.btn { + appearance: none; height: 2rem; padding: 0 0.75rem; border: 1px solid var(--rule-strong); border-radius: var(--radius); + background: var(--sheet); color: var(--ink); font-size: var(--step--1); font-weight: 600; cursor: pointer; + transition: background 120ms ease-out, border-color 120ms ease-out; +} +.btn:hover:not(:disabled) { background: var(--rain-wash); border-color: var(--rain); } +.btn:disabled { color: var(--ink-3); cursor: default; opacity: 0.7; } + +/* Page */ +main { max-width: 1440px; margin: 0 auto; padding: 1.25rem clamp(1rem, 3vw, 2rem) 2rem; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1.25rem; align-items: start; } +.span-12 { grid-column: span 12; } +.span-8 { grid-column: span 8; } +.span-4 { grid-column: span 4; } +.stack { display: grid; gap: 1.25rem; min-width: 0; } +@media (max-width: 1100px) { + .span-8, .span-4 { grid-column: span 12; } + .stack { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; } + #kinds-sheet, #anat-sheet { grid-column: span 6; } +} +.loading { color: var(--ink-3); } +[hidden] { display: none !important; } + +.statement { padding: 0.5rem 0 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem 2.5rem; align-items: end; } +@media (min-width: 1101px) { .statement { grid-template-columns: minmax(0, 1fr) minmax(20rem, 27rem); } } +.basis-h { margin: 0 0 0.35rem; font-size: var(--step--1); font-weight: 600; color: var(--ink-2); } +.statement .lead { margin: 0; font-size: clamp(1.375rem, 2.4vw + 0.7rem, 2rem); font-weight: 400; line-height: 1.25; letter-spacing: -0.015em; max-width: 40ch; text-wrap: balance; } +.statement .amt { font-size: clamp(2rem, 4.5vw + 0.8rem, 3.25rem); font-weight: 700; letter-spacing: -0.03em; color: var(--ink); white-space: nowrap; } +.statement .cr { display: block; margin-top: 0.35rem; color: var(--ink-3); font-size: var(--step--1); } +.facts { display: flex; flex-wrap: wrap; margin: 1rem 0 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); max-width: 960px; } +.facts > div { padding: 0.55rem 1rem 0.6rem 0; margin-right: 1rem; border-right: 1px solid var(--rule); } +.facts > div:last-child { border-right: 0; margin-right: 0; } +.facts dt { font-size: var(--step--1); color: var(--ink-3); } +.facts dd { margin: 0.1rem 0 0; font-size: var(--step-1); font-weight: 600; color: var(--ink); } +.chg { font-size: var(--step--1); font-weight: 600; color: var(--ink-2); white-space: nowrap; } +.statement .note { margin-top: 0.5rem; } +.filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.75rem 0 0; align-items: center; } +.filters .label { color: var(--ink-3); font-size: var(--step--1); } +.tag { + display: inline-flex; gap: 0.4rem; align-items: center; background: var(--rain-wash); border: 1px solid var(--rain-soft); + border-radius: var(--radius); padding: 0.15rem 0.3rem 0.15rem 0.55rem; font-size: var(--step--1); color: var(--ink); +} +.tag button { appearance: none; border: 0; background: transparent; cursor: pointer; padding: 0.1rem 0.4rem; color: var(--ink-2); font-size: 1rem; line-height: 1; border-radius: 2px; } +.tag button:hover { background: #fff; color: var(--ink); } +.linkbtn { appearance: none; background: transparent; border: 0; color: var(--rain); text-decoration: underline; cursor: pointer; padding: 0.15rem 0.25rem; font-size: var(--step--1); } + +.basis { max-width: 760px; } +.basis-bar { display: flex; height: 10px; border: 1px solid var(--rule-strong); background: #fff; } +.basis-bar span { display: block; height: 100%; } +.basis-key { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin-top: 0.4rem; font-size: var(--step--1); color: var(--ink-2); } +.sw { display: inline-block; width: 0.85rem; height: 0.7rem; vertical-align: -0.05rem; margin-right: 0.35rem; border: 1px solid var(--rain); } +.sw.t0 { background: var(--rain); } +.sw.t1 { background: var(--rain-soft); border-color: var(--rain-soft); } + +/* Sheets */ +.sheet { background: var(--sheet); border-top: 2px solid var(--ink); border-bottom: 1px solid var(--rule); padding: 0.9rem clamp(0.75rem, 2vw, 1.25rem) 1.1rem; min-width: 0; } +.sheet h2 { margin: 0 0 0.15rem; font-size: var(--step-1); font-weight: 700; } +.sheet .sub { margin: 0 0 0.75rem; color: var(--ink-3); font-size: var(--step--1); max-width: 70ch; } +.stack .sheet h2, #models-sheet h2, #kinds-sheet h2, #anat-sheet h2, #hours-sheet h2 { margin-bottom: 0.6rem; } +.empty { color: var(--ink-3); font-size: var(--step--1); margin: 0.25rem 0 0; } +.keybtn { appearance: none; background: transparent; border: 0; padding: 0.1rem 0.25rem; margin-left: -0.25rem; border-radius: 2px; cursor: pointer; font-size: inherit; color: inherit; } +.keybtn:hover { background: var(--rain-wash); } +.keybtn[aria-pressed="true"] { background: var(--rain-wash); outline: 1px solid var(--rain-soft); color: var(--ink); font-weight: 600; } + +/* Rain chart */ +.rain { position: relative; outline-offset: 4px; } +.rain svg { display: block; width: 100%; height: auto; overflow: visible; } +.rain text { font: 400 11px var(--font); fill: var(--ink-3); font-variant-numeric: tabular-nums; } +.rain .ax { stroke: var(--rule-strong); stroke-width: 1; } +.rain .gl { stroke: var(--grid); stroke-width: 1; } +.rain .mo { stroke: var(--rule); stroke-width: 1; stroke-dasharray: 2 3; } +.rain .cross { stroke: var(--ink); stroke-width: 1; pointer-events: none; } +.rain svg { cursor: crosshair; touch-action: pan-y; user-select: none; -webkit-user-select: none; } +.rain .brush { fill: var(--rain); fill-opacity: 0.12; stroke: var(--rain); stroke-width: 1; pointer-events: none; } +.rain .t0 { fill: var(--rain); } +.rain .t1 { fill: var(--rain-soft); } +.rain .curve { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; } +.rain .wash { fill: var(--rain-wash); } +.rain .endlabel { fill: var(--ink); font-weight: 700; } +.rain .sub { fill: var(--ink-3); font-size: 10px; } +.rain .moname { fill: var(--ink-2); font-weight: 600; } +.rain .cap { fill: var(--ink-2); font-weight: 600; } +.legend { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin-top: 0.5rem; font-size: var(--step--1); color: var(--ink-2); } + +/* Automation chart */ +.automation-summary { margin: 0 0 0.75rem; max-width: none; } +.automation-chart { min-width: 0; } +.automation-chart svg { display: block; width: 100%; height: auto; cursor: crosshair; touch-action: pan-y; } +.automation-chart text { font: 400 11px var(--font); fill: var(--ink-3); } +.automation-chart .cap { fill: var(--ink-2); font-weight: 600; } +.automation-chart .gl { stroke: var(--grid); stroke-width: 1; } +.automation-chart .cross { stroke: var(--rule-strong); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; } +.automation-line { fill: none; stroke: var(--series-color); stroke-width: 2; stroke-linejoin: round; } +.automation-series circle { fill: var(--series-color); } +.automation-series.highlighted .automation-line { stroke-width: 4; } +.automation-series.highlighted circle { r: 4; } +.automation-legend { margin-top: 0.75rem; } +.automation-legend th[scope="row"] { text-align: left; font-weight: 400; white-space: normal; min-width: 10rem; } +.automation-key { display: inline-flex; gap: 0.6rem; align-items: center; text-align: left; min-height: 2rem; } +.automation-swatch { flex: 0 0 2rem; width: 2rem; height: 0.75rem; } +.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } + +/* Tables */ +.scroll { overflow-x: auto; } +.tbl { width: 100%; border-collapse: collapse; font-size: var(--step-0); } +.tbl th, .tbl td { padding: 0.45rem 0.6rem; text-align: right; border-bottom: 1px solid var(--rule); white-space: nowrap; } +.tbl th:first-child, .tbl td:first-child, .tbl .left { text-align: left; } +.tbl thead th { font-size: var(--step--1); font-weight: 600; color: var(--ink-2); border-bottom: 1px solid var(--rule-strong); vertical-align: bottom; } +.tbl tbody tr:hover { background: #f7fafc; } +.tbl tfoot td { font-weight: 700; border-top: 2px solid var(--ink); border-bottom: 0; } +.tbl .rowbtn { appearance: none; background: transparent; border: 0; padding: 0; cursor: pointer; font-weight: 600; text-align: left; text-decoration: underline; text-decoration-color: var(--rule-strong); text-underline-offset: 3px; } +.tbl .rowbtn:hover { text-decoration-color: var(--rain); } +.tbl tr.on { background: var(--rain-wash); } +.tbl .bar { width: 9rem; min-width: 6rem; } +.repos td { padding-top: 0.5rem; padding-bottom: 0.5rem; } +.repos .rowbtn { font-weight: 600; } +.repos .owner { color: var(--ink-3); font-weight: 400; } +.repos .spent, .caches .spent { font-size: var(--step-1); font-weight: 700; color: var(--ink); } +.repos .share { width: 22%; min-width: 8rem; text-align: left; } +@media (max-width: 1499px) and (min-width: 1101px) { .repos .opt.left { display: none; } } +.sharewrap { display: flex; align-items: center; gap: 0.6rem; } +.sharebar { flex: 1; height: 10px; background: var(--grid); display: block; } +.sharebar i { display: block; height: 100%; background: var(--rain); } +.repos .pct, .caches .pct { min-width: 2.6rem; color: var(--ink-2); font-size: var(--step--1); text-align: right; } +.repos tfoot .spent, .caches tfoot .spent { font-size: var(--step-1); } + +/* Prompt cache table */ +.caches td { padding-top: 0.55rem; padding-bottom: 0.55rem; vertical-align: baseline; } +.caches .state { white-space: normal; min-width: 15rem; } +.caches .desc { display: block; margin-top: 0.15rem; max-width: 52ch; color: var(--ink-3); font-size: var(--step--1); line-height: 1.4; } +.caches .share { width: 17%; min-width: 7rem; text-align: left; } +.caches .paid { width: 19%; min-width: 11rem; } +.caches .paid .pct { min-width: 5.5rem; text-align: left; } +.caches tr.none .sharebar i { background: var(--rule-strong); } +.paidwrap { display: flex; align-items: center; gap: 0.6rem; } +.paidbar { flex: 1; display: flex; height: 10px; min-width: 5rem; border: 1px solid var(--rule-strong); background: #fff; } +.paidbar i { display: block; height: 100%; } +.cache-legend { margin-top: 0.75rem; } +.cache-legend .label { font-weight: 600; } +.hang { position: relative; height: 14px; } +.hang i { position: absolute; left: 0; top: 0; height: 100%; background: var(--rain); display: block; } +.hang i.t1 { background: var(--rain-soft); } +.mini { display: flex; width: 4.5rem; height: 8px; border: 1px solid var(--rule-strong); margin-left: auto; } +.mini i { display: block; height: 100%; } +.mini i.t0 { background: var(--rain); } +.mini i.t1 { background: var(--rain-soft); } +.muted { color: var(--ink-3); } +.partial { color: var(--ink-3); font-weight: 400; font-size: var(--step--1); } + +/* Ranked lists */ +.rank { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; } +.rank li { margin: 0; } +.rank button { + appearance: none; width: 100%; background: transparent; border: 0; text-align: left; cursor: pointer; + display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 0.15rem 0.75rem; align-items: baseline; + padding: 0.4rem 0.5rem; border-radius: var(--radius); position: relative; +} +.rank button:hover { background: #f4f8fb; } +.rank button[aria-pressed="true"] { background: var(--rain-wash); outline: 1px solid var(--rain-soft); } +.rank .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } +.rank .val { font-weight: 600; } +.rank .pct { color: var(--ink-3); min-width: 3.2rem; text-align: right; font-size: var(--step--1); } +.rank .track { grid-column: 1 / -1; height: 6px; background: var(--grid); } +.rank .track i { display: block; height: 100%; background: var(--c, var(--rain)); } +.dot { display: inline-block; width: 0.6rem; height: 0.6rem; margin-right: 0.45rem; background: var(--c, var(--rain)); border-radius: 1px; } +.fam { margin: 0.75rem 0 0; display: flex; flex-wrap: wrap; gap: 0.2rem 1rem; font-size: var(--step--1); color: var(--ink-2); } +.sub-h { margin: 1rem 0 0.35rem; font-size: var(--step--1); font-weight: 600; color: var(--ink-2); } +.more { margin-top: 0.4rem; } + +/* Anatomy */ +.anat-bar { display: flex; height: 22px; border: 1px solid var(--rule-strong); } +.anat-bar i { display: block; height: 100%; border-right: 1px solid #fff; } +.anat-bar i:last-child { border-right: 0; } +.anat-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.35rem; } +.anat-list li { display: grid; grid-template-columns: 1fr auto auto; gap: 0.75rem; align-items: baseline; } +.anat-list .why { grid-column: 1 / -1; color: var(--ink-3); font-size: var(--step--1); margin-top: -0.2rem; } +.note { color: var(--ink-3); font-size: var(--step--1); margin: 0.75rem 0 0; max-width: 62ch; } + +/* Hours */ +.hours { display: grid; grid-template-columns: 2.2rem repeat(24, minmax(0, 1fr)); gap: 2px; align-items: center; } +.hours .d, .hours .h { font-size: 0.6875rem; color: var(--ink-3); } +.hours .h { text-align: left; } +.hours .cell { aspect-ratio: 1 / 1; background: var(--grid); border-radius: 1px; } + +/* Notes */ +.notes > summary { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; } +.notes > summary::-webkit-details-marker { display: none; } +.notes > summary h2 { margin: 0; display: inline; } +.notes > summary h2::before { content: "▸"; display: inline-block; width: 1.1em; color: var(--ink-3); transition: transform 120ms ease-out; } +.notes[open] > summary h2::before { transform: rotate(90deg); } +.notes > summary .sub { margin: 0; } +.notes[open] > summary { margin-bottom: 0.9rem; } +.notes .cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem; } +.notes h3 { margin: 0 0 0.35rem; font-size: var(--step-0); } +.notes p, .notes li { max-width: 62ch; margin: 0 0 0.5rem; color: var(--ink-2); } +.notes ul { margin: 0 0 0.5rem; padding-left: 1.1rem; } +.callout { background: var(--warn-wash); border: 1px solid #e7c78f; padding: 0.7rem 0.85rem; border-radius: var(--radius); color: var(--ink); max-width: 70ch; } +.callout strong { color: var(--warn); } +.callout p { color: var(--ink); margin: 0 0 0.3rem; } +.callout p:last-child { margin: 0; } + +.foot { max-width: 1440px; margin: 0 auto; padding: 0 clamp(1rem, 3vw, 2rem) 2rem; color: var(--ink-3); font-size: var(--step--1); } + +.tip { + position: fixed; z-index: 30; pointer-events: none; background: var(--ink); color: #fff; padding: 0.5rem 0.65rem; border-radius: var(--radius); + font-size: var(--step--1); line-height: 1.4; max-width: 18rem; box-shadow: 0 4px 14px rgba(11, 37, 64, 0.25); +} +.tip b { font-weight: 700; } +.tip .row { display: flex; justify-content: space-between; gap: 1rem; } + +@media (max-width: 900px) { + .notes .cols { grid-template-columns: minmax(0, 1fr); } + .board > * { grid-column: span 12 !important; } + .stack { grid-template-columns: minmax(0, 1fr); } + .dimbar { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .factions { margin-left: 0; } + h1 .who { display: block; margin-left: 0; } + .tbl .bar, .repos .opt, .caches .opt { display: none; } + .facts > div { border-right: 0; flex: 1 1 100%; margin-right: 0; padding-right: 0; } + .facts > div + div { border-top: 1px solid var(--grid); } +} +@media (max-width: 640px) { + .masthead-row { flex-direction: column; align-items: stretch; } + .daterange { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); } + .daterange[hidden] { display: none; } + .fld-period, .fld-date { width: auto; } + .fld-period, .factions { grid-column: span 2; } + .factions .btn { flex: 1; padding: 0 0.4rem; } + .btn-more { display: inline-block; } + .btn-more[aria-expanded="true"] { background: var(--rain-wash); border-color: var(--rain); } + .dimbar { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .dimbar:not(.open) { display: none; } + .repos .share, .repos .cred, .caches .share, .caches .cred { display: none; } + .caches th, .caches td { padding-left: 0.45rem; padding-right: 0.45rem; } + .caches .state { min-width: 9.5rem; } + .repos td:first-child { white-space: normal; } + .repos .owner { display: block; font-size: var(--step--1); } +} +@media (max-width: 520px) { + body { font-size: 0.8125rem; } + .hours { grid-template-columns: 1.8rem repeat(24, minmax(0, 1fr)); gap: 1px; } +} +@media (prefers-reduced-motion: reduce) { + * { transition: none !important; } +} +@media print { + .masthead { position: static; } + .daterange, .dimbar { display: none; } +} diff --git a/plugins/ai-credits-breakdown/README.md b/plugins/ai-credits-breakdown/README.md new file mode 100644 index 000000000..1369d0f12 --- /dev/null +++ b/plugins/ai-credits-breakdown/README.md @@ -0,0 +1,22 @@ +# AI Credits Breakdown Plugin + +See exactly where your own GitHub Copilot AI credits go: dollars by day, repository, model, call type and prompt-cache state, built privately on your computer from your Copilot telemetry. + +## Requirements + +- GitHub Copilot app with canvas support +- Node 22.13+ (the extension uses `node:sqlite` and will not load on older Node versions) + +## Installation + +```bash +copilot plugin install ai-credits-breakdown@awesome-copilot +``` + +## Source + +This plugin is part of [Awesome Copilot](https://github.com/github/awesome-copilot). See the [extension README](../../extensions/ai-credits-breakdown/README.md) for what it shows and exactly what it reads. + +## License + +MIT diff --git a/plugins/ai-credits-breakdown/plugin.json b/plugins/ai-credits-breakdown/plugin.json new file mode 100644 index 000000000..9cfa59a75 --- /dev/null +++ b/plugins/ai-credits-breakdown/plugin.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "ai-credits-breakdown", + "description": "See exactly where your own GitHub Copilot AI credits go: dollars by day, repository, model, call type and prompt-cache state, built privately on your computer from your Copilot telemetry.", + "version": "1.0.0", + "author": { + "name": "Roza Jiang", + "url": "https://github.com/rozajay" + }, + "repository": "https://github.com/github/awesome-copilot", + "license": "MIT", + "keywords": [ + "ai-credits", + "canvas", + "copilot-extension", + "cost-tracking", + "token-usage", + "usage-analytics" + ], + "extensions": { + "com.github.copilot": { + "logo": "assets/preview.png" + }, + "com.github.awesome-copilot": { + "extensions": [ + "./extensions/ai-credits-breakdown" + ] + } + } +} From 49c5f10726809e0f93d3484a2afa9ac2531d1ed3 Mon Sep 17 00:00:00 2001 From: Roza Jiang Date: Thu, 29 Oct 2026 00:09:38 +1100 Subject: [PATCH 2/5] Load node:sqlite with process.getBuiltinModule Node 22's module.builtinModules omits "node:"-only modules, so the canvas smoke test, which runs on Node 22, reported a static import of node:sqlite as an undeclared dependency. process.getBuiltinModule is available from Node 22.3, below the 22.13 minimum that node:sqlite needs, and gives a clearer error on older Node versions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/ai-credits-breakdown/collector.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/ai-credits-breakdown/collector.mjs b/extensions/ai-credits-breakdown/collector.mjs index abd636dca..d93d88bf9 100644 --- a/extensions/ai-credits-breakdown/collector.mjs +++ b/extensions/ai-credits-breakdown/collector.mjs @@ -13,7 +13,13 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { DatabaseSync } from "node:sqlite"; + +// node:sqlite needs Node 22.13+. It is loaded with process.getBuiltinModule (Node 22.3+) rather than a +// static import: Node 22's module.builtinModules omits "node:"-only modules, so the repository's canvas +// smoke test, which runs on Node 22, would report a static import of it as an undeclared dependency. +const sqlite = process.getBuiltinModule?.("node:sqlite"); +if (!sqlite) throw new Error("AI Credits Breakdown needs Node 22.13 or later (it reads your usage with node:sqlite)."); +const { DatabaseSync } = sqlite; export const CREDIT_USD = 0.01; const EV_MIN_RESIDUAL = 1.0; // credits; ignore smaller disagreements between sources From ba97d8479bc99058f42a4558ac5519531fde2999 Mon Sep 17 00:00:00 2001 From: Roza Jiang Date: Thu, 29 Oct 2026 07:08:37 +1100 Subject: [PATCH 3/5] Refactor AI credits breakdown logic and UI updates Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- extensions/ai-credits-breakdown/collector.mjs | 10 +++++++- extensions/ai-credits-breakdown/ui/app.js | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/extensions/ai-credits-breakdown/collector.mjs b/extensions/ai-credits-breakdown/collector.mjs index d93d88bf9..faf21a713 100644 --- a/extensions/ai-credits-breakdown/collector.mjs +++ b/extensions/ai-credits-breakdown/collector.mjs @@ -192,8 +192,10 @@ export function loadEvents(db) { "total_nano_aiu", "initiator", "token_details_json", "created_at", "id", "agent_id", "duration_ms"]; const select = wanted.map((column) => (have.has(column) ? column : `NULL AS ${column}`)).join(", "); for (const row of db.prepare(`SELECT ${select} FROM assistant_usage_events`).iterate()) { - const startMs = Date.parse(row.created_at); + const timestamp = String(row.created_at ?? "").trim().replace(" ", "T"); + const startMs = Date.parse(/[zZ]$|[+-]\d{2}:\d{2}$/.test(timestamp) ? timestamp : `${timestamp}Z`); if (!row.session_id || !row.model || Number.isNaN(startMs)) continue; + row.created_at = new Date(startMs).toISOString(); const comps = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; const tok = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; try { @@ -209,6 +211,12 @@ export function loadEvents(db) { } } } catch { /* malformed token details: keep the call's credits without a split */ } + tok.cache_read = row.cache_read_tokens ?? tok.cache_read; + tok.cache_write = row.cache_write_tokens ?? tok.cache_write; + tok.output = row.output_tokens ?? tok.output; + if (row.input_tokens != null) { + tok.input = Math.max(0, row.input_tokens - tok.cache_read - tok.cache_write); + } nested(bySession, row.session_id, () => []).push({ model: row.model, credits: (row.total_nano_aiu || 0) / 1e9, diff --git a/extensions/ai-credits-breakdown/ui/app.js b/extensions/ai-credits-breakdown/ui/app.js index 3424bc09c..7aaa52f15 100644 --- a/extensions/ai-credits-breakdown/ui/app.js +++ b/extensions/ai-credits-breakdown/ui/app.js @@ -60,8 +60,8 @@ const modelName = (i) => D.models[i].name; const modelFam = (i) => D.models[i].family; const months = Array.from(new Set(facts.map((f) => f.mo))).sort(); - const lastMonth = months[months.length - 1]; - const START = M.periodStart, END = M.periodEnd; + const lastMonth = M.generated.slice(0, 7); + const START = M.periodStart, END = M.generated.slice(0, 10); const EVENTS_FROM = M.eventsStart ? longDate(M.eventsStart.slice(0, 10)) : 'the day the Copilot CLI began recording them'; const TZ = M.timeZone || 'UTC'; @@ -127,7 +127,13 @@ if (url !== location.pathname + location.search + location.hash) history.replaceState(null, '', url); } readHash(); - window.addEventListener('hashchange', () => { readHash(); render(); }); + window.addEventListener('hashchange', () => { + if (location.hash === '#provenance' || location.hash === '#main') { + writeHash(); + return; + } + readHash(); render(); + }); const updated = new Date(M.generated).toLocaleString(undefined, { day: 'numeric', month: 'short', year: 'numeric', @@ -511,7 +517,9 @@ } automationEl.setAttribute('tabindex', '0'); const total = series.reduce((v, a) => v + a.total, 0); - $('automations-summary').textContent = plural(series.length, 'series', 'series') + ' · ' + credTxt(total) + ' recorded · ' + rangeText(F.from, F.to) + + $('automations-summary').textContent = + (M.automationsUnavailable ? 'Automation attribution is unavailable: ' + M.automationsUnavailable + '. ' : '') + + plural(series.length, 'series', 'series') + ' · ' + credTxt(total) + ' recorded · ' + rangeText(F.from, F.to) + (total ? '' : '. No automation spend under these filters; widen the date range or clear a filter.'); const W = Math.max(240, automationEl.clientWidth), L = 72, R = 16, T = 26, PH = 240, H = T + PH + 34; const pw = W - L - R, n = days.length; @@ -658,9 +666,14 @@ let h = '' + (peak ? '

Busiest slot: ' + days[peak.d] + ' ' + String(peak.i).padStart(2, '0') + ':00, ' + credTxt(peak.v) + '. Grid covers ' + credTxt(total) + ' of per-call telemetry.

' : '

No per-call telemetry under these filters.

') + - '

' + esc(TZ) + ' time, per-call telemetry only (from ' + EVENTS_FROM + '). Darker = more credits. Follows every filter.

'; + '

' + esc(TZ) + ' time, per-call telemetry only (from ' + EVENTS_FROM + '). Darker = more credits. Follows every filter.

' + hourlyTable; } /* ---------- Prompt cache ---------- */ From 69e231530e5d2ebd134973e0e2f30bf52f19d48c Mon Sep 17 00:00:00 2001 From: Roza Jiang Date: Thu, 29 Oct 2026 07:18:43 +1100 Subject: [PATCH 4/5] Address review feedback on the AI Credits Breakdown extension Collector - Keep a call's billed token details as the token split and use the call's own counters only when it has none. The earlier change applied the counters to every call and moved about 3.4M tokens from cache writes to fresh input on real telemetry, away from what is billed. - Count a session whose whole total is one credit or less. The reconciliation tolerance now applies only against usage that is already counted, for both the per-call residual and the session-total gap. - Cache what is parsed from each session log by inode, size and modification time, read changed logs asynchronously, and drop entries for logs that have gone, so a refresh no longer rereads all history. - Look up rates, call types, surfaces and model name parts with Object.hasOwn, and keep model-keyed log data in null-prototype objects. Dashboard - Group totals by repository, owner, model and so on in null-prototype objects so an owner named "constructor" no longer renders as NaN. - Announce the day picked with the arrow keys through a polite live status linked to the daily chart. - Show per-call averages with decimals and sub-cent dollars instead of rounding a fraction of a credit to 0 ($0.00). - Rename the sessions column to "First matching usage". Adds regression tests for the UTC timestamp handling, the token fallback, small session totals, the tolerance, the log cache and inherited-property names. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/ai-credits-breakdown/collector.mjs | 143 +++++++++++------ .../ai-credits-breakdown/collector.test.mjs | 151 +++++++++++++++++- extensions/ai-credits-breakdown/ui/app.js | 40 +++-- extensions/ai-credits-breakdown/ui/index.html | 3 +- 4 files changed, 271 insertions(+), 66 deletions(-) diff --git a/extensions/ai-credits-breakdown/collector.mjs b/extensions/ai-credits-breakdown/collector.mjs index faf21a713..48fd41db1 100644 --- a/extensions/ai-credits-breakdown/collector.mjs +++ b/extensions/ai-credits-breakdown/collector.mjs @@ -104,11 +104,15 @@ export function family(model) { return "Other"; } +// Lookup in a table keyed by values read from local telemetry, which must never reach inherited +// properties such as "constructor". +const ownValue = (table, key) => (Object.hasOwn(table, key) ? table[key] : undefined); + const PRETTY = { gpt: "GPT", mai: "MAI", kimi: "Kimi", claude: "Claude" }; export function prettyModel(id) { return id.replace("-1m", " (1M)").split("-").map((part) => { const lower = part.toLowerCase(); - if (PRETTY[lower]) return PRETTY[lower]; + if (ownValue(PRETTY, lower)) return PRETTY[lower]; if (/^\d/.test(part) || part.startsWith("(")) return part; return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); }).join(" "); @@ -198,11 +202,13 @@ export function loadEvents(db) { row.created_at = new Date(startMs).toISOString(); const comps = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; const tok = { input: 0, cache_read: 0, cache_write: 0, output: 0 }; + let itemised = false; try { for (const t of JSON.parse(row.token_details_json || "[]")) { - if (!(t.tokenType in comps)) continue; + if (!Object.hasOwn(comps, t.tokenType)) continue; const rate = t.costPerBatch / t.batchSize / 1e9; // credits per token if (!Number.isFinite(rate)) continue; + itemised = true; comps[t.tokenType] += t.tokenCount * rate; tok[t.tokenType] += t.tokenCount; if (t.tokenCount > 0) { @@ -211,17 +217,20 @@ export function loadEvents(db) { } } } catch { /* malformed token details: keep the call's credits without a split */ } - tok.cache_read = row.cache_read_tokens ?? tok.cache_read; - tok.cache_write = row.cache_write_tokens ?? tok.cache_write; - tok.output = row.output_tokens ?? tok.output; - if (row.input_tokens != null) { - tok.input = Math.max(0, row.input_tokens - tok.cache_read - tok.cache_write); + // The token details are what the credits are priced from, so they win. Without them (absent, + // empty or malformed) the call's own counters still give its token counts; input_tokens includes + // the cached tokens, so fresh input is what is left after them. + if (!itemised) { + tok.cache_read = row.cache_read_tokens ?? 0; + tok.cache_write = row.cache_write_tokens ?? 0; + tok.output = row.output_tokens ?? 0; + tok.input = row.input_tokens == null ? 0 : Math.max(0, row.input_tokens - tok.cache_read - tok.cache_write); } nested(bySession, row.session_id, () => []).push({ model: row.model, credits: (row.total_nano_aiu || 0) / 1e9, comps, tok, - kind: KIND_BY_INITIATOR[row.initiator || ""] || "Background", + kind: ownValue(KIND_BY_INITIATOR, row.initiator || "") || "Background", ts: row.created_at, // created_at is when the call finished; the request went out duration_ms earlier. start: startMs / 1000 - (row.duration_ms || 0) / 1000, @@ -280,7 +289,7 @@ export function cacheStates(bySession, ttlFor) { export function buildRates(ratesSeen) { const rates = new Map(); for (const [model, byType] of ratesSeen) { - const fallback = FALLBACK_RATES[model]; + const fallback = ownValue(FALLBACK_RATES, model); rates.set(model, TOKEN_TYPES.map((type, i) => { const counter = byType.get(type); if (counter) return mode(counter); @@ -316,51 +325,83 @@ function* linesStartingWith(buffer, needle) { const SHUTDOWN_LINE = Buffer.from('{"type":"session.shutdown"'); const CHECKPOINT_LINE = Buffer.from('{"type":"session.usage_checkpoint"'); -// Per session: [timestamp, totalCredits, {model: {credits, tok, calls}}] in file order, from the CLI's -// shutdown records. Also the prompt-cache lifetimes the CLI recorded: Map>. +// What one transcript contributes: the prompt-cache lifetimes its usage checkpoints recorded, and its +// shutdown records as [timestamp, totalCredits, {model: {credits, tok, calls}}] in file order. +function parseTranscript(buffer) { + const ttls = []; + for (const line of linesStartingWith(buffer, CHECKPOINT_LINE)) { + try { + for (const state of JSON.parse(line).data.modelCacheState || []) { + if (state.modelId && state.cacheTtlSeconds) ttls.push([state.modelId, Math.trunc(Number(state.cacheTtlSeconds))]); + } + } catch { /* skip malformed checkpoint */ } + } + const seq = []; + for (const line of linesStartingWith(buffer, SHUTDOWN_LINE)) { + let event; + try { event = JSON.parse(line); } catch { continue; } + const d = event.data || {}; + const models = Object.create(null); + for (const [model, mm] of Object.entries(d.modelMetrics || {})) { + const td = mm.tokenDetails || {}; + const u = mm.usage || {}; + const tok = { + input: td.input?.tokenCount ?? 0, + cache_read: td.cache_read?.tokenCount ?? u.cacheReadTokens ?? 0, + cache_write: td.cache_write?.tokenCount ?? u.cacheWriteTokens ?? 0, + output: td.output?.tokenCount ?? u.outputTokens ?? 0, + }; + if (Object.keys(td).length === 0) { + tok.input = Math.max(0, (u.inputTokens || 0) - tok.cache_read - tok.cache_write); + } + models[model] = { credits: (mm.totalNanoAiu || 0) / 1e9, tok, calls: mm.requests?.count ?? 0 }; + } + seq.push([event.timestamp, (d.totalNanoAiu || 0) / 1e9, models]); + } + return { ttls, seq }; +} + +// Transcripts only grow, and most are finished long ago, so what was parsed from each file is kept by +// its identity (inode, size and modification time) and only changed files are read again. Entries for +// files that have gone are dropped on the next load. `reads` counts file reads, for the tests. +const transcriptCache = new Map(); // events.jsonl path -> { key, parsed } +export const transcriptStats = { reads: 0 }; + +// Per session: the shutdown records of its transcript, in file order. Also the prompt-cache lifetimes +// the CLI recorded: Map>. Files are read asynchronously and the loop yields +// regularly, so a large history does not block the extension's other work. export async function loadShutdowns(home) { const out = new Map(); const cacheTtls = new Map(); const root = path.join(home, "session-state"); let dirs = []; - try { dirs = fs.readdirSync(root); } catch { return { shutdowns: out, cacheTtls }; } + try { dirs = await fs.promises.readdir(root); } catch { return { shutdowns: out, cacheTtls }; } + const seen = new Set(); let processed = 0; for (const dir of dirs) { if (++processed % 64 === 0) await new Promise((resolve) => setImmediate(resolve)); - let buffer; - try { buffer = fs.readFileSync(path.join(root, dir, "events.jsonl")); } catch { continue; } - for (const line of linesStartingWith(buffer, CHECKPOINT_LINE)) { - try { - for (const state of JSON.parse(line).data.modelCacheState || []) { - if (state.modelId && state.cacheTtlSeconds) { - bump(nested(cacheTtls, state.modelId, () => new Map()), Math.trunc(Number(state.cacheTtlSeconds))); - } - } - } catch { /* skip malformed checkpoint */ } - } - const seq = []; - for (const line of linesStartingWith(buffer, SHUTDOWN_LINE)) { - let event; - try { event = JSON.parse(line); } catch { continue; } - const d = event.data || {}; - const models = {}; - for (const [model, mm] of Object.entries(d.modelMetrics || {})) { - const td = mm.tokenDetails || {}; - const u = mm.usage || {}; - const tok = { - input: td.input?.tokenCount ?? 0, - cache_read: td.cache_read?.tokenCount ?? u.cacheReadTokens ?? 0, - cache_write: td.cache_write?.tokenCount ?? u.cacheWriteTokens ?? 0, - output: td.output?.tokenCount ?? u.outputTokens ?? 0, - }; - if (Object.keys(td).length === 0) { - tok.input = Math.max(0, (u.inputTokens || 0) - tok.cache_read - tok.cache_write); - } - models[model] = { credits: (mm.totalNanoAiu || 0) / 1e9, tok, calls: mm.requests?.count ?? 0 }; + const file = path.join(root, dir, "events.jsonl"); + let parsed; + try { + const stat = await fs.promises.stat(file, { bigint: true }); + if (!stat.isFile()) continue; + const key = `${stat.ino}:${stat.size}:${stat.mtimeNs}`; + const cached = transcriptCache.get(file); + if (cached?.key === key) { + parsed = cached.parsed; + } else { + transcriptStats.reads++; + parsed = parseTranscript(await fs.promises.readFile(file)); + // The key is taken before the read, so a file that grew meanwhile is read again next time. + transcriptCache.set(file, { key, parsed }); } - seq.push([event.timestamp, (d.totalNanoAiu || 0) / 1e9, models]); - } - if (seq.length) out.set(dir, seq); + } catch { continue; } + seen.add(file); + for (const [model, seconds] of parsed.ttls) bump(nested(cacheTtls, model, () => new Map()), seconds); + if (parsed.seq.length) out.set(dir, parsed.seq); + } + for (const file of transcriptCache.keys()) { + if (file.startsWith(root + path.sep) && !seen.has(file)) transcriptCache.delete(file); } return { shutdowns: out, cacheTtls }; } @@ -376,7 +417,7 @@ export function shutdownIncrements(seq) { const best = new Map(); for (const [ts, total, models] of seq) { if (total <= prevTotal + 1e-9) continue; - const delta = {}; + const delta = Object.create(null); for (const [model, cur] of Object.entries(models)) { const base = best.get(model); const dc = cur.credits - (base ? base.credits : 0); @@ -452,6 +493,10 @@ function loadProjectRepos(db) { const emptyBreakdown = () => ({ credits: 0, comps: zeros(), tok: zeros(), calls: 0 }); +// Sources that count the same usage disagree a little, and that is forgiven, but only against usage +// that is already counted. A total with nothing counted yet is real usage however small it is. +const exceedsCounted = (extra, best, counted) => extra > (counted > 0 ? Math.max(EV_MIN_RESIDUAL, 0.02 * best) : 1e-9); + // Build the whole ledger. Returns a compact, column-oriented object that the dashboard filters in the browser. export async function buildLedger({ home = copilotHome(), timeZone = localTimeZone(), now = new Date() } = {}) { const store = openReadOnly(path.join(home, "session-store.db")); @@ -488,7 +533,7 @@ export async function buildLedger({ home = copilotHome(), timeZone = localTimeZo const projectRepo = appDb ? loadProjectRepos(appDb) : new Map(); for (const [sid, a] of app) { const m = nested(meta, sid, () => newMeta({ created: a.created, updated: a.updated })); - m.surface = SURFACE_BY_APP_TYPE[a.type] || "Copilot app · project session"; + m.surface = ownValue(SURFACE_BY_APP_TYPE, a.type) || "Copilot app · project session"; if (!m.repo && projectRepo.has(sid)) m.repo = projectRepo.get(sid); if (!m.created) { m.created = a.created; m.updated = a.updated; } } @@ -545,7 +590,7 @@ export async function buildLedger({ home = copilotHome(), timeZone = localTimeZo // (2) residual not covered by per-call events const residual = best - evTotal; - if (residual <= Math.max(EV_MIN_RESIDUAL, 0.02 * best)) continue; + if (!exceedsCounted(residual, best, evTotal)) continue; if (evs.length) { // Per-call events start where the CLI began recording them; anything older is dated at session creation. const evModels = new Map(); @@ -589,7 +634,7 @@ export async function buildLedger({ home = copilotHome(), timeZone = localTimeZo } } const gap = best - placed; - if (gap > Math.max(EV_MIN_RESIDUAL, 0.02 * best)) { + if (exceedsCounted(gap, best, placed)) { add(createdDay, sid, appModel, "Session total", 1, gap, zeros(), zeros(), 0); } } diff --git a/extensions/ai-credits-breakdown/collector.test.mjs b/extensions/ai-credits-breakdown/collector.test.mjs index 57f18487a..ab44274d9 100644 --- a/extensions/ai-credits-breakdown/collector.test.mjs +++ b/extensions/ai-credits-breakdown/collector.test.mjs @@ -6,7 +6,7 @@ import path from "node:path"; import { DatabaseSync } from "node:sqlite"; import { CACHE_STATES, buildLedger, buildRates, cacheStates, cacheTtlFor, createLocalClock, family, - loadAutomations, prettyModel, shutdownIncrements, summarizeLedger, + loadAutomations, prettyModel, shutdownIncrements, summarizeLedger, transcriptStats, } from "./collector.mjs"; import { SENTINELS, writeSampleHome } from "./sample-data.mjs"; @@ -315,3 +315,152 @@ test("tolerates an older per-call schema and malformed log lines", async () => { cleanup(); } }); + +// --------------------------------------------------------------------------------------------- +// Small hand-made telemetry homes +// --------------------------------------------------------------------------------------------- +const EVENT_COLUMNS = "id INTEGER, session_id TEXT, model TEXT, input_tokens INTEGER, output_tokens INTEGER, " + + "cache_read_tokens INTEGER, cache_write_tokens INTEGER, total_nano_aiu INTEGER, initiator TEXT, " + + "token_details_json TEXT, created_at TEXT, agent_id TEXT, duration_ms INTEGER"; +const details = (input, cacheRead, cacheWrite, output) => JSON.stringify([ + ["input", input], ["cache_read", cacheRead], ["cache_write", cacheWrite], ["output", output], +].map(([tokenType, tokenCount]) => ({ tokenType, tokenCount, batchSize: 1000000, costPerBatch: 1e9 }))); + +// events: [session, model, created_at, nanoAiu, columns?, detailsJson?, initiator?] +function eventsHome(home, events) { + const store = new DatabaseSync(path.join(home, "session-store.db")); + store.exec(`CREATE TABLE sessions (id TEXT, repository TEXT, created_at TEXT, updated_at TEXT); + CREATE TABLE assistant_usage_events (${EVENT_COLUMNS});`); + const insert = store.prepare("INSERT INTO assistant_usage_events VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + events.forEach(([sid, model, createdAt, nano, c = {}, json = null, initiator = "user"], i) => { + insert.run(i + 1, sid, model, c.input ?? null, c.output ?? null, c.cacheRead ?? null, c.cacheWrite ?? null, + nano, initiator, json, createdAt, "", 1000); + }); + store.close(); +} +function shutdownLine(timestamp, credits, model = "claude-sonnet-5.5") { + return JSON.stringify({ + type: "session.shutdown", timestamp, + data: { totalNanoAiu: credits * 1e9, modelMetrics: { [model]: { totalNanoAiu: credits * 1e9, requests: { count: 3 }, + tokenDetails: { input: { tokenCount: 100 }, output: { tokenCount: 10 } } } } }, + }); +} +function writeTranscript(home, sid, lines) { + fs.mkdirSync(path.join(home, "session-state", sid), { recursive: true }); + fs.writeFileSync(path.join(home, "session-state", sid, "events.jsonl"), lines.join("\n") + "\n"); +} +const sumColumn = (ledger, column) => ledger.facts.reduce((a, f) => a + f[column], 0); + +test("unzoned SQLite timestamps are UTC whatever the machine's time zone is", async () => { + const { home, cleanup } = tempHome(); + const before = process.env.TZ; + try { + eventsHome(home, [["s1", "claude-sonnet-5.5", "2026-10-28 12:34:00", 2e9]]); + process.env.TZ = "America/New_York"; // Date.parse() reads an unzoned time in this zone + const ledger = await buildLedger({ home, timeZone: "UTC" }); + assert.equal(ledger.dims.days[ledger.facts[0][0]], "2026-10-28"); + assert.equal(ledger.hourly.length, 1); + assert.deepEqual(ledger.hourly[0].slice(4, 6), [2, 12], "Wednesday 12:00 UTC, not 16:00"); + assert.equal(ledger.meta.eventsStart, "2026-10-28T12:34:00.000Z"); + } finally { + if (before === undefined) delete process.env.TZ; else process.env.TZ = before; + cleanup(); + } +}); + +test("token counts fall back to the call's own counters only when it has no usable token details", async () => { + const { home, cleanup } = tempHome(); + try { + const counters = { input: 1000, cacheRead: 600, cacheWrite: 100, output: 50 }; + eventsHome(home, [ + ["none", "claude-sonnet-5.5", "2026-10-01T10:00:00Z", 1e9, counters, null], + ["bad", "claude-sonnet-5.5", "2026-10-02T10:00:00Z", 1e9, counters, "not json"], + ["empty", "claude-sonnet-5.5", "2026-10-03T10:00:00Z", 1e9, counters, "[]"], + // The billed details win when they disagree with the counters. + ["billed", "claude-sonnet-5.5", "2026-10-04T10:00:00Z", 1e9, counters, details(250, 600, 450, 50)], + ]); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + const bySession = Object.fromEntries(ledger.facts.map((f) => [ledger.dims.days[f[0]], f.slice(10, 14)])); + assert.deepEqual(bySession["2026-10-01"], [300, 600, 100, 50], "fresh input = input minus cached tokens"); + assert.deepEqual(bySession["2026-10-02"], [300, 600, 100, 50]); + assert.deepEqual(bySession["2026-10-03"], [300, 600, 100, 50]); + assert.deepEqual(bySession["2026-10-04"], [250, 600, 450, 50]); + assert.ok(ledger.facts.every((f) => f[6] + f[7] + f[8] + f[9] <= f[5] + 1e-6)); + } finally { + cleanup(); + } +}); + +test("a session whose whole total is one credit or less is still counted", async () => { + const { home, cleanup } = tempHome(); + try { + writeTranscript(home, "shutdown-only", [shutdownLine("2026-10-05T10:00:00Z", 0.4)]); + fs.mkdirSync(home, { recursive: true }); + const app = new DatabaseSync(path.join(home, "data.db")); + app.exec(`CREATE TABLE sessions (id TEXT, title TEXT, session_type TEXT, model TEXT, created_at TEXT, updated_at TEXT, total_nano_aiu INTEGER); + INSERT INTO sessions VALUES ('app-only', NULL, 'project', 'claude-sonnet-5.5', '2026-10-06T10:00:00Z', '2026-10-06T11:00:00Z', 300000000);`); + app.close(); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + assert.equal(ledger.sessions.length, 2); + assert.ok(Math.abs(ledger.meta.total - 0.7) < 1e-6, String(ledger.meta.total)); + assert.deepEqual(ledger.dims.kinds.sort(), ["Session total"]); + } finally { + cleanup(); + } +}); + +test("the tolerance still forgives small disagreements against usage already counted", async () => { + const { home, cleanup } = tempHome(); + try { + eventsHome(home, [["s1", "claude-sonnet-5.5", "2026-10-01T10:00:00Z", 100e9]]); + writeTranscript(home, "s1", [shutdownLine("2026-10-01T11:00:00Z", 100.6)]); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + assert.ok(Math.abs(ledger.meta.total - 100) < 1e-6, "0.6 credits of disagreement is not added on top"); + assert.equal(ledger.meta.tierTotals[1], 0); + } finally { + cleanup(); + } +}); + +test("session logs are read once while they are unchanged, and again when they change or go", async () => { + const { home, cleanup } = tempHome(); + try { + writeTranscript(home, "a", [shutdownLine("2026-10-01T10:00:00Z", 5)]); + writeTranscript(home, "b", [shutdownLine("2026-10-02T10:00:00Z", 7)]); + const reads = () => transcriptStats.reads; + const start = reads(); + assert.equal((await buildLedger({ home, timeZone: "UTC" })).meta.total, 12); + assert.equal(reads() - start, 2); + assert.equal((await buildLedger({ home, timeZone: "UTC" })).meta.total, 12); + assert.equal(reads() - start, 2, "unchanged files are not read again"); + + fs.appendFileSync(path.join(home, "session-state", "a", "events.jsonl"), shutdownLine("2026-10-03T10:00:00Z", 9) + "\n"); + assert.equal((await buildLedger({ home, timeZone: "UTC" })).meta.total, 16, "the grown log is read again"); + assert.equal(reads() - start, 3); + + fs.rmSync(path.join(home, "session-state", "b"), { recursive: true }); + assert.equal((await buildLedger({ home, timeZone: "UTC" })).meta.total, 9, "a removed log no longer counts"); + assert.equal(reads() - start, 3); + } finally { + cleanup(); + } +}); + +test("values that look like inherited object properties are treated as ordinary names", async () => { + const { home, cleanup } = tempHome(); + try { + eventsHome(home, [["s1", "constructor", "2026-10-01T10:00:00Z", 3e9, {}, details(10, 0, 0, 5), "constructor"]]); + const app = new DatabaseSync(path.join(home, "data.db")); + app.exec(`CREATE TABLE sessions (id TEXT, title TEXT, session_type TEXT, model TEXT, created_at TEXT, updated_at TEXT, total_nano_aiu INTEGER); + INSERT INTO sessions VALUES ('s1', NULL, 'toString', 'constructor', '2026-10-01T10:00:00Z', '2026-10-01T11:00:00Z', 3000000000);`); + app.close(); + const ledger = await buildLedger({ home, timeZone: "UTC" }); + assert.equal(ledger.meta.total, 3); + assert.deepEqual(ledger.dims.models, [{ id: "constructor", name: "Constructor", family: "Other" }]); + assert.deepEqual(ledger.dims.kinds, ["Background"]); + assert.deepEqual(ledger.dims.surfaces, ["Copilot app · project session"]); + assert.ok(Number.isFinite(sumColumn(ledger, 5)) && Number.isFinite(sumColumn(ledger, 6))); + } finally { + cleanup(); + } +}); diff --git a/extensions/ai-credits-breakdown/ui/app.js b/extensions/ai-credits-breakdown/ui/app.js index 7aaa52f15..9ed26582d 100644 --- a/extensions/ai-credits-breakdown/ui/app.js +++ b/extensions/ai-credits-breakdown/ui/app.js @@ -11,6 +11,11 @@ }; const plural = (n, one, many) => fmt(n) + ' ' + (n === 1 ? one : (many || one + 's')); const cu = (c) => fmt(c) + ' (' + usd(c) + ')'; + // Averages per call are often a fraction of a credit, so they keep their decimals and sub-cent dollars. + const cuFine = (c) => (c >= 100 ? fmt(c) : c >= 10 ? c.toFixed(1) : c.toFixed(2)) + ' (' + + (c >= 100 ? usd(c) : '$' + (c * 0.01).toFixed(4).replace(/(\.\d\d)0+$/, '$1')) + ')'; + // Totals grouped by names that come from telemetry: a name like "constructor" must stay an ordinary key. + const dict = () => Object.create(null); const credTxt = (c) => fmt(c) + ' credits (' + usd(c) + ')'; const pct = (x) => (x >= 0.1 ? Math.round(x * 100) + '%' : x >= 0.01 ? (x * 100).toFixed(1) + '%' : x > 0 ? '<1%' : '0%'); const mtok = (n) => (n >= 1e9 ? (n / 1e9).toFixed(2) + 'B' : n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? Math.round(n / 1e3) + 'K' : String(n)); @@ -174,7 +179,7 @@ const pid = presetOf(); preset.value = pid; fromEl.value = F.from; toEl.value = F.to; DIMS.forEach((d) => { - const g = {}; rowsFor(d.key).forEach((f) => { g[f[d.key]] = (g[f[d.key]] || 0) + f.c; }); + const g = dict(); rowsFor(d.key).forEach((f) => { g[f[d.key]] = (g[f[d.key]] || 0) + f.c; }); const raw = (v) => (d.num ? Number(v) : v); const opts = Object.entries(g).map(([v, c]) => ({ v: raw(v), c })).sort((a, b) => b.c - a.c); if (F[d.key] !== null && !opts.some((o) => o.v === F[d.key])) opts.unshift({ v: F[d.key], c: 0 }); @@ -265,7 +270,7 @@ const tiers = [0, 1].map((t) => sum(rows.filter((f) => f.tier === t), 'c')); const amt = '' + usd(c) + ''; const len = spanDays(F.from, F.to); - const byDay = {}; rows.forEach((f) => { byDay[f.day] = (byDay[f.day] || 0) + f.c; }); + const byDay = dict(); rows.forEach((f) => { byDay[f.day] = (byDay[f.day] || 0) + f.c; }); const active = Object.keys(byDay).length; const peak = Object.entries(byDay).sort((a, b) => b[1] - a[1])[0]; const counts = '' + credTxt(c) + ' · ' + plural(sess, 'session') + ' · ' + plural(repos, 'repository', 'repositories') + ' · ' + plural(active, 'active day') + ' of ' + len + ''; @@ -311,13 +316,13 @@ let allRepos = false; function renderRepoSpend() { const rows = rowsFor('repo'); - const by = {}; + const by = dict(); rows.forEach((f) => { const k = f.repo || ''; - const b = by[k] || (by[k] = { c: 0, sess: new Set(), models: {} }); + const b = by[k] || (by[k] = { c: 0, sess: new Set(), models: dict() }); b.c += f.c; b.sess.add(f.s); b.models[f.m] = (b.models[f.m] || 0) + f.c; }); - const cmpP = comparison(); const cmpLabel = cmpP ? cmpP.label : null; const cmp = {}; + const cmpP = comparison(); const cmpLabel = cmpP ? cmpP.label : null; const cmp = dict(); if (cmpP) rowsBetween(cmpP.a, cmpP.b, 'repo').forEach((f) => { cmp[f.repo || ''] = (cmp[f.repo || ''] || 0) + f.c; }); const items = Object.entries(by).sort((a, b) => b[1].c - a[1].c); const total = items.reduce((a, [, b]) => a + b.c, 0); const max = items.length ? items[0][1].c : 1; @@ -362,9 +367,9 @@ function renderRain() { const rows = rowsFor(null); const days = dayList(F.from, F.to); - const idx = {}; days.forEach((d, i) => { idx[d] = i; }); + const idx = dict(); days.forEach((d, i) => { idx[d] = i; }); const per = days.map(() => [0, 0]); - const topModel = days.map(() => ({})); + const topModel = days.map(() => dict()); const sessSet = days.map(() => new Set()); rows.forEach((f) => { const i = idx[f.day]; if (i === undefined) return; @@ -444,6 +449,11 @@ (tm ? '
Top model' + esc(modelName(Number(tm[0]))) + '
' : '') + (per[1] ? '
Basis' + [per[0] && 'call ' + cu(per[0]), 'session ' + cu(per[1])].filter(Boolean).join(' · ') + '
' : ''); tip.hidden = false; + // Keyboard users get the same detail as a spoken status; pointer movement stays silent. + if (fromKey) { + $('rain-status').textContent = longDate(rainState.days[i]) + '. ' + credTxt(tot) + '. Running total ' + credTxt(rainState.cum[i]) + '. ' + + plural(rainState.sessSet[i].size, 'session') + (tm ? '. Top model ' + modelName(Number(tm[0])) : '') + '.'; + } const svg = $('rain').querySelector('svg'); const r = svg.getBoundingClientRect(); const px = r.left + (xx / g.W) * r.width; const py = r.top + 20; const tw = tip.offsetWidth; const left = Math.min(Math.max(8, px + 14), window.innerWidth - tw - 8); @@ -479,7 +489,7 @@ }); rainEl.addEventListener('pointercancel', () => { brush = null; const el = $('brush'); if (el) el.setAttribute('hidden', ''); }); rainEl.addEventListener('pointerleave', () => { if (!brush) hideDay(); }); - rainEl.addEventListener('blur', hideDay); + rainEl.addEventListener('blur', () => { hideDay(); $('rain-status').textContent = ''; }); rainEl.addEventListener('keydown', (e) => { if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; e.preventDefault(); const n = rainState.days.length; let i = rainState.active === null ? (e.key === 'ArrowLeft' ? n - 1 : 0) : rainState.active + (e.key === 'ArrowLeft' ? -1 : 1); @@ -611,12 +621,12 @@ if (rest.length) h += '

+ ' + rest.length + ' more, ' + credTxt(rest.reduce((a, i) => a + i.c, 0)) + ' combined.

'; return h; } - const groupSum = (rows, keyFn) => { const o = {}; rows.forEach((f) => { const k = keyFn(f); o[k] = (o[k] || 0) + f.c; }); return o; }; + const groupSum = (rows, keyFn) => { const o = dict(); rows.forEach((f) => { const k = keyFn(f); o[k] = (o[k] || 0) + f.c; }); return o; }; function renderModels() { const rows = rowsFor('m'); const g = groupSum(rows, (f) => f.m); const items = Object.entries(g).map(([m, c]) => ({ v: m, label: modelName(Number(m)), c, color: FAMILY_COLOR[modelFam(Number(m))] })).sort((a, b) => b.c - a.c); - const tok = {}; rows.forEach((f) => { tok[f.m] = (tok[f.m] || 0) + f.ti + f.tr + f.tw + f.to; }); + const tok = dict(); rows.forEach((f) => { tok[f.m] = (tok[f.m] || 0) + f.ti + f.tr + f.tw + f.to; }); $('models').innerHTML = rankList(items, 'm', { limit: 10 }) + '

' + Object.keys(FAMILY_COLOR).map((k) => '' + esc(k) + '').join('') + '

' + (items.length ? '

' + esc(items[0].label) + ' alone accounts for ' + pct(items[0].c / items.reduce((a, i) => a + i.c, 0)) + ' of the credits shown.

' : ''); @@ -710,7 +720,7 @@ return '' + esc(CACHE_DESC[s.id] || '') + '' + '' + usd(b.c) + '' + '
' + pct(total ? b.c / total : 0) + '
' + - '' + (perCall ? fmt(b.calls) : '–') + '' + (perCall ? cu(b.c / b.calls) : '–') + '' + + '' + (perCall ? fmt(b.calls) : '–') + '' + (perCall ? cuFine(b.c / b.calls) : '–') + '' + '' + paidBar(b) + '' + cu(b.c) + ''; }).join('') : 'No spend under these filters. Widen the date range or clear a filter.') + '' + (shown.length > 1 ? 'Total' + usd(total) + '' + (mCalls ? fmt(mCalls) : '') + '' + cu(total) + '' : ''); @@ -718,8 +728,8 @@ let insight = ''; if (W && X && W.calls && X.calls) { const pw = W.c / W.calls, px = X.c / X.calls, r = px / pw; - insight = 'After a pause, a call cost ' + cu(px) + ' on average' + - (r >= 1.5 ? ': ' + (r >= 10 ? Math.round(r) : r.toFixed(1)) + ' times the ' + cu(pw) + ' of a call that reused the cache' : '') + + insight = 'After a pause, a call cost ' + cuFine(px) + ' on average' + + (r >= 1.5 ? ': ' + (r >= 10 ? Math.round(r) : r.toFixed(1)) + ' times the ' + cuFine(pw) + ' of a call that reused the cache' : '') + '. Those ' + plural(X.calls, 'call') + ' came to ' + credTxt(X.c) + ', ' + pct(mCredits ? X.c / mCredits : 0) + ' of the per-call metered credits shown.'; } $('cache-notes').innerHTML = @@ -730,14 +740,14 @@ /* ---------- Sessions ---------- */ function renderSessions() { - const rows = rowsFor(null); const by = {}; + const rows = rowsFor(null); const by = dict(); rows.forEach((f) => { const b = by[f.s] || (by[f.s] = { c: 0, calls: 0, tok: 0, first: f.day, models: {}, t: [0, 0] }); b.c += f.c; b.calls += f.calls; b.tok += f.ti + f.tr + f.tw + f.to; if (f.day < b.first) b.first = f.day; b.models[f.m] = (b.models[f.m] || 0) + f.c; b.t[f.tier] += f.c; }); const top = Object.entries(by).sort((a, b) => b[1].c - a[1].c).slice(0, 25); const all = rows.length ? sum(rows, 'c') : 0; - $('sessions-table').innerHTML = 'SessionStartedRepositorySurfaceMain modelCredits (USD)ShareCallsTokensBasis' + + $('sessions-table').innerHTML = 'SessionFirst matching usageRepositorySurfaceMain modelCredits (USD)ShareCallsTokensBasis' + top.map(([s, b]) => { const sess = data.sessions[s]; const mm = Object.entries(b.models).sort((a, c) => c[1] - a[1])[0]; return '' + esc(sess.id) + '' + shortDate(b.first) + '' + esc(repoLabel(D.repos[sess.repo])) + '' + esc(D.surfaces[sess.surface]) + '' + esc(modelName(Number(mm[0]))) + (Object.keys(b.models).length > 1 ? ' +' + (Object.keys(b.models).length - 1) + '' : '') + '' + diff --git a/extensions/ai-credits-breakdown/ui/index.html b/extensions/ai-credits-breakdown/ui/index.html index 9b20fb29a..7a119aa8b 100644 --- a/extensions/ai-credits-breakdown/ui/index.html +++ b/extensions/ai-credits-breakdown/ui/index.html @@ -37,7 +37,8 @@

AI credits GitHub Copilot

From bba40d58bf3e63057011d0ce82c991a821fe11da Mon Sep 17 00:00:00 2001 From: Roza Jiang Date: Thu, 29 Oct 2026 08:39:21 +1100 Subject: [PATCH 5/5] Show the top 10 heaviest sessions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- extensions/ai-credits-breakdown/ui/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ai-credits-breakdown/ui/app.js b/extensions/ai-credits-breakdown/ui/app.js index 9ed26582d..54053d2df 100644 --- a/extensions/ai-credits-breakdown/ui/app.js +++ b/extensions/ai-credits-breakdown/ui/app.js @@ -745,7 +745,7 @@ const b = by[f.s] || (by[f.s] = { c: 0, calls: 0, tok: 0, first: f.day, models: {}, t: [0, 0] }); b.c += f.c; b.calls += f.calls; b.tok += f.ti + f.tr + f.tw + f.to; if (f.day < b.first) b.first = f.day; b.models[f.m] = (b.models[f.m] || 0) + f.c; b.t[f.tier] += f.c; }); - const top = Object.entries(by).sort((a, b) => b[1].c - a[1].c).slice(0, 25); + const top = Object.entries(by).sort((a, b) => b[1].c - a[1].c).slice(0, 10); const all = rows.length ? sum(rows, 'c') : 0; $('sessions-table').innerHTML = 'SessionFirst matching usageRepositorySurfaceMain modelCredits (USD)ShareCallsTokensBasis' + top.map(([s, b]) => {