billion-context-dsh 0.2.21 → 0.2.23
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.en.md +81 -28
- package/README.md +77 -24
- package/dist/block-ledger.d.ts +66 -0
- package/dist/commands.d.ts +2 -1
- package/dist/config.d.ts +1 -1
- package/dist/host-tokens.d.ts +49 -0
- package/dist/index.d.ts +48 -5
- package/dist/index.js +2451 -834
- package/dist/index.js.map +1 -1
- package/dist/lru.d.ts +15 -0
- package/dist/messages.d.ts +100 -0
- package/dist/nudge.d.ts +59 -13
- package/dist/presets.d.ts +61 -0
- package/dist/prompts.d.ts +2 -2
- package/dist/region.d.ts +185 -34
- package/dist/session-events.d.ts +18 -11
- package/dist/settings.d.ts +157 -0
- package/dist/state.d.ts +11 -0
- package/dist/tools.d.ts +113 -2
- package/dist/window.d.ts +32 -3
- package/package.json +25 -17
- package/dist/tool-pairing.d.ts +0 -36
package/dist/lru.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A size-capped Map that evicts least-recently-used entries once the cap is
|
|
3
|
+
* reached (issue #113). Recency is refreshed by both get and set. Backs the
|
|
4
|
+
* engine's per-session caches so idle sessions can be dropped and later
|
|
5
|
+
* rebuilt from the durable session log instead of accumulating forever.
|
|
6
|
+
* @module billion-context-dsh/lru
|
|
7
|
+
*/
|
|
8
|
+
/** Default cap for the engine's per-session caches (kernel states, nudge dedup). */
|
|
9
|
+
export declare const DEFAULT_SESSION_CACHE_LIMIT = 512;
|
|
10
|
+
export declare class LruMap<K, V> extends Map<K, V> {
|
|
11
|
+
private readonly maxEntries;
|
|
12
|
+
constructor(maxEntries: number);
|
|
13
|
+
get(key: K): V | undefined;
|
|
14
|
+
set(key: K, value: V): this;
|
|
15
|
+
}
|
package/dist/messages.d.ts
CHANGED
|
@@ -20,6 +20,20 @@ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
|
20
20
|
* the actual `text` blocks, so a top-level-only walk would drop every tool
|
|
21
21
|
* result from the projection (and with it the seq's ref assignment, breaking
|
|
22
22
|
* compress boundary resolution). Nested arrays are flattened depth-first.
|
|
23
|
+
*
|
|
24
|
+
* Non-text blocks that the provider still bills for render as a deterministic
|
|
25
|
+
* one-line placeholder instead of vanishing (issue #117). An `image`/`file`
|
|
26
|
+
* block used to contribute nothing, which silently made a picture-only user
|
|
27
|
+
* message — or a tool result carrying a screenshot — invisible to the engine:
|
|
28
|
+
* no ref (so no compress boundary), `hasPlainRef` false (so the range solver
|
|
29
|
+
* shrank past it and swallowed neighbours), invisible to the kernel's
|
|
30
|
+
* recent/last-user protection (so the last real user turn could be compressed
|
|
31
|
+
* away), priced at zero tokens, and absent from search/decompress output. The
|
|
32
|
+
* host itself projects non-text references to deterministic handle text for
|
|
33
|
+
* files ("request assembly projects every occurrence to deterministic handle
|
|
34
|
+
* text", dsh-llm/lib/types/types.d.ts), and this is the same idea one layer
|
|
35
|
+
* down. Only durable attachment metadata is used, so the placeholder is stable
|
|
36
|
+
* across turns (cache prefix, summary text, search hits).
|
|
23
37
|
*/
|
|
24
38
|
export declare function extractText(content: unknown): string;
|
|
25
39
|
/**
|
|
@@ -50,6 +64,17 @@ export declare function buildToolCallIndex(events: readonly SessionEvent[]): Rea
|
|
|
50
64
|
* untagged (`toolName: ''`), never "text".
|
|
51
65
|
* Non-surface events project to nothing.
|
|
52
66
|
*/
|
|
67
|
+
/**
|
|
68
|
+
* B1 summary source framing. A compaction summary is MODEL-WRITTEN text, not
|
|
69
|
+
* user words — injected as a user/message with the same standing as real input,
|
|
70
|
+
* which let obligation sentences inside summaries read as user directives and
|
|
71
|
+
* the model's own guesses read as user commitments. The frame says both things
|
|
72
|
+
* up front. It is applied at creation (src/region.ts writes the framed blocks
|
|
73
|
+
* to BOTH durable writes) and again at projection (below) as an idempotent
|
|
74
|
+
* safety net for legacy blocks written before the feature.
|
|
75
|
+
*/
|
|
76
|
+
export declare const SUMMARY_FRAME_PREFIX = "[Model-written summary \u2014 not user words; re-verify any obligations before relying on them]";
|
|
77
|
+
export declare function withSummaryFramePrefix(text: string): string;
|
|
53
78
|
export declare function projectEvent(event: SessionEvent, toolNames?: ReadonlyMap<string, string>): CoreMessage[];
|
|
54
79
|
/** Project a session's message events into CoreMessage[] in log order. */
|
|
55
80
|
export declare function eventsToCoreMessages(events: readonly SessionEvent[], toolNames?: ReadonlyMap<string, string>): CoreMessage[];
|
|
@@ -65,3 +90,78 @@ export declare function surfaceEventsOf(session: Session): SessionEvent[];
|
|
|
65
90
|
export declare function allLogMessages(session: import('@deepseek-ai/dsh-session').Session): CoreMessage[];
|
|
66
91
|
/** Extract the model-facing text of any surface message event. */
|
|
67
92
|
export declare function extractEventText(event: SessionEvent): string;
|
|
93
|
+
/** Count image/file blocks reachable from a content payload (same walk as extractText). */
|
|
94
|
+
export declare function countAttachmentBlocks(content: unknown): {
|
|
95
|
+
images: number;
|
|
96
|
+
files: number;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Attachments carried by one surface event, walked exactly like
|
|
100
|
+
* `extractEventText`. Used in two places: the compressible-range rows mark
|
|
101
|
+
* media-bearing spans, and the callers that already own a token meter price
|
|
102
|
+
* those spans with the provider-anchored media price instead of the text-only
|
|
103
|
+
* estimate (issue #117).
|
|
104
|
+
*/
|
|
105
|
+
export declare function attachmentsOfEvent(event: SessionEvent): {
|
|
106
|
+
images: number;
|
|
107
|
+
files: number;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* The image/file blocks themselves (not just their counts), in document order.
|
|
111
|
+
* The compressible-range rows price these with the fixed-heuristic media price
|
|
112
|
+
* when the meter reports no routed surcharge, so a media-bearing span is never
|
|
113
|
+
* shown as free (issue #117).
|
|
114
|
+
*/
|
|
115
|
+
export declare function mediaBlocksOfEvent(event: SessionEvent): readonly unknown[];
|
|
116
|
+
/**
|
|
117
|
+
* Whether a surface user message is a compaction checkpoint node (already
|
|
118
|
+
* compressed). Defined here (not in region.ts) so the classifier below and
|
|
119
|
+
* region.ts share ONE implementation.
|
|
120
|
+
*/
|
|
121
|
+
export declare function isCheckpointNode(event: SessionEvent): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Injection/authoring classification of one surface event — the ONE shared
|
|
124
|
+
* classifier for range scanning and the protected-tail scan (never ad-hoc
|
|
125
|
+
* predicates that drift apart).
|
|
126
|
+
*
|
|
127
|
+
* - `real` — genuine conversation content (user turns without an injected
|
|
128
|
+
* source, assistant prose/tool-calls, tool results, sub-agent relay rows).
|
|
129
|
+
* This is the only class that may win "last real user message" protection
|
|
130
|
+
* (minus relay rows, see `isRealUserTurn`).
|
|
131
|
+
* - `metadata` — the engine's own ephemeral rows: nudge echoes and
|
|
132
|
+
* compress-pair replacement stubs. Their content is derived from
|
|
133
|
+
* already-visible messages, so folding them into an adjacent real segment
|
|
134
|
+
* is zero-loss — this preserves main's behavior for engine-authored rows.
|
|
135
|
+
* - `checkpoint` — compaction summary nodes (`plugin: 'compact'`).
|
|
136
|
+
* Distillation is an explicit act; never folded into any segment.
|
|
137
|
+
* - `instruction` — host-authored policy/instructions: AGENTS.md injections
|
|
138
|
+
* (both host shapes), skill catalogs, and ANY unknown `kind:'plugin'` row.
|
|
139
|
+
* Folding these is unsafe (the model would lose live policy text, and the
|
|
140
|
+
* host re-injects the current AGENTS.md copy when it disappears — the
|
|
141
|
+
* compress → re-inject loop this PR fixes). Unknown plugin names fall here
|
|
142
|
+
* deliberately: a future host injection must never silently become
|
|
143
|
+
* compressible content.
|
|
144
|
+
*/
|
|
145
|
+
export type SurfaceEventClass = 'real' | 'metadata' | 'checkpoint' | 'instruction';
|
|
146
|
+
/** Plugin names the engine itself authors — safe to fold into real segments. */
|
|
147
|
+
export declare const METADATA_PLUGINS: ReadonlySet<string>;
|
|
148
|
+
/**
|
|
149
|
+
* True for AGENTS.md instruction rows in BOTH host shapes: the hook shape
|
|
150
|
+
* (`kind:'agent-instructions'`, form 'instructions') and the baseline shape
|
|
151
|
+
* (`kind:'plugin'` + plugin 'agent-instructions'). Shared by the newest-row
|
|
152
|
+
* scan and the range scanner so protection and folding always agree on what
|
|
153
|
+
* counts as an AGENTS.md row.
|
|
154
|
+
*/
|
|
155
|
+
export declare function isAgentInstructionsRow(event: SessionEvent): boolean;
|
|
156
|
+
export declare function classifySurfaceEvent(event: SessionEvent): SurfaceEventClass;
|
|
157
|
+
/**
|
|
158
|
+
* Whether an event is a real user turn — the protected-tail criterion. An
|
|
159
|
+
* injected row (AGENTS.md, skill catalog, nudge echo, tool notice) is real
|
|
160
|
+
* *content* at most but is never the user speaking: the latest real user
|
|
161
|
+
* message must keep its protection window even when an injected row lands
|
|
162
|
+
* after it. The scan this replaces protected "the last non-checkpoint
|
|
163
|
+
* user/message", which on live sessions is frequently an AGENTS.md injection
|
|
164
|
+
* row (the host appends it in the same enter batch) — the actual last user
|
|
165
|
+
* message was left compressible while synthetic output sat safe.
|
|
166
|
+
*/
|
|
167
|
+
export declare function isRealUserTurn(event: SessionEvent): boolean;
|
package/dist/nudge.d.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* targets ranges by surface seq rather than by <acp> tags).
|
|
6
6
|
* @module billion-context-dsh/nudge
|
|
7
7
|
*/
|
|
8
|
-
import { type CompressionCore, type CoreMessage, type NudgeDecision } from 'acp-kernel';
|
|
8
|
+
import { type CompressionCore, type CompressionState, type ContextBreakdown, type CoreMessage, type NudgeDecision } from 'acp-kernel';
|
|
9
9
|
import { type UserMessage } from '@deepseek-ai/dsh-llm';
|
|
10
10
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
11
|
import { AcpStateStore } from './state.ts';
|
|
12
|
+
import { type KernelRangeView, type MediaPriceOf } from './region.ts';
|
|
12
13
|
import { type KernelConfigInput } from './config.ts';
|
|
13
14
|
import { type ResolvedPrompts } from './prompts.ts';
|
|
15
|
+
export declare function stripNudgeGuidance(text: string): string;
|
|
14
16
|
/** Kernel inputs the nudge path shares with the compress tool. */
|
|
15
17
|
export interface NudgeEnvironment extends KernelConfigInput {
|
|
16
18
|
readonly kernel: CompressionCore;
|
|
@@ -38,21 +40,65 @@ export interface NudgeOutcome {
|
|
|
38
40
|
export declare function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number;
|
|
39
41
|
/**
|
|
40
42
|
* Render the compressible-range table as seq refs for the model.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
*
|
|
44
|
+
* The spans are the kernel's own (`compressibleRanges`, translated to surface
|
|
45
|
+
* seqs) with the host guards applied on top — see buildCompressibleSeqRanges.
|
|
46
|
+
* This function used to self-compute them from the surface as a labeled
|
|
47
|
+
* `UPSTREAM:` workaround for kernel ref-map drift; that drift is fixed upstream
|
|
48
|
+
* (acp-kernel #207) and the workaround is gone (rule 11).
|
|
49
|
+
*/
|
|
50
|
+
export declare function rangeTable(session: import('@deepseek-ai/dsh-session').Session, kernelView: KernelRangeView, prompts?: ResolvedPrompts, mediaPriceOf?: MediaPriceOf): string;
|
|
51
|
+
/**
|
|
52
|
+
* Compute a SURFACE-ONLY context breakdown for display, aligned with
|
|
53
|
+
* `acp_status` (kernel `buildStatusReport`/`renderOverview`).
|
|
54
|
+
*
|
|
55
|
+
* The kernel's own `computeContextBreakdown` (which the nudge text renders)
|
|
56
|
+
* walks the message array it is fed — and `buildNudge` feeds it the FULL log
|
|
57
|
+
* (`allLogMessages`, needed so T2/T3 distillation can anchor every block). So
|
|
58
|
+
* a session with compressed blocks reports HISTORICAL totals there: every
|
|
59
|
+
* original tool/text message already absorbed into a block is counted again,
|
|
60
|
+
* e.g. `85.2K tool` for ~8.5K of live tool context. `acp_status` instead feeds
|
|
61
|
+
* `buildStatusReport` the VISIBLE surface + active-block summaries, so its
|
|
62
|
+
* breakdown reads the true current context. This function reproduces that
|
|
63
|
+
* visible-surface reality for the nudge line so the two tools agree.
|
|
64
|
+
*
|
|
65
|
+
* Classification replicates kernel `computeContextBreakdown` (tool-call/
|
|
66
|
+
* tool-result → tool, `system` role → system, `` code `` fence in text →
|
|
67
|
+
* code, else text) EXCEPT summaries: kernel detects summaries by a
|
|
68
|
+
* `[Compressed conversation section]` text prefix, which never matches a DSH
|
|
69
|
+
* checkpoint node (our summary is the plain summary + `compactCheckpointSource`
|
|
70
|
+
* source marker). We instead count active-block summaries directly from kernel
|
|
71
|
+
* state (same source `buildStatusReport` uses), and the caller must exclude
|
|
72
|
+
* checkpoint summary nodes from `messages` (they are not in any block's
|
|
73
|
+
* `effectiveMessageIds` and would double-count — mirror of `/acp` status's
|
|
74
|
+
* `isCheckpointNode` exclusion).
|
|
75
|
+
*/
|
|
76
|
+
export declare function computeSurfaceBreakdown(state: CompressionState, messages: readonly CoreMessage[], total: number, growth: number): ContextBreakdown;
|
|
77
|
+
/**
|
|
78
|
+
* Max emergency nudge injections within a single user turn. Bounds the
|
|
79
|
+
* positive-feedback loop where an unrelieved ≥emergency-threshold pressure
|
|
80
|
+
* re-injects a durable emergency nudge on every pre-step forever (issue #108).
|
|
81
|
+
* Mirrors billion-context-pi commit 414acd1 (cap emergency nudge injections per
|
|
82
|
+
* user turn). Normal-pressure nudges remain limited to one per turn regardless.
|
|
46
83
|
*/
|
|
47
|
-
export declare
|
|
84
|
+
export declare const EMERGENCY_NUDGE_MAX_PER_TURN = 3;
|
|
48
85
|
/**
|
|
49
86
|
* Decide and build one nudge message for the agent's next pre-step. Returns
|
|
50
|
-
* null when the kernel recommends no nudge or
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
87
|
+
* null when the kernel recommends no nudge or the per-turn budget is spent:
|
|
88
|
+
* normal-pressure nudges fire at most once per user turn, and emergency nudges
|
|
89
|
+
* are capped at {@link EMERGENCY_NUDGE_MAX_PER_TURN} per user turn so an
|
|
90
|
+
* unrelieved ≥threshold pressure cannot re-inject a durable nudge on every
|
|
91
|
+
* pre-step forever (issue #108). Also advances the in-memory kernel state (ref
|
|
92
|
+
* assignment) so the compress tool can resolve seq → mNNNNN refs.
|
|
93
|
+
*
|
|
94
|
+
* `onEmergencyCapHit` (optional) fires when the kernel still wants an
|
|
95
|
+
* emergency nudge but the per-turn budget is spent — the host uses it to log
|
|
96
|
+
* why the model stops receiving nudges (issue #108 review).
|
|
54
97
|
*/
|
|
55
|
-
export declare function buildNudge(agent: Agent, env: NudgeEnvironment, lastNudgeTurn: Map<string, number
|
|
98
|
+
export declare function buildNudge(agent: Agent, env: NudgeEnvironment, lastNudgeTurn: Map<string, number>, emergencyNudges: Map<string, {
|
|
99
|
+
turn: number;
|
|
100
|
+
count: number;
|
|
101
|
+
}>, onEmergencyCapHit?: () => void): NudgeOutcome | null;
|
|
56
102
|
/**
|
|
57
103
|
* Render the nudge message text. DEFAULT (no `config.prompts.nudge` override)
|
|
58
104
|
* calls the kernel's own `renderNudgeText` — EFFICIENCY_NOTE/EMERGENCY_HEADER,
|
|
@@ -66,4 +112,4 @@ export declare function buildNudge(agent: Agent, env: NudgeEnvironment, lastNudg
|
|
|
66
112
|
* When a host overrides any `prompts.nudge` slot, the template path is used so
|
|
67
113
|
* `config.prompts` keeps full control (custom copy wins over kernel defaults).
|
|
68
114
|
*/
|
|
69
|
-
export declare function buildNudgeText(nudge: NudgeDecision, emergency: boolean, session: import('@deepseek-ai/dsh-session').Session, prompts?: ResolvedPrompts): string;
|
|
115
|
+
export declare function buildNudgeText(nudge: NudgeDecision, emergency: boolean, session: import('@deepseek-ai/dsh-session').Session, kernelView: KernelRangeView, prompts?: ResolvedPrompts, mediaPriceOf?: MediaPriceOf): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named presets for the nudge thresholds — how eagerly the model is asked to
|
|
3
|
+
* compress, in one word instead of three hand-tuned percentages (issue #105).
|
|
4
|
+
*
|
|
5
|
+
* A preset is a bundle of the three first-class nudge-threshold knobs
|
|
6
|
+
* (`nudgeMinContextLimitPct` / `nudgeMaxContextLimitPct` /
|
|
7
|
+
* `nudgeEmergencyThresholdPct`). It does NOT touch any other knob: `modelContextLimit`,
|
|
8
|
+
* `autoNudge`, prompts, and the `coreOverrides` escape hatch all stay exactly as
|
|
9
|
+
* configured. The individual thresholds remain fully available and win over the
|
|
10
|
+
* preset when both are set — precedence is explicit value > preset > engine
|
|
11
|
+
* default (applied in `resolveAcpConfig`, src/index.ts), so a partial override
|
|
12
|
+
* on top of a preset is honored.
|
|
13
|
+
*
|
|
14
|
+
* The five tiers form a monotonic spectrum from least to most aggressive
|
|
15
|
+
* compression. `balanced` reproduces the current out-of-the-box engine defaults
|
|
16
|
+
* exactly (min 0.45 = kernel default, max 0.70, emergency 0.85), so choosing it
|
|
17
|
+
* changes nothing relative to today's behavior.
|
|
18
|
+
*
|
|
19
|
+
* Presets are set at composition time (`config: { preset: 'efficient' }`).
|
|
20
|
+
* Runtime hot-reload of the underlying keys rides on issue #75 Phase 1
|
|
21
|
+
* (`settings.yaml` + `/acp config`); surfacing the `preset` alias through that
|
|
22
|
+
* same channel is the small follow-up once Phase 1 lands. The two knobs named in
|
|
23
|
+
* the original request that are NOT first-class engine knobs today — `growthRatio`
|
|
24
|
+
* (exists in acp-kernel as `nudge.growthRatio`, reachable via `coreOverrides`) and
|
|
25
|
+
* `protectedLastMessages` (≈ kernel `preserveRecentMessages`) — are deliberately
|
|
26
|
+
* out of scope here; adopting them as named knobs is an owner decision, not a
|
|
27
|
+
* preset detail.
|
|
28
|
+
* @module billion-context-dsh/presets
|
|
29
|
+
*/
|
|
30
|
+
/** The five preset tier names. */
|
|
31
|
+
export type PresetName = 'preserve' | 'relaxed' | 'balanced' | 'efficient' | 'aggressive';
|
|
32
|
+
/** The preset tiers ordered least → most aggressive (for help text / display). */
|
|
33
|
+
export declare const PRESET_NAMES: readonly PresetName[];
|
|
34
|
+
/** One preset tier: a human label plus the three nudge-threshold values it sets. */
|
|
35
|
+
export interface NudgePreset {
|
|
36
|
+
/** Plain-language one-liner describing the tier's trade-off. */
|
|
37
|
+
readonly label: string;
|
|
38
|
+
/** Nudge window lower bound (usage fraction; the threshold gate floor). */
|
|
39
|
+
readonly nudgeMinContextLimitPct: number;
|
|
40
|
+
/** Over-limit guarantee line — above this the nudge fires regardless of growth. */
|
|
41
|
+
readonly nudgeMaxContextLimitPct: number;
|
|
42
|
+
/** Emergency nudge threshold (bypasses the per-turn dedup). */
|
|
43
|
+
readonly nudgeEmergencyThresholdPct: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The five tiers. Every row satisfies the kernel invariant
|
|
47
|
+
* `min ≤ max ≤ emergency` (the kernel only WARNS on the reverse — it never rejects
|
|
48
|
+
* the config, so `resolveAcpConfig` rejects an inverted merged triple itself), and
|
|
49
|
+
* all three values move monotonically toward "compress sooner" as you go down
|
|
50
|
+
* the list. Values are fractions of the context window, not token counts.
|
|
51
|
+
*/
|
|
52
|
+
export declare const PRESETS: Readonly<Record<PresetName, NudgePreset>>;
|
|
53
|
+
/** Type guard: true when `value` is one of the five preset names. */
|
|
54
|
+
export declare function isPresetName(value: unknown): value is PresetName;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a preset name to its tier. Throws on an unknown name so a typo in the
|
|
57
|
+
* composition config fails engine construction loudly (the same fail-fast
|
|
58
|
+
* contract as prompt-template validation) rather than silently falling back to
|
|
59
|
+
* the engine defaults.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolvePreset(name: string): NudgePreset;
|
package/dist/prompts.d.ts
CHANGED
|
@@ -22,9 +22,9 @@ export type PromptOverride<T> = {
|
|
|
22
22
|
[K in keyof T]?: PromptInput;
|
|
23
23
|
};
|
|
24
24
|
export interface NudgePrompts {
|
|
25
|
-
/** 普通档首句。占位符:{pct}
|
|
25
|
+
/** 普通档首句。占位符:{pct}(`{philosophy}` 仍被校验器接受,但 B6 会把渲染出的哲学段摘掉——它只能经系统提示到达模型) */
|
|
26
26
|
normal: string;
|
|
27
|
-
/** 紧急档首句。占位符:{pct}
|
|
27
|
+
/** 紧急档首句。占位符:{pct}(`{philosophy}` 同上) */
|
|
28
28
|
emergency: string;
|
|
29
29
|
/** 指导行(HOW_TO_COMPRESS_RULES)。无占位符 */
|
|
30
30
|
guidance: string;
|
package/dist/region.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session';
|
|
14
14
|
import { type ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import { type AcpBlockLedgerPayload } from './block-ledger.ts';
|
|
15
16
|
/** One durable ACP block as rebuilt from the session log. */
|
|
16
17
|
export interface AcpBlockLedgerEntry {
|
|
17
18
|
/** The compaction transaction id (stable block identity). */
|
|
@@ -34,6 +35,8 @@ export interface AcpBlockLedgerEntry {
|
|
|
34
35
|
/** The kernel block's raw direct/effective message ids at creation (recorded since the tier feature; absent for legacy). */
|
|
35
36
|
readonly directMessageIds?: readonly string[];
|
|
36
37
|
readonly effectiveMessageIds?: readonly string[];
|
|
38
|
+
/** B3: acceptance readings that were already green before compression (absent when the compress call carried none). */
|
|
39
|
+
readonly verifiedReadings?: readonly string[];
|
|
37
40
|
/** Unix epoch ms of the compaction/summary event. */
|
|
38
41
|
readonly createdAt: number;
|
|
39
42
|
}
|
|
@@ -117,33 +120,32 @@ export interface CompactionTransactionInput {
|
|
|
117
120
|
/** The kernel block's direct/effective message ids (raw CoreMessage ids) — recorded for faithful rehydration. */
|
|
118
121
|
readonly directMessageIds?: readonly string[];
|
|
119
122
|
readonly effectiveMessageIds?: readonly string[];
|
|
123
|
+
/** B3:压缩前已绿的验收读数(结构化,压缩后仍可读)。 */
|
|
124
|
+
readonly verifiedReadings?: readonly string[];
|
|
120
125
|
}
|
|
126
|
+
type CompactionSummaryData = SessionEventMap['compaction/summary'];
|
|
121
127
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
128
|
+
* Read a `compaction/summary` event's data. The six ACP tier/lineage fields are
|
|
129
|
+
* no longer top-level members (issue #141): post-fix writers carry them in the
|
|
130
|
+
* admitted optional `rawOutput` member (decode via {@link decodeAcpBlockLedger}),
|
|
131
|
+
* while logs written by pre-fix engines still carry them as top-level members —
|
|
132
|
+
* so the returned type also intersects with {@link AcpBlockLedgerPayload}, letting
|
|
133
|
+
* readers fall back to the legacy shape. Never `any`.
|
|
125
134
|
*/
|
|
126
|
-
export
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
readonly directMessageIds?: readonly string[];
|
|
141
|
-
/** The kernel block's effective message ids (raw CoreMessage ids) at creation. */
|
|
142
|
-
readonly effectiveMessageIds?: readonly string[];
|
|
143
|
-
}
|
|
144
|
-
type CompactionSummaryData = SessionEventMap['compaction/summary'];
|
|
145
|
-
/** Read a `compaction/summary` event's data including the ACP tier extension fields. */
|
|
146
|
-
export declare function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpCompactionSummaryFields;
|
|
135
|
+
export declare function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpBlockLedgerPayload;
|
|
136
|
+
/**
|
|
137
|
+
* B3: read the structured verified readings a compress call recorded for this
|
|
138
|
+
* block (acceptance checks that were already green before the range was
|
|
139
|
+
* shadowed — e.g. "t0-fastpath 8/8"). Post-fix writers carry them inside the
|
|
140
|
+
* admitted `rawOutput` member (AcpBlockLedgerPayload); legacy writers put them
|
|
141
|
+
* top-level. Absent in either shape → empty array; never throws.
|
|
142
|
+
*/
|
|
143
|
+
export declare function verifiedReadingsOf(event: SessionEvent): string[];
|
|
144
|
+
/**
|
|
145
|
+
* B1:给摘要块数组的第一个文本块加标源前缀(幂等——已带前缀不重复加)。
|
|
146
|
+
* 只动文本块,工具/图片块原样保留。
|
|
147
|
+
*/
|
|
148
|
+
export declare function prefixSummaryBlocks(blocks: readonly ContentBlock[]): ContentBlock[];
|
|
147
149
|
/**
|
|
148
150
|
* Run one durable compression transaction. Throws on invalid state; on success
|
|
149
151
|
* the four events are in the log and the surface has one summary node.
|
|
@@ -162,7 +164,30 @@ export interface SeqCompressibleRange {
|
|
|
162
164
|
readonly tokens: number;
|
|
163
165
|
/** Share of messages that are tool messages (tool-call or tool-result), 0-100 — kernel `toolPct` parity. */
|
|
164
166
|
readonly toolPct: number;
|
|
167
|
+
/** Image blocks reachable inside the span (directly or through a tool result). */
|
|
168
|
+
readonly images: number;
|
|
169
|
+
/** File blocks reachable inside the span. */
|
|
170
|
+
readonly files: number;
|
|
165
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Per-seq provider-anchored price for non-text blocks (see `mediaPriceViaMeter`
|
|
174
|
+
* in host-tokens.ts). A callback, not a map, so a media-free session never pays
|
|
175
|
+
* for a meter measurement: the range walk only asks about seqs it already knows
|
|
176
|
+
* carry an image/file block.
|
|
177
|
+
*/
|
|
178
|
+
export type MediaPriceOf = (seq: number) => number;
|
|
179
|
+
/**
|
|
180
|
+
* Durable model-free prune: append `compaction/prune` as the shadow price,
|
|
181
|
+
* then replace the given surface seqs with a user message. dsh-session 0.1.5+
|
|
182
|
+
* allows only user/message (and system/message) replacements to cite source
|
|
183
|
+
* events — assistant/message FORBIDS `sourceEventSeqs` because it embeds its
|
|
184
|
+
* own provider stream — so there is no invisible replacement node anymore:
|
|
185
|
+
* every hidden span becomes a user message. Callers with meaningful text pass
|
|
186
|
+
* it (compress call/result hiding keeps the tool outcome visible to the
|
|
187
|
+
* model); callers without get the fixed prune note. The originals remain in
|
|
188
|
+
* the append-only log.
|
|
189
|
+
*/
|
|
190
|
+
export declare const PRUNE_NOTE = "(removed by context management)";
|
|
166
191
|
/**
|
|
167
192
|
* Hide one successful `compress` tool's call/result pair after its tool/result
|
|
168
193
|
* has been logged. The durable compaction summary is inserted BEFORE the
|
|
@@ -208,18 +233,92 @@ export declare function openToolCallIds(session: Session): Set<string>;
|
|
|
208
233
|
*/
|
|
209
234
|
export declare function deferCompressPairHide(session: Session, callId: string, resultSeq: number, onError?: (error: unknown) => void): void;
|
|
210
235
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
236
|
+
* Newest AGENTS.md instruction row per scope (source file). The host
|
|
237
|
+
* re-injects a file's instructions when its CURRENT copy is absent from the
|
|
238
|
+
* surface (deepseek-harness packages/context/agent-instructions presence
|
|
239
|
+
* gate, index.ts:137/:163 — presence+identity, not payload diff), so
|
|
240
|
+
* compressing the newest row of a scope makes that file come straight back,
|
|
241
|
+
* while compressing a STALE copy of the same file is silent. Live-audited
|
|
242
|
+
* shape (session-f25e4fad): EVERY injection row — baseline and worktree —
|
|
243
|
+
* carries `source.changes[].scope` = `"<dir>\u0000<file>"` (root
|
|
244
|
+
* `.\u0000AGENTS.md`, worktree `worktrees/<name>\u0000AGENTS.md`), which is
|
|
245
|
+
* stable across config tweaks unlike `baselineIdentity`. Tail-scan the log,
|
|
246
|
+
* group by scope, keep the last seq of each group. O(events), mirrors
|
|
247
|
+
* indexWatermarkOf. Rows without `changes[]` (legacy shapes) are SKIPPED
|
|
248
|
+
* entirely: identity is what the host's presence gate needs in order to
|
|
249
|
+
* re-inject a file, so a scope-less row can never come back and must not be
|
|
250
|
+
* guarded (the earlier shape gave each its own group, which made every legacy
|
|
251
|
+
* row a permanent hard-reject — issue #71 review S3).
|
|
220
252
|
*/
|
|
221
|
-
export declare function
|
|
253
|
+
export declare function newestInstructionSeqsOf(session: Session): Set<number>;
|
|
254
|
+
/**
|
|
255
|
+
* Surface seqs NO caller may compress: the CURRENT (newest) injected
|
|
256
|
+
* agent-instructions row of every scope, restricted to rows still visible on
|
|
257
|
+
* the surface (one definition of "current" — `newestInstructionSeqsOf`).
|
|
258
|
+
* `buildCompressibleSeqRanges` never OFFERS them, and both compress entry
|
|
259
|
+
* points (`handleCompress` in src/tools.ts, `/acp compress` in
|
|
260
|
+
* src/commands.ts) probe the RESOLVED span against this set and HARD-REJECT a
|
|
261
|
+
* covering range before the kernel applies it, so nothing durable lands and no
|
|
262
|
+
* phantom block can exist. This supersedes the earlier F7 draft (warn only):
|
|
263
|
+
* folding a current copy reclaims nothing — the host re-injects it — so there
|
|
264
|
+
* is no legitimate outcome to warn about. Deliberately NARROW (issue #71
|
|
265
|
+
* review F4): only CURRENT agent-instructions rows — the audited loop driver.
|
|
266
|
+
* Engine-authored metadata rows (nudge echo, compress-pair stub) stay
|
|
267
|
+
* foldable like main, and STALE copies of the same file stay compressible —
|
|
268
|
+
* removing them while the newest copy stays visible is the real cleanup.
|
|
269
|
+
*/
|
|
270
|
+
export declare function guardedSurfaceSeqsOf(session: Session): Set<number>;
|
|
271
|
+
/**
|
|
272
|
+
* The kernel's own view of what can be compressed, as the engine hands it to
|
|
273
|
+
* the range table: the geometry (`nudge.compressibleRanges`) plus the ref map
|
|
274
|
+
* that turns a kernel ref back into a surface seq (`state.messageRefs`).
|
|
275
|
+
*
|
|
276
|
+
* Structural shapes only, so the engine passes the kernel's own objects
|
|
277
|
+
* straight through and tests can hand-build a view.
|
|
278
|
+
*/
|
|
279
|
+
export interface KernelRangeView {
|
|
280
|
+
/** Kernel `recommendedRanges`/`compressibleRanges` entries (oldest first). */
|
|
281
|
+
readonly ranges: readonly {
|
|
282
|
+
readonly startRef: string;
|
|
283
|
+
readonly endRef: string;
|
|
284
|
+
}[];
|
|
285
|
+
/** Kernel ref map: `mNNNNN` → our message id (which IS the surface seq). */
|
|
286
|
+
readonly refs: {
|
|
287
|
+
readonly byRef: Readonly<Record<string, string>>;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Compressible spans in the DSH seq dialect, for the nudge range table.
|
|
292
|
+
*
|
|
293
|
+
* The GEOMETRY — which messages group into one compressible span — comes from
|
|
294
|
+
* the kernel's own ranges (design decision 7: the kernel owns the algorithm).
|
|
295
|
+
* The kernel splits a group when the next message is a user turn and the group
|
|
296
|
+
* already holds 3+ messages, and after any protected or already-compressed
|
|
297
|
+
* message, so a row reads as "roughly one stretch of work" rather than an
|
|
298
|
+
* arbitrary slice. This function only does the two jobs the kernel cannot:
|
|
299
|
+
*
|
|
300
|
+
* 1. Translate refs into surface seqs — DSH has no `<acp>` ref tags; seq is our
|
|
301
|
+
* ref (design decision 2).
|
|
302
|
+
* 2. Apply the host guards on top of the kernel's grouping: injected
|
|
303
|
+
* instruction rows split a span and the newest copy of every scope is never
|
|
304
|
+
* offered, checkpoints and the surface's system node are not compressible,
|
|
305
|
+
* and the recent tail plus the last REAL user turn stay protected (rule 16).
|
|
306
|
+
*
|
|
307
|
+
* History — why this used to compute the spans itself. A kernel range's edges
|
|
308
|
+
* were derived by counting refs, and a surface replacement breaks that
|
|
309
|
+
* arithmetic: the checkpoint node of a replace lands mid-array carrying a much
|
|
310
|
+
* higher ref, so ref order and array order diverge and the spans came back
|
|
311
|
+
* reversed (`end < start`) or lost large tool results entirely. The table was
|
|
312
|
+
* therefore self-computed from the surface, labeled `UPSTREAM:` and tracked as
|
|
313
|
+
* issue #38 (rule 11). The pinned kernel segments by ARRAY adjacency instead
|
|
314
|
+
* (upstream #207) and the drift is gone — measured on a session whose
|
|
315
|
+
* compressed span sits in the MIDDLE of the surface: every ref resolves to the
|
|
316
|
+
* right seq, no span crosses the shadowed hole, and the compressed span is
|
|
317
|
+
* excluded. Rules 3 and 11 are updated with it.
|
|
318
|
+
*/
|
|
319
|
+
export declare function buildCompressibleSeqRanges(session: Session, kernelView: KernelRangeView, opts?: {
|
|
222
320
|
preserveRecent?: number;
|
|
321
|
+
mediaPriceOf?: MediaPriceOf;
|
|
223
322
|
}): SeqCompressibleRange[];
|
|
224
323
|
/**
|
|
225
324
|
* A compact human-readable description of the current surface for the model:
|
|
@@ -280,4 +379,56 @@ export declare function summarySeqOfKernelBlock(session: Session, kernelBlockId:
|
|
|
280
379
|
* seqs. Cycle-safe (a block can never be its own ancestor).
|
|
281
380
|
*/
|
|
282
381
|
export declare function expandShadowedSeqs(session: Session, blockId: string): number[];
|
|
382
|
+
/**
|
|
383
|
+
* Default decompress page size (#112): a block shadowing hundreds of
|
|
384
|
+
* messages used to be returned whole in ONE tool result — big enough to
|
|
385
|
+
* flood the context window or get silently trimmed by the host's
|
|
386
|
+
* tool-result pruner before the model ever saw the tail. One page per call
|
|
387
|
+
* keeps every recovery usable; `offset` walks the rest.
|
|
388
|
+
*
|
|
389
|
+
* A page is bounded by BOTH this message count and a rendered-character
|
|
390
|
+
* budget ({@link DEFAULT_DECOMPRESS_PAGE_CHARS}). Count alone was not enough:
|
|
391
|
+
* the host's `dsh-compaction-tool-result-pruner` (docs/dsh-porting-analysis.md)
|
|
392
|
+
* trims by CHARACTERS (thresholdChars 8192), so a wide page of long messages
|
|
393
|
+
* still crossed that line and had its middle dropped. The char bound keeps an
|
|
394
|
+
* ordinary page under the pruner threshold so it comes back intact; the
|
|
395
|
+
* message count doubles as a hard ceiling so a pathological `limit` can't
|
|
396
|
+
* re-open the whole-block flooding half of #112.
|
|
397
|
+
*/
|
|
398
|
+
export declare const DEFAULT_DECOMPRESS_PAGE = 100;
|
|
399
|
+
/**
|
|
400
|
+
* Rendered-character budget per decompress page (#112). Kept below the host's
|
|
401
|
+
* tool-result pruner threshold (8192, docs/dsh-porting-analysis.md) with
|
|
402
|
+
* headroom for the block header, the `[seq N]` prefixes, and the continue hint,
|
|
403
|
+
* so a normal page survives intact instead of middle-trimmed. Deliberately NOT
|
|
404
|
+
* tied to acp-kernel's `config.truncate.threshold`: that knob truncates a single
|
|
405
|
+
* oversized tool output during compression, whereas the host pruner trims our
|
|
406
|
+
* whole decompress result — different mechanisms, different thresholds.
|
|
407
|
+
*/
|
|
408
|
+
export declare const DEFAULT_DECOMPRESS_PAGE_CHARS = 7000;
|
|
409
|
+
export interface DecompressPage {
|
|
410
|
+
/** Requested offset floored to >= 0; reported as-is when it lands past the end. */
|
|
411
|
+
offset: number;
|
|
412
|
+
/** Limit actually applied (clamped to [1, DEFAULT_DECOMPRESS_PAGE]). */
|
|
413
|
+
limit: number;
|
|
414
|
+
/** Total shadowed messages in the block (tier-expanded). */
|
|
415
|
+
total: number;
|
|
416
|
+
/** This page's shadowed seqs, in expansion order. */
|
|
417
|
+
seqs: number[];
|
|
418
|
+
/** True when no further page follows this one. */
|
|
419
|
+
exhausted: boolean;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Slice a block's expanded shadowed-seq list into one page. A page holds at most
|
|
423
|
+
* `limit` messages AND at most `charBudget` rendered characters, where
|
|
424
|
+
* `renderLen(seq)` reports each message's on-the-wire length (0 when it carries
|
|
425
|
+
* no text). Seqs whose original carries no text still occupy a slot, so `offset`
|
|
426
|
+
* stays a stable continuation index across calls while the log is frozen.
|
|
427
|
+
* Out-of-range / negative / non-finite values clamp instead of failing (optional
|
|
428
|
+
* convenience params, not semantic boundaries); non-numeric input falls back to
|
|
429
|
+
* the default rather than leaking NaN into the result. The first message of the
|
|
430
|
+
* page is always included even if it alone exceeds the budget, so a walk always
|
|
431
|
+
* makes progress past a single giant message.
|
|
432
|
+
*/
|
|
433
|
+
export declare function sliceDecompressPage(expanded: number[], offset: number, limit: number, charBudget: number, renderLen: (seq: number) => number): DecompressPage;
|
|
283
434
|
export {};
|
package/dist/session-events.d.ts
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-version session event access.
|
|
3
3
|
*
|
|
4
|
-
* DSH `0.1.2-alpha` replaced the public `Session.events` getter with
|
|
5
|
-
* `snapshotEvents()` / `eventAt(seq)` methods
|
|
6
|
-
* `events
|
|
7
|
-
*
|
|
4
|
+
* History: DSH `0.1.2-alpha` replaced the public `Session.events` getter with
|
|
5
|
+
* explicit `snapshotEvents()` / `eventAt(seq)` methods (only rc.6 /
|
|
6
|
+
* 0.1.1-rc.x had `events`). Both shapes are feature-detected here; since the
|
|
7
|
+
* peer floor moved to the 0.1.5 line (`>=0.1.5-alpha.1 <0.1.6-0`, issue #136),
|
|
8
|
+
* every supported seam exposes the method pair — dsh-session 0.1.5 has no
|
|
9
|
+
* public `.events` getter — so the `events` fallback is defensive robustness
|
|
10
|
+
* for foreign or stub handles, not a supported-host path.
|
|
8
11
|
*
|
|
9
|
-
* Semantics
|
|
10
|
-
* - `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `undefined`-when-absent contract.
|
|
12
|
+
* Semantics:
|
|
13
|
+
* - `snapshotEvents()` returns the current full log as a stable, cached
|
|
14
|
+
* snapshot (reused until the next append), with `seq === array index`.
|
|
15
|
+
* - indexed reads map to `eventAt(seq)` (or `events[seq]` on the legacy
|
|
16
|
+
* shape) with the same `undefined`-when-absent contract.
|
|
15
17
|
* @module billion-context-dsh/session-events
|
|
16
18
|
*/
|
|
17
19
|
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
18
20
|
/** All events of a session in log order (seq == array index). */
|
|
19
21
|
export declare function sessionEventsOf(session: Session): readonly SessionEvent[];
|
|
20
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The event at one exact seq, or undefined when the log has no such seq.
|
|
24
|
+
* Falls back through `sessionEventsOf` (snapshotEvents → events) so a
|
|
25
|
+
* snapshot-only handle stays readable; yields undefined, never throws,
|
|
26
|
+
* when a handle exposes neither read method.
|
|
27
|
+
*/
|
|
21
28
|
export declare function eventAtOf(session: Session, seq: number): SessionEvent | undefined;
|