dsh-layered-memory 0.8.5 → 0.8.7

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.
@@ -0,0 +1,178 @@
1
+ let db = null;
2
+ let retentionDays = 365;
3
+ /** 插件启动时注入 db 与明细保留期(index.ts 调用;retentionDays 0 = 永久保留)。 */
4
+ export function initTokenCost(d, retention) {
5
+ db = d;
6
+ retentionDays = Math.max(0, Math.round(retention));
7
+ }
8
+ /** 插件卸载时清空 db 引用(index.ts 的 ctx.effect 清理里调用,防悬空引用)。 */
9
+ export function resetTokenCost() {
10
+ db = null;
11
+ }
12
+ /** 记录一次蒸馏调用成本(callLLM 出口调用;provider/model 由调用方传入)。 */
13
+ export function recordCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens) {
14
+ if (!db)
15
+ return;
16
+ db.insertCostCall(provider, model, layer, inputChars, outputTokens, reasoningTokens, retentionDays);
17
+ }
18
+ /** 四窗口定义:range + 回看毫秒数(all 的 ms 恒 0)。 */
19
+ const WINDOW_DEFS = [
20
+ { range: 'day', ms: 24 * 3600_000 },
21
+ { range: 'week', ms: 7 * 24 * 3600_000 },
22
+ { range: 'month', ms: 30 * 24 * 3600_000 },
23
+ { range: 'all', ms: 0 },
24
+ ];
25
+ /** 趋势桶宽(毫秒)与桶数。 */
26
+ const TREND_MS = { day: 24 * 3600_000, week: 7 * 24 * 3600_000, month: 30 * 24 * 3600_000 };
27
+ const TREND_COUNT = { day: 30, week: 12, month: 12 };
28
+ /** 三个归并层级。 */
29
+ const LAYERS = ['l1', 'l2', 'l3'];
30
+ /** 本地时区偏移(东正西负):把 SQL 端 UTC epoch 桶边界对齐到本地午夜。 */
31
+ function localOffsetMs() {
32
+ return -new Date().getTimezoneOffset() * 60_000;
33
+ }
34
+ /** 已排序序列的中位数(偶数取中间两者平均;空返回 0)。 */
35
+ function medianOf(sorted) {
36
+ const n = sorted.length;
37
+ if (n === 0)
38
+ return 0;
39
+ const mid = Math.floor(n / 2);
40
+ return n % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
41
+ }
42
+ /** provider/model 复合键(避免跨 provider 同名 model 在聚合里混淆)。 */
43
+ function modelKey(provider, model) {
44
+ return provider + '/' + model;
45
+ }
46
+ /** 从某模型某粒度的 bucket 序列算均值/中位数(活跃桶口径:只除有调用的桶数)。 */
47
+ function statOf(rows) {
48
+ const active = rows.filter((r) => r.calls > 0);
49
+ if (active.length === 0)
50
+ return { avgCalls: 0, avgOutput: 0, medianOutput: 0 };
51
+ const calls = active.reduce((s, r) => s + r.calls, 0);
52
+ const output = active.reduce((s, r) => s + r.outputTokens, 0);
53
+ return {
54
+ avgCalls: calls / active.length,
55
+ avgOutput: output / active.length,
56
+ medianOutput: medianOf(active.map((r) => r.outputTokens).sort((a, b) => a - b)),
57
+ };
58
+ }
59
+ /** 从 day/week/month 三组 bucket 行组装每个模型的统计指标(provider/model 复合键)。 */
60
+ function buildModelMetrics(dayRows, weekRows, monthRows) {
61
+ const keys = new Set();
62
+ for (const r of dayRows)
63
+ keys.add(modelKey(r.provider, r.model));
64
+ for (const r of weekRows)
65
+ keys.add(modelKey(r.provider, r.model));
66
+ for (const r of monthRows)
67
+ keys.add(modelKey(r.provider, r.model));
68
+ return Array.from(keys)
69
+ .sort()
70
+ .map((key) => {
71
+ const d = statOf(dayRows.filter((r) => modelKey(r.provider, r.model) === key));
72
+ const w = statOf(weekRows.filter((r) => modelKey(r.provider, r.model) === key));
73
+ const m = statOf(monthRows.filter((r) => modelKey(r.provider, r.model) === key));
74
+ return {
75
+ model: key,
76
+ dayCalls: d.avgCalls,
77
+ weekCalls: w.avgCalls,
78
+ monthCalls: m.avgCalls,
79
+ dayOutput: d.avgOutput,
80
+ dayMedian: d.medianOutput,
81
+ weekOutput: w.avgOutput,
82
+ weekMedian: w.medianOutput,
83
+ monthOutput: m.avgOutput,
84
+ monthMedian: m.medianOutput,
85
+ };
86
+ });
87
+ }
88
+ /** 从某层级某粒度的 bucket 行生成连续趋势桶(空桶补 0;count 为桶数,由调用方按展示范围算)。 */
89
+ function buildTrend(rows, granularity, now, count) {
90
+ const bucketMs = TREND_MS[granularity];
91
+ const offset = localOffsetMs();
92
+ const cur = Math.floor((now + offset) / bucketMs);
93
+ const buckets = [];
94
+ for (let i = count - 1; i >= 0; i--) {
95
+ const b = cur - i;
96
+ buckets.push({ ts: b * bucketMs - offset, total: 0, byModel: {} });
97
+ }
98
+ for (const r of rows) {
99
+ const idx = count - 1 - (cur - r.bucket);
100
+ if (idx < 0 || idx >= count)
101
+ continue;
102
+ const tb = buckets[idx];
103
+ tb.total += r.outputTokens;
104
+ const key = modelKey(r.provider, r.model);
105
+ tb.byModel[key] = (tb.byModel[key] ?? 0) + r.outputTokens;
106
+ }
107
+ return buckets;
108
+ }
109
+ /** 读成本看板快照(db 未注入/降级时返回全零结构,不抛错;rangeDays>0 = 趋势展示近 N 天)。 */
110
+ export function snapshotTokenCost(granularity, rangeDays) {
111
+ const now = Date.now();
112
+ const emptyWindow = (range, ms) => ({
113
+ range,
114
+ since: ms === 0 ? 0 : now - ms,
115
+ calls: 0,
116
+ inputChars: 0,
117
+ outputTokens: 0,
118
+ reasoningTokens: 0,
119
+ avgOutputTokens: 0,
120
+ medianOutputTokens: 0,
121
+ });
122
+ if (!db) {
123
+ return {
124
+ windows: WINDOW_DEFS.map((w) => emptyWindow(w.range, w.ms)),
125
+ byModel: [],
126
+ byLayer: [],
127
+ byLayerStats: [],
128
+ trend: { granularity, byLayer: { l1: [], l2: [], l3: [] } },
129
+ };
130
+ }
131
+ // 四窗口总聚合(累计窗口顺带取 byModel)
132
+ const d = db;
133
+ const windows = [];
134
+ let byModel = [];
135
+ for (const w of WINDOW_DEFS) {
136
+ const since = w.ms === 0 ? 0 : now - w.ms;
137
+ const agg = d.aggregateCost(since);
138
+ windows.push({ range: w.range, since, ...agg.total });
139
+ if (w.range === 'all')
140
+ byModel = agg.byModel;
141
+ }
142
+ // 按层级 × 窗口聚合(层级×窗口表格):每个窗口调一次 aggregateCostByLayer,再按层组装
143
+ const layerByWindow = WINDOW_DEFS.map((w) => {
144
+ const since = w.ms === 0 ? 0 : now - w.ms;
145
+ return { range: w.range, rows: d.aggregateCostByLayer(since) };
146
+ });
147
+ const byLayer = LAYERS.map((layer) => ({
148
+ layer,
149
+ windows: layerByWindow.map(({ range, rows }) => {
150
+ const row = rows.find((r) => r.layer === layer);
151
+ return {
152
+ range,
153
+ calls: row?.calls ?? 0,
154
+ inputChars: row?.inputChars ?? 0,
155
+ outputTokens: row?.outputTokens ?? 0,
156
+ reasoningTokens: row?.reasoningTokens ?? 0,
157
+ avgOutputTokens: row?.avgOutputTokens ?? 0,
158
+ medianOutputTokens: row?.medianOutputTokens ?? 0,
159
+ };
160
+ }),
161
+ }));
162
+ // 每层级模型统计(全量历史口径)+ 趋势(按展示范围)
163
+ const offset = localOffsetMs();
164
+ const byLayerStats = [];
165
+ const trendByLayer = { l1: [], l2: [], l3: [] };
166
+ // 趋势展示范围:rangeDays > 0 = 近 N 天(强制按「日」粒度出 N 个日桶,不受周/月聚合影响);否则用默认窗口
167
+ const trendGranularity = rangeDays > 0 ? 'day' : granularity;
168
+ const trendSince = rangeDays > 0 ? now - rangeDays * 24 * 3600_000 : 0;
169
+ const trendCount = rangeDays > 0 ? rangeDays : TREND_COUNT[granularity];
170
+ for (const layer of LAYERS) {
171
+ const dayRows = d.aggregateByBucket(TREND_MS.day, offset, 0, layer);
172
+ const weekRows = d.aggregateByBucket(TREND_MS.week, offset, 0, layer);
173
+ const monthRows = d.aggregateByBucket(TREND_MS.month, offset, 0, layer);
174
+ byLayerStats.push({ layer, models: buildModelMetrics(dayRows, weekRows, monthRows) });
175
+ trendByLayer[layer] = buildTrend(d.aggregateByBucket(TREND_MS[trendGranularity], offset, trendSince, layer), trendGranularity, now, trendCount);
176
+ }
177
+ return { windows, byModel, byLayer, byLayerStats, trend: { granularity: trendGranularity, byLayer: trendByLayer } };
178
+ }
@@ -27,6 +27,6 @@ export declare function applyRecallBudget(lines: string[], limits: RecallBudgetL
27
27
  * resolve 为空结果(空数组)与超时(undefined)语义不同,调用方据此区分日志。
28
28
  */
29
29
  export declare function raceRecallTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | undefined>;
30
- /** 召回路径远程嵌入 fetch 的内层钳制(固定值):给 FTS 降级留出总预算内的时间。
31
- * 仅作用于远程 HTTP 调用;本地推理不受钳制(进程内 CPU 推理无挂起风险)。 */
30
+ /** 召回路径嵌入调用的内层钳制(固定值):给 FTS 降级留出总预算内的时间。
31
+ * 远程作用于 HTTP fetch;本地作用于 worker 代理的等待(race 放弃、迟到回复丢弃)。 */
32
32
  export declare const RECALL_EMBED_CAP_MS = 3000;
@@ -80,6 +80,6 @@ export async function raceRecallTimeout(promise, timeoutMs) {
80
80
  clearTimeout(timer);
81
81
  }
82
82
  }
83
- /** 召回路径远程嵌入 fetch 的内层钳制(固定值):给 FTS 降级留出总预算内的时间。
84
- * 仅作用于远程 HTTP 调用;本地推理不受钳制(进程内 CPU 推理无挂起风险)。 */
83
+ /** 召回路径嵌入调用的内层钳制(固定值):给 FTS 降级留出总预算内的时间。
84
+ * 远程作用于 HTTP fetch;本地作用于 worker 代理的等待(race 放弃、迟到回复丢弃)。 */
85
85
  export const RECALL_EMBED_CAP_MS = 3_000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-layered-memory",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",