@liwu1995/afu-context 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "afu-pi-runtime",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "dependencies": {
7
+ "@earendil-works/pi-coding-agent": "0.84.4"
8
+ }
9
+ }
@@ -0,0 +1,22 @@
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
+ certifi==2026.10.23
7
+ charset-normalizer==3.5.2
8
+ frozenlist==1.8.0
9
+ idna==3.20
10
+ multidict==7.2.1
11
+ propcache==0.5.4
12
+ pydantic==2.14.1
13
+ pydantic-core==2.50.1
14
+ regex==2026.9.29
15
+ requests==2.34.2
16
+ tiktoken==0.12.0
17
+ tomlkit==0.15.2
18
+ truststore==0.10.5
19
+ typing-extensions==4.16.0
20
+ typing-inspection==0.4.4
21
+ urllib3==2.8.0
22
+ yarl==1.25.1
package/release.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": "0.1.0",
3
+ "build": "edb9a28f8ba42bc4b9fa4858fa5438e90570cc442b79c8bc82c6b9bfd5fa3d66",
4
+ "wheel": "afu_context_local-0.1.0-py3-none-any.whl",
5
+ "prebuilt": true
6
+ }
@@ -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,400 @@
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
+ this.handed.set(agent, { id });
15
+ this.log("summary_handoff", { session: sessionHash(agent.session.id), artifact_id: id });
16
+ }
17
+ finishHandoff(agent, committed) {
18
+ const handoff = this.handed.get(agent);
19
+ if (!handoff)
20
+ return;
21
+ this.handed.delete(agent);
22
+ this.log(committed ? "summary_applied" : "summary_discarded", { session: sessionHash(agent.session.id), artifact_id: handoff.id, ...(committed ? { phase: "host_commit" } : { reason: "host_commit_failed" }) });
23
+ }
24
+ host = new HostCompactPool();
25
+ bindings = new Map();
26
+ compactClient;
27
+ generation = 0;
28
+ idlePrecomputer;
29
+ selectedResults = new Map();
30
+ constructor(ctx, config = {}) {
31
+ super(ctx, { ...config, summarizationProvider: "", summarizationModel: "" });
32
+ this.compactClient = { compact: (request, signal) => {
33
+ const binding = this.bindings.get(String(request.session_id));
34
+ if (!binding)
35
+ throw new Error("No DSH model binding");
36
+ return this.host.compact(request, binding, signal);
37
+ } };
38
+ ctx.on("llm/adapters-updated", () => { this.generation++; });
39
+ this.idlePrecomputer = new DshIdlePrecomputer(this.compactClient, (sessionId, error) => {
40
+ this.log("adapter_error", { session: sessionHash(sessionId), reason: "idle_preparation_unavailable" });
41
+ ctx.logger.warn("compact idle preparation unavailable; native compaction remains active");
42
+ });
43
+ ctx.on("agent/status", async ({ agent, status }) => {
44
+ if (status !== "idle")
45
+ return;
46
+ this.log("request_observed", { session: sessionHash(agent.session.id), phase: "host_turn_idle" });
47
+ try {
48
+ const request = await this.boundRequest(agent, agent.session.deriveMessages(), "sync");
49
+ this.idlePrecomputer.schedule(request);
50
+ }
51
+ catch {
52
+ ctx.logger.warn("compact host model unavailable; native compaction remains active");
53
+ }
54
+ });
55
+ ctx.effect(() => async () => {
56
+ this.log("runtime_stopped");
57
+ await this.host.close();
58
+ await this.idlePrecomputer.close();
59
+ this.bindings.clear();
60
+ }, "afuContext.compact.workerClose");
61
+ }
62
+ async boundRequest(agent, messages, mode) {
63
+ const binding = await dshHostBinding(this.ctx, agent, () => this.generation);
64
+ for (const [key, old] of this.bindings) {
65
+ if (!await old.current()) {
66
+ this.bindings.delete(key);
67
+ this.idlePrecomputer.forget(key);
68
+ }
69
+ }
70
+ if (!this.bindings.has(binding.key) && this.bindings.size >= 16)
71
+ throw new Error("DSH binding limit reached");
72
+ this.bindings.set(binding.key, binding);
73
+ return dshMessagesToRequest(messages, binding.key, mode);
74
+ }
75
+ async compactIfNeeded(agent, trigger, signal) {
76
+ const started = performance.now();
77
+ try {
78
+ return await this.compactObserved(agent, trigger, signal);
79
+ }
80
+ finally {
81
+ this.finishHandoff(agent, false);
82
+ this.log("host_compaction_receipt", { session: sessionHash(agent.session.id), host_compaction_ms: performance.now() - started });
83
+ }
84
+ }
85
+ async compactObserved(agent, trigger, signal) {
86
+ let request;
87
+ try {
88
+ request = await this.boundRequest(agent, agent.session.deriveMessages(), "active");
89
+ }
90
+ catch {
91
+ return super.compactIfNeeded(agent, trigger, signal);
92
+ }
93
+ this.handed.delete(agent);
94
+ const sessionId = String(request.session_id);
95
+ const prepared = this.idlePrecomputer.latest(sessionId);
96
+ const range = prepared === undefined
97
+ ? undefined
98
+ : dshPreparedRange(agent.session, request, prepared);
99
+ if (range !== undefined && prepared !== undefined) {
100
+ this.selectedResults.set(sessionId, prepared);
101
+ try {
102
+ const result = await this.compactRegion(range.start, range.end, agent, signal);
103
+ this.finishHandoff(agent, true);
104
+ return result;
105
+ }
106
+ catch (error) {
107
+ signal.throwIfAborted();
108
+ this.ctx.logger.warn("compact artifact unavailable; using DSH compaction policy");
109
+ }
110
+ finally {
111
+ this.selectedResults.delete(sessionId);
112
+ }
113
+ }
114
+ this.finishHandoff(agent, false);
115
+ try {
116
+ const result = await super.compactIfNeeded(agent, trigger, signal);
117
+ if (result)
118
+ this.finishHandoff(agent, true);
119
+ return result;
120
+ }
121
+ finally {
122
+ this.finishHandoff(agent, false);
123
+ }
124
+ }
125
+ async summarize(input, agent, signal) {
126
+ const request = await this.boundRequest(agent, input.messages, "sync");
127
+ const sessionId = String(request.session_id);
128
+ const target = dshRoute(agent);
129
+ const prepared = this.selectedResults.get(sessionId) ?? this.idlePrecomputer.latest(sessionId);
130
+ const preparedSummary = prepared === undefined
131
+ ? undefined
132
+ : dshSummaryFromResult(request, prepared);
133
+ if (preparedSummary !== undefined) {
134
+ this.handoff(agent, prepared);
135
+ return { ...preparedSummary, provider: target.provider, model: target.model };
136
+ }
137
+ try {
138
+ const result = await this.compactClient.compact(request, signal);
139
+ const summary = dshSummaryFromResult(request, result);
140
+ if (summary !== undefined) {
141
+ this.handoff(agent, result);
142
+ return { ...summary, provider: target.provider, model: target.model };
143
+ }
144
+ }
145
+ catch (error) {
146
+ signal?.throwIfAborted();
147
+ this.ctx.logger.warn("compact worker unavailable; using DSH summarizer");
148
+ }
149
+ return super.summarize(input, agent, signal);
150
+ }
151
+ }
152
+ /** Resolve the host's route; credentials and provider middleware stay in ctx.llm. */
153
+ const dshRoutes = new WeakMap();
154
+ function dshRoute(agent) {
155
+ const latest = agent.session.requestHeader()?.config;
156
+ const options = { ...(agent.options.provider ? { provider: agent.options.provider } : {}), ...(agent.options.model ? { model: agent.options.model } : {}) };
157
+ const optionsKey = bindingKey(options), headerKey = bindingKey(latest);
158
+ const previous = dshRoutes.get(agent);
159
+ const target = !previous || previous.header !== headerKey
160
+ ? (latest ? { provider: latest.provider, model: latest.model } : options)
161
+ : previous.options !== optionsKey ? options : previous.target;
162
+ dshRoutes.set(agent, { options: optionsKey, header: headerKey, target });
163
+ return target;
164
+ }
165
+ export async function dshHostBinding(ctx, agent, generation) {
166
+ const route = () => dshRoute(agent);
167
+ const target = route();
168
+ if (!target.provider || !target.model)
169
+ throw new Error("No selected DSH model");
170
+ const windowFor = async (selected) => {
171
+ if (!selected.provider || !selected.model)
172
+ throw new Error("DSH model unavailable");
173
+ const info = await ctx.llm.resolveModelInfo(selected.provider, selected.model);
174
+ const window = info.context?.contextWindow;
175
+ if (!Number.isSafeInteger(window) || !window || window <= 0)
176
+ throw new Error("DSH model window unavailable");
177
+ return window;
178
+ };
179
+ const epoch = generation();
180
+ const contextWindow = await windowFor(target);
181
+ const key = bindingKey([agent.session.id, target, epoch, contextWindow]);
182
+ let usage = {};
183
+ return {
184
+ key,
185
+ diagnostics: { host: "dsh", session: sessionHash(agent.session.id), usage: () => usage },
186
+ contextWindow,
187
+ current: async () => {
188
+ const selected = route();
189
+ if (generation() !== epoch || bindingKey(selected) !== bindingKey(target))
190
+ return false;
191
+ const window = await windowFor(selected);
192
+ return generation() === epoch && bindingKey(route()) === bindingKey(target) && window === contextWindow;
193
+ },
194
+ complete: async (prompt, signal, ids) => {
195
+ const header = agent.session.requestHeader();
196
+ const selected = summaryPrefix((ids?.length ? agent.session.deriveMessages() : []).map(message => ({ id: String(message.id), messages: [message] })), ids, prompt);
197
+ const assembler = new BlockAssembler();
198
+ let finished = false;
199
+ for await (const chunk of ctx.llm.stream({
200
+ provider: target.provider, model: target.model,
201
+ ...(ids?.length ? { ...(header?.system === undefined ? {} : { system: header.system }), ...(header?.tools === undefined ? {} : { tools: [...header.tools] }) } : {}),
202
+ messages: [...selected.messages, createUserMessage({ content: [{ type: "text", text: selected.prompt }], source: { kind: "plugin", plugin: "afu-context" } })],
203
+ maxTokens: 4096, purpose: "compaction", sessionId: agent.session.id, signal,
204
+ })) {
205
+ if (chunk.type === "finish")
206
+ finished = true;
207
+ assembler.push(chunk);
208
+ }
209
+ const u = assembler.usage;
210
+ 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 } : {};
211
+ if (!finished || assembler.finish.kind !== "stop")
212
+ throw new Error("DSH summary incomplete");
213
+ const blocks = assembler.blocks();
214
+ if (blocks.some(b => b.type === "tool-call"))
215
+ throw new Error("DSH summary called a tool");
216
+ return blocks.filter(b => b.type === "text").map(b => b.text).join("\n");
217
+ },
218
+ };
219
+ }
220
+ export function dshMessagesToRequest(messages, sessionId, mode = "active") {
221
+ const groups = toolCallGroups(messages);
222
+ const items = messages.map((message) => {
223
+ const content = normalizeContent(message.content);
224
+ const toolResult = message.content.find((block) => block.type === "tool-result");
225
+ const atomicGroup = toolResult?.type === "tool-result"
226
+ ? groups.calls.get(String(toolResult.toolCallId))
227
+ : groups.messageGroups.get(String(message.id));
228
+ return {
229
+ schema_version: "1",
230
+ id: String(message.id),
231
+ kind: toolResult === undefined ? dshKind(message.role) : "tool",
232
+ content,
233
+ source: { schema_version: "1", provider: "dsh.surface", source_id: String(message.id) },
234
+ token_count: estimateContentTokens(content),
235
+ ...(atomicGroup === undefined ? {} : { atomic_group: atomicGroup }),
236
+ metadata: {
237
+ adapter: "dsh",
238
+ native_role: message.role,
239
+ },
240
+ };
241
+ });
242
+ const estimatedTokens = items.reduce((total, item) => total + (item.token_count ?? 0), 0);
243
+ return {
244
+ schema_version: "1",
245
+ session_id: sessionId,
246
+ token_budget: Math.max(20_000, estimatedTokens),
247
+ items,
248
+ query: "",
249
+ mode,
250
+ metadata: {
251
+ adapter: "dsh",
252
+ source_profile: {
253
+ representation: "text",
254
+ sequence_semantics: "append_only",
255
+ atomic_unit: "turn",
256
+ preserve_atomic_units: true,
257
+ loss_tolerance: "low",
258
+ },
259
+ runtime_version: "0.1.2-rc.1",
260
+ query_aware: false,
261
+ host_owns_compaction_transaction: true,
262
+ },
263
+ };
264
+ }
265
+ /**
266
+ * Resolve a prepared Artifact to the exact DSH surface span it covers.
267
+ * Stale, reordered, or non-contiguous coverage is rejected without mutation.
268
+ */
269
+ export function dshPreparedRange(session, request, result) {
270
+ const artifact = findAppliedArtifact(result);
271
+ if (artifact === undefined)
272
+ return undefined;
273
+ const requestIds = request.items.map((item) => item.id);
274
+ const startIndex = requestIds.indexOf(artifact.replacedItemIds[0] ?? "");
275
+ if (startIndex < 0)
276
+ return undefined;
277
+ const coveredIds = requestIds.slice(startIndex, startIndex + artifact.replacedItemIds.length);
278
+ if (coveredIds.length !== artifact.replacedItemIds.length ||
279
+ coveredIds.some((id, index) => id !== artifact.replacedItemIds[index])) {
280
+ return undefined;
281
+ }
282
+ const seqByMessageId = new Map();
283
+ for (const seq of session.surface.nodes) {
284
+ const event = session.eventAt(seq);
285
+ if (event === undefined)
286
+ return undefined;
287
+ const message = session.deriveEventMessage(event);
288
+ if (message !== null)
289
+ seqByMessageId.set(String(message.id), seq);
290
+ }
291
+ const start = seqByMessageId.get(artifact.replacedItemIds[0] ?? "");
292
+ const end = seqByMessageId.get(artifact.replacedItemIds.at(-1) ?? "");
293
+ return start === undefined || end === undefined ? undefined : { start, end };
294
+ }
295
+ /** Return a DSH summary only when one Artifact covers the whole selected input. */
296
+ export function dshSummaryFromResult(request, result) {
297
+ const artifact = findAppliedArtifact(result);
298
+ if (artifact === undefined || typeof artifact.item.content !== "string")
299
+ return undefined;
300
+ const requestIds = request.items.map((item) => item.id);
301
+ if (artifact.replacedItemIds.length !== requestIds.length ||
302
+ artifact.replacedItemIds.some((id, index) => id !== requestIds[index])) {
303
+ return undefined;
304
+ }
305
+ const summary = [{ type: "text", text: artifact.item.content }];
306
+ return {
307
+ summary,
308
+ rawOutput: summary,
309
+ provider: "afu_context.compact",
310
+ model: "worker",
311
+ };
312
+ }
313
+ /** Serializes background preparation per session and keeps only completed results. */
314
+ export class DshIdlePrecomputer {
315
+ client;
316
+ onError;
317
+ pending = new Map();
318
+ running = new Map();
319
+ completed = new Map();
320
+ closed = false;
321
+ constructor(client, onError = () => undefined) {
322
+ this.client = client;
323
+ this.onError = onError;
324
+ }
325
+ schedule(request) {
326
+ if (this.closed || request.session_id === null || request.session_id === undefined)
327
+ return;
328
+ const sessionId = request.session_id;
329
+ this.pending.set(sessionId, structuredClone({ ...request, mode: "sync" }));
330
+ if (!this.running.has(sessionId))
331
+ this.start(sessionId);
332
+ }
333
+ forget(sessionId) {
334
+ this.pending.delete(sessionId);
335
+ this.completed.delete(sessionId);
336
+ }
337
+ latest(sessionId) {
338
+ return this.completed.get(sessionId);
339
+ }
340
+ async drain() {
341
+ while (this.running.size > 0) {
342
+ await Promise.all(this.running.values());
343
+ }
344
+ }
345
+ async close() {
346
+ this.closed = true;
347
+ this.pending.clear();
348
+ await this.drain();
349
+ }
350
+ start(sessionId) {
351
+ const task = this.run(sessionId).finally(() => {
352
+ if (this.running.get(sessionId) === task)
353
+ this.running.delete(sessionId);
354
+ if (!this.closed && this.pending.has(sessionId))
355
+ this.start(sessionId);
356
+ });
357
+ this.running.set(sessionId, task);
358
+ }
359
+ async run(sessionId) {
360
+ while (!this.closed) {
361
+ const request = this.pending.get(sessionId);
362
+ if (request === undefined)
363
+ return;
364
+ this.pending.delete(sessionId);
365
+ try {
366
+ const result = await this.client.compact(request);
367
+ if (findAppliedArtifact(result) === undefined)
368
+ this.completed.delete(sessionId);
369
+ else
370
+ this.completed.set(sessionId, result);
371
+ }
372
+ catch (error) {
373
+ this.onError(sessionId, error);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ function dshKind(role) {
379
+ if (role === "assistant")
380
+ return "assistant";
381
+ if (role === "system")
382
+ return "system";
383
+ return "user";
384
+ }
385
+ function toolCallGroups(messages) {
386
+ const calls = new Map();
387
+ const messageGroups = new Map();
388
+ for (const message of messages) {
389
+ const callIds = message.content.flatMap((block) => block.type === "tool-call" ? [String(block.id)] : []);
390
+ if (callIds.length === 0)
391
+ continue;
392
+ const group = `dsh-tool-batch:${String(message.id)}`;
393
+ messageGroups.set(String(message.id), group);
394
+ for (const callId of callIds)
395
+ calls.set(callId, group);
396
+ }
397
+ return { calls, messageGroups };
398
+ }
399
+ export default CompactDshEngine;
400
+ //# sourceMappingURL=index.js.map