@hasna/terminal 0.5.2 → 0.5.3
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/dist/compression.js +2 -2
- package/dist/mcp/server.js +10 -7
- package/dist/output-processor.js +26 -0
- package/package.json +1 -1
- package/src/compression.ts +2 -2
- package/src/mcp/server.ts +10 -7
- package/src/output-processor.ts +39 -1
package/dist/compression.js
CHANGED
|
@@ -74,8 +74,8 @@ export function compress(command, output, options = {}) {
|
|
|
74
74
|
const json = JSON.stringify(parsed.data, null, format === "summary" ? 0 : 2);
|
|
75
75
|
const savings = tokenSavings(output, parsed.data);
|
|
76
76
|
const compressedTokens = estimateTokens(json);
|
|
77
|
-
//
|
|
78
|
-
if (!maxTokens || compressedTokens <= maxTokens) {
|
|
77
|
+
// ONLY use JSON if it actually saves tokens (never return larger output)
|
|
78
|
+
if (savings.saved > 0 && (!maxTokens || compressedTokens <= maxTokens)) {
|
|
79
79
|
return {
|
|
80
80
|
content: json,
|
|
81
81
|
format: "json",
|
package/dist/mcp/server.js
CHANGED
|
@@ -64,17 +64,20 @@ export function createServer() {
|
|
|
64
64
|
}) }],
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
// JSON mode — structured parsing
|
|
67
|
+
// JSON mode — structured parsing (only if it actually saves tokens)
|
|
68
68
|
if (format === "json") {
|
|
69
69
|
const parsed = parseOutput(command, output);
|
|
70
70
|
if (parsed) {
|
|
71
71
|
const savings = tokenSavings(output, parsed.data);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
if (savings.saved > 0) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
75
|
+
exitCode: result.exitCode, parsed: parsed.data, parser: parsed.parser,
|
|
76
|
+
duration: result.duration, tokensSaved: savings.saved, savingsPercent: savings.percent,
|
|
77
|
+
}) }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// JSON was larger — fall through to compression
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
// Compressed mode (also fallback for json when no parser matches)
|
package/dist/output-processor.js
CHANGED
|
@@ -32,7 +32,11 @@ export async function processOutput(command, output) {
|
|
|
32
32
|
summary: output,
|
|
33
33
|
full: output,
|
|
34
34
|
tokensSaved: 0,
|
|
35
|
+
aiTokensUsed: 0,
|
|
35
36
|
aiProcessed: false,
|
|
37
|
+
aiCostUsd: 0,
|
|
38
|
+
savingsValueUsd: 0,
|
|
39
|
+
netSavingsUsd: 0,
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
// Truncate very long output before sending to AI
|
|
@@ -65,12 +69,30 @@ export async function processOutput(command, output) {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
catch { /* not JSON, that's fine */ }
|
|
72
|
+
// Cost calculation
|
|
73
|
+
// AI input: system prompt (~200 tokens) + command + output sent to AI
|
|
74
|
+
const aiInputTokens = estimateTokens(SUMMARIZE_PROMPT) + estimateTokens(toSummarize) + 20;
|
|
75
|
+
const aiOutputTokens = summaryTokens;
|
|
76
|
+
const aiTokensUsed = aiInputTokens + aiOutputTokens;
|
|
77
|
+
// Cerebras qwen-3-235b pricing: $0.60/M input, $1.20/M output
|
|
78
|
+
const aiCostUsd = (aiInputTokens * 0.60 + aiOutputTokens * 1.20) / 1_000_000;
|
|
79
|
+
// Value of tokens saved (at Claude Sonnet $3/M input — what the agent would pay)
|
|
80
|
+
const savingsValueUsd = (saved * 3.0) / 1_000_000;
|
|
81
|
+
const netSavingsUsd = savingsValueUsd - aiCostUsd;
|
|
82
|
+
// Only record savings if net positive (AI cost < token savings value)
|
|
83
|
+
if (netSavingsUsd > 0 && saved > 0) {
|
|
84
|
+
recordSaving("compressed", saved);
|
|
85
|
+
}
|
|
68
86
|
return {
|
|
69
87
|
summary,
|
|
70
88
|
full: output,
|
|
71
89
|
structured,
|
|
72
90
|
tokensSaved: saved,
|
|
91
|
+
aiTokensUsed,
|
|
73
92
|
aiProcessed: true,
|
|
93
|
+
aiCostUsd,
|
|
94
|
+
savingsValueUsd,
|
|
95
|
+
netSavingsUsd,
|
|
74
96
|
};
|
|
75
97
|
}
|
|
76
98
|
catch {
|
|
@@ -82,7 +104,11 @@ export async function processOutput(command, output) {
|
|
|
82
104
|
summary: fallback,
|
|
83
105
|
full: output,
|
|
84
106
|
tokensSaved: Math.max(0, estimateTokens(output) - estimateTokens(fallback)),
|
|
107
|
+
aiTokensUsed: 0,
|
|
85
108
|
aiProcessed: false,
|
|
109
|
+
aiCostUsd: 0,
|
|
110
|
+
savingsValueUsd: 0,
|
|
111
|
+
netSavingsUsd: 0,
|
|
86
112
|
};
|
|
87
113
|
}
|
|
88
114
|
}
|
package/package.json
CHANGED
package/src/compression.ts
CHANGED
|
@@ -104,8 +104,8 @@ export function compress(command: string, output: string, options: CompressOptio
|
|
|
104
104
|
const savings = tokenSavings(output, parsed.data);
|
|
105
105
|
const compressedTokens = estimateTokens(json);
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
if (!maxTokens || compressedTokens <= maxTokens) {
|
|
107
|
+
// ONLY use JSON if it actually saves tokens (never return larger output)
|
|
108
|
+
if (savings.saved > 0 && (!maxTokens || compressedTokens <= maxTokens)) {
|
|
109
109
|
return {
|
|
110
110
|
content: json,
|
|
111
111
|
format: "json",
|
package/src/mcp/server.ts
CHANGED
|
@@ -77,17 +77,20 @@ export function createServer(): McpServer {
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// JSON mode — structured parsing
|
|
80
|
+
// JSON mode — structured parsing (only if it actually saves tokens)
|
|
81
81
|
if (format === "json") {
|
|
82
82
|
const parsed = parseOutput(command, output);
|
|
83
83
|
if (parsed) {
|
|
84
84
|
const savings = tokenSavings(output, parsed.data);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
if (savings.saved > 0) {
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text" as const, text: JSON.stringify({
|
|
88
|
+
exitCode: result.exitCode, parsed: parsed.data, parser: parsed.parser,
|
|
89
|
+
duration: result.duration, tokensSaved: savings.saved, savingsPercent: savings.percent,
|
|
90
|
+
}) }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// JSON was larger — fall through to compression
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
package/src/output-processor.ts
CHANGED
|
@@ -12,10 +12,18 @@ export interface ProcessedOutput {
|
|
|
12
12
|
full: string;
|
|
13
13
|
/** Structured JSON if the AI could extract it */
|
|
14
14
|
structured?: Record<string, unknown>;
|
|
15
|
-
/** How many tokens were saved */
|
|
15
|
+
/** How many tokens were saved (net, after subtracting AI cost) */
|
|
16
16
|
tokensSaved: number;
|
|
17
|
+
/** Tokens used by the AI summarization call */
|
|
18
|
+
aiTokensUsed: number;
|
|
17
19
|
/** Whether AI processing was used (vs passthrough) */
|
|
18
20
|
aiProcessed: boolean;
|
|
21
|
+
/** Cost of the AI call in USD (Cerebras pricing) */
|
|
22
|
+
aiCostUsd: number;
|
|
23
|
+
/** Value of tokens saved in USD (at Claude Sonnet rates) */
|
|
24
|
+
savingsValueUsd: number;
|
|
25
|
+
/** Net ROI: savings minus AI cost */
|
|
26
|
+
netSavingsUsd: number;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
const MIN_LINES_TO_PROCESS = 15;
|
|
@@ -53,7 +61,11 @@ export async function processOutput(
|
|
|
53
61
|
summary: output,
|
|
54
62
|
full: output,
|
|
55
63
|
tokensSaved: 0,
|
|
64
|
+
aiTokensUsed: 0,
|
|
56
65
|
aiProcessed: false,
|
|
66
|
+
aiCostUsd: 0,
|
|
67
|
+
savingsValueUsd: 0,
|
|
68
|
+
netSavingsUsd: 0,
|
|
57
69
|
};
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -94,12 +106,34 @@ export async function processOutput(
|
|
|
94
106
|
}
|
|
95
107
|
} catch { /* not JSON, that's fine */ }
|
|
96
108
|
|
|
109
|
+
// Cost calculation
|
|
110
|
+
// AI input: system prompt (~200 tokens) + command + output sent to AI
|
|
111
|
+
const aiInputTokens = estimateTokens(SUMMARIZE_PROMPT) + estimateTokens(toSummarize) + 20;
|
|
112
|
+
const aiOutputTokens = summaryTokens;
|
|
113
|
+
const aiTokensUsed = aiInputTokens + aiOutputTokens;
|
|
114
|
+
|
|
115
|
+
// Cerebras qwen-3-235b pricing: $0.60/M input, $1.20/M output
|
|
116
|
+
const aiCostUsd = (aiInputTokens * 0.60 + aiOutputTokens * 1.20) / 1_000_000;
|
|
117
|
+
|
|
118
|
+
// Value of tokens saved (at Claude Sonnet $3/M input — what the agent would pay)
|
|
119
|
+
const savingsValueUsd = (saved * 3.0) / 1_000_000;
|
|
120
|
+
const netSavingsUsd = savingsValueUsd - aiCostUsd;
|
|
121
|
+
|
|
122
|
+
// Only record savings if net positive (AI cost < token savings value)
|
|
123
|
+
if (netSavingsUsd > 0 && saved > 0) {
|
|
124
|
+
recordSaving("compressed", saved);
|
|
125
|
+
}
|
|
126
|
+
|
|
97
127
|
return {
|
|
98
128
|
summary,
|
|
99
129
|
full: output,
|
|
100
130
|
structured,
|
|
101
131
|
tokensSaved: saved,
|
|
132
|
+
aiTokensUsed,
|
|
102
133
|
aiProcessed: true,
|
|
134
|
+
aiCostUsd,
|
|
135
|
+
savingsValueUsd,
|
|
136
|
+
netSavingsUsd,
|
|
103
137
|
};
|
|
104
138
|
} catch {
|
|
105
139
|
// AI unavailable — fall back to simple truncation
|
|
@@ -111,7 +145,11 @@ export async function processOutput(
|
|
|
111
145
|
summary: fallback,
|
|
112
146
|
full: output,
|
|
113
147
|
tokensSaved: Math.max(0, estimateTokens(output) - estimateTokens(fallback)),
|
|
148
|
+
aiTokensUsed: 0,
|
|
114
149
|
aiProcessed: false,
|
|
150
|
+
aiCostUsd: 0,
|
|
151
|
+
savingsValueUsd: 0,
|
|
152
|
+
netSavingsUsd: 0,
|
|
115
153
|
};
|
|
116
154
|
}
|
|
117
155
|
}
|