dsh-context-compression-improved 0.2.1 → 0.3.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/.gitattributes +1 -1
- package/.github/workflows/ci.yml +1 -7
- package/CHANGELOG.ja.md +18 -21
- package/CHANGELOG.ko.md +17 -21
- package/CHANGELOG.md +16 -17
- package/CHANGELOG.zh.md +15 -18
- package/CONTRIBUTING.md +1 -1
- package/README.ja.md +1 -20
- package/README.ko.md +12 -30
- package/README.md +1 -19
- package/README.zh.md +5 -21
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/docs/compat-0.1.5-report.md +77 -0
- package/docs/installation.ja.md +6 -4
- package/docs/installation.ko.md +9 -7
- package/docs/installation.md +3 -1
- package/docs/installation.zh.md +3 -2
- package/docs/repair-log.md +54 -33
- package/eslint.config.js +2 -2
- package/package.json +56 -36
- package/packages/selector/README.md +7 -12
- package/packages/selector/README.zh.md +6 -13
- package/packages/selector/THIRD_PARTY_NOTICES.md +2 -8
- package/packages/selector/cordis.patch.yml +7 -5
- package/packages/selector/dsh.plugin.json +2 -2
- package/packages/selector/lib/client.d.ts +31 -5
- package/packages/selector/lib/client.js +100 -82
- package/packages/selector/lib/index.d.ts +8 -20
- package/packages/selector/lib/index.js +9 -14
- package/packages/selector/lib/invariant.js +4 -4
- package/packages/selector/lib/pruner.d.ts +13 -4
- package/packages/selector/lib/pruner.js +91 -46
- package/packages/selector/lib/tail-trim.js +13 -4
- package/packages/selector/package.json +23 -23
- package/packages/selector/screenshots.json +1 -4
- package/packages/selector/src/client/CompressionProfileControls.tsx +5 -8
- package/packages/selector/src/client/CompressionProfileSelector.tsx +9 -5
- package/packages/selector/src/client/EstimatorControls.tsx +28 -32
- package/packages/selector/src/client/index.ts +101 -63
- package/packages/selector/src/client/locales.ts +0 -2
- package/packages/selector/src/client/preset-options.ts +49 -35
- package/packages/selector/src/client/settings-section.tsx +4 -6
- package/packages/selector/src/deepseek-v4-tokenizer.ts +0 -5
- package/packages/selector/src/index.ts +41 -46
- package/packages/selector/src/invariant.ts +5 -5
- package/packages/selector/src/pruner.ts +67 -36
- package/packages/selector/src/runtime/measurement.ts +6 -2
- package/packages/selector/src/runtime/session-events.ts +14 -1
- package/packages/selector/src/runtime/tail-trim.ts +4 -4
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +34 -3
- package/packages/selector/src/runtime/types.ts +1 -1
- package/packages/selector/tests/auto-compact.client.spec.tsx +0 -13
- package/packages/selector/tests/built/client-artifact.spec.ts +5 -1
- package/packages/selector/tests/code-skeleton.client.spec.ts +2 -1
- package/packages/selector/tests/custom-contract.client.spec.ts +2 -1
- package/packages/selector/tests/estimator-channel.client.spec.tsx +16 -11
- package/packages/selector/tests/estimator-route-registration.host.spec.ts +12 -0
- package/packages/selector/tests/host-preset-overlay.host.spec.ts +6 -4
- package/packages/selector/tests/preset-options-write.client.spec.ts +87 -79
- package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +17 -12
- package/packages/selector/tests/preset-overlay.host.spec.ts +5 -2
- package/packages/selector/tests/profiles.client.spec.tsx +3 -3
- package/packages/selector/tests/public/package-contract.client.spec.ts +27 -3
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +103 -81
- package/packages/selector/tests/runtime/session-events.spec.ts +1 -1
- package/packages/selector/tests/settings-seat.client.spec.ts +86 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +88 -71
- package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +24 -21
- package/pnpm-workspace.yaml +52 -5
- package/scripts/capture-profile-baseline.mjs +42 -0
- package/scripts/generate-tokenizer-fixtures.py +5 -5
- package/scripts/generate-vision-fixtures.py +2 -2
- package/scripts/{packed-components-smoke.ts → packed-components-smoke.mjs} +116 -119
- package/scripts/{packed-install-e2e.ts → packed-install-e2e.mjs} +188 -160
- package/scripts/{verify-release.ts → verify-release.mjs} +70 -94
- package/tests/TEST_INVENTORY.md +6 -7
- package/scripts/capture-profile-baseline.ts +0 -80
- package/tsconfig.scripts.json +0 -13
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-profiles.jpg +0 -0
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-settings.png +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { _ as COMPRESSION_PROFILES, a as PRUNE_MARKER, c as isValidAutoCompactThresholdPercent, d as resolvePolicy, f as CustomCompressionPolicySchema, g as deepFreeze, h as assertNever, i as DEFAULTS, l as parseContextCompressionSettings, m as resolveCustomPolicy, n as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, o as codePointLength, p as DEFAULT_CUSTOM_COMPRESSION_POLICY, r as ContextCompressionSettingsSchema, s as isCompressionProfile, t as AUTO_COMPACT_THRESHOLD_LIMITS, u as resolveConfig } from "./config.js";
|
|
2
|
-
import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as
|
|
2
|
+
import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as eventBySeq, r as tailTrimRef, s as sessionEvents, t as parseTailTrimRef } from "./tail-trim.js";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { Service } from "@deepseek-ai/cordis";
|
|
6
6
|
import { createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
-
import { deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
7
|
+
import { SessionSeq, deriveEventMessage } from "@deepseek-ai/dsh-session";
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
9
|
import { Tokenizer } from "@huggingface/tokenizers";
|
|
10
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -45,11 +45,6 @@ const DEEPSEEK_VISION_TOKENIZER_ARTIFACT = Object.freeze({
|
|
|
45
45
|
* Every bundled artifact is registered with independent asset roots, integrity
|
|
46
46
|
* manifests, and cache entries: one corrupted artifact must never disable the
|
|
47
47
|
* tokenizer serving the other model family.
|
|
48
|
-
*
|
|
49
|
-
* This module deliberately sits at the package's `src/` root rather than in
|
|
50
|
-
* `src/runtime/`: the build flattens the runtime into `lib/*.js`, so only a
|
|
51
|
-
* module one level below the package root resolves `../assets/` identically
|
|
52
|
-
* from source and from the published artifact.
|
|
53
48
|
*/
|
|
54
49
|
const ARTIFACTS = Object.freeze([Object.freeze({
|
|
55
50
|
origin: DEEPSEEK_V4_TOKENIZER_ARTIFACT,
|
|
@@ -374,8 +369,9 @@ function measureForCompaction(ctx, session) {
|
|
|
374
369
|
const target = header?.config;
|
|
375
370
|
const counter = bindCounter(target?.provider, target?.model);
|
|
376
371
|
const events = sessionEvents(session);
|
|
372
|
+
const eventsBySeq = new Map(events.map((event) => [Number(event.seq), event]));
|
|
377
373
|
const measuredNodes = measurement.nodes.map((node) => {
|
|
378
|
-
const event =
|
|
374
|
+
const event = eventsBySeq.get(Number(node.seq));
|
|
379
375
|
if (event === void 0) return {
|
|
380
376
|
seq: node.seq,
|
|
381
377
|
count: unavailableTokenCount(`surface node ${String(node.seq)} is missing`)
|
|
@@ -1311,6 +1307,30 @@ var Estimator = class {
|
|
|
1311
1307
|
return;
|
|
1312
1308
|
}
|
|
1313
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Resolve the host LLM route. Explicit estimator provider/model win; with
|
|
1312
|
+
* them empty, reuse the route the harness already has configured via the
|
|
1313
|
+
* optional `agentDefaultModel` service's current selection (same seam as
|
|
1314
|
+
* dsh-prime-memory's resolveModelRoute) — the user must not re-enter a
|
|
1315
|
+
* provider/model the host already knows.
|
|
1316
|
+
*/
|
|
1317
|
+
resolveHostRoute() {
|
|
1318
|
+
const provider = this.options.estimatorProvider ?? "";
|
|
1319
|
+
const model = this.options.estimatorModel ?? "";
|
|
1320
|
+
if (provider.length > 0 && model.length > 0) return {
|
|
1321
|
+
provider,
|
|
1322
|
+
model
|
|
1323
|
+
};
|
|
1324
|
+
try {
|
|
1325
|
+
const selected = this.ctx.get("agentDefaultModel")?.currentSelection?.();
|
|
1326
|
+
const selectedProvider = selected?.provider ?? "";
|
|
1327
|
+
const selectedModel = selected?.model ?? "";
|
|
1328
|
+
if (selectedProvider.length > 0 && selectedModel.length > 0) return {
|
|
1329
|
+
provider: provider.length > 0 ? provider : selectedProvider,
|
|
1330
|
+
model: model.length > 0 ? model : selectedModel
|
|
1331
|
+
};
|
|
1332
|
+
} catch {}
|
|
1333
|
+
}
|
|
1314
1334
|
async askHost(system, user, signal) {
|
|
1315
1335
|
let llm;
|
|
1316
1336
|
try {
|
|
@@ -1319,9 +1339,10 @@ var Estimator = class {
|
|
|
1319
1339
|
return;
|
|
1320
1340
|
}
|
|
1321
1341
|
if (llm?.stream === void 0) return void 0;
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1342
|
+
const route = this.resolveHostRoute();
|
|
1343
|
+
if (route === void 0) return void 0;
|
|
1344
|
+
const provider = route.provider;
|
|
1345
|
+
const model = route.model;
|
|
1325
1346
|
let text = "";
|
|
1326
1347
|
const stream = llm.stream({
|
|
1327
1348
|
provider,
|
|
@@ -2448,6 +2469,8 @@ var ToolResultPruner = class extends Service {
|
|
|
2448
2469
|
});
|
|
2449
2470
|
/** Consolidated per-session mutable state. */
|
|
2450
2471
|
state;
|
|
2472
|
+
/** 0.1.5-specific: per-session sets of already-audited native summary seqs. */
|
|
2473
|
+
auditedNativeSummaries = /* @__PURE__ */ new WeakMap();
|
|
2451
2474
|
constructor(ctx, config = {}) {
|
|
2452
2475
|
super(ctx, "toolResultPruner");
|
|
2453
2476
|
ctx.inject(["tools", "systemPrompt"], (recoveryCtx) => {
|
|
@@ -2468,19 +2491,9 @@ var ToolResultPruner = class extends Service {
|
|
|
2468
2491
|
policyResolutionAudits: /* @__PURE__ */ new WeakMap()
|
|
2469
2492
|
};
|
|
2470
2493
|
ctx.on("session/event", (session, event) => {
|
|
2494
|
+
this.scanForSeededNativeSummary(session);
|
|
2471
2495
|
if (event.type === "compaction/summary") {
|
|
2472
|
-
|
|
2473
|
-
schemaVersion: 1,
|
|
2474
|
-
kind: "native-auto-compact",
|
|
2475
|
-
sessionId: String(session.id),
|
|
2476
|
-
manifestEventType: "compaction/summary",
|
|
2477
|
-
manifestSeq: event.seq,
|
|
2478
|
-
reducer: "llm-summary",
|
|
2479
|
-
provider: event.data.provider,
|
|
2480
|
-
model: event.data.model,
|
|
2481
|
-
tokensBefore: event.data.shadowedTokenCount,
|
|
2482
|
-
tokensAfter: null
|
|
2483
|
-
});
|
|
2496
|
+
this.emitNativeSummaryAudit(session, event.seq, event.data);
|
|
2484
2497
|
return;
|
|
2485
2498
|
}
|
|
2486
2499
|
if (event.type === "compaction/end") try {
|
|
@@ -2500,7 +2513,9 @@ var ToolResultPruner = class extends Service {
|
|
|
2500
2513
|
this.auditFailure(agent.session, "fresh", "request-boundary", error);
|
|
2501
2514
|
ctx.logger.warn("context-compression fresh pass failed open: %o", error);
|
|
2502
2515
|
}
|
|
2503
|
-
|
|
2516
|
+
const outcome = await next();
|
|
2517
|
+
this.scanForSeededNativeSummary(agent.session);
|
|
2518
|
+
return outcome;
|
|
2504
2519
|
} finally {
|
|
2505
2520
|
if (this.state.activeRequestBoundaries.get(agent.session) === boundary) this.state.activeRequestBoundaries.delete(agent.session);
|
|
2506
2521
|
}
|
|
@@ -2522,9 +2537,6 @@ var ToolResultPruner = class extends Service {
|
|
|
2522
2537
|
* @param blocks - tool-result content to measure.
|
|
2523
2538
|
* @returns total Unicode code points across text blocks.
|
|
2524
2539
|
*/
|
|
2525
|
-
measureContent(blocks) {
|
|
2526
|
-
return measureContent(blocks);
|
|
2527
|
-
}
|
|
2528
2540
|
/**
|
|
2529
2541
|
* Apply the configured native head/middle/tail transform.
|
|
2530
2542
|
* @param blocks - original tool-result content.
|
|
@@ -2542,6 +2554,7 @@ var ToolResultPruner = class extends Service {
|
|
|
2542
2554
|
* @returns landed replacements and aggregate Unicode-code-point savings.
|
|
2543
2555
|
*/
|
|
2544
2556
|
pruneSession(session, options = {}) {
|
|
2557
|
+
this.scanForSeededNativeSummary(session);
|
|
2545
2558
|
const stage = options.stage ?? "pressure";
|
|
2546
2559
|
const contextWindowTokens = options.contextWindowTokens ?? this.contextWindowForRequest(session);
|
|
2547
2560
|
const policy = this.activePolicy(session, contextWindowTokens, stage);
|
|
@@ -2651,7 +2664,7 @@ var ToolResultPruner = class extends Service {
|
|
|
2651
2664
|
const block = located.text;
|
|
2652
2665
|
let checkpointSeq;
|
|
2653
2666
|
for (const seq of [...session.surface.nodes].reverse()) {
|
|
2654
|
-
const event = events
|
|
2667
|
+
const event = eventBySeq(events, seq);
|
|
2655
2668
|
if (event === void 0 || event.type !== "user/message") continue;
|
|
2656
2669
|
const source = event.data.source;
|
|
2657
2670
|
if (source === void 0 || source === null) continue;
|
|
@@ -2678,10 +2691,10 @@ var ToolResultPruner = class extends Service {
|
|
|
2678
2691
|
session.append("user/message", replacement, {
|
|
2679
2692
|
surfaceOp: {
|
|
2680
2693
|
op: "replace",
|
|
2681
|
-
|
|
2682
|
-
|
|
2694
|
+
startSeq: SessionSeq(checkpointSeq),
|
|
2695
|
+
endSeq: SessionSeq(checkpointSeq)
|
|
2683
2696
|
},
|
|
2684
|
-
sourceEventSeqs: [checkpointSeq]
|
|
2697
|
+
sourceEventSeqs: [SessionSeq(checkpointSeq)]
|
|
2685
2698
|
});
|
|
2686
2699
|
emitCompressionAudit(this.ctx.logger, {
|
|
2687
2700
|
schemaVersion: 1,
|
|
@@ -3116,7 +3129,7 @@ var ToolResultPruner = class extends Service {
|
|
|
3116
3129
|
const measured = new Map(view.measuredNodes.map((node) => [node.seq, node.count]));
|
|
3117
3130
|
const projectionPrices = new Map(view.nodes.map((node) => [node.seq, node.tokens]));
|
|
3118
3131
|
for (const seq of [...session.surface.nodes]) {
|
|
3119
|
-
const event = events
|
|
3132
|
+
const event = eventBySeq(events, seq);
|
|
3120
3133
|
if (event?.type !== "tool/result") continue;
|
|
3121
3134
|
const shadowedHeuristicTokenCount = projectionPrices.get(seq);
|
|
3122
3135
|
if (shadowedHeuristicTokenCount === void 0) throw new Error(`surface node ${String(seq)} is absent from the atomic legacy projection`);
|
|
@@ -3441,12 +3454,12 @@ var ToolResultPruner = class extends Service {
|
|
|
3441
3454
|
const completedSteps = /* @__PURE__ */ new Set();
|
|
3442
3455
|
for (const event of events) if (event.type === "turn/end") completedTurns.add(event.data.turn);
|
|
3443
3456
|
else if (event.type === "step/end") completedSteps.add(`${String(event.data.turn)}:${String(event.data.step)}`);
|
|
3444
|
-
const firstCompletedSurfaceTurn = session.surface.nodes.map((seq) => events
|
|
3457
|
+
const firstCompletedSurfaceTurn = session.surface.nodes.map((seq) => eventBySeq(events, seq)).filter((event) => (event?.type === "assistant/message" || event?.type === "tool/result") && completedTurns.has(event.data.turn)).reduce((first, event) => first === void 0 ? event.data.turn : Math.min(first, event.data.turn), void 0);
|
|
3445
3458
|
const nodes = [...session.surface.nodes];
|
|
3446
3459
|
for (let index = 0; index < nodes.length; index++) {
|
|
3447
3460
|
const assistantSeq = nodes[index];
|
|
3448
3461
|
if (assistantSeq === void 0) continue;
|
|
3449
|
-
const assistant = events
|
|
3462
|
+
const assistant = eventBySeq(events, assistantSeq);
|
|
3450
3463
|
if (assistant?.type !== "assistant/message" || assistant.data.interrupted === true || assistant.data.message.content.length === 0 || assistant.data.message.content.some((block) => block.type !== "tool-call") || assistant.data.turn === firstCompletedSurfaceTurn || !completedTurns.has(assistant.data.turn) || !completedSteps.has(`${String(assistant.data.turn)}:${String(assistant.data.step)}`)) continue;
|
|
3451
3464
|
const calls = assistant.data.message.content;
|
|
3452
3465
|
if (calls.some((call) => call.name === "context_compression_retrieve")) continue;
|
|
@@ -3483,8 +3496,13 @@ var ToolResultPruner = class extends Service {
|
|
|
3483
3496
|
if (stubCount.kind !== "exact-tokenizer" || stubCount.tokenizerId !== surfaceCount.tokenizerId || stubCount.tokenizerRevision !== surfaceCount.tokenizerRevision || stubCount.tokens <= 0 || tokensBefore - stubCount.tokens < policy.historyMinReclaimTokens) continue;
|
|
3484
3497
|
const heuristicTokens = shadowedSeqs.reduce((sum, seq) => sum + (heuristic.get(seq) ?? 0), 0);
|
|
3485
3498
|
const range = {
|
|
3486
|
-
start: assistantSeq,
|
|
3487
|
-
end: resultSeqs.at(-1) ?? assistantSeq
|
|
3499
|
+
start: SessionSeq(assistantSeq),
|
|
3500
|
+
end: SessionSeq(resultSeqs.at(-1) ?? assistantSeq)
|
|
3501
|
+
};
|
|
3502
|
+
const surfaceRange = {
|
|
3503
|
+
op: "replace",
|
|
3504
|
+
startSeq: range.start,
|
|
3505
|
+
endSeq: range.end
|
|
3488
3506
|
};
|
|
3489
3507
|
if (!this.reserveTailTrimBoundaryAttempt(session)) {
|
|
3490
3508
|
this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "already-attempted-at-request-boundary", {
|
|
@@ -3502,10 +3520,7 @@ var ToolResultPruner = class extends Service {
|
|
|
3502
3520
|
let replacement;
|
|
3503
3521
|
try {
|
|
3504
3522
|
replacement = session.append("user/message", tailTrimMessage(stub), {
|
|
3505
|
-
surfaceOp:
|
|
3506
|
-
op: "replace",
|
|
3507
|
-
...range
|
|
3508
|
-
},
|
|
3523
|
+
surfaceOp: surfaceRange,
|
|
3509
3524
|
sourceEventSeqs: [manifest.seq, ...shadowedSeqs]
|
|
3510
3525
|
});
|
|
3511
3526
|
} catch (error) {
|
|
@@ -3619,10 +3634,10 @@ var ToolResultPruner = class extends Service {
|
|
|
3619
3634
|
});
|
|
3620
3635
|
const manifest = session.append("compaction/prune", {
|
|
3621
3636
|
shadowedRange: {
|
|
3622
|
-
start: candidate.seq,
|
|
3623
|
-
end: candidate.seq
|
|
3637
|
+
start: SessionSeq(candidate.seq),
|
|
3638
|
+
end: SessionSeq(candidate.seq)
|
|
3624
3639
|
},
|
|
3625
|
-
shadowedSeqs: [candidate.seq],
|
|
3640
|
+
shadowedSeqs: [SessionSeq(candidate.seq)],
|
|
3626
3641
|
shadowedTokenCount: candidate.shadowedHeuristicTokenCount
|
|
3627
3642
|
});
|
|
3628
3643
|
let replacement;
|
|
@@ -3633,10 +3648,10 @@ var ToolResultPruner = class extends Service {
|
|
|
3633
3648
|
}, {
|
|
3634
3649
|
surfaceOp: {
|
|
3635
3650
|
op: "replace",
|
|
3636
|
-
|
|
3637
|
-
|
|
3651
|
+
startSeq: SessionSeq(candidate.seq),
|
|
3652
|
+
endSeq: SessionSeq(candidate.seq)
|
|
3638
3653
|
},
|
|
3639
|
-
sourceEventSeqs: [candidate.seq]
|
|
3654
|
+
sourceEventSeqs: [SessionSeq(candidate.seq)]
|
|
3640
3655
|
});
|
|
3641
3656
|
} catch (error) {
|
|
3642
3657
|
this.auditPublicationFailure(session, plan.stage, plan.component, manifest.seq, error);
|
|
@@ -3763,6 +3778,36 @@ var ToolResultPruner = class extends Service {
|
|
|
3763
3778
|
...detail
|
|
3764
3779
|
});
|
|
3765
3780
|
}
|
|
3781
|
+
/** Emit the native-auto-compact audit for one summary manifest, once. */
|
|
3782
|
+
emitNativeSummaryAudit(session, manifestSeq, data) {
|
|
3783
|
+
let audited = this.auditedNativeSummaries.get(session);
|
|
3784
|
+
if (audited === void 0) {
|
|
3785
|
+
audited = /* @__PURE__ */ new Set();
|
|
3786
|
+
this.auditedNativeSummaries.set(session, audited);
|
|
3787
|
+
}
|
|
3788
|
+
if (audited.has(manifestSeq)) return;
|
|
3789
|
+
audited.add(manifestSeq);
|
|
3790
|
+
emitCompressionAudit(this.ctx.logger, {
|
|
3791
|
+
schemaVersion: 1,
|
|
3792
|
+
kind: "native-auto-compact",
|
|
3793
|
+
sessionId: String(session.id),
|
|
3794
|
+
manifestEventType: "compaction/summary",
|
|
3795
|
+
manifestSeq,
|
|
3796
|
+
reducer: "llm-summary",
|
|
3797
|
+
provider: data.provider === void 0 ? "unknown" : String(data.provider),
|
|
3798
|
+
model: data.model === void 0 ? "unknown" : String(data.model),
|
|
3799
|
+
tokensBefore: typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : null,
|
|
3800
|
+
tokensAfter: null
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3803
|
+
/**
|
|
3804
|
+
* 0.1.5 commits Native auto-compact by reopening the Session with a seed
|
|
3805
|
+
* log; seed events never reach the `session/event` firehose, so scan the
|
|
3806
|
+
* snapshot for summary manifests the live listener could not observe.
|
|
3807
|
+
*/
|
|
3808
|
+
scanForSeededNativeSummary(session) {
|
|
3809
|
+
for (const event of sessionEvents(session)) if (event.type === "compaction/summary") this.emitNativeSummaryAudit(session, event.seq, event.data);
|
|
3810
|
+
}
|
|
3766
3811
|
auditFailure(session, stage, operation, error) {
|
|
3767
3812
|
emitCompressionAudit(this.ctx.logger, {
|
|
3768
3813
|
schemaVersion: 1,
|
|
@@ -9,7 +9,16 @@ import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
|
9
9
|
*/
|
|
10
10
|
function sessionEvents(session) {
|
|
11
11
|
const snapshot = session.snapshotEvents;
|
|
12
|
-
|
|
12
|
+
if (typeof snapshot === "function") return snapshot.call(session);
|
|
13
|
+
return session.events ?? [];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve one surface-node seq to its event. 0.1.5 surface nodes are
|
|
17
|
+
* positional and may replace earlier ranges, so a node's seq is not
|
|
18
|
+
* guaranteed to equal the snapshot array index: always resolve by seq.
|
|
19
|
+
*/
|
|
20
|
+
function eventBySeq(events, seq) {
|
|
21
|
+
return events[Number(seq)];
|
|
13
22
|
}
|
|
14
23
|
//#endregion
|
|
15
24
|
//#region src/runtime/tail-trim.ts
|
|
@@ -60,9 +69,9 @@ function tailTrimMessage(stub) {
|
|
|
60
69
|
function validatePublishedTailTrim(session, manifestSeq) {
|
|
61
70
|
const events = sessionEvents(session);
|
|
62
71
|
const manifest = events[manifestSeq];
|
|
63
|
-
if (manifest?.type !== "compaction/prune" || manifest.data.shadowedSeqs.length < 2 || manifest.data.shadowedSeqs.length > MAX_ROOTS || manifest.data.shadowedSeqs[0] !== manifest.data.shadowedRange.start || manifest.data.shadowedSeqs.at(-1) !== manifest.data.shadowedRange.end || new Set(manifest.data.shadowedSeqs).size !== manifest.data.shadowedSeqs.length) return null;
|
|
72
|
+
if (manifest?.type !== "compaction/prune" || manifest.data.shadowedSeqs.length < 2 || manifest.data.shadowedSeqs.length > MAX_ROOTS || manifest.data.shadowedSeqs[0] !== Number(manifest.data.shadowedRange.start) || manifest.data.shadowedSeqs.at(-1) !== Number(manifest.data.shadowedRange.end) || new Set(manifest.data.shadowedSeqs).size !== manifest.data.shadowedSeqs.length) return null;
|
|
64
73
|
const replacement = events[manifestSeq + 1];
|
|
65
|
-
if (replacement?.type !== "user/message" || replacement.seq !== manifest.seq + 1 || replacement.data.source.kind !== "plugin" || replacement.data.source.plugin !== "dsh-context-compression-improved-runtime" || replacement.surfaceOp === void 0 || replacement.surfaceOp === "append" || replacement.surfaceOp.
|
|
74
|
+
if (replacement?.type !== "user/message" || replacement.seq !== manifest.seq + 1 || replacement.data.source.kind !== "plugin" || replacement.data.source.plugin !== "dsh-context-compression-improved-runtime" || replacement.surfaceOp === void 0 || replacement.surfaceOp === "append" || replacement.surfaceOp.startSeq !== manifest.data.shadowedRange.start || replacement.surfaceOp.endSeq !== manifest.data.shadowedRange.end || !sameNumbers(replacement.sourceEventSeqs, [manifest.seq, ...manifest.data.shadowedSeqs]) || replacement.data.content.length !== 1 || replacement.data.content[0]?.type !== "text") return null;
|
|
66
75
|
const tracedRoots = manifest.data.shadowedSeqs.map((seq) => uniqueAppendRoot(session, seq, manifestSeq));
|
|
67
76
|
if (tracedRoots.some((root) => root === null)) return null;
|
|
68
77
|
const sourceEventSeqs = tracedRoots;
|
|
@@ -133,4 +142,4 @@ function sameNumbers(left, right) {
|
|
|
133
142
|
return left !== void 0 && left.length === right.length && left.every((value, index) => value === right[index]);
|
|
134
143
|
}
|
|
135
144
|
//#endregion
|
|
136
|
-
export { validatePublishedTailTrim as a, tailTrimStub as i, tailTrimMessage as n,
|
|
145
|
+
export { validatePublishedTailTrim as a, tailTrimStub as i, tailTrimMessage as n, eventBySeq as o, tailTrimRef as r, sessionEvents as s, parseTailTrimRef as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context-compression-improved",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "One-install context-compression selector bundle for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -40,9 +40,8 @@
|
|
|
40
40
|
"client": {
|
|
41
41
|
"inject": [
|
|
42
42
|
"@deepseek-ai/dsh-client-locale",
|
|
43
|
-
"@deepseek-ai/dsh-client-
|
|
44
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
45
|
-
"@deepseek-ai/dsh-client-ui-workspace"
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
46
45
|
],
|
|
47
46
|
"platform": "web"
|
|
48
47
|
},
|
|
@@ -69,7 +68,7 @@
|
|
|
69
68
|
"author": "WilliamShi666",
|
|
70
69
|
"engines": {
|
|
71
70
|
"node": "^22.19.0 || >=24.0.0",
|
|
72
|
-
"dsh": ">=0.1.
|
|
71
|
+
"dsh": ">=0.1.5-alpha.1 <0.2.0-0"
|
|
73
72
|
},
|
|
74
73
|
"keywords": [
|
|
75
74
|
"deepseek",
|
|
@@ -91,24 +90,25 @@
|
|
|
91
90
|
"@deepseek-ai/cordis": ">=4.0.1 <5",
|
|
92
91
|
"@deepseek-ai/cordis-plugin-include": ">=1.0.6 <2",
|
|
93
92
|
"@deepseek-ai/cordis-plugin-loader": ">=1.0.2 <2",
|
|
94
|
-
"@deepseek-ai/dsh-agent": ">=0.1.
|
|
95
|
-
"@deepseek-ai/dsh-agent-presets": ">=0.1.
|
|
96
|
-
"@deepseek-ai/dsh-command-compact": ">=0.1.
|
|
97
|
-
"@deepseek-ai/dsh-compaction": ">=0.1.
|
|
98
|
-
"@deepseek-ai/dsh-compaction-basic": ">=0.1.
|
|
99
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.
|
|
100
|
-
"@deepseek-ai/dsh-client-
|
|
101
|
-
"@deepseek-ai/dsh-client-ui-
|
|
102
|
-
"@deepseek-ai/dsh-client-ui-
|
|
103
|
-
"@deepseek-ai/dsh-client-ui-
|
|
104
|
-
"@deepseek-ai/dsh-client-ui-
|
|
105
|
-
"@deepseek-ai/dsh-
|
|
106
|
-
"@deepseek-ai/dsh-
|
|
107
|
-
"@deepseek-ai/dsh-
|
|
108
|
-
"@deepseek-ai/dsh-
|
|
109
|
-
"@deepseek-ai/dsh-
|
|
110
|
-
"@deepseek-ai/dsh-
|
|
111
|
-
"@deepseek-ai/dsh-
|
|
93
|
+
"@deepseek-ai/dsh-agent": ">=0.1.5-rc.2 <0.2.0-0",
|
|
94
|
+
"@deepseek-ai/dsh-agent-presets": ">=0.1.5-rc.2 <0.2.0-0",
|
|
95
|
+
"@deepseek-ai/dsh-command-compact": ">=0.1.5-rc.2 <0.2.0-0",
|
|
96
|
+
"@deepseek-ai/dsh-compaction": ">=0.1.5-rc.2 <0.2.0-0",
|
|
97
|
+
"@deepseek-ai/dsh-compaction-basic": ">=0.1.5-rc.2 <0.2.0-0",
|
|
98
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.2 <0.2.0-0",
|
|
99
|
+
"@deepseek-ai/dsh-client-store": ">=0.1.5-rc.2 <0.2.0-0",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-session": ">=0.1.5-rc.2 <0.2.0",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-rc.2 <0.2.0-0",
|
|
102
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.2 <0.2.0-0",
|
|
103
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.2 <0.2.0-0",
|
|
104
|
+
"@deepseek-ai/dsh-client-ui-workspace": ">=0.1.5-rc.2 <0.2.0-0",
|
|
105
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.5-rc.2 <0.2.0-0",
|
|
106
|
+
"@deepseek-ai/dsh-llm": ">=0.1.5-rc.2 <0.2.0-0",
|
|
107
|
+
"@deepseek-ai/dsh-session": ">=0.1.5-rc.2 <0.2.0-0",
|
|
108
|
+
"@deepseek-ai/dsh-settings": ">=0.1.5-rc.2 <0.2.0-0",
|
|
109
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.5-rc.2 <0.2.0-0",
|
|
110
|
+
"@deepseek-ai/dsh-token-meter": ">=0.1.5-rc.2 <0.2.0-0",
|
|
111
|
+
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.2 <0.2.0-0",
|
|
112
112
|
"@deepseek-ai/schemastery": ">=3.18.1 <4",
|
|
113
113
|
"react": ">=18 <19"
|
|
114
114
|
}
|
|
@@ -22,16 +22,13 @@ import type { CompressionProfileSelectorProps } from './CompressionProfileSelect
|
|
|
22
22
|
import { CustomPolicyEditor, editableCustom } from './CustomPolicyEditor.tsx'
|
|
23
23
|
|
|
24
24
|
export function CompressionProfileControls({
|
|
25
|
-
useCompression,
|
|
25
|
+
useCompression, select, saveCustom, resetCustom, t, showCustomEditor,
|
|
26
26
|
}: CompressionProfileSelectorProps & { showCustomEditor: boolean }) {
|
|
27
27
|
const state = useCompression(snapshot => snapshot)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// A profile is Host-global. A cold Session may not have reported a live
|
|
33
|
-
// capability yet, so only Minimal blocks initial configuration.
|
|
34
|
-
const contextCompressionAvailable = currentPreset !== 'minimal'
|
|
28
|
+
// Harness 0.1.5 dropped browser-side agentPreset from session summaries; see
|
|
29
|
+
// the section component note. Only Minimal used to block initial
|
|
30
|
+
// configuration, and that signal is no longer available client-side.
|
|
31
|
+
const contextCompressionAvailable = true
|
|
35
32
|
const [open, setOpen] = useState(false)
|
|
36
33
|
const [saving, setSaving] = useState(false)
|
|
37
34
|
const [saveError, setSaveError] = useState<string | null>(null)
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* @module dsh-context-compression-improved/client/CompressionProfileSelector
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-client-
|
|
12
|
-
import type { InjectFace, PropsLocale
|
|
11
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
12
|
+
import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
|
|
13
13
|
import {
|
|
14
14
|
COMPRESSION_PROFILES,
|
|
15
15
|
isCustomCompressionPolicy,
|
|
@@ -33,8 +33,14 @@ export interface CompressionSelectorInjected {
|
|
|
33
33
|
savePresetOptions: (options: import('./preset-options.ts').PresetOptionsPatch) => Promise<void>
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Owner-agnostic panel props: the same component registers under the 0.1.5
|
|
38
|
+
* `settings.plugins.tab` card (no owner props) and the legacy
|
|
39
|
+
* `settings.section` page (whose shell supplies `close`), so the section
|
|
40
|
+
* owner share stays optional.
|
|
41
|
+
*/
|
|
36
42
|
export type CompressionProfileSelectorProps =
|
|
37
|
-
|
|
43
|
+
{ close?: () => void }
|
|
38
44
|
& PropsLocale<'context-compression'>
|
|
39
45
|
& InjectFace<CompressionSelectorInjected>
|
|
40
46
|
|
|
@@ -52,8 +58,6 @@ export {
|
|
|
52
58
|
|
|
53
59
|
export {
|
|
54
60
|
EstimatorControls,
|
|
55
|
-
EstimatorInactiveNotice,
|
|
56
|
-
ESTIMATOR_CATALOG_ROUTES,
|
|
57
61
|
} from './EstimatorControls.tsx'
|
|
58
62
|
|
|
59
63
|
export {
|
|
@@ -12,6 +12,14 @@ import type { PresetOptionsPatch } from './preset-options.ts'
|
|
|
12
12
|
import type { PresetOptionsSettings } from '../profiles.ts'
|
|
13
13
|
import css from './CompressionProfileSelector.module.css'
|
|
14
14
|
|
|
15
|
+
interface EstimatorControlsProps {
|
|
16
|
+
options: PresetOptionsSettings
|
|
17
|
+
disabled: boolean
|
|
18
|
+
save: (options: PresetOptionsPatch) => Promise<void>
|
|
19
|
+
settle: (operation: () => Promise<void>) => void
|
|
20
|
+
t: (key: ContextCompressionLocaleKey) => string
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
interface EstimatorInactiveNoticeProps {
|
|
16
24
|
/** Already-localized label of the profile that currently owns the selector. */
|
|
17
25
|
profile: string
|
|
@@ -25,6 +33,9 @@ interface EstimatorInactiveNoticeProps {
|
|
|
25
33
|
* exactly that — so keep the heading and its anchor id in place and spend them
|
|
26
34
|
* on the reason plus the profile that unlocks the card. The gate itself is
|
|
27
35
|
* unchanged: no estimator control exists outside tokenpilot-inspired.
|
|
36
|
+
*
|
|
37
|
+
* Exported from the estimator card's own module so the gated card and its
|
|
38
|
+
* explanation cannot drift apart.
|
|
28
39
|
*/
|
|
29
40
|
export function EstimatorInactiveNotice({ profile, t }: EstimatorInactiveNoticeProps) {
|
|
30
41
|
return (
|
|
@@ -35,14 +46,6 @@ export function EstimatorInactiveNotice({ profile, t }: EstimatorInactiveNoticeP
|
|
|
35
46
|
)
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
interface EstimatorControlsProps {
|
|
39
|
-
options: PresetOptionsSettings
|
|
40
|
-
disabled: boolean
|
|
41
|
-
save: (options: PresetOptionsPatch) => Promise<void>
|
|
42
|
-
settle: (operation: () => Promise<void>) => void
|
|
43
|
-
t: (key: ContextCompressionLocaleKey) => string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
49
|
interface CatalogModelEntry { readonly id: string, readonly name: string }
|
|
47
50
|
interface CatalogProviderEntry { readonly id: string, readonly name: string, readonly models: readonly CatalogModelEntry[], readonly error?: string }
|
|
48
51
|
interface EstimatorCatalogBody {
|
|
@@ -51,23 +54,18 @@ interface EstimatorCatalogBody {
|
|
|
51
54
|
readonly selection?: { readonly provider: string, readonly model: string }
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
// does not exist on this tier's hosts, so probing it first only bought a
|
|
56
|
-
// guaranteed 404 round-trip ahead of every successful load.
|
|
57
|
-
export const ESTIMATOR_CATALOG_ROUTES = [
|
|
58
|
-
'/api/dsh-context-compression-improved/estimator-catalog',
|
|
59
|
-
]
|
|
57
|
+
const ESTIMATOR_CATALOG_ROUTE = '/api/dsh-context-compression-improved/estimator-catalog'
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* TokenPilot-inspired estimator channel card. Shown only while the
|
|
63
61
|
* tokenpilot-inspired profile is selected, and split by channel:
|
|
64
62
|
*
|
|
65
|
-
* - `host` reuses the providers and credentials already configured in
|
|
66
|
-
* through the Harness `llm` service, so this card names a provider and a
|
|
63
|
+
* - `host` reuses the providers and credentials the user already configured in
|
|
64
|
+
* DSH through the Harness `llm` service, so this card names a provider and a
|
|
67
65
|
* model and accepts NO API key — the key field belongs to the direct channel
|
|
68
66
|
* alone.
|
|
69
|
-
* - `direct` talks to a native OpenAI-compatible endpoint, the only
|
|
70
|
-
*
|
|
67
|
+
* - `direct` talks to a native OpenAI-compatible endpoint, which is the only
|
|
68
|
+
* channel that carries its own base URL and write-only key.
|
|
71
69
|
*
|
|
72
70
|
* The whole card is advisory: an unconfigured or failing endpoint keeps every
|
|
73
71
|
* consumer on its rule-only fallback.
|
|
@@ -79,9 +77,11 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
|
|
|
79
77
|
const [provider, setProvider] = useState(options.estimatorProvider ?? '')
|
|
80
78
|
const mode = options.estimatorMode ?? ''
|
|
81
79
|
// Host-mode dropdown source: the live provider/model-group catalog served by
|
|
82
|
-
// the
|
|
83
|
-
//
|
|
84
|
-
//
|
|
80
|
+
// the Bundle entry's estimator-catalog route (dsh-perm-gate receiver
|
|
81
|
+
// pattern). The provider/model fields are always comboboxes: an empty
|
|
82
|
+
// catalog (route missing / llm service absent) simply offers no suggestions
|
|
83
|
+
// while custom typing keeps working, so nothing breaks on hosts without the
|
|
84
|
+
// webServer.
|
|
85
85
|
const [catalog, setCatalog] = useState<EstimatorCatalogBody | undefined>()
|
|
86
86
|
useEffect(() => {
|
|
87
87
|
if (mode !== 'host') return
|
|
@@ -89,20 +89,17 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
|
|
|
89
89
|
// The catalog route answers once the host webServer/llm services are up;
|
|
90
90
|
// poll briefly so the dropdowns fill without reopening the panel.
|
|
91
91
|
let attempts = 0
|
|
92
|
-
const load = async (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// try the next prefix
|
|
99
|
-
}
|
|
92
|
+
const load = async (): Promise<EstimatorCatalogBody | undefined> => {
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(ESTIMATOR_CATALOG_ROUTE, { headers: { 'cache-control': 'no-cache' } })
|
|
95
|
+
return response.ok ? (await response.json()) as EstimatorCatalogBody : undefined
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined
|
|
100
98
|
}
|
|
101
|
-
return undefined
|
|
102
99
|
}
|
|
103
100
|
const tick = (): void => {
|
|
104
101
|
attempts += 1
|
|
105
|
-
void load(
|
|
102
|
+
void load().then((body) => {
|
|
106
103
|
if (!alive) return
|
|
107
104
|
if (body !== undefined && (body.providers?.length ?? 0) > 0) {
|
|
108
105
|
setCatalog(body)
|
|
@@ -236,7 +233,6 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
|
|
|
236
233
|
type="text"
|
|
237
234
|
value={model}
|
|
238
235
|
disabled={disabled}
|
|
239
|
-
placeholder={t('estimator.model.placeholder')}
|
|
240
236
|
onChange={(event) => { setModel(event.currentTarget.value) }}
|
|
241
237
|
onBlur={() => { if (model !== (options.estimatorModel ?? '')) commit({ estimatorModel: model }) }}
|
|
242
238
|
/>
|