dsh-prime-memory 0.11.0
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.en.md +28 -0
- package/CHANGELOG.ja.md +30 -0
- package/CHANGELOG.ko.md +30 -0
- package/CHANGELOG.md +1220 -0
- package/ENGINEERING-NOTES.md +452 -0
- package/INSTALL.en.md +92 -0
- package/INSTALL.ja.md +92 -0
- package/INSTALL.ko.md +92 -0
- package/INSTALL.md +92 -0
- package/LICENSE +21 -0
- package/README.en.md +458 -0
- package/README.ja.md +306 -0
- package/README.ko.md +306 -0
- package/README.md +425 -0
- package/assets/changelog/0.8.10/01-write-only-pill.png +0 -0
- package/assets/changelog/0.8.9/01-panel.png +0 -0
- package/assets/changelog/0.8.9/02-halo.png +0 -0
- package/assets/changelog/0.8.9/03-layer-segmented-panel.png +0 -0
- package/assets/changelog/0.8.9/04-layer-l1-panel.png +0 -0
- package/assets/img/EmbeddingSource.png +0 -0
- package/assets/img/Hero.png +0 -0
- package/assets/img/Layers.png +0 -0
- package/assets/img/MemoryTools.png +0 -0
- package/assets/img/Modes.png +0 -0
- package/assets/img/ToolTrajectory.png +0 -0
- package/assets/img/ui-dark.jpg +0 -0
- package/assets/img/ui-light.jpg +0 -0
- package/assets/readme/bench-dialog.svg +70 -0
- package/assets/readme/bench-workflow.svg +79 -0
- package/assets/readme/flow.svg +189 -0
- package/assets/readme/storage.svg +115 -0
- package/cordis.patch.yml +16 -0
- package/dist/bench-control.d.ts +34 -0
- package/dist/bench-control.js +16 -0
- package/dist/client.js +4293 -0
- package/dist/config.d.ts +683 -0
- package/dist/config.js +129 -0
- package/dist/contract.d.ts +820 -0
- package/dist/contract.js +1 -0
- package/dist/embedding-worker.cjs +176 -0
- package/dist/graph/apply.d.ts +37 -0
- package/dist/graph/apply.js +270 -0
- package/dist/graph/constraints.d.ts +47 -0
- package/dist/graph/constraints.js +38 -0
- package/dist/graph/search.d.ts +16 -0
- package/dist/graph/search.js +115 -0
- package/dist/graph/types.d.ts +142 -0
- package/dist/graph/types.js +14 -0
- package/dist/hooks/capture.d.ts +32 -0
- package/dist/hooks/capture.js +194 -0
- package/dist/hooks/recall.d.ts +63 -0
- package/dist/hooks/recall.js +429 -0
- package/dist/index.d.ts +534 -0
- package/dist/index.js +344 -0
- package/dist/llm-usage.d.ts +26 -0
- package/dist/llm-usage.js +37 -0
- package/dist/llm.d.ts +153 -0
- package/dist/llm.js +530 -0
- package/dist/pipeline/graph.d.ts +35 -0
- package/dist/pipeline/graph.js +104 -0
- package/dist/pipeline/l1.d.ts +19 -0
- package/dist/pipeline/l1.js +271 -0
- package/dist/pipeline/l2.d.ts +13 -0
- package/dist/pipeline/l2.js +83 -0
- package/dist/pipeline/l3.d.ts +15 -0
- package/dist/pipeline/l3.js +78 -0
- package/dist/pipeline/rebuild.d.ts +61 -0
- package/dist/pipeline/rebuild.js +307 -0
- package/dist/pipeline/ruminate.d.ts +89 -0
- package/dist/pipeline/ruminate.js +298 -0
- package/dist/pipeline/runner.d.ts +167 -0
- package/dist/pipeline/runner.js +638 -0
- package/dist/pipeline/trigger.d.ts +40 -0
- package/dist/pipeline/trigger.js +75 -0
- package/dist/prompts/graph-projection.d.ts +70 -0
- package/dist/prompts/graph-projection.js +167 -0
- package/dist/prompts/l1-dedup.d.ts +22 -0
- package/dist/prompts/l1-dedup.js +251 -0
- package/dist/prompts/l1-extraction.d.ts +22 -0
- package/dist/prompts/l1-extraction.js +457 -0
- package/dist/prompts/persona.d.ts +23 -0
- package/dist/prompts/persona.js +240 -0
- package/dist/prompts/scene.d.ts +32 -0
- package/dist/prompts/scene.js +414 -0
- package/dist/runtime-package-lock.json +982 -0
- package/dist/settings.d.ts +50 -0
- package/dist/settings.js +355 -0
- package/dist/stats.d.ts +109 -0
- package/dist/stats.js +929 -0
- package/dist/store/bm25.d.ts +19 -0
- package/dist/store/bm25.js +63 -0
- package/dist/store/cost-ledger.d.ts +75 -0
- package/dist/store/cost-ledger.js +171 -0
- package/dist/store/download-queue.d.ts +79 -0
- package/dist/store/download-queue.js +424 -0
- package/dist/store/embedding-source.d.ts +118 -0
- package/dist/store/embedding-source.js +443 -0
- package/dist/store/embedding.d.ts +90 -0
- package/dist/store/embedding.js +206 -0
- package/dist/store/graph-store.d.ts +94 -0
- package/dist/store/graph-store.js +641 -0
- package/dist/store/l0.d.ts +40 -0
- package/dist/store/l0.js +197 -0
- package/dist/store/l1.d.ts +93 -0
- package/dist/store/l1.js +297 -0
- package/dist/store/local-embedding.d.ts +89 -0
- package/dist/store/local-embedding.js +227 -0
- package/dist/store/model-catalog.d.ts +48 -0
- package/dist/store/model-catalog.js +81 -0
- package/dist/store/occupancy.d.ts +30 -0
- package/dist/store/occupancy.js +134 -0
- package/dist/store/pending.d.ts +36 -0
- package/dist/store/pending.js +103 -0
- package/dist/store/persona.d.ts +15 -0
- package/dist/store/persona.js +60 -0
- package/dist/store/recall-dedupe.d.ts +26 -0
- package/dist/store/recall-dedupe.js +138 -0
- package/dist/store/runtime-installer.d.ts +59 -0
- package/dist/store/runtime-installer.js +243 -0
- package/dist/store/scenes.d.ts +24 -0
- package/dist/store/scenes.js +160 -0
- package/dist/store/search-utils.d.ts +38 -0
- package/dist/store/search-utils.js +100 -0
- package/dist/store/session-modes.d.ts +35 -0
- package/dist/store/session-modes.js +144 -0
- package/dist/store/sqlite.d.ts +246 -0
- package/dist/store/sqlite.js +1491 -0
- package/dist/store/state.d.ts +41 -0
- package/dist/store/state.js +72 -0
- package/dist/token-cost.d.ts +23 -0
- package/dist/token-cost.js +185 -0
- package/dist/tools/index.d.ts +34 -0
- package/dist/tools/index.js +758 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +38 -0
- package/dist/util/context-occupancy.d.ts +68 -0
- package/dist/util/context-occupancy.js +92 -0
- package/dist/util/filelog.d.ts +6 -0
- package/dist/util/filelog.js +108 -0
- package/dist/util/io.d.ts +18 -0
- package/dist/util/io.js +97 -0
- package/dist/util/recall-budget.d.ts +31 -0
- package/dist/util/recall-budget.js +84 -0
- package/dist/util/sanitize.d.ts +11 -0
- package/dist/util/sanitize.js +67 -0
- package/dist/util/text.d.ts +16 -0
- package/dist/util/text.js +61 -0
- package/dist/util/tokenizer.d.ts +9 -0
- package/dist/util/tokenizer.js +50 -0
- package/dsh.plugin.json +22 -0
- package/package.json +118 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MemoryFamily } from '../types.js';
|
|
2
|
+
export interface MemoryState {
|
|
3
|
+
/** 上次 L1 抽取时间(epoch ms)。 */
|
|
4
|
+
lastExtractAt: number;
|
|
5
|
+
/** 上次 L1 抽取得到的最后一个情境名(情境连续性用)。 */
|
|
6
|
+
lastSceneName: string;
|
|
7
|
+
/** L1 累计抽取条数。 */
|
|
8
|
+
totalExtracted: number;
|
|
9
|
+
/** 自上次 L2 整合以来的新记忆数。 */
|
|
10
|
+
newMemoriesSinceL2: number;
|
|
11
|
+
/** 上次 L2 整合时间。 */
|
|
12
|
+
lastL2At: number;
|
|
13
|
+
/** 自上次 L3 蒸馏以来的新记忆数。 */
|
|
14
|
+
memoriesSinceL3: number;
|
|
15
|
+
/** 上次 L3 蒸馏时间。 */
|
|
16
|
+
lastL3At: number;
|
|
17
|
+
/** L3 是否已生成过(冷启动判定)。 */
|
|
18
|
+
hasPersona: boolean;
|
|
19
|
+
/** L2 输出请求的 L3 更新原因([PERSONA_UPDATE_REQUEST])。 */
|
|
20
|
+
personaRequestedReason?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function defaultState(): MemoryState;
|
|
23
|
+
export declare class StateStore {
|
|
24
|
+
private readonly file;
|
|
25
|
+
private buckets;
|
|
26
|
+
private migrated;
|
|
27
|
+
constructor(file: string);
|
|
28
|
+
load(): Promise<void>;
|
|
29
|
+
/** v1 → v2 迁移发生时为 true(调用方记日志/落盘)。 */
|
|
30
|
+
get didMigrate(): boolean;
|
|
31
|
+
/** 取某族的 checkpoint(活引用——改字段后 save 生效)。 */
|
|
32
|
+
forFamily(family: MemoryFamily): MemoryState;
|
|
33
|
+
/**
|
|
34
|
+
* 重建用:两族 checkpoint 重置为默认值。
|
|
35
|
+
* 必须原地突变(Object.assign)——runner.states 等处持有桶对象的活引用,
|
|
36
|
+
* 换新对象会让引用指向已废弃的桶,后续计数写到内存孤儿上。
|
|
37
|
+
*/
|
|
38
|
+
reset(): void;
|
|
39
|
+
save(): Promise<void>;
|
|
40
|
+
static pathFor(dataDir: string): string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管线 checkpoint 状态。持久化在 <dataDir>/state.json。
|
|
3
|
+
*
|
|
4
|
+
* v2 起按族分桶(L2/L3 分族隔离后阈值计数、情境链各自独立);
|
|
5
|
+
* 旧平铺格式(v1)在 load 时整体迁入 chat 桶(历史数据由 chat 档蒸馏产出)。
|
|
6
|
+
*/
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { atomicWriteJson, readJsonIfExists } from '../util/io.js';
|
|
9
|
+
export function defaultState() {
|
|
10
|
+
return {
|
|
11
|
+
lastExtractAt: 0,
|
|
12
|
+
lastSceneName: '',
|
|
13
|
+
totalExtracted: 0,
|
|
14
|
+
newMemoriesSinceL2: 0,
|
|
15
|
+
lastL2At: 0,
|
|
16
|
+
memoriesSinceL3: 0,
|
|
17
|
+
lastL3At: 0,
|
|
18
|
+
hasPersona: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export class StateStore {
|
|
22
|
+
file;
|
|
23
|
+
// 声明即初始化:forFamily 在 load 完成前也安全(stats 面板可能早于 runner.init 拉取)
|
|
24
|
+
buckets = { chat: defaultState(), work: defaultState() };
|
|
25
|
+
migrated = false;
|
|
26
|
+
constructor(file) {
|
|
27
|
+
this.file = file;
|
|
28
|
+
}
|
|
29
|
+
async load() {
|
|
30
|
+
const raw = await readJsonIfExists(this.file);
|
|
31
|
+
if (!raw)
|
|
32
|
+
return;
|
|
33
|
+
if (raw.version === 2 && raw.families && typeof raw.families === 'object') {
|
|
34
|
+
// v2:逐族宽容合并(新字段自动带默认值)
|
|
35
|
+
this.buckets = {
|
|
36
|
+
chat: { ...defaultState(), ...(raw.families.chat ?? {}) },
|
|
37
|
+
work: { ...defaultState(), ...(raw.families.work ?? {}) },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// v1 平铺 → 整体迁入 chat 桶(历史数据是 chat 档蒸馏产出)
|
|
42
|
+
this.buckets = { chat: { ...defaultState(), ...raw }, work: defaultState() };
|
|
43
|
+
this.migrated = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** v1 → v2 迁移发生时为 true(调用方记日志/落盘)。 */
|
|
47
|
+
get didMigrate() {
|
|
48
|
+
return this.migrated;
|
|
49
|
+
}
|
|
50
|
+
/** 取某族的 checkpoint(活引用——改字段后 save 生效)。 */
|
|
51
|
+
forFamily(family) {
|
|
52
|
+
return this.buckets[family];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 重建用:两族 checkpoint 重置为默认值。
|
|
56
|
+
* 必须原地突变(Object.assign)——runner.states 等处持有桶对象的活引用,
|
|
57
|
+
* 换新对象会让引用指向已废弃的桶,后续计数写到内存孤儿上。
|
|
58
|
+
*/
|
|
59
|
+
reset() {
|
|
60
|
+
for (const family of ['chat', 'work']) {
|
|
61
|
+
Object.assign(this.buckets[family], defaultState());
|
|
62
|
+
this.buckets[family].personaRequestedReason = undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async save() {
|
|
66
|
+
const file = { version: 2, families: this.buckets };
|
|
67
|
+
await atomicWriteJson(this.file, file);
|
|
68
|
+
}
|
|
69
|
+
static pathFor(dataDir) {
|
|
70
|
+
return path.join(dataDir, 'state.json');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 蒸馏成本看板账本:把每次蒸馏调用(model × layer)的 token 成本写入 SQLite 明细表。
|
|
3
|
+
*
|
|
4
|
+
* 模块级单例 + init 注入 db:callLLM 拿不到 db(db 在 index.ts 运行时创建),
|
|
5
|
+
* 故通过 initTokenCost(db, retentionDays) 在插件启动时注入;recordCostCall 每次
|
|
6
|
+
* 调用写一行明细。
|
|
7
|
+
*
|
|
8
|
+
* 与 llm-usage.ts 的关系:那是"按 layer 累计的纯内存计数器"(给 bench 用);
|
|
9
|
+
* 本模块补上三个缺口——按 model 分组、持久化(保留期可配置,默认 365 天)、
|
|
10
|
+
* 面向 UI 成本看板。
|
|
11
|
+
*/
|
|
12
|
+
import type { CostSnapshot, Granularity } from './contract.js';
|
|
13
|
+
import type { DistillLayer } from './llm-usage.js';
|
|
14
|
+
import type { MemoryDb } from './store/sqlite.js';
|
|
15
|
+
export type { CostWindow, Granularity, ModelMetrics, LayerMetrics, LayerWindow, LayerCost, TrendBucket, TrendSnapshot, CostSnapshot, } from './contract.js';
|
|
16
|
+
/** 插件启动时注入 db 与明细保留期(index.ts 调用;retentionDays 0 = 永久保留)。 */
|
|
17
|
+
export declare function initTokenCost(d: MemoryDb, retention: number): void;
|
|
18
|
+
/** 插件卸载时清空 db 引用(index.ts 的 ctx.effect 清理里调用,防悬空引用)。 */
|
|
19
|
+
export declare function resetTokenCost(): void;
|
|
20
|
+
/** 记录一次蒸馏调用成本(callLLM 出口调用;provider/model 由调用方传入)。 */
|
|
21
|
+
export declare function recordCostCall(provider: string, model: string, layer: DistillLayer, inputChars: number, outputTokens: number, reasoningTokens: number): void;
|
|
22
|
+
/** 读成本看板快照(db 未注入/降级时返回全零结构,不抛错;rangeDays>0 = 趋势展示近 N 天)。 */
|
|
23
|
+
export declare function snapshotTokenCost(granularity: Granularity, rangeDays: number): CostSnapshot;
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
+
* 三个归并层级(层级表格/趋势的分组口径)。
|
|
30
|
+
* 豁免约定:graph 投影调用(DistillLayer 'graph')的成本**进总额窗口与按模型
|
|
31
|
+
* 分组**(windows/byModel),但**不进分层表格与趋势**(byLayer/byLayerStats/
|
|
32
|
+
* trend)——层级表是"蒸馏管线 l1→l2→l3"的产物结构视图,图谱投影是旁路增强,
|
|
33
|
+
* 混入会破坏该表"逐层产物成本"的语义。aggregateCostByLayer 的 SQL 会原样带出
|
|
34
|
+
* 'graph' 行,此处映射时丢弃(聚合口径变更只需改这一处)。
|
|
35
|
+
*/
|
|
36
|
+
const LAYERS = ['l1', 'l2', 'l3'];
|
|
37
|
+
/** 本地时区偏移(东正西负):把 SQL 端 UTC epoch 桶边界对齐到本地午夜。 */
|
|
38
|
+
function localOffsetMs() {
|
|
39
|
+
return -new Date().getTimezoneOffset() * 60_000;
|
|
40
|
+
}
|
|
41
|
+
/** 已排序序列的中位数(偶数取中间两者平均;空返回 0)。 */
|
|
42
|
+
function medianOf(sorted) {
|
|
43
|
+
const n = sorted.length;
|
|
44
|
+
if (n === 0)
|
|
45
|
+
return 0;
|
|
46
|
+
const mid = Math.floor(n / 2);
|
|
47
|
+
return n % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
48
|
+
}
|
|
49
|
+
/** provider/model 复合键(避免跨 provider 同名 model 在聚合里混淆)。 */
|
|
50
|
+
function modelKey(provider, model) {
|
|
51
|
+
return provider + '/' + model;
|
|
52
|
+
}
|
|
53
|
+
/** 从某模型某粒度的 bucket 序列算均值/中位数(活跃桶口径:只除有调用的桶数)。 */
|
|
54
|
+
function statOf(rows) {
|
|
55
|
+
const active = rows.filter((r) => r.calls > 0);
|
|
56
|
+
if (active.length === 0)
|
|
57
|
+
return { avgCalls: 0, avgOutput: 0, medianOutput: 0 };
|
|
58
|
+
const calls = active.reduce((s, r) => s + r.calls, 0);
|
|
59
|
+
const output = active.reduce((s, r) => s + r.outputTokens, 0);
|
|
60
|
+
return {
|
|
61
|
+
avgCalls: calls / active.length,
|
|
62
|
+
avgOutput: output / active.length,
|
|
63
|
+
medianOutput: medianOf(active.map((r) => r.outputTokens).sort((a, b) => a - b)),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** 从 day/week/month 三组 bucket 行组装每个模型的统计指标(provider/model 复合键)。 */
|
|
67
|
+
function buildModelMetrics(dayRows, weekRows, monthRows) {
|
|
68
|
+
const keys = new Set();
|
|
69
|
+
for (const r of dayRows)
|
|
70
|
+
keys.add(modelKey(r.provider, r.model));
|
|
71
|
+
for (const r of weekRows)
|
|
72
|
+
keys.add(modelKey(r.provider, r.model));
|
|
73
|
+
for (const r of monthRows)
|
|
74
|
+
keys.add(modelKey(r.provider, r.model));
|
|
75
|
+
return Array.from(keys)
|
|
76
|
+
.sort()
|
|
77
|
+
.map((key) => {
|
|
78
|
+
const d = statOf(dayRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
79
|
+
const w = statOf(weekRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
80
|
+
const m = statOf(monthRows.filter((r) => modelKey(r.provider, r.model) === key));
|
|
81
|
+
return {
|
|
82
|
+
model: key,
|
|
83
|
+
dayCalls: d.avgCalls,
|
|
84
|
+
weekCalls: w.avgCalls,
|
|
85
|
+
monthCalls: m.avgCalls,
|
|
86
|
+
dayOutput: d.avgOutput,
|
|
87
|
+
dayMedian: d.medianOutput,
|
|
88
|
+
weekOutput: w.avgOutput,
|
|
89
|
+
weekMedian: w.medianOutput,
|
|
90
|
+
monthOutput: m.avgOutput,
|
|
91
|
+
monthMedian: m.medianOutput,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** 从某层级某粒度的 bucket 行生成连续趋势桶(空桶补 0;count 为桶数,由调用方按展示范围算)。 */
|
|
96
|
+
function buildTrend(rows, granularity, now, count) {
|
|
97
|
+
const bucketMs = TREND_MS[granularity];
|
|
98
|
+
const offset = localOffsetMs();
|
|
99
|
+
const cur = Math.floor((now + offset) / bucketMs);
|
|
100
|
+
const buckets = [];
|
|
101
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
102
|
+
const b = cur - i;
|
|
103
|
+
buckets.push({ ts: b * bucketMs - offset, total: 0, byModel: {} });
|
|
104
|
+
}
|
|
105
|
+
for (const r of rows) {
|
|
106
|
+
const idx = count - 1 - (cur - r.bucket);
|
|
107
|
+
if (idx < 0 || idx >= count)
|
|
108
|
+
continue;
|
|
109
|
+
const tb = buckets[idx];
|
|
110
|
+
tb.total += r.outputTokens;
|
|
111
|
+
const key = modelKey(r.provider, r.model);
|
|
112
|
+
tb.byModel[key] = (tb.byModel[key] ?? 0) + r.outputTokens;
|
|
113
|
+
}
|
|
114
|
+
return buckets;
|
|
115
|
+
}
|
|
116
|
+
/** 读成本看板快照(db 未注入/降级时返回全零结构,不抛错;rangeDays>0 = 趋势展示近 N 天)。 */
|
|
117
|
+
export function snapshotTokenCost(granularity, rangeDays) {
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
const emptyWindow = (range, ms) => ({
|
|
120
|
+
range,
|
|
121
|
+
since: ms === 0 ? 0 : now - ms,
|
|
122
|
+
calls: 0,
|
|
123
|
+
inputChars: 0,
|
|
124
|
+
outputTokens: 0,
|
|
125
|
+
reasoningTokens: 0,
|
|
126
|
+
avgOutputTokens: 0,
|
|
127
|
+
medianOutputTokens: 0,
|
|
128
|
+
});
|
|
129
|
+
if (!db) {
|
|
130
|
+
return {
|
|
131
|
+
windows: WINDOW_DEFS.map((w) => emptyWindow(w.range, w.ms)),
|
|
132
|
+
byModel: [],
|
|
133
|
+
byLayer: [],
|
|
134
|
+
byLayerStats: [],
|
|
135
|
+
trend: { granularity, byLayer: { l1: [], l2: [], l3: [] } },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// 四窗口总聚合(累计窗口顺带取 byModel)
|
|
139
|
+
const d = db;
|
|
140
|
+
const windows = [];
|
|
141
|
+
let byModel = [];
|
|
142
|
+
for (const w of WINDOW_DEFS) {
|
|
143
|
+
const since = w.ms === 0 ? 0 : now - w.ms;
|
|
144
|
+
const agg = d.aggregateCost(since);
|
|
145
|
+
windows.push({ range: w.range, since, ...agg.total });
|
|
146
|
+
if (w.range === 'all')
|
|
147
|
+
byModel = agg.byModel;
|
|
148
|
+
}
|
|
149
|
+
// 按层级 × 窗口聚合(层级×窗口表格):每个窗口调一次 aggregateCostByLayer,再按层组装
|
|
150
|
+
const layerByWindow = WINDOW_DEFS.map((w) => {
|
|
151
|
+
const since = w.ms === 0 ? 0 : now - w.ms;
|
|
152
|
+
return { range: w.range, rows: d.aggregateCostByLayer(since) };
|
|
153
|
+
});
|
|
154
|
+
const byLayer = LAYERS.map((layer) => ({
|
|
155
|
+
layer,
|
|
156
|
+
windows: layerByWindow.map(({ range, rows }) => {
|
|
157
|
+
const row = rows.find((r) => r.layer === layer);
|
|
158
|
+
return {
|
|
159
|
+
range,
|
|
160
|
+
calls: row?.calls ?? 0,
|
|
161
|
+
inputChars: row?.inputChars ?? 0,
|
|
162
|
+
outputTokens: row?.outputTokens ?? 0,
|
|
163
|
+
reasoningTokens: row?.reasoningTokens ?? 0,
|
|
164
|
+
avgOutputTokens: row?.avgOutputTokens ?? 0,
|
|
165
|
+
medianOutputTokens: row?.medianOutputTokens ?? 0,
|
|
166
|
+
};
|
|
167
|
+
}),
|
|
168
|
+
}));
|
|
169
|
+
// 每层级模型统计(全量历史口径)+ 趋势(按展示范围)
|
|
170
|
+
const offset = localOffsetMs();
|
|
171
|
+
const byLayerStats = [];
|
|
172
|
+
const trendByLayer = { l1: [], l2: [], l3: [] };
|
|
173
|
+
// 趋势展示范围:rangeDays > 0 = 近 N 天(强制按「日」粒度出 N 个日桶,不受周/月聚合影响);否则用默认窗口
|
|
174
|
+
const trendGranularity = rangeDays > 0 ? 'day' : granularity;
|
|
175
|
+
const trendSince = rangeDays > 0 ? now - rangeDays * 24 * 3600_000 : 0;
|
|
176
|
+
const trendCount = rangeDays > 0 ? rangeDays : TREND_COUNT[granularity];
|
|
177
|
+
for (const layer of LAYERS) {
|
|
178
|
+
const dayRows = d.aggregateByBucket(TREND_MS.day, offset, 0, layer);
|
|
179
|
+
const weekRows = d.aggregateByBucket(TREND_MS.week, offset, 0, layer);
|
|
180
|
+
const monthRows = d.aggregateByBucket(TREND_MS.month, offset, 0, layer);
|
|
181
|
+
byLayerStats.push({ layer, models: buildModelMetrics(dayRows, weekRows, monthRows) });
|
|
182
|
+
trendByLayer[layer] = buildTrend(d.aggregateByBucket(TREND_MS[trendGranularity], offset, trendSince, layer), trendGranularity, now, trendCount);
|
|
183
|
+
}
|
|
184
|
+
return { windows, byModel, byLayer, byLayerStats, trend: { granularity: trendGranularity, byLayer: trendByLayer } };
|
|
185
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型可调用工具:memory_search(L1)、conversation_search(L0)、memory_read_scene(L2/L3)、
|
|
3
|
+
* 高权限 memory_add / memory_delete。
|
|
4
|
+
*
|
|
5
|
+
* 会话档位联动:execute 的 exec.agent 即发起调用的 agent(agent.id === sessionId),
|
|
6
|
+
* memory_search 按会话档位过滤族(auto 不过滤,纯档只查本族);off 档下读类工具统一
|
|
7
|
+
* 返回提示(本会话已对记忆系统隐身)。conversation_search 检索范围保持全库。
|
|
8
|
+
* 只写会话(注入覆盖=关)同款拒读,notice 区分文案——写入走捕获钩子不经工具,
|
|
9
|
+
* 拒读不影响"只写"语义。
|
|
10
|
+
*
|
|
11
|
+
* 写删工具受 live.memoryMutate 高权限门控(默认 false):显式记忆指令语义——
|
|
12
|
+
* 用户说"记得 X" → memory_add;说"忘了/删除该记忆" → memory_delete。
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import type { MemoryConfig } from '../config.js';
|
|
16
|
+
import type { LiveSettingsHandle } from '../settings.js';
|
|
17
|
+
import type { RuminateController } from '../pipeline/ruminate.js';
|
|
18
|
+
import type { GraphStore } from '../store/graph-store.js';
|
|
19
|
+
import type { L0Store } from '../store/l0.js';
|
|
20
|
+
import type { L1Store } from '../store/l1.js';
|
|
21
|
+
import type { PersonaStore } from '../store/persona.js';
|
|
22
|
+
import type { SceneStore } from '../store/scenes.js';
|
|
23
|
+
import type { SessionModeStore } from '../store/session-modes.js';
|
|
24
|
+
import type { MemoryFamily, MemoryLogger } from '../types.js';
|
|
25
|
+
export declare function registerMemoryTools(ctx: Context, cfg: MemoryConfig, stores: {
|
|
26
|
+
l0: L0Store;
|
|
27
|
+
l1: L1Store;
|
|
28
|
+
scenes: Record<MemoryFamily, SceneStore>;
|
|
29
|
+
persona: Record<MemoryFamily, PersonaStore>;
|
|
30
|
+
/** 图谱存储(可选:未装配时图谱工具返回未启用提示)。 */
|
|
31
|
+
graph?: GraphStore;
|
|
32
|
+
}, logger: MemoryLogger, modes: SessionModeStore, live: LiveSettingsHandle,
|
|
33
|
+
/** 反刍控制器(可选:未装配时 ruminate 工具返回未启用提示)。 */
|
|
34
|
+
ruminate?: RuminateController): void;
|