dsh-context-core 0.1.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/LICENSE +21 -0
- package/README.md +80 -0
- package/README.zh-CN.md +80 -0
- package/dist/compat/@deepseek-ai__dsh-session@0.1.7-rc.2.patch +25 -0
- package/dist/compat/DSH-LICENSE +21 -0
- package/dist/compat/README.md +28 -0
- package/dist/compat/README.zh-CN.md +28 -0
- package/dist/compat/dsh-session-source.patch +16 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/host.d.ts +30 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +185 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline.d.ts +104 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +2 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/plugin.d.ts +38 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +180 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tools/create-profile-patch.mjs +187 -0
- package/dist/transaction.d.ts +29 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +122 -0
- package/dist/transaction.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
- package/src/config.ts +18 -0
- package/src/host.ts +189 -0
- package/src/index.ts +7 -0
- package/src/pipeline.ts +114 -0
- package/src/plugin.ts +160 -0
- package/src/transaction.ts +121 -0
- package/src/types.ts +81 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** DSH append protocol, exclusion and surface validation; selection belongs to plugins. */
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { CompactionId, ManualCompactionError, compactCheckpointSource, toolPairingBalancedBefore, toolPairingBalancedAfter } from '@deepseek-ai/dsh-compaction';
|
|
5
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
6
|
+
/** Inspect current durable ownership, excluding interrupted constructor seeds. */
|
|
7
|
+
export function entryState(session) {
|
|
8
|
+
let active = false;
|
|
9
|
+
let turn = null;
|
|
10
|
+
for (const event of session.snapshotEvents()) {
|
|
11
|
+
if (event.type === 'session/end-seed') {
|
|
12
|
+
active = false;
|
|
13
|
+
turn = null;
|
|
14
|
+
}
|
|
15
|
+
if (event.type === 'compaction/start')
|
|
16
|
+
active = true;
|
|
17
|
+
if (event.type === 'compaction/end')
|
|
18
|
+
active = false;
|
|
19
|
+
if (event.type === 'turn/start')
|
|
20
|
+
turn = event.data.turn;
|
|
21
|
+
if (event.type === 'turn/end')
|
|
22
|
+
turn = null;
|
|
23
|
+
}
|
|
24
|
+
return { active, turn };
|
|
25
|
+
}
|
|
26
|
+
/** Reject competing mutation before any surface change. */
|
|
27
|
+
export function assertInactive(session) {
|
|
28
|
+
if (entryState(session).active)
|
|
29
|
+
throw new ManualCompactionError('busy', 'A context compaction is already active');
|
|
30
|
+
}
|
|
31
|
+
function validateSpan(session, seqs) {
|
|
32
|
+
const start = seqs[0];
|
|
33
|
+
const end = seqs.at(-1);
|
|
34
|
+
if (start === undefined || end === undefined)
|
|
35
|
+
throw new Error('Cannot compact an empty range');
|
|
36
|
+
const position = session.surface.nodes.indexOf(start);
|
|
37
|
+
if (position < 0 || !isDeepStrictEqual(session.surface.nodes.slice(position, position + seqs.length), [...seqs]))
|
|
38
|
+
throw new ManualCompactionError('changed', 'The selected history is no longer contiguous on the surface');
|
|
39
|
+
if (!toolPairingBalancedBefore(session, start) || !toolPairingBalancedAfter(session, end))
|
|
40
|
+
throw new Error('The selected range splits a tool exchange or unfinished step');
|
|
41
|
+
for (const seq of seqs) {
|
|
42
|
+
const event = session.eventAt(seq);
|
|
43
|
+
const message = event && session.deriveEventMessage(event);
|
|
44
|
+
if (message?.role === 'system' || message?.role === 'developer')
|
|
45
|
+
throw new Error('System and developer messages must remain outside the compacted range');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Commit the chosen range only after its summary and recovery have both succeeded. */
|
|
49
|
+
export async function compactTransaction(options) {
|
|
50
|
+
const { session, meter, entries, signal, manual, sourceCommandId } = options;
|
|
51
|
+
signal.throwIfAborted();
|
|
52
|
+
assertInactive(session);
|
|
53
|
+
const seqs = entries.map(entry => entry.seq);
|
|
54
|
+
validateSpan(session, seqs);
|
|
55
|
+
const turn = entryState(session).turn;
|
|
56
|
+
if (manual ? turn !== null : turn === null)
|
|
57
|
+
throw new ManualCompactionError('busy', manual ? 'Manual compaction requires an idle session' : 'Automatic compaction requires an open turn');
|
|
58
|
+
const before = meter.measure(session).nodes;
|
|
59
|
+
const priced = before.filter(node => seqs.includes(node.seq));
|
|
60
|
+
const shadowedRange = { start: seqs[0], end: seqs.at(-1) };
|
|
61
|
+
const shadowedTokenCount = priced.reduce((sum, node) => sum + node.heuristicTokens, 0);
|
|
62
|
+
const compactionId = CompactionId(randomUUID());
|
|
63
|
+
const lifecycle = { compactionId, turn, ...sourceCommandId === undefined ? {} : { sourceCommandId } };
|
|
64
|
+
const startEvent = session.append('compaction/start', lifecycle);
|
|
65
|
+
let closing = false;
|
|
66
|
+
let stage = 'summary';
|
|
67
|
+
try {
|
|
68
|
+
const checkpoint = await options.summarize();
|
|
69
|
+
signal.throwIfAborted();
|
|
70
|
+
validateSpan(session, seqs);
|
|
71
|
+
const after = meter.measure(session).nodes;
|
|
72
|
+
if (!isDeepStrictEqual(manual ? after.filter(node => seqs.includes(node.seq)) : after, manual ? priced : before))
|
|
73
|
+
throw new ManualCompactionError('changed', 'Session history changed during summarization');
|
|
74
|
+
const summary = [...checkpoint.beforeSummary ?? [], { type: 'text', text: checkpoint.summary }, ...checkpoint.restored ?? []];
|
|
75
|
+
const message = createUserMessage({ content: summary, source: compactCheckpointSource(compactionId, sourceCommandId) });
|
|
76
|
+
if (meter.estimateMessage(message) >= priced.reduce((sum, node) => sum + node.tokens, 0))
|
|
77
|
+
throw new Error('Summary and recovered context are not smaller than the selected history');
|
|
78
|
+
const calls = options.calls();
|
|
79
|
+
const call = calls.at(-1);
|
|
80
|
+
stage = 'commit';
|
|
81
|
+
const callRecord = calls.length === 1 && call ? { rawOutput: call.content, llmStreamCall: true } : {};
|
|
82
|
+
const summaryEvent = session.append('compaction/summary', {
|
|
83
|
+
compactionId, ...sourceCommandId === undefined ? {} : { sourceCommandId },
|
|
84
|
+
summary, shadowedRange, shadowedSeqs: seqs, shadowedTokenCount,
|
|
85
|
+
provider: call?.provider ?? options.target.provider, model: call?.model ?? options.target.model,
|
|
86
|
+
...callRecord,
|
|
87
|
+
...calls.length !== 1 || call === undefined ? {} : {
|
|
88
|
+
maxTokens: call.maxTokens,
|
|
89
|
+
...call.usage === undefined ? {} : { usage: call.usage },
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// The summary record and its replacement are adjacent in DSH's durable protocol.
|
|
93
|
+
session.append('user/message', message, {
|
|
94
|
+
surfaceOp: { op: 'replace', startSeq: shadowedRange.start, endSeq: shadowedRange.end },
|
|
95
|
+
sourceEventSeqs: [startEvent.seq, summaryEvent.seq, ...seqs],
|
|
96
|
+
});
|
|
97
|
+
closing = true;
|
|
98
|
+
const endEvent = session.append('compaction/end', lifecycle);
|
|
99
|
+
return {
|
|
100
|
+
compactionId, ...sourceCommandId === undefined ? {} : { sourceCommandId },
|
|
101
|
+
startSeq: startEvent.seq, summarySeq: summaryEvent.seq, endSeq: endEvent.seq,
|
|
102
|
+
summary, shadowedRange, shadowedSeqs: seqs, shadowedTokenCount,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
let failure = error;
|
|
107
|
+
if (!closing) {
|
|
108
|
+
try {
|
|
109
|
+
session.append('compaction/end', { ...lifecycle, error: error instanceof Error ? error.message : String(error) });
|
|
110
|
+
}
|
|
111
|
+
catch (closeError) {
|
|
112
|
+
failure = closeError;
|
|
113
|
+
stage = 'commit';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
signal.throwIfAborted();
|
|
117
|
+
if (manual && !(failure instanceof ManualCompactionError))
|
|
118
|
+
throw new ManualCompactionError(stage, failure instanceof Error ? failure.message : String(failure), { cause: failure });
|
|
119
|
+
throw failure;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=transaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Inputs shared by the source-specific budget functions. */
|
|
2
|
+
export interface BudgetInput {
|
|
3
|
+
contextWindow: number;
|
|
4
|
+
maxOutputTokens: number;
|
|
5
|
+
reserveTokens?: number;
|
|
6
|
+
thresholdRatio?: number;
|
|
7
|
+
keepRecentTokens?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Source-specific budget metadata; the pipeline owns its token estimator and retention semantics. */
|
|
10
|
+
export interface ContextBudget {
|
|
11
|
+
triggerTokens: number;
|
|
12
|
+
retainTokens: number;
|
|
13
|
+
strict?: boolean;
|
|
14
|
+
/** Minimum recent messages, subject to retainTokens when tailMode is maximum. */
|
|
15
|
+
minRecentMessages?: number;
|
|
16
|
+
tailMode?: 'minimum' | 'maximum';
|
|
17
|
+
retainLastRound?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Optional model-free removal of old textual tool results. */
|
|
20
|
+
export interface PrunePolicy {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
keepRecentTools: number;
|
|
23
|
+
protectTokens: number;
|
|
24
|
+
minSavingsTokens: number;
|
|
25
|
+
strictSavings?: boolean;
|
|
26
|
+
keepRecentTurns?: number;
|
|
27
|
+
protectedTools?: readonly string[];
|
|
28
|
+
eligibleTools?: readonly string[];
|
|
29
|
+
groupByAssistant?: boolean;
|
|
30
|
+
triggerRatio?: number;
|
|
31
|
+
triggerBufferTokens?: number;
|
|
32
|
+
}
|
|
33
|
+
/** One independently implemented adaptation of a pinned agent strategy. */
|
|
34
|
+
export interface ContextStrategy {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
/** Original project URL; revision and license describe the inspected local fork or source reference. */
|
|
38
|
+
source: {
|
|
39
|
+
url: string;
|
|
40
|
+
revision: string;
|
|
41
|
+
license: string;
|
|
42
|
+
};
|
|
43
|
+
/** What this adaptation deliberately leaves to the host or upstream product. */
|
|
44
|
+
limitations: readonly string[];
|
|
45
|
+
budget(input: BudgetInput): ContextBudget;
|
|
46
|
+
summaryInstructions: string;
|
|
47
|
+
maxSummaryTokens: number;
|
|
48
|
+
/** Maximum tool-result characters in the auxiliary summary request; zero leaves them intact. */
|
|
49
|
+
summaryToolChars: number;
|
|
50
|
+
prune: PrunePolicy;
|
|
51
|
+
}
|
|
52
|
+
/** User overrides for a strategy plugin. All token values are integers. */
|
|
53
|
+
export interface ContextConfig {
|
|
54
|
+
auto?: boolean;
|
|
55
|
+
reserveTokens?: number;
|
|
56
|
+
thresholdRatio?: number;
|
|
57
|
+
keepRecentTokens?: number;
|
|
58
|
+
maxSummaryTokens?: number;
|
|
59
|
+
summaryToolChars?: number;
|
|
60
|
+
summarizationProvider?: string;
|
|
61
|
+
summarizationModel?: string;
|
|
62
|
+
maxOverflowRetries?: number;
|
|
63
|
+
maxConsecutiveFailures?: number;
|
|
64
|
+
prune?: boolean;
|
|
65
|
+
keepRecentTools?: number;
|
|
66
|
+
protectToolTokens?: number;
|
|
67
|
+
minPruneTokens?: number;
|
|
68
|
+
screenshotTriggerImages?: number;
|
|
69
|
+
tailTurns?: number;
|
|
70
|
+
summaryRetryDelayMs?: number;
|
|
71
|
+
idleMinutes?: number;
|
|
72
|
+
toolHighWaterChars?: number;
|
|
73
|
+
toolLowWaterChars?: number;
|
|
74
|
+
maxSummaryAttempts?: number;
|
|
75
|
+
maxRestoredFiles?: number;
|
|
76
|
+
maxFileTokens?: number;
|
|
77
|
+
maxRestoreTokens?: number;
|
|
78
|
+
maxRestoredImages?: number;
|
|
79
|
+
maxSkillTokens?: number;
|
|
80
|
+
restoreContext?: boolean;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,sGAAsG;AACtG,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wGAAwG;IACxG,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,gFAAgF;IAChF,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC;IAC1C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-context-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "DSH host primitives and durable context transactions for independent agent pipelines.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/zonemeen/dsh-context-zoo.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./compat/*": "./dist/compat/*"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/**/*.js",
|
|
24
|
+
"dist/**/*.js.map",
|
|
25
|
+
"dist/**/*.d.ts",
|
|
26
|
+
"dist/**/*.d.ts.map",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README.zh-CN.md",
|
|
29
|
+
"src",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"dist/tools",
|
|
32
|
+
"dist/compat"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"yaml": "^2.8.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@deepseek-ai/dsh-agent": "0.1.7-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-commands": "0.1.7-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-compaction": "0.1.7-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "0.1.7-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-token-meter": "0.1.7-rc.2",
|
|
51
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
52
|
+
"@deepseek-ai/dsh-fs": "0.1.7-rc.2"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/zonemeen/dsh-context-zoo/tree/main/packages/core#readme",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/zonemeen/dsh-context-zoo/issues"
|
|
57
|
+
},
|
|
58
|
+
"bin": {
|
|
59
|
+
"dsh-context-patch": "./dist/tools/create-profile-patch.mjs"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ContextConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
const integerKeys = ['reserveTokens', 'keepRecentTokens', 'maxSummaryTokens', 'summaryToolChars', 'maxOverflowRetries', 'maxConsecutiveFailures', 'keepRecentTools', 'protectToolTokens', 'minPruneTokens', 'screenshotTriggerImages', 'tailTurns', 'summaryRetryDelayMs', 'idleMinutes', 'toolHighWaterChars', 'toolLowWaterChars', 'maxSummaryAttempts', 'maxRestoredFiles', 'maxFileTokens', 'maxRestoreTokens', 'maxRestoredImages', 'maxSkillTokens'] as const;
|
|
4
|
+
const keys = new Set<string>([...integerKeys, 'auto', 'thresholdRatio', 'summarizationProvider', 'summarizationModel', 'prune', 'restoreContext']);
|
|
5
|
+
|
|
6
|
+
/** Validate YAML/JSON plugin settings before registering any listeners. */
|
|
7
|
+
export function validateConfig(config: ContextConfig): void {
|
|
8
|
+
for (const key of Object.keys(config)) if (!keys.has(key)) throw new Error(`Unknown context setting: ${key}`);
|
|
9
|
+
for (const key of integerKeys) {
|
|
10
|
+
const value = config[key];
|
|
11
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`${key} must be a non-negative safe integer`);
|
|
12
|
+
}
|
|
13
|
+
for (const key of ['maxSummaryTokens', 'maxConsecutiveFailures', 'maxSummaryAttempts'] as const) if (config[key] === 0) throw new Error(`${key} must be positive`);
|
|
14
|
+
for (const key of ['auto', 'prune', 'restoreContext'] as const) if (config[key] !== undefined && typeof config[key] !== 'boolean') throw new Error(`${key} must be a boolean`);
|
|
15
|
+
if (config.thresholdRatio !== undefined && (!Number.isFinite(config.thresholdRatio) || config.thresholdRatio <= 0 || config.thresholdRatio > 1)) throw new Error('thresholdRatio must be greater than 0 and at most 1');
|
|
16
|
+
for (const key of ['summarizationProvider', 'summarizationModel'] as const) if (config[key] !== undefined && (typeof config[key] !== 'string' || config[key].trim().length === 0)) throw new Error(`${key} must be a non-empty string`);
|
|
17
|
+
if ((config.summarizationProvider === undefined) !== (config.summarizationModel === undefined)) throw new Error('Set summarizationProvider and summarizationModel together');
|
|
18
|
+
}
|
package/src/host.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/** DSH observations and transport for independently implemented context pipelines. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import type {} from '@deepseek-ai/dsh-fs';
|
|
4
|
+
import type { CommandId } from '@deepseek-ai/dsh-commands/brand';
|
|
5
|
+
import type { CompactionAgentContext } from '@deepseek-ai/dsh-compaction';
|
|
6
|
+
import { assembleAssistantStream, BlockAssembler, freezeMessage, LlmError } from '@deepseek-ai/dsh-llm';
|
|
7
|
+
import type { RequestMessage } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
9
|
+
import type { Session, SessionSeq } from '@deepseek-ai/dsh-session';
|
|
10
|
+
import type { ContextConfig } from './types.js';
|
|
11
|
+
import type { ContextEntry, ContextHost, ContextRecovery, SummaryResponse } from './pipeline.js';
|
|
12
|
+
import { assertInactive, compactTransaction } from './transaction.js';
|
|
13
|
+
|
|
14
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
15
|
+
interface SessionEventMap {
|
|
16
|
+
'context-zoo/state': { plugin: string; kind: string; data: Record<string, unknown> };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare module '@deepseek-ai/cordis' {
|
|
21
|
+
interface Events {
|
|
22
|
+
/**
|
|
23
|
+
* Supply observed external runtime state, such as an actual transcript location.
|
|
24
|
+
* @param agent - session owner requesting recovery observations.
|
|
25
|
+
* @param signal - operation cancellation.
|
|
26
|
+
* @param next - delegate when this integration has no observations.
|
|
27
|
+
* @mode waterfall
|
|
28
|
+
*/
|
|
29
|
+
'context-zoo/recovery'(agent: CompactionAgentContext, signal: AbortSignal, next: () => Promise<ContextRecovery | undefined>): Promise<ContextRecovery | undefined>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const checkedSessionClasses = new WeakSet<Function>();
|
|
34
|
+
|
|
35
|
+
function checkSessionWriter(session: Session, plugin: string): void {
|
|
36
|
+
const implementation = session.constructor as typeof Session;
|
|
37
|
+
if (checkedSessionClasses.has(implementation)) return;
|
|
38
|
+
const probe = implementation.create(SessionId('context-zoo-runtime-probe'));
|
|
39
|
+
const marker = probe.append('context-zoo/state', { plugin, kind: 'probe', data: {} }, { ignorable: true });
|
|
40
|
+
if (marker.ignorable !== true) throw new Error('The active DSH Session implementation needs the supplied ignorable-event patch; see dsh-context-core/dist/compat/README.md');
|
|
41
|
+
checkedSessionClasses.add(implementation);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Build low-level operations scoped to one run and its cancellation signal. */
|
|
45
|
+
export function createHost(ctx: Context, agent: CompactionAgentContext, id: string, config: ContextConfig, signal: AbortSignal, manual = false, sourceCommandId?: CommandId): ContextHost {
|
|
46
|
+
const session = agent.session;
|
|
47
|
+
checkSessionWriter(session, id);
|
|
48
|
+
const calls: Array<SummaryResponse | undefined> = [];
|
|
49
|
+
const target = () => {
|
|
50
|
+
const request = session.requestHeader()?.config;
|
|
51
|
+
const provider = request?.provider ?? agent.options.provider;
|
|
52
|
+
const model = request?.model ?? agent.options.model;
|
|
53
|
+
if (!provider || !model) throw new Error('Context management needs a routed provider and model');
|
|
54
|
+
return { provider, model, maxTokens: request?.maxTokens };
|
|
55
|
+
};
|
|
56
|
+
const record: ContextHost['record'] = (kind, data) => {
|
|
57
|
+
// Durable metadata must contain JSON values, including no undefined properties.
|
|
58
|
+
const durable: Record<string, unknown> = JSON.parse(JSON.stringify(data));
|
|
59
|
+
const event = session.append('context-zoo/state', { plugin: id, kind, data: durable }, { ignorable: true });
|
|
60
|
+
if (event.ignorable !== true) throw new Error('DSH Session.append needs the supplied ignorable-event patch; see dsh-context-core/dist/compat/README.md');
|
|
61
|
+
};
|
|
62
|
+
const host: ContextHost = {
|
|
63
|
+
signal,
|
|
64
|
+
record,
|
|
65
|
+
records: kind => session.snapshotEvents().flatMap(event => event.type === 'context-zoo/state' && event.data.plugin === id && event.data.kind === kind ? [event.data.data] : []),
|
|
66
|
+
async snapshot() {
|
|
67
|
+
signal.throwIfAborted();
|
|
68
|
+
const route = target();
|
|
69
|
+
const info = await ctx.llm.resolveModelInfo(route.provider, route.model, signal);
|
|
70
|
+
signal.throwIfAborted();
|
|
71
|
+
if (!info.context) throw new Error(`No context window configured for ${route.provider}/${route.model}`);
|
|
72
|
+
const recovery = await ctx.waterfall('context-zoo/recovery', agent, signal, async () => undefined);
|
|
73
|
+
signal.throwIfAborted();
|
|
74
|
+
const measurement = ctx.tokenMeter.measure(session);
|
|
75
|
+
const prices = new Map(measurement.nodes.map(node => [node.seq, node.tokens]));
|
|
76
|
+
const tools = new Map<string, { name: string; arguments: string }>();
|
|
77
|
+
let requestSeries = 'initial';
|
|
78
|
+
let turnKey: string | undefined;
|
|
79
|
+
const archive: ContextEntry[] = [];
|
|
80
|
+
for (const event of session.snapshotEvents()) {
|
|
81
|
+
if (event.type === 'turn/start') { turnKey = `turn:${event.seq}`; requestSeries = turnKey; }
|
|
82
|
+
if (event.type === 'tool/call') tools.set(event.data.callId, event.data);
|
|
83
|
+
const message = session.deriveEventMessage(event) ?? (event.type === 'system/message' || event.type === 'developer/message' ? event.data.message : null);
|
|
84
|
+
if (message === null) continue;
|
|
85
|
+
for (const block of message.content) if (block.type === 'tool-call') tools.set(block.id, { name: block.name, arguments: block.arguments });
|
|
86
|
+
const assembled = event.type === 'assistant/message' ? assembleAssistantStream(event.data.stream) : undefined;
|
|
87
|
+
if (event.type === 'assistant/message' && assembled?.finish.kind !== 'error' && assembled?.finish.kind !== 'aborted') requestSeries = `response:${event.seq}`;
|
|
88
|
+
const usage = event.type === 'assistant/message' ? event.data.usage ?? assembled?.usage : undefined;
|
|
89
|
+
const tool = message.role === 'tool' ? tools.get(message.toolCallId) : undefined;
|
|
90
|
+
archive.push({
|
|
91
|
+
seq: event.seq, message, time: event.time, tokens: prices.get(event.seq) ?? ctx.tokenMeter.estimateMessage(message),
|
|
92
|
+
...assembled === undefined ? {} : { finish: assembled.finish.kind, ...usage === undefined ? {} : { usage } },
|
|
93
|
+
...tool === undefined ? {} : { toolName: tool.name, toolArguments: tool.arguments },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const bySeq = new Map(archive.map(entry => [entry.seq, entry]));
|
|
97
|
+
const entries = session.surface.nodes.map(seq => {
|
|
98
|
+
const entry = bySeq.get(seq);
|
|
99
|
+
if (!entry) throw new Error(`Missing message for surface event ${seq}`);
|
|
100
|
+
return entry;
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
entries, archive, contextWindow: info.context.contextWindow,
|
|
104
|
+
maxOutputTokens: route.maxTokens ?? info.defaultMaxTokens ?? 32_000,
|
|
105
|
+
provider: route.provider, model: route.model, tools: session.requestHeader()?.tools ?? [],
|
|
106
|
+
measuredTokens: measurement.totalTokens, now: Date.now(), cwd: session.header.cwd ?? '', sessionId: session.id, requestSeries, ...turnKey === undefined ? {} : { turnKey },
|
|
107
|
+
...recovery === undefined ? {} : { recovery },
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
async summarize(request) {
|
|
111
|
+
signal.throwIfAborted();
|
|
112
|
+
const route = target();
|
|
113
|
+
const provider = request.provider ?? config.summarizationProvider ?? route.provider;
|
|
114
|
+
const model = request.model ?? config.summarizationModel ?? route.model;
|
|
115
|
+
const messages: RequestMessage[] = [...request.messages, { role: 'user', content: [{ type: 'text', text: request.instruction }] }];
|
|
116
|
+
const tools = request.includeTools ? session.requestHeader()?.tools : undefined;
|
|
117
|
+
const requestData = { provider, model, maxTokens: request.maxTokens, messages, ...(tools === undefined ? {} : { tools }) };
|
|
118
|
+
const callId = `${id}:${session.seq}`;
|
|
119
|
+
record('model-request', { callId, ...requestData });
|
|
120
|
+
const callIndex = calls.push(undefined) - 1;
|
|
121
|
+
try {
|
|
122
|
+
const assembler = new BlockAssembler();
|
|
123
|
+
for await (const chunk of ctx.llm.stream({ ...requestData, toolHistory: session.toolHistory(), sessionId: session.id, purpose: 'compaction', signal })) assembler.push(chunk);
|
|
124
|
+
signal.throwIfAborted();
|
|
125
|
+
const finish = assembler.finish;
|
|
126
|
+
const content = assembler.blocks();
|
|
127
|
+
const response: SummaryResponse = {
|
|
128
|
+
provider, model, maxTokens: request.maxTokens, content,
|
|
129
|
+
text: content.filter(block => block.type === 'text').map(block => block.text).join('\n'),
|
|
130
|
+
finish: finish.kind, ...assembler.usage === undefined ? {} : { usage: assembler.usage },
|
|
131
|
+
};
|
|
132
|
+
record('model-result', { callId, ...response, ...(finish.kind === 'error' || finish.kind === 'aborted' ? { failure: finish.failure } : {}) });
|
|
133
|
+
calls[callIndex] = response;
|
|
134
|
+
if (finish.kind === 'error' || finish.kind === 'aborted') throw new LlmError(finish.failure.message, finish.failure.code, finish.failure);
|
|
135
|
+
return response;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
record('model-error', { callId, message: error instanceof Error ? error.message : String(error), ...error instanceof LlmError ? { failure: error.failure } : {} });
|
|
138
|
+
signal.throwIfAborted();
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
replace(replacements) {
|
|
143
|
+
signal.throwIfAborted();
|
|
144
|
+
assertInactive(session);
|
|
145
|
+
const seen = new Set<SessionSeq>();
|
|
146
|
+
const changes = replacements.map(replacement => {
|
|
147
|
+
if (seen.has(replacement.seq)) throw new Error('Duplicate tool replacement');
|
|
148
|
+
seen.add(replacement.seq);
|
|
149
|
+
const event = session.eventAt(replacement.seq);
|
|
150
|
+
if (!session.surface.nodes.includes(replacement.seq) || (event?.type !== 'tool/result' && event?.type !== 'user/message')) throw new Error('Replacement must target a current user message or tool result');
|
|
151
|
+
const original = event.type === 'tool/result' ? event.data.message : event.data;
|
|
152
|
+
const message = freezeMessage({ ...original, content: [...replacement.content] });
|
|
153
|
+
return { event, message, price: ctx.tokenMeter.estimateMessage(original) };
|
|
154
|
+
});
|
|
155
|
+
for (const { event, message, price } of changes) {
|
|
156
|
+
session.append('compaction/prune', { shadowedRange: { start: event.seq, end: event.seq }, shadowedSeqs: [event.seq], shadowedTokenCount: price });
|
|
157
|
+
const options = { surfaceOp: { op: 'replace' as const, startSeq: event.seq, endSeq: event.seq }, sourceEventSeqs: [event.seq] };
|
|
158
|
+
if (event.type === 'tool/result' && message.role === 'tool') session.append('tool/result', { ...event.data, message }, options);
|
|
159
|
+
else if (event.type === 'user/message' && message.role === 'user') session.append('user/message', message, options);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
compact(entries, summarize) {
|
|
163
|
+
const firstCall = calls.length;
|
|
164
|
+
return compactTransaction({ session, meter: ctx.tokenMeter, entries, signal, manual, sourceCommandId, summarize, calls: () => calls.slice(firstCall), target: target() });
|
|
165
|
+
},
|
|
166
|
+
async readFile(path, maxChars) {
|
|
167
|
+
signal.throwIfAborted();
|
|
168
|
+
const fs = ctx.get('fs');
|
|
169
|
+
if (!fs) { record('file-unavailable', { path, reason: 'No DSH filesystem provider is installed' }); return null; }
|
|
170
|
+
try {
|
|
171
|
+
const target = await fs.resolve(path, { cwd: session.header.cwd, signal });
|
|
172
|
+
const chunks = await fs.streamText(target, signal);
|
|
173
|
+
let text = '';
|
|
174
|
+
for await (const chunk of chunks) {
|
|
175
|
+
signal.throwIfAborted();
|
|
176
|
+
text += chunk.slice(0, Math.max(0, maxChars - text.length));
|
|
177
|
+
if (text.length >= maxChars) break;
|
|
178
|
+
}
|
|
179
|
+
signal.throwIfAborted();
|
|
180
|
+
return text;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
signal.throwIfAborted();
|
|
183
|
+
record('file-unavailable', { path, reason: error instanceof Error ? error.message : String(error) });
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
return host;
|
|
189
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Independent agent pipelines and their DSH integration. */
|
|
2
|
+
import './host.js';
|
|
3
|
+
export type * from './types.js';
|
|
4
|
+
export { createContextPlugin, summarizeBranch } from './plugin.js';
|
|
5
|
+
export type { ContextPlugin } from './plugin.js';
|
|
6
|
+
export { validateConfig } from './config.js';
|
|
7
|
+
export type * from './pipeline.js';
|
package/src/pipeline.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ContentBlock, Message, RequestMessage, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import type { CompactionResult } from '@deepseek-ai/dsh-compaction';
|
|
3
|
+
import type { SessionSeq } from '@deepseek-ai/dsh-session';
|
|
4
|
+
import type { ContextConfig } from './types.js';
|
|
5
|
+
|
|
6
|
+
/** A durable message with the host's observations; plugins choose their own prices and interpretation. */
|
|
7
|
+
export interface ContextEntry {
|
|
8
|
+
seq: SessionSeq;
|
|
9
|
+
message: Message;
|
|
10
|
+
time: number;
|
|
11
|
+
tokens: number;
|
|
12
|
+
usage?: TokenUsage;
|
|
13
|
+
finish?: string;
|
|
14
|
+
toolName?: string;
|
|
15
|
+
toolArguments?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Immutable observations for one admitted operation. */
|
|
19
|
+
export interface ContextSnapshot {
|
|
20
|
+
entries: readonly ContextEntry[];
|
|
21
|
+
/** Full durable message history, including content replaced by earlier checkpoints. */
|
|
22
|
+
archive: readonly ContextEntry[];
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
maxOutputTokens: number;
|
|
25
|
+
inputLimit?: number;
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
tools: readonly ToolSchema[];
|
|
29
|
+
measuredTokens: number;
|
|
30
|
+
now: number;
|
|
31
|
+
/** Stable through failed retries; advances at a new turn or successful assistant response. */
|
|
32
|
+
requestSeries: string;
|
|
33
|
+
/** Stable for an entire user turn; absent for hosts without a turn lifecycle. */
|
|
34
|
+
turnKey?: string;
|
|
35
|
+
cwd: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
/** Observed host recovery pointers; absent capabilities must not be claimed. */
|
|
38
|
+
recovery?: ContextRecovery;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ContextTrigger = 'pressure' | 'manual' | 'context-overflow' | 'request-too-large';
|
|
42
|
+
|
|
43
|
+
/** Low-level auxiliary model call. Formatting, clipping, retry and output validation belong to the plugin. */
|
|
44
|
+
export interface SummaryRequest {
|
|
45
|
+
messages: readonly RequestMessage[];
|
|
46
|
+
instruction: string;
|
|
47
|
+
maxTokens: number;
|
|
48
|
+
includeTools?: boolean;
|
|
49
|
+
provider?: string;
|
|
50
|
+
model?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One settled call, preserved even when a plugin later rejects its text. */
|
|
54
|
+
export interface SummaryResponse {
|
|
55
|
+
text: string;
|
|
56
|
+
content: ContentBlock[];
|
|
57
|
+
provider: string;
|
|
58
|
+
model: string;
|
|
59
|
+
maxTokens: number;
|
|
60
|
+
usage?: TokenUsage;
|
|
61
|
+
finish?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Checkpoint and recovered context that will replace the selected history atomically. */
|
|
65
|
+
export interface Checkpoint {
|
|
66
|
+
summary: string;
|
|
67
|
+
beforeSummary?: readonly ContentBlock[];
|
|
68
|
+
restored?: readonly ContentBlock[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Explicit replacement selected by a plugin's microcompaction algorithm. */
|
|
72
|
+
export interface ContextReplacement {
|
|
73
|
+
seq: SessionSeq;
|
|
74
|
+
content: readonly ContentBlock[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Observations supplied by an installed host integration. */
|
|
78
|
+
export interface ContextRecovery {
|
|
79
|
+
approvedPlanPath?: string;
|
|
80
|
+
transcriptPath?: string;
|
|
81
|
+
wirePath?: string;
|
|
82
|
+
windowLines?: string;
|
|
83
|
+
todos?: string;
|
|
84
|
+
reminders?: readonly ContentBlock[];
|
|
85
|
+
replStateCleared?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Host primitives contain no source-agent trigger, retention, pruning or retry policy. */
|
|
89
|
+
export interface ContextHost {
|
|
90
|
+
readonly signal: AbortSignal;
|
|
91
|
+
snapshot(): Promise<ContextSnapshot>;
|
|
92
|
+
summarize(request: SummaryRequest): Promise<SummaryResponse>;
|
|
93
|
+
replace(replacements: readonly ContextReplacement[]): void;
|
|
94
|
+
compact(entries: readonly ContextEntry[], summarize: () => Promise<Checkpoint>): Promise<CompactionResult>;
|
|
95
|
+
/** Read through DSH's filesystem provider; null means absent or unavailable, never a fabricated file snapshot. */
|
|
96
|
+
readFile(path: string, maxChars: number): Promise<string | null>;
|
|
97
|
+
/** Append plugin state as an ignorable durable event, keeping replay independent of live plugin instances. */
|
|
98
|
+
record(kind: string, data: Record<string, unknown>): void;
|
|
99
|
+
records(kind: string): readonly Record<string, unknown>[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Each package owns the complete sequence of decisions and transformations. */
|
|
103
|
+
export interface ContextPipeline {
|
|
104
|
+
run(host: ContextHost, trigger: ContextTrigger): Promise<CompactionResult | null>;
|
|
105
|
+
/** Explicit range requests still use the package's summary and recovery sequence. */
|
|
106
|
+
summarizeRange(host: ContextHost, entries: readonly ContextEntry[]): Promise<Checkpoint>;
|
|
107
|
+
/** Optional native branch-navigation integration, invoked explicitly by the host. */
|
|
108
|
+
summarizeBranch?(host: ContextHost, entries: readonly ContextEntry[]): Promise<string>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface PipelineDefinition {
|
|
112
|
+
id: string;
|
|
113
|
+
create(config?: ContextConfig): ContextPipeline;
|
|
114
|
+
}
|