@qilin-agent/usage-report 0.0.0-stage → 3.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,43 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/session/usage-report/README.md
5
+ /:
6
+ en: add4f5d9e3647553
7
+ zh: caf7e5690c6e1c19
8
+ /qilin-agent-usage-report:
9
+ en: 23f9d69bce105f30
10
+ zh: fd63b675803b2e10
11
+ /qilin-agent-usage-report/summary:
12
+ en: fc367ea6226c2745
13
+ zh: 6bd5694f14f2e26d
14
+ /qilin-agent-usage-report/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 3a2f21ff0dc3420d
17
+ /qilin-agent-usage-report/use-this-package:
18
+ en: 6ab5be9b6c4932dc
19
+ zh: 6b6c8cb38dc482b5
20
+ /qilin-agent-usage-report/use-this-package/delivery:
21
+ en: d655776fa5d639fe
22
+ zh: 7ffeb5e35292f3fc
23
+ /qilin-agent-usage-report/use-this-package/reading-it-back:
24
+ en: 2fafcce90e8be0ec
25
+ zh: 21c9e2bc01656687
26
+ /qilin-agent-usage-report/understand-the-implementation:
27
+ en: 92eab5d20c00bb14
28
+ zh: 410ec89aa8349c96
29
+ /qilin-agent-usage-report/further-exploration:
30
+ en: 5b47ffebcd81cf93
31
+ zh: 1e0d15118024c165
32
+ /qilin-agent-usage-report/model-experience:
33
+ en: c09aea9033676d8d
34
+ zh: cca033e311899e14
35
+ /qilin-agent-usage-report/model-experience/kv-cache-effect:
36
+ en: a9ffb42d486fd22f
37
+ zh: d08cc33e1dfc9439
38
+ /qilin-agent-usage-report/known-limitations-and-deferred-work:
39
+ en: 08d6482805679287
40
+ zh: c90c570aa63cc7d1
41
+ /qilin-agent-usage-report/known-limitations-and-deferred-work/dev-note:
42
+ en: 108f22f2695e0f2e
43
+ zh: 78f1ef317b7bd5fb
package/README.md CHANGED
@@ -1,3 +1,82 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Reports each settled Turn's exact token usage to the deployment supervisor that started the runtime, for the per-account ledger a multi-account deployment keeps."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/usage-report
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ A deployment that serves one web entry gives every account a runtime of its own and needs to know what each one spent. This plugin is the runtime's half of that accounting: it follows the session event stream, folds one Turn's durable attempts into exact usage — the same derivation the transcript shows, which refuses to guess — and posts the result to the endpoint its supervisor named. A runtime no supervisor started carries no endpoint, and the plugin then does nothing at all.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount the row once per runtime. It declares no configuration: the deployment supplies two environment variables, and their absence is the only switch.
29
+
30
+ | Variable | Meaning |
31
+ |---|---|
32
+ | `QILIN_USAGE_URL` | Endpoint that accepts one JSON report per settled Turn |
33
+ | `QILIN_USAGE_TOKEN` | Credential that endpoint accepts; the deployment binds it to the account this runtime serves |
34
+
35
+ The supervisor that starts a runtime writes both; a hand-started `qilin` carries neither. One report is one Turn, and delivery is best effort:
36
+
37
+ ```json
38
+ { "sessionId": "…", "turn": 3, "inputTokens": 4192, "outputTokens": 318,
39
+ "cacheReadTokens": 3072, "at": 1791725148421 }
40
+ ```
41
+
42
+ `inputTokens` counts prompt tokens billed as uncached, `cacheReadTokens` and `cacheWriteTokens` appear only when every attempt of the Turn reported them, and `at` is this process's report time. The endpoint identifies the account from the credential, never from the body.
43
+
44
+ ### Delivery
45
+
46
+ A report is sent once, when the Turn ends. A refused or unreachable endpoint is written to the log at warning level and dropped; the ledger is a report rather than an obligation, so telemetry trouble never delays a Turn or fails it.
47
+
48
+ ### Reading it back
49
+
50
+ A runtime that serves a web transport also answers `GET /api/usage/me` for its own account, forwarding the query string (such as a report-time window, `?from=<epoch ms>&to=<epoch ms>`) to the ledger with the same credential. The answer carries the account's `total`, the `records` inside the window in report-time order, and `truncated` when the ledger held more Turns than one answer carries. The account gate protects the route like any other `/api` call, and the read is scoped by the credential, so one runtime can only ever read its own account. A ledger the runtime cannot reach answers `503` with `{ "error": { "code": "ledger-unavailable" } }` rather than an empty total.
51
+
52
+ <a id="understand-the-implementation"></a>
53
+ ## Understand the implementation
54
+
55
+ The plugin buffers the durable events of the Turn each session is inside, from `turn/start` through `turn/end`, and folds them with the token-meter package's `deriveTurnTokenUsage`. That derivation refuses to infer usage: a Turn whose attempt lifecycle is incomplete, whose samples are unsafe, or whose exact total contradicts its parts yields nothing, and this plugin then reports nothing rather than an estimate. A Turn whose `turn/start` this process never saw — replayed history, or a session that began before the row mounted — is not one it can account for and is skipped.
56
+
57
+ <a id="further-exploration"></a>
58
+ ## Further Exploration
59
+
60
+ - [Token meter](../../llm/token-meter/README.md) owns the derivation this plugin reports.
61
+ - [Accounts](../../identity/accounts-local/README.md) owns the roster the deployment's ledger attributes reports to.
62
+ - [CLI reference](../../../apps/cli/reference/README.md) documents the deployment flags that start these runtimes.
63
+
64
+ <a id="model-experience"></a>
65
+ ## Model Experience
66
+
67
+ None, as this row only reads the session event stream and writes to a deployment endpoint, adding no prompt, tool schema, or model-visible output.
68
+
69
+ #### KV Cache effect
70
+
71
+ No request this row observes is altered, so every Session's cache footprint is unchanged.
72
+
73
+ ## Known Limitations and Deferred Work
74
+
75
+ - **A report is not durable.** A process that dies before its Turn ends loses that Turn's report, and a dropped report is not retried; the ledger sums what arrived.
76
+ - **Delivery is per Turn, not per request.** Retries inside a Turn are folded into its attempts, so the ledger shows what the Turn settled rather than what each call cost.
77
+ - **The ledger's own attribution is the deployment's.** The credential decides which account a report lands under; a runtime started by hand cannot report at all.
78
+
79
+ <a id="dev-note"></a>
80
+ ### Dev Note
81
+
82
+ The row is mounted by every runtime, including single-account ones, and is inert without the two variables; that is why it needs no configuration and no per-deployment enabling.
package/README.zh.md ADDED
@@ -0,0 +1,83 @@
1
+ ---
2
+ description: "把每个已结算 Turn 的精确 token 用量上报给启动该运行时的部署监督者,供多账户部署的按账户账本使用。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/usage-report
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 一个 web 入口服务多账户的部署给每个账户一个自己的运行时,因此需要知道各自花了多少。本插件是这笔记账在运行时一侧的那一半:它跟随会话事件流,把一个 Turn 的持久尝试折成精确用量——与转录页显示的是同一套推导,且拒绝猜测——再投递给监督者指定的端点。不是由监督者启动的运行时没有这个端点,插件于是什么都不做。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与待办](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 每个运行时挂载一次。它不声明任何配置:部署提供两个环境变量,二者缺席就是唯一的开关。
29
+
30
+ | 变量 | 含义 |
31
+ |---|---|
32
+ | `QILIN_USAGE_URL` | 接受「每个已结算 Turn 一份 JSON 报告」的端点 |
33
+ | `QILIN_USAGE_TOKEN` | 该端点接受的凭据;部署把它绑定到本运行时服务的账户 |
34
+
35
+ 启动运行时的监督者会写入这两者;手工启动的 `qilin` 两者都没有。一份报告就是一个 Turn,投递为尽力而为:
36
+
37
+ ```json
38
+ { "sessionId": "…", "turn": 3, "inputTokens": 4192, "outputTokens": 318,
39
+ "cacheReadTokens": 3072, "at": 1791725148421 }
40
+ ```
41
+
42
+ `inputTokens` 计的是按未命中计费的提示 token;`cacheReadTokens` 与 `cacheWriteTokens` 只在该 Turn 的每次尝试都报了它们时才出现;`at` 是本进程的上报时刻。端点**从凭据**判断账户,绝不从请求体判断。
43
+
44
+ ### 投递
45
+
46
+ 报告在 Turn 结束时发送一次。端点拒绝或不可达只写入 warning 级日志然后丢弃;账本是报告而非义务,因此遥测故障既不会拖延一个 Turn,也不会让它失败。
47
+
48
+ ### 读回自己的用量
49
+
50
+ 提供 web 传输的运行时还会为它自己的账户应答 `GET /api/usage/me`:把查询串(例如上报时间窗口 `?from=<epoch ms>&to=<epoch ms>`)连同同一凭据转发给账本。应答里带该账户的 `total`、窗口内按上报时间排序的 `records`,以及当账本里的 Turn 超过单次应答容量时的 `truncated`。该路由与其它 `/api` 调用一样受账户门禁保护,且读取由凭据限定作用域——一个运行时只可能读到自己的账户。账本不可达时返回 `503` 与 `{ "error": { "code": "ledger-unavailable" } }`,而不是一个空合计。
51
+
52
+ <a id="understand-the-implementation"></a>
53
+ ## 理解实现
54
+
55
+ 插件缓冲每个会话当前所在 Turn 的持久事件(`turn/start` 到 `turn/end`),用 token-meter 包的 `deriveTurnTokenUsage` 折叠它们。该推导拒绝推断用量:尝试生命周期不完整、样本不安全、或精确总数与各部分矛盾的 Turn 一律得不出结果,此时本插件不上报而不是上报估计值。本进程没有见过 `turn/start` 的 Turn——重放的历史,或在本行挂载之前就开始的会话——不在它能记账的范围内,直接跳过。
56
+
57
+ <a id="further-exploration"></a>
58
+ ## 进一步探索
59
+
60
+ - [Token meter](../../llm/token-meter/README.zh.md) 拥有本插件所上报的那套推导。
61
+ - [Accounts](../../identity/accounts-local/README.zh.md) 拥有部署账本据以归属报告的名册。
62
+ - [CLI reference](../../../apps/cli/reference/README.zh.md) 记录了启动这些运行时的部署 flag。
63
+
64
+ <a id="model-experience"></a>
65
+ ## 模型体验
66
+
67
+ 无:本行只读取会话事件流并写入部署端点,不增加任何提示词、工具 schema 或模型可见输出。
68
+
69
+ #### KV 缓存影响
70
+
71
+ 本行观察的请求都不被改动,因此每个 Session 的缓存占用不变。
72
+
73
+ <a id="known-limitations-and-deferred-work"></a>
74
+ ## 已知限制与待办
75
+
76
+ - **报告不是持久义务。** 在 Turn 结束前死掉的进程会丢掉该 Turn 的报告,被丢弃的报告不会重试;账本汇总的是已到达的那些。
77
+ - **按 Turn 投递,而不是按请求。** 一个 Turn 内的重试被折进它的尝试里,因此账本显示的是该 Turn 结算的量,而不是每次调用的开销。
78
+ - **账本的归属由部署决定。** 凭据决定报告落在哪个账户名下;手工启动的运行时根本无法上报。
79
+
80
+ <a id="dev-note"></a>
81
+ ### 开发备注
82
+
83
+ 每个运行时都会挂载这一行(包括单账户的),而它在缺少那两个变量时是惰性的;这就是它不需要配置、也不需要按部署启用的原因。
package/lib/index.js ADDED
@@ -0,0 +1,90 @@
1
+ import { deriveTurnTokenUsage } from "@qilin-agent/token-meter";
2
+ //#region lib/types/index.js
3
+ /**
4
+ * Reports each settled Turn's token usage to the deployment that started this
5
+ * runtime.
6
+ *
7
+ * A deployment that serves one web entry gives every account a runtime of its
8
+ * own and keeps one ledger of what each account spent. This plugin is the
9
+ * runtime's half of that: it folds one Turn's durable attempts into exact usage
10
+ * — the same derivation the transcript shows — and posts it to the endpoint the
11
+ * supervisor named in this process's environment. The browser reads that ledger
12
+ * from the deployment itself, so a runtime started by hand carries no endpoint
13
+ * and this plugin then does nothing at all.
14
+ * @module @qilin-agent/usage-report
15
+ */
16
+ /** Environment variable naming the deployment's usage endpoint. */
17
+ const USAGE_URL_VARIABLE = "QILIN_USAGE_URL";
18
+ /** Environment variable carrying the credential that endpoint accepts. */
19
+ const USAGE_TOKEN_VARIABLE = "QILIN_USAGE_TOKEN";
20
+ /** Plugin name the Loader mounts. */
21
+ const name = "usage-report";
22
+ /** This plugin reads no service; it only follows the session event stream. */
23
+ const inject = [];
24
+ /** How long one report may take before it is abandoned. */
25
+ const REPORT_TIMEOUT_MS = 5e3;
26
+ /**
27
+ * Report every Turn this process settles to the deployment that started it.
28
+ * @param ctx - plugin context carrying the session event stream.
29
+ */
30
+ function apply(ctx) {
31
+ const url = process.env[USAGE_URL_VARIABLE];
32
+ const token = process.env[USAGE_TOKEN_VARIABLE];
33
+ if (url === void 0 || url === "" || token === void 0 || token === "") return;
34
+ /** Durable events of the Turn each session is inside, from `turn/start` on. */
35
+ const turns = /* @__PURE__ */ new Map();
36
+ ctx.effect(() => () => {
37
+ turns.clear();
38
+ });
39
+ ctx.on("session/event", (session, event) => {
40
+ const sessionId = session.id;
41
+ if (event.type === "turn/start") {
42
+ turns.set(sessionId, [event]);
43
+ return;
44
+ }
45
+ const buffered = turns.get(sessionId);
46
+ if (buffered === void 0) return;
47
+ buffered.push(event);
48
+ if (event.type !== "turn/end") return;
49
+ turns.delete(sessionId);
50
+ const usage = deriveTurnTokenUsage(buffered);
51
+ if (usage === void 0) return;
52
+ report(ctx, url, token, {
53
+ sessionId,
54
+ turn: event.data.turn,
55
+ inputTokens: usage.uncachedInputTokens,
56
+ outputTokens: usage.outputTokens,
57
+ ...usage.cacheReadTokens === void 0 ? {} : { cacheReadTokens: usage.cacheReadTokens },
58
+ ...usage.cacheWriteTokens === void 0 ? {} : { cacheWriteTokens: usage.cacheWriteTokens }
59
+ });
60
+ });
61
+ }
62
+ /**
63
+ * Post one Turn's report, without letting the deployment's health reach the
64
+ * session that produced it.
65
+ * @param ctx - plugin context the diagnostic goes to.
66
+ * @param url - endpoint the deployment named.
67
+ * @param token - credential that endpoint accepts.
68
+ * @param report - the Turn's usage.
69
+ */
70
+ async function report(ctx, url, token, report) {
71
+ try {
72
+ const response = await fetch(url, {
73
+ method: "POST",
74
+ headers: {
75
+ "content-type": "application/json",
76
+ authorization: `Bearer ${token}`
77
+ },
78
+ body: JSON.stringify({
79
+ ...report,
80
+ at: Date.now()
81
+ }),
82
+ signal: AbortSignal.timeout(REPORT_TIMEOUT_MS)
83
+ });
84
+ if (!response.ok) ctx.logger.warn(`usage-report: the deployment refused one report (HTTP ${String(response.status)})`);
85
+ } catch (error) {
86
+ ctx.logger.warn(`usage-report: one report failed: ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ }
89
+ //#endregion
90
+ export { USAGE_TOKEN_VARIABLE, USAGE_URL_VARIABLE, apply, inject, name };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Reports each settled Turn's token usage to the deployment that started this
3
+ * runtime.
4
+ *
5
+ * A deployment that serves one web entry gives every account a runtime of its
6
+ * own and keeps one ledger of what each account spent. This plugin is the
7
+ * runtime's half of that: it folds one Turn's durable attempts into exact usage
8
+ * — the same derivation the transcript shows — and posts it to the endpoint the
9
+ * supervisor named in this process's environment. The browser reads that ledger
10
+ * from the deployment itself, so a runtime started by hand carries no endpoint
11
+ * and this plugin then does nothing at all.
12
+ * @module @qilin-agent/usage-report
13
+ */
14
+ import type { Context } from '@qilin-agent/kylin';
15
+ /** Environment variable naming the deployment's usage endpoint. */
16
+ export declare const USAGE_URL_VARIABLE = "QILIN_USAGE_URL";
17
+ /** Environment variable carrying the credential that endpoint accepts. */
18
+ export declare const USAGE_TOKEN_VARIABLE = "QILIN_USAGE_TOKEN";
19
+ /** Plugin name the Loader mounts. */
20
+ export declare const name = "usage-report";
21
+ /** This plugin reads no service; it only follows the session event stream. */
22
+ export declare const inject: readonly string[];
23
+ /**
24
+ * Report every Turn this process settles to the deployment that started it.
25
+ * @param ctx - plugin context carrying the session event stream.
26
+ */
27
+ export declare function apply(ctx: Context): void;
28
+ //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@qilin-agent/usage-report",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Reports each settled Turn's token usage to the deployment that started the runtime",
4
+ "version": "3.1.4",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/session/usage-report"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/index.js",
29
+ "lib/types/**/*.d.ts"
30
+ ],
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "@qilin-agent/kylin": "~4.0.3"
34
+ },
35
+ "dependencies": {
36
+ "@qilin-agent/token-meter": "3.1.4"
37
+ },
38
+ "devDependencies": {
39
+ "@qilin-agent/kylin": "~4.0.3",
40
+ "@qilin-agent/client-connection": "3.1.4",
41
+ "@qilin-agent/session": "3.1.4"
42
+ }
6
43
  }