trickle-cli 0.1.186 → 0.1.187
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.
|
@@ -107,10 +107,51 @@ function costReportCommand(opts) {
|
|
|
107
107
|
const monthlyProjection = timeSpanMs > 60000
|
|
108
108
|
? (totalCost / timeSpanMs) * 30 * 24 * 60 * 60 * 1000
|
|
109
109
|
: null;
|
|
110
|
+
// Per-agent cost roll-up — read agents.jsonl and attribute LLM costs to agents
|
|
111
|
+
const agentsFile = path.join(dir, 'agents.jsonl');
|
|
112
|
+
const byAgent = {};
|
|
113
|
+
if (fs.existsSync(agentsFile)) {
|
|
114
|
+
const agentEvents = fs.readFileSync(agentsFile, 'utf-8').split('\n').filter(Boolean)
|
|
115
|
+
.map(l => { try {
|
|
116
|
+
return JSON.parse(l);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
} }).filter(Boolean);
|
|
121
|
+
// Build agent activity windows: agent_start → agent_end with timestamps
|
|
122
|
+
const activeAgents = [];
|
|
123
|
+
const startTimes = {};
|
|
124
|
+
for (const ev of agentEvents) {
|
|
125
|
+
const name = ev.chain || ev.tool || 'unknown';
|
|
126
|
+
const fw = ev.framework || 'unknown';
|
|
127
|
+
if (ev.event === 'agent_start' || ev.event === 'crew_start') {
|
|
128
|
+
startTimes[name] = { name, framework: fw, ts: ev.timestamp || 0 };
|
|
129
|
+
}
|
|
130
|
+
else if ((ev.event === 'agent_end' || ev.event === 'crew_end') && startTimes[name]) {
|
|
131
|
+
activeAgents.push({ name, framework: fw, start: startTimes[name].ts, end: ev.timestamp || Date.now() });
|
|
132
|
+
delete startTimes[name];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Attribute each LLM call to the most-recently-started agent active at that time
|
|
136
|
+
for (const call of calls) {
|
|
137
|
+
const ts = call.timestamp || 0;
|
|
138
|
+
const matching = activeAgents.filter(a => ts >= a.start && ts <= a.end);
|
|
139
|
+
const agent = matching.length > 0 ? matching[matching.length - 1] : null;
|
|
140
|
+
if (agent) {
|
|
141
|
+
const key = `${agent.framework}/${agent.name}`;
|
|
142
|
+
if (!byAgent[key])
|
|
143
|
+
byAgent[key] = { calls: 0, tokens: 0, cost: 0, framework: agent.framework };
|
|
144
|
+
byAgent[key].calls++;
|
|
145
|
+
byAgent[key].tokens += call.totalTokens || 0;
|
|
146
|
+
byAgent[key].cost += call.estimatedCostUsd || 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
110
150
|
if (opts.json) {
|
|
111
151
|
console.log(JSON.stringify({
|
|
112
152
|
summary: { totalCost, totalTokens, totalInputTokens, totalOutputTokens, totalCalls: calls.length, totalDurationMs: totalDuration, errors: errorCount, monthlyProjection },
|
|
113
153
|
byProvider, byModel,
|
|
154
|
+
...(Object.keys(byAgent).length > 0 ? { byAgent } : {}),
|
|
114
155
|
}, null, 2));
|
|
115
156
|
return;
|
|
116
157
|
}
|
|
@@ -154,6 +195,16 @@ function costReportCommand(opts) {
|
|
|
154
195
|
}
|
|
155
196
|
// Top costly calls
|
|
156
197
|
const costlyCalls = calls.filter(c => c.estimatedCostUsd > 0).sort((a, b) => b.estimatedCostUsd - a.estimatedCostUsd).slice(0, 5);
|
|
198
|
+
// By agent (if agent data exists)
|
|
199
|
+
if (Object.keys(byAgent).length > 0) {
|
|
200
|
+
console.log(chalk_1.default.gray('\n ' + '─'.repeat(60)));
|
|
201
|
+
console.log(chalk_1.default.bold(' By Agent/Workflow'));
|
|
202
|
+
const sortedAgents = Object.entries(byAgent).sort((a, b) => b[1].cost - a[1].cost);
|
|
203
|
+
for (const [name, data] of sortedAgents) {
|
|
204
|
+
const pct = totalCost > 0 ? ((data.cost / totalCost) * 100).toFixed(0) : '0';
|
|
205
|
+
console.log(` ${chalk_1.default.cyan(name.padEnd(30))} $${data.cost.toFixed(4).padEnd(10)} ${chalk_1.default.gray(pct + '%')} ${data.calls} calls ${formatTokens(data.tokens)} tokens`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
157
208
|
if (costlyCalls.length > 0) {
|
|
158
209
|
console.log(chalk_1.default.gray('\n ' + '─'.repeat(60)));
|
|
159
210
|
console.log(chalk_1.default.bold(' Most Expensive Calls'));
|
package/package.json
CHANGED
|
@@ -85,10 +85,48 @@ export function costReportCommand(opts: { json?: boolean; budget?: string }): vo
|
|
|
85
85
|
? (totalCost / timeSpanMs) * 30 * 24 * 60 * 60 * 1000
|
|
86
86
|
: null;
|
|
87
87
|
|
|
88
|
+
// Per-agent cost roll-up — read agents.jsonl and attribute LLM costs to agents
|
|
89
|
+
const agentsFile = path.join(dir, 'agents.jsonl');
|
|
90
|
+
const byAgent: Record<string, { calls: number; tokens: number; cost: number; framework: string }> = {};
|
|
91
|
+
if (fs.existsSync(agentsFile)) {
|
|
92
|
+
const agentEvents = fs.readFileSync(agentsFile, 'utf-8').split('\n').filter(Boolean)
|
|
93
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
94
|
+
|
|
95
|
+
// Build agent activity windows: agent_start → agent_end with timestamps
|
|
96
|
+
const activeAgents: { name: string; framework: string; start: number; end: number }[] = [];
|
|
97
|
+
const startTimes: Record<string, { name: string; framework: string; ts: number }> = {};
|
|
98
|
+
|
|
99
|
+
for (const ev of agentEvents) {
|
|
100
|
+
const name = ev.chain || ev.tool || 'unknown';
|
|
101
|
+
const fw = ev.framework || 'unknown';
|
|
102
|
+
if (ev.event === 'agent_start' || ev.event === 'crew_start') {
|
|
103
|
+
startTimes[name] = { name, framework: fw, ts: ev.timestamp || 0 };
|
|
104
|
+
} else if ((ev.event === 'agent_end' || ev.event === 'crew_end') && startTimes[name]) {
|
|
105
|
+
activeAgents.push({ name, framework: fw, start: startTimes[name].ts, end: ev.timestamp || Date.now() });
|
|
106
|
+
delete startTimes[name];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Attribute each LLM call to the most-recently-started agent active at that time
|
|
111
|
+
for (const call of calls) {
|
|
112
|
+
const ts = call.timestamp || 0;
|
|
113
|
+
const matching = activeAgents.filter(a => ts >= a.start && ts <= a.end);
|
|
114
|
+
const agent = matching.length > 0 ? matching[matching.length - 1] : null;
|
|
115
|
+
if (agent) {
|
|
116
|
+
const key = `${agent.framework}/${agent.name}`;
|
|
117
|
+
if (!byAgent[key]) byAgent[key] = { calls: 0, tokens: 0, cost: 0, framework: agent.framework };
|
|
118
|
+
byAgent[key].calls++;
|
|
119
|
+
byAgent[key].tokens += call.totalTokens || 0;
|
|
120
|
+
byAgent[key].cost += call.estimatedCostUsd || 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
88
125
|
if (opts.json) {
|
|
89
126
|
console.log(JSON.stringify({
|
|
90
127
|
summary: { totalCost, totalTokens, totalInputTokens, totalOutputTokens, totalCalls: calls.length, totalDurationMs: totalDuration, errors: errorCount, monthlyProjection },
|
|
91
128
|
byProvider, byModel,
|
|
129
|
+
...(Object.keys(byAgent).length > 0 ? { byAgent } : {}),
|
|
92
130
|
}, null, 2));
|
|
93
131
|
return;
|
|
94
132
|
}
|
|
@@ -137,6 +175,17 @@ export function costReportCommand(opts: { json?: boolean; budget?: string }): vo
|
|
|
137
175
|
|
|
138
176
|
// Top costly calls
|
|
139
177
|
const costlyCalls = calls.filter(c => c.estimatedCostUsd > 0).sort((a, b) => b.estimatedCostUsd - a.estimatedCostUsd).slice(0, 5);
|
|
178
|
+
// By agent (if agent data exists)
|
|
179
|
+
if (Object.keys(byAgent).length > 0) {
|
|
180
|
+
console.log(chalk.gray('\n ' + '─'.repeat(60)));
|
|
181
|
+
console.log(chalk.bold(' By Agent/Workflow'));
|
|
182
|
+
const sortedAgents = Object.entries(byAgent).sort((a, b) => b[1].cost - a[1].cost);
|
|
183
|
+
for (const [name, data] of sortedAgents) {
|
|
184
|
+
const pct = totalCost > 0 ? ((data.cost / totalCost) * 100).toFixed(0) : '0';
|
|
185
|
+
console.log(` ${chalk.cyan(name.padEnd(30))} $${data.cost.toFixed(4).padEnd(10)} ${chalk.gray(pct + '%')} ${data.calls} calls ${formatTokens(data.tokens)} tokens`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
140
189
|
if (costlyCalls.length > 0) {
|
|
141
190
|
console.log(chalk.gray('\n ' + '─'.repeat(60)));
|
|
142
191
|
console.log(chalk.bold(' Most Expensive Calls'));
|