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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-cli",
3
- "version": "0.1.186",
3
+ "version": "0.1.187",
4
4
  "description": "CLI for trickle runtime type observability",
5
5
  "bin": {
6
6
  "trickle": "dist/index.js"
@@ -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'));