@@ -9,6 +9,95 @@ import { getGitHubHeaders } from "./github.js";
99 */
1010const COMMENT_MARKER = "<!-- dalestudy-learning-status -->" ;
1111
12+ /**
13+ * Hidden marker for embedding cumulative usage data in the comment.
14+ * Format: <!-- usage-data: {"prompt":N,"completion":N,"requests":N} -->
15+ */
16+ const USAGE_DATA_RE = / < ! - - u s a g e - d a t a : ( { .* ?} ) - - > / ;
17+
18+ /** gpt-4.1-nano pricing (USD per token) */
19+ const INPUT_COST_PER_TOKEN = 0.10 / 1_000_000 ;
20+ const OUTPUT_COST_PER_TOKEN = 0.40 / 1_000_000 ;
21+
22+ /**
23+ * Calculates cost in USD from token counts.
24+ *
25+ * @param {number } promptTokens
26+ * @param {number } completionTokens
27+ * @returns {number }
28+ */
29+ function calcCost ( promptTokens , completionTokens ) {
30+ return promptTokens * INPUT_COST_PER_TOKEN + completionTokens * OUTPUT_COST_PER_TOKEN ;
31+ }
32+
33+ /**
34+ * Formats a number with thousand-separating commas.
35+ *
36+ * @param {number } n
37+ * @returns {string }
38+ */
39+ function fmt ( n ) {
40+ return n . toLocaleString ( "en-US" ) ;
41+ }
42+
43+ /**
44+ * Parses per-request usage history embedded in an existing comment body.
45+ *
46+ * @param {string|undefined } body
47+ * @returns {Array<{ prompt: number, completion: number }> }
48+ */
49+ function parseUsageFromComment ( body ) {
50+ if ( ! body ) return [ ] ;
51+ const match = body . match ( USAGE_DATA_RE ) ;
52+ if ( ! match ) return [ ] ;
53+ try {
54+ const parsed = JSON . parse ( match [ 1 ] ) ;
55+ return Array . isArray ( parsed ) ? parsed : [ ] ;
56+ } catch {
57+ return [ ] ;
58+ }
59+ }
60+
61+ /**
62+ * Builds the usage footer section (per-request history table + hidden data marker).
63+ *
64+ * @param {Array<{ prompt: number, completion: number }> } history - all requests including current (latest last)
65+ * @returns {string }
66+ */
67+ function formatUsageSection ( history ) {
68+ const lines = [ ] ;
69+ lines . push ( "<details>" ) ;
70+ lines . push ( "<summary>🔢 API 사용량 (gpt-4.1-nano)</summary>" ) ;
71+ lines . push ( "" ) ;
72+ lines . push ( "| 요청 | 입력 토큰 | 출력 토큰 | 합계 | 비용 |" ) ;
73+ lines . push ( "|---:|---:|---:|---:|---:|" ) ;
74+
75+ let totalPrompt = 0 ;
76+ let totalCompletion = 0 ;
77+
78+ for ( let i = 0 ; i < history . length ; i ++ ) {
79+ const { prompt, completion } = history [ i ] ;
80+ const total = prompt + completion ;
81+ const cost = calcCost ( prompt , completion ) ;
82+ lines . push ( `| #${ i + 1 } | ${ fmt ( prompt ) } | ${ fmt ( completion ) } | ${ fmt ( total ) } | $${ cost . toFixed ( 6 ) } |` ) ;
83+ totalPrompt += prompt ;
84+ totalCompletion += completion ;
85+ }
86+
87+ if ( history . length > 1 ) {
88+ const totalCost = calcCost ( totalPrompt , totalCompletion ) ;
89+ lines . push ( `| **합계** | **${ fmt ( totalPrompt ) } ** | **${ fmt ( totalCompletion ) } ** | **${ fmt ( totalPrompt + totalCompletion ) } ** | **$${ totalCost . toFixed ( 6 ) } ** |` ) ;
90+ }
91+
92+ lines . push ( "" ) ;
93+ lines . push ( "</details>" ) ;
94+
95+ // Embed history for future parsing
96+ lines . push ( `<!-- usage-data: ${ JSON . stringify ( history ) } -->` ) ;
97+
98+ return lines . join ( "\n" ) ;
99+ }
100+
12101/**
13102 * Returns true when `comment` is a Bot comment containing our marker.
14103 *
@@ -129,19 +218,24 @@ export function formatLearningStatusComment(
129218 * Searches through the first 100 issue comments for an existing Bot comment
130219 * that contains COMMENT_MARKER. If found, PATCHes it; otherwise POSTs a new one.
131220 *
221+ * When `currentUsage` is provided, appends a collapsible usage/cost section to
222+ * the comment and accumulates it with any previously stored cumulative totals.
223+ *
132224 * @param {string } repoOwner
133225 * @param {string } repoName
134226 * @param {number } prNumber
135227 * @param {string } commentBody
136228 * @param {string } appToken
229+ * @param {{ prompt_tokens: number, completion_tokens: number }|null } [currentUsage]
137230 * @returns {Promise<void> }
138231 */
139232export async function upsertLearningStatusComment (
140233 repoOwner ,
141234 repoName ,
142235 prNumber ,
143236 commentBody ,
144- appToken
237+ appToken ,
238+ currentUsage = null
145239) {
146240 const baseUrl = `https://api.github.com/repos/${ repoOwner } /${ repoName } ` ;
147241
@@ -160,6 +254,16 @@ export async function upsertLearningStatusComment(
160254 const comments = await listResponse . json ( ) ;
161255 const existing = comments . find ( isLearningStatusComment ) ;
162256
257+ // Append usage section when token data is available
258+ if ( currentUsage ) {
259+ const prevHistory = parseUsageFromComment ( existing ?. body ) ;
260+ const history = [
261+ ...prevHistory ,
262+ { prompt : currentUsage . prompt_tokens , completion : currentUsage . completion_tokens } ,
263+ ] ;
264+ commentBody = commentBody . trimEnd ( ) + "\n\n" + formatUsageSection ( history ) + "\n" ;
265+ }
266+
163267 if ( existing ) {
164268 // Update existing comment
165269 console . log (
0 commit comments