dsh-context 0.46.0 → 0.47.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 +5 -4
- package/lib/client.js +490 -285
- package/lib/index.d.ts +48 -3
- package/lib/index.js +243 -32
- package/package.json +6 -3
package/lib/index.d.ts
CHANGED
|
@@ -72,6 +72,27 @@ interface TimelineState {
|
|
|
72
72
|
surface: SurfaceNode[];
|
|
73
73
|
sums: Record<Category, number>;
|
|
74
74
|
systemTokens: number;
|
|
75
|
+
/**
|
|
76
|
+
* The live system-prompt nodes, oldest first — a V3 log's `system/message`
|
|
77
|
+
* surface nodes, or the single entry a V0/V2 `request/header.header.system`
|
|
78
|
+
* envelope defines. `systemTokens` is the LAST entry with tokens > 0 (the
|
|
79
|
+
* harness's own "last nonempty surviving system" rule), so an empty dormant
|
|
80
|
+
* node keeps its position without clearing the prompt. Bounded by
|
|
81
|
+
* SYSTEM_NODES_MAX. ABSENT on rows folded before this field existed — the
|
|
82
|
+
* wire then serves no `systems` and the client falls back to the header
|
|
83
|
+
* epoch's own envelope figure.
|
|
84
|
+
*/
|
|
85
|
+
systems?: SystemPromptNode[];
|
|
86
|
+
/**
|
|
87
|
+
* Whether `systems` was built from the V0/V2 request ENVELOPE
|
|
88
|
+
* (`header.system`) rather than from V3 `system/message` events. Only then
|
|
89
|
+
* may a system-less header CLEAR the list: its canonical V0 meaning is
|
|
90
|
+
* "this request has no system prompt", while a V3 header never carries one
|
|
91
|
+
* (its prompt lives in the message history). Absent = log-sourced, and
|
|
92
|
+
* never materialized as an `undefined`-valued property (plain-JSON
|
|
93
|
+
* precondition — see the note above `model`).
|
|
94
|
+
*/
|
|
95
|
+
systemsFromHeader?: true;
|
|
75
96
|
toolsTokens: number;
|
|
76
97
|
/**
|
|
77
98
|
* The projection-cache precondition is plain JSON: a property whose value
|
|
@@ -221,6 +242,19 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
|
221
242
|
}
|
|
222
243
|
}
|
|
223
244
|
type Category = 'user' | 'inject' | 'assistant' | 'tool';
|
|
245
|
+
/**
|
|
246
|
+
* One live system-prompt node (Snapshot.systems) — the harness models the
|
|
247
|
+
* system prompt as a surface node, so its TEXT is fetched on demand from the
|
|
248
|
+
* event at `seq`: a V3 `system/message` event, or the V0/V2 `request/header`
|
|
249
|
+
* whose envelope carried `header.system`. `tokens` is the node's heuristic
|
|
250
|
+
* price (0 for a dormant empty node, which the harness reads as "no system
|
|
251
|
+
* prompt"); the effective figure is the LAST node with `tokens > 0`.
|
|
252
|
+
*/
|
|
253
|
+
interface SystemPromptNode {
|
|
254
|
+
seq: number;
|
|
255
|
+
time: number;
|
|
256
|
+
tokens: number;
|
|
257
|
+
}
|
|
224
258
|
/**
|
|
225
259
|
* The stats board's count figures, precomputed host-side over the RETAINED
|
|
226
260
|
* request/event records (the same set the detail payload serves). Carried by
|
|
@@ -316,6 +350,13 @@ interface Snapshot {
|
|
|
316
350
|
* one — clients treat absence as an empty timing card).
|
|
317
351
|
*/
|
|
318
352
|
timing?: TimingTotals;
|
|
353
|
+
/**
|
|
354
|
+
* The live system-prompt nodes, oldest first — the browser's per-step source
|
|
355
|
+
* for the System section. Absent when the log carried no system prompt, and
|
|
356
|
+
* on older plugin builds (the client then falls back to the header epoch's
|
|
357
|
+
* own `systemTokens`, the pre-V3 shape).
|
|
358
|
+
*/
|
|
359
|
+
systems?: SystemPromptNode[];
|
|
319
360
|
/**
|
|
320
361
|
* The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
|
|
321
362
|
* few,
|
|
@@ -450,9 +491,13 @@ interface ToolTimingTotals {
|
|
|
450
491
|
}
|
|
451
492
|
/**
|
|
452
493
|
* Whole-session timing totals, host-folded from the durable `step/start` /
|
|
453
|
-
`step/end` / `
|
|
454
|
-
* (running totals over the COMPLETE session log — the same
|
|
455
|
-
* framing as `cost`).
|
|
494
|
+
* `step/end` / `tool/call` / `tool/result` lifecycle plus the model call's
|
|
495
|
+
* first token (running totals over the COMPLETE session log — the same
|
|
496
|
+
* never-trimmed framing as `cost`). The first token comes from a V0
|
|
497
|
+
* `assistant/chunk` delta or from the call's own embedded stream
|
|
498
|
+
* (`assistant/message.data.stream` / `assistant/attempt.data.stream`, the
|
|
499
|
+
* V2+ settlement) — whichever the log carries, matching the harness's own
|
|
500
|
+
* session-stats fold. Durations are wall-clock milliseconds: `wallMs` sums
|
|
456
501
|
* whole steps, `ttftMs` the step-start → first-token slice (the model wait)
|
|
457
502
|
* and `genMs` the first-token → assistant-message slice (the generation) —
|
|
458
503
|
* both only over calls whose stream carried a token delta, `toolsMs` the sum
|
package/lib/index.js
CHANGED
|
@@ -308,6 +308,30 @@ function estimateSystemTokens(text) {
|
|
|
308
308
|
if (typeof text !== "string" || text.length === 0) return 0;
|
|
309
309
|
return Math.ceil(text.length / CHARS_PER_TOKEN$1) + ROLE_OVERHEAD$1;
|
|
310
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Price a `system/message` payload's content exactly like the harness's
|
|
313
|
+
* token-meter (`estimateSystemMessage`): text density over EVERY text block
|
|
314
|
+
* plus role framing, with no per-block overhead — an adapter serializes the
|
|
315
|
+
* prompt as plain text, so a text block costs its characters alone. Any other
|
|
316
|
+
* block (or a hostile element) falls back to its JSON length. 0 for empty
|
|
317
|
+
* content, which the harness reads as "no system prompt".
|
|
318
|
+
*/
|
|
319
|
+
function estimateSystemContent(blocks) {
|
|
320
|
+
if (!Array.isArray(blocks) || blocks.length === 0) return 0;
|
|
321
|
+
let characters = 0;
|
|
322
|
+
for (const block of blocks) {
|
|
323
|
+
const text = block !== null && typeof block === "object" && block.type === "text" ? block.text : void 0;
|
|
324
|
+
if (typeof text === "string") {
|
|
325
|
+
characters += text.length;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const json = JSON.stringify(block);
|
|
330
|
+
if (typeof json === "string") characters += json.length;
|
|
331
|
+
} catch {}
|
|
332
|
+
}
|
|
333
|
+
return Math.ceil(characters / CHARS_PER_TOKEN$1) + ROLE_OVERHEAD$1;
|
|
334
|
+
}
|
|
311
335
|
//#endregion
|
|
312
336
|
//#region src/shared/imageTokens.ts
|
|
313
337
|
/**
|
|
@@ -565,6 +589,111 @@ function isInjection(source) {
|
|
|
565
589
|
return source !== null && source !== void 0 && (typeof source.kind === "string" && source.kind !== "" && source.kind !== "user" || typeof source.form === "string");
|
|
566
590
|
}
|
|
567
591
|
//#endregion
|
|
592
|
+
//#region src/host/logShapes.ts
|
|
593
|
+
/**
|
|
594
|
+
* Shape-driven readers over the durable session-event vocabulary — the ONE
|
|
595
|
+
* place the plugin reconciles the two supported log generations:
|
|
596
|
+
*
|
|
597
|
+
* - V0 (dsh 0.1.2-rc.1): `request/header.header.system`, `assistant/chunk`
|
|
598
|
+
* stream events, `SurfaceOp { start, end }`, `tool/code-dispatch`.
|
|
599
|
+
* - V3 (dsh 0.1.5-alpha.1+): `system/message` surface nodes,
|
|
600
|
+
* `assistant/message.data.stream` / `assistant/attempt.data.stream`,
|
|
601
|
+
* `SurfaceOp { startSeq, endSeq }`, `tool/ptc-dispatch`.
|
|
602
|
+
*
|
|
603
|
+
* The fold reads SHAPES, never a detected harness version: a session log is
|
|
604
|
+
* written by exactly one generation, the two spellings are mutually
|
|
605
|
+
* exclusive within it, and a deployment's version probe can be wrong (a
|
|
606
|
+
* healed profile mirror may name a different release than the running
|
|
607
|
+
* harness). Every reader is total over untrusted input — a malformed record
|
|
608
|
+
* yields "nothing here", never a throw (the projection registry drives the
|
|
609
|
+
* fold without an error boundary; one throw stalls the unit's push feed and
|
|
610
|
+
* the browser waits on "loading" forever).
|
|
611
|
+
*
|
|
612
|
+
* @module dsh-context/host/log-shapes
|
|
613
|
+
*/
|
|
614
|
+
/**
|
|
615
|
+
* Whether one raw stream chunk carries a token delta — the first-token marker
|
|
616
|
+
* both generations share. Mirrors dsh-llm's `isTokenDelta` (a non-empty text
|
|
617
|
+
* or reasoning fragment, or any Tool-call delta carrying arguments or a name);
|
|
618
|
+
* a malformed chunk is simply not a token.
|
|
619
|
+
*/
|
|
620
|
+
function isTokenChunk(chunk) {
|
|
621
|
+
if (chunk === null || typeof chunk !== "object") return false;
|
|
622
|
+
const c = chunk;
|
|
623
|
+
switch (c.type) {
|
|
624
|
+
case "text-delta":
|
|
625
|
+
case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
|
|
626
|
+
case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
|
|
627
|
+
default: return false;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* The first token's instant inside one PACKED delta run (`text-chunks` /
|
|
632
|
+
* `reasoning-chunks` / `tool-call-chunks`): the run's base time plus the
|
|
633
|
+
* accumulated inter-member deltas, taken at the first qualifying member —
|
|
634
|
+
* a name-bearing Tool-call run starts at its first member. Mirrors dsh-llm's
|
|
635
|
+
* `runFirstTokenTime`; a non-finite base or delta yields undefined rather
|
|
636
|
+
* than a NaN instant.
|
|
637
|
+
*/
|
|
638
|
+
function runFirstTokenTime(record) {
|
|
639
|
+
const time0 = record.time0;
|
|
640
|
+
if (typeof time0 !== "number" || !Number.isFinite(time0)) return void 0;
|
|
641
|
+
if (record.type === "tool-call-chunks" && record.name !== void 0) return time0;
|
|
642
|
+
const fragments = record.type === "tool-call-chunks" ? record.args : record.texts;
|
|
643
|
+
if (!Array.isArray(fragments)) return void 0;
|
|
644
|
+
const dt = Array.isArray(record.dt) ? record.dt : [];
|
|
645
|
+
let time = time0;
|
|
646
|
+
for (const [index, fragment] of fragments.entries()) {
|
|
647
|
+
if (index > 0) {
|
|
648
|
+
const step = dt[index - 1];
|
|
649
|
+
if (typeof step !== "number" || !Number.isFinite(step)) return void 0;
|
|
650
|
+
time += step;
|
|
651
|
+
}
|
|
652
|
+
if (typeof fragment === "string" && fragment !== "") return time;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* The first token's instant inside an embedded assistant stream
|
|
657
|
+
* (`assistant/message.data.stream`, `assistant/attempt.data.stream` — the V2+
|
|
658
|
+
* settlement that replaced the V0 `assistant/chunk` events), or undefined
|
|
659
|
+
* when the stream carries no token. Mirrors dsh-llm's
|
|
660
|
+
* `assistantStreamFirstTokenTime` over the compact record union.
|
|
661
|
+
*/
|
|
662
|
+
function firstTokenTimeOfStream(stream) {
|
|
663
|
+
if (!Array.isArray(stream)) return void 0;
|
|
664
|
+
for (const record of stream) {
|
|
665
|
+
if (record === null || typeof record !== "object") continue;
|
|
666
|
+
const r = record;
|
|
667
|
+
if (r.type === "chunk") {
|
|
668
|
+
const time = r.time;
|
|
669
|
+
if (typeof time === "number" && Number.isFinite(time) && isTokenChunk(r.chunk)) return time;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const time = runFirstTokenTime(r);
|
|
673
|
+
if (time !== void 0) return time;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* The inclusive surface range a replacement op covers, or null for `append`
|
|
678
|
+
* and for any unrecognized/hostile op (which the fold treats as an append).
|
|
679
|
+
* Reads BOTH endpoint spellings: V3's `startSeq`/`endSeq` first, then V0's
|
|
680
|
+
* `start`/`end` — each accepted only as a finite number, so a hostile op
|
|
681
|
+
* with one good and one malformed endpoint degrades to append.
|
|
682
|
+
*/
|
|
683
|
+
function replaceRangeOf(surfaceOp) {
|
|
684
|
+
if (surfaceOp === null || typeof surfaceOp !== "object") return null;
|
|
685
|
+
const op = surfaceOp;
|
|
686
|
+
if (op.op !== "replace") return null;
|
|
687
|
+
const start = typeof op.startSeq === "number" ? op.startSeq : op.start;
|
|
688
|
+
const end = typeof op.endSeq === "number" ? op.endSeq : op.end;
|
|
689
|
+
if (typeof start !== "number" || !Number.isFinite(start)) return null;
|
|
690
|
+
if (typeof end !== "number" || !Number.isFinite(end)) return null;
|
|
691
|
+
return {
|
|
692
|
+
start,
|
|
693
|
+
end
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
//#endregion
|
|
568
697
|
//#region src/shared/fileOps.ts
|
|
569
698
|
/** Parse a call's raw JSON arguments; non-string/malformed/non-record inputs yield null. */
|
|
570
699
|
function parseCallArgs(raw) {
|
|
@@ -873,6 +1002,24 @@ function bumpDetailRev(st) {
|
|
|
873
1002
|
st.detailRev = (st.detailRev ?? 0) + 1;
|
|
874
1003
|
}
|
|
875
1004
|
/**
|
|
1005
|
+
* Bound on the live system-prompt nodes (TimelineState.systems). The
|
|
1006
|
+
* effective figure is the LAST nonempty node, so dropping the oldest can only
|
|
1007
|
+
* under-report a pathological log whose newest SYSTEM_NODES_MAX nodes are all
|
|
1008
|
+
* empty while an older one still carried text.
|
|
1009
|
+
*/
|
|
1010
|
+
const SYSTEM_NODES_MAX = 8;
|
|
1011
|
+
/** The effective system-prompt price: the last nonempty node, else 0 (the harness's own rule). */
|
|
1012
|
+
function systemTokensOf(systems) {
|
|
1013
|
+
for (let i = systems.length - 1; i >= 0; i--) if (systems[i].tokens > 0) return systems[i].tokens;
|
|
1014
|
+
return 0;
|
|
1015
|
+
}
|
|
1016
|
+
/** Append one system-prompt node, bounding the list (see SYSTEM_NODES_MAX). */
|
|
1017
|
+
function pushSystem(st, node) {
|
|
1018
|
+
const systems = [...st.systems ?? [], node];
|
|
1019
|
+
st.systems = systems.length > SYSTEM_NODES_MAX ? systems.slice(-8) : systems;
|
|
1020
|
+
st.systemTokens = systemTokensOf(st.systems);
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
876
1023
|
* Bound on the buffered nested Code-Mode ops (TimelineState.pendingCodeOps)
|
|
877
1024
|
* — a hostile log that dispatches without settling the parent run_code
|
|
878
1025
|
* cannot grow the persisted state past this.
|
|
@@ -919,6 +1066,35 @@ function archiveRemoved(st, removed, goneSeq) {
|
|
|
919
1066
|
});
|
|
920
1067
|
}
|
|
921
1068
|
/**
|
|
1069
|
+
* Remove every live surface node whose seq the replacement claims, keeping the
|
|
1070
|
+
* per-category sums equal to the surviving nodes and archiving the removals.
|
|
1071
|
+
* Removal follows the SEQ list, not the declared range: pruned replacement
|
|
1072
|
+
* nodes keep their own seqs beyond the range end, so a range-based removal
|
|
1073
|
+
* would leave them behind and overcount. Returns the removed nodes.
|
|
1074
|
+
*/
|
|
1075
|
+
function removeSurfaceSeqs(st, claimed, goneSeq) {
|
|
1076
|
+
if (claimed.size === 0) return [];
|
|
1077
|
+
const kept = [];
|
|
1078
|
+
const removed = [];
|
|
1079
|
+
for (const n of st.surface) if (claimed.has(n.seq)) {
|
|
1080
|
+
st.sums[n.cat] -= n.tokens;
|
|
1081
|
+
removed.push(n);
|
|
1082
|
+
} else kept.push(n);
|
|
1083
|
+
archiveRemoved(st, removed, goneSeq);
|
|
1084
|
+
st.surface = kept;
|
|
1085
|
+
return removed;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* The message nested under an event payload's `message` field
|
|
1089
|
+
* (`system/message`, `assistant/message`, `tool/result`) — read structurally
|
|
1090
|
+
* rather than through `deriveEventMessage`, whose 0.1.2-rc.1 generation knows
|
|
1091
|
+
* nothing of the V3 `system/message` variant. A malformed payload reads null.
|
|
1092
|
+
*/
|
|
1093
|
+
function messageOf(data) {
|
|
1094
|
+
const message = data?.message;
|
|
1095
|
+
return message !== null && typeof message === "object" ? message : null;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
922
1098
|
* The first full text block, recursing through nested content blocks (a tool
|
|
923
1099
|
* result wraps its text in a `tool-result` block). Unlike `firstText` this
|
|
924
1100
|
* must NOT truncate/normalize: the skill name is matched off the raw
|
|
@@ -1002,18 +1178,10 @@ function applySurface(st, ev, type, data, message) {
|
|
|
1002
1178
|
const shadowEventSeq = st.pendingShadowEventSeq;
|
|
1003
1179
|
delete st.pendingShadowedSeqs;
|
|
1004
1180
|
delete st.pendingShadowEventSeq;
|
|
1005
|
-
const op = ev.surfaceOp;
|
|
1006
|
-
if (op !== null
|
|
1181
|
+
const op = replaceRangeOf(ev.surfaceOp);
|
|
1182
|
+
if (op !== null) {
|
|
1007
1183
|
if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
|
|
1008
|
-
const
|
|
1009
|
-
const kept = [];
|
|
1010
|
-
const removed = [];
|
|
1011
|
-
for (const n of st.surface) if (shadowed.has(n.seq)) {
|
|
1012
|
-
st.sums[n.cat] -= n.tokens;
|
|
1013
|
-
removed.push(n);
|
|
1014
|
-
} else kept.push(n);
|
|
1015
|
-
archiveRemoved(st, removed, ev.seq);
|
|
1016
|
-
st.surface = kept;
|
|
1184
|
+
const removed = removeSurfaceSeqs(st, new Set(shadowedSeqs), ev.seq);
|
|
1017
1185
|
st.sums[cat] += node.tokens;
|
|
1018
1186
|
st.surface.push(node);
|
|
1019
1187
|
if (shadowEventSeq !== void 0) {
|
|
@@ -1138,21 +1306,6 @@ function durOf(from, to) {
|
|
|
1138
1306
|
return Math.max(0, to - from);
|
|
1139
1307
|
}
|
|
1140
1308
|
/**
|
|
1141
|
-
* Whether a stream chunk carries a non-empty token delta — the first-token
|
|
1142
|
-
* marker the TTFT fold waits for (the same rule as the harness's own
|
|
1143
|
-
* session-stats fold). Shape-guarded: a malformed chunk is just not a token.
|
|
1144
|
-
*/
|
|
1145
|
-
function isTokenDelta(chunk) {
|
|
1146
|
-
if (chunk === null || typeof chunk !== "object") return false;
|
|
1147
|
-
const c = chunk;
|
|
1148
|
-
switch (c.type) {
|
|
1149
|
-
case "text-delta":
|
|
1150
|
-
case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
|
|
1151
|
-
case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
|
|
1152
|
-
default: return false;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
1309
|
* The fold's private timing accumulator: created on first use, and CLONED on
|
|
1157
1310
|
* every later ensure() (see `applyTimeline`) — the object left in the
|
|
1158
1311
|
* persisted previous state is never written into in place.
|
|
@@ -1225,7 +1378,20 @@ function applyTimeline(state, event, bounds) {
|
|
|
1225
1378
|
const tools = Array.isArray(header.tools) ? header.tools : [];
|
|
1226
1379
|
const s = ensure();
|
|
1227
1380
|
s.toolsTokens = estimateToolsTotal(tools);
|
|
1228
|
-
|
|
1381
|
+
const systemText = header.system;
|
|
1382
|
+
if (typeof systemText === "string" && systemText !== "") {
|
|
1383
|
+
s.systems = [{
|
|
1384
|
+
seq: event.seq,
|
|
1385
|
+
time: event.time,
|
|
1386
|
+
tokens: estimateSystemTokens(systemText)
|
|
1387
|
+
}];
|
|
1388
|
+
s.systemsFromHeader = true;
|
|
1389
|
+
s.systemTokens = systemTokensOf(s.systems);
|
|
1390
|
+
} else if (s.systemsFromHeader === true) {
|
|
1391
|
+
s.systems = [];
|
|
1392
|
+
delete s.systemsFromHeader;
|
|
1393
|
+
s.systemTokens = 0;
|
|
1394
|
+
}
|
|
1229
1395
|
if (header.config && typeof header.config.model === "string") s.model = header.config.model;
|
|
1230
1396
|
if (header.config && typeof header.config.provider === "string") s.provider = header.config.provider;
|
|
1231
1397
|
if ((data?.reason === "change" || data?.reason === "resume") && s.model && s.lastModel && s.model !== s.lastModel) {
|
|
@@ -1241,6 +1407,25 @@ function applyTimeline(state, event, bounds) {
|
|
|
1241
1407
|
if (s.model) s.lastModel = s.model;
|
|
1242
1408
|
break;
|
|
1243
1409
|
}
|
|
1410
|
+
case "system/message": {
|
|
1411
|
+
const s = ensure();
|
|
1412
|
+
delete s.pendingShadowedSeqs;
|
|
1413
|
+
delete s.pendingShadowEventSeq;
|
|
1414
|
+
const op = replaceRangeOf(event.surfaceOp);
|
|
1415
|
+
if (op !== null) {
|
|
1416
|
+
s.systems = (s.systems ?? []).filter((n) => n.seq < op.start || n.seq > op.end);
|
|
1417
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1418
|
+
for (const n of s.surface) if (n.seq >= op.start && n.seq <= op.end) claimed.add(n.seq);
|
|
1419
|
+
if (removeSurfaceSeqs(s, claimed, event.seq).length > 0) bumpDetailRev(s);
|
|
1420
|
+
}
|
|
1421
|
+
delete s.systemsFromHeader;
|
|
1422
|
+
pushSystem(s, {
|
|
1423
|
+
seq: event.seq,
|
|
1424
|
+
time: event.time,
|
|
1425
|
+
tokens: estimateSystemContent(messageOf(data)?.content)
|
|
1426
|
+
});
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1244
1429
|
case "request/context": {
|
|
1245
1430
|
const s = ensure();
|
|
1246
1431
|
if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
|
|
@@ -1259,7 +1444,8 @@ function applyTimeline(state, event, bounds) {
|
|
|
1259
1444
|
};
|
|
1260
1445
|
}
|
|
1261
1446
|
break;
|
|
1262
|
-
case "tool/code-dispatch":
|
|
1447
|
+
case "tool/code-dispatch":
|
|
1448
|
+
case "tool/ptc-dispatch": {
|
|
1263
1449
|
const rootCallId = data?.rootCallId;
|
|
1264
1450
|
const name = data?.name;
|
|
1265
1451
|
if (typeof rootCallId === "string" && typeof name === "string") {
|
|
@@ -1277,7 +1463,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1277
1463
|
case "assistant/chunk": {
|
|
1278
1464
|
const start = state.stepStart;
|
|
1279
1465
|
if (start === void 0 || start.firstToken !== void 0) return state;
|
|
1280
|
-
if (!
|
|
1466
|
+
if (!isTokenChunk(data?.chunk)) return state;
|
|
1281
1467
|
const s = ensure();
|
|
1282
1468
|
s.stepStart = {
|
|
1283
1469
|
time: start.time,
|
|
@@ -1285,6 +1471,18 @@ function applyTimeline(state, event, bounds) {
|
|
|
1285
1471
|
};
|
|
1286
1472
|
break;
|
|
1287
1473
|
}
|
|
1474
|
+
case "assistant/attempt": {
|
|
1475
|
+
const start = state.stepStart;
|
|
1476
|
+
if (start === void 0 || start.firstToken !== void 0) return state;
|
|
1477
|
+
const first = firstTokenTimeOfStream(data?.stream);
|
|
1478
|
+
if (first === void 0) return state;
|
|
1479
|
+
const s = ensure();
|
|
1480
|
+
s.stepStart = {
|
|
1481
|
+
time: start.time,
|
|
1482
|
+
firstToken: first
|
|
1483
|
+
};
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1288
1486
|
case "step/start": {
|
|
1289
1487
|
const s = ensure();
|
|
1290
1488
|
s.stepStart = { time: event.time };
|
|
@@ -1410,9 +1608,12 @@ function applyTimeline(state, event, bounds) {
|
|
|
1410
1608
|
const timing = ensureTiming(s);
|
|
1411
1609
|
timing.calls += 1;
|
|
1412
1610
|
const stepStart = state.stepStart;
|
|
1413
|
-
if (stepStart !== void 0
|
|
1414
|
-
|
|
1415
|
-
|
|
1611
|
+
if (stepStart !== void 0) {
|
|
1612
|
+
const firstToken = stepStart.firstToken ?? firstTokenTimeOfStream(data?.stream);
|
|
1613
|
+
if (firstToken !== void 0) {
|
|
1614
|
+
timing.ttftMs += durOf(stepStart.time, firstToken);
|
|
1615
|
+
timing.genMs += durOf(firstToken, event.time);
|
|
1616
|
+
}
|
|
1416
1617
|
}
|
|
1417
1618
|
const asstMsg = deriveEventMessage(event);
|
|
1418
1619
|
applySurface(s, event, event.type, data, asstMsg);
|
|
@@ -1514,6 +1715,7 @@ function headFieldsOf(state) {
|
|
|
1514
1715
|
tools
|
|
1515
1716
|
};
|
|
1516
1717
|
}
|
|
1718
|
+
if (state.systems !== void 0 && state.systems.length > 0) result.systems = state.systems.map((n) => ({ ...n }));
|
|
1517
1719
|
return result;
|
|
1518
1720
|
}
|
|
1519
1721
|
/**
|
|
@@ -1951,6 +2153,12 @@ const surfaceNodeSchema = z.object({
|
|
|
1951
2153
|
skill: z.string().optional(),
|
|
1952
2154
|
calls: z.array(z.string()).optional()
|
|
1953
2155
|
}).strict();
|
|
2156
|
+
/** One live system-prompt node (shared/types.ts SystemPromptNode). */
|
|
2157
|
+
const systemPromptNodeSchema = z.object({
|
|
2158
|
+
seq: z.number().int().nonnegative(),
|
|
2159
|
+
time: z.number(),
|
|
2160
|
+
tokens: z.number().int().nonnegative()
|
|
2161
|
+
}).strict();
|
|
1954
2162
|
const requestRecordSchema = z.object({
|
|
1955
2163
|
turn: z.number().optional(),
|
|
1956
2164
|
step: z.number().optional(),
|
|
@@ -2097,6 +2305,7 @@ const contextTimelineSchema = z.object({
|
|
|
2097
2305
|
pro: costFamilySchema.optional()
|
|
2098
2306
|
}).strict().optional(),
|
|
2099
2307
|
timing: timingTotalsSchema.optional(),
|
|
2308
|
+
systems: z.array(systemPromptNodeSchema).optional(),
|
|
2100
2309
|
nodes: z.array(surfaceNodeSchema).optional(),
|
|
2101
2310
|
droppedNodes: z.number().int().nonnegative().optional(),
|
|
2102
2311
|
archive: z.array(surfaceNodeSchema).optional(),
|
|
@@ -2120,6 +2329,8 @@ const timelineStateSchema = z.object({
|
|
|
2120
2329
|
tool: z.number().int().nonnegative()
|
|
2121
2330
|
}).strict(),
|
|
2122
2331
|
systemTokens: z.number().int().nonnegative(),
|
|
2332
|
+
systems: z.array(systemPromptNodeSchema).optional(),
|
|
2333
|
+
systemsFromHeader: z.literal(true).optional(),
|
|
2123
2334
|
toolsTokens: z.number().int().nonnegative(),
|
|
2124
2335
|
model: z.string().optional(),
|
|
2125
2336
|
provider: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
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": {
|
|
@@ -57,13 +57,16 @@
|
|
|
57
57
|
"@deepseek-ai/dsh-client-connection",
|
|
58
58
|
"@deepseek-ai/dsh-client-locale",
|
|
59
59
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right"
|
|
61
62
|
],
|
|
62
63
|
"platform": "web"
|
|
63
64
|
},
|
|
64
65
|
"compatibility": {
|
|
65
66
|
"dshReleases": {
|
|
66
|
-
"0.1.2-rc.1": "compatible"
|
|
67
|
+
"0.1.2-rc.1": "compatible",
|
|
68
|
+
"0.1.3-alpha.2": "compatible",
|
|
69
|
+
"0.1.5-alpha.1": "compatible"
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
},
|