dsh-context 0.37.0 → 0.38.1
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 +69 -69
- package/lib/client.js +587 -49
- package/lib/index.d.ts +65 -5
- package/lib/index.js +119 -7
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -85,12 +85,35 @@ interface TimelineState {
|
|
|
85
85
|
cost?: SessionCostUsage;
|
|
86
86
|
archiveFloor?: number;
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
88
|
+
* Whole-session timing totals (see TimingTotals) — running sums over the
|
|
89
|
+
* COMPLETE session log, like `cost`. Absent until the first step or tool
|
|
90
|
+
* lifecycle folds in; created once and cloned-on-touch afterwards (the
|
|
91
|
+
* object is shared with the persisted previous state — see `ensure`).
|
|
92
92
|
*/
|
|
93
|
-
|
|
93
|
+
timing?: TimingTotals;
|
|
94
|
+
/**
|
|
95
|
+
* The open step's start instant, armed by `step/start` and consumed by the
|
|
96
|
+
* `assistant/message` (LM-call time) and `step/end` (wall time) that follow
|
|
97
|
+
* it. One slot, not a map: steps are sequential in the log, so the newest
|
|
98
|
+
* `step/start` is the one those events close — a hostile interleaved log
|
|
99
|
+
* degrades to skipped durations, never to unbounded state. Same
|
|
100
|
+
* arm/remove lifecycle as `pendingShadowedSeqs`.
|
|
101
|
+
*/
|
|
102
|
+
stepStart?: {
|
|
103
|
+
time: number;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Tool callId → the call's name and start instant, armed by `tool/call` and
|
|
107
|
+
* DELETED when its `tool/result` folds in (one result per call, in log
|
|
108
|
+
* order) — the map stays at pending-call size instead of growing for the
|
|
109
|
+
* session's whole lifetime (it is persisted state, shallow-copied by every
|
|
110
|
+
* fold step). The start instant prices the call's duration into
|
|
111
|
+
* `timing.toolsMs` when the result arrives.
|
|
112
|
+
*/
|
|
113
|
+
callNames: Record<string, {
|
|
114
|
+
name: string;
|
|
115
|
+
start: number;
|
|
116
|
+
}>;
|
|
94
117
|
/**
|
|
95
118
|
* Seq list of the surface nodes the next replacement will shadow, armed by
|
|
96
119
|
* the metering event (`compaction/summary` | `compaction/prune`) and
|
|
@@ -187,6 +210,12 @@ interface Snapshot {
|
|
|
187
210
|
* request reports usage.
|
|
188
211
|
*/
|
|
189
212
|
cost?: SessionCostUsage;
|
|
213
|
+
/**
|
|
214
|
+
* Whole-session timing totals (see TimingTotals). Absent until the first
|
|
215
|
+
* step lifecycle completes in the log (older plugin builds never folded
|
|
216
|
+
* one — clients treat absence as an empty timing card).
|
|
217
|
+
*/
|
|
218
|
+
timing?: TimingTotals;
|
|
190
219
|
/**
|
|
191
220
|
* The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
|
|
192
221
|
* few,
|
|
@@ -230,6 +259,37 @@ interface CostBucketTotals {
|
|
|
230
259
|
cacheWrite: number;
|
|
231
260
|
output: number;
|
|
232
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* One completed tool name's whole-session call tally behind the timing
|
|
264
|
+
* card's top-tools ranking (running totals, never trimmed).
|
|
265
|
+
*/
|
|
266
|
+
interface ToolTimingTotals {
|
|
267
|
+
calls: number;
|
|
268
|
+
ms: number;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Whole-session timing totals, host-folded from the durable `step/start` /
|
|
272
|
+
`step/end` / `tool/call` / `tool/result` lifecycle (running totals over the
|
|
273
|
+
* COMPLETE session log — the same never-trimmed framing as `cost`). Durations
|
|
274
|
+
* are wall-clock milliseconds: `wallMs` sums whole steps, `lmMs` the
|
|
275
|
+
* step-start → assistant-message slice (the model call), `toolsMs` the sum of
|
|
276
|
+
* per-call tool durations (parallel calls each count, so it can overlap).
|
|
277
|
+
* Absent until the first step lifecycle completes in the log.
|
|
278
|
+
*/
|
|
279
|
+
interface TimingTotals {
|
|
280
|
+
/** Summed wall time of completed steps (the session's active time). */
|
|
281
|
+
wallMs: number;
|
|
282
|
+
/** Summed step-start → assistant-message time (model-call time). */
|
|
283
|
+
lmMs: number;
|
|
284
|
+
/** Completed model calls (assistant messages folded). */
|
|
285
|
+
calls: number;
|
|
286
|
+
/** Summed per-call durations of completed tool calls. */
|
|
287
|
+
toolsMs: number;
|
|
288
|
+
/** Completed tool calls (call/result pairs folded). */
|
|
289
|
+
toolCalls: number;
|
|
290
|
+
/** Per-tool-name tallies behind the timing card's ranking (bounded). */
|
|
291
|
+
tools: Record<string, ToolTimingTotals>;
|
|
292
|
+
}
|
|
233
293
|
/** One model family's totals split by DeepSeek's pricing period (Beijing Time). */
|
|
234
294
|
interface CostFamilyUsage {
|
|
235
295
|
peak?: CostBucketTotals;
|
package/lib/index.js
CHANGED
|
@@ -786,10 +786,18 @@ function applySurface(st, ev, type, data, message) {
|
|
|
786
786
|
}
|
|
787
787
|
} else if (type === "tool/result") {
|
|
788
788
|
const srcId = source?.callId;
|
|
789
|
-
const srcName = typeof srcId === "string" ? st.callNames[srcId] : void 0;
|
|
790
789
|
const blockId = (message?.content?.[0])?.toolCallId;
|
|
791
|
-
|
|
792
|
-
|
|
790
|
+
const srcEntry = typeof srcId === "string" ? st.callNames[srcId] : void 0;
|
|
791
|
+
const blockEntry = srcEntry === void 0 && typeof blockId === "string" ? st.callNames[blockId] : void 0;
|
|
792
|
+
const toolEntry = srcEntry ?? blockEntry;
|
|
793
|
+
if (toolEntry !== void 0) {
|
|
794
|
+
node.tool = toolEntry.name;
|
|
795
|
+
const timing = ensureTiming(st);
|
|
796
|
+
const dur = durOf(toolEntry.start, ev.time);
|
|
797
|
+
timing.toolsMs += dur;
|
|
798
|
+
timing.toolCalls += 1;
|
|
799
|
+
bumpToolTotals(timing, toolEntry.name, dur);
|
|
800
|
+
}
|
|
793
801
|
if (typeof srcId === "string" || typeof blockId === "string") {
|
|
794
802
|
const kept = {};
|
|
795
803
|
for (const k in st.callNames) if (k !== srcId && k !== blockId) kept[k] = st.callNames[k];
|
|
@@ -922,6 +930,60 @@ function accumulateCost(st, time, usage) {
|
|
|
922
930
|
* `bounds` come from the plugin config (config.ts) — retention only, they
|
|
923
931
|
* never change the state shape.
|
|
924
932
|
*/
|
|
933
|
+
/** The timing card's per-tool ranking cap: the busiest 16 names are kept. */
|
|
934
|
+
const TOOL_TIMING_CAP = 16;
|
|
935
|
+
/** Non-negative, NaN-proof duration between two instants (hostile times degrade to 0). */
|
|
936
|
+
function durOf(from, to) {
|
|
937
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
|
|
938
|
+
return Math.max(0, to - from);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* The fold's private timing accumulator: created on first use, and CLONED on
|
|
942
|
+
* every later ensure() (see `applyTimeline`) — the object left in the
|
|
943
|
+
* persisted previous state is never written into in place.
|
|
944
|
+
*/
|
|
945
|
+
function ensureTiming(st) {
|
|
946
|
+
if (st.timing === void 0) st.timing = {
|
|
947
|
+
wallMs: 0,
|
|
948
|
+
lmMs: 0,
|
|
949
|
+
calls: 0,
|
|
950
|
+
toolsMs: 0,
|
|
951
|
+
toolCalls: 0,
|
|
952
|
+
tools: {}
|
|
953
|
+
};
|
|
954
|
+
return st.timing;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Tally one completed tool call into the per-name ranking, bounded to
|
|
958
|
+
* TOOL_TIMING_CAP names: repeated names update in place, a new name beyond
|
|
959
|
+
* the cap evicts the smallest tally first (the ranking's tail), so state
|
|
960
|
+
* stays bounded even over a hostile log of unique names.
|
|
961
|
+
*/
|
|
962
|
+
function bumpToolTotals(timing, name, ms) {
|
|
963
|
+
if (!Object.hasOwn(timing.tools, name)) {
|
|
964
|
+
if (Object.keys(timing.tools).length >= TOOL_TIMING_CAP) {
|
|
965
|
+
let minKey = "";
|
|
966
|
+
let minMs = Infinity;
|
|
967
|
+
for (const k in timing.tools) if (timing.tools[k].ms < minMs) {
|
|
968
|
+
minMs = timing.tools[k].ms;
|
|
969
|
+
minKey = k;
|
|
970
|
+
}
|
|
971
|
+
const kept = {};
|
|
972
|
+
for (const k in timing.tools) if (k !== minKey) kept[k] = timing.tools[k];
|
|
973
|
+
timing.tools = kept;
|
|
974
|
+
}
|
|
975
|
+
timing.tools[name] = {
|
|
976
|
+
calls: 1,
|
|
977
|
+
ms
|
|
978
|
+
};
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const cur = timing.tools[name];
|
|
982
|
+
timing.tools[name] = {
|
|
983
|
+
calls: cur.calls + 1,
|
|
984
|
+
ms: cur.ms + ms
|
|
985
|
+
};
|
|
986
|
+
}
|
|
925
987
|
function applyTimeline(state, event, bounds) {
|
|
926
988
|
let st;
|
|
927
989
|
const ensure = () => st ??= {
|
|
@@ -931,7 +993,11 @@ function applyTimeline(state, event, bounds) {
|
|
|
931
993
|
requests: [...state.requests],
|
|
932
994
|
events: [...state.events],
|
|
933
995
|
archived: [...state.archived],
|
|
934
|
-
callNames: { ...state.callNames }
|
|
996
|
+
callNames: { ...state.callNames },
|
|
997
|
+
...state.timing !== void 0 ? { timing: {
|
|
998
|
+
...state.timing,
|
|
999
|
+
tools: { ...state.timing.tools }
|
|
1000
|
+
} } : {}
|
|
935
1001
|
};
|
|
936
1002
|
const data = event.data;
|
|
937
1003
|
try {
|
|
@@ -964,9 +1030,25 @@ function applyTimeline(state, event, bounds) {
|
|
|
964
1030
|
case "tool/call":
|
|
965
1031
|
if (data && typeof data.callId === "string" && typeof data.name === "string") {
|
|
966
1032
|
const s = ensure();
|
|
967
|
-
s.callNames[data.callId] =
|
|
1033
|
+
s.callNames[data.callId] = {
|
|
1034
|
+
name: data.name,
|
|
1035
|
+
start: event.time
|
|
1036
|
+
};
|
|
968
1037
|
}
|
|
969
1038
|
break;
|
|
1039
|
+
case "step/start": {
|
|
1040
|
+
const s = ensure();
|
|
1041
|
+
s.stepStart = { time: event.time };
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
case "step/end": {
|
|
1045
|
+
const start = state.stepStart;
|
|
1046
|
+
if (start === void 0) return state;
|
|
1047
|
+
const s = ensure();
|
|
1048
|
+
ensureTiming(s).wallMs += durOf(start.time, event.time);
|
|
1049
|
+
delete s.stepStart;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
970
1052
|
case "user/message": {
|
|
971
1053
|
const msg = deriveEventMessage(event);
|
|
972
1054
|
const s = ensure();
|
|
@@ -1037,6 +1119,10 @@ function applyTimeline(state, event, bounds) {
|
|
|
1037
1119
|
accumulateCost(s, event.time, usage);
|
|
1038
1120
|
}
|
|
1039
1121
|
s.requests.push(record);
|
|
1122
|
+
const timing = ensureTiming(s);
|
|
1123
|
+
timing.calls += 1;
|
|
1124
|
+
const stepStart = state.stepStart;
|
|
1125
|
+
if (stepStart !== void 0) timing.lmMs += durOf(stepStart.time, event.time);
|
|
1040
1126
|
const asstMsg = deriveEventMessage(event);
|
|
1041
1127
|
applySurface(s, event, event.type, data, asstMsg);
|
|
1042
1128
|
break;
|
|
@@ -1120,6 +1206,14 @@ function buildTimelineView(state, bounds) {
|
|
|
1120
1206
|
if (pro !== void 0) cost.pro = pro;
|
|
1121
1207
|
result.cost = cost;
|
|
1122
1208
|
}
|
|
1209
|
+
if (state.timing !== void 0) {
|
|
1210
|
+
const tools = {};
|
|
1211
|
+
for (const k in state.timing.tools) tools[k] = { ...state.timing.tools[k] };
|
|
1212
|
+
result.timing = {
|
|
1213
|
+
...state.timing,
|
|
1214
|
+
tools
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1123
1217
|
const overflowCount = Math.max(0, state.surface.length - bounds.maxNodes);
|
|
1124
1218
|
const overflow = state.surface.slice(0, overflowCount);
|
|
1125
1219
|
const tail = state.surface.slice(overflowCount);
|
|
@@ -1249,6 +1343,18 @@ const costFamilySchema = z.object({
|
|
|
1249
1343
|
peak: costBucketsSchema.optional(),
|
|
1250
1344
|
off: costBucketsSchema.optional()
|
|
1251
1345
|
}).strict();
|
|
1346
|
+
const toolTimingSchema = z.object({
|
|
1347
|
+
calls: z.number().int().nonnegative(),
|
|
1348
|
+
ms: z.number().nonnegative()
|
|
1349
|
+
}).strict();
|
|
1350
|
+
const timingTotalsSchema = z.object({
|
|
1351
|
+
wallMs: z.number().nonnegative(),
|
|
1352
|
+
lmMs: z.number().nonnegative(),
|
|
1353
|
+
calls: z.number().int().nonnegative(),
|
|
1354
|
+
toolsMs: z.number().nonnegative(),
|
|
1355
|
+
toolCalls: z.number().int().nonnegative(),
|
|
1356
|
+
tools: z.record(z.string(), toolTimingSchema)
|
|
1357
|
+
}).strict();
|
|
1252
1358
|
const contextTimelineSchema = z.object({
|
|
1253
1359
|
ok: z.literal(true),
|
|
1254
1360
|
model: z.string().optional(),
|
|
@@ -1263,6 +1369,7 @@ const contextTimelineSchema = z.object({
|
|
|
1263
1369
|
flash: costFamilySchema.optional(),
|
|
1264
1370
|
pro: costFamilySchema.optional()
|
|
1265
1371
|
}).strict().optional(),
|
|
1372
|
+
timing: timingTotalsSchema.optional(),
|
|
1266
1373
|
nodes: z.array(surfaceNodeSchema),
|
|
1267
1374
|
droppedNodes: z.number().int().nonnegative(),
|
|
1268
1375
|
archive: z.array(surfaceNodeSchema),
|
|
@@ -1297,7 +1404,12 @@ const timelineStateSchema = z.object({
|
|
|
1297
1404
|
pro: costFamilySchema.optional()
|
|
1298
1405
|
}).strict().optional(),
|
|
1299
1406
|
archiveFloor: z.number().optional(),
|
|
1300
|
-
|
|
1407
|
+
timing: timingTotalsSchema.optional(),
|
|
1408
|
+
stepStart: z.object({ time: z.number() }).strict().optional(),
|
|
1409
|
+
callNames: z.record(z.string(), z.object({
|
|
1410
|
+
name: z.string(),
|
|
1411
|
+
start: z.number()
|
|
1412
|
+
}).strict()),
|
|
1301
1413
|
pendingShadowedSeqs: z.array(z.number()).optional(),
|
|
1302
1414
|
pendingShadowEventSeq: z.number().optional()
|
|
1303
1415
|
});
|
|
@@ -1335,7 +1447,7 @@ function createContextTimelineDefinition(config) {
|
|
|
1335
1447
|
},
|
|
1336
1448
|
init: () => createTimelineState(),
|
|
1337
1449
|
apply: (state, event) => applyTimeline(state, event, bounds),
|
|
1338
|
-
stateVersion:
|
|
1450
|
+
stateVersion: 11
|
|
1339
1451
|
};
|
|
1340
1452
|
}
|
|
1341
1453
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"build": "tsdown",
|
|
35
35
|
"lint": "oxlint",
|
|
36
36
|
"lint:fix": "oxlint --fix",
|
|
37
|
-
"watch": "
|
|
37
|
+
"watch": "bash scripts/watch.sh",
|
|
38
38
|
"register": "bash scripts/register.sh",
|
|
39
39
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
|
|
40
40
|
"test": "pnpm run typecheck && vitest run --coverage",
|