@afu_context/afu-context 0.0.0-stage → 0.1.7
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 +201 -0
- package/README.md +116 -2
- package/README.zh-CN.md +117 -0
- package/bin/afu.mjs +77 -0
- package/bin/cache-lease.mjs +16 -0
- package/bin/host-autoload.py +328 -0
- package/bin/install-guide.mjs +34 -0
- package/bin/install-process.mjs +19 -0
- package/bin/platform.mjs +74 -0
- package/bin/prebuilt.mjs +64 -0
- package/bin/runtime.mjs +111 -0
- package/package.json +24 -4
- package/platform-dependencies.json +15 -0
- package/platform-locks/dsh/package-lock.json +410 -0
- package/platform-locks/dsh/package.json +16 -0
- package/platform-locks/pi/package-lock.json +1789 -0
- package/platform-locks/pi/package.json +9 -0
- package/python/afu_context_local-0.1.7-py3-none-any.whl +0 -0
- package/python/constraints.txt +23 -0
- package/release.json +6 -0
- package/scripts/dsh-overlay.mjs +63 -0
- package/scripts/dsh-presets.mjs +49 -0
- package/scripts/dsh-web-probe.mjs +14 -0
- package/src/afu_host_integrations/dsh/dist/index.js +422 -0
- package/src/afu_host_integrations/pi/dist/index.js +272 -0
- package/src/afu_host_integrations/shared/runtime-client/dist/host-worker.js +168 -0
- package/src/afu_host_integrations/shared/runtime-client/dist/index.js +242 -0
- package/src/afu_host_integrations/shared/runtime-client/dist/prefix.js +12 -0
- package/src/afu_host_integrations/shared/runtime-client/dist/protocol.js +79 -0
- package/src/afu_host_integrations/shared/runtime-client/dist/telemetry.js +58 -0
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
aiohappyeyeballs==2.7.1
|
|
2
|
+
aiohttp==3.14.5
|
|
3
|
+
aiosignal==1.4.0
|
|
4
|
+
annotated-types==0.8.0
|
|
5
|
+
attrs==26.1.0
|
|
6
|
+
backports-zstd==1.8.0 ; python_full_version < '3.14'
|
|
7
|
+
certifi==2026.10.23
|
|
8
|
+
charset-normalizer==3.5.2
|
|
9
|
+
frozenlist==1.8.0
|
|
10
|
+
idna==3.20
|
|
11
|
+
multidict==7.2.1
|
|
12
|
+
propcache==0.5.4
|
|
13
|
+
pydantic==2.14.1
|
|
14
|
+
pydantic-core==2.50.1
|
|
15
|
+
regex==2026.9.29
|
|
16
|
+
requests==2.34.2
|
|
17
|
+
tiktoken==0.12.0
|
|
18
|
+
tomlkit==0.15.2
|
|
19
|
+
truststore==0.10.5
|
|
20
|
+
typing-extensions==4.16.0
|
|
21
|
+
typing-inspection==0.4.4
|
|
22
|
+
urllib3==2.8.0
|
|
23
|
+
yarl==1.25.1
|
package/release.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Compose a disposable overlay using DSH's own YAML and patch semantics. */
|
|
2
|
+
import {readFileSync, writeFileSync, realpathSync} from 'node:fs';
|
|
3
|
+
import {createRequire} from 'node:module';
|
|
4
|
+
import {join} from 'node:path';
|
|
5
|
+
import {pathToFileURL} from 'node:url';
|
|
6
|
+
import {load,dump} from 'js-yaml';
|
|
7
|
+
import {entryListSchema,applyEntryPatches} from '@deepseek-ai/cordis-plugin-include';
|
|
8
|
+
|
|
9
|
+
export function overlay(entries,module,presetModule) {
|
|
10
|
+
if (!Array.isArray(entries)) throw Error('Expected a composed DSH entry list');
|
|
11
|
+
const ids=new Set(), matches=[], presets=[];
|
|
12
|
+
function visit(rows,parent=null) {
|
|
13
|
+
for (const row of rows) {
|
|
14
|
+
if (!row || typeof row!=='object') throw Error('Invalid DSH entry');
|
|
15
|
+
if (row.id) {if(ids.has(row.id))throw Error('Ambiguous duplicate DSH entry id');ids.add(row.id);}
|
|
16
|
+
if (row.disabled) continue;
|
|
17
|
+
if (row.name==='@deepseek-ai/dsh-agent-presets') presets.push({row,parent});
|
|
18
|
+
if (row.name==='@deepseek-ai/dsh-compaction-basic') matches.push({row,parent});
|
|
19
|
+
if(row.group && Array.isArray(row.config))visit(row.config,row);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
visit(entries);
|
|
23
|
+
if (presetModule) matches.push(...presets);
|
|
24
|
+
if (!matches.length) throw Error('No active basic compaction provider in the composed profile');
|
|
25
|
+
const patches=[];
|
|
26
|
+
for (const {row,parent} of matches) {
|
|
27
|
+
if(!row.id || (parent && !parent.id))throw Error('Compaction scope must have stable entry ids');
|
|
28
|
+
const id='afu-'+row.id;
|
|
29
|
+
if(ids.has(id))throw Error('AFU entry already exists');
|
|
30
|
+
patches.push({id:row.id,name:row.name,disabled:true});
|
|
31
|
+
patches.push({...parent && {id:parent.id},insert:[{...row,id,name:row.name==='@deepseek-ai/dsh-agent-presets'?presetModule:module}]});
|
|
32
|
+
}
|
|
33
|
+
const warnings=[];applyEntryPatches(entries,patches,(x)=>warnings.push(x));
|
|
34
|
+
if(warnings.length)throw Error('DSH overlay did not match the composed tree');
|
|
35
|
+
return patches;
|
|
36
|
+
}
|
|
37
|
+
if(process.argv[1] && import.meta.url===pathToFileURL(process.argv[1]).href) {
|
|
38
|
+
try {
|
|
39
|
+
const tree=load(readFileSync(0,'utf8'),{schema:entryListSchema});
|
|
40
|
+
let presetModule;
|
|
41
|
+
if (process.argv[3] && process.argv[4]) {
|
|
42
|
+
const requireHost=createRequire(realpathSync(process.argv[4]));
|
|
43
|
+
try {
|
|
44
|
+
const native=requireHost.resolve('@deepseek-ai/dsh-agent-presets');
|
|
45
|
+
presetModule=join(process.argv[3],'presets.mjs');
|
|
46
|
+
writeFileSync(presetModule, `import Native from ${JSON.stringify(pathToFileURL(native).href)};\nimport {presetAdapter} from ${JSON.stringify(new URL('./dsh-presets.mjs',import.meta.url).href)};\nexport default presetAdapter(Native, ${JSON.stringify(process.argv[2])}, ${JSON.stringify(process.argv[3])});\n`, {mode:0o600});
|
|
47
|
+
} catch (error) { if(error.code!=='MODULE_NOT_FOUND') throw error; }
|
|
48
|
+
}
|
|
49
|
+
const patches=overlay(tree,process.argv[2],presetModule);
|
|
50
|
+
if (process.argv[5]==='web-compat') {
|
|
51
|
+
const picker=tree.find(row=>!row.disabled && row.name==='@deepseek-ai/dsh-host-directory-picker-auto');
|
|
52
|
+
if (picker) {
|
|
53
|
+
if(!picker.id) throw Error('Directory picker has no stable ID');
|
|
54
|
+
patches.push({id:picker.id,name:picker.name,disabled:true}, {insert:[
|
|
55
|
+
{id:'afu-directory-backend',name:'@deepseek-ai/dsh-host-directory-picker-browse'},
|
|
56
|
+
{id:'afu-directory-ui',name:'@deepseek-ai/dsh-client-ui-directory-picker-browse'}
|
|
57
|
+
]});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
process.stdout.write(dump(patches,{schema:entryListSchema}));
|
|
61
|
+
}
|
|
62
|
+
catch {process.stderr.write('AFU: cannot safely locate DSH compaction scopes\n');process.exitCode=2;}
|
|
63
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Adapt only mount-time preset snapshots; discovery and authoring stay native. */
|
|
2
|
+
import {cp, mkdtemp, readFile, writeFile, unlink} from 'node:fs/promises';
|
|
3
|
+
import {dirname, join} from 'node:path';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import {load, dump} from 'js-yaml';
|
|
6
|
+
import {entryListSchema, applyEntryPatches} from '@deepseek-ai/cordis-plugin-include';
|
|
7
|
+
import {overlay} from './dsh-overlay.mjs';
|
|
8
|
+
|
|
9
|
+
export function presetAdapter(Native, module, scratch) {
|
|
10
|
+
return class AfuAgentPresets extends Native {
|
|
11
|
+
snapshots = new Map();
|
|
12
|
+
async resolveMountable(id) {
|
|
13
|
+
const preset = await super.resolveMountable(id);
|
|
14
|
+
try {
|
|
15
|
+
const source = await readFile(preset.path, 'utf8');
|
|
16
|
+
// COSEC: only host-authorized local preset config; this schema preserves
|
|
17
|
+
// !!js expressions as data. AFU never evaluates config expressions.
|
|
18
|
+
const entries = load(source, {schema:entryListSchema});
|
|
19
|
+
let patches;
|
|
20
|
+
try { patches = overlay(entries, module); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.message === 'No active basic compaction provider in the composed profile') return preset;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const key = createHash('sha256').update(preset.path).update(source).digest('hex');
|
|
26
|
+
let pending = this.snapshots.get(key);
|
|
27
|
+
if (!pending) {
|
|
28
|
+
pending = (async () => {
|
|
29
|
+
// Random directory under the launcher's private temporary directory;
|
|
30
|
+
// never use a preset ID as a writable path or modify installed files.
|
|
31
|
+
const folder = await mkdtemp(join(scratch, 'preset-'));
|
|
32
|
+
await cp(dirname(preset.path), folder, {recursive:true, verbatimSymlinks:false});
|
|
33
|
+
const path = join(folder, 'agent.cordis.yml');
|
|
34
|
+
// COSEC: detach any copied symlink before writing the replacement.
|
|
35
|
+
await unlink(path);
|
|
36
|
+
await writeFile(path, dump(applyEntryPatches(entries, patches), {schema:entryListSchema}), {mode:0o600});
|
|
37
|
+
return {...preset, path};
|
|
38
|
+
})();
|
|
39
|
+
this.snapshots.set(key, pending);
|
|
40
|
+
pending.catch(() => this.snapshots.delete(key));
|
|
41
|
+
}
|
|
42
|
+
return await pending;
|
|
43
|
+
} catch {
|
|
44
|
+
this.ctx.logger.warn('AFU preset overlay unavailable; retaining native compaction');
|
|
45
|
+
return preset;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Read host capabilities without starting plugins or changing its profile. */
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import {realpathSync,readFileSync} from 'node:fs';
|
|
4
|
+
import {pathToFileURL} from 'node:url';
|
|
5
|
+
const host=createRequire(realpathSync(process.argv[2]));
|
|
6
|
+
try {
|
|
7
|
+
const boot=await import(pathToFileURL(host.resolve('@deepseek-ai/dsh-app-boot')).href);
|
|
8
|
+
boot.loadLayeredEnv('afu');
|
|
9
|
+
const dir=boot.resolveProfileDir(process.argv[3]);
|
|
10
|
+
const manifest=JSON.parse(readFileSync(new URL('package.json',pathToFileURL(dir+'/')),'utf8'));
|
|
11
|
+
let compatible=false;
|
|
12
|
+
try { const {default:Hmr}=await import(pathToFileURL(host.resolve('@deepseek-ai/cordis-plugin-hmr')).href);compatible=typeof Hmr?.prototype?.registerConfig==='function'; } catch {}
|
|
13
|
+
process.stdout.write(JSON.stringify({profileDir:dir,patchReload:manifest.dsh?.profile?.patchReload??'live',hmrCompatible:compatible}));
|
|
14
|
+
} catch { process.exitCode=2; }
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { BasicCompactionEngine, } from "@deepseek-ai/dsh-compaction-basic";
|
|
2
|
+
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { journal, sessionHash, summaryPrefix, observationId, HostCompactPool, bindingKey, estimateContentTokens, findAppliedArtifact, normalizeContent, } from "../../shared/runtime-client/dist/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* DSH-native compaction provider. Idle turns prepare Compact Artifacts off-path;
|
|
6
|
+
* the next pre-step commits only exact, still-contiguous coverage through DSH's
|
|
7
|
+
* native transaction. Missing or stale Artifacts keep DSH's default policy.
|
|
8
|
+
*/
|
|
9
|
+
export class CompactDshEngine extends BasicCompactionEngine {
|
|
10
|
+
log = journal("dsh");
|
|
11
|
+
handed = new WeakMap();
|
|
12
|
+
handoff(agent, result) {
|
|
13
|
+
const id = observationId(result);
|
|
14
|
+
const previous = this.handed.get(agent);
|
|
15
|
+
// A retried or manual compactRegion hands off again; the earlier summary
|
|
16
|
+
// was superseded rather than silently dropped.
|
|
17
|
+
if (previous && previous.id !== id)
|
|
18
|
+
this.log("summary_discarded", { session: sessionHash(agent.session.id), artifact_id: previous.id, reason: "superseded" });
|
|
19
|
+
this.handed.set(agent, { id });
|
|
20
|
+
this.log("summary_handoff", { session: sessionHash(agent.session.id), artifact_id: id });
|
|
21
|
+
}
|
|
22
|
+
finishHandoff(agent, committed) {
|
|
23
|
+
const handoff = this.handed.get(agent);
|
|
24
|
+
if (!handoff)
|
|
25
|
+
return;
|
|
26
|
+
this.handed.delete(agent);
|
|
27
|
+
this.log(committed ? "summary_applied" : "summary_discarded", { session: sessionHash(agent.session.id), artifact_id: handoff.id, ...(committed ? { phase: "host_commit" } : { reason: "host_commit_failed" }) });
|
|
28
|
+
}
|
|
29
|
+
host = new HostCompactPool();
|
|
30
|
+
bindings = new Map();
|
|
31
|
+
compactClient;
|
|
32
|
+
generation = 0;
|
|
33
|
+
idlePrecomputer;
|
|
34
|
+
selectedResults = new Map();
|
|
35
|
+
constructor(ctx, config = {}) {
|
|
36
|
+
super(ctx, { ...config, summarizationProvider: "", summarizationModel: "" });
|
|
37
|
+
this.compactClient = { compact: (request, signal) => {
|
|
38
|
+
const binding = this.bindings.get(String(request.session_id));
|
|
39
|
+
if (!binding)
|
|
40
|
+
throw new Error("No DSH model binding");
|
|
41
|
+
return this.host.compact(request, binding, signal);
|
|
42
|
+
} };
|
|
43
|
+
ctx.on("llm/adapters-updated", () => { this.generation++; });
|
|
44
|
+
this.idlePrecomputer = new DshIdlePrecomputer(this.compactClient, (sessionId, error) => {
|
|
45
|
+
this.log("adapter_error", { session: sessionHash(sessionId), reason: "idle_preparation_unavailable" });
|
|
46
|
+
ctx.logger.warn("compact idle preparation unavailable; native compaction remains active");
|
|
47
|
+
});
|
|
48
|
+
ctx.on("agent/status", async ({ agent, status }) => {
|
|
49
|
+
if (status !== "idle")
|
|
50
|
+
return;
|
|
51
|
+
this.log("request_observed", { session: sessionHash(agent.session.id), phase: "host_turn_idle" });
|
|
52
|
+
try {
|
|
53
|
+
const request = await this.boundRequest(agent, agent.session.deriveMessages(), "sync");
|
|
54
|
+
this.idlePrecomputer.schedule(request);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
ctx.logger.warn("compact host model unavailable; native compaction remains active");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
ctx.effect(() => async () => {
|
|
61
|
+
this.log("runtime_stopped");
|
|
62
|
+
// Stop scheduling first so jobs cancelled by shutdown are not reported
|
|
63
|
+
// as preparation failures, then stop the host and let them settle.
|
|
64
|
+
this.idlePrecomputer.stop();
|
|
65
|
+
await this.host.close();
|
|
66
|
+
await this.idlePrecomputer.close();
|
|
67
|
+
this.bindings.clear();
|
|
68
|
+
}, "afuContext.compact.workerClose");
|
|
69
|
+
}
|
|
70
|
+
async boundRequest(agent, messages, mode) {
|
|
71
|
+
const binding = await dshHostBinding(this.ctx, agent, () => this.generation);
|
|
72
|
+
for (const [key, old] of this.bindings) {
|
|
73
|
+
if (!await old.current()) {
|
|
74
|
+
this.bindings.delete(key);
|
|
75
|
+
this.idlePrecomputer.forget(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!this.bindings.has(binding.key) && this.bindings.size >= 16)
|
|
79
|
+
throw new Error("DSH binding limit reached");
|
|
80
|
+
this.bindings.set(binding.key, binding);
|
|
81
|
+
return dshMessagesToRequest(messages, binding.key, mode);
|
|
82
|
+
}
|
|
83
|
+
async compactIfNeeded(agent, trigger, signal) {
|
|
84
|
+
const started = performance.now();
|
|
85
|
+
try {
|
|
86
|
+
return await this.compactObserved(agent, trigger, signal);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
this.finishHandoff(agent, false);
|
|
90
|
+
this.log("host_compaction_receipt", { session: sessionHash(agent.session.id), host_compaction_ms: performance.now() - started });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async compactObserved(agent, trigger, signal) {
|
|
94
|
+
let request;
|
|
95
|
+
try {
|
|
96
|
+
request = await this.boundRequest(agent, agent.session.deriveMessages(), "active");
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return super.compactIfNeeded(agent, trigger, signal);
|
|
100
|
+
}
|
|
101
|
+
this.handed.delete(agent);
|
|
102
|
+
const sessionId = String(request.session_id);
|
|
103
|
+
const prepared = this.idlePrecomputer.latest(sessionId);
|
|
104
|
+
const range = prepared === undefined
|
|
105
|
+
? undefined
|
|
106
|
+
: dshPreparedRange(agent.session, request, prepared);
|
|
107
|
+
if (range !== undefined && prepared !== undefined) {
|
|
108
|
+
this.selectedResults.set(sessionId, prepared);
|
|
109
|
+
try {
|
|
110
|
+
const result = await this.compactRegion(range.start, range.end, agent, signal);
|
|
111
|
+
this.finishHandoff(agent, true);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
signal.throwIfAborted();
|
|
116
|
+
this.ctx.logger.warn("compact artifact unavailable; using DSH compaction policy");
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
this.selectedResults.delete(sessionId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.finishHandoff(agent, false);
|
|
123
|
+
try {
|
|
124
|
+
const result = await super.compactIfNeeded(agent, trigger, signal);
|
|
125
|
+
if (result)
|
|
126
|
+
this.finishHandoff(agent, true);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
this.finishHandoff(agent, false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async summarize(input, agent, signal) {
|
|
134
|
+
let request;
|
|
135
|
+
try {
|
|
136
|
+
request = await this.boundRequest(agent, input.messages, "sync");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Model window unknown or binding limit reached: DSH's own summarizer
|
|
140
|
+
// must still run (this path is also used by manual compactNow).
|
|
141
|
+
signal?.throwIfAborted();
|
|
142
|
+
return super.summarize(input, agent, signal);
|
|
143
|
+
}
|
|
144
|
+
const sessionId = String(request.session_id);
|
|
145
|
+
const target = dshRoute(agent);
|
|
146
|
+
const prepared = this.selectedResults.get(sessionId) ?? this.idlePrecomputer.latest(sessionId);
|
|
147
|
+
const preparedSummary = prepared === undefined
|
|
148
|
+
? undefined
|
|
149
|
+
: dshSummaryFromResult(request, prepared);
|
|
150
|
+
if (preparedSummary !== undefined) {
|
|
151
|
+
this.handoff(agent, prepared);
|
|
152
|
+
return { ...preparedSummary, provider: target.provider, model: target.model };
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const result = await this.compactClient.compact(request, signal);
|
|
156
|
+
const summary = dshSummaryFromResult(request, result);
|
|
157
|
+
if (summary !== undefined) {
|
|
158
|
+
this.handoff(agent, result);
|
|
159
|
+
return { ...summary, provider: target.provider, model: target.model };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
signal?.throwIfAborted();
|
|
164
|
+
this.ctx.logger.warn("compact worker unavailable; using DSH summarizer");
|
|
165
|
+
}
|
|
166
|
+
return super.summarize(input, agent, signal);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Resolve the host's route; credentials and provider middleware stay in ctx.llm. */
|
|
170
|
+
const dshRoutes = new WeakMap();
|
|
171
|
+
function dshRoute(agent) {
|
|
172
|
+
const latest = agent.session.requestHeader()?.config;
|
|
173
|
+
const options = { ...(agent.options.provider ? { provider: agent.options.provider } : {}), ...(agent.options.model ? { model: agent.options.model } : {}) };
|
|
174
|
+
const optionsKey = bindingKey(options), headerKey = bindingKey(latest);
|
|
175
|
+
const previous = dshRoutes.get(agent);
|
|
176
|
+
const target = !previous || previous.header !== headerKey
|
|
177
|
+
? (latest ? { provider: latest.provider, model: latest.model } : options)
|
|
178
|
+
: previous.options !== optionsKey ? options : previous.target;
|
|
179
|
+
dshRoutes.set(agent, { options: optionsKey, header: headerKey, target });
|
|
180
|
+
return target;
|
|
181
|
+
}
|
|
182
|
+
export async function dshHostBinding(ctx, agent, generation) {
|
|
183
|
+
const route = () => dshRoute(agent);
|
|
184
|
+
const target = route();
|
|
185
|
+
if (!target.provider || !target.model)
|
|
186
|
+
throw new Error("No selected DSH model");
|
|
187
|
+
const windowFor = async (selected) => {
|
|
188
|
+
if (!selected.provider || !selected.model)
|
|
189
|
+
throw new Error("DSH model unavailable");
|
|
190
|
+
const info = await ctx.llm.resolveModelInfo(selected.provider, selected.model);
|
|
191
|
+
const window = info.context?.contextWindow;
|
|
192
|
+
if (!Number.isSafeInteger(window) || !window || window <= 0)
|
|
193
|
+
throw new Error("DSH model window unavailable");
|
|
194
|
+
return window;
|
|
195
|
+
};
|
|
196
|
+
const epoch = generation();
|
|
197
|
+
const contextWindow = await windowFor(target);
|
|
198
|
+
const key = bindingKey([agent.session.id, target, epoch, contextWindow]);
|
|
199
|
+
let usage = {};
|
|
200
|
+
return {
|
|
201
|
+
key,
|
|
202
|
+
diagnostics: { host: "dsh", session: sessionHash(agent.session.id), usage: () => usage },
|
|
203
|
+
contextWindow,
|
|
204
|
+
current: async () => {
|
|
205
|
+
const selected = route();
|
|
206
|
+
if (generation() !== epoch || bindingKey(selected) !== bindingKey(target))
|
|
207
|
+
return false;
|
|
208
|
+
const window = await windowFor(selected);
|
|
209
|
+
return generation() === epoch && bindingKey(route()) === bindingKey(target) && window === contextWindow;
|
|
210
|
+
},
|
|
211
|
+
complete: async (prompt, signal, ids) => {
|
|
212
|
+
const header = agent.session.requestHeader();
|
|
213
|
+
const selected = summaryPrefix((ids?.length ? agent.session.deriveMessages() : []).map(message => ({ id: String(message.id), messages: [message] })), ids, prompt);
|
|
214
|
+
const assembler = new BlockAssembler();
|
|
215
|
+
let finished = false;
|
|
216
|
+
for await (const chunk of ctx.llm.stream({
|
|
217
|
+
provider: target.provider, model: target.model,
|
|
218
|
+
...(ids?.length ? { ...(header?.system === undefined ? {} : { system: header.system }), ...(header?.tools === undefined ? {} : { tools: [...header.tools] }) } : {}),
|
|
219
|
+
messages: [...selected.messages, createUserMessage({ content: [{ type: "text", text: selected.prompt }], source: { kind: "plugin", plugin: "afu-context" } })],
|
|
220
|
+
maxTokens: 4096, purpose: "compaction", sessionId: agent.session.id, signal,
|
|
221
|
+
})) {
|
|
222
|
+
if (chunk.type === "finish")
|
|
223
|
+
finished = true;
|
|
224
|
+
assembler.push(chunk);
|
|
225
|
+
}
|
|
226
|
+
const u = assembler.usage;
|
|
227
|
+
usage = u ? { input_tokens: u.inputTokens, output_tokens: u.outputTokens, cache_read_input_tokens: u.cacheReadTokens, cache_creation_input_tokens: u.cacheWriteTokens, cache_usage_known: u.cacheReadTokens !== undefined } : {};
|
|
228
|
+
if (!finished || assembler.finish.kind !== "stop")
|
|
229
|
+
throw new Error("DSH summary incomplete");
|
|
230
|
+
const blocks = assembler.blocks();
|
|
231
|
+
if (blocks.some(b => b.type === "tool-call"))
|
|
232
|
+
throw new Error("DSH summary called a tool");
|
|
233
|
+
return blocks.filter(b => b.type === "text").map(b => b.text).join("\n");
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function dshMessagesToRequest(messages, sessionId, mode = "active") {
|
|
238
|
+
const groups = toolCallGroups(messages);
|
|
239
|
+
const items = messages.map((message) => {
|
|
240
|
+
const content = normalizeContent(message.content);
|
|
241
|
+
const toolResult = message.content.find((block) => block.type === "tool-result");
|
|
242
|
+
const atomicGroup = toolResult?.type === "tool-result"
|
|
243
|
+
? groups.calls.get(String(toolResult.toolCallId))
|
|
244
|
+
: groups.messageGroups.get(String(message.id));
|
|
245
|
+
return {
|
|
246
|
+
schema_version: "1",
|
|
247
|
+
id: String(message.id),
|
|
248
|
+
kind: toolResult === undefined ? dshKind(message.role) : "tool",
|
|
249
|
+
content,
|
|
250
|
+
source: { schema_version: "1", provider: "dsh.surface", source_id: String(message.id) },
|
|
251
|
+
token_count: estimateContentTokens(content),
|
|
252
|
+
...(atomicGroup === undefined ? {} : { atomic_group: atomicGroup }),
|
|
253
|
+
metadata: {
|
|
254
|
+
adapter: "dsh",
|
|
255
|
+
native_role: message.role,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
const estimatedTokens = items.reduce((total, item) => total + (item.token_count ?? 0), 0);
|
|
260
|
+
return {
|
|
261
|
+
schema_version: "1",
|
|
262
|
+
session_id: sessionId,
|
|
263
|
+
token_budget: Math.max(20_000, estimatedTokens),
|
|
264
|
+
items,
|
|
265
|
+
query: "",
|
|
266
|
+
mode,
|
|
267
|
+
metadata: {
|
|
268
|
+
adapter: "dsh",
|
|
269
|
+
source_profile: {
|
|
270
|
+
representation: "text",
|
|
271
|
+
sequence_semantics: "append_only",
|
|
272
|
+
atomic_unit: "turn",
|
|
273
|
+
preserve_atomic_units: true,
|
|
274
|
+
loss_tolerance: "low",
|
|
275
|
+
},
|
|
276
|
+
runtime_version: "0.1.2-rc.1",
|
|
277
|
+
query_aware: false,
|
|
278
|
+
host_owns_compaction_transaction: true,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Resolve a prepared Artifact to the exact DSH surface span it covers.
|
|
284
|
+
* Stale, reordered, or non-contiguous coverage is rejected without mutation.
|
|
285
|
+
*/
|
|
286
|
+
export function dshPreparedRange(session, request, result) {
|
|
287
|
+
const artifact = findAppliedArtifact(result);
|
|
288
|
+
if (artifact === undefined)
|
|
289
|
+
return undefined;
|
|
290
|
+
const requestIds = request.items.map((item) => item.id);
|
|
291
|
+
const startIndex = requestIds.indexOf(artifact.replacedItemIds[0] ?? "");
|
|
292
|
+
if (startIndex < 0)
|
|
293
|
+
return undefined;
|
|
294
|
+
const coveredIds = requestIds.slice(startIndex, startIndex + artifact.replacedItemIds.length);
|
|
295
|
+
if (coveredIds.length !== artifact.replacedItemIds.length ||
|
|
296
|
+
coveredIds.some((id, index) => id !== artifact.replacedItemIds[index])) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
const seqByMessageId = new Map();
|
|
300
|
+
for (const seq of session.surface.nodes) {
|
|
301
|
+
const event = session.eventAt(seq);
|
|
302
|
+
if (event === undefined)
|
|
303
|
+
return undefined;
|
|
304
|
+
const message = session.deriveEventMessage(event);
|
|
305
|
+
if (message !== null)
|
|
306
|
+
seqByMessageId.set(String(message.id), seq);
|
|
307
|
+
}
|
|
308
|
+
const start = seqByMessageId.get(artifact.replacedItemIds[0] ?? "");
|
|
309
|
+
const end = seqByMessageId.get(artifact.replacedItemIds.at(-1) ?? "");
|
|
310
|
+
return start === undefined || end === undefined ? undefined : { start, end };
|
|
311
|
+
}
|
|
312
|
+
/** Return a DSH summary only when one Artifact covers the whole selected input. */
|
|
313
|
+
export function dshSummaryFromResult(request, result) {
|
|
314
|
+
const artifact = findAppliedArtifact(result);
|
|
315
|
+
if (artifact === undefined || typeof artifact.item.content !== "string")
|
|
316
|
+
return undefined;
|
|
317
|
+
const requestIds = request.items.map((item) => item.id);
|
|
318
|
+
if (artifact.replacedItemIds.length !== requestIds.length ||
|
|
319
|
+
artifact.replacedItemIds.some((id, index) => id !== requestIds[index])) {
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
const summary = [{ type: "text", text: artifact.item.content }];
|
|
323
|
+
return {
|
|
324
|
+
summary,
|
|
325
|
+
rawOutput: summary,
|
|
326
|
+
provider: "afu_context.compact",
|
|
327
|
+
model: "worker",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Serializes background preparation per session and keeps only completed results. */
|
|
331
|
+
export class DshIdlePrecomputer {
|
|
332
|
+
client;
|
|
333
|
+
onError;
|
|
334
|
+
pending = new Map();
|
|
335
|
+
running = new Map();
|
|
336
|
+
completed = new Map();
|
|
337
|
+
closed = false;
|
|
338
|
+
constructor(client, onError = () => undefined) {
|
|
339
|
+
this.client = client;
|
|
340
|
+
this.onError = onError;
|
|
341
|
+
}
|
|
342
|
+
schedule(request) {
|
|
343
|
+
if (this.closed || request.session_id === null || request.session_id === undefined)
|
|
344
|
+
return;
|
|
345
|
+
const sessionId = request.session_id;
|
|
346
|
+
this.pending.set(sessionId, structuredClone({ ...request, mode: "sync" }));
|
|
347
|
+
if (!this.running.has(sessionId))
|
|
348
|
+
this.start(sessionId);
|
|
349
|
+
}
|
|
350
|
+
forget(sessionId) {
|
|
351
|
+
this.pending.delete(sessionId);
|
|
352
|
+
this.completed.delete(sessionId);
|
|
353
|
+
}
|
|
354
|
+
latest(sessionId) {
|
|
355
|
+
return this.completed.get(sessionId);
|
|
356
|
+
}
|
|
357
|
+
async drain() {
|
|
358
|
+
while (this.running.size > 0) {
|
|
359
|
+
await Promise.all(this.running.values());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Stop accepting work; in-flight failures after this are expected. */
|
|
363
|
+
stop() {
|
|
364
|
+
this.closed = true;
|
|
365
|
+
this.pending.clear();
|
|
366
|
+
}
|
|
367
|
+
async close() {
|
|
368
|
+
this.stop();
|
|
369
|
+
await this.drain();
|
|
370
|
+
}
|
|
371
|
+
start(sessionId) {
|
|
372
|
+
const task = this.run(sessionId).finally(() => {
|
|
373
|
+
if (this.running.get(sessionId) === task)
|
|
374
|
+
this.running.delete(sessionId);
|
|
375
|
+
if (!this.closed && this.pending.has(sessionId))
|
|
376
|
+
this.start(sessionId);
|
|
377
|
+
});
|
|
378
|
+
this.running.set(sessionId, task);
|
|
379
|
+
}
|
|
380
|
+
async run(sessionId) {
|
|
381
|
+
while (!this.closed) {
|
|
382
|
+
const request = this.pending.get(sessionId);
|
|
383
|
+
if (request === undefined)
|
|
384
|
+
return;
|
|
385
|
+
this.pending.delete(sessionId);
|
|
386
|
+
try {
|
|
387
|
+
const result = await this.client.compact(request);
|
|
388
|
+
if (findAppliedArtifact(result) === undefined)
|
|
389
|
+
this.completed.delete(sessionId);
|
|
390
|
+
else
|
|
391
|
+
this.completed.set(sessionId, result);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
if (!this.closed)
|
|
395
|
+
this.onError(sessionId, error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function dshKind(role) {
|
|
401
|
+
if (role === "assistant")
|
|
402
|
+
return "assistant";
|
|
403
|
+
if (role === "system")
|
|
404
|
+
return "system";
|
|
405
|
+
return "user";
|
|
406
|
+
}
|
|
407
|
+
function toolCallGroups(messages) {
|
|
408
|
+
const calls = new Map();
|
|
409
|
+
const messageGroups = new Map();
|
|
410
|
+
for (const message of messages) {
|
|
411
|
+
const callIds = message.content.flatMap((block) => block.type === "tool-call" ? [String(block.id)] : []);
|
|
412
|
+
if (callIds.length === 0)
|
|
413
|
+
continue;
|
|
414
|
+
const group = `dsh-tool-batch:${String(message.id)}`;
|
|
415
|
+
messageGroups.set(String(message.id), group);
|
|
416
|
+
for (const callId of callIds)
|
|
417
|
+
calls.set(callId, group);
|
|
418
|
+
}
|
|
419
|
+
return { calls, messageGroups };
|
|
420
|
+
}
|
|
421
|
+
export default CompactDshEngine;
|
|
422
|
+
//# sourceMappingURL=index.js.map
|