dsh-context 0.39.0 → 0.40.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/lib/client.js +363 -101
- package/lib/index.d.ts +30 -8
- package/lib/index.js +97 -49
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -30,8 +30,27 @@ declare const Config: z.ZodPreprocess<z.ZodObject<{
|
|
|
30
30
|
}, z.core.$strict>>;
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region src/host/headers.d.ts
|
|
33
|
+
/**
|
|
34
|
+
* One stored tool: the v1 row shape carried the producer description and the
|
|
35
|
+
* raw schema; folds since the #37 slim-down append metadata-only entries.
|
|
36
|
+
*/
|
|
37
|
+
interface StoredHeaderTool {
|
|
38
|
+
name: string;
|
|
39
|
+
tokens: number;
|
|
40
|
+
description?: string;
|
|
41
|
+
plugin?: string;
|
|
42
|
+
schema?: unknown;
|
|
43
|
+
}
|
|
44
|
+
/** One stored epoch: v1 rows carried `system`; folds since carry `systemTokens`. */
|
|
45
|
+
interface StoredHeaderRecord {
|
|
46
|
+
seq: number;
|
|
47
|
+
time: number;
|
|
48
|
+
system?: string;
|
|
49
|
+
systemTokens?: number;
|
|
50
|
+
tools: StoredHeaderTool[];
|
|
51
|
+
}
|
|
33
52
|
interface HeadersState {
|
|
34
|
-
headers:
|
|
53
|
+
headers: StoredHeaderRecord[];
|
|
35
54
|
}
|
|
36
55
|
//#endregion
|
|
37
56
|
//#region src/host/fold.d.ts
|
|
@@ -382,12 +401,10 @@ interface ContextEventRecord {
|
|
|
382
401
|
turn?: number;
|
|
383
402
|
step?: number;
|
|
384
403
|
}
|
|
385
|
-
/** One tool
|
|
404
|
+
/** One tool of a request-header epoch, with its display price. */
|
|
386
405
|
interface HeaderTool {
|
|
387
406
|
name: string;
|
|
388
407
|
tokens: number;
|
|
389
|
-
/** Producer-declared description (may be long; the browser truncates). */
|
|
390
|
-
description?: string;
|
|
391
408
|
/**
|
|
392
409
|
* The registering plugin's label, when attribution is known: either a
|
|
393
410
|
* `plugin` field carried by the raw header entry (harness-provided) or the
|
|
@@ -397,16 +414,21 @@ interface HeaderTool {
|
|
|
397
414
|
* the browser shows no tag.
|
|
398
415
|
*/
|
|
399
416
|
plugin?: string;
|
|
400
|
-
/** The raw JSON schema object the model received (plain JSON). */
|
|
401
|
-
schema?: unknown;
|
|
402
417
|
}
|
|
403
418
|
/**
|
|
404
|
-
* One request-header epoch: the
|
|
419
|
+
* One request-header epoch's METADATA: the epoch boundaries and token prices
|
|
420
|
+
* in force from this event's seq until the next epoch. The epoch CONTENT
|
|
421
|
+
* (full system prompt text, tool descriptions/schemas) is not projected —
|
|
422
|
+
* every session.list row, control baseline, push frame, and projection-cache
|
|
423
|
+
* checkpoint would otherwise carry it per session × epoch. The client
|
|
424
|
+
* fetches one epoch's content on demand (a seq-anchored history read off
|
|
425
|
+
* `seq`) as a {@link HeaderEpochContent}.
|
|
405
426
|
*/
|
|
406
427
|
interface HeaderRecord {
|
|
407
428
|
seq: number;
|
|
408
429
|
time: number;
|
|
409
|
-
system
|
|
430
|
+
/** The epoch's estimated system-prompt tokens; absent when it logged no system prompt. */
|
|
431
|
+
systemTokens?: number;
|
|
410
432
|
tools: HeaderTool[];
|
|
411
433
|
}
|
|
412
434
|
/** The `contextHeaders` projection value: the bounded epoch list (newest last). */
|
package/lib/index.js
CHANGED
|
@@ -433,11 +433,11 @@ function estimateImageTokens(width, height) {
|
|
|
433
433
|
* (shared/imageTokens.ts), falling back to the meter's JSON price when the
|
|
434
434
|
* attachment's dimensions are unknown.
|
|
435
435
|
*/
|
|
436
|
-
const CHARS_PER_TOKEN = 4;
|
|
436
|
+
const CHARS_PER_TOKEN$1 = 4;
|
|
437
437
|
const BLOCK_OVERHEAD = 4;
|
|
438
|
-
const ROLE_OVERHEAD = 4;
|
|
438
|
+
const ROLE_OVERHEAD$1 = 4;
|
|
439
439
|
function estimateToolsTotal(tools) {
|
|
440
|
-
return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
|
|
440
|
+
return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN$1) + BLOCK_OVERHEAD : 0;
|
|
441
441
|
}
|
|
442
442
|
/** The `ContentBlock` walkers take `unknown`: block arrays ride the untrusted
|
|
443
443
|
* log, so their element shapes (null and primitives included) are re-proved
|
|
@@ -454,10 +454,10 @@ function estimateBlocks(blocks) {
|
|
|
454
454
|
switch (block.type) {
|
|
455
455
|
case "text":
|
|
456
456
|
case "reasoning":
|
|
457
|
-
tokens += Math.ceil((block.text || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
|
|
457
|
+
tokens += Math.ceil((block.text || "").length / CHARS_PER_TOKEN$1) + BLOCK_OVERHEAD;
|
|
458
458
|
break;
|
|
459
459
|
case "tool-call":
|
|
460
|
-
tokens += Math.ceil((block.name || "").length / CHARS_PER_TOKEN) + Math.ceil((block.arguments || "").length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
|
|
460
|
+
tokens += Math.ceil((block.name || "").length / CHARS_PER_TOKEN$1) + Math.ceil((block.arguments || "").length / CHARS_PER_TOKEN$1) + BLOCK_OVERHEAD;
|
|
461
461
|
break;
|
|
462
462
|
case "tool-result":
|
|
463
463
|
tokens += estimateBlocks(block.content) + BLOCK_OVERHEAD;
|
|
@@ -465,10 +465,10 @@ function estimateBlocks(blocks) {
|
|
|
465
465
|
case "image": {
|
|
466
466
|
const ref = block.attachment;
|
|
467
467
|
const priced = ref !== null && typeof ref === "object" && typeof ref.width === "number" && typeof ref.height === "number" ? estimateImageTokens(ref.width, ref.height) : null;
|
|
468
|
-
tokens += (priced ?? Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)) + BLOCK_OVERHEAD;
|
|
468
|
+
tokens += (priced ?? Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN$1)) + BLOCK_OVERHEAD;
|
|
469
469
|
break;
|
|
470
470
|
}
|
|
471
|
-
default: tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN);
|
|
471
|
+
default: tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN$1);
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
return tokens;
|
|
@@ -480,15 +480,12 @@ function estimateBlocks(blocks) {
|
|
|
480
480
|
*/
|
|
481
481
|
function estimateMessage(message, emptyIsZero = false) {
|
|
482
482
|
if (emptyIsZero && (message === null || message === void 0 || !Array.isArray(message.content) || message.content.length === 0)) return 0;
|
|
483
|
-
return estimateBlocks(message?.content) + ROLE_OVERHEAD;
|
|
484
|
-
}
|
|
485
|
-
function estimateSystem(text) {
|
|
486
|
-
if (typeof text !== "string" || text.length === 0) return 0;
|
|
487
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD;
|
|
483
|
+
return estimateBlocks(message?.content) + ROLE_OVERHEAD$1;
|
|
488
484
|
}
|
|
485
|
+
/** The shared meter heuristic over rendered system-prompt text (shared/estimate.ts). */
|
|
489
486
|
/** Per-tool price for the top-tools display (the total uses dsh's whole-array price). */
|
|
490
487
|
function estimateToolSchema(tool) {
|
|
491
|
-
return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
|
|
488
|
+
return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN$1) + BLOCK_OVERHEAD;
|
|
492
489
|
}
|
|
493
490
|
/**
|
|
494
491
|
* Count image blocks in a message payload, recursing into nested content (tool-result blocks carry their inner blocks) — seeds each node's
|
|
@@ -547,44 +544,90 @@ function isInjection(source) {
|
|
|
547
544
|
return source !== null && source !== void 0 && (typeof source.kind === "string" && source.kind !== "" && source.kind !== "user" || typeof source.form === "string");
|
|
548
545
|
}
|
|
549
546
|
//#endregion
|
|
547
|
+
//#region src/shared/estimate.ts
|
|
548
|
+
/**
|
|
549
|
+
* Token heuristics shared by the host fold and the client boundary — the
|
|
550
|
+
* harness token-meter's own fixed-density figure (dsh-token-meter/estimate.ts:
|
|
551
|
+
* ~4 chars ≈ 1 token, +4 role framing). Priced identically on both sides so a
|
|
552
|
+
* legacy value normalized at the client boundary matches what the host view
|
|
553
|
+
* would have served.
|
|
554
|
+
*/
|
|
555
|
+
const CHARS_PER_TOKEN = 4;
|
|
556
|
+
const ROLE_OVERHEAD = 4;
|
|
557
|
+
/** Price rendered system-prompt text; 0 for absent/empty/non-string input. */
|
|
558
|
+
function estimateSystemTokens(text) {
|
|
559
|
+
if (typeof text !== "string" || text.length === 0) return 0;
|
|
560
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN) + ROLE_OVERHEAD;
|
|
561
|
+
}
|
|
562
|
+
//#endregion
|
|
550
563
|
//#region src/host/headers.ts
|
|
551
564
|
/**
|
|
552
|
-
* The `contextHeaders` session projection unit — the request-header
|
|
553
|
-
*
|
|
565
|
+
* The `contextHeaders` session projection unit — the request-header EPOCH
|
|
566
|
+
* METADATA behind the timeline's envelope figures.
|
|
554
567
|
*
|
|
555
568
|
* The hot `contextTimeline` unit carries only token prices of the system
|
|
556
|
-
* prompt and tool schemas; this companion unit keeps the
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
569
|
+
* prompt and tool schemas; this companion unit keeps the per-epoch METADATA
|
|
570
|
+
* (epoch seq/time boundaries, per-tool token prices and plugin attribution)
|
|
571
|
+
* so the Context browser can pick the header epoch in force at any step and
|
|
572
|
+
* size its sections immediately. The epoch CONTENT (full system prompt text,
|
|
573
|
+
* full tool JSON schemas) deliberately does NOT ride the projection VALUE:
|
|
574
|
+
* session projections are served whole in every `session.list` row, control
|
|
575
|
+
* baseline, push frame, and change notification, so carrying content here
|
|
576
|
+
* multiplied it by sessions × epochs across every channel. The client
|
|
577
|
+
* fetches one epoch's `request/header` event on demand — a seq-anchored
|
|
578
|
+
* history read off the epoch's `seq`, the same targeted read the browser
|
|
579
|
+
* already uses for message content — and caches it per session (history is
|
|
580
|
+
* immutable).
|
|
581
|
+
*
|
|
582
|
+
* Read-compat over the persisted state (the pinned decision behind keeping
|
|
583
|
+
* `stateVersion` at 1): the harness serves a cold session's projections from
|
|
584
|
+
* its CACHED checkpoint rows and has no refresh channel for an idle session
|
|
585
|
+
* — a version bump invalidates every row and orphans the key until the
|
|
586
|
+
* session goes live again (the #37 regression). The state therefore still
|
|
587
|
+
* ACCEPTS the v1 content-bearing record shape, current folds append
|
|
588
|
+
* metadata-only records alongside any seeded legacy ones, and the view
|
|
589
|
+
* normalizes BOTH to the metadata-only wire shape (pricing the legacy system
|
|
590
|
+
* text at read time). Cached v1 rows keep working, new checkpoint writes
|
|
591
|
+
* shrink as legacy epochs age out of the capped list, and the wire — the
|
|
592
|
+
* part every delivery channel carries — is metadata-only from day one.
|
|
563
593
|
*
|
|
564
594
|
* Same projection contract as the timeline unit: pure init/apply/view,
|
|
565
595
|
* `Object.is` reference stability for uninteresting events, plain-JSON
|
|
566
596
|
* bounded state (epoch list capped — see HEADERS_MAX).
|
|
567
597
|
*/
|
|
568
|
-
/** Retention cap on header epochs (changes are rare; 50 is generous). */
|
|
598
|
+
/** Retention cap on header epochs (metadata only; changes are rare; 50 is generous). */
|
|
569
599
|
const HEADERS_MAX = 50;
|
|
570
|
-
|
|
600
|
+
/**
|
|
601
|
+
* The persisted-state schema: the SUPERSET of both record generations, so a
|
|
602
|
+
* cached v1 row (content-bearing) seeds the fold instead of being discarded.
|
|
603
|
+
*/
|
|
604
|
+
const storedToolSchema = z.object({
|
|
571
605
|
name: z.string(),
|
|
572
606
|
tokens: z.number().int().nonnegative(),
|
|
573
607
|
description: z.string().optional(),
|
|
574
608
|
plugin: z.string().optional(),
|
|
575
609
|
schema: z.unknown().optional()
|
|
576
610
|
}).strict();
|
|
577
|
-
const
|
|
611
|
+
const storedEpochSchema = z.object({
|
|
578
612
|
seq: z.number(),
|
|
579
613
|
time: z.number(),
|
|
580
614
|
system: z.string().optional(),
|
|
581
|
-
|
|
615
|
+
systemTokens: z.number().int().nonnegative().optional(),
|
|
616
|
+
tools: z.array(storedToolSchema)
|
|
617
|
+
}).strict();
|
|
618
|
+
const contextHeadersStateSchema = z.object({ headers: z.array(storedEpochSchema) }).strict();
|
|
619
|
+
/** The wire schema: strict metadata — the shape every delivery channel carries. */
|
|
620
|
+
const headerToolWireSchema = z.object({
|
|
621
|
+
name: z.string(),
|
|
622
|
+
tokens: z.number().int().nonnegative(),
|
|
623
|
+
plugin: z.string().optional()
|
|
624
|
+
}).strict();
|
|
625
|
+
const contextHeadersSchema = z.object({ headers: z.array(z.object({
|
|
626
|
+
seq: z.number(),
|
|
627
|
+
time: z.number(),
|
|
628
|
+
systemTokens: z.number().int().nonnegative().optional(),
|
|
629
|
+
tools: z.array(headerToolWireSchema)
|
|
582
630
|
}).strict()) }).strict();
|
|
583
|
-
/**
|
|
584
|
-
* State and wire are the same shape (the view only shallow-copies each record), so one schema validates both under the dsh 0.1.1-rc.1+
|
|
585
|
-
* `stateSchema`/`wire` contract.
|
|
586
|
-
*/
|
|
587
|
-
const contextHeadersStateSchema = contextHeadersSchema;
|
|
588
631
|
function recordOf(event) {
|
|
589
632
|
if (event.type !== "request/header") return null;
|
|
590
633
|
const rawHeader = event.data.header;
|
|
@@ -598,36 +641,41 @@ function recordOf(event) {
|
|
|
598
641
|
const tool = t !== null && typeof t === "object" ? t : {};
|
|
599
642
|
const entry = {
|
|
600
643
|
name: typeof tool.name === "string" ? tool.name : "?",
|
|
601
|
-
tokens: estimateToolSchema(t)
|
|
602
|
-
schema: t
|
|
644
|
+
tokens: estimateToolSchema(t)
|
|
603
645
|
};
|
|
604
|
-
if (typeof tool.description === "string" && tool.description !== "") entry.description = tool.description;
|
|
605
646
|
if (typeof tool.plugin === "string" && tool.plugin !== "") entry.plugin = tool.plugin;
|
|
606
647
|
return entry;
|
|
607
648
|
})
|
|
608
649
|
};
|
|
609
|
-
if (typeof header.system === "string" && header.system.length > 0) record.
|
|
650
|
+
if (typeof header.system === "string" && header.system.length > 0) record.systemTokens = estimateSystemTokens(header.system);
|
|
610
651
|
return record;
|
|
611
652
|
}
|
|
612
653
|
/**
|
|
613
654
|
* The context-headers projection unit; registered alongside the timeline unit (host/index.ts); clients read it through
|
|
614
|
-
* `useProjection('contextHeaders')` and
|
|
615
|
-
* with a REQUIRED `wire` block (see compat.ts).
|
|
655
|
+
* `useProjection('contextHeaders')` and fetch an epoch's full content on demand via the session history (historyPage.ts).
|
|
656
|
+
* Contract mirror with a REQUIRED `wire` block (see compat.ts).
|
|
616
657
|
* @param resolve - best-effort tool-to-plugin attribution (see toolSources.ts); fills a missing `plugin` at view time so
|
|
617
658
|
* epochs folded without attribution still render a tag when the source is known.
|
|
618
659
|
*/
|
|
619
660
|
function createContextHeadersDefinition(resolve) {
|
|
620
|
-
const view = (state) => ({ headers: state.headers.map((h) =>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
661
|
+
const view = (state) => ({ headers: state.headers.map((h) => {
|
|
662
|
+
const record = {
|
|
663
|
+
seq: h.seq,
|
|
664
|
+
time: h.time,
|
|
665
|
+
tools: h.tools.map((t) => {
|
|
666
|
+
const entry = {
|
|
667
|
+
name: t.name,
|
|
668
|
+
tokens: t.tokens
|
|
669
|
+
};
|
|
670
|
+
const plugin = t.plugin ?? (resolve !== void 0 ? resolve(t.name) : void 0);
|
|
671
|
+
if (plugin !== void 0) entry.plugin = plugin;
|
|
672
|
+
return entry;
|
|
673
|
+
})
|
|
674
|
+
};
|
|
675
|
+
const systemTokens = h.systemTokens ?? (typeof h.system === "string" && h.system !== "" ? estimateSystemTokens(h.system) : void 0);
|
|
676
|
+
if (systemTokens !== void 0) record.systemTokens = systemTokens;
|
|
677
|
+
return record;
|
|
678
|
+
}) });
|
|
631
679
|
return {
|
|
632
680
|
key: "contextHeaders",
|
|
633
681
|
stateSchema: contextHeadersStateSchema,
|
|
@@ -1014,7 +1062,7 @@ function applyTimeline(state, event, bounds) {
|
|
|
1014
1062
|
const tools = Array.isArray(header.tools) ? header.tools : [];
|
|
1015
1063
|
const s = ensure();
|
|
1016
1064
|
s.toolsTokens = estimateToolsTotal(tools);
|
|
1017
|
-
s.systemTokens =
|
|
1065
|
+
s.systemTokens = estimateSystemTokens(header.system);
|
|
1018
1066
|
if (header.config && typeof header.config.model === "string") s.model = header.config.model;
|
|
1019
1067
|
if (header.config && typeof header.config.provider === "string") s.provider = header.config.provider;
|
|
1020
1068
|
if ((data?.reason === "change" || data?.reason === "resume") && s.model && s.lastModel && s.model !== s.lastModel) s.events.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.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": {
|