@mafdet/cli 0.4.11 → 0.4.13
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 +24 -3
- package/README.md +21 -7
- package/README.zh-CN.md +18 -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,26 @@ 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.13 - 2026-08-17
|
|
10
|
+
|
|
11
|
+
- Documentation only; no code change. Both READMEs now open by saying what
|
|
12
|
+
Mafdet AI is — an OpenAI-compatible gateway, its base URL, its endpoints and
|
|
13
|
+
its providers — because the npm page was addressed to readers who already
|
|
14
|
+
knew, and most do not. The model count is dated and points at
|
|
15
|
+
`models.json` rather than standing alone, so it cannot quietly go stale.
|
|
16
|
+
|
|
17
|
+
## 0.4.12 - 2026-08-12
|
|
18
|
+
|
|
19
|
+
- Validate usage-event pages, model identities, non-stream Chat responses and
|
|
20
|
+
Agent tool calls at the HTTP boundary. Malformed remote data now produces a
|
|
21
|
+
typed unavailable error instead of an empty success or a runtime exception.
|
|
22
|
+
- Require account and settled ledger amounts to be non-negative integer
|
|
23
|
+
micro-USD strings. An invalid settlement remains pending; it is never
|
|
24
|
+
rewritten as a fabricated zero-dollar settlement.
|
|
25
|
+
- Correct the reconciliation documentation: refresh queries the current key's
|
|
26
|
+
paged usage-event window and matches request IDs locally, while retaining
|
|
27
|
+
local estimates alongside settled figures.
|
|
28
|
+
|
|
9
29
|
## 0.4.11 - 2026-08-12
|
|
10
30
|
|
|
11
31
|
A seventh audit round, fixed at the root rather than sink by sink - which is
|
|
@@ -343,9 +363,10 @@ each platform, and that deleting one is an ordinary file delete rather than a
|
|
|
343
363
|
secure erase.
|
|
344
364
|
|
|
345
365
|
`mafdet sessions usage <id> --refresh` asks the billing ledger what the
|
|
346
|
-
requests actually cost and
|
|
347
|
-
|
|
348
|
-
|
|
366
|
+
requests actually cost and records the settled figure alongside the local
|
|
367
|
+
estimate. **Correction:** it queries the current key's paged usage-event
|
|
368
|
+
window; request IDs stay local for matching rather than being sent. No
|
|
369
|
+
messages, title, workspace or session ID leave the machine. Requests paid for by a
|
|
349
370
|
different API key are left alone and reported rather than guessed at, and a
|
|
350
371
|
request the ledger has not settled stays `pending` rather than being shown as
|
|
351
372
|
zero.
|
package/README.md
CHANGED
|
@@ -3,12 +3,25 @@
|
|
|
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.13.** 中文版见 [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
|
>
|
|
10
10
|
> macOS and Windows are not yet verified. On Windows the command timeout uses `taskkill /T`, which is written but has never been run on a real Windows machine - so whether every grandchild process is reached there is unverified rather than fixed.
|
|
11
11
|
|
|
12
|
+
## What Mafdet AI is
|
|
13
|
+
|
|
14
|
+
Mafdet AI is an OpenAI-compatible AI gateway: one API key and one base URL reach
|
|
15
|
+
models from several providers, billed by usage. Any tool or SDK that speaks the
|
|
16
|
+
OpenAI API works by pointing its base URL and key at Mafdet AI.
|
|
17
|
+
|
|
18
|
+
- Base URL: `https://api.mafdet.ai/v1`
|
|
19
|
+
- Endpoints: `/chat/completions`, `/embeddings`, `/audio/speech`
|
|
20
|
+
- Providers: Google (Gemini), Kimi (Moonshot), Qwen, DeepSeek
|
|
21
|
+
- 30 models as of August 2026. The catalogue changes, so the authoritative list
|
|
22
|
+
with current prices is [`models.json`](https://docs.mafdet.ai/models.json)
|
|
23
|
+
- Docs: <https://docs.mafdet.ai>
|
|
24
|
+
|
|
12
25
|
## Requirements
|
|
13
26
|
|
|
14
27
|
Node.js 20 or newer, and a Mafdet API key.
|
|
@@ -23,8 +36,8 @@ Or build it from this repository:
|
|
|
23
36
|
|
|
24
37
|
```bash
|
|
25
38
|
pnpm --filter @mafdet/cli build
|
|
26
|
-
cd packages/cli && npm pack # -> mafdet-cli-0.4.
|
|
27
|
-
npm install -g ./mafdet-cli-0.4.
|
|
39
|
+
cd packages/cli && npm pack # -> mafdet-cli-0.4.13.tgz
|
|
40
|
+
npm install -g ./mafdet-cli-0.4.13.tgz
|
|
28
41
|
```
|
|
29
42
|
|
|
30
43
|
## The interactive session
|
|
@@ -87,7 +100,7 @@ prompt as an argument, or use `--input <file>`.
|
|
|
87
100
|
| `mafdet speech [text\|<file>] --model <id> --output <file> --voice <name>` | Text to an audio file |
|
|
88
101
|
| `mafdet account balance\|usage\|subscription` | Wallet, spend and plan (needs the `account:read` scope) |
|
|
89
102
|
| `mafdet sessions list\|show\|usage\|delete` | Conversations saved on this computer (never calls a model) |
|
|
90
|
-
| `mafdet sessions usage <id> --refresh` |
|
|
103
|
+
| `mafdet sessions usage <id> --refresh` | Show settled ledger figures alongside the local estimate |
|
|
91
104
|
| `mafdet config list` | The resolved settings and where each came from |
|
|
92
105
|
|
|
93
106
|
`--json` on any query command emits one complete JSON document; `--plain`
|
|
@@ -247,9 +260,10 @@ erase — backups, snapshots and the filesystem itself may still hold copies.
|
|
|
247
260
|
record. Those charges are estimates, computed with the gateway's own
|
|
248
261
|
arithmetic but computed here.
|
|
249
262
|
|
|
250
|
-
`--refresh` asks the billing ledger for
|
|
251
|
-
|
|
252
|
-
|
|
263
|
+
`--refresh` asks the billing ledger for settled figures. It pages through the
|
|
264
|
+
current API key's usage events for the session's time window; request IDs stay
|
|
265
|
+
local and are used only to match returned rows. No messages, title, workspace
|
|
266
|
+
or session ID are sent. Requests paid for by a different API key are left
|
|
253
267
|
alone and reported, so you know to run it again with that key.
|
|
254
268
|
|
|
255
269
|
A request that the ledger has not caught up with stays `pending`, and a request
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Mafdet AI 官方命令行:模型调用、账户查询,以及一个受权限约束的本地 Agent。
|
|
4
4
|
|
|
5
|
-
> **0.4.
|
|
5
|
+
> **0.4.13。** 英文版见 [README.md](./README.md)。
|
|
6
|
+
|
|
7
|
+
## Mafdet AI 是什么
|
|
8
|
+
|
|
9
|
+
Mafdet AI 是一个 OpenAI 兼容的 AI 网关:一把 API Key、一个 base URL,即可调用多家
|
|
10
|
+
提供方的模型,按量计费。任何支持 OpenAI API 格式的工具或 SDK,只要把 base URL 和
|
|
11
|
+
Key 换成 Mafdet AI 的就能用。
|
|
12
|
+
|
|
13
|
+
- Base URL:`https://api.mafdet.ai/v1`
|
|
14
|
+
- 端点:`/chat/completions`、`/embeddings`、`/audio/speech`
|
|
15
|
+
- 提供方:Google(Gemini)、Kimi(月之暗面)、Qwen、DeepSeek
|
|
16
|
+
- 截至 2026 年 8 月共 30 个模型。目录会变动,**以带实时价格的
|
|
17
|
+
[`models.json`](https://docs.mafdet.ai/models.json) 为准**
|
|
18
|
+
- 文档:<https://docs.mafdet.ai>
|
|
6
19
|
|
|
7
20
|
## 环境要求
|
|
8
21
|
|
|
@@ -73,7 +86,7 @@ mafdet doctor
|
|
|
73
86
|
| `mafdet agent`(不带目标,需终端) | 交互会话 |
|
|
74
87
|
| `mafdet account balance\|usage\|subscription` | 钱包、用量、套餐(需 `account:read` scope) |
|
|
75
88
|
| `mafdet sessions list\|show\|usage\|delete` | 保存在这台电脑上的会话(不会调用模型) |
|
|
76
|
-
| `mafdet sessions usage <id> --refresh` |
|
|
89
|
+
| `mafdet sessions usage <id> --refresh` | 在本地估算旁显示账单账本的已结算金额 |
|
|
77
90
|
| `mafdet config list` | 当前生效的配置及其来源 |
|
|
78
91
|
|
|
79
92
|
查询类命令都支持 `--json`(输出一份完整 JSON 文档)与 `--plain`(无表头的 TSV,方便
|
|
@@ -214,9 +227,9 @@ mafdet sessions delete <id> --yes
|
|
|
214
227
|
`mafdet sessions usage <id>` 从本地记录累加一次会话的用量。那些金额是**估算**,
|
|
215
228
|
算法与网关一致,但是在本地算的。
|
|
216
229
|
|
|
217
|
-
`--refresh`
|
|
218
|
-
|
|
219
|
-
|
|
230
|
+
`--refresh` 会按会话时间窗口和游标查询当前 API Key 的用量事件;request ID 只留在本地,
|
|
231
|
+
用于匹配账本返回的记录。它不发送消息、标题、工作区路径或 session ID。由另一把 API Key
|
|
232
|
+
支付的请求会被跳过并如实告知,好让你知道该换那把 Key 再跑一次。
|
|
220
233
|
|
|
221
234
|
账本还没跟上的请求保持 `pending`,从未拿到 ID 的请求保持 `unknown`。
|
|
222
235
|
**两者都绝不显示为 `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
|
*
|