dsh-context 0.34.0 → 0.35.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/lib/index.js CHANGED
@@ -190,27 +190,37 @@ const ROLE_OVERHEAD = 4;
190
190
  function estimateToolsTotal(tools) {
191
191
  return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
192
192
  }
193
+ /** The `ContentBlock` walkers take `unknown`: block arrays ride the untrusted
194
+ * log, so their element shapes (null and primitives included) are re-proved
195
+ * here, not trusted from the declared message types. */
193
196
  function estimateBlocks(blocks) {
194
197
  let tokens = 0;
195
198
  if (!Array.isArray(blocks)) return 0;
196
- for (const block of blocks) switch (block.type) {
197
- case "text":
198
- case "reasoning":
199
- tokens += Math.ceil((block.text || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
200
- break;
201
- case "tool-call":
202
- tokens += Math.ceil((block.name || "").length / CHARS_PER_TOKEN) + Math.ceil((block.arguments || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
203
- break;
204
- case "tool-result":
205
- tokens += estimateBlocks(block.content) + BLOCK_OVERHEAD;
206
- break;
207
- case "image": {
208
- const ref = block.attachment;
209
- const priced = ref !== null && typeof ref === "object" && typeof ref.width === "number" && typeof ref.height === "number" ? estimateImageTokens(ref.width, ref.height) : null;
210
- tokens += (priced ?? Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)) + BLOCK_OVERHEAD;
211
- break;
199
+ for (const item of blocks) {
200
+ if (item === null || typeof item !== "object") {
201
+ tokens += BLOCK_OVERHEAD;
202
+ continue;
203
+ }
204
+ const block = item;
205
+ switch (block.type) {
206
+ case "text":
207
+ case "reasoning":
208
+ tokens += Math.ceil((block.text || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
209
+ break;
210
+ case "tool-call":
211
+ tokens += Math.ceil((block.name || "").length / CHARS_PER_TOKEN) + Math.ceil((block.arguments || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
212
+ break;
213
+ case "tool-result":
214
+ tokens += estimateBlocks(block.content) + BLOCK_OVERHEAD;
215
+ break;
216
+ case "image": {
217
+ const ref = block.attachment;
218
+ const priced = ref !== null && typeof ref === "object" && typeof ref.width === "number" && typeof ref.height === "number" ? estimateImageTokens(ref.width, ref.height) : null;
219
+ tokens += (priced ?? Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)) + BLOCK_OVERHEAD;
220
+ break;
221
+ }
222
+ default: tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN);
212
223
  }
213
- default: tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN);
214
224
  }
215
225
  return tokens;
216
226
  }
@@ -238,19 +248,31 @@ function estimateToolSchema(tool) {
238
248
  function imageCountOf(blocks) {
239
249
  let count = 0;
240
250
  if (!Array.isArray(blocks)) return 0;
241
- for (const block of blocks) if (block.type === "image") count++;
242
- else if (Array.isArray(block.content)) count += imageCountOf(block.content);
251
+ for (const item of blocks) {
252
+ if (item === null || typeof item !== "object") continue;
253
+ const block = item;
254
+ if (block.type === "image") count++;
255
+ else if (Array.isArray(block.content)) count += imageCountOf(block.content);
256
+ }
243
257
  return count;
244
258
  }
245
259
  function firstText(blocks) {
246
260
  if (!Array.isArray(blocks)) return "";
247
- for (const b of blocks) if (b.type === "text" && typeof b.text === "string" && b.text.trim() !== "") return b.text.replace(/\s+/g, " ").trim().slice(0, 80);
261
+ for (const item of blocks) {
262
+ if (item === null || typeof item !== "object") continue;
263
+ const b = item;
264
+ if (b.type === "text" && typeof b.text === "string" && b.text.trim() !== "") return b.text.replace(/\s+/g, " ").trim().slice(0, 80);
265
+ }
248
266
  return "";
249
267
  }
250
268
  function toolCallNames(blocks) {
251
269
  const names = [];
252
270
  if (!Array.isArray(blocks)) return names;
253
- for (const b of blocks) if (b.type === "tool-call" && typeof b.name === "string") names.push(b.name);
271
+ for (const item of blocks) {
272
+ if (item === null || typeof item !== "object") continue;
273
+ const b = item;
274
+ if (b.type === "tool-call" && typeof b.name === "string") names.push(b.name);
275
+ }
254
276
  return names;
255
277
  }
256
278
  /**
@@ -323,7 +345,7 @@ function recordOf(event) {
323
345
  seq: event.seq,
324
346
  time: event.time,
325
347
  tools: tools.map((t) => {
326
- const tool = t;
348
+ const tool = t !== null && typeof t === "object" ? t : {};
327
349
  const entry = {
328
350
  name: typeof tool.name === "string" ? tool.name : "?",
329
351
  tokens: estimateToolSchema(t),
@@ -471,7 +493,9 @@ function archiveRemoved(st, removed, goneSeq) {
471
493
  */
472
494
  function nestedText(blocks) {
473
495
  if (!Array.isArray(blocks)) return "";
474
- for (const block of blocks) {
496
+ for (const item of blocks) {
497
+ if (item === null || typeof item !== "object") continue;
498
+ const block = item;
475
499
  if (block.type === "text" && typeof block.text === "string" && block.text !== "") return block.text;
476
500
  if (block.content !== void 0) {
477
501
  const nested = nestedText(block.content);
@@ -513,8 +537,8 @@ function applySurface(st, ev, type, data, message) {
513
537
  const srcId = source?.callId;
514
538
  const srcName = typeof srcId === "string" ? st.callNames[srcId] : void 0;
515
539
  const blockId = (message?.content?.[0])?.toolCallId;
516
- if (srcName) node.tool = srcName;
517
- else if (typeof blockId === "string") node.tool = st.callNames[blockId];
540
+ if (srcName !== void 0) node.tool = srcName;
541
+ else if (typeof blockId === "string" && Object.hasOwn(st.callNames, blockId)) node.tool = st.callNames[blockId];
518
542
  if (typeof srcId === "string" || typeof blockId === "string") {
519
543
  const kept = {};
520
544
  for (const k in st.callNames) if (k !== srcId && k !== blockId) kept[k] = st.callNames[k];
@@ -659,137 +683,141 @@ function applyTimeline(state, event, bounds) {
659
683
  callNames: { ...state.callNames }
660
684
  };
661
685
  const data = event.data;
662
- switch (event.type) {
663
- case "request/header": {
664
- const header = data?.header ?? {};
665
- const tools = Array.isArray(header.tools) ? header.tools : [];
666
- const s = ensure();
667
- s.toolsTokens = estimateToolsTotal(tools);
668
- s.systemTokens = estimateSystem(header.system);
669
- if (header.config && typeof header.config.model === "string") s.model = header.config.model;
670
- if (header.config && typeof header.config.provider === "string") s.provider = header.config.provider;
671
- if ((data?.reason === "change" || data?.reason === "resume") && s.model && s.lastModel && s.model !== s.lastModel) s.events.push({
672
- seq: event.seq,
673
- time: event.time,
674
- kind: "model",
675
- from: s.lastModel,
676
- to: s.model
677
- });
678
- if (s.model) s.lastModel = s.model;
679
- break;
680
- }
681
- case "request/context": {
682
- const s = ensure();
683
- if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
684
- if (data && typeof data.model === "string") s.model = data.model;
685
- if (data && typeof data.provider === "string") s.provider = data.provider;
686
- break;
687
- }
688
- case "tool/call":
689
- if (data && typeof data.callId === "string" && typeof data.name === "string") {
686
+ try {
687
+ switch (event.type) {
688
+ case "request/header": {
689
+ const header = data?.header ?? {};
690
+ const tools = Array.isArray(header.tools) ? header.tools : [];
690
691
  const s = ensure();
691
- s.callNames[data.callId] = data.name;
692
- }
693
- break;
694
- case "user/message": {
695
- const msg = deriveEventMessage(event);
696
- const s = ensure();
697
- const node = applySurface(s, event, event.type, data, msg);
698
- const source = msg?.source;
699
- if (isInjection(source)) {
700
- const rec = {
692
+ s.toolsTokens = estimateToolsTotal(tools);
693
+ s.systemTokens = estimateSystem(header.system);
694
+ if (header.config && typeof header.config.model === "string") s.model = header.config.model;
695
+ if (header.config && typeof header.config.provider === "string") s.provider = header.config.provider;
696
+ if ((data?.reason === "change" || data?.reason === "resume") && s.model && s.lastModel && s.model !== s.lastModel) s.events.push({
701
697
  seq: event.seq,
702
698
  time: event.time,
703
- kind: "inject",
704
- form: source.form || "context",
705
- tokens: node.tokens
706
- };
707
- if (source.kind === "skill-invocation") {
708
- rec.sub = "skill";
709
- rec.name = typeof source.name === "string" ? source.name : "?";
710
- } else {
711
- const label = injectionSourceName(source);
712
- if (label !== "") rec.name = label;
713
- if (source.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
714
- }
715
- s.events.push(rec);
699
+ kind: "model",
700
+ from: s.lastModel,
701
+ to: s.model
702
+ });
703
+ if (s.model) s.lastModel = s.model;
704
+ break;
716
705
  }
717
- break;
718
- }
719
- case "tool/result": {
720
- const toolMsg = deriveEventMessage(event);
721
- const s = ensure();
722
- const node = applySurface(s, event, event.type, data, toolMsg);
723
- if (node.tool === "skill" || node.tool === void 0) {
724
- const name = skillNameOf(toolMsg);
725
- if (name !== "") {
726
- node.skill = name;
727
- s.events.push({
706
+ case "request/context": {
707
+ const s = ensure();
708
+ if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
709
+ if (data && typeof data.model === "string") s.model = data.model;
710
+ if (data && typeof data.provider === "string") s.provider = data.provider;
711
+ break;
712
+ }
713
+ case "tool/call":
714
+ if (data && typeof data.callId === "string" && typeof data.name === "string") {
715
+ const s = ensure();
716
+ s.callNames[data.callId] = data.name;
717
+ }
718
+ break;
719
+ case "user/message": {
720
+ const msg = deriveEventMessage(event);
721
+ const s = ensure();
722
+ const node = applySurface(s, event, event.type, data, msg);
723
+ const source = msg?.source;
724
+ if (isInjection(source)) {
725
+ const rec = {
728
726
  seq: event.seq,
729
727
  time: event.time,
730
728
  kind: "inject",
731
- form: "instructions",
732
- sub: "skill",
733
- name,
729
+ form: source.form || "context",
734
730
  tokens: node.tokens
735
- });
731
+ };
732
+ if (source.kind === "skill-invocation") {
733
+ rec.sub = "skill";
734
+ rec.name = typeof source.name === "string" ? source.name : "?";
735
+ } else {
736
+ const label = injectionSourceName(source);
737
+ if (label !== "") rec.name = label;
738
+ if (source.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
739
+ }
740
+ s.events.push(rec);
736
741
  }
742
+ break;
737
743
  }
738
- break;
739
- }
740
- case "assistant/message": {
741
- const usage = data?.usage;
742
- const s = ensure();
743
- const total = s.systemTokens + s.toolsTokens + s.sums.user + s.sums.inject + s.sums.assistant + s.sums.tool;
744
- const record = {
745
- time: event.time,
746
- seq: event.seq,
747
- system: s.systemTokens,
748
- tools: s.toolsTokens,
749
- user: s.sums.user,
750
- inject: s.sums.inject,
751
- assistant: s.sums.assistant,
752
- tool: s.sums.tool,
753
- total
754
- };
755
- if (data && typeof data.turn === "number") record.turn = data.turn;
756
- if (data && typeof data.step === "number") record.step = data.step;
757
- if (usage && typeof usage.inputTokens === "number") {
758
- record.prompt = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
759
- if (typeof usage.cacheReadTokens === "number") record.cacheRead = usage.cacheReadTokens;
760
- if (typeof usage.outputTokens === "number") record.output = usage.outputTokens;
761
- accumulateCost(s, event.time, usage);
744
+ case "tool/result": {
745
+ const toolMsg = deriveEventMessage(event);
746
+ const s = ensure();
747
+ const node = applySurface(s, event, event.type, data, toolMsg);
748
+ if (node.tool === "skill" || node.tool === void 0) {
749
+ const name = skillNameOf(toolMsg);
750
+ if (name !== "") {
751
+ node.skill = name;
752
+ s.events.push({
753
+ seq: event.seq,
754
+ time: event.time,
755
+ kind: "inject",
756
+ form: "instructions",
757
+ sub: "skill",
758
+ name,
759
+ tokens: node.tokens
760
+ });
761
+ }
762
+ }
763
+ break;
762
764
  }
763
- s.requests.push(record);
764
- const asstMsg = deriveEventMessage(event);
765
- applySurface(s, event, event.type, data, asstMsg);
766
- break;
767
- }
768
- case "plan/mode":
769
- if (data && typeof data.active === "boolean") ensure().events.push({
770
- seq: event.seq,
771
- time: event.time,
772
- kind: "mode",
773
- name: data.active ? "plan.on" : "plan.off"
774
- });
775
- break;
776
- case "compaction/summary":
777
- case "compaction/prune": {
778
- const s = ensure();
779
- if (data && Array.isArray(data.shadowedSeqs)) {
780
- s.pendingShadowedSeqs = data.shadowedSeqs.filter((x) => typeof x === "number");
781
- s.pendingShadowEventSeq = event.seq;
765
+ case "assistant/message": {
766
+ const usage = data?.usage;
767
+ const s = ensure();
768
+ const total = s.systemTokens + s.toolsTokens + s.sums.user + s.sums.inject + s.sums.assistant + s.sums.tool;
769
+ const record = {
770
+ time: event.time,
771
+ seq: event.seq,
772
+ system: s.systemTokens,
773
+ tools: s.toolsTokens,
774
+ user: s.sums.user,
775
+ inject: s.sums.inject,
776
+ assistant: s.sums.assistant,
777
+ tool: s.sums.tool,
778
+ total
779
+ };
780
+ if (data && typeof data.turn === "number") record.turn = data.turn;
781
+ if (data && typeof data.step === "number") record.step = data.step;
782
+ if (usage && typeof usage.inputTokens === "number") {
783
+ record.prompt = usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
784
+ if (typeof usage.cacheReadTokens === "number") record.cacheRead = usage.cacheReadTokens;
785
+ if (typeof usage.outputTokens === "number") record.output = usage.outputTokens;
786
+ accumulateCost(s, event.time, usage);
787
+ }
788
+ s.requests.push(record);
789
+ const asstMsg = deriveEventMessage(event);
790
+ applySurface(s, event, event.type, data, asstMsg);
791
+ break;
782
792
  }
783
- s.events.push({
784
- seq: event.seq,
785
- time: event.time,
786
- kind: event.type === "compaction/summary" ? "compaction" : "prune",
787
- tokens: data && typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : 0,
788
- ...event.type === "compaction/summary" && data && Array.isArray(data.shadowedSeqs) ? { count: data.shadowedSeqs.length } : {}
789
- });
790
- break;
793
+ case "plan/mode":
794
+ if (data && typeof data.active === "boolean") ensure().events.push({
795
+ seq: event.seq,
796
+ time: event.time,
797
+ kind: "mode",
798
+ name: data.active ? "plan.on" : "plan.off"
799
+ });
800
+ break;
801
+ case "compaction/summary":
802
+ case "compaction/prune": {
803
+ const s = ensure();
804
+ if (data && Array.isArray(data.shadowedSeqs)) {
805
+ s.pendingShadowedSeqs = data.shadowedSeqs.filter((x) => typeof x === "number");
806
+ s.pendingShadowEventSeq = event.seq;
807
+ }
808
+ s.events.push({
809
+ seq: event.seq,
810
+ time: event.time,
811
+ kind: event.type === "compaction/summary" ? "compaction" : "prune",
812
+ tokens: data && typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : 0,
813
+ ...event.type === "compaction/summary" && data && Array.isArray(data.shadowedSeqs) ? { count: data.shadowedSeqs.length } : {}
814
+ });
815
+ break;
816
+ }
817
+ default: return state;
791
818
  }
792
- default: return state;
819
+ } catch {
820
+ st = void 0;
793
821
  }
794
822
  if (st !== void 0) {
795
823
  trimState(st, bounds);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.34.0",
3
+ "version": "0.35.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": {