@outputai/cli 0.7.1-next.5a29fff.0 → 0.7.1-next.bd6bd49.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/bin/run.js +1 -1
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/hooks/init.js +12 -3
- package/dist/hooks/init.spec.js +18 -8
- package/dist/scripts/refresh_version_check.d.ts +1 -0
- package/dist/scripts/refresh_version_check.js +9 -0
- package/dist/services/cost_calculator.d.ts +1 -5
- package/dist/services/cost_calculator.js +214 -102
- package/dist/services/cost_calculator.spec.js +329 -253
- package/dist/services/npm_update_service.js +11 -3
- package/dist/services/npm_update_service.spec.js +20 -7
- package/dist/services/version_check.d.ts +19 -1
- package/dist/services/version_check.js +53 -17
- package/dist/services/version_check.spec.js +88 -58
- package/dist/types/cost.d.ts +64 -23
- package/dist/types/cost.js +4 -0
- package/dist/utils/cost_formatter.js +65 -43
- package/dist/utils/proxy.d.ts +3 -2
- package/dist/utils/proxy.js +4 -3
- package/dist/utils/proxy.spec.js +4 -4
- package/oclif.manifest.json +1428 -0
- package/package.json +6 -5
|
@@ -29,76 +29,96 @@ export function parseCostData(report) {
|
|
|
29
29
|
const byModel = {};
|
|
30
30
|
for (const r of report.llmCalls) {
|
|
31
31
|
if (!byModel[r.model]) {
|
|
32
|
-
byModel[r.model] = { count: 0,
|
|
32
|
+
byModel[r.model] = { count: 0, originalCost: 0, adjustedCost: 0 };
|
|
33
33
|
}
|
|
34
34
|
byModel[r.model].count++;
|
|
35
|
-
byModel[r.model].
|
|
35
|
+
byModel[r.model].originalCost += r.originalCost;
|
|
36
|
+
byModel[r.model].adjustedCost += r.adjustedCost;
|
|
36
37
|
}
|
|
37
38
|
const llmModels = Object.entries(byModel)
|
|
38
|
-
.sort((a, b) => b[1].
|
|
39
|
-
.map(([model,
|
|
40
|
-
const
|
|
41
|
-
.sort((a, b) => b.
|
|
42
|
-
.map(
|
|
43
|
-
|
|
39
|
+
.sort((a, b) => b[1].adjustedCost - a[1].adjustedCost)
|
|
40
|
+
.map(([model, s]) => ({ model, ...s }));
|
|
41
|
+
const hosts = [...report.httpCosts]
|
|
42
|
+
.sort((a, b) => b.adjustedTotalCost - a.adjustedTotalCost)
|
|
43
|
+
.map(h => ({
|
|
44
|
+
host: h.host,
|
|
45
|
+
callCount: h.calls.length,
|
|
46
|
+
originalCost: h.originalTotalCost,
|
|
47
|
+
adjustedCost: h.adjustedTotalCost
|
|
48
|
+
}));
|
|
49
|
+
const httpTotalCalls = hosts.reduce((sum, h) => sum + h.callCount, 0);
|
|
44
50
|
return {
|
|
45
51
|
traceFile: report.traceFile,
|
|
46
52
|
workflowName: report.workflowName,
|
|
47
53
|
duration: report.durationMs ? `${(report.durationMs / 1000).toFixed(1)}s` : 'N/A',
|
|
48
54
|
llmModels,
|
|
49
55
|
llmTotalCalls: report.llmCalls.length,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
llmOriginalCost: report.llmOriginalCost,
|
|
57
|
+
llmAdjustedCost: report.llmAdjustedCost,
|
|
58
|
+
hosts,
|
|
59
|
+
httpTotalCalls,
|
|
60
|
+
httpOriginalCost: report.httpOriginalCost,
|
|
61
|
+
httpAdjustedCost: report.httpAdjustedCost,
|
|
54
62
|
verbose: {
|
|
55
63
|
hasReasoning: report.totalReasoningTokens > 0,
|
|
56
64
|
hasCached: report.totalCachedTokens > 0
|
|
57
65
|
},
|
|
58
66
|
llmCalls: report.llmCalls,
|
|
59
|
-
|
|
67
|
+
httpDetails: report.httpCosts,
|
|
60
68
|
totalInputTokens: report.totalInputTokens,
|
|
61
69
|
totalOutputTokens: report.totalOutputTokens,
|
|
62
70
|
totalCachedTokens: report.totalCachedTokens,
|
|
63
71
|
totalReasoningTokens: report.totalReasoningTokens,
|
|
72
|
+
originalTotalCost: report.originalTotalCost,
|
|
64
73
|
totalCost: report.totalCost,
|
|
65
|
-
|
|
66
|
-
isEmpty: report.llmCalls.length === 0 && report.services.length === 0
|
|
74
|
+
isEmpty: report.llmCalls.length === 0 && report.httpCosts.length === 0
|
|
67
75
|
};
|
|
68
76
|
}
|
|
69
77
|
function formatSummary(data) {
|
|
70
78
|
const lines = [];
|
|
71
79
|
if (data.llmModels.length > 0) {
|
|
72
80
|
const table = new Table({
|
|
73
|
-
head: ['Model', 'Calls', '
|
|
81
|
+
head: ['Model', 'Calls', 'Original', 'Adjusted'],
|
|
74
82
|
style: { head: ['cyan'] },
|
|
75
|
-
colAligns: ['left', 'right', 'right']
|
|
83
|
+
colAligns: ['left', 'right', 'right', 'right']
|
|
76
84
|
});
|
|
77
85
|
for (const m of data.llmModels) {
|
|
78
|
-
table.push([
|
|
86
|
+
table.push([
|
|
87
|
+
m.model,
|
|
88
|
+
pluralize(m.count, 'call'),
|
|
89
|
+
formatCurrency(m.originalCost),
|
|
90
|
+
formatCurrency(m.adjustedCost)
|
|
91
|
+
]);
|
|
79
92
|
}
|
|
80
93
|
table.push([
|
|
81
94
|
'Subtotal',
|
|
82
95
|
pluralize(data.llmTotalCalls, 'call'),
|
|
83
|
-
formatCurrency(data.
|
|
96
|
+
formatCurrency(data.llmOriginalCost),
|
|
97
|
+
formatCurrency(data.llmAdjustedCost)
|
|
84
98
|
]);
|
|
85
99
|
lines.push('LLM Costs:');
|
|
86
100
|
lines.push(table.toString());
|
|
87
101
|
lines.push('');
|
|
88
102
|
}
|
|
89
|
-
if (data.
|
|
103
|
+
if (data.hosts.length > 0) {
|
|
90
104
|
const table = new Table({
|
|
91
|
-
head: ['
|
|
105
|
+
head: ['Host', 'Calls', 'Original', 'Adjusted'],
|
|
92
106
|
style: { head: ['cyan'] },
|
|
93
|
-
colAligns: ['left', 'right', 'right']
|
|
107
|
+
colAligns: ['left', 'right', 'right', 'right']
|
|
94
108
|
});
|
|
95
|
-
for (const
|
|
96
|
-
table.push([
|
|
109
|
+
for (const h of data.hosts) {
|
|
110
|
+
table.push([
|
|
111
|
+
h.host,
|
|
112
|
+
pluralize(h.callCount, 'call'),
|
|
113
|
+
formatCurrency(h.originalCost),
|
|
114
|
+
formatCurrency(h.adjustedCost)
|
|
115
|
+
]);
|
|
97
116
|
}
|
|
98
117
|
table.push([
|
|
99
118
|
'Subtotal',
|
|
100
|
-
pluralize(data.
|
|
101
|
-
formatCurrency(data.
|
|
119
|
+
pluralize(data.httpTotalCalls, 'call'),
|
|
120
|
+
formatCurrency(data.httpOriginalCost),
|
|
121
|
+
formatCurrency(data.httpAdjustedCost)
|
|
102
122
|
]);
|
|
103
123
|
lines.push('API Costs:');
|
|
104
124
|
lines.push(table.toString());
|
|
@@ -119,8 +139,8 @@ function formatVerbose(data) {
|
|
|
119
139
|
head.push('Reasoning');
|
|
120
140
|
colAligns.push('right');
|
|
121
141
|
}
|
|
122
|
-
head.push('
|
|
123
|
-
colAligns.push('right');
|
|
142
|
+
head.push('Original', 'Adjusted');
|
|
143
|
+
colAligns.push('right', 'right');
|
|
124
144
|
const table = new Table({
|
|
125
145
|
head,
|
|
126
146
|
style: { head: ['cyan'] },
|
|
@@ -139,7 +159,7 @@ function formatVerbose(data) {
|
|
|
139
159
|
if (data.verbose.hasReasoning) {
|
|
140
160
|
row.push(formatNumber(r.reasoning));
|
|
141
161
|
}
|
|
142
|
-
row.push(formatCurrency(r.
|
|
162
|
+
row.push(formatCurrency(r.originalCost), formatCurrency(r.adjustedCost));
|
|
143
163
|
table.push(row);
|
|
144
164
|
}
|
|
145
165
|
const totalRow = [
|
|
@@ -154,29 +174,34 @@ function formatVerbose(data) {
|
|
|
154
174
|
if (data.verbose.hasReasoning) {
|
|
155
175
|
totalRow.push(formatNumber(data.totalReasoningTokens));
|
|
156
176
|
}
|
|
157
|
-
totalRow.push(formatCurrency(data.
|
|
177
|
+
totalRow.push(formatCurrency(data.llmOriginalCost), formatCurrency(data.llmAdjustedCost));
|
|
158
178
|
table.push(totalRow);
|
|
159
179
|
lines.push('LLM Calls:');
|
|
160
180
|
lines.push(table.toString());
|
|
161
181
|
lines.push('');
|
|
162
182
|
}
|
|
163
|
-
if (data.
|
|
183
|
+
if (data.httpDetails.length > 0) {
|
|
164
184
|
const table = new Table({
|
|
165
|
-
head: ['
|
|
185
|
+
head: ['Host', 'Step', 'Usage', 'Original', 'Adjusted'],
|
|
166
186
|
style: { head: ['cyan'] },
|
|
167
|
-
colAligns: ['left', 'left', 'right', 'right']
|
|
187
|
+
colAligns: ['left', 'left', 'right', 'right', 'right']
|
|
168
188
|
});
|
|
169
|
-
for (const
|
|
170
|
-
for (const call of
|
|
189
|
+
for (const host of data.httpDetails) {
|
|
190
|
+
for (const call of host.calls) {
|
|
171
191
|
table.push([
|
|
172
|
-
|
|
192
|
+
host.host,
|
|
173
193
|
call.step,
|
|
174
194
|
call.usage,
|
|
175
|
-
formatCurrency(call.
|
|
195
|
+
formatCurrency(call.originalCost),
|
|
196
|
+
formatCurrency(call.adjustedCost)
|
|
176
197
|
]);
|
|
177
198
|
}
|
|
178
199
|
}
|
|
179
|
-
table.push([
|
|
200
|
+
table.push([
|
|
201
|
+
'Subtotal', '', '',
|
|
202
|
+
formatCurrency(data.httpOriginalCost),
|
|
203
|
+
formatCurrency(data.httpAdjustedCost)
|
|
204
|
+
]);
|
|
180
205
|
lines.push('API Calls:');
|
|
181
206
|
lines.push(table.toString());
|
|
182
207
|
lines.push('');
|
|
@@ -203,13 +228,10 @@ export function formatCostReport(report, options = {}) {
|
|
|
203
228
|
colAligns: ['left', 'right'],
|
|
204
229
|
colWidths: [36, 12]
|
|
205
230
|
});
|
|
206
|
-
totalTable.push(['TOTAL ESTIMATED COST', formatCurrency(data.totalCost)]);
|
|
231
|
+
totalTable.push(['TOTAL ESTIMATED COST (adjusted)', formatCurrency(data.totalCost)]);
|
|
232
|
+
totalTable.push(['As-charged (from trace)', formatCurrency(data.originalTotalCost)]);
|
|
207
233
|
lines.push(totalTable.toString());
|
|
208
234
|
}
|
|
209
|
-
if (data.unknownModels.length > 0) {
|
|
210
|
-
lines.push('');
|
|
211
|
-
lines.push(`Warning: Unknown models (add to config/costs.yml): ${data.unknownModels.join(', ')}`);
|
|
212
|
-
}
|
|
213
235
|
if (data.isEmpty) {
|
|
214
236
|
lines.push('No billable calls found in trace.');
|
|
215
237
|
}
|
package/dist/utils/proxy.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* `http_proxy`). No-op when none are set. Invalid URLs are logged and
|
|
5
5
|
* skipped so the CLI keeps running.
|
|
6
6
|
*
|
|
7
|
-
* Call once at CLI startup, before any network activity.
|
|
7
|
+
* Call once at CLI startup, before any network activity. `undici` is
|
|
8
|
+
* imported lazily so invocations without a proxy skip loading it.
|
|
8
9
|
*/
|
|
9
|
-
export declare const bootstrapProxy: () => void
|
|
10
|
+
export declare const bootstrapProxy: () => Promise<void>;
|
package/dist/utils/proxy.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
|
|
2
1
|
/**
|
|
3
2
|
* Routes all `fetch()` calls through an HTTP/HTTPS proxy when standard
|
|
4
3
|
* proxy env vars are set (`HTTPS_PROXY`, `https_proxy`, `HTTP_PROXY`,
|
|
5
4
|
* `http_proxy`). No-op when none are set. Invalid URLs are logged and
|
|
6
5
|
* skipped so the CLI keeps running.
|
|
7
6
|
*
|
|
8
|
-
* Call once at CLI startup, before any network activity.
|
|
7
|
+
* Call once at CLI startup, before any network activity. `undici` is
|
|
8
|
+
* imported lazily so invocations without a proxy skip loading it.
|
|
9
9
|
*/
|
|
10
|
-
export const bootstrapProxy = () => {
|
|
10
|
+
export const bootstrapProxy = async () => {
|
|
11
11
|
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy ||
|
|
12
12
|
process.env.HTTP_PROXY || process.env.http_proxy;
|
|
13
13
|
if (!proxyUrl) {
|
|
@@ -20,5 +20,6 @@ export const bootstrapProxy = () => {
|
|
|
20
20
|
console.warn(`[proxy] Ignoring invalid proxy URL: ${proxyUrl}`);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
+
const { EnvHttpProxyAgent, setGlobalDispatcher } = await import('undici');
|
|
23
24
|
setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
24
25
|
};
|
package/dist/utils/proxy.spec.js
CHANGED
|
@@ -19,20 +19,20 @@ describe('proxy bootstrap', () => {
|
|
|
19
19
|
});
|
|
20
20
|
it('does nothing when no proxy env vars are set', async () => {
|
|
21
21
|
const { bootstrapProxy } = await import('./proxy.js');
|
|
22
|
-
bootstrapProxy();
|
|
22
|
+
await bootstrapProxy();
|
|
23
23
|
expect(mockSetGlobalDispatcher).not.toHaveBeenCalled();
|
|
24
24
|
});
|
|
25
25
|
it('sets global dispatcher when HTTPS_PROXY is set', async () => {
|
|
26
26
|
process.env.HTTPS_PROXY = 'http://proxy:8080';
|
|
27
27
|
const { bootstrapProxy } = await import('./proxy.js');
|
|
28
|
-
bootstrapProxy();
|
|
28
|
+
await bootstrapProxy();
|
|
29
29
|
expect(MockEnvHttpProxyAgent).toHaveBeenCalled();
|
|
30
30
|
expect(mockSetGlobalDispatcher).toHaveBeenCalledTimes(1);
|
|
31
31
|
});
|
|
32
32
|
it('sets global dispatcher when HTTP_PROXY is set', async () => {
|
|
33
33
|
process.env.HTTP_PROXY = 'http://proxy:8080';
|
|
34
34
|
const { bootstrapProxy } = await import('./proxy.js');
|
|
35
|
-
bootstrapProxy();
|
|
35
|
+
await bootstrapProxy();
|
|
36
36
|
expect(MockEnvHttpProxyAgent).toHaveBeenCalled();
|
|
37
37
|
expect(mockSetGlobalDispatcher).toHaveBeenCalledTimes(1);
|
|
38
38
|
});
|
|
@@ -40,7 +40,7 @@ describe('proxy bootstrap', () => {
|
|
|
40
40
|
process.env.HTTPS_PROXY = 'not a url';
|
|
41
41
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
42
42
|
const { bootstrapProxy } = await import('./proxy.js');
|
|
43
|
-
bootstrapProxy();
|
|
43
|
+
await bootstrapProxy();
|
|
44
44
|
expect(mockSetGlobalDispatcher).not.toHaveBeenCalled();
|
|
45
45
|
expect(warnSpy).toHaveBeenCalled();
|
|
46
46
|
warnSpy.mockRestore();
|