rafcode 2.4.1-0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +4 -4
- package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
- package/RAF/ahwqwq-model-whisperer/input.md +5 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
- package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
- package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
- package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
- package/RAF/ahwvrz-legacy-sunset/decisions.md +10 -0
- package/RAF/ahwvrz-legacy-sunset/input.md +10 -0
- package/RAF/ahwvrz-legacy-sunset/outcomes/01-remove-migrate-command.md +30 -0
- package/RAF/ahwvrz-legacy-sunset/outcomes/02-fix-resume-worktree-resolution.md +62 -0
- package/RAF/ahwvrz-legacy-sunset/plans/01-remove-migrate-command.md +65 -0
- package/RAF/ahwvrz-legacy-sunset/plans/02-fix-resume-worktree-resolution.md +72 -0
- package/README.md +0 -17
- package/dist/commands/do.js +13 -15
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +98 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +8 -0
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +72 -0
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +2 -0
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +2 -0
- package/dist/parsers/stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +3 -1
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +4 -1
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +4 -28
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +0 -24
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +1 -26
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +2 -98
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +7 -16
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +16 -42
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +4 -30
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +17 -98
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/do.ts +14 -15
- package/src/commands/plan.ts +112 -1
- package/src/core/claude-runner.ts +81 -0
- package/src/index.ts +0 -2
- package/src/parsers/stream-renderer.ts +4 -0
- package/src/prompts/amend.ts +3 -1
- package/src/prompts/config-docs.md +1 -72
- package/src/prompts/planning.ts +4 -1
- package/src/types/config.ts +4 -57
- package/src/utils/config.ts +2 -112
- package/src/utils/terminal-symbols.ts +16 -46
- package/src/utils/token-tracker.ts +19 -113
- package/tests/unit/claude-runner.test.ts +1 -0
- package/tests/unit/config-command.test.ts +4 -13
- package/tests/unit/config.test.ts +6 -148
- package/tests/unit/plan-resume-worktree-resolution.test.ts +153 -0
- package/tests/unit/stream-renderer.test.ts +82 -0
- package/tests/unit/terminal-symbols.test.ts +86 -124
- package/tests/unit/token-tracker.test.ts +159 -679
- package/src/commands/migrate.ts +0 -269
- package/tests/unit/migrate-command.test.ts +0 -197
|
@@ -267,6 +267,7 @@ describe('renderStreamEvent', () => {
|
|
|
267
267
|
outputTokens: 500,
|
|
268
268
|
cacheReadInputTokens: 200,
|
|
269
269
|
cacheCreationInputTokens: 100,
|
|
270
|
+
costUsd: 0, // costUSD is extracted from modelUsage data
|
|
270
271
|
});
|
|
271
272
|
});
|
|
272
273
|
|
|
@@ -318,6 +319,87 @@ describe('renderStreamEvent', () => {
|
|
|
318
319
|
expect(result.usageData!.modelUsage['claude-opus-4-6'].inputTokens).toBe(1500);
|
|
319
320
|
expect(result.usageData!.modelUsage['claude-haiku-4-5-20251001'].inputTokens).toBe(500);
|
|
320
321
|
});
|
|
322
|
+
|
|
323
|
+
it('should extract total_cost_usd from result event', () => {
|
|
324
|
+
const line = JSON.stringify({
|
|
325
|
+
type: 'result',
|
|
326
|
+
subtype: 'success',
|
|
327
|
+
result: 'Done',
|
|
328
|
+
usage: {
|
|
329
|
+
input_tokens: 1000,
|
|
330
|
+
output_tokens: 500,
|
|
331
|
+
},
|
|
332
|
+
total_cost_usd: 18.5,
|
|
333
|
+
});
|
|
334
|
+
const result = renderStreamEvent(line);
|
|
335
|
+
expect(result.usageData).toBeDefined();
|
|
336
|
+
expect(result.usageData!.totalCostUsd).toBe(18.5);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should default totalCostUsd to 0 when no cost provided', () => {
|
|
340
|
+
const line = JSON.stringify({
|
|
341
|
+
type: 'result',
|
|
342
|
+
subtype: 'success',
|
|
343
|
+
result: 'Done',
|
|
344
|
+
usage: {
|
|
345
|
+
input_tokens: 1000,
|
|
346
|
+
output_tokens: 500,
|
|
347
|
+
},
|
|
348
|
+
// No cost field - costUSD is in modelUsage, not at top level
|
|
349
|
+
});
|
|
350
|
+
const result = renderStreamEvent(line);
|
|
351
|
+
expect(result.usageData).toBeDefined();
|
|
352
|
+
expect(result.usageData!.totalCostUsd).toBe(0);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should prioritize total_cost_usd over costUSD', () => {
|
|
356
|
+
const line = JSON.stringify({
|
|
357
|
+
type: 'result',
|
|
358
|
+
subtype: 'success',
|
|
359
|
+
result: 'Done',
|
|
360
|
+
usage: {
|
|
361
|
+
input_tokens: 1000,
|
|
362
|
+
output_tokens: 500,
|
|
363
|
+
},
|
|
364
|
+
total_cost_usd: 18.5,
|
|
365
|
+
costUSD: 20.0,
|
|
366
|
+
});
|
|
367
|
+
const result = renderStreamEvent(line);
|
|
368
|
+
expect(result.usageData).toBeDefined();
|
|
369
|
+
expect(result.usageData!.totalCostUsd).toBe(18.5);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should handle missing cost fields gracefully', () => {
|
|
373
|
+
const line = JSON.stringify({
|
|
374
|
+
type: 'result',
|
|
375
|
+
subtype: 'success',
|
|
376
|
+
result: 'Done',
|
|
377
|
+
usage: {
|
|
378
|
+
input_tokens: 1000,
|
|
379
|
+
output_tokens: 500,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
const result = renderStreamEvent(line);
|
|
383
|
+
expect(result.usageData).toBeDefined();
|
|
384
|
+
// When no cost is provided, defaults to 0
|
|
385
|
+
expect(result.usageData!.totalCostUsd).toBe(0);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should extract zero cost correctly', () => {
|
|
389
|
+
const line = JSON.stringify({
|
|
390
|
+
type: 'result',
|
|
391
|
+
subtype: 'success',
|
|
392
|
+
result: 'Done',
|
|
393
|
+
usage: {
|
|
394
|
+
input_tokens: 0,
|
|
395
|
+
output_tokens: 0,
|
|
396
|
+
},
|
|
397
|
+
total_cost_usd: 0,
|
|
398
|
+
});
|
|
399
|
+
const result = renderStreamEvent(line);
|
|
400
|
+
expect(result.usageData).toBeDefined();
|
|
401
|
+
expect(result.usageData!.totalCostUsd).toBe(0);
|
|
402
|
+
});
|
|
321
403
|
});
|
|
322
404
|
|
|
323
405
|
describe('edge cases', () => {
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
formatCost,
|
|
9
9
|
formatTaskTokenSummary,
|
|
10
10
|
formatTokenTotalSummary,
|
|
11
|
-
formatRateLimitPercentage,
|
|
12
11
|
TokenSummaryOptions,
|
|
13
12
|
TaskStatus,
|
|
14
13
|
} from '../../src/utils/terminal-symbols.js';
|
|
@@ -110,6 +109,61 @@ describe('Terminal Symbols', () => {
|
|
|
110
109
|
const result = formatTaskProgress(3, 5, 'failed', 'deploy', 45000, '003');
|
|
111
110
|
expect(result).toBe('✗ 003-deploy 45s');
|
|
112
111
|
});
|
|
112
|
+
|
|
113
|
+
it('should show model name in parentheses for running task with time', () => {
|
|
114
|
+
const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, undefined, 'sonnet');
|
|
115
|
+
expect(result).toBe('● auth-login (sonnet) 1m 23s');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should show model name in parentheses for running task without time', () => {
|
|
119
|
+
const result = formatTaskProgress(1, 5, 'running', 'auth-login', undefined, undefined, 'opus');
|
|
120
|
+
expect(result).toBe('● auth-login (opus) 1/5');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should show model name in parentheses for completed task with time', () => {
|
|
124
|
+
const result = formatTaskProgress(3, 5, 'completed', 'setup-db', 154000, undefined, 'haiku');
|
|
125
|
+
expect(result).toBe('✓ setup-db (haiku) 2m 34s');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should show model name in parentheses for completed task without time', () => {
|
|
129
|
+
const result = formatTaskProgress(3, 5, 'completed', 'setup-db', undefined, undefined, 'sonnet');
|
|
130
|
+
expect(result).toBe('✓ setup-db (sonnet) 3/5');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should show model name in parentheses for failed task with time', () => {
|
|
134
|
+
const result = formatTaskProgress(2, 5, 'failed', 'deploy', 45000, undefined, 'opus');
|
|
135
|
+
expect(result).toBe('✗ deploy (opus) 45s');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should show model name in parentheses for failed task without time', () => {
|
|
139
|
+
const result = formatTaskProgress(2, 5, 'failed', 'deploy', undefined, undefined, 'haiku');
|
|
140
|
+
expect(result).toBe('✗ deploy (haiku) 2/5');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should show model name with task ID prefix', () => {
|
|
144
|
+
const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, '001', 'sonnet');
|
|
145
|
+
expect(result).toBe('● 001-auth-login (sonnet) 1m 23s');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should show model name for blocked task without time', () => {
|
|
149
|
+
const result = formatTaskProgress(2, 5, 'blocked', 'depends-on-failed', undefined, undefined, 'sonnet');
|
|
150
|
+
expect(result).toBe('⊘ depends-on-failed (sonnet) 2/5');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should show model name for pending task', () => {
|
|
154
|
+
const result = formatTaskProgress(4, 5, 'pending', 'cleanup', undefined, undefined, 'haiku');
|
|
155
|
+
expect(result).toBe('○ cleanup (haiku) 4/5');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should not show model name when model is undefined', () => {
|
|
159
|
+
const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, undefined, undefined);
|
|
160
|
+
expect(result).toBe('● auth-login 1m 23s');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle model name with task ID and time', () => {
|
|
164
|
+
const result = formatTaskProgress(2, 5, 'completed', 'setup-db', 154000, '002', 'opus');
|
|
165
|
+
expect(result).toBe('✓ 002-setup-db (opus) 2m 34s');
|
|
166
|
+
});
|
|
113
167
|
});
|
|
114
168
|
|
|
115
169
|
describe('formatProjectHeader', () => {
|
|
@@ -291,10 +345,6 @@ describe('Terminal Symbols', () => {
|
|
|
291
345
|
});
|
|
292
346
|
|
|
293
347
|
const makeCost = (total: number): CostBreakdown => ({
|
|
294
|
-
inputCost: 0,
|
|
295
|
-
outputCost: 0,
|
|
296
|
-
cacheReadCost: 0,
|
|
297
|
-
cacheCreateCost: 0,
|
|
298
348
|
totalCost: total,
|
|
299
349
|
});
|
|
300
350
|
|
|
@@ -309,67 +359,59 @@ describe('Terminal Symbols', () => {
|
|
|
309
359
|
it('should format basic token summary without cache', () => {
|
|
310
360
|
const usage = makeUsage();
|
|
311
361
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42)));
|
|
312
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out |
|
|
362
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.42');
|
|
313
363
|
});
|
|
314
364
|
|
|
315
365
|
it('should include cache read tokens', () => {
|
|
316
366
|
const usage = makeUsage({ cacheReadInputTokens: 18500 });
|
|
317
367
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42)));
|
|
318
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read |
|
|
368
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Cost: $0.42');
|
|
319
369
|
});
|
|
320
370
|
|
|
321
371
|
it('should include cache creation tokens', () => {
|
|
322
372
|
const usage = makeUsage({ cacheCreationInputTokens: 5000 });
|
|
323
373
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.55)));
|
|
324
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 5,000 created |
|
|
374
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 5,000 created | Cost: $0.55');
|
|
325
375
|
});
|
|
326
376
|
|
|
327
377
|
it('should include both cache read and creation tokens', () => {
|
|
328
378
|
const usage = makeUsage({ cacheReadInputTokens: 18500, cacheCreationInputTokens: 5000 });
|
|
329
379
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.75)));
|
|
330
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read / 5,000 created |
|
|
380
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read / 5,000 created | Cost: $0.75');
|
|
331
381
|
});
|
|
332
382
|
|
|
333
383
|
it('should format small costs with 4 decimal places', () => {
|
|
334
384
|
const usage = makeUsage();
|
|
335
385
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.0042)));
|
|
336
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out |
|
|
386
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.0042');
|
|
337
387
|
});
|
|
338
388
|
|
|
339
389
|
it('should format single-attempt task with empty attempts array as single-line', () => {
|
|
340
390
|
const usage = makeUsage();
|
|
341
391
|
const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42), []));
|
|
342
|
-
expect(result).toBe(' Tokens: 5,234 in / 1,023 out |
|
|
392
|
+
expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.42');
|
|
343
393
|
});
|
|
344
394
|
});
|
|
345
395
|
|
|
346
396
|
describe('multi-attempt tasks', () => {
|
|
347
|
-
it('should show per-attempt breakdown with costs
|
|
348
|
-
const attempt1 = makeUsage({ inputTokens: 1234, outputTokens: 567 });
|
|
349
|
-
const attempt2 = makeUsage({ inputTokens: 2345, outputTokens: 890 });
|
|
397
|
+
it('should show per-attempt breakdown with Claude-provided costs', () => {
|
|
398
|
+
const attempt1 = makeUsage({ inputTokens: 1234, outputTokens: 567, totalCostUsd: 0.02 });
|
|
399
|
+
const attempt2 = makeUsage({ inputTokens: 2345, outputTokens: 890, totalCostUsd: 0.04 });
|
|
350
400
|
const totalUsage = makeUsage({ inputTokens: 3579, outputTokens: 1457 });
|
|
351
401
|
const entry = makeEntry(totalUsage, makeCost(0.06), [attempt1, attempt2]);
|
|
352
402
|
|
|
353
|
-
const
|
|
354
|
-
inputCost: 0,
|
|
355
|
-
outputCost: 0,
|
|
356
|
-
cacheReadCost: 0,
|
|
357
|
-
cacheCreateCost: 0,
|
|
358
|
-
totalCost: usage.inputTokens === 1234 ? 0.02 : 0.04,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const result = formatTaskTokenSummary(entry, calculateCost);
|
|
403
|
+
const result = formatTaskTokenSummary(entry);
|
|
362
404
|
const lines = result.split('\n');
|
|
363
405
|
|
|
364
406
|
expect(lines).toHaveLength(3);
|
|
365
|
-
expect(lines[0]).toBe(' Attempt 1: 1,234 in / 567 out |
|
|
366
|
-
expect(lines[1]).toBe(' Attempt 2: 2,345 in / 890 out |
|
|
367
|
-
expect(lines[2]).toBe(' Total: 3,579 in / 1,457 out |
|
|
407
|
+
expect(lines[0]).toBe(' Attempt 1: 1,234 in / 567 out | Cost: $0.02');
|
|
408
|
+
expect(lines[1]).toBe(' Attempt 2: 2,345 in / 890 out | Cost: $0.04');
|
|
409
|
+
expect(lines[2]).toBe(' Total: 3,579 in / 1,457 out | Cost: $0.06');
|
|
368
410
|
});
|
|
369
411
|
|
|
370
|
-
it('should show per-attempt breakdown
|
|
371
|
-
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200 });
|
|
372
|
-
const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400 });
|
|
412
|
+
it('should show per-attempt breakdown with zero costs', () => {
|
|
413
|
+
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, totalCostUsd: 0 });
|
|
414
|
+
const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400, totalCostUsd: 0 });
|
|
373
415
|
const totalUsage = makeUsage({ inputTokens: 3000, outputTokens: 600 });
|
|
374
416
|
const entry = makeEntry(totalUsage, makeCost(0.05), [attempt1, attempt2]);
|
|
375
417
|
|
|
@@ -377,14 +419,14 @@ describe('Terminal Symbols', () => {
|
|
|
377
419
|
const lines = result.split('\n');
|
|
378
420
|
|
|
379
421
|
expect(lines).toHaveLength(3);
|
|
380
|
-
expect(lines[0]).toBe(' Attempt 1: 1,000 in / 200 out |
|
|
381
|
-
expect(lines[1]).toBe(' Attempt 2: 2,000 in / 400 out |
|
|
382
|
-
expect(lines[2]).toBe(' Total: 3,000 in / 600 out |
|
|
422
|
+
expect(lines[0]).toBe(' Attempt 1: 1,000 in / 200 out | Cost: $0.00');
|
|
423
|
+
expect(lines[1]).toBe(' Attempt 2: 2,000 in / 400 out | Cost: $0.00');
|
|
424
|
+
expect(lines[2]).toBe(' Total: 3,000 in / 600 out | Cost: $0.05');
|
|
383
425
|
});
|
|
384
426
|
|
|
385
427
|
it('should include cache tokens in per-attempt breakdown', () => {
|
|
386
|
-
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000 });
|
|
387
|
-
const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000 });
|
|
428
|
+
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000, totalCostUsd: 0.04 });
|
|
429
|
+
const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000, totalCostUsd: 0.04 });
|
|
388
430
|
const totalUsage = makeUsage({
|
|
389
431
|
inputTokens: 2500,
|
|
390
432
|
outputTokens: 500,
|
|
@@ -403,9 +445,9 @@ describe('Terminal Symbols', () => {
|
|
|
403
445
|
});
|
|
404
446
|
|
|
405
447
|
it('should handle three or more attempts', () => {
|
|
406
|
-
const attempt1 = makeUsage({ inputTokens: 500, outputTokens: 100 });
|
|
407
|
-
const attempt2 = makeUsage({ inputTokens: 600, outputTokens: 120 });
|
|
408
|
-
const attempt3 = makeUsage({ inputTokens: 700, outputTokens: 140 });
|
|
448
|
+
const attempt1 = makeUsage({ inputTokens: 500, outputTokens: 100, totalCostUsd: 0.03 });
|
|
449
|
+
const attempt2 = makeUsage({ inputTokens: 600, outputTokens: 120, totalCostUsd: 0.03 });
|
|
450
|
+
const attempt3 = makeUsage({ inputTokens: 700, outputTokens: 140, totalCostUsd: 0.04 });
|
|
409
451
|
const totalUsage = makeUsage({ inputTokens: 1800, outputTokens: 360 });
|
|
410
452
|
const entry = makeEntry(totalUsage, makeCost(0.10), [attempt1, attempt2, attempt3]);
|
|
411
453
|
|
|
@@ -428,14 +470,11 @@ describe('Terminal Symbols', () => {
|
|
|
428
470
|
cacheReadInputTokens: 0,
|
|
429
471
|
cacheCreationInputTokens: 0,
|
|
430
472
|
modelUsage: {},
|
|
473
|
+
totalCostUsd: 0,
|
|
431
474
|
...overrides,
|
|
432
475
|
});
|
|
433
476
|
|
|
434
477
|
const makeCost = (total: number): CostBreakdown => ({
|
|
435
|
-
inputCost: 0,
|
|
436
|
-
outputCost: 0,
|
|
437
|
-
cacheReadCost: 0,
|
|
438
|
-
cacheCreateCost: 0,
|
|
439
478
|
totalCost: total,
|
|
440
479
|
});
|
|
441
480
|
|
|
@@ -443,7 +482,7 @@ describe('Terminal Symbols', () => {
|
|
|
443
482
|
const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75));
|
|
444
483
|
expect(result).toContain('Token Usage Summary');
|
|
445
484
|
expect(result).toContain('Total tokens: 45,678 in / 12,345 out');
|
|
446
|
-
expect(result).toContain('
|
|
485
|
+
expect(result).toContain('Total cost: $3.75');
|
|
447
486
|
expect(result).not.toContain('Cache:');
|
|
448
487
|
});
|
|
449
488
|
|
|
@@ -478,24 +517,6 @@ describe('Terminal Symbols', () => {
|
|
|
478
517
|
expect(lines[lines.length - 1]).toContain('──');
|
|
479
518
|
});
|
|
480
519
|
|
|
481
|
-
it('should include rate limit percentage when option enabled', () => {
|
|
482
|
-
const options: TokenSummaryOptions = {
|
|
483
|
-
showRateLimitEstimate: true,
|
|
484
|
-
rateLimitPercentage: 42.5,
|
|
485
|
-
};
|
|
486
|
-
const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75), options);
|
|
487
|
-
expect(result).toContain('~43% of 5h window');
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it('should not include rate limit when option disabled', () => {
|
|
491
|
-
const options: TokenSummaryOptions = {
|
|
492
|
-
showRateLimitEstimate: false,
|
|
493
|
-
rateLimitPercentage: 42.5,
|
|
494
|
-
};
|
|
495
|
-
const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75), options);
|
|
496
|
-
expect(result).not.toContain('5h window');
|
|
497
|
-
});
|
|
498
|
-
|
|
499
520
|
it('should hide cache tokens when option disabled', () => {
|
|
500
521
|
const options: TokenSummaryOptions = {
|
|
501
522
|
showCacheTokens: false,
|
|
@@ -509,30 +530,6 @@ describe('Terminal Symbols', () => {
|
|
|
509
530
|
});
|
|
510
531
|
});
|
|
511
532
|
|
|
512
|
-
describe('formatRateLimitPercentage', () => {
|
|
513
|
-
it('should format zero percentage', () => {
|
|
514
|
-
expect(formatRateLimitPercentage(0)).toBe('~0% of 5h window');
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it('should format very small percentages with 2 decimals', () => {
|
|
518
|
-
expect(formatRateLimitPercentage(0.05)).toBe('~0.05% of 5h window');
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('should format small percentages with 1 decimal', () => {
|
|
522
|
-
expect(formatRateLimitPercentage(0.5)).toBe('~0.5% of 5h window');
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
it('should round percentages >= 1', () => {
|
|
526
|
-
expect(formatRateLimitPercentage(1.5)).toBe('~2% of 5h window');
|
|
527
|
-
expect(formatRateLimitPercentage(42.3)).toBe('~42% of 5h window');
|
|
528
|
-
expect(formatRateLimitPercentage(100)).toBe('~100% of 5h window');
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
it('should handle percentages over 100%', () => {
|
|
532
|
-
expect(formatRateLimitPercentage(150.7)).toBe('~151% of 5h window');
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
|
|
536
533
|
describe('formatTaskTokenSummary with options', () => {
|
|
537
534
|
const makeUsage = (overrides: Partial<UsageData> = {}): UsageData => ({
|
|
538
535
|
inputTokens: 5234,
|
|
@@ -544,10 +541,6 @@ describe('Terminal Symbols', () => {
|
|
|
544
541
|
});
|
|
545
542
|
|
|
546
543
|
const makeCost = (total: number): CostBreakdown => ({
|
|
547
|
-
inputCost: 0,
|
|
548
|
-
outputCost: 0,
|
|
549
|
-
cacheReadCost: 0,
|
|
550
|
-
cacheCreateCost: 0,
|
|
551
544
|
totalCost: total,
|
|
552
545
|
});
|
|
553
546
|
|
|
@@ -558,51 +551,20 @@ describe('Terminal Symbols', () => {
|
|
|
558
551
|
attempts: attempts ?? [usage],
|
|
559
552
|
});
|
|
560
553
|
|
|
561
|
-
it('should include rate limit percentage in single-attempt summary', () => {
|
|
562
|
-
const usage = makeUsage({ cacheReadInputTokens: 18500 });
|
|
563
|
-
const entry = makeEntry(usage, makeCost(0.42));
|
|
564
|
-
const options: TokenSummaryOptions = {
|
|
565
|
-
showRateLimitEstimate: true,
|
|
566
|
-
rateLimitPercentage: 2.5,
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
const result = formatTaskTokenSummary(entry, undefined, options);
|
|
570
|
-
expect(result).toContain('~3% of 5h window');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
554
|
it('should hide cache tokens in single-attempt summary when disabled', () => {
|
|
574
|
-
const usage = makeUsage({ cacheReadInputTokens: 18500 });
|
|
555
|
+
const usage = makeUsage({ cacheReadInputTokens: 18500, totalCostUsd: 0.42 });
|
|
575
556
|
const entry = makeEntry(usage, makeCost(0.42));
|
|
576
557
|
const options: TokenSummaryOptions = {
|
|
577
558
|
showCacheTokens: false,
|
|
578
559
|
};
|
|
579
560
|
|
|
580
|
-
const result = formatTaskTokenSummary(entry,
|
|
561
|
+
const result = formatTaskTokenSummary(entry, options);
|
|
581
562
|
expect(result).not.toContain('Cache:');
|
|
582
563
|
});
|
|
583
564
|
|
|
584
|
-
it('should only show rate limit on total line in multi-attempt summary', () => {
|
|
585
|
-
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200 });
|
|
586
|
-
const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400 });
|
|
587
|
-
const totalUsage = makeUsage({ inputTokens: 3000, outputTokens: 600 });
|
|
588
|
-
const entry = makeEntry(totalUsage, makeCost(0.05), [attempt1, attempt2]);
|
|
589
|
-
const options: TokenSummaryOptions = {
|
|
590
|
-
showRateLimitEstimate: true,
|
|
591
|
-
rateLimitPercentage: 1.5,
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
const result = formatTaskTokenSummary(entry, undefined, options);
|
|
595
|
-
const lines = result.split('\n');
|
|
596
|
-
|
|
597
|
-
// Rate limit should only appear on the Total line
|
|
598
|
-
expect(lines[0]).not.toContain('5h window'); // Attempt 1
|
|
599
|
-
expect(lines[1]).not.toContain('5h window'); // Attempt 2
|
|
600
|
-
expect(lines[2]).toContain('~2% of 5h window'); // Total
|
|
601
|
-
});
|
|
602
|
-
|
|
603
565
|
it('should respect showCacheTokens in multi-attempt summary', () => {
|
|
604
|
-
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000 });
|
|
605
|
-
const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000 });
|
|
566
|
+
const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000, totalCostUsd: 0.04 });
|
|
567
|
+
const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000, totalCostUsd: 0.04 });
|
|
606
568
|
const totalUsage = makeUsage({
|
|
607
569
|
inputTokens: 2500,
|
|
608
570
|
outputTokens: 500,
|
|
@@ -614,7 +576,7 @@ describe('Terminal Symbols', () => {
|
|
|
614
576
|
showCacheTokens: false,
|
|
615
577
|
};
|
|
616
578
|
|
|
617
|
-
const result = formatTaskTokenSummary(entry,
|
|
579
|
+
const result = formatTaskTokenSummary(entry, options);
|
|
618
580
|
expect(result).not.toContain('Cache:');
|
|
619
581
|
});
|
|
620
582
|
});
|