@mafdet/cli 0.4.11 → 0.4.12
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 +16 -3
- package/README.md +8 -7
- package/README.zh-CN.md +5 -5
- package/dist/agent/loop.d.ts +1 -9
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +14 -7
- package/dist/api/account.d.ts.map +1 -1
- package/dist/api/account.js +30 -11
- package/dist/api/chat-response.d.ts +32 -0
- package/dist/api/chat-response.d.ts.map +1 -0
- package/dist/api/chat-response.js +39 -0
- package/dist/api/models.d.ts.map +1 -1
- package/dist/api/models.js +5 -5
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +6 -2
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +2 -1
- package/dist/sessions/reconcile.d.ts.map +1 -1
- package/dist/sessions/reconcile.js +8 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ 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.12 - 2026-08-12
|
|
10
|
+
|
|
11
|
+
- Validate usage-event pages, model identities, non-stream Chat responses and
|
|
12
|
+
Agent tool calls at the HTTP boundary. Malformed remote data now produces a
|
|
13
|
+
typed unavailable error instead of an empty success or a runtime exception.
|
|
14
|
+
- Require account and settled ledger amounts to be non-negative integer
|
|
15
|
+
micro-USD strings. An invalid settlement remains pending; it is never
|
|
16
|
+
rewritten as a fabricated zero-dollar settlement.
|
|
17
|
+
- Correct the reconciliation documentation: refresh queries the current key's
|
|
18
|
+
paged usage-event window and matches request IDs locally, while retaining
|
|
19
|
+
local estimates alongside settled figures.
|
|
20
|
+
|
|
9
21
|
## 0.4.11 - 2026-08-12
|
|
10
22
|
|
|
11
23
|
A seventh audit round, fixed at the root rather than sink by sink - which is
|
|
@@ -343,9 +355,10 @@ each platform, and that deleting one is an ordinary file delete rather than a
|
|
|
343
355
|
secure erase.
|
|
344
356
|
|
|
345
357
|
`mafdet sessions usage <id> --refresh` asks the billing ledger what the
|
|
346
|
-
requests actually cost and
|
|
347
|
-
|
|
348
|
-
|
|
358
|
+
requests actually cost and records the settled figure alongside the local
|
|
359
|
+
estimate. **Correction:** it queries the current key's paged usage-event
|
|
360
|
+
window; request IDs stay local for matching rather than being sent. No
|
|
361
|
+
messages, title, workspace or session ID leave the machine. Requests paid for by a
|
|
349
362
|
different API key are left alone and reported rather than guessed at, and a
|
|
350
363
|
request the ledger has not settled stays `pending` rather than being shown as
|
|
351
364
|
zero.
|
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.12.** 中文版见 [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.12.tgz
|
|
27
|
+
npm install -g ./mafdet-cli-0.4.12.tgz
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## The interactive session
|
|
@@ -87,7 +87,7 @@ prompt as an argument, or use `--input <file>`.
|
|
|
87
87
|
| `mafdet speech [text\|<file>] --model <id> --output <file> --voice <name>` | Text to an audio file |
|
|
88
88
|
| `mafdet account balance\|usage\|subscription` | Wallet, spend and plan (needs the `account:read` scope) |
|
|
89
89
|
| `mafdet sessions list\|show\|usage\|delete` | Conversations saved on this computer (never calls a model) |
|
|
90
|
-
| `mafdet sessions usage <id> --refresh` |
|
|
90
|
+
| `mafdet sessions usage <id> --refresh` | Show settled ledger figures alongside the local estimate |
|
|
91
91
|
| `mafdet config list` | The resolved settings and where each came from |
|
|
92
92
|
|
|
93
93
|
`--json` on any query command emits one complete JSON document; `--plain`
|
|
@@ -247,9 +247,10 @@ erase — backups, snapshots and the filesystem itself may still hold copies.
|
|
|
247
247
|
record. Those charges are estimates, computed with the gateway's own
|
|
248
248
|
arithmetic but computed here.
|
|
249
249
|
|
|
250
|
-
`--refresh` asks the billing ledger for
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
`--refresh` asks the billing ledger for settled figures. It pages through the
|
|
251
|
+
current API key's usage events for the session's time window; request IDs stay
|
|
252
|
+
local and are used only to match returned rows. No messages, title, workspace
|
|
253
|
+
or session ID are sent. Requests paid for by a different API key are left
|
|
253
254
|
alone and reported, so you know to run it again with that key.
|
|
254
255
|
|
|
255
256
|
A request that the ledger has not caught up with stays `pending`, and a request
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Mafdet AI 官方命令行:模型调用、账户查询,以及一个受权限约束的本地 Agent。
|
|
4
4
|
|
|
5
|
-
> **0.4.
|
|
5
|
+
> **0.4.12。** 英文版见 [README.md](./README.md)。
|
|
6
6
|
|
|
7
7
|
## 环境要求
|
|
8
8
|
|
|
@@ -73,7 +73,7 @@ mafdet doctor
|
|
|
73
73
|
| `mafdet agent`(不带目标,需终端) | 交互会话 |
|
|
74
74
|
| `mafdet account balance\|usage\|subscription` | 钱包、用量、套餐(需 `account:read` scope) |
|
|
75
75
|
| `mafdet sessions list\|show\|usage\|delete` | 保存在这台电脑上的会话(不会调用模型) |
|
|
76
|
-
| `mafdet sessions usage <id> --refresh` |
|
|
76
|
+
| `mafdet sessions usage <id> --refresh` | 在本地估算旁显示账单账本的已结算金额 |
|
|
77
77
|
| `mafdet config list` | 当前生效的配置及其来源 |
|
|
78
78
|
|
|
79
79
|
查询类命令都支持 `--json`(输出一份完整 JSON 文档)与 `--plain`(无表头的 TSV,方便
|
|
@@ -214,9 +214,9 @@ mafdet sessions delete <id> --yes
|
|
|
214
214
|
`mafdet sessions usage <id>` 从本地记录累加一次会话的用量。那些金额是**估算**,
|
|
215
215
|
算法与网关一致,但是在本地算的。
|
|
216
216
|
|
|
217
|
-
`--refresh`
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
`--refresh` 会按会话时间窗口和游标查询当前 API Key 的用量事件;request ID 只留在本地,
|
|
218
|
+
用于匹配账本返回的记录。它不发送消息、标题、工作区路径或 session ID。由另一把 API Key
|
|
219
|
+
支付的请求会被跳过并如实告知,好让你知道该换那把 Key 再跑一次。
|
|
220
220
|
|
|
221
221
|
账本还没跟上的请求保持 `pending`,从未拿到 ID 的请求保持 `unknown`。
|
|
222
222
|
**两者都绝不显示为 `0`**——0 读起来像「免费」。
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolCall } from "../api/chat-response.js";
|
|
1
2
|
import type { HttpClient } from "../api/http.js";
|
|
2
3
|
import type { CatalogModel } from "../api/models.js";
|
|
3
4
|
import type { Approver } from "./approvals.js";
|
|
@@ -146,14 +147,6 @@ export interface ChatMessage {
|
|
|
146
147
|
tool_calls?: ToolCall[];
|
|
147
148
|
tool_call_id?: string;
|
|
148
149
|
}
|
|
149
|
-
interface ToolCall {
|
|
150
|
-
id: string;
|
|
151
|
-
type: "function";
|
|
152
|
-
function: {
|
|
153
|
-
name: string;
|
|
154
|
-
arguments: string;
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
150
|
/**
|
|
158
151
|
* The read-only agent loop.
|
|
159
152
|
*
|
|
@@ -180,5 +173,4 @@ export declare function backoffMs(retryAfterSeconds: number | undefined, attempt
|
|
|
180
173
|
* direction - a free model would have been refused.
|
|
181
174
|
*/
|
|
182
175
|
export declare function hasValidPrices(model: CatalogModel | undefined): boolean;
|
|
183
|
-
export {};
|
|
184
176
|
//# sourceMappingURL=loop.d.ts.map
|
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":"
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { ApiError } from "../api/errors.js";
|
|
2
|
+
import { parseChatCompletion, } from "../api/chat-response.js";
|
|
2
3
|
import { compactUntilItFits, requestSize, shouldCompact, } from "./context-budget.js";
|
|
3
4
|
import { isMicroUsd } from "../output/money.js";
|
|
4
5
|
import { toolToJsonSchema, } from "./tools/types.js";
|
|
@@ -130,7 +131,7 @@ export async function runAgent(options) {
|
|
|
130
131
|
// report and fed into the compaction ratio - it no longer priced anything,
|
|
131
132
|
// but it still produced negative token totals and steered a decision about
|
|
132
133
|
// what to send next.
|
|
133
|
-
const usage = response.usage;
|
|
134
|
+
const usage = usageObject(response.usage);
|
|
134
135
|
const promptTokens = isTokenCount(usage?.prompt_tokens)
|
|
135
136
|
? usage.prompt_tokens
|
|
136
137
|
: undefined;
|
|
@@ -162,9 +163,8 @@ export async function runAgent(options) {
|
|
|
162
163
|
});
|
|
163
164
|
if (!canPrice(options.pricing, usage))
|
|
164
165
|
report.unpricedRequests += 1;
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const calls = message?.tool_calls ?? [];
|
|
166
|
+
const message = response.choices[0].message;
|
|
167
|
+
const calls = message.tool_calls ?? [];
|
|
168
168
|
if (typeof message?.content === "string" && message.content.length > 0) {
|
|
169
169
|
report.finalText = message.content;
|
|
170
170
|
options.onEvent?.({ type: "text", text: message.content });
|
|
@@ -295,7 +295,7 @@ async function requestWithRateLimitHandling(options, limits, body, report, clock
|
|
|
295
295
|
const { data, requestId } = await options.client.postJson("/chat/completions", body, { signal: options.signal });
|
|
296
296
|
// Carried on the response so the caller can tie this turn to its ledger
|
|
297
297
|
// row without the loop needing to know what a session is.
|
|
298
|
-
return { ...data, requestId };
|
|
298
|
+
return { ...parseChatCompletion(data, "agent API"), requestId };
|
|
299
299
|
}
|
|
300
300
|
catch (error) {
|
|
301
301
|
// Everything except an explicit 429 propagates. A timeout or a 5xx may
|
|
@@ -346,6 +346,11 @@ export function backoffMs(retryAfterSeconds, attempt, random = Math.random) {
|
|
|
346
346
|
function isTokenCount(value) {
|
|
347
347
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
348
348
|
}
|
|
349
|
+
function usageObject(value) {
|
|
350
|
+
return value !== null && typeof value === "object"
|
|
351
|
+
? value
|
|
352
|
+
: undefined;
|
|
353
|
+
}
|
|
349
354
|
/**
|
|
350
355
|
* W-1 · Can this model be priced at all, before anything is sent?
|
|
351
356
|
*
|
|
@@ -376,8 +381,10 @@ function priceTurn(model, usage) {
|
|
|
376
381
|
// charged for and reported as unpriced - or the reverse.
|
|
377
382
|
if (!canPrice(model, usage))
|
|
378
383
|
return 0n;
|
|
379
|
-
const prompt = usage?.prompt_tokens
|
|
380
|
-
const completion = usage?.completion_tokens
|
|
384
|
+
const prompt = usage?.prompt_tokens;
|
|
385
|
+
const completion = usage?.completion_tokens;
|
|
386
|
+
if (!isTokenCount(prompt) || !isTokenCount(completion))
|
|
387
|
+
return 0n;
|
|
381
388
|
const ceilDiv = (tokens, price) => (BigInt(tokens) * BigInt(price) + 999999n) / 1000000n;
|
|
382
389
|
return (ceilDiv(prompt, model?.inputPricePer1mMicroUsd ?? "0") +
|
|
383
390
|
ceilDiv(completion, model?.outputPricePer1mMicroUsd ?? "0"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/api/account.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/api/account.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AA8C5C;;;GAGG;AAEH,MAAM,WAAW,MAAM;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE;QACX,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAyBD,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrE;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,UAAU,EAClB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC,CAMvB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,CAAC,CAKvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,yBAAyB,EAAE,MAAM,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACjB,GACL,OAAO,CAAC,iBAAiB,CAAC,CAiC5B"}
|
package/dist/api/account.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { ApiError } from "./errors.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { isMicroUsd } from "../output/money.js";
|
|
3
4
|
import { parseApiData } from "./validate.js";
|
|
4
|
-
const
|
|
5
|
+
const nonnegativeMoney = z.string().refine(isMicroUsd);
|
|
5
6
|
const count = z.number().safe().int().nonnegative();
|
|
6
7
|
const walletSchema = z
|
|
7
8
|
.object({
|
|
8
|
-
balance_micro_usd:
|
|
9
|
-
bonus_micro_usd:
|
|
10
|
-
frozen_micro_usd:
|
|
11
|
-
available_micro_usd:
|
|
9
|
+
balance_micro_usd: nonnegativeMoney,
|
|
10
|
+
bonus_micro_usd: nonnegativeMoney,
|
|
11
|
+
frozen_micro_usd: nonnegativeMoney,
|
|
12
|
+
available_micro_usd: nonnegativeMoney,
|
|
12
13
|
currency_anchor: z.string(),
|
|
13
14
|
})
|
|
14
15
|
.passthrough();
|
|
@@ -21,7 +22,7 @@ const usageSummarySchema = z
|
|
|
21
22
|
input_tokens: count,
|
|
22
23
|
output_tokens: count,
|
|
23
24
|
total_tokens: count,
|
|
24
|
-
charge_micro_usd:
|
|
25
|
+
charge_micro_usd: nonnegativeMoney,
|
|
25
26
|
})
|
|
26
27
|
.passthrough();
|
|
27
28
|
const subscriptionSchema = z
|
|
@@ -32,9 +33,9 @@ const subscriptionSchema = z
|
|
|
32
33
|
current_period_end: z.string().optional(),
|
|
33
34
|
playground: z
|
|
34
35
|
.object({
|
|
35
|
-
allowance_micro_usd:
|
|
36
|
-
used_micro_usd:
|
|
37
|
-
remaining_micro_usd:
|
|
36
|
+
allowance_micro_usd: nonnegativeMoney,
|
|
37
|
+
used_micro_usd: nonnegativeMoney,
|
|
38
|
+
remaining_micro_usd: nonnegativeMoney,
|
|
38
39
|
usage_percent: z.number().finite(),
|
|
39
40
|
state: z.string(),
|
|
40
41
|
})
|
|
@@ -80,6 +81,23 @@ export async function fetchSubscription(client) {
|
|
|
80
81
|
.catch(explainScope);
|
|
81
82
|
return parseApiData(subscriptionSchema, data, "subscription API");
|
|
82
83
|
}
|
|
84
|
+
const usageEventSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
request_id: z.string().min(1),
|
|
87
|
+
model_id: z.string().min(1),
|
|
88
|
+
input_tokens: count,
|
|
89
|
+
output_tokens: count,
|
|
90
|
+
platform_charge_micro_usd: nonnegativeMoney,
|
|
91
|
+
status: z.string().min(1),
|
|
92
|
+
created_at: z.string().min(1),
|
|
93
|
+
})
|
|
94
|
+
.passthrough();
|
|
95
|
+
const usageEventsPageSchema = z
|
|
96
|
+
.object({
|
|
97
|
+
data: z.array(usageEventSchema),
|
|
98
|
+
next_cursor: z.string().nullable().optional(),
|
|
99
|
+
})
|
|
100
|
+
.passthrough();
|
|
83
101
|
/** Hard stop on paging, so a busy key cannot turn one command into hundreds. */
|
|
84
102
|
export const MAX_USAGE_EVENT_PAGES = 20;
|
|
85
103
|
/**
|
|
@@ -106,15 +124,16 @@ export async function fetchUsageEvents(client, options = {}) {
|
|
|
106
124
|
signal: options.signal,
|
|
107
125
|
})
|
|
108
126
|
.catch(explainScope);
|
|
127
|
+
const page = parseApiData(usageEventsPageSchema, data, "usage events API");
|
|
109
128
|
pages += 1;
|
|
110
|
-
for (const event of
|
|
129
|
+
for (const event of page.data) {
|
|
111
130
|
events.push(event);
|
|
112
131
|
remaining?.delete(event.request_id);
|
|
113
132
|
}
|
|
114
133
|
if (remaining !== undefined && remaining.size === 0) {
|
|
115
134
|
return { events, pages, exhausted: true };
|
|
116
135
|
}
|
|
117
|
-
cursor =
|
|
136
|
+
cursor = page.next_cursor ?? null;
|
|
118
137
|
if (cursor === null)
|
|
119
138
|
return { events, pages, exhausted: true };
|
|
120
139
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const toolCallSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
type: z.ZodLiteral<"function">;
|
|
5
|
+
function: z.ZodObject<{
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
arguments: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
}, z.core.$loose>;
|
|
10
|
+
declare const chatCompletionSchema: z.ZodObject<{
|
|
11
|
+
choices: z.ZodArray<z.ZodObject<{
|
|
12
|
+
message: z.ZodObject<{
|
|
13
|
+
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
14
|
+
tool_calls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
type: z.ZodLiteral<"function">;
|
|
17
|
+
function: z.ZodObject<{
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
arguments: z.ZodString;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
}, z.core.$loose>>>;
|
|
22
|
+
}, z.core.$loose>;
|
|
23
|
+
finish_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
|
+
}, z.core.$loose>>;
|
|
25
|
+
usage: z.ZodOptional<z.ZodUnknown>;
|
|
26
|
+
}, z.core.$loose>;
|
|
27
|
+
export type ToolCall = z.infer<typeof toolCallSchema>;
|
|
28
|
+
export type ChatCompletion = z.infer<typeof chatCompletionSchema>;
|
|
29
|
+
export declare function parseChatCompletion(value: unknown, label?: string): ChatCompletion;
|
|
30
|
+
export declare function parseChatTextCompletion(value: unknown, label?: string): ChatCompletion;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=chat-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-response.d.ts","sourceRoot":"","sources":["../../src/api/chat-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,cAAc;;;;;;;iBASJ,CAAC;AAEjB,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;iBAqBV,CAAC;AAMjB,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,KAAK,SAAa,GACjB,cAAc,CAEhB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,KAAK,SAAa,GACjB,cAAc,CAEhB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { parseApiData } from "./validate.js";
|
|
3
|
+
const toolCallSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
id: z.string().min(1),
|
|
6
|
+
type: z.literal("function"),
|
|
7
|
+
function: z.object({
|
|
8
|
+
name: z.string().min(1),
|
|
9
|
+
arguments: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
})
|
|
12
|
+
.passthrough();
|
|
13
|
+
const chatCompletionSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
choices: z
|
|
16
|
+
.array(z
|
|
17
|
+
.object({
|
|
18
|
+
message: z
|
|
19
|
+
.object({
|
|
20
|
+
content: z.string().nullable().optional(),
|
|
21
|
+
tool_calls: z.array(toolCallSchema).optional(),
|
|
22
|
+
})
|
|
23
|
+
.passthrough(),
|
|
24
|
+
finish_reason: z.string().nullable().optional(),
|
|
25
|
+
})
|
|
26
|
+
.passthrough())
|
|
27
|
+
.min(1),
|
|
28
|
+
// Usage is validated independently. A usable, already-billed answer must
|
|
29
|
+
// survive malformed accounting and be reported with unknown usage.
|
|
30
|
+
usage: z.unknown().optional(),
|
|
31
|
+
})
|
|
32
|
+
.passthrough();
|
|
33
|
+
const chatTextCompletionSchema = chatCompletionSchema.refine((response) => typeof response.choices[0]?.message.content === "string", { path: ["choices", 0, "message", "content"] });
|
|
34
|
+
export function parseChatCompletion(value, label = "chat API") {
|
|
35
|
+
return parseApiData(chatCompletionSchema, value, label);
|
|
36
|
+
}
|
|
37
|
+
export function parseChatTextCompletion(value, label = "chat API") {
|
|
38
|
+
return parseApiData(chatTextCompletionSchema, value, label);
|
|
39
|
+
}
|
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":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/api/models.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AA+B5C,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"}
|
package/dist/api/models.js
CHANGED
|
@@ -2,12 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
import { parseApiData } from "./validate.js";
|
|
3
3
|
const dataPlaneModelsSchema = z
|
|
4
4
|
.object({
|
|
5
|
-
data: z.array(z.object({ id: z.string() }).passthrough())
|
|
5
|
+
data: z.array(z.object({ id: z.string().refine(isUsableModelId) }).passthrough()),
|
|
6
6
|
})
|
|
7
7
|
.passthrough();
|
|
8
8
|
const catalogModelSchema = z
|
|
9
9
|
.object({
|
|
10
|
-
modelId: z.string(),
|
|
10
|
+
modelId: z.string().refine(isUsableModelId),
|
|
11
11
|
displayName: z.string().optional(),
|
|
12
12
|
provider: z.string().optional(),
|
|
13
13
|
capabilities: z.array(z.string()).optional(),
|
|
@@ -22,7 +22,7 @@ const catalogModelSchema = z
|
|
|
22
22
|
})
|
|
23
23
|
.passthrough();
|
|
24
24
|
const catalogSchema = z
|
|
25
|
-
.object({ data: z.array(catalogModelSchema)
|
|
25
|
+
.object({ data: z.array(catalogModelSchema) })
|
|
26
26
|
.passthrough();
|
|
27
27
|
/**
|
|
28
28
|
* Agent admission. Every condition is something the catalogue states outright;
|
|
@@ -86,12 +86,12 @@ export function intersectModels(dataPlane, catalog) {
|
|
|
86
86
|
/** GET /v1/models — the key's access list. */
|
|
87
87
|
export async function fetchAccessibleModels(client) {
|
|
88
88
|
const response = await client.getJson("/models");
|
|
89
|
-
return
|
|
89
|
+
return parseApiData(dataPlaneModelsSchema, response.data, "model API").data;
|
|
90
90
|
}
|
|
91
91
|
/** GET /models on the business API — public catalogue, no key required. */
|
|
92
92
|
export async function fetchCatalog(client) {
|
|
93
93
|
const response = await client.getJson("/models");
|
|
94
|
-
return
|
|
94
|
+
return parseApiData(catalogSchema, response.data, "model catalogue").data;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* U-3 · Is this a usable model identifier?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWxE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAatB,MAAM,WAAW,MAAO,SAAQ,SAAS;IACvC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,gDAAgD;IAChD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAcD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAiTD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,GAAG,SAAS,EAC/B,KAAK,EAAE,WAAW,GACjB,MAAM,GAAG,SAAS,CAYpB"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { fetchCatalog } from "../api/models.js";
|
|
3
|
+
import { parseChatTextCompletion } from "../api/chat-response.js";
|
|
3
4
|
import { readChatStream } from "../api/sse-parser.js";
|
|
4
5
|
import { ApiError } from "../api/errors.js";
|
|
5
6
|
import { safeForTerminal, terminalSafeWriter, } from "../output/terminal-safe.js";
|
|
@@ -275,8 +276,11 @@ async function streamChat(runtime, body, args, io) {
|
|
|
275
276
|
return ExitCode.Ok;
|
|
276
277
|
}
|
|
277
278
|
async function completeChat(runtime, body, args, io) {
|
|
278
|
-
const { data, requestId } = await runtime.dataPlane.postJson("/chat/completions", body, {
|
|
279
|
-
|
|
279
|
+
const { data: rawData, requestId } = await runtime.dataPlane.postJson("/chat/completions", body, {
|
|
280
|
+
signal: io.signal,
|
|
281
|
+
});
|
|
282
|
+
const data = parseChatTextCompletion(rawData);
|
|
283
|
+
const rawAnswer = data.choices[0].message.content;
|
|
280
284
|
const answer = typeof rawAnswer === "string" ? rawAnswer : "";
|
|
281
285
|
const usage = validUsage(data.usage);
|
|
282
286
|
io.stdout(`${safeForTerminal(answer, isTty(io))}\n`);
|
|
@@ -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;AAgBtB,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"}
|
|
@@ -30,7 +30,8 @@ Conversations are stored on this computer only. Browsing them never
|
|
|
30
30
|
calls a model and is never charged.
|
|
31
31
|
|
|
32
32
|
--refresh asks the billing ledger what this key's requests actually
|
|
33
|
-
cost. It
|
|
33
|
+
cost. It queries this key's usage-event window; request IDs stay local
|
|
34
|
+
for matching. No messages, titles, workspace or session ID are sent.
|
|
34
35
|
`;
|
|
35
36
|
export async function sessionsCommand(argv, io) {
|
|
36
37
|
// F-013 · Nothing here may reach the top level as a raw Error. `--json
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/sessions/reconcile.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/sessions/reconcile.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,gBAAgB,EAAE,MAAM,GACvB,aAAa,CAwCf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,CAAC;IAC9C,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,SAAS,gBAAgB,EAAE,EACnC,UAAU,EAAE,MAAM,GACjB,WAAW,CAiCb;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAM7E"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// caught up with yet, and the user is told to wait for something that will
|
|
13
13
|
// never arrive. With it, we can say which key to run this under instead.
|
|
14
14
|
import { isComparableKeyRef } from "../security/redact.js";
|
|
15
|
+
import { isMicroUsd } from "../output/money.js";
|
|
15
16
|
/**
|
|
16
17
|
* Decide what to ask about, from the transcript alone.
|
|
17
18
|
*
|
|
@@ -69,8 +70,13 @@ export function planReconciliation(events, currentKeyDigest) {
|
|
|
69
70
|
export function matchSettlements(plan, ledger, observedAt) {
|
|
70
71
|
const byId = new Map();
|
|
71
72
|
for (const row of ledger) {
|
|
72
|
-
|
|
73
|
+
// Defense in depth: the API boundary rejects this already. If an invalid
|
|
74
|
+
// row is nevertheless injected, it remains pending instead of becoming a
|
|
75
|
+
// fabricated zero-dollar settlement.
|
|
76
|
+
if (plan.wanted.has(row.request_id) &&
|
|
77
|
+
isMicroUsd(row.platform_charge_micro_usd)) {
|
|
73
78
|
byId.set(row.request_id, row);
|
|
79
|
+
}
|
|
74
80
|
}
|
|
75
81
|
const settlements = [];
|
|
76
82
|
const missing = [];
|
|
@@ -85,17 +91,13 @@ export function matchSettlements(plan, ledger, observedAt) {
|
|
|
85
91
|
request_id: id,
|
|
86
92
|
// Kept as the string the server sent. Parsing it into a number here is
|
|
87
93
|
// how integer micro-USD becomes a float somewhere downstream.
|
|
88
|
-
settled_charge_micro_usd:
|
|
94
|
+
settled_charge_micro_usd: row.platform_charge_micro_usd,
|
|
89
95
|
server_status: typeof row.status === "string" ? row.status : "unknown",
|
|
90
96
|
observed_at: observedAt,
|
|
91
97
|
});
|
|
92
98
|
}
|
|
93
99
|
return { settlements, missing };
|
|
94
100
|
}
|
|
95
|
-
/** Anything that is not an integer count of micro-USD is not a charge. */
|
|
96
|
-
function normaliseMicro(value) {
|
|
97
|
-
return typeof value === "string" && /^-?\d+$/.test(value) ? value : "0";
|
|
98
|
-
}
|
|
99
101
|
/**
|
|
100
102
|
* How many days back the ledger has to be asked about.
|
|
101
103
|
*
|