rafcode 2.4.1-0 → 2.5.0-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.
Files changed (54) hide show
  1. package/CLAUDE.md +4 -4
  2. package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
  3. package/RAF/ahwqwq-model-whisperer/input.md +5 -0
  4. package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
  5. package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
  6. package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
  7. package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
  8. package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
  9. package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
  10. package/dist/commands/do.js +13 -15
  11. package/dist/commands/do.js.map +1 -1
  12. package/dist/commands/plan.d.ts.map +1 -1
  13. package/dist/commands/plan.js +92 -1
  14. package/dist/commands/plan.js.map +1 -1
  15. package/dist/core/claude-runner.d.ts +8 -0
  16. package/dist/core/claude-runner.d.ts.map +1 -1
  17. package/dist/core/claude-runner.js +72 -0
  18. package/dist/core/claude-runner.js.map +1 -1
  19. package/dist/parsers/stream-renderer.d.ts +2 -0
  20. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  21. package/dist/parsers/stream-renderer.js +2 -0
  22. package/dist/parsers/stream-renderer.js.map +1 -1
  23. package/dist/types/config.d.ts +4 -24
  24. package/dist/types/config.d.ts.map +1 -1
  25. package/dist/types/config.js +0 -24
  26. package/dist/types/config.js.map +1 -1
  27. package/dist/utils/config.d.ts +1 -26
  28. package/dist/utils/config.d.ts.map +1 -1
  29. package/dist/utils/config.js +2 -98
  30. package/dist/utils/config.js.map +1 -1
  31. package/dist/utils/terminal-symbols.d.ts +7 -16
  32. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  33. package/dist/utils/terminal-symbols.js +16 -42
  34. package/dist/utils/terminal-symbols.js.map +1 -1
  35. package/dist/utils/token-tracker.d.ts +4 -30
  36. package/dist/utils/token-tracker.d.ts.map +1 -1
  37. package/dist/utils/token-tracker.js +17 -98
  38. package/dist/utils/token-tracker.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/commands/do.ts +14 -15
  41. package/src/commands/plan.ts +107 -1
  42. package/src/core/claude-runner.ts +81 -0
  43. package/src/parsers/stream-renderer.ts +4 -0
  44. package/src/prompts/config-docs.md +1 -72
  45. package/src/types/config.ts +4 -52
  46. package/src/utils/config.ts +2 -112
  47. package/src/utils/terminal-symbols.ts +16 -46
  48. package/src/utils/token-tracker.ts +19 -113
  49. package/tests/unit/claude-runner.test.ts +1 -0
  50. package/tests/unit/config-command.test.ts +4 -13
  51. package/tests/unit/config.test.ts +6 -148
  52. package/tests/unit/stream-renderer.test.ts +82 -0
  53. package/tests/unit/terminal-symbols.test.ts +86 -124
  54. package/tests/unit/token-tracker.test.ts +159 -679
@@ -135,49 +135,12 @@ Controls the format of git commit messages. Templates use `{placeholder}` syntax
135
135
 
136
136
  Unknown placeholders are left as-is in the output.
137
137
 
138
- ### `pricing` — Token Cost Pricing
139
-
140
- Controls per-model token pricing used for cost estimation. Prices are in dollars per million tokens. Each model category (`opus`, `sonnet`, `haiku`) has four pricing fields:
141
-
142
- | Field | Description |
143
- |-------|-------------|
144
- | `inputPerMTok` | Cost per million input tokens |
145
- | `outputPerMTok` | Cost per million output tokens |
146
- | `cacheReadPerMTok` | Cost per million cache read tokens (discounted) |
147
- | `cacheCreatePerMTok` | Cost per million cache creation tokens |
148
-
149
- **Default values:**
150
-
151
- | Category | Input | Output | Cache Read | Cache Create |
152
- |----------|-------|--------|------------|--------------|
153
- | `opus` | $15 | $75 | $1.50 | $18.75 |
154
- | `sonnet` | $3 | $15 | $0.30 | $3.75 |
155
- | `haiku` | $1 | $5 | $0.10 | $1.25 |
156
-
157
- Full model IDs from CLI output (e.g., `claude-opus-4-6`) are automatically mapped to the corresponding pricing category based on the model family name.
158
-
159
- Example override:
160
-
161
- ```json
162
- {
163
- "pricing": {
164
- "opus": {
165
- "inputPerMTok": 10,
166
- "outputPerMTok": 50
167
- }
168
- }
169
- }
170
- ```
171
-
172
- Only specify the fields you want to change — unset fields keep their defaults.
173
-
174
138
  ### `display` — Token Summary Display Options
175
139
 
176
140
  Controls what information is shown in token usage summaries after tasks and in the grand total.
177
141
 
178
142
  | Key | Default | Description |
179
143
  |-----|---------|-------------|
180
- | `display.showRateLimitEstimate` | `true` | Show estimated 5h rate limit window percentage (e.g., `~42% of 5h window`) |
181
144
  | `display.showCacheTokens` | `true` | Show cache read/create token counts in summaries |
182
145
 
183
146
  Example:
@@ -185,34 +148,11 @@ Example:
185
148
  ```json
186
149
  {
187
150
  "display": {
188
- "showRateLimitEstimate": false,
189
151
  "showCacheTokens": true
190
152
  }
191
153
  }
192
154
  ```
193
155
 
194
- ### `rateLimitWindow` — Rate Limit Configuration
195
-
196
- Controls the rate limit estimation calculation.
197
-
198
- | Key | Default | Description |
199
- |-----|---------|-------------|
200
- | `rateLimitWindow.sonnetTokenCap` | `88000` | The Sonnet-equivalent token cap for the 5-hour window. All token usage is normalized to Sonnet-equivalent tokens using pricing ratios. |
201
-
202
- The 5h window percentage is calculated as: `(estimatedCost / sonnetCostPerToken) / sonnetTokenCap * 100`
203
-
204
- Where `sonnetCostPerToken` is derived from the configured Sonnet pricing. Heavier models (Opus) consume the window faster than lighter ones (Haiku) in proportion to their API pricing ratios.
205
-
206
- Example:
207
-
208
- ```json
209
- {
210
- "rateLimitWindow": {
211
- "sonnetTokenCap": 100000
212
- }
213
- }
214
- ```
215
-
216
156
  ## Validation Rules
217
157
 
218
158
  The config is validated when loaded. Invalid configs cause an error with a descriptive message. The following rules are enforced:
@@ -224,9 +164,7 @@ The config is validated when loaded. Invalid configs cause an error with a descr
224
164
  - **`maxRetries`** must be a non-negative integer.
225
165
  - **`autoCommit`**, **`worktree`**, and **`syncMainBranch`** must be booleans.
226
166
  - **`commitFormat` values** must be strings.
227
- - **`pricing`** categories must be `"opus"`, `"sonnet"`, or `"haiku"`. Each field must be a non-negative number.
228
- - **`display` values** (`showRateLimitEstimate`, `showCacheTokens`) must be booleans.
229
- - **`rateLimitWindow.sonnetTokenCap`** must be a positive number.
167
+ - **`display` values** (`showCacheTokens`) must be booleans.
230
168
  - The config file must be valid JSON containing an object (not an array or primitive).
231
169
 
232
170
  ## CLI Precedence
@@ -294,17 +232,8 @@ Uses Sonnet for planning and caps task execution at Sonnet (tasks with `effort:
294
232
  "amend": "{prefix}[{projectId}] Amend: {projectName}",
295
233
  "prefix": "RAF"
296
234
  },
297
- "pricing": {
298
- "opus": { "inputPerMTok": 15, "outputPerMTok": 75, "cacheReadPerMTok": 1.5, "cacheCreatePerMTok": 18.75 },
299
- "sonnet": { "inputPerMTok": 3, "outputPerMTok": 15, "cacheReadPerMTok": 0.3, "cacheCreatePerMTok": 3.75 },
300
- "haiku": { "inputPerMTok": 1, "outputPerMTok": 5, "cacheReadPerMTok": 0.1, "cacheCreatePerMTok": 1.25 }
301
- },
302
235
  "display": {
303
- "showRateLimitEstimate": true,
304
236
  "showCacheTokens": true
305
- },
306
- "rateLimitWindow": {
307
- "sonnetTokenCap": 88000
308
237
  }
309
238
  }
310
239
  ```
@@ -39,38 +39,12 @@ export interface CommitFormatConfig {
39
39
  prefix: string;
40
40
  }
41
41
 
42
- /** Pricing category derived from model family name. */
43
- export type PricingCategory = 'opus' | 'sonnet' | 'haiku';
44
-
45
- /** Per-direction pricing for a single model category, in dollars per million tokens. */
46
- export interface ModelPricing {
47
- inputPerMTok: number;
48
- outputPerMTok: number;
49
- cacheReadPerMTok: number;
50
- cacheCreatePerMTok: number;
51
- }
52
-
53
- /** Pricing config: per-category pricing in dollars per million tokens. */
54
- export interface PricingConfig {
55
- opus: ModelPricing;
56
- sonnet: ModelPricing;
57
- haiku: ModelPricing;
58
- }
59
-
60
42
  /** Display options for token usage summaries. */
61
43
  export interface DisplayConfig {
62
- /** Show estimated 5h rate limit window percentage. Default: true */
63
- showRateLimitEstimate: boolean;
64
44
  /** Show cache token counts in summaries. Default: true */
65
45
  showCacheTokens: boolean;
66
46
  }
67
47
 
68
- /** Rate limit window configuration. */
69
- export interface RateLimitWindowConfig {
70
- /** Sonnet-equivalent token cap for the 5h window. Default: 88000 */
71
- sonnetTokenCap: number;
72
- }
73
-
74
48
  export interface RafConfig {
75
49
  models: ModelsConfig;
76
50
  /** Maps task complexity labels (low/medium/high) to models. Used for per-task effort frontmatter. */
@@ -82,9 +56,7 @@ export interface RafConfig {
82
56
  /** Sync main branch with remote before worktree/PR operations. Default: true */
83
57
  syncMainBranch: boolean;
84
58
  commitFormat: CommitFormatConfig;
85
- pricing: PricingConfig;
86
59
  display: DisplayConfig;
87
- rateLimitWindow: RateLimitWindowConfig;
88
60
  }
89
61
 
90
62
  export const DEFAULT_CONFIG: RafConfig = {
@@ -112,33 +84,9 @@ export const DEFAULT_CONFIG: RafConfig = {
112
84
  amend: '{prefix}[{projectId}] Amend: {projectName}',
113
85
  prefix: 'RAF',
114
86
  },
115
- pricing: {
116
- opus: {
117
- inputPerMTok: 15,
118
- outputPerMTok: 75,
119
- cacheReadPerMTok: 1.5,
120
- cacheCreatePerMTok: 18.75,
121
- },
122
- sonnet: {
123
- inputPerMTok: 3,
124
- outputPerMTok: 15,
125
- cacheReadPerMTok: 0.3,
126
- cacheCreatePerMTok: 3.75,
127
- },
128
- haiku: {
129
- inputPerMTok: 1,
130
- outputPerMTok: 5,
131
- cacheReadPerMTok: 0.1,
132
- cacheCreatePerMTok: 1.25,
133
- },
134
- },
135
87
  display: {
136
- showRateLimitEstimate: true,
137
88
  showCacheTokens: true,
138
89
  },
139
- rateLimitWindow: {
140
- sonnetTokenCap: 88000,
141
- },
142
90
  };
143
91
 
144
92
  /** Deep partial type for user config files — all fields optional at every level */
@@ -201,6 +149,8 @@ export interface ModelTokenUsage {
201
149
  outputTokens: number;
202
150
  cacheReadInputTokens: number;
203
151
  cacheCreationInputTokens: number;
152
+ /** Cost in USD for this model's usage (provided by Claude CLI). */
153
+ costUsd: number;
204
154
  }
205
155
 
206
156
  /** Token usage data extracted from Claude CLI stream-json result event. */
@@ -212,4 +162,6 @@ export interface UsageData {
212
162
  cacheCreationInputTokens: number;
213
163
  /** Per-model breakdown (e.g., { "claude-opus-4-6": { ... } }). */
214
164
  modelUsage: Record<string, ModelTokenUsage>;
165
+ /** Total cost in USD for this usage (provided by Claude CLI). */
166
+ totalCostUsd: number;
215
167
  }
@@ -12,11 +12,7 @@ import {
12
12
  TaskEffortLevel,
13
13
  ModelScenario,
14
14
  CommitFormatType,
15
- PricingCategory,
16
- ModelPricing,
17
- PricingConfig,
18
15
  DisplayConfig,
19
- RateLimitWindowConfig,
20
16
  EffortMappingConfig,
21
17
  } from '../types/config.js';
22
18
 
@@ -38,12 +34,9 @@ export function getClaudeSettingsPath(): string {
38
34
 
39
35
  const VALID_TOP_LEVEL_KEYS = new Set<string>([
40
36
  'models', 'effortMapping', 'timeout', 'maxRetries', 'autoCommit',
41
- 'worktree', 'syncMainBranch', 'commitFormat', 'pricing', 'display', 'rateLimitWindow',
37
+ 'worktree', 'syncMainBranch', 'commitFormat', 'display',
42
38
  ]);
43
39
 
44
- const VALID_PRICING_CATEGORIES = new Set<string>(['opus', 'sonnet', 'haiku']);
45
- const VALID_PRICING_FIELDS = new Set<string>(['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheCreatePerMTok']);
46
-
47
40
  const VALID_MODEL_KEYS = new Set<string>([
48
41
  'plan', 'execute', 'nameGeneration', 'failureAnalysis', 'prGeneration', 'config',
49
42
  ]);
@@ -52,9 +45,7 @@ const VALID_EFFORT_MAPPING_KEYS = new Set<string>(['low', 'medium', 'high']);
52
45
 
53
46
  const VALID_COMMIT_FORMAT_KEYS = new Set<string>(['task', 'plan', 'amend', 'prefix']);
54
47
 
55
- const VALID_DISPLAY_KEYS = new Set<string>(['showRateLimitEstimate', 'showCacheTokens']);
56
-
57
- const VALID_RATE_LIMIT_WINDOW_KEYS = new Set<string>(['sonnetTokenCap']);
48
+ const VALID_DISPLAY_KEYS = new Set<string>(['showCacheTokens']);
58
49
 
59
50
  export class ConfigValidationError extends Error {
60
51
  constructor(message: string) {
@@ -167,27 +158,6 @@ export function validateConfig(config: unknown): UserConfig {
167
158
  }
168
159
  }
169
160
 
170
- // pricing
171
- if (obj.pricing !== undefined) {
172
- if (typeof obj.pricing !== 'object' || obj.pricing === null || Array.isArray(obj.pricing)) {
173
- throw new ConfigValidationError('pricing must be an object');
174
- }
175
- const pricing = obj.pricing as Record<string, unknown>;
176
- checkUnknownKeys(pricing, VALID_PRICING_CATEGORIES, 'pricing');
177
- for (const [category, catVal] of Object.entries(pricing)) {
178
- if (typeof catVal !== 'object' || catVal === null || Array.isArray(catVal)) {
179
- throw new ConfigValidationError(`pricing.${category} must be an object`);
180
- }
181
- const fields = catVal as Record<string, unknown>;
182
- checkUnknownKeys(fields, VALID_PRICING_FIELDS, `pricing.${category}`);
183
- for (const [field, val] of Object.entries(fields)) {
184
- if (typeof val !== 'number' || val < 0 || !Number.isFinite(val)) {
185
- throw new ConfigValidationError(`pricing.${category}.${field} must be a non-negative number`);
186
- }
187
- }
188
- }
189
- }
190
-
191
161
  // display
192
162
  if (obj.display !== undefined) {
193
163
  if (typeof obj.display !== 'object' || obj.display === null || Array.isArray(obj.display)) {
@@ -202,20 +172,6 @@ export function validateConfig(config: unknown): UserConfig {
202
172
  }
203
173
  }
204
174
 
205
- // rateLimitWindow
206
- if (obj.rateLimitWindow !== undefined) {
207
- if (typeof obj.rateLimitWindow !== 'object' || obj.rateLimitWindow === null || Array.isArray(obj.rateLimitWindow)) {
208
- throw new ConfigValidationError('rateLimitWindow must be an object');
209
- }
210
- const rlw = obj.rateLimitWindow as Record<string, unknown>;
211
- checkUnknownKeys(rlw, VALID_RATE_LIMIT_WINDOW_KEYS, 'rateLimitWindow');
212
- if (rlw.sonnetTokenCap !== undefined) {
213
- if (typeof rlw.sonnetTokenCap !== 'number' || rlw.sonnetTokenCap <= 0 || !Number.isFinite(rlw.sonnetTokenCap)) {
214
- throw new ConfigValidationError('rateLimitWindow.sonnetTokenCap must be a positive number');
215
- }
216
- }
217
- }
218
-
219
175
  return config as UserConfig;
220
176
  }
221
177
 
@@ -233,19 +189,9 @@ function deepMerge(defaults: RafConfig, overrides: UserConfig): RafConfig {
233
189
  if (overrides.commitFormat) {
234
190
  result.commitFormat = { ...defaults.commitFormat, ...overrides.commitFormat };
235
191
  }
236
- if (overrides.pricing) {
237
- result.pricing = {
238
- opus: { ...defaults.pricing.opus, ...overrides.pricing.opus },
239
- sonnet: { ...defaults.pricing.sonnet, ...overrides.pricing.sonnet },
240
- haiku: { ...defaults.pricing.haiku, ...overrides.pricing.haiku },
241
- };
242
- }
243
192
  if (overrides.display) {
244
193
  result.display = { ...defaults.display, ...overrides.display };
245
194
  }
246
- if (overrides.rateLimitWindow) {
247
- result.rateLimitWindow = { ...defaults.rateLimitWindow, ...overrides.rateLimitWindow };
248
- }
249
195
  if (overrides.timeout !== undefined) result.timeout = overrides.timeout;
250
196
  if (overrides.maxRetries !== undefined) result.maxRetries = overrides.maxRetries;
251
197
  if (overrides.autoCommit !== undefined) result.autoCommit = overrides.autoCommit;
@@ -271,7 +217,6 @@ export function resolveConfig(configPath?: string): RafConfig {
271
217
  effortMapping: { ...DEFAULT_CONFIG.effortMapping },
272
218
  commitFormat: { ...DEFAULT_CONFIG.commitFormat },
273
219
  display: { ...DEFAULT_CONFIG.display },
274
- rateLimitWindow: { ...DEFAULT_CONFIG.rateLimitWindow },
275
220
  };
276
221
  }
277
222
 
@@ -459,40 +404,6 @@ export function resolveFullModelId(modelName: string): string {
459
404
  return modelName;
460
405
  }
461
406
 
462
- /**
463
- * Map a full model ID (e.g., `claude-opus-4-6`) or short alias to a pricing category.
464
- * Returns null if the model cannot be mapped.
465
- */
466
- export function resolveModelPricingCategory(modelId: string): PricingCategory | null {
467
- // Short aliases map directly
468
- if (modelId === 'opus' || modelId === 'sonnet' || modelId === 'haiku') {
469
- return modelId;
470
- }
471
- // Full model IDs: extract family from `claude-{family}-{version}`
472
- const match = modelId.match(/^claude-([a-z]+)-/);
473
- if (match) {
474
- const family = match[1];
475
- if (family === 'opus' || family === 'sonnet' || family === 'haiku') {
476
- return family;
477
- }
478
- }
479
- return null;
480
- }
481
-
482
- /**
483
- * Get pricing config for a specific model category.
484
- */
485
- export function getPricing(category: PricingCategory): ModelPricing {
486
- return getResolvedConfig().pricing[category];
487
- }
488
-
489
- /**
490
- * Get the full pricing config.
491
- */
492
- export function getPricingConfig(): PricingConfig {
493
- return getResolvedConfig().pricing;
494
- }
495
-
496
407
  /**
497
408
  * Get the full display config.
498
409
  */
@@ -500,20 +411,6 @@ export function getDisplayConfig(): DisplayConfig {
500
411
  return getResolvedConfig().display;
501
412
  }
502
413
 
503
- /**
504
- * Get the full rate limit window config.
505
- */
506
- export function getRateLimitWindowConfig(): RateLimitWindowConfig {
507
- return getResolvedConfig().rateLimitWindow;
508
- }
509
-
510
- /**
511
- * Get whether to show rate limit estimate in token summaries.
512
- */
513
- export function getShowRateLimitEstimate(): boolean {
514
- return getResolvedConfig().display.showRateLimitEstimate;
515
- }
516
-
517
414
  /**
518
415
  * Get whether to show cache tokens in summaries.
519
416
  */
@@ -521,13 +418,6 @@ export function getShowCacheTokens(): boolean {
521
418
  return getResolvedConfig().display.showCacheTokens;
522
419
  }
523
420
 
524
- /**
525
- * Get the Sonnet-equivalent token cap for the 5h rate limit window.
526
- */
527
- export function getSonnetTokenCap(): number {
528
- return getResolvedConfig().rateLimitWindow.sonnetTokenCap;
529
- }
530
-
531
421
  /**
532
422
  * Render a commit message template by replacing {placeholder} tokens with values.
533
423
  * Unknown placeholders are left as-is.
@@ -11,10 +11,6 @@ import type { CostBreakdown, TaskUsageEntry } from './token-tracker.js';
11
11
  export interface TokenSummaryOptions {
12
12
  /** Whether to show cache token counts. Default: true */
13
13
  showCacheTokens?: boolean;
14
- /** Whether to show rate limit percentage. Default: true */
15
- showRateLimitEstimate?: boolean;
16
- /** Rate limit percentage to display (requires showRateLimitEstimate: true) */
17
- rateLimitPercentage?: number;
18
14
  }
19
15
 
20
16
  /**
@@ -49,7 +45,8 @@ function truncate(str: string, maxLength: number): string {
49
45
  * @param name - Task name
50
46
  * @param elapsedMs - Optional elapsed time in milliseconds
51
47
  * @param taskId - Optional task ID prefix display
52
- * @returns Formatted string like "● 001-auth-login 1:23" or " 001-auth-login 1/5"
48
+ * @param model - Optional model short name to display (e.g., "sonnet", "opus", "haiku")
49
+ * @returns Formatted string like "● 001-auth-login (sonnet) 1:23" or "✓ 001-auth-login (opus) 1/5"
53
50
  */
54
51
  export function formatTaskProgress(
55
52
  current: number,
@@ -57,19 +54,21 @@ export function formatTaskProgress(
57
54
  status: TaskStatus,
58
55
  name: string,
59
56
  elapsedMs?: number,
60
- taskId?: string
57
+ taskId?: string,
58
+ model?: string
61
59
  ): string {
62
60
  const symbol = SYMBOLS[status];
63
61
  const displayName = truncate(name || 'task', 40);
64
62
  const idPrefix = taskId ? `${taskId}-` : '';
63
+ const modelSuffix = model ? ` (${model})` : '';
65
64
 
66
65
  // Show elapsed time for running tasks, completed tasks, and failed tasks
67
66
  if (elapsedMs !== undefined) {
68
67
  const timeStr = formatElapsedTime(elapsedMs);
69
- return `${symbol} ${idPrefix}${displayName} ${timeStr}`;
68
+ return `${symbol} ${idPrefix}${displayName}${modelSuffix} ${timeStr}`;
70
69
  }
71
70
 
72
- return `${symbol} ${idPrefix}${displayName} ${current}/${total}`;
71
+ return `${symbol} ${idPrefix}${displayName}${modelSuffix} ${current}/${total}`;
73
72
  }
74
73
 
75
74
  /**
@@ -155,17 +154,6 @@ export function formatCost(cost: number): string {
155
154
  return `$${cost.toFixed(2)}`;
156
155
  }
157
156
 
158
- /**
159
- * Formats a rate limit percentage for display.
160
- * Uses tilde (~) prefix to indicate estimate.
161
- */
162
- export function formatRateLimitPercentage(percentage: number): string {
163
- if (percentage === 0) return '~0% of 5h window';
164
- if (percentage < 0.1) return `~${percentage.toFixed(2)}% of 5h window`;
165
- if (percentage < 1) return `~${percentage.toFixed(1)}% of 5h window`;
166
- return `~${Math.round(percentage)}% of 5h window`;
167
- }
168
-
169
157
  /**
170
158
  * Formats a single line of token usage (for a single attempt or total).
171
159
  * Used internally by formatTaskTokenSummary.
@@ -177,7 +165,7 @@ function formatTokenLine(
177
165
  indent: string = ' ',
178
166
  options: TokenSummaryOptions = {}
179
167
  ): string {
180
- const { showCacheTokens = true, showRateLimitEstimate = true, rateLimitPercentage } = options;
168
+ const { showCacheTokens = true } = options;
181
169
  const parts: string[] = [];
182
170
  const tokenPart = `${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`;
183
171
  parts.push(prefix ? `${prefix}: ${tokenPart}` : `Tokens: ${tokenPart}`);
@@ -195,27 +183,21 @@ function formatTokenLine(
195
183
  }
196
184
  }
197
185
 
198
- parts.push(`Est. cost: ${formatCost(costValue)}`);
199
-
200
- if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
201
- parts.push(formatRateLimitPercentage(rateLimitPercentage));
202
- }
186
+ parts.push(`Cost: ${formatCost(costValue)}`);
203
187
 
204
188
  return `${indent}${parts.join(' | ')}`;
205
189
  }
206
190
 
207
191
  /**
208
192
  * Formats a per-task token usage summary.
209
- * For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42 | ~2% of 5h window"
193
+ * For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Cost: $0.42"
210
194
  * For multi-attempt tasks: shows per-attempt breakdown plus total.
211
195
  *
212
196
  * @param entry - The TaskUsageEntry containing accumulated usage, cost, and attempts array
213
- * @param calculateAttemptCost - Optional function to calculate cost for a single attempt's UsageData
214
- * @param options - Display options for showing cache tokens and rate limit percentage
197
+ * @param options - Display options for showing cache tokens
215
198
  */
216
199
  export function formatTaskTokenSummary(
217
200
  entry: TaskUsageEntry,
218
- calculateAttemptCost?: (usage: UsageData) => CostBreakdown,
219
201
  options: TokenSummaryOptions = {}
220
202
  ): string {
221
203
  // Single-attempt: render exactly as before (no per-attempt breakdown)
@@ -224,19 +206,10 @@ export function formatTaskTokenSummary(
224
206
  }
225
207
 
226
208
  // Multi-attempt: show per-attempt lines plus total
227
- // Per-attempt lines don't show rate limit (only show on total)
228
- const perAttemptOptions: TokenSummaryOptions = {
229
- ...options,
230
- showRateLimitEstimate: false,
231
- rateLimitPercentage: undefined,
232
- };
233
-
234
209
  const lines: string[] = [];
235
210
  entry.attempts.forEach((attemptUsage, i) => {
236
- const attemptCost = calculateAttemptCost
237
- ? calculateAttemptCost(attemptUsage).totalCost
238
- : 0;
239
- lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' ', perAttemptOptions));
211
+ const attemptCost = attemptUsage.totalCostUsd;
212
+ lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' ', options));
240
213
  });
241
214
  lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' ', options));
242
215
  return lines.join('\n');
@@ -248,14 +221,14 @@ export function formatTaskTokenSummary(
248
221
  *
249
222
  * @param usage - Total usage data
250
223
  * @param cost - Total cost breakdown
251
- * @param options - Display options for cache tokens and rate limit
224
+ * @param options - Display options for cache tokens
252
225
  */
253
226
  export function formatTokenTotalSummary(
254
227
  usage: UsageData,
255
228
  cost: CostBreakdown,
256
229
  options: TokenSummaryOptions = {}
257
230
  ): string {
258
- const { showCacheTokens = true, showRateLimitEstimate = true, rateLimitPercentage } = options;
231
+ const { showCacheTokens = true } = options;
259
232
  const lines: string[] = [];
260
233
  const divider = '── Token Usage Summary ──────────────────';
261
234
  lines.push(divider);
@@ -272,11 +245,8 @@ export function formatTokenTotalSummary(
272
245
  lines.push(`Cache: ${cacheParts.join(' / ')}`);
273
246
  }
274
247
 
275
- lines.push(`Estimated cost: ${formatCost(cost.totalCost)}`);
248
+ lines.push(`Total cost: ${formatCost(cost.totalCost)}`);
276
249
 
277
- if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
278
- lines.push(formatRateLimitPercentage(rateLimitPercentage));
279
- }
280
250
  lines.push('─────────────────────────────────────────');
281
251
  return lines.join('\n');
282
252
  }