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.
- 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/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 +92 -1
- 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/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/types/config.d.ts +4 -24
- 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 +107 -1
- package/src/core/claude-runner.ts +81 -0
- package/src/parsers/stream-renderer.ts +4 -0
- package/src/prompts/config-docs.md +1 -72
- package/src/types/config.ts +4 -52
- 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/stream-renderer.test.ts +82 -0
- package/tests/unit/terminal-symbols.test.ts +86 -124
- package/tests/unit/token-tracker.test.ts +159 -679
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-symbols.js","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal-symbols.js","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { UsageData
|
|
1
|
+
import { UsageData } from '../types/config.js';
|
|
2
2
|
/** Cost breakdown for a single task or accumulated total. */
|
|
3
3
|
export interface CostBreakdown {
|
|
4
|
-
inputCost: number;
|
|
5
|
-
outputCost: number;
|
|
6
|
-
cacheReadCost: number;
|
|
7
|
-
cacheCreateCost: number;
|
|
8
4
|
totalCost: number;
|
|
9
5
|
}
|
|
10
6
|
/** Per-task usage snapshot stored by the tracker. */
|
|
@@ -27,18 +23,15 @@ export declare function sumCostBreakdowns(costs: CostBreakdown[]): CostBreakdown
|
|
|
27
23
|
*/
|
|
28
24
|
export declare function accumulateUsage(attempts: UsageData[]): UsageData;
|
|
29
25
|
/**
|
|
30
|
-
* Accumulates token usage across multiple task executions
|
|
31
|
-
* using configurable per-model pricing.
|
|
26
|
+
* Accumulates token usage across multiple task executions using Claude-provided cost data.
|
|
32
27
|
*/
|
|
33
28
|
export declare class TokenTracker {
|
|
34
29
|
private entries;
|
|
35
|
-
|
|
36
|
-
constructor(pricingConfig?: PricingConfig);
|
|
30
|
+
constructor();
|
|
37
31
|
/**
|
|
38
32
|
* Record usage data from a completed task.
|
|
39
33
|
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
40
|
-
*
|
|
41
|
-
* have modelUsage and others only have aggregate fields.
|
|
34
|
+
* Costs are summed from Claude-provided totalCostUsd values.
|
|
42
35
|
*/
|
|
43
36
|
addTask(taskId: string, attempts: UsageData[]): TaskUsageEntry;
|
|
44
37
|
/**
|
|
@@ -52,24 +45,5 @@ export declare class TokenTracker {
|
|
|
52
45
|
usage: UsageData;
|
|
53
46
|
cost: CostBreakdown;
|
|
54
47
|
};
|
|
55
|
-
/**
|
|
56
|
-
* Calculate cost for a given UsageData using per-model pricing.
|
|
57
|
-
* Uses per-model breakdown when available, falls back to aggregate with sonnet pricing.
|
|
58
|
-
*/
|
|
59
|
-
calculateCost(usage: UsageData): CostBreakdown;
|
|
60
|
-
/**
|
|
61
|
-
* Calculate the 5h rate limit window percentage for a given cost.
|
|
62
|
-
* Converts cost to Sonnet-equivalent tokens using the configured Sonnet pricing,
|
|
63
|
-
* then divides by the configured cap.
|
|
64
|
-
*
|
|
65
|
-
* @param totalCost - The total cost in dollars
|
|
66
|
-
* @param sonnetTokenCap - Optional override for the Sonnet-equivalent token cap (defaults to config value)
|
|
67
|
-
* @returns The percentage of the 5h window consumed (0-100+)
|
|
68
|
-
*/
|
|
69
|
-
calculateRateLimitPercentage(totalCost: number, sonnetTokenCap?: number): number;
|
|
70
|
-
/**
|
|
71
|
-
* Get the cumulative 5h window percentage across all recorded tasks.
|
|
72
|
-
*/
|
|
73
|
-
getCumulativeRateLimitPercentage(sonnetTokenCap?: number): number;
|
|
74
48
|
}
|
|
75
49
|
//# sourceMappingURL=token-tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,KAAK,EAAE,SAAS,CAAC;IACjB,4CAA4C;IAC5C,IAAI,EAAE,aAAa,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,aAAa,CAMvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,CAmChE;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAwB;;IAMvC;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,cAAc;IAU9D;;OAEG;IACH,UAAU,IAAI,SAAS,cAAc,EAAE;IAIvC;;OAEG;IACH,SAAS,IAAI;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE;CAwCvD"}
|
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
import { resolveModelPricingCategory, getPricingConfig, getRateLimitWindowConfig } from './config.js';
|
|
2
1
|
/**
|
|
3
2
|
* Sum multiple CostBreakdown objects into a single total.
|
|
4
3
|
*/
|
|
5
4
|
export function sumCostBreakdowns(costs) {
|
|
6
|
-
|
|
7
|
-
inputCost: 0,
|
|
8
|
-
outputCost: 0,
|
|
9
|
-
cacheReadCost: 0,
|
|
10
|
-
cacheCreateCost: 0,
|
|
11
|
-
totalCost: 0,
|
|
12
|
-
};
|
|
5
|
+
let totalCost = 0;
|
|
13
6
|
for (const cost of costs) {
|
|
14
|
-
|
|
15
|
-
result.outputCost += cost.outputCost;
|
|
16
|
-
result.cacheReadCost += cost.cacheReadCost;
|
|
17
|
-
result.cacheCreateCost += cost.cacheCreateCost;
|
|
18
|
-
result.totalCost += cost.totalCost;
|
|
7
|
+
totalCost += cost.totalCost;
|
|
19
8
|
}
|
|
20
|
-
return
|
|
9
|
+
return { totalCost };
|
|
21
10
|
}
|
|
22
11
|
/**
|
|
23
12
|
* Merge multiple UsageData objects into a single accumulated UsageData.
|
|
@@ -30,6 +19,7 @@ export function accumulateUsage(attempts) {
|
|
|
30
19
|
cacheReadInputTokens: 0,
|
|
31
20
|
cacheCreationInputTokens: 0,
|
|
32
21
|
modelUsage: {},
|
|
22
|
+
totalCostUsd: 0,
|
|
33
23
|
};
|
|
34
24
|
for (const attempt of attempts) {
|
|
35
25
|
result.inputTokens += attempt.inputTokens;
|
|
@@ -44,36 +34,35 @@ export function accumulateUsage(attempts) {
|
|
|
44
34
|
existing.outputTokens += modelUsage.outputTokens;
|
|
45
35
|
existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
|
|
46
36
|
existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
|
|
37
|
+
existing.costUsd += modelUsage.costUsd;
|
|
47
38
|
}
|
|
48
39
|
else {
|
|
49
40
|
result.modelUsage[modelId] = { ...modelUsage };
|
|
50
41
|
}
|
|
51
42
|
}
|
|
43
|
+
// Sum totalCostUsd across attempts
|
|
44
|
+
result.totalCostUsd += attempt.totalCostUsd;
|
|
52
45
|
}
|
|
53
46
|
return result;
|
|
54
47
|
}
|
|
55
48
|
/**
|
|
56
|
-
* Accumulates token usage across multiple task executions
|
|
57
|
-
* using configurable per-model pricing.
|
|
49
|
+
* Accumulates token usage across multiple task executions using Claude-provided cost data.
|
|
58
50
|
*/
|
|
59
51
|
export class TokenTracker {
|
|
60
52
|
entries = [];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.pricingConfig = pricingConfig ?? getPricingConfig();
|
|
53
|
+
constructor() {
|
|
54
|
+
// No pricing config needed - costs come from Claude
|
|
64
55
|
}
|
|
65
56
|
/**
|
|
66
57
|
* Record usage data from a completed task.
|
|
67
58
|
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
68
|
-
*
|
|
69
|
-
* have modelUsage and others only have aggregate fields.
|
|
59
|
+
* Costs are summed from Claude-provided totalCostUsd values.
|
|
70
60
|
*/
|
|
71
61
|
addTask(taskId, attempts) {
|
|
72
62
|
const usage = accumulateUsage(attempts);
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const cost = sumCostBreakdowns(perAttemptCosts);
|
|
63
|
+
// Sum costs from Claude-provided totalCostUsd
|
|
64
|
+
const totalCost = attempts.reduce((sum, attempt) => sum + attempt.totalCostUsd, 0);
|
|
65
|
+
const cost = { totalCost };
|
|
77
66
|
const entry = { taskId, usage, cost, attempts };
|
|
78
67
|
this.entries.push(entry);
|
|
79
68
|
return entry;
|
|
@@ -94,12 +83,9 @@ export class TokenTracker {
|
|
|
94
83
|
cacheReadInputTokens: 0,
|
|
95
84
|
cacheCreationInputTokens: 0,
|
|
96
85
|
modelUsage: {},
|
|
86
|
+
totalCostUsd: 0,
|
|
97
87
|
};
|
|
98
88
|
const totalCost = {
|
|
99
|
-
inputCost: 0,
|
|
100
|
-
outputCost: 0,
|
|
101
|
-
cacheReadCost: 0,
|
|
102
|
-
cacheCreateCost: 0,
|
|
103
89
|
totalCost: 0,
|
|
104
90
|
};
|
|
105
91
|
for (const entry of this.entries) {
|
|
@@ -107,6 +93,7 @@ export class TokenTracker {
|
|
|
107
93
|
totalUsage.outputTokens += entry.usage.outputTokens;
|
|
108
94
|
totalUsage.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
|
|
109
95
|
totalUsage.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
|
|
96
|
+
totalUsage.totalCostUsd += entry.usage.totalCostUsd;
|
|
110
97
|
// Merge per-model usage
|
|
111
98
|
for (const [modelId, modelUsage] of Object.entries(entry.usage.modelUsage)) {
|
|
112
99
|
const existing = totalUsage.modelUsage[modelId];
|
|
@@ -115,83 +102,15 @@ export class TokenTracker {
|
|
|
115
102
|
existing.outputTokens += modelUsage.outputTokens;
|
|
116
103
|
existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
|
|
117
104
|
existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
|
|
105
|
+
existing.costUsd += modelUsage.costUsd;
|
|
118
106
|
}
|
|
119
107
|
else {
|
|
120
108
|
totalUsage.modelUsage[modelId] = { ...modelUsage };
|
|
121
109
|
}
|
|
122
110
|
}
|
|
123
|
-
totalCost.inputCost += entry.cost.inputCost;
|
|
124
|
-
totalCost.outputCost += entry.cost.outputCost;
|
|
125
|
-
totalCost.cacheReadCost += entry.cost.cacheReadCost;
|
|
126
|
-
totalCost.cacheCreateCost += entry.cost.cacheCreateCost;
|
|
127
111
|
totalCost.totalCost += entry.cost.totalCost;
|
|
128
112
|
}
|
|
129
113
|
return { usage: totalUsage, cost: totalCost };
|
|
130
114
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Calculate cost for a given UsageData using per-model pricing.
|
|
133
|
-
* Uses per-model breakdown when available, falls back to aggregate with sonnet pricing.
|
|
134
|
-
*/
|
|
135
|
-
calculateCost(usage) {
|
|
136
|
-
const result = {
|
|
137
|
-
inputCost: 0,
|
|
138
|
-
outputCost: 0,
|
|
139
|
-
cacheReadCost: 0,
|
|
140
|
-
cacheCreateCost: 0,
|
|
141
|
-
totalCost: 0,
|
|
142
|
-
};
|
|
143
|
-
const modelEntries = Object.entries(usage.modelUsage);
|
|
144
|
-
if (modelEntries.length > 0) {
|
|
145
|
-
// Use per-model breakdown for accurate pricing
|
|
146
|
-
for (const [modelId, modelUsage] of modelEntries) {
|
|
147
|
-
const category = resolveModelPricingCategory(modelId);
|
|
148
|
-
const pricing = this.pricingConfig[category ?? 'sonnet'];
|
|
149
|
-
result.inputCost += (modelUsage.inputTokens / 1_000_000) * pricing.inputPerMTok;
|
|
150
|
-
result.outputCost += (modelUsage.outputTokens / 1_000_000) * pricing.outputPerMTok;
|
|
151
|
-
result.cacheReadCost += (modelUsage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
|
|
152
|
-
result.cacheCreateCost += (modelUsage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// Fallback: use aggregate totals with sonnet pricing
|
|
157
|
-
const pricing = this.pricingConfig.sonnet;
|
|
158
|
-
result.inputCost = (usage.inputTokens / 1_000_000) * pricing.inputPerMTok;
|
|
159
|
-
result.outputCost = (usage.outputTokens / 1_000_000) * pricing.outputPerMTok;
|
|
160
|
-
result.cacheReadCost = (usage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
|
|
161
|
-
result.cacheCreateCost = (usage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
|
|
162
|
-
}
|
|
163
|
-
result.totalCost = result.inputCost + result.outputCost + result.cacheReadCost + result.cacheCreateCost;
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Calculate the 5h rate limit window percentage for a given cost.
|
|
168
|
-
* Converts cost to Sonnet-equivalent tokens using the configured Sonnet pricing,
|
|
169
|
-
* then divides by the configured cap.
|
|
170
|
-
*
|
|
171
|
-
* @param totalCost - The total cost in dollars
|
|
172
|
-
* @param sonnetTokenCap - Optional override for the Sonnet-equivalent token cap (defaults to config value)
|
|
173
|
-
* @returns The percentage of the 5h window consumed (0-100+)
|
|
174
|
-
*/
|
|
175
|
-
calculateRateLimitPercentage(totalCost, sonnetTokenCap) {
|
|
176
|
-
if (totalCost === 0)
|
|
177
|
-
return 0;
|
|
178
|
-
// Get the configured cap or use the provided override
|
|
179
|
-
const cap = sonnetTokenCap ?? getRateLimitWindowConfig().sonnetTokenCap;
|
|
180
|
-
// Calculate the average Sonnet cost per token
|
|
181
|
-
// Using the average of input and output pricing (simplified approach)
|
|
182
|
-
const sonnetPricing = this.pricingConfig.sonnet;
|
|
183
|
-
const avgSonnetCostPerToken = (sonnetPricing.inputPerMTok + sonnetPricing.outputPerMTok) / 2 / 1_000_000;
|
|
184
|
-
// Convert cost to Sonnet-equivalent tokens
|
|
185
|
-
const sonnetEquivalentTokens = totalCost / avgSonnetCostPerToken;
|
|
186
|
-
// Calculate percentage
|
|
187
|
-
return (sonnetEquivalentTokens / cap) * 100;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Get the cumulative 5h window percentage across all recorded tasks.
|
|
191
|
-
*/
|
|
192
|
-
getCumulativeRateLimitPercentage(sonnetTokenCap) {
|
|
193
|
-
const totals = this.getTotals();
|
|
194
|
-
return this.calculateRateLimitPercentage(totals.cost.totalCost, sonnetTokenCap);
|
|
195
|
-
}
|
|
196
115
|
}
|
|
197
116
|
//# sourceMappingURL=token-tracker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB;IACtD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAqB;IACnD,MAAM,MAAM,GAAc;QACxB,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;QACf,oBAAoB,EAAE,CAAC;QACvB,wBAAwB,EAAE,CAAC;QAC3B,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,CAAC;KAChB,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;QAC1C,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;QAC5C,MAAM,CAAC,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC;QAC5D,MAAM,CAAC,wBAAwB,IAAI,OAAO,CAAC,wBAAwB,CAAC;QAEpE,wBAAwB;QACxB,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACvE,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,WAAW,IAAI,UAAU,CAAC,WAAW,CAAC;gBAC/C,QAAQ,CAAC,YAAY,IAAI,UAAU,CAAC,YAAY,CAAC;gBACjD,QAAQ,CAAC,oBAAoB,IAAI,UAAU,CAAC,oBAAoB,CAAC;gBACjE,QAAQ,CAAC,wBAAwB,IAAI,UAAU,CAAC,wBAAwB,CAAC;gBACzE,QAAQ,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC;YACjD,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC;IAC9C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,YAAY;IACf,OAAO,GAAqB,EAAE,CAAC;IAEvC;QACE,oDAAoD;IACtD,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAc,EAAE,QAAqB;QAC3C,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACxC,8CAA8C;QAC9C,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QACnF,MAAM,IAAI,GAAkB,EAAE,SAAS,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,SAAS;QACP,MAAM,UAAU,GAAc;YAC5B,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;YACf,oBAAoB,EAAE,CAAC;YACvB,wBAAwB,EAAE,CAAC;YAC3B,UAAU,EAAE,EAAE;YACd,YAAY,EAAE,CAAC;SAChB,CAAC;QACF,MAAM,SAAS,GAAkB;YAC/B,SAAS,EAAE,CAAC;SACb,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,UAAU,CAAC,WAAW,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC;YAClD,UAAU,CAAC,YAAY,IAAI,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC;YACpD,UAAU,CAAC,oBAAoB,IAAI,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC;YACpE,UAAU,CAAC,wBAAwB,IAAI,KAAK,CAAC,KAAK,CAAC,wBAAwB,CAAC;YAC5E,UAAU,CAAC,YAAY,IAAI,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC;YAEpD,wBAAwB;YACxB,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3E,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBAChD,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,CAAC,WAAW,IAAI,UAAU,CAAC,WAAW,CAAC;oBAC/C,QAAQ,CAAC,YAAY,IAAI,UAAU,CAAC,YAAY,CAAC;oBACjD,QAAQ,CAAC,oBAAoB,IAAI,UAAU,CAAC,oBAAoB,CAAC;oBACjE,QAAQ,CAAC,wBAAwB,IAAI,UAAU,CAAC,wBAAwB,CAAC;oBACzE,QAAQ,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC;gBACrD,CAAC;YACH,CAAC;YAED,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;QAC9C,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAChD,CAAC;CAEF"}
|
package/package.json
CHANGED
package/src/commands/do.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFro
|
|
|
13
13
|
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
|
|
14
14
|
import type { PendingProjectInfo } from '../ui/project-picker.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
-
import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling,
|
|
16
|
+
import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
|
|
17
17
|
import type { PlanFrontmatter } from '../utils/frontmatter.js';
|
|
18
18
|
import { getVersion } from '../utils/version.js';
|
|
19
19
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
@@ -1021,6 +1021,8 @@ async function executeSingleProject(
|
|
|
1021
1021
|
const attemptUsageData: import('../types/config.js').UsageData[] = [];
|
|
1022
1022
|
// Track failure history for each attempt (attempt number -> reason)
|
|
1023
1023
|
const failureHistory: Array<{ attempt: number; reason: string }> = [];
|
|
1024
|
+
// Track current model for display in status line (updated in retry loop)
|
|
1025
|
+
let currentModel: string | undefined;
|
|
1024
1026
|
|
|
1025
1027
|
// Set up timer for elapsed time tracking
|
|
1026
1028
|
const statusLine = createStatusLine();
|
|
@@ -1031,7 +1033,8 @@ async function executeSingleProject(
|
|
|
1031
1033
|
return;
|
|
1032
1034
|
}
|
|
1033
1035
|
// Show running status with task name and timer (updates in place)
|
|
1034
|
-
|
|
1036
|
+
const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
|
|
1037
|
+
statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId, modelShortName));
|
|
1035
1038
|
});
|
|
1036
1039
|
timer.start();
|
|
1037
1040
|
|
|
@@ -1054,6 +1057,9 @@ async function executeSingleProject(
|
|
|
1054
1057
|
isRetry,
|
|
1055
1058
|
);
|
|
1056
1059
|
|
|
1060
|
+
// Update current model for timer callback display
|
|
1061
|
+
currentModel = modelResolution.model;
|
|
1062
|
+
|
|
1057
1063
|
// Log missing frontmatter warning on first attempt only
|
|
1058
1064
|
if (!isRetry && modelResolution.missingFrontmatter) {
|
|
1059
1065
|
logger.warn(` No effort frontmatter found — using ceiling model`);
|
|
@@ -1229,17 +1235,15 @@ Task completed. No detailed report provided.
|
|
|
1229
1235
|
logger.success(` Task ${taskLabel} completed (${elapsedFormatted})`);
|
|
1230
1236
|
} else {
|
|
1231
1237
|
// Minimal mode: show completed task line
|
|
1232
|
-
|
|
1238
|
+
const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
|
|
1239
|
+
logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id, modelShortName));
|
|
1233
1240
|
}
|
|
1234
1241
|
|
|
1235
1242
|
// Track and display token usage for this task
|
|
1236
1243
|
if (attemptUsageData.length > 0) {
|
|
1237
1244
|
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1238
|
-
|
|
1239
|
-
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
|
|
1245
|
+
logger.dim(formatTaskTokenSummary(entry, {
|
|
1240
1246
|
showCacheTokens: getShowCacheTokens(),
|
|
1241
|
-
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1242
|
-
rateLimitPercentage: taskRateLimitPct,
|
|
1243
1247
|
}));
|
|
1244
1248
|
}
|
|
1245
1249
|
|
|
@@ -1262,17 +1266,15 @@ Task completed. No detailed report provided.
|
|
|
1262
1266
|
logger.info(` Analyzing failure with ${analysisModel}...`);
|
|
1263
1267
|
} else {
|
|
1264
1268
|
// Minimal mode: show failed task line
|
|
1265
|
-
|
|
1269
|
+
const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
|
|
1270
|
+
logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id, modelShortName));
|
|
1266
1271
|
}
|
|
1267
1272
|
|
|
1268
1273
|
// Track token usage even for failed tasks (partial data still useful for totals)
|
|
1269
1274
|
if (attemptUsageData.length > 0) {
|
|
1270
1275
|
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1271
|
-
|
|
1272
|
-
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
|
|
1276
|
+
logger.dim(formatTaskTokenSummary(entry, {
|
|
1273
1277
|
showCacheTokens: getShowCacheTokens(),
|
|
1274
|
-
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1275
|
-
rateLimitPercentage: taskRateLimitPct,
|
|
1276
1278
|
}));
|
|
1277
1279
|
}
|
|
1278
1280
|
|
|
@@ -1386,11 +1388,8 @@ ${stashName ? `- Stash: ${stashName}` : ''}
|
|
|
1386
1388
|
if (trackerEntries.length > 0) {
|
|
1387
1389
|
logger.newline();
|
|
1388
1390
|
const totals = tokenTracker.getTotals();
|
|
1389
|
-
const totalRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
|
|
1390
1391
|
logger.dim(formatTokenTotalSummary(totals.usage, totals.cost, {
|
|
1391
1392
|
showCacheTokens: getShowCacheTokens(),
|
|
1392
|
-
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1393
|
-
rateLimitPercentage: totalRateLimitPct,
|
|
1394
1393
|
}));
|
|
1395
1394
|
}
|
|
1396
1395
|
|
package/src/commands/plan.ts
CHANGED
|
@@ -57,6 +57,7 @@ interface PlanCommandOptions {
|
|
|
57
57
|
sonnet?: boolean;
|
|
58
58
|
auto?: boolean;
|
|
59
59
|
worktree?: boolean;
|
|
60
|
+
resume?: string;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function createPlanCommand(): Command {
|
|
@@ -71,6 +72,7 @@ export function createPlanCommand(): Command {
|
|
|
71
72
|
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
72
73
|
.option('-y, --auto', "Skip Claude's permission prompts for file operations")
|
|
73
74
|
.option('-w, --worktree', 'Create a git worktree for isolated planning')
|
|
75
|
+
.option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
|
|
74
76
|
.action(async (projectName: string | undefined, options: PlanCommandOptions) => {
|
|
75
77
|
// Validate and resolve model option
|
|
76
78
|
let model: string;
|
|
@@ -84,7 +86,9 @@ export function createPlanCommand(): Command {
|
|
|
84
86
|
const autoMode = options.auto ?? false;
|
|
85
87
|
const worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
86
88
|
|
|
87
|
-
if (options.
|
|
89
|
+
if (options.resume) {
|
|
90
|
+
await runResumeCommand(options.resume, model);
|
|
91
|
+
} else if (options.amend) {
|
|
88
92
|
if (!projectName) {
|
|
89
93
|
logger.error('--amend requires a project identifier');
|
|
90
94
|
logger.error('Usage: raf plan <project> --amend');
|
|
@@ -676,3 +680,105 @@ ${taskList}
|
|
|
676
680
|
# Describe what you want to add below:
|
|
677
681
|
`;
|
|
678
682
|
}
|
|
683
|
+
|
|
684
|
+
async function runResumeCommand(identifier: string, model?: string): Promise<void> {
|
|
685
|
+
// Validate environment
|
|
686
|
+
const validation = validateEnvironment();
|
|
687
|
+
reportValidation(validation);
|
|
688
|
+
|
|
689
|
+
if (!validation.valid) {
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// First, try to resolve the project from main repo
|
|
694
|
+
const rafDir = getRafDir();
|
|
695
|
+
const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
696
|
+
|
|
697
|
+
if (!mainResolution.path) {
|
|
698
|
+
if (mainResolution.error === 'ambiguous' && mainResolution.matches) {
|
|
699
|
+
logger.error(`Ambiguous project name: ${identifier}`);
|
|
700
|
+
logger.error('Multiple projects match:');
|
|
701
|
+
for (const match of mainResolution.matches) {
|
|
702
|
+
logger.error(` - ${match.folder}`);
|
|
703
|
+
}
|
|
704
|
+
logger.error('Please specify the project ID or full folder name.');
|
|
705
|
+
} else {
|
|
706
|
+
logger.error(`Project not found: ${identifier}`);
|
|
707
|
+
}
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const projectPath = mainResolution.path;
|
|
712
|
+
const folderName = path.basename(projectPath);
|
|
713
|
+
|
|
714
|
+
// Determine if this is a worktree project by checking if a worktree exists
|
|
715
|
+
let resumeCwd = projectPath; // Default to main repo project path
|
|
716
|
+
const repoBasename = getRepoBasename();
|
|
717
|
+
|
|
718
|
+
if (repoBasename) {
|
|
719
|
+
const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
|
|
720
|
+
|
|
721
|
+
// Check if a worktree exists for this project
|
|
722
|
+
if (fs.existsSync(worktreeBaseDir)) {
|
|
723
|
+
const entries = fs.readdirSync(worktreeBaseDir, { withFileTypes: true });
|
|
724
|
+
const worktreeEntry = entries.find(
|
|
725
|
+
(entry) => entry.isDirectory() && entry.name === folderName
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
if (worktreeEntry) {
|
|
729
|
+
// Worktree exists - use it as the CWD
|
|
730
|
+
const worktreePath = path.join(worktreeBaseDir, worktreeEntry.name);
|
|
731
|
+
const wtValidation = validateWorktree(worktreePath, '');
|
|
732
|
+
if (wtValidation.isValidWorktree) {
|
|
733
|
+
resumeCwd = worktreePath;
|
|
734
|
+
logger.info(`Resuming session in worktree: ${worktreePath}`);
|
|
735
|
+
} else {
|
|
736
|
+
logger.warn(`Worktree found but invalid: ${worktreePath}`);
|
|
737
|
+
logger.warn('Falling back to main repo path.');
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
logger.info(`Project: ${folderName}`);
|
|
744
|
+
if (model) {
|
|
745
|
+
logger.info(`Model: ${model}`);
|
|
746
|
+
}
|
|
747
|
+
logger.newline();
|
|
748
|
+
|
|
749
|
+
// Set up shutdown handler
|
|
750
|
+
const claudeRunner = new ClaudeRunner({ model });
|
|
751
|
+
shutdownHandler.init();
|
|
752
|
+
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
753
|
+
|
|
754
|
+
// Launch Claude's resume picker
|
|
755
|
+
logger.info('Starting Claude session resume picker...');
|
|
756
|
+
logger.newline();
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
const exitCode = await claudeRunner.runResume({ cwd: resumeCwd });
|
|
760
|
+
|
|
761
|
+
if (exitCode !== 0) {
|
|
762
|
+
logger.warn(`Claude exited with code ${exitCode}`);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Check for created/updated plan files after resume session
|
|
766
|
+
const plansDir = getPlansDir(projectPath);
|
|
767
|
+
const planFiles = fs.existsSync(plansDir)
|
|
768
|
+
? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
|
|
769
|
+
: [];
|
|
770
|
+
|
|
771
|
+
if (planFiles.length > 0) {
|
|
772
|
+
logger.newline();
|
|
773
|
+
logger.success(`Session complete! Project has ${planFiles.length} plan(s).`);
|
|
774
|
+
logger.newline();
|
|
775
|
+
logger.info('Plans:');
|
|
776
|
+
for (const planFile of planFiles) {
|
|
777
|
+
logger.info(` - plans/${planFile}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
} catch (error) {
|
|
781
|
+
logger.error(`Resume session failed: ${error}`);
|
|
782
|
+
throw error;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
@@ -364,6 +364,87 @@ export class ClaudeRunner {
|
|
|
364
364
|
});
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Resume a Claude planning session using the interactive session picker.
|
|
369
|
+
* Launches `claude --resume` (or `claude -r`) to show available sessions for the CWD.
|
|
370
|
+
* Minimal approach - no system prompt or user message injection.
|
|
371
|
+
*
|
|
372
|
+
* @param options - Runner options (cwd)
|
|
373
|
+
*/
|
|
374
|
+
async runResume(options: ClaudeRunnerOptions = {}): Promise<number> {
|
|
375
|
+
const { cwd = process.cwd() } = options;
|
|
376
|
+
|
|
377
|
+
return new Promise((resolve) => {
|
|
378
|
+
const args = ['--resume', '--model', this.model];
|
|
379
|
+
|
|
380
|
+
logger.debug(`Starting Claude session resume picker with model: ${this.model}`);
|
|
381
|
+
|
|
382
|
+
this.activeProcess = pty.spawn(getClaudePath(), args, {
|
|
383
|
+
name: 'xterm-256color',
|
|
384
|
+
cols: process.stdout.columns ?? 80,
|
|
385
|
+
rows: process.stdout.rows ?? 24,
|
|
386
|
+
cwd,
|
|
387
|
+
env: process.env as Record<string, string>,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Set raw mode to pass through all input
|
|
391
|
+
if (process.stdin.isTTY) {
|
|
392
|
+
process.stdin.setRawMode(true);
|
|
393
|
+
}
|
|
394
|
+
process.stdin.resume();
|
|
395
|
+
|
|
396
|
+
// Pipe input to Claude
|
|
397
|
+
const onData = (data: Buffer): void => {
|
|
398
|
+
if (this.activeProcess && !this.killed) {
|
|
399
|
+
this.activeProcess.write(data.toString());
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
process.stdin.on('data', onData);
|
|
403
|
+
|
|
404
|
+
// Store disposables for proper cleanup
|
|
405
|
+
const disposables: IDisposable[] = [];
|
|
406
|
+
|
|
407
|
+
// Pipe output to stdout
|
|
408
|
+
disposables.push(this.activeProcess.onData((data) => {
|
|
409
|
+
process.stdout.write(data);
|
|
410
|
+
}));
|
|
411
|
+
|
|
412
|
+
disposables.push(this.activeProcess.onExit(({ exitCode }) => {
|
|
413
|
+
// Cleanup stdin
|
|
414
|
+
process.stdin.off('data', onData);
|
|
415
|
+
if (process.stdin.isTTY) {
|
|
416
|
+
process.stdin.setRawMode(false);
|
|
417
|
+
}
|
|
418
|
+
process.stdin.pause();
|
|
419
|
+
|
|
420
|
+
// Dispose all event listeners to prevent FD leaks
|
|
421
|
+
for (const disposable of disposables) {
|
|
422
|
+
try {
|
|
423
|
+
disposable.dispose();
|
|
424
|
+
} catch {
|
|
425
|
+
// Ignore disposal errors
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Ensure PTY is fully cleaned up
|
|
430
|
+
if (this.activeProcess) {
|
|
431
|
+
try {
|
|
432
|
+
this.activeProcess.kill();
|
|
433
|
+
} catch {
|
|
434
|
+
// Ignore - process may already be dead
|
|
435
|
+
}
|
|
436
|
+
this.activeProcess = null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (this.killed) {
|
|
440
|
+
resolve(130); // SIGINT exit code
|
|
441
|
+
} else {
|
|
442
|
+
resolve(exitCode);
|
|
443
|
+
}
|
|
444
|
+
}));
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
367
448
|
/**
|
|
368
449
|
* Run Claude non-interactively and collect output.
|
|
369
450
|
* Uses stream-json format internally to capture token usage data.
|
|
@@ -23,11 +23,13 @@ export interface StreamEvent {
|
|
|
23
23
|
cache_read_input_tokens?: number;
|
|
24
24
|
cache_creation_input_tokens?: number;
|
|
25
25
|
};
|
|
26
|
+
total_cost_usd?: number;
|
|
26
27
|
modelUsage?: Record<string, {
|
|
27
28
|
inputTokens?: number;
|
|
28
29
|
outputTokens?: number;
|
|
29
30
|
cacheReadInputTokens?: number;
|
|
30
31
|
cacheCreationInputTokens?: number;
|
|
32
|
+
costUSD?: number;
|
|
31
33
|
}>;
|
|
32
34
|
tool_use_result?: {
|
|
33
35
|
type?: string;
|
|
@@ -175,6 +177,7 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
|
|
|
175
177
|
outputTokens: data.outputTokens ?? 0,
|
|
176
178
|
cacheReadInputTokens: data.cacheReadInputTokens ?? 0,
|
|
177
179
|
cacheCreationInputTokens: data.cacheCreationInputTokens ?? 0,
|
|
180
|
+
costUsd: data.costUSD ?? 0,
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
}
|
|
@@ -185,5 +188,6 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
|
|
|
185
188
|
cacheReadInputTokens: usage?.cache_read_input_tokens ?? 0,
|
|
186
189
|
cacheCreationInputTokens: usage?.cache_creation_input_tokens ?? 0,
|
|
187
190
|
modelUsage,
|
|
191
|
+
totalCostUsd: event.total_cost_usd ?? 0,
|
|
188
192
|
};
|
|
189
193
|
}
|