@arcaneorion/dsh-model-channel-manager 0.3.14 → 0.4.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/README.md +72 -317
- package/package.json +18 -4
- package/src/channel-state.js +196 -0
- package/src/client.js +126 -307
- package/src/health-store.js +11 -20
- package/src/index.js +174 -622
package/src/health-store.js
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 动机(审计 F11/F12/F23 的根因):0.1 时代健康流水整字段存在 settings 命名空间里,
|
|
5
|
-
* 与配置共用同一条「HMR 独占事务 + 整字段落盘」通道,导致:
|
|
6
|
-
* - 2s 防抖 flush 与 volatile-update 快照覆盖互相踩(刚记的账被旧快照抹掉);
|
|
7
|
-
* - profile patch 文件膨胀(实测 6350 行中 health 约占 3000 行);
|
|
8
|
-
* - 任何无关 settings 写入都会整段重写健康数据。
|
|
9
|
-
*
|
|
10
|
-
* 现在的归属:事实数据(append-only 事件)进 domain;settings 里只保留
|
|
11
|
-
* 一份给 client 渲染用的小投影(聚合摘要 + 运行态指针),由 host 定期刷新。
|
|
12
|
-
*
|
|
13
|
-
* 组合方式照抄 dsh-session-projection-cache 范本:
|
|
14
|
-
* - 依赖方 inject ['storageDomain'](dsh-base 已装 json 后端,root=storages/);
|
|
15
|
-
* - Service.init 里 open(spec),effect 持有 close;
|
|
16
|
-
* - per-record 布局:一条事件一个文档,坏记录 backup-and-skip 不阻塞整体。
|
|
1
|
+
/** Retained health events and speed results in the existing per-record storage domain.
|
|
2
|
+
* Runtime snapshots and task results live in channel-state.js; neither uses Config.
|
|
17
3
|
*/
|
|
18
4
|
import { z } from 'zod';
|
|
19
5
|
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
|
|
@@ -24,6 +10,7 @@ const PER_KEY_LIMIT = 300;
|
|
|
24
10
|
|
|
25
11
|
/** 单条健康事件记录(zod,domain 落盘边界校验)。向后兼容:token 字段可选。 */
|
|
26
12
|
const eventRecord = z.object({
|
|
13
|
+
id: z.string().optional(),
|
|
27
14
|
ts: z.number().int().nonnegative(),
|
|
28
15
|
provider: z.string(),
|
|
29
16
|
model: z.string(),
|
|
@@ -73,7 +60,7 @@ const healthDomainSpec = defineDomain({
|
|
|
73
60
|
* 会被当成同一笔合并掉。把判定性字段一起纳入指纹——同毫秒 + 同渠道 + 同结果形态
|
|
74
61
|
* 的两笔在语义上本就不可区分;真正不同的两笔(成功率/延迟/token 不同)不会再被吞。
|
|
75
62
|
*/
|
|
76
|
-
const eventKey = (e) => [
|
|
63
|
+
const eventKey = (e) => e.id || [
|
|
77
64
|
e.ts, e.provider, e.model, e.ok ? 1 : 0, e.code || '',
|
|
78
65
|
e.ttftMs ?? '', e.latencyMs ?? '',
|
|
79
66
|
e.inputTokens ?? '', e.outputTokens ?? '', e.cacheReadTokens ?? '', e.cacheWriteTokens ?? '',
|
|
@@ -132,6 +119,7 @@ export class HealthStore {
|
|
|
132
119
|
appendEvent(provider, rec) {
|
|
133
120
|
if (this.events === null) return Promise.resolve();
|
|
134
121
|
return this.enqueueEvent(async () => {
|
|
122
|
+
rec = eventRecord.parse(rec);
|
|
135
123
|
const cutoff = Date.now() - WINDOW_MS;
|
|
136
124
|
const cur = this.events.get(provider);
|
|
137
125
|
if (cur === undefined) {
|
|
@@ -148,9 +136,10 @@ export class HealthStore {
|
|
|
148
136
|
* 避免与迁移/追加写入交错(快照语义下后写者胜,交错会写出半新半旧的桶)。 */
|
|
149
137
|
async putSpeedRows(groupId, rows) {
|
|
150
138
|
if (this.speed === null) return;
|
|
139
|
+
const record = speedRecord.parse({ rows });
|
|
151
140
|
await this.enqueueEvent(async () => {
|
|
152
141
|
if (this.speed === null) return;
|
|
153
|
-
await this.speed.put(groupId,
|
|
142
|
+
await this.speed.put(groupId, record);
|
|
154
143
|
});
|
|
155
144
|
}
|
|
156
145
|
|
|
@@ -185,7 +174,8 @@ export class HealthStore {
|
|
|
185
174
|
const cutoff = Date.now() - WINDOW_MS;
|
|
186
175
|
for (const [provider, list] of Object.entries(records || {})) {
|
|
187
176
|
const incoming = (Array.isArray(list) ? list : [])
|
|
188
|
-
.filter((e) => e && Number.isFinite(e.ts) && e.ts >= cutoff)
|
|
177
|
+
.filter((e) => e && Number.isFinite(e.ts) && e.ts >= cutoff)
|
|
178
|
+
.map(e => eventRecord.parse(e));
|
|
189
179
|
if (incoming.length === 0) continue;
|
|
190
180
|
await this.enqueueEvent(async () => {
|
|
191
181
|
const existing = (this.events.get(provider) || {}).events || [];
|
|
@@ -203,10 +193,11 @@ export class HealthStore {
|
|
|
203
193
|
}
|
|
204
194
|
for (const [groupId, rows] of Object.entries(speedResults || {})) {
|
|
205
195
|
if (!Array.isArray(rows) || rows.length === 0) continue;
|
|
196
|
+
const record = speedRecord.parse({ rows });
|
|
206
197
|
await this.enqueueEvent(async () => {
|
|
207
198
|
if (this.speed === null) return;
|
|
208
199
|
if (this.speed.get(groupId) !== undefined) return; // domain 已有该组结果:以 domain 为准
|
|
209
|
-
await this.speed.put(groupId,
|
|
200
|
+
await this.speed.put(groupId, record);
|
|
210
201
|
});
|
|
211
202
|
}
|
|
212
203
|
return true;
|