@skillfm/local 2.7.3 → 2.7.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.
Files changed (116) hide show
  1. package/dist/checkup/daily-push.d.ts +29 -0
  2. package/dist/checkup/daily-push.d.ts.map +1 -0
  3. package/dist/checkup/daily-push.js +86 -0
  4. package/dist/checkup/daily-push.js.map +1 -0
  5. package/dist/checkup/dimension-1-usage.d.ts +22 -0
  6. package/dist/checkup/dimension-1-usage.d.ts.map +1 -0
  7. package/dist/checkup/dimension-1-usage.js +115 -0
  8. package/dist/checkup/dimension-1-usage.js.map +1 -0
  9. package/dist/checkup/dimension-3-files.d.ts +23 -0
  10. package/dist/checkup/dimension-3-files.d.ts.map +1 -0
  11. package/dist/checkup/dimension-3-files.js +165 -0
  12. package/dist/checkup/dimension-3-files.js.map +1 -0
  13. package/dist/checkup/dimension-4-context.d.ts +18 -0
  14. package/dist/checkup/dimension-4-context.d.ts.map +1 -0
  15. package/dist/checkup/dimension-4-context.js +177 -0
  16. package/dist/checkup/dimension-4-context.js.map +1 -0
  17. package/dist/checkup/index.d.ts +11 -0
  18. package/dist/checkup/index.d.ts.map +1 -0
  19. package/dist/checkup/index.js +10 -0
  20. package/dist/checkup/index.js.map +1 -0
  21. package/dist/checkup/report.d.ts +21 -0
  22. package/dist/checkup/report.d.ts.map +1 -0
  23. package/dist/checkup/report.js +133 -0
  24. package/dist/checkup/report.js.map +1 -0
  25. package/dist/checkup/score.d.ts +27 -0
  26. package/dist/checkup/score.d.ts.map +1 -0
  27. package/dist/checkup/score.js +101 -0
  28. package/dist/checkup/score.js.map +1 -0
  29. package/dist/checkup/types.d.ts +64 -0
  30. package/dist/checkup/types.d.ts.map +1 -0
  31. package/dist/checkup/types.js +5 -0
  32. package/dist/checkup/types.js.map +1 -0
  33. package/dist/mcp/index.d.ts.map +1 -1
  34. package/dist/mcp/index.js +27 -0
  35. package/dist/mcp/index.js.map +1 -1
  36. package/dist/mcp/tools/checkup.d.ts +21 -0
  37. package/dist/mcp/tools/checkup.d.ts.map +1 -0
  38. package/dist/mcp/tools/checkup.js +35 -0
  39. package/dist/mcp/tools/checkup.js.map +1 -0
  40. package/dist/mcp/tools/index.d.ts +6 -0
  41. package/dist/mcp/tools/index.d.ts.map +1 -1
  42. package/dist/mcp/tools/index.js +37 -0
  43. package/dist/mcp/tools/index.js.map +1 -1
  44. package/dist/mcp/tools/setup-gateway.d.ts +16 -0
  45. package/dist/mcp/tools/setup-gateway.d.ts.map +1 -0
  46. package/dist/mcp/tools/setup-gateway.js +79 -0
  47. package/dist/mcp/tools/setup-gateway.js.map +1 -0
  48. package/dist/mcp/tools/show-my-usage.d.ts +26 -0
  49. package/dist/mcp/tools/show-my-usage.d.ts.map +1 -0
  50. package/dist/mcp/tools/show-my-usage.js +68 -0
  51. package/dist/mcp/tools/show-my-usage.js.map +1 -0
  52. package/dist/reconciliation/engine.d.ts +30 -0
  53. package/dist/reconciliation/engine.d.ts.map +1 -0
  54. package/dist/reconciliation/engine.js +62 -0
  55. package/dist/reconciliation/engine.js.map +1 -0
  56. package/dist/reconciliation/index.d.ts +8 -0
  57. package/dist/reconciliation/index.d.ts.map +1 -0
  58. package/dist/reconciliation/index.js +8 -0
  59. package/dist/reconciliation/index.js.map +1 -0
  60. package/dist/reconciliation/l1-reconciler.d.ts +29 -0
  61. package/dist/reconciliation/l1-reconciler.d.ts.map +1 -0
  62. package/dist/reconciliation/l1-reconciler.js +144 -0
  63. package/dist/reconciliation/l1-reconciler.js.map +1 -0
  64. package/dist/reconciliation/l2-reconciler.d.ts +21 -0
  65. package/dist/reconciliation/l2-reconciler.d.ts.map +1 -0
  66. package/dist/reconciliation/l2-reconciler.js +241 -0
  67. package/dist/reconciliation/l2-reconciler.js.map +1 -0
  68. package/dist/reconciliation/types.d.ts +49 -0
  69. package/dist/reconciliation/types.d.ts.map +1 -0
  70. package/dist/reconciliation/types.js +9 -0
  71. package/dist/reconciliation/types.js.map +1 -0
  72. package/dist/save-token/e1-router.d.ts +7 -0
  73. package/dist/save-token/e1-router.d.ts.map +1 -0
  74. package/dist/save-token/e1-router.js +112 -0
  75. package/dist/save-token/e1-router.js.map +1 -0
  76. package/dist/save-token/e2-cache.d.ts +7 -0
  77. package/dist/save-token/e2-cache.d.ts.map +1 -0
  78. package/dist/save-token/e2-cache.js +128 -0
  79. package/dist/save-token/e2-cache.js.map +1 -0
  80. package/dist/save-token/e3-batch.d.ts +7 -0
  81. package/dist/save-token/e3-batch.d.ts.map +1 -0
  82. package/dist/save-token/e3-batch.js +80 -0
  83. package/dist/save-token/e3-batch.js.map +1 -0
  84. package/dist/save-token/gateway-setup.d.ts +27 -0
  85. package/dist/save-token/gateway-setup.d.ts.map +1 -0
  86. package/dist/save-token/gateway-setup.js +200 -0
  87. package/dist/save-token/gateway-setup.js.map +1 -0
  88. package/dist/save-token/index.d.ts +13 -0
  89. package/dist/save-token/index.d.ts.map +1 -0
  90. package/dist/save-token/index.js +23 -0
  91. package/dist/save-token/index.js.map +1 -0
  92. package/dist/save-token/types.d.ts +46 -0
  93. package/dist/save-token/types.d.ts.map +1 -0
  94. package/dist/save-token/types.js +5 -0
  95. package/dist/save-token/types.js.map +1 -0
  96. package/dist/usage-local/index.d.ts +32 -0
  97. package/dist/usage-local/index.d.ts.map +1 -0
  98. package/dist/usage-local/index.js +32 -0
  99. package/dist/usage-local/index.js.map +1 -0
  100. package/dist/usage-local/model-pricing.d.ts +38 -0
  101. package/dist/usage-local/model-pricing.d.ts.map +1 -0
  102. package/dist/usage-local/model-pricing.js +230 -0
  103. package/dist/usage-local/model-pricing.js.map +1 -0
  104. package/dist/usage-local/openclaw-watcher.d.ts +35 -0
  105. package/dist/usage-local/openclaw-watcher.d.ts.map +1 -0
  106. package/dist/usage-local/openclaw-watcher.js +190 -0
  107. package/dist/usage-local/openclaw-watcher.js.map +1 -0
  108. package/dist/usage-local/store.d.ts +50 -0
  109. package/dist/usage-local/store.d.ts.map +1 -0
  110. package/dist/usage-local/store.js +201 -0
  111. package/dist/usage-local/store.js.map +1 -0
  112. package/dist/usage-local/types.d.ts +94 -0
  113. package/dist/usage-local/types.d.ts.map +1 -0
  114. package/dist/usage-local/types.js +6 -0
  115. package/dist/usage-local/types.js.map +1 -0
  116. package/package.json +2 -2
@@ -0,0 +1,29 @@
1
+ import type { Vault } from '../vault/index.js';
2
+ import type { Connector } from '../connectors/types.js';
3
+ import type { LocalUsageStore } from '../usage-local/store.js';
4
+ import type { ReconciliationResult, DeviationLevel } from './types.js';
5
+ /**
6
+ * 偏差分级 (Eric 决策点 #1, 拍板 ACTIVE):
7
+ * < 5%: healthy
8
+ * 5-15%: warning
9
+ * 15-30%: medium
10
+ * > 30%: severe
11
+ */
12
+ export declare function classifyDeviation(deviation_pct: number): DeviationLevel;
13
+ export interface L1ReconcileDeps {
14
+ vault: Vault;
15
+ /** Connector registry: provider → Connector instance */
16
+ connectors: Record<string, Connector>;
17
+ store: LocalUsageStore;
18
+ }
19
+ export interface L1ReconcileOptions {
20
+ provider: string;
21
+ start: string;
22
+ end: string;
23
+ }
24
+ /**
25
+ * L1 对账: 单 provider, 时间窗口
26
+ * 返 ReconciliationResult, 含 l1_actual_usd / deviation_pct / level / anomalies.
27
+ */
28
+ export declare function reconcileL1(deps: L1ReconcileDeps, opts: L1ReconcileOptions): Promise<ReconciliationResult>;
29
+ //# sourceMappingURL=l1-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l1-reconciler.d.ts","sourceRoot":"","sources":["../../src/reconciliation/l1-reconciler.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EAEf,MAAM,YAAY,CAAC;AAIpB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,CAKvE;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,oBAAoB,CAAC,CAwG/B"}
@@ -0,0 +1,144 @@
1
+ // sdk/skillfm-local/src/reconciliation/l1-reconciler.ts
2
+ //
3
+ // L1 reconciliation — provider admin API 真账单 vs L0 本地估算.
4
+ // 仅 OpenAI / Anthropic 真支持 admin usage API.
5
+ //
6
+ // 流程:
7
+ // 1. 从 vault 拿 admin key (BYOK)
8
+ // 2. 调 connector.fetchUsage(key, {start, end}) 拿真账单 UsageRecord[]
9
+ // 3. 加总 estimated_usd → 真 cost (provider 自己算的)
10
+ // 4. 跟 L0 store 同窗口聚合的 estimated_cost_usd 对比
11
+ // 5. 偏差 = abs(L0 - L1) / L1, 分级 → ReconciliationResult
12
+ const L1_PROVIDERS = new Set(['openai', 'anthropic']);
13
+ /**
14
+ * 偏差分级 (Eric 决策点 #1, 拍板 ACTIVE):
15
+ * < 5%: healthy
16
+ * 5-15%: warning
17
+ * 15-30%: medium
18
+ * > 30%: severe
19
+ */
20
+ export function classifyDeviation(deviation_pct) {
21
+ if (deviation_pct < 5)
22
+ return 'healthy';
23
+ if (deviation_pct < 15)
24
+ return 'warning';
25
+ if (deviation_pct < 30)
26
+ return 'medium';
27
+ return 'severe';
28
+ }
29
+ /**
30
+ * L1 对账: 单 provider, 时间窗口
31
+ * 返 ReconciliationResult, 含 l1_actual_usd / deviation_pct / level / anomalies.
32
+ */
33
+ export async function reconcileL1(deps, opts) {
34
+ const provider = opts.provider.toLowerCase();
35
+ const reconciledAt = new Date().toISOString();
36
+ // 基础聚合 L0 数字 (无 BYOK key 仍可显示)
37
+ const l0Agg = deps.store.aggregate({
38
+ start: opts.start,
39
+ end: opts.end,
40
+ providers: [provider],
41
+ });
42
+ const l0_estimated_usd = l0Agg.total_cost_usd;
43
+ if (!L1_PROVIDERS.has(provider)) {
44
+ // L1 不支持此 provider (可能走 L2 或无任何 reconciliation)
45
+ return {
46
+ provider,
47
+ reconciled_at: reconciledAt,
48
+ window_start: opts.start,
49
+ window_end: opts.end,
50
+ l0_estimated_usd,
51
+ level: 'no_data',
52
+ anomalies: [],
53
+ human_summary: `${provider} 不支持 L1 admin API 对账 (尝试 L2 balance 差量)`,
54
+ };
55
+ }
56
+ const key = await deps.vault.get(provider);
57
+ if (!key) {
58
+ return {
59
+ provider,
60
+ reconciled_at: reconciledAt,
61
+ window_start: opts.start,
62
+ window_end: opts.end,
63
+ l0_estimated_usd,
64
+ level: 'no_data',
65
+ anomalies: ['no_byok_key'],
66
+ human_summary: `${provider} 无 BYOK key, 仅显示 L0 估算 ¥${(l0_estimated_usd * 7.3).toFixed(2)}; 添加 admin key 即可对账验真`,
67
+ };
68
+ }
69
+ const connector = deps.connectors[provider];
70
+ if (!connector) {
71
+ return {
72
+ provider,
73
+ reconciled_at: reconciledAt,
74
+ window_start: opts.start,
75
+ window_end: opts.end,
76
+ l0_estimated_usd,
77
+ level: 'no_data',
78
+ anomalies: [],
79
+ human_summary: `${provider} connector 未配置`,
80
+ };
81
+ }
82
+ // 调真 admin API
83
+ let l1_actual_usd;
84
+ try {
85
+ const records = await connector.fetchUsage(key, {
86
+ start_date: new Date(opts.start),
87
+ end_date: new Date(opts.end),
88
+ });
89
+ l1_actual_usd = records.reduce((sum, r) => sum + (r.estimated_usd ?? 0), 0);
90
+ }
91
+ catch (e) {
92
+ return {
93
+ provider,
94
+ reconciled_at: reconciledAt,
95
+ window_start: opts.start,
96
+ window_end: opts.end,
97
+ l0_estimated_usd,
98
+ level: 'no_data',
99
+ anomalies: ['l1_api_stale'],
100
+ human_summary: `${provider} admin API 调用失败 (${e.message.slice(0, 80)}), 仅显示 L0 估算`,
101
+ };
102
+ }
103
+ // 计算偏差
104
+ const deviation_pct = l1_actual_usd > 0
105
+ ? Math.abs(l0_estimated_usd - l1_actual_usd) / l1_actual_usd * 100
106
+ : 0;
107
+ const level = classifyDeviation(deviation_pct);
108
+ const anomalies = [];
109
+ if (level === 'severe')
110
+ anomalies.push('price_table_outdated');
111
+ const human_summary = buildL1Summary({
112
+ provider,
113
+ l0_estimated_usd,
114
+ l1_actual_usd,
115
+ deviation_pct,
116
+ level,
117
+ });
118
+ return {
119
+ provider,
120
+ reconciled_at: reconciledAt,
121
+ window_start: opts.start,
122
+ window_end: opts.end,
123
+ l0_estimated_usd,
124
+ l1_actual_usd,
125
+ deviation_pct,
126
+ level,
127
+ anomalies,
128
+ human_summary,
129
+ };
130
+ }
131
+ function buildL1Summary(input) {
132
+ const cny0 = (input.l0_estimated_usd * 7.3).toFixed(2);
133
+ const cny1 = (input.l1_actual_usd * 7.3).toFixed(2);
134
+ const pct = input.deviation_pct.toFixed(1);
135
+ const emoji = {
136
+ healthy: '✅',
137
+ warning: '⚠',
138
+ medium: '🔴',
139
+ severe: '🚨',
140
+ no_data: '❓',
141
+ }[input.level];
142
+ return `${input.provider}: 估算 ¥${cny0} vs 真扣 ¥${cny1} 偏差 ${pct}% ${emoji}`;
143
+ }
144
+ //# sourceMappingURL=l1-reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l1-reconciler.js","sourceRoot":"","sources":["../../src/reconciliation/l1-reconciler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Vault } from '../vault/index.js';
2
+ import type { Connector } from '../connectors/types.js';
3
+ import type { LocalUsageStore } from '../usage-local/store.js';
4
+ import type { ReconciliationResult } from './types.js';
5
+ export interface L2ReconcileDeps {
6
+ vault: Vault;
7
+ connectors: Record<string, Connector>;
8
+ store: LocalUsageStore;
9
+ /** Override balance history file path (默认 ~/.skillfm/balance-history.jsonl) */
10
+ historyPath?: string;
11
+ }
12
+ export interface L2ReconcileOptions {
13
+ provider: string;
14
+ start: string;
15
+ end: string;
16
+ }
17
+ /**
18
+ * L2 对账: 单 provider, 时间窗口
19
+ */
20
+ export declare function reconcileL2(deps: L2ReconcileDeps, opts: L2ReconcileOptions): Promise<ReconciliationResult>;
21
+ //# sourceMappingURL=l2-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2-reconciler.d.ts","sourceRoot":"","sources":["../../src/reconciliation/l2-reconciler.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,KAAK,EAAE,eAAe,CAAC;IACvB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAsED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,oBAAoB,CAAC,CAiJ/B"}
@@ -0,0 +1,241 @@
1
+ // sdk/skillfm-local/src/reconciliation/l2-reconciler.ts
2
+ //
3
+ // L2 reconciliation — provider balance 差量 vs L0 本地估算.
4
+ // 适用 DeepSeek / Kimi 等只暴露 balance 不暴露 usage 的 provider.
5
+ //
6
+ // 流程:
7
+ // 1. 维护 balance 历史快照 (~/.skillfm/balance-history.jsonl)
8
+ // 2. 每次 reconcile 时, 拉当前 balance + 历史窗口起始点 balance, 算 drop
9
+ // 3. balance_drop = old_balance - new_balance (单位 USD)
10
+ // 4. 跟 L0 同窗口估算对比, 偏差分级
11
+ // 5. 余额耗尽预测: days_to_zero = current_balance / 7天日均 drop
12
+ // 6. 异常: drop 速度 > 用量增长 × 1.5 → balance_drop_too_fast (可能盗用)
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import { homedir } from 'node:os';
16
+ import { classifyDeviation } from './l1-reconciler.js';
17
+ const L2_PROVIDERS = new Set(['deepseek', 'kimi']);
18
+ const DEFAULT_HISTORY_PATH = path.join(homedir(), '.skillfm', 'balance-history.jsonl');
19
+ /**
20
+ * 加载 balance 历史快照 (jsonl)
21
+ */
22
+ async function loadHistory(filePath) {
23
+ if (!fs.existsSync(filePath))
24
+ return [];
25
+ const text = await fs.promises.readFile(filePath, 'utf-8');
26
+ const out = [];
27
+ for (const line of text.split('\n')) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed.startsWith('{'))
30
+ continue;
31
+ try {
32
+ out.push(JSON.parse(trimmed));
33
+ }
34
+ catch {
35
+ // skip malformed
36
+ }
37
+ }
38
+ return out.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
39
+ }
40
+ /**
41
+ * Append 一个 balance snapshot
42
+ */
43
+ async function appendSnapshot(filePath, snap) {
44
+ const dir = path.dirname(filePath);
45
+ if (!fs.existsSync(dir)) {
46
+ await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
47
+ }
48
+ await fs.promises.appendFile(filePath, JSON.stringify(snap) + '\n', { mode: 0o600 });
49
+ }
50
+ /**
51
+ * 找窗口起始点附近的 balance snapshot (用于计算 drop)
52
+ * 策略: 找 windowStart 之前最近的一个 snapshot (上一次 poll 的余额 = 窗口开始时余额)
53
+ */
54
+ function findWindowStartBalance(history, provider, windowStart) {
55
+ const ws = Date.parse(windowStart);
56
+ let best = null;
57
+ for (const s of history) {
58
+ if (s.provider !== provider)
59
+ continue;
60
+ const ts = Date.parse(s.timestamp);
61
+ if (ts > ws)
62
+ break; // history 已 sorted
63
+ best = s;
64
+ }
65
+ return best;
66
+ }
67
+ /**
68
+ * 计算近 7 天日均消费 (用 history snapshots 算 drop / 天数)
69
+ */
70
+ function computeAvgDailyDrop(history, provider) {
71
+ const now = Date.now();
72
+ const sevenDaysAgo = now - 7 * 24 * 3600 * 1000;
73
+ const recent = history.filter((s) => s.provider === provider && Date.parse(s.timestamp) >= sevenDaysAgo);
74
+ if (recent.length < 2)
75
+ return null;
76
+ const first = recent[0];
77
+ const last = recent[recent.length - 1];
78
+ const days = Math.max(0.5, (Date.parse(last.timestamp) - Date.parse(first.timestamp)) / 86400000);
79
+ const drop = first.balance_usd - last.balance_usd;
80
+ if (drop <= 0)
81
+ return null;
82
+ return drop / days;
83
+ }
84
+ /**
85
+ * L2 对账: 单 provider, 时间窗口
86
+ */
87
+ export async function reconcileL2(deps, opts) {
88
+ const provider = opts.provider.toLowerCase();
89
+ const reconciledAt = new Date().toISOString();
90
+ const historyPath = deps.historyPath ?? DEFAULT_HISTORY_PATH;
91
+ const l0Agg = deps.store.aggregate({
92
+ start: opts.start,
93
+ end: opts.end,
94
+ providers: [provider],
95
+ });
96
+ const l0_estimated_usd = l0Agg.total_cost_usd;
97
+ if (!L2_PROVIDERS.has(provider)) {
98
+ return {
99
+ provider,
100
+ reconciled_at: reconciledAt,
101
+ window_start: opts.start,
102
+ window_end: opts.end,
103
+ l0_estimated_usd,
104
+ level: 'no_data',
105
+ anomalies: [],
106
+ human_summary: `${provider} 不支持 L2 balance 对账`,
107
+ };
108
+ }
109
+ const key = await deps.vault.get(provider);
110
+ if (!key) {
111
+ return {
112
+ provider,
113
+ reconciled_at: reconciledAt,
114
+ window_start: opts.start,
115
+ window_end: opts.end,
116
+ l0_estimated_usd,
117
+ level: 'no_data',
118
+ anomalies: ['no_byok_key'],
119
+ human_summary: `${provider} 无 BYOK key, 仅显示 L0 估算 ¥${(l0_estimated_usd * 7.3).toFixed(2)}; 添加 key 即可看真余额 + 对账验真`,
120
+ };
121
+ }
122
+ const connector = deps.connectors[provider];
123
+ if (!connector) {
124
+ return {
125
+ provider,
126
+ reconciled_at: reconciledAt,
127
+ window_start: opts.start,
128
+ window_end: opts.end,
129
+ l0_estimated_usd,
130
+ level: 'no_data',
131
+ anomalies: [],
132
+ human_summary: `${provider} connector 未配置`,
133
+ };
134
+ }
135
+ // 拉当前 balance (DeepSeek/Kimi connector 返 estimated_usd 字段实际是 balance USD)
136
+ let current_balance_usd;
137
+ try {
138
+ const records = await connector.fetchUsage(key, {
139
+ start_date: new Date(opts.start),
140
+ end_date: new Date(opts.end),
141
+ });
142
+ current_balance_usd = records[0]?.estimated_usd ?? 0;
143
+ }
144
+ catch (e) {
145
+ return {
146
+ provider,
147
+ reconciled_at: reconciledAt,
148
+ window_start: opts.start,
149
+ window_end: opts.end,
150
+ l0_estimated_usd,
151
+ level: 'no_data',
152
+ anomalies: ['l1_api_stale'],
153
+ human_summary: `${provider} balance API 调用失败 (${e.message.slice(0, 80)}), 仅显示 L0 估算`,
154
+ };
155
+ }
156
+ // 持久化新 snapshot (无论后面计算如何, 这次 poll 的 balance 必须存)
157
+ const newSnapshot = {
158
+ provider,
159
+ timestamp: reconciledAt,
160
+ balance_usd: current_balance_usd,
161
+ };
162
+ await appendSnapshot(historyPath, newSnapshot);
163
+ // 加载历史 (含刚 append 的 newSnapshot 之前的 records)
164
+ const history = await loadHistory(historyPath);
165
+ const startSnapshot = findWindowStartBalance(history, provider, opts.start);
166
+ let l2_balance_drop_usd;
167
+ let deviation_pct;
168
+ let level = 'no_data';
169
+ const anomalies = [];
170
+ if (startSnapshot) {
171
+ l2_balance_drop_usd = Math.max(0, startSnapshot.balance_usd - current_balance_usd);
172
+ if (l2_balance_drop_usd > 0) {
173
+ deviation_pct = Math.abs(l0_estimated_usd - l2_balance_drop_usd) / l2_balance_drop_usd * 100;
174
+ level = classifyDeviation(deviation_pct);
175
+ // 异常: balance drop 速度 > L0 估算 × 1.5 (可能盗用)
176
+ if (l2_balance_drop_usd > l0_estimated_usd * 1.5 && l0_estimated_usd > 0.01) {
177
+ anomalies.push('balance_drop_too_fast');
178
+ }
179
+ }
180
+ else {
181
+ // 余额没掉 (用户充钱了 / 或没消费)
182
+ level = 'healthy';
183
+ l2_balance_drop_usd = 0;
184
+ }
185
+ }
186
+ else {
187
+ // 无历史 snapshot, 这是首次 poll, 无法对账
188
+ level = 'no_data';
189
+ }
190
+ // 余额耗尽预测
191
+ const avgDailyDrop = computeAvgDailyDrop(history, provider);
192
+ const days_to_zero = avgDailyDrop && avgDailyDrop > 0
193
+ ? current_balance_usd / avgDailyDrop
194
+ : null;
195
+ if (days_to_zero != null) {
196
+ if (days_to_zero < 3)
197
+ anomalies.push('budget_critical');
198
+ else if (days_to_zero < 7)
199
+ anomalies.push('budget_exhausting');
200
+ }
201
+ const human_summary = buildL2Summary({
202
+ provider,
203
+ l0_estimated_usd,
204
+ l2_balance_drop_usd,
205
+ current_balance_usd,
206
+ days_to_zero,
207
+ deviation_pct,
208
+ level,
209
+ });
210
+ return {
211
+ provider,
212
+ reconciled_at: reconciledAt,
213
+ window_start: opts.start,
214
+ window_end: opts.end,
215
+ l0_estimated_usd,
216
+ l2_balance_drop_usd,
217
+ deviation_pct,
218
+ level,
219
+ anomalies,
220
+ remaining_balance_usd: current_balance_usd,
221
+ days_to_zero,
222
+ human_summary,
223
+ };
224
+ }
225
+ function buildL2Summary(input) {
226
+ const balCny = (input.current_balance_usd * 7.3).toFixed(2);
227
+ const days = input.days_to_zero != null ? input.days_to_zero.toFixed(1) : '?';
228
+ const dropCny = input.l2_balance_drop_usd != null
229
+ ? (input.l2_balance_drop_usd * 7.3).toFixed(2)
230
+ : null;
231
+ if (input.level === 'no_data' || dropCny == null) {
232
+ return `${input.provider}: 余额 ¥${balCny}, 收集中 (首次 poll)`;
233
+ }
234
+ const cny0 = (input.l0_estimated_usd * 7.3).toFixed(2);
235
+ const pct = input.deviation_pct?.toFixed(1) ?? '?';
236
+ const emoji = {
237
+ healthy: '✅', warning: '⚠', medium: '🔴', severe: '🚨', no_data: '❓',
238
+ }[input.level];
239
+ return `${input.provider}: 估算 ¥${cny0} vs 真扣 ¥${dropCny} 偏差 ${pct}% ${emoji} | 余额 ¥${balCny} → ${days} 天耗尽`;
240
+ }
241
+ //# sourceMappingURL=l2-reconciler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2-reconciler.js","sourceRoot":"","sources":["../../src/reconciliation/l2-reconciler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ export type DeviationLevel = 'healthy' | 'warning' | 'medium' | 'severe' | 'no_data';
2
+ export type AnomalyKind = 'balance_drop_too_fast' | 'l1_api_stale' | 'no_byok_key' | 'price_table_outdated' | 'budget_exhausting' | 'budget_critical';
3
+ /**
4
+ * 单 provider 的对账结果 (一个时间窗口内)
5
+ */
6
+ export interface ReconciliationResult {
7
+ provider: string;
8
+ reconciled_at: string;
9
+ window_start: string;
10
+ window_end: string;
11
+ /** L0: 本地 trajectory 估算的成本 USD */
12
+ l0_estimated_usd: number;
13
+ /** L1: provider admin API 拉的真账单 USD (仅 OpenAI/Anthropic) */
14
+ l1_actual_usd?: number;
15
+ /** L2: provider balance 在窗口内的下跌量 USD (仅 DeepSeek/Kimi 等) */
16
+ l2_balance_drop_usd?: number;
17
+ /** 跟"真"对比的偏差百分比 (优先 L1 > L2) */
18
+ deviation_pct?: number;
19
+ /** 偏差分级 */
20
+ level: DeviationLevel;
21
+ /** 检测到的异常 */
22
+ anomalies: AnomalyKind[];
23
+ /** L2 当前余额 USD (仅 DeepSeek/Kimi 等有 balance) */
24
+ remaining_balance_usd?: number;
25
+ /** L2 余额耗尽预测 (按当前 7 天日均) */
26
+ days_to_zero?: number | null;
27
+ /** 人类可读的解释 (用于 surface 给 agent / 用户) */
28
+ human_summary: string;
29
+ }
30
+ /**
31
+ * 对账窗口选项
32
+ */
33
+ export interface ReconcileOptions {
34
+ /** ISO 8601 起始 (默认今日 00:00) */
35
+ start?: string;
36
+ /** ISO 8601 结束 (默认现在) */
37
+ end?: string;
38
+ /** 仅这些 provider (默认所有 vault 里有 key 的) */
39
+ providers?: string[];
40
+ }
41
+ /**
42
+ * Balance 历史快照 (持久化在 ~/.skillfm/balance-history.jsonl)
43
+ */
44
+ export interface BalanceSnapshot {
45
+ provider: string;
46
+ timestamp: string;
47
+ balance_usd: number;
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/reconciliation/types.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,SAAS,CAAC;AAEd,MAAM,MAAM,WAAW,GACnB,uBAAuB,GACvB,cAAc,GACd,aAAa,GACb,sBAAsB,GACtB,mBAAmB,GACnB,iBAAiB,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAEnB,kCAAkC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,gCAAgC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,WAAW;IACX,KAAK,EAAE,cAAc,CAAC;IAEtB,aAAa;IACb,SAAS,EAAE,WAAW,EAAE,CAAC;IAEzB,+CAA+C;IAC/C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B,wCAAwC;IACxC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,9 @@
1
+ // sdk/skillfm-local/src/reconciliation/types.ts
2
+ //
3
+ // L0+L1+L2 三口径对账 — 真护城河 (PRD V1.3 §5).
4
+ // 对账 = 把 L0 (本地 trajectory 估算) 跟 L1 (provider admin API) /
5
+ // L2 (provider balance diff) 真账单交叉验证.
6
+ //
7
+ // 输出: ReconciliationResult (per-provider, 跟时间窗口对应).
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/reconciliation/types.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,uCAAuC;AACvC,2DAA2D;AAC3D,sCAAsC;AACtC,EAAE;AACF,oDAAoD"}
@@ -0,0 +1,7 @@
1
+ import type { LocalUsageStore } from '../usage-local/store.js';
2
+ import type { SaveTokenSuggestion, SaveTokenScanOptions } from './types.js';
3
+ /**
4
+ * 扫描 + 返建议
5
+ */
6
+ export declare function scanE1Router(store: LocalUsageStore, opts: SaveTokenScanOptions): Promise<SaveTokenSuggestion[]>;
7
+ //# sourceMappingURL=e1-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e1-router.d.ts","sourceRoot":"","sources":["../../src/save-token/e1-router.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA2C5E;;GAEG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAoEhC"}
@@ -0,0 +1,112 @@
1
+ // sdk/skillfm-local/src/save-token/e1-router.ts
2
+ //
3
+ // E1 Free 智能 Model Router — cascading 启发式 (PRD §6.2 Phase 1).
4
+ // 扫 trajectory 找 expensive model 调用, 推荐切 cheaper 同档.
5
+ //
6
+ // MVP: 只做规则启发式 (基于价目表 + model family 映射).
7
+ // Pro 升级 (2.7.5): 通过 gateway hook 真自动 cascade + brain LLM 评 quality.
8
+ import { getModelPrice, USD_TO_CNY } from '../usage-local/model-pricing.js';
9
+ import { timeWindow } from '../usage-local/store.js';
10
+ /**
11
+ * Model family 映射: expensive → cheaper 同档替代
12
+ * 仅在质量"通常"够 (90%+) 的场景下推荐. 用户决定切不切.
13
+ */
14
+ const CASCADE_MAP = [
15
+ {
16
+ expensive_pattern: /claude.*opus/i,
17
+ cheaper: { provider: 'anthropic', model_id: 'claude-sonnet-4-6' },
18
+ description: 'Opus 级任务很多 Sonnet 4.6 能干 (95%+ 质量)',
19
+ },
20
+ {
21
+ expensive_pattern: /claude.*sonnet/i,
22
+ cheaper: { provider: 'anthropic', model_id: 'claude-haiku-4-5' },
23
+ description: '日常 Sonnet 调用 80% 任务 Haiku 4.5 同质量, 价格 1/4',
24
+ },
25
+ {
26
+ expensive_pattern: /(gpt-4o|gpt-4-turbo)$/i,
27
+ cheaper: { provider: 'openai', model_id: 'gpt-4o-mini' },
28
+ description: 'GPT-4o 很多任务 4o-mini 同质量, 价格 1/17',
29
+ },
30
+ {
31
+ expensive_pattern: /^o1$/i,
32
+ cheaper: { provider: 'openai', model_id: 'o1-mini' },
33
+ description: 'o1 reasoning 任务 o1-mini 同等推理能力, 价格 1/5',
34
+ },
35
+ {
36
+ expensive_pattern: /minimax-m2\.7/i,
37
+ cheaper: { provider: 'minimax-cn', model_id: 'MiniMax-M2.5' },
38
+ description: 'M2.7 → M2.5 价格 1/2.5, 大部分 routine 任务质量同档',
39
+ },
40
+ {
41
+ expensive_pattern: /deepseek-v4-pro/i,
42
+ cheaper: { provider: 'deepseek', model_id: 'deepseek-v4-flash' },
43
+ description: 'V4 Pro → V4 Flash 价格 1/5',
44
+ },
45
+ ];
46
+ /**
47
+ * 扫描 + 返建议
48
+ */
49
+ export async function scanE1Router(store, opts) {
50
+ const window = opts.start && opts.end
51
+ ? { start: opts.start, end: opts.end }
52
+ : timeWindow('week'); // 默认近 7 天
53
+ const records = store.query(window);
54
+ if (records.length === 0)
55
+ return [];
56
+ // 按 (provider, model) 聚合
57
+ const byModel = new Map();
58
+ for (const r of records) {
59
+ const key = `${r.provider}/${r.model_id}`;
60
+ const cur = byModel.get(key) ?? { count: 0, tokens_in: 0, tokens_out: 0, cost_usd: 0 };
61
+ cur.count += 1;
62
+ cur.tokens_in += r.input_tokens;
63
+ cur.tokens_out += r.output_tokens;
64
+ cur.cost_usd += r.estimated_cost_usd;
65
+ byModel.set(key, cur);
66
+ }
67
+ const suggestions = [];
68
+ for (const [key, agg] of byModel) {
69
+ if (agg.cost_usd < 0.5)
70
+ continue; // 低于 ¥3.65 不值得 surface
71
+ for (const rule of CASCADE_MAP) {
72
+ if (rule.expensive_pattern.test(key)) {
73
+ // 算切 cheaper 后估省
74
+ const cheaperPrice = getModelPrice(rule.cheaper.provider, rule.cheaper.model_id);
75
+ const newCost = (agg.tokens_in / 1_000_000) * cheaperPrice.input_per_1m
76
+ + (agg.tokens_out / 1_000_000) * cheaperPrice.output_per_1m;
77
+ const savings_usd = agg.cost_usd - newCost;
78
+ if (savings_usd <= 0)
79
+ continue;
80
+ const savings_pct = (savings_usd / agg.cost_usd) * 100;
81
+ // 月度估算 (从 7 天 → 30 天 × 4.3 比例)
82
+ const monthly_savings_usd = savings_usd * (30 / 7);
83
+ const monthly_savings_tokens = (agg.tokens_in + agg.tokens_out) * (30 / 7);
84
+ const fix_method = opts.is_pro ? 'pro_auto' : 'free_manual';
85
+ suggestions.push({
86
+ engine: 'E1_router',
87
+ headline: `${agg.count} 次 ${key} 调用可切 ${rule.cheaper.provider}/${rule.cheaper.model_id} (${rule.description.slice(0, 50)}...)`,
88
+ estimated_savings_pct: Math.round(savings_pct),
89
+ estimated_savings_tokens: Math.round(monthly_savings_tokens),
90
+ estimated_savings_cny: Math.round(monthly_savings_usd * USD_TO_CNY * 100) / 100,
91
+ fix_method,
92
+ fix_instructions: opts.is_pro
93
+ ? undefined
94
+ : `在你 IDE 中, 把 model 设置改成 ${rule.cheaper.provider}/${rule.cheaper.model_id}; 或手动针对 routine 任务用 cheaper model`,
95
+ pro_unlock_label: opts.is_pro
96
+ ? undefined
97
+ : `Pro 升级后 SkillFM gateway 自动 cascade routing — 估月省 ¥${Math.round(monthly_savings_usd * USD_TO_CNY)}`,
98
+ details: {
99
+ from_model: key,
100
+ to_model: `${rule.cheaper.provider}/${rule.cheaper.model_id}`,
101
+ calls_in_window: agg.count,
102
+ cost_usd_window: agg.cost_usd,
103
+ },
104
+ });
105
+ break; // 一个 expensive model 只推一个 cheaper 候选
106
+ }
107
+ }
108
+ }
109
+ // 按估省排序, 大头先 surface
110
+ return suggestions.sort((a, b) => b.estimated_savings_cny - a.estimated_savings_cny);
111
+ }
112
+ //# sourceMappingURL=e1-router.js.map