@mafdet/cli 0.4.8 → 0.4.9
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/CHANGELOG.md +30 -0
- package/README.md +3 -3
- package/README.zh-CN.md +1 -1
- package/dist/agent/context-budget.d.ts +20 -6
- package/dist/agent/context-budget.d.ts.map +1 -1
- package/dist/agent/context-budget.js +22 -8
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +51 -20
- package/dist/agent/session.js +2 -2
- package/dist/agent/tools/run-command.js +1 -1
- package/dist/api/models.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +9 -2
- package/dist/commands/runtime.d.ts +16 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +15 -3
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/sessions/open.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,36 @@ The version is the contract for the JSON output shapes and the exit codes.
|
|
|
6
6
|
Being 0.x, semver still permits a break in a minor bump; the intent is not to
|
|
7
7
|
use that latitude, and the promise becomes binding at 1.0.0.
|
|
8
8
|
|
|
9
|
+
## 0.4.9 - 2026-08-12
|
|
10
|
+
|
|
11
|
+
A fifth audit round: two billing and terminal-safety fixes, and two smaller
|
|
12
|
+
corrections.
|
|
13
|
+
|
|
14
|
+
**Incomplete usage is no longer priced as though the missing half were zero.**
|
|
15
|
+
A reply carrying `{}`, or only `prompt_tokens`, or only `completion_tokens`,
|
|
16
|
+
was charged with the absent side counted as nothing - so a run under
|
|
17
|
+
`--max-cost-usd` finished successfully having applied the ceiling to a partly
|
|
18
|
+
invented figure. A fractional count did worse: it crashed with an uncaught
|
|
19
|
+
error out of the pricing code. Both token counts and both prices must now be
|
|
20
|
+
present and valid; anything else is reported as unpriced, stops a run that
|
|
21
|
+
asked for a ceiling, and keeps the answer that was generated.
|
|
22
|
+
|
|
23
|
+
**Errors and diagnostics can no longer carry terminal control sequences.**
|
|
24
|
+
`mafdet doctor` interpolated a model's `apiSurface` straight into its table,
|
|
25
|
+
and a surface-mismatch error did the same into stderr. Rather than cleaning
|
|
26
|
+
those two strings, the cleaning now happens at the two boundaries every
|
|
27
|
+
command already funnels through - the shared error reporter and the diagnostic
|
|
28
|
+
table - so the checks and messages added later are covered too. Redirected
|
|
29
|
+
output and `--json` keep the bytes exactly.
|
|
30
|
+
|
|
31
|
+
**The context estimate measures UTF-8 bytes**, not UTF-16 code units, and
|
|
32
|
+
counts the whole request body. A Chinese conversation measured about 10%
|
|
33
|
+
smaller than it is on the wire, in the one estimate that must not under-count.
|
|
34
|
+
|
|
35
|
+
**Unused code is now a compile error** (`noUnusedLocals`,
|
|
36
|
+
`noUnusedParameters`). A dead import survived four review rounds because
|
|
37
|
+
nothing was looking for one; turning the check on found eight more.
|
|
38
|
+
|
|
9
39
|
## 0.4.8 - 2026-08-12
|
|
10
40
|
|
|
11
41
|
A fourth audit round: three findings, one of which was a regression I
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The official Mafdet AI command line: model calls, account queries, and a
|
|
4
4
|
permissioned local agent.
|
|
5
5
|
|
|
6
|
-
> **0.4.
|
|
6
|
+
> **0.4.9.** 中文版见 [README.zh-CN.md](./README.zh-CN.md). The CLI never
|
|
7
7
|
> advertises a command it cannot serve, so `mafdet help` always lists exactly
|
|
8
8
|
> what works.
|
|
9
9
|
>
|
|
@@ -23,8 +23,8 @@ Or build it from this repository:
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
pnpm --filter @mafdet/cli build
|
|
26
|
-
cd packages/cli && npm pack # -> mafdet-cli-0.4.
|
|
27
|
-
npm install -g ./mafdet-cli-0.4.
|
|
26
|
+
cd packages/cli && npm pack # -> mafdet-cli-0.4.9.tgz
|
|
27
|
+
npm install -g ./mafdet-cli-0.4.9.tgz
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## The interactive session
|
package/README.zh-CN.md
CHANGED
|
@@ -94,7 +94,7 @@ export declare function compactForBudget(history: ChatMessage[], keepRecent?: nu
|
|
|
94
94
|
* is not a constant across English, Chinese, code and JSON.
|
|
95
95
|
*
|
|
96
96
|
* What is measured here instead is the ratio between the provider's own token
|
|
97
|
-
* count and the
|
|
97
|
+
* count and the UTF-8 size of what we sent to earn it. Applied to the bytes we are
|
|
98
98
|
* about to send - schemas included - it needs no assumption about language or
|
|
99
99
|
* encoding, because both sides of the ratio came from the same request.
|
|
100
100
|
*
|
|
@@ -105,10 +105,24 @@ export declare function compactForBudget(history: ChatMessage[], keepRecent?: nu
|
|
|
105
105
|
* counted rather than assumed away.
|
|
106
106
|
*/
|
|
107
107
|
export declare function compactUntilItFits(history: ChatMessage[], contextLength: number | undefined, lastPromptTokens: number,
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
/**
|
|
108
|
+
/** Size of the request that produced `lastPromptTokens`, in UTF-8 bytes. */
|
|
109
|
+
lastRequestSize: number,
|
|
110
|
+
/** Size of the request as it stands now, recomputed after each removal. */
|
|
111
111
|
sizeNow: () => number, keepRecent?: number): CompactionResult;
|
|
112
|
-
/**
|
|
113
|
-
|
|
112
|
+
/**
|
|
113
|
+
* V-3 · The size of a request, as UTF-8 bytes.
|
|
114
|
+
*
|
|
115
|
+
* Named `requestSize` rather than `requestBytes` for a while it was neither:
|
|
116
|
+
* `String.length` counts UTF-16 code units, so a Chinese conversation measured
|
|
117
|
+
* about 10% smaller than it is on the wire - and the estimate that uses this
|
|
118
|
+
* is precisely the one that must not under-count. `Buffer.byteLength` counts
|
|
119
|
+
* what is actually sent.
|
|
120
|
+
*
|
|
121
|
+
* ⚠️ It feeds a heuristic, not a tokenizer. The ratio it participates in is
|
|
122
|
+
* measured from a real request, which is the best available answer without a
|
|
123
|
+
* server-side token count - but it cannot prove a request fits the window.
|
|
124
|
+
*/
|
|
125
|
+
export declare function requestSize(messages: readonly ChatMessage[], tools: unknown,
|
|
126
|
+
/** The rest of the body, so the measurement covers what is really sent. */
|
|
127
|
+
extra?: Record<string, unknown>): number;
|
|
114
128
|
//# sourceMappingURL=context-budget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget.d.ts","sourceRoot":"","sources":["../../src/agent/context-budget.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,OAAO,CAAC;AAE/B,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAsCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,GAAE,MAA8B,GACzC,WAAW,CAkBb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA0BpC;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,GAAE,MAA8B,GACzC,gBAAgB,CASlB;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,gBAAgB,EAAE,MAAM;AACxB,
|
|
1
|
+
{"version":3,"file":"context-budget.d.ts","sourceRoot":"","sources":["../../src/agent/context-budget.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,OAAO,CAAC;AAE/B,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAsCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,GAAE,MAA8B,GACzC,WAAW,CAkBb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,WAAW,EAAE,EACtB,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA0BpC;AAED,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,UAAU,GAAE,MAA8B,GACzC,gBAAgB,CASlB;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EAAE,EACtB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,gBAAgB,EAAE,MAAM;AACxB,4EAA4E;AAC5E,eAAe,EAAE,MAAM;AACvB,2EAA2E;AAC3E,OAAO,EAAE,MAAM,MAAM,EACrB,UAAU,GAAE,MAA8B,GACzC,gBAAgB,CA8BlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,KAAK,EAAE,OAAO;AACd,2EAA2E;AAC3E,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,MAAM,CAWR"}
|
|
@@ -192,7 +192,7 @@ export function compactForBudget(history, keepRecent = KEEP_RECENT_EXCHANGES) {
|
|
|
192
192
|
* is not a constant across English, Chinese, code and JSON.
|
|
193
193
|
*
|
|
194
194
|
* What is measured here instead is the ratio between the provider's own token
|
|
195
|
-
* count and the
|
|
195
|
+
* count and the UTF-8 size of what we sent to earn it. Applied to the bytes we are
|
|
196
196
|
* about to send - schemas included - it needs no assumption about language or
|
|
197
197
|
* encoding, because both sides of the ratio came from the same request.
|
|
198
198
|
*
|
|
@@ -203,12 +203,12 @@ export function compactForBudget(history, keepRecent = KEEP_RECENT_EXCHANGES) {
|
|
|
203
203
|
* counted rather than assumed away.
|
|
204
204
|
*/
|
|
205
205
|
export function compactUntilItFits(history, contextLength, lastPromptTokens,
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
/**
|
|
206
|
+
/** Size of the request that produced `lastPromptTokens`, in UTF-8 bytes. */
|
|
207
|
+
lastRequestSize,
|
|
208
|
+
/** Size of the request as it stands now, recomputed after each removal. */
|
|
209
209
|
sizeNow, keepRecent = KEEP_RECENT_EXCHANGES) {
|
|
210
210
|
const startedWith = history.length;
|
|
211
|
-
const tokensPerByte =
|
|
211
|
+
const tokensPerByte = lastRequestSize > 0 ? lastPromptTokens / lastRequestSize : 0;
|
|
212
212
|
let exchanges = 0;
|
|
213
213
|
let turns = 0;
|
|
214
214
|
for (;;) {
|
|
@@ -235,10 +235,24 @@ sizeNow, keepRecent = KEEP_RECENT_EXCHANGES) {
|
|
|
235
235
|
turns += step.turns;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
/**
|
|
239
|
-
|
|
238
|
+
/**
|
|
239
|
+
* V-3 · The size of a request, as UTF-8 bytes.
|
|
240
|
+
*
|
|
241
|
+
* Named `requestSize` rather than `requestBytes` for a while it was neither:
|
|
242
|
+
* `String.length` counts UTF-16 code units, so a Chinese conversation measured
|
|
243
|
+
* about 10% smaller than it is on the wire - and the estimate that uses this
|
|
244
|
+
* is precisely the one that must not under-count. `Buffer.byteLength` counts
|
|
245
|
+
* what is actually sent.
|
|
246
|
+
*
|
|
247
|
+
* ⚠️ It feeds a heuristic, not a tokenizer. The ratio it participates in is
|
|
248
|
+
* measured from a real request, which is the best available answer without a
|
|
249
|
+
* server-side token count - but it cannot prove a request fits the window.
|
|
250
|
+
*/
|
|
251
|
+
export function requestSize(messages, tools,
|
|
252
|
+
/** The rest of the body, so the measurement covers what is really sent. */
|
|
253
|
+
extra = {}) {
|
|
240
254
|
try {
|
|
241
|
-
return JSON.stringify({ messages, tools })
|
|
255
|
+
return Buffer.byteLength(JSON.stringify({ ...extra, messages, tools }), "utf8");
|
|
242
256
|
}
|
|
243
257
|
catch {
|
|
244
258
|
// Circular or unserialisable: fall back to something monotone in the
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiError } from "../api/errors.js";
|
|
2
|
-
import { compactUntilItFits,
|
|
2
|
+
import { compactUntilItFits, requestSize, shouldCompact, } from "./context-budget.js";
|
|
3
3
|
import { toolToJsonSchema, } from "./tools/types.js";
|
|
4
4
|
/** Plan §7.4. */
|
|
5
5
|
export const DEFAULT_LIMITS = {
|
|
@@ -48,8 +48,8 @@ export async function runAgent(options) {
|
|
|
48
48
|
};
|
|
49
49
|
/** What the provider counted for the previous request; 0 until one lands. */
|
|
50
50
|
let lastPromptTokens = 0;
|
|
51
|
-
/** U-1 ·
|
|
52
|
-
let
|
|
51
|
+
/** U-1 · UTF-8 size of the request that produced `lastPromptTokens`. */
|
|
52
|
+
let lastRequestSize = 0;
|
|
53
53
|
// T-3 · Known before anything is sent. Waiting for the first response to
|
|
54
54
|
// discover it meant a run with an explicit ceiling still made one billable
|
|
55
55
|
// request that could never be counted against it.
|
|
@@ -76,7 +76,10 @@ export async function runAgent(options) {
|
|
|
76
76
|
// R-2 · Keep going until the estimate is inside the budget. Dropping one
|
|
77
77
|
// exchange and sending anyway left a history that was still over the
|
|
78
78
|
// window: the request failed upstream and was billed for the attempt.
|
|
79
|
-
const compacted = compactUntilItFits(messages, options.pricing?.contextLength, lastPromptTokens,
|
|
79
|
+
const compacted = compactUntilItFits(messages, options.pricing?.contextLength, lastPromptTokens, lastRequestSize, () => requestSize(messages, toolSchemas, {
|
|
80
|
+
model: options.model,
|
|
81
|
+
tool_choice: "auto",
|
|
82
|
+
}));
|
|
80
83
|
if (compacted.exhausted) {
|
|
81
84
|
// F-008 · Nothing left that can be dropped without breaking the
|
|
82
85
|
// conversation. Sending anyway means a request over the window, which
|
|
@@ -100,7 +103,10 @@ export async function runAgent(options) {
|
|
|
100
103
|
}
|
|
101
104
|
// U-1 · Measured for the request we are about to make, so the ratio the
|
|
102
105
|
// next compaction uses is the provider's own count over these exact bytes.
|
|
103
|
-
const
|
|
106
|
+
const sentSize = requestSize(messages, toolSchemas, {
|
|
107
|
+
model: options.model,
|
|
108
|
+
tool_choice: "auto",
|
|
109
|
+
});
|
|
104
110
|
let response;
|
|
105
111
|
try {
|
|
106
112
|
response = await requestWithRateLimitHandling(options, limits, {
|
|
@@ -122,7 +128,7 @@ export async function runAgent(options) {
|
|
|
122
128
|
const usage = response.usage;
|
|
123
129
|
if (usage?.prompt_tokens !== undefined) {
|
|
124
130
|
lastPromptTokens = usage.prompt_tokens;
|
|
125
|
-
|
|
131
|
+
lastRequestSize = sentSize;
|
|
126
132
|
}
|
|
127
133
|
report.promptTokens += usage?.prompt_tokens ?? 0;
|
|
128
134
|
report.completionTokens += usage?.completion_tokens ?? 0;
|
|
@@ -157,9 +163,9 @@ export async function runAgent(options) {
|
|
|
157
163
|
// had never been applied to anything.
|
|
158
164
|
if (limits.maxCostMicroUsd !== undefined &&
|
|
159
165
|
!canPrice(options.pricing, usage)) {
|
|
160
|
-
return stop(report, "cost_unknown", `--max-cost-usd cannot be enforced:
|
|
161
|
-
`
|
|
162
|
-
`the ceiling.`);
|
|
166
|
+
return stop(report, "cost_unknown", `--max-cost-usd cannot be enforced: usage was missing or incomplete ` +
|
|
167
|
+
`in the response for ${options.model}, so this request could not be ` +
|
|
168
|
+
`counted towards the ceiling.`);
|
|
163
169
|
}
|
|
164
170
|
if (calls.length === 0) {
|
|
165
171
|
// Keep the answer in the history so a session's next turn has context.
|
|
@@ -311,21 +317,46 @@ export function backoffMs(retryAfterSeconds, attempt, random = Math.random) {
|
|
|
311
317
|
// Full jitter: a fleet of CLIs must not retry in lockstep.
|
|
312
318
|
return Math.round(base * (0.5 + random() * 0.5));
|
|
313
319
|
}
|
|
314
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* V-1 · Whether this turn can be priced at all.
|
|
322
|
+
*
|
|
323
|
+
* "There is a usage object" is not the same as "there is a usage". A reply can
|
|
324
|
+
* carry `{}`, or only `prompt_tokens`, or a fractional or negative count - and
|
|
325
|
+
* each of those used to be priced as though the missing half were zero, so a
|
|
326
|
+
* run under `--max-cost-usd` finished successfully having applied the ceiling
|
|
327
|
+
* to a number that was partly invented. A fractional count did worse: it threw
|
|
328
|
+
* out of `priceTurn` as an uncaught RangeError.
|
|
329
|
+
*
|
|
330
|
+
* Both token counts and both prices have to be there and be usable. Anything
|
|
331
|
+
* short of that is unpriced, which the caller reports rather than rounds down.
|
|
332
|
+
*/
|
|
333
|
+
function isTokenCount(value) {
|
|
334
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
335
|
+
}
|
|
336
|
+
/** Integer micro-USD, non-negative. Anything else is not a price. */
|
|
337
|
+
function isPrice(value) {
|
|
338
|
+
return typeof value === "string" && /^\d+$/.test(value);
|
|
339
|
+
}
|
|
315
340
|
function canPrice(model, usage) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
341
|
+
if (usage === undefined)
|
|
342
|
+
return false;
|
|
343
|
+
if (!isTokenCount(usage.prompt_tokens))
|
|
344
|
+
return false;
|
|
345
|
+
if (!isTokenCount(usage.completion_tokens))
|
|
346
|
+
return false;
|
|
347
|
+
return (isPrice(model?.inputPricePer1mMicroUsd) &&
|
|
348
|
+
isPrice(model?.outputPricePer1mMicroUsd));
|
|
319
349
|
}
|
|
320
350
|
function priceTurn(model, usage) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
351
|
+
// V-1 · One gate, shared with the caller's reporting, so a turn can never be
|
|
352
|
+
// charged for and reported as unpriced - or the reverse.
|
|
353
|
+
if (!canPrice(model, usage))
|
|
324
354
|
return 0n;
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
355
|
+
const prompt = usage?.prompt_tokens ?? 0;
|
|
356
|
+
const completion = usage?.completion_tokens ?? 0;
|
|
357
|
+
const ceilDiv = (tokens, price) => (BigInt(tokens) * BigInt(price) + 999999n) / 1000000n;
|
|
358
|
+
return (ceilDiv(prompt, model?.inputPricePer1mMicroUsd ?? "0") +
|
|
359
|
+
ceilDiv(completion, model?.outputPricePer1mMicroUsd ?? "0"));
|
|
329
360
|
}
|
|
330
361
|
function stop(report, reason, error) {
|
|
331
362
|
report.stopReason = reason;
|
package/dist/agent/session.js
CHANGED
|
@@ -38,7 +38,7 @@ export async function runSession(options) {
|
|
|
38
38
|
let pricing = options.pricing;
|
|
39
39
|
const source = options.readLine
|
|
40
40
|
? undefined
|
|
41
|
-
: lineSource(options.input ?? process.stdin
|
|
41
|
+
: lineSource(options.input ?? process.stdin);
|
|
42
42
|
const readLine = options.readLine ??
|
|
43
43
|
(() => {
|
|
44
44
|
options.stderr("\n> ");
|
|
@@ -253,7 +253,7 @@ export function compactHistory(history) {
|
|
|
253
253
|
* a queue keeps them, and end-of-input only reports undefined once the queue
|
|
254
254
|
* is actually empty.
|
|
255
255
|
*/
|
|
256
|
-
function lineSource(input
|
|
256
|
+
function lineSource(input) {
|
|
257
257
|
const rl = createInterface({
|
|
258
258
|
input,
|
|
259
259
|
output: process.stderr,
|
|
@@ -55,7 +55,7 @@ export async function executeCommand(argv, cwd, timeoutMs = COMMAND_TIMEOUT_MS)
|
|
|
55
55
|
clearTimeout(timer);
|
|
56
56
|
resolve({ code, signal, output: stripAnsi(output), timedOut });
|
|
57
57
|
};
|
|
58
|
-
child.on("error", (
|
|
58
|
+
child.on("error", () => finish(null, null));
|
|
59
59
|
child.on("close", (code, signal) => finish(code, signal));
|
|
60
60
|
});
|
|
61
61
|
}
|
package/dist/api/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/api/models.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/api/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gDAAgD;IAChD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAUxE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,SAAS,CA4BX;AAED,8CAA8C;AAC9C,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,cAAc,EAAE,CAAC,CAG3B;AAED,2EAA2E;AAC3E,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAGzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,YAAY,GAAG,SAAS,CAY1B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM;gBAFhB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM;CAO5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAKnD,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAY5D,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,QAAQ,GACX,OAAO,CAAC,QAAQ,CAAC,CAyFnB"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ConfigError } from "../config/config.js";
|
|
|
7
7
|
import { ExitCode } from "../output/exit-codes.js";
|
|
8
8
|
import { parseFormatFlags, renderJson, renderTable } from "../output/format.js";
|
|
9
9
|
import { formatUsd } from "../output/money.js";
|
|
10
|
+
import { safeForTerminal } from "../output/terminal-safe.js";
|
|
10
11
|
import { keyFingerprint, redact } from "../security/redact.js";
|
|
11
12
|
import { buildRuntime } from "./runtime.js";
|
|
12
13
|
const MINIMUM_NODE_MAJOR = 20;
|
|
@@ -141,14 +142,20 @@ function describe(error) {
|
|
|
141
142
|
function report(checks, format, io) {
|
|
142
143
|
const failed = checks.filter((check) => check.status === "fail").length;
|
|
143
144
|
if (format === "json") {
|
|
145
|
+
// Exact values, as everywhere else that a machine reads.
|
|
144
146
|
io.stdout(renderJson({ ok: failed === 0, checks }));
|
|
145
147
|
}
|
|
146
148
|
else {
|
|
147
149
|
const mark = { ok: "ok", warn: "warn", fail: "FAIL" };
|
|
150
|
+
// V-2 · One place every check's detail is drawn, and several of them are
|
|
151
|
+
// built from remote text - a model's apiSurface, an endpoint, an upstream
|
|
152
|
+
// error. Cleaning here covers the checks that exist and the ones added
|
|
153
|
+
// later; cleaning each check's string did not.
|
|
154
|
+
const tty = io.isTty ?? process.stdout.isTTY === true;
|
|
148
155
|
io.stdout(renderTable(checks, [
|
|
149
156
|
{ header: "", value: (c) => mark[c.status] },
|
|
150
|
-
{ header: "CHECK", value: (c) => c.name },
|
|
151
|
-
{ header: "DETAIL", value: (c) => c.detail },
|
|
157
|
+
{ header: "CHECK", value: (c) => safeForTerminal(c.name, tty) },
|
|
158
|
+
{ header: "DETAIL", value: (c) => safeForTerminal(c.detail, tty) },
|
|
152
159
|
]));
|
|
153
160
|
}
|
|
154
161
|
// Warnings are survivable — a missing scope or an unverified model does not
|
|
@@ -16,6 +16,12 @@ export interface CommandIo {
|
|
|
16
16
|
* Defaults to `process.stdout.isTTY`; tests set it explicitly.
|
|
17
17
|
*/
|
|
18
18
|
isTty?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* V-2 · Whether stderr is a terminal. Separate from `isTty` because
|
|
21
|
+
* `mafdet ... 2> log` redirects one and not the other, and the rule is about
|
|
22
|
+
* the destination rather than the command.
|
|
23
|
+
*/
|
|
24
|
+
isErrTty?: boolean;
|
|
19
25
|
}
|
|
20
26
|
export interface Runtime {
|
|
21
27
|
config: LoadedConfig;
|
|
@@ -31,5 +37,15 @@ export declare function requireApiKey(config: LoadedConfig): void;
|
|
|
31
37
|
* Turn any failure into an exit code and a single stderr line. Nothing here
|
|
32
38
|
* prints a raw upstream message: ApiError redacted it at construction.
|
|
33
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* V-2 · The one place errors reach a person.
|
|
42
|
+
*
|
|
43
|
+
* Every command funnels through here, so this is where untrusted text in an
|
|
44
|
+
* error message stops being able to drive the terminal. Patching the
|
|
45
|
+
* individual messages was tried and missed two of them; a message is
|
|
46
|
+
* assembled from remote data far more often than anyone remembers.
|
|
47
|
+
*
|
|
48
|
+
* Redirected stderr keeps the bytes, the same rule as everywhere else.
|
|
49
|
+
*/
|
|
34
50
|
export declare function reportError(error: unknown, io: CommandIo): ExitCode;
|
|
35
51
|
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGnD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,sCAAsC;IACtC,SAAS,EAAE,UAAU,CAAC;IACtB,yCAAyC;IACzC,YAAY,EAAE,UAAU,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAgBnD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CASxD;AAED;;;GAGG;AACH;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,GAAG,QAAQ,CAqBnE"}
|
package/dist/commands/runtime.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loadConfig } from "../config/load.js";
|
|
|
3
3
|
import { HttpClient } from "../api/http.js";
|
|
4
4
|
import { ApiError } from "../api/errors.js";
|
|
5
5
|
import { ExitCode } from "../output/exit-codes.js";
|
|
6
|
+
import { safeForTerminal } from "../output/terminal-safe.js";
|
|
6
7
|
export function userAgent(version) {
|
|
7
8
|
// Identifiable, and carrying nothing about the user (plan §9.2).
|
|
8
9
|
return `mafdet-cli/${version}`;
|
|
@@ -37,10 +38,21 @@ export function requireApiKey(config) {
|
|
|
37
38
|
* Turn any failure into an exit code and a single stderr line. Nothing here
|
|
38
39
|
* prints a raw upstream message: ApiError redacted it at construction.
|
|
39
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* V-2 · The one place errors reach a person.
|
|
43
|
+
*
|
|
44
|
+
* Every command funnels through here, so this is where untrusted text in an
|
|
45
|
+
* error message stops being able to drive the terminal. Patching the
|
|
46
|
+
* individual messages was tried and missed two of them; a message is
|
|
47
|
+
* assembled from remote data far more often than anyone remembers.
|
|
48
|
+
*
|
|
49
|
+
* Redirected stderr keeps the bytes, the same rule as everywhere else.
|
|
50
|
+
*/
|
|
40
51
|
export function reportError(error, io) {
|
|
52
|
+
const safe = (text) => safeForTerminal(text, io.isErrTty ?? process.stderr.isTTY === true);
|
|
41
53
|
if (error instanceof ApiError) {
|
|
42
54
|
const id = error.requestId ? ` (request ${error.requestId})` : "";
|
|
43
|
-
io.stderr(`mafdet: ${error.message}${id}\n`);
|
|
55
|
+
io.stderr(`mafdet: ${safe(error.message)}${id}\n`);
|
|
44
56
|
if (error.kind === "rate_limited" &&
|
|
45
57
|
error.retryAfterSeconds !== undefined) {
|
|
46
58
|
io.stderr(`mafdet: retry after ${error.retryAfterSeconds}s\n`);
|
|
@@ -48,9 +60,9 @@ export function reportError(error, io) {
|
|
|
48
60
|
return error.exitCode;
|
|
49
61
|
}
|
|
50
62
|
if (error instanceof ConfigError) {
|
|
51
|
-
io.stderr(`mafdet: ${error.message}\n`);
|
|
63
|
+
io.stderr(`mafdet: ${safe(error.message)}\n`);
|
|
52
64
|
return ExitCode.Usage;
|
|
53
65
|
}
|
|
54
|
-
io.stderr(`mafdet: ${error.message}\n`);
|
|
66
|
+
io.stderr(`mafdet: ${safe(error.message)}\n`);
|
|
55
67
|
return ExitCode.General;
|
|
56
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4BnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4BnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAetB,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB;AAoRD,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAUX,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../src/sessions/open.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,oBAAoB,EAGpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAKrB,OAAO,EAAE,aAAa,EAAe,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;CACpD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;CAC/D,CAAC;AAEN,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,UAAU,CAqFnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAC9C,MAAM,CAyBR"}
|
|
@@ -53,7 +53,7 @@ export async function promptLine(io, prompt) {
|
|
|
53
53
|
}
|
|
54
54
|
export async function runAgentSurface(options) {
|
|
55
55
|
const { io, capabilities } = options;
|
|
56
|
-
const { dim
|
|
56
|
+
const { dim } = palette(capabilities);
|
|
57
57
|
const cards = new ToolCardLog();
|
|
58
58
|
const run = options.run ?? runSession;
|
|
59
59
|
const onEvent = (event) => {
|