@aexol/spectral 0.9.58 → 0.9.60
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/CHANGELOG.md +6 -0
- package/README.md +43 -0
- package/dist/agent/index.d.ts +6 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +133 -14
- package/dist/extensions/kanban-bridge.d.ts.map +1 -1
- package/dist/extensions/kanban-bridge.js +21 -1
- package/dist/extensions/web/index.d.ts +10 -0
- package/dist/extensions/web/index.d.ts.map +1 -0
- package/dist/extensions/web/index.js +11 -0
- package/dist/extensions/web/tools/fetch.d.ts +3 -0
- package/dist/extensions/web/tools/fetch.d.ts.map +1 -0
- package/dist/extensions/web/tools/fetch.js +80 -0
- package/dist/extensions/web/utils/fetcher.d.ts +34 -0
- package/dist/extensions/web/utils/fetcher.d.ts.map +1 -0
- package/dist/extensions/web/utils/fetcher.js +289 -0
- package/dist/extensions/web/utils/parser.d.ts +15 -0
- package/dist/extensions/web/utils/parser.d.ts.map +1 -0
- package/dist/extensions/web/utils/parser.js +205 -0
- package/dist/memory/branch.d.ts.map +1 -1
- package/dist/memory/branch.js +16 -8
- package/dist/memory/commands/status.d.ts.map +1 -1
- package/dist/memory/commands/status.js +3 -2
- package/dist/memory/config.d.ts +12 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +21 -2
- package/dist/memory/hooks/compaction-trigger.d.ts.map +1 -1
- package/dist/memory/hooks/compaction-trigger.js +19 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +2 -0
- package/dist/memory/testing/random-text.js +1 -1
- package/dist/memory/tool-output-compressor.d.ts +14 -20
- package/dist/memory/tool-output-compressor.d.ts.map +1 -1
- package/dist/memory/tool-output-compressor.js +391 -137
- package/dist/memory/tools/compact-context.d.ts +5 -0
- package/dist/memory/tools/compact-context.d.ts.map +1 -0
- package/dist/memory/tools/compact-context.js +772 -0
- package/dist/relay/dispatcher.d.ts +1 -1
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +61 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts +56 -3
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +408 -210
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +57 -7
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +146 -27
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.d.ts +24 -0
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/dcp-lite.js +411 -0
- package/dist/sdk/coding-agent/core/compaction/index.d.ts +2 -0
- package/dist/sdk/coding-agent/core/compaction/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/index.js +2 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts +71 -0
- package/dist/sdk/coding-agent/core/compaction/policy.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/policy.js +147 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +9 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +17 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +4 -10
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +17 -17
- package/dist/sdk/coding-agent/index.d.ts +1 -1
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/server/agent-bridge.d.ts +12 -5
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +65 -17
- package/dist/server/handlers/paths-home.d.ts +11 -0
- package/dist/server/handlers/paths-home.d.ts.map +1 -0
- package/dist/server/handlers/paths-home.js +10 -0
- package/dist/server/handlers/paths-list.d.ts +24 -0
- package/dist/server/handlers/paths-list.d.ts.map +1 -0
- package/dist/server/handlers/paths-list.js +56 -0
- package/dist/server/handlers/paths-pick-directory.d.ts.map +1 -1
- package/dist/server/handlers/paths-pick-directory.js +2 -1
- package/dist/server/handlers/sessions.d.ts +11 -1
- package/dist/server/handlers/sessions.d.ts.map +1 -1
- package/dist/server/handlers/sessions.js +22 -2
- package/dist/server/handlers/settings.d.ts +18 -1
- package/dist/server/handlers/settings.d.ts.map +1 -1
- package/dist/server/handlers/settings.js +63 -13
- package/dist/server/session-stream.d.ts +65 -37
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +525 -119
- package/dist/server/storage.d.ts +3 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +25 -0
- package/dist/server/wire.d.ts +127 -2
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -8,25 +8,53 @@ import type { AgentMessage, StreamFn, ThinkingLevel } from "../../../agent-core/
|
|
|
8
8
|
import type { Model, Usage } from "../../../ai/index.js";
|
|
9
9
|
import { type SessionEntry } from "../session-manager.js";
|
|
10
10
|
import { type FileOperations } from "./utils.js";
|
|
11
|
+
import { type ConversationCompactionPolicy, type ManualCompactionPolicy } from "./policy.js";
|
|
11
12
|
/** Details stored in CompactionEntry.details for file tracking */
|
|
12
13
|
export interface CompactionDetails {
|
|
13
14
|
readFiles: string[];
|
|
14
15
|
modifiedFiles: string[];
|
|
15
16
|
}
|
|
17
|
+
export type CompactionSkipReason = "non_shrinking";
|
|
18
|
+
export interface CompactionTokenSourceBucket {
|
|
19
|
+
type: "role" | "tool" | "source";
|
|
20
|
+
name: string;
|
|
21
|
+
tokens: number;
|
|
22
|
+
}
|
|
16
23
|
/** Result from compact() - SessionManager adds uuid/parentUuid when saving */
|
|
17
24
|
export interface CompactionResult<T = unknown> {
|
|
18
25
|
summary: string;
|
|
19
26
|
firstKeptEntryId: string;
|
|
20
27
|
tokensBefore: number;
|
|
28
|
+
/** Estimated tokens in the raw span replaced by the compaction summary. */
|
|
29
|
+
tokensCompacted?: number;
|
|
30
|
+
/** Estimated net tokens removed by replacing raw span tokens with summary tokens. */
|
|
31
|
+
tokensRemoved?: number;
|
|
32
|
+
/** Estimated tokens added back as the compaction summary. */
|
|
33
|
+
summaryTokens?: number;
|
|
34
|
+
/** Estimated percentage of compacted raw tokens removed by the summary. */
|
|
35
|
+
reductionPercent?: number;
|
|
36
|
+
/** 1-based compaction ordinal within the current session branch. */
|
|
37
|
+
compactionNumber?: number;
|
|
38
|
+
/** Largest estimated token buckets from the span being compacted. */
|
|
39
|
+
largestTokenSources?: CompactionTokenSourceBucket[];
|
|
40
|
+
/** True when the compaction was intentionally skipped and no history was pruned. */
|
|
41
|
+
skipped?: boolean;
|
|
42
|
+
/** Machine-readable reason for a skipped compaction. */
|
|
43
|
+
skipReason?: CompactionSkipReason;
|
|
44
|
+
/** Human-readable skipped/failure message for UI and persisted history. */
|
|
45
|
+
message?: string;
|
|
21
46
|
/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
|
|
22
47
|
details?: T;
|
|
23
48
|
}
|
|
24
|
-
export
|
|
25
|
-
enabled: boolean;
|
|
26
|
-
reserveTokens: number;
|
|
27
|
-
keepRecentTokens: number;
|
|
28
|
-
}
|
|
49
|
+
export type CompactionSettings = ConversationCompactionPolicy;
|
|
29
50
|
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
51
|
+
export declare const MANUAL_COMPACTION_KEEP_RECENT_TOKENS: number;
|
|
52
|
+
/**
|
|
53
|
+
* Manual/model-visible compaction is an explicit phase-boundary action. Keep a
|
|
54
|
+
* smaller recent suffix than automatic threshold compaction so a ~20k-token
|
|
55
|
+
* default session does not keep the whole eligible branch and compact nothing.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getManualCompactionSettings(settings: CompactionSettings, manualPolicy?: ManualCompactionPolicy): CompactionSettings;
|
|
30
58
|
/**
|
|
31
59
|
* Calculate total context tokens from usage.
|
|
32
60
|
* Uses the native totalTokens field when available, falls back to computing from components.
|
|
@@ -70,6 +98,13 @@ export interface CutPointResult {
|
|
|
70
98
|
/** Whether this cut splits a turn (cut point is not a user message) */
|
|
71
99
|
isSplitTurn: boolean;
|
|
72
100
|
}
|
|
101
|
+
export interface FindCutPointOptions {
|
|
102
|
+
/**
|
|
103
|
+
* Explicit phase-boundary compaction should prefer the newest safe boundary
|
|
104
|
+
* over retaining a large just-finished turn as the recent raw suffix.
|
|
105
|
+
*/
|
|
106
|
+
phaseBoundary?: boolean;
|
|
107
|
+
}
|
|
73
108
|
/**
|
|
74
109
|
* Find the cut point in session entries that keeps approximately `keepRecentTokens`.
|
|
75
110
|
*
|
|
@@ -86,10 +121,14 @@ export interface CutPointResult {
|
|
|
86
121
|
*
|
|
87
122
|
* Only considers entries between `startIndex` and `endIndex` (exclusive).
|
|
88
123
|
*/
|
|
89
|
-
export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
|
|
124
|
+
export declare function findCutPoint(entries: SessionEntry[], startIndex: number, endIndex: number, keepRecentTokens: number, options?: FindCutPointOptions): CutPointResult;
|
|
90
125
|
/**
|
|
91
126
|
* Generate a summary of the conversation using the LLM.
|
|
92
127
|
* If previousSummary is provided, uses the update prompt to merge.
|
|
128
|
+
*
|
|
129
|
+
* @deprecated The active coding-agent session path uses deterministic DCP-lite
|
|
130
|
+
* (`compactDcpLite`) plus `session_before_compact` hook details. This legacy
|
|
131
|
+
* LLM summarizer remains exported for compatibility with older callers only.
|
|
93
132
|
*/
|
|
94
133
|
export declare function generateSummary(currentMessages: AgentMessage[], model: Model<any>, reserveTokens: number, apiKey: string | undefined, headers?: Record<string, string>, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn): Promise<string>;
|
|
95
134
|
export interface CompactionPreparation {
|
|
@@ -108,8 +147,16 @@ export interface CompactionPreparation {
|
|
|
108
147
|
fileOps: FileOperations;
|
|
109
148
|
/** Compaction settions from settings.jsonl */
|
|
110
149
|
settings: CompactionSettings;
|
|
150
|
+
/** 1-based compaction ordinal within the current session branch. */
|
|
151
|
+
compactionNumber: number;
|
|
152
|
+
}
|
|
153
|
+
export interface PrepareCompactionOptions {
|
|
154
|
+
/** Prefer a caller-provided phase boundary over the usual recent-token suffix. */
|
|
155
|
+
phaseBoundary?: boolean;
|
|
111
156
|
}
|
|
112
|
-
export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings): CompactionPreparation | undefined;
|
|
157
|
+
export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, options?: PrepareCompactionOptions): CompactionPreparation | undefined;
|
|
158
|
+
export declare function estimateTextTokens(text: string): number;
|
|
159
|
+
export declare function estimateCompactionTokenMetrics(preparation: Pick<CompactionPreparation, "messagesToSummarize" | "turnPrefixMessages"> & Partial<Pick<CompactionPreparation, "compactionNumber">>, summary: string): Pick<CompactionResult, "tokensCompacted" | "tokensRemoved" | "summaryTokens" | "reductionPercent" | "compactionNumber" | "largestTokenSources">;
|
|
113
160
|
/**
|
|
114
161
|
* Deduplicate repeated tool calls in a message array by keeping only the
|
|
115
162
|
* most recent occurrence of each (tool, args) pair.
|
|
@@ -137,6 +184,9 @@ export declare function deduplicateToolCalls(messages: AgentMessage[]): AgentMes
|
|
|
137
184
|
*
|
|
138
185
|
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
139
186
|
* @param customInstructions - Optional custom focus for the summary
|
|
187
|
+
* @deprecated The active AgentSession compaction flow uses deterministic
|
|
188
|
+
* DCP-lite (`compactDcpLite`) through `_buildDcpCompactionResult`. Keep this
|
|
189
|
+
* exported legacy LLM fallback for SDK/harness compatibility only.
|
|
140
190
|
*/
|
|
141
191
|
export declare function compact(preparation: CompactionPreparation, model: Model<any>, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn): Promise<CompactionResult>;
|
|
142
192
|
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/compaction/compaction.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,KAAK,EAA6B,KAAK,EAAuB,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAQzG,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrG,OAAO,EAIN,KAAK,cAAc,EAInB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/compaction/compaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { completeSimple } from "../../../ai/index.js";
|
|
|
8
8
|
import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
9
9
|
import { buildSessionContext } from "../session-manager.js";
|
|
10
10
|
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
11
|
+
import { DEFAULT_COMPACTION_POLICY, normalizeManualKeepRecentTokens, } from "./policy.js";
|
|
11
12
|
/**
|
|
12
13
|
* Extract file operations from messages and previous compaction entries.
|
|
13
14
|
*/
|
|
@@ -64,10 +65,20 @@ function getMessageFromEntryForCompaction(entry) {
|
|
|
64
65
|
return getMessageFromEntry(entry);
|
|
65
66
|
}
|
|
66
67
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
67
|
-
|
|
68
|
-
reserveTokens: 16384,
|
|
69
|
-
keepRecentTokens: 20000,
|
|
68
|
+
...DEFAULT_COMPACTION_POLICY.conversation,
|
|
70
69
|
};
|
|
70
|
+
export const MANUAL_COMPACTION_KEEP_RECENT_TOKENS = DEFAULT_COMPACTION_POLICY.manual.keepRecentTokens;
|
|
71
|
+
/**
|
|
72
|
+
* Manual/model-visible compaction is an explicit phase-boundary action. Keep a
|
|
73
|
+
* smaller recent suffix than automatic threshold compaction so a ~20k-token
|
|
74
|
+
* default session does not keep the whole eligible branch and compact nothing.
|
|
75
|
+
*/
|
|
76
|
+
export function getManualCompactionSettings(settings, manualPolicy = DEFAULT_COMPACTION_POLICY.manual) {
|
|
77
|
+
const manualKeepRecentTokens = normalizeManualKeepRecentTokens(manualPolicy.keepRecentTokens, manualPolicy);
|
|
78
|
+
if (settings.keepRecentTokens <= manualKeepRecentTokens)
|
|
79
|
+
return settings;
|
|
80
|
+
return { ...settings, keepRecentTokens: manualKeepRecentTokens };
|
|
81
|
+
}
|
|
71
82
|
// ============================================================================
|
|
72
83
|
// Token calculation
|
|
73
84
|
// ============================================================================
|
|
@@ -149,7 +160,11 @@ export function estimateContextTokens(messages) {
|
|
|
149
160
|
export function shouldCompact(contextTokens, contextWindow, settings) {
|
|
150
161
|
if (!settings.enabled)
|
|
151
162
|
return false;
|
|
152
|
-
|
|
163
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0)
|
|
164
|
+
return false;
|
|
165
|
+
if (!Number.isFinite(contextTokens) || contextTokens < 0)
|
|
166
|
+
return false;
|
|
167
|
+
return contextTokens >= contextWindow * settings.contextLimitRatio;
|
|
153
168
|
}
|
|
154
169
|
// ============================================================================
|
|
155
170
|
// Cut point detection
|
|
@@ -285,6 +300,20 @@ export function findTurnStartIndex(entries, entryIndex, startIndex) {
|
|
|
285
300
|
}
|
|
286
301
|
return -1;
|
|
287
302
|
}
|
|
303
|
+
function isTurnBoundaryEntry(entry) {
|
|
304
|
+
if (!entry)
|
|
305
|
+
return false;
|
|
306
|
+
if (entry.type === "branch_summary" || entry.type === "custom_message")
|
|
307
|
+
return true;
|
|
308
|
+
if (entry.type !== "message")
|
|
309
|
+
return false;
|
|
310
|
+
const role = entry.message.role;
|
|
311
|
+
return role === "user" ||
|
|
312
|
+
role === "bashExecution" ||
|
|
313
|
+
role === "custom" ||
|
|
314
|
+
role === "branchSummary" ||
|
|
315
|
+
role === "compactionSummary";
|
|
316
|
+
}
|
|
288
317
|
/**
|
|
289
318
|
* Find the cut point in session entries that keeps approximately `keepRecentTokens`.
|
|
290
319
|
*
|
|
@@ -301,31 +330,38 @@ export function findTurnStartIndex(entries, entryIndex, startIndex) {
|
|
|
301
330
|
*
|
|
302
331
|
* Only considers entries between `startIndex` and `endIndex` (exclusive).
|
|
303
332
|
*/
|
|
304
|
-
export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
333
|
+
export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens, options = {}) {
|
|
305
334
|
const cutPoints = findValidCutPoints(entries, startIndex, endIndex);
|
|
306
335
|
if (cutPoints.length === 0) {
|
|
307
336
|
return { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };
|
|
308
337
|
}
|
|
309
|
-
// Walk backwards from newest, accumulating estimated message sizes
|
|
338
|
+
// Walk backwards from newest, accumulating estimated message sizes.
|
|
339
|
+
// Phase-boundary callers provide an explicit marker, so the newest valid
|
|
340
|
+
// boundary is the desired cut point and the usual recent-token suffix is not
|
|
341
|
+
// retained raw.
|
|
310
342
|
let accumulatedTokens = 0;
|
|
311
|
-
let cutIndex =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
343
|
+
let cutIndex = options.phaseBoundary
|
|
344
|
+
? cutPoints[cutPoints.length - 1]
|
|
345
|
+
: cutPoints[0]; // Default: keep from first message (not header)
|
|
346
|
+
if (!options.phaseBoundary) {
|
|
347
|
+
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
348
|
+
const entry = entries[i];
|
|
349
|
+
if (entry.type !== "message")
|
|
350
|
+
continue;
|
|
351
|
+
// Estimate this message's size
|
|
352
|
+
const messageTokens = estimateTokens(entry.message);
|
|
353
|
+
accumulatedTokens += messageTokens;
|
|
354
|
+
// Check if we've exceeded the budget
|
|
355
|
+
if (accumulatedTokens >= keepRecentTokens) {
|
|
356
|
+
// Find the closest valid cut point at or after this entry
|
|
357
|
+
for (let c = 0; c < cutPoints.length; c++) {
|
|
358
|
+
if (cutPoints[c] >= i) {
|
|
359
|
+
cutIndex = cutPoints[c];
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
326
362
|
}
|
|
363
|
+
break;
|
|
327
364
|
}
|
|
328
|
-
break;
|
|
329
365
|
}
|
|
330
366
|
}
|
|
331
367
|
// Scan backwards from cutIndex to include any non-message entries (bash, settings, etc.)
|
|
@@ -344,17 +380,22 @@ export function findCutPoint(entries, startIndex, endIndex, keepRecentTokens) {
|
|
|
344
380
|
}
|
|
345
381
|
// Determine if this is a split turn
|
|
346
382
|
const cutEntry = entries[cutIndex];
|
|
347
|
-
const
|
|
348
|
-
const turnStartIndex =
|
|
383
|
+
const isTurnBoundary = isTurnBoundaryEntry(cutEntry);
|
|
384
|
+
const turnStartIndex = isTurnBoundary ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);
|
|
349
385
|
return {
|
|
350
386
|
firstKeptEntryIndex: cutIndex,
|
|
351
387
|
turnStartIndex,
|
|
352
|
-
isSplitTurn: !
|
|
388
|
+
isSplitTurn: !isTurnBoundary && turnStartIndex !== -1,
|
|
353
389
|
};
|
|
354
390
|
}
|
|
355
391
|
// ============================================================================
|
|
356
392
|
// Summarization
|
|
357
393
|
// ============================================================================
|
|
394
|
+
// Legacy LLM summarization path. AgentSession now builds persisted compaction
|
|
395
|
+
// entries with deterministic DCP-lite (`compactDcpLite`) and lets
|
|
396
|
+
// `session_before_compact` hooks provide memory details. Keep these exports for
|
|
397
|
+
// compatibility with external SDK callers and older harness code, but do not
|
|
398
|
+
// route new session compaction work through this model-generated fallback.
|
|
358
399
|
const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
|
|
359
400
|
|
|
360
401
|
Use this EXACT format:
|
|
@@ -442,6 +483,10 @@ async function completeSummarization(model, context, options, streamFn) {
|
|
|
442
483
|
/**
|
|
443
484
|
* Generate a summary of the conversation using the LLM.
|
|
444
485
|
* If previousSummary is provided, uses the update prompt to merge.
|
|
486
|
+
*
|
|
487
|
+
* @deprecated The active coding-agent session path uses deterministic DCP-lite
|
|
488
|
+
* (`compactDcpLite`) plus `session_before_compact` hook details. This legacy
|
|
489
|
+
* LLM summarizer remains exported for compatibility with older callers only.
|
|
445
490
|
*/
|
|
446
491
|
export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn) {
|
|
447
492
|
const maxTokens = Math.min(Math.floor(0.8 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
@@ -478,7 +523,7 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
478
523
|
.join("\n");
|
|
479
524
|
return textContent;
|
|
480
525
|
}
|
|
481
|
-
export function prepareCompaction(pathEntries, settings) {
|
|
526
|
+
export function prepareCompaction(pathEntries, settings, options = {}) {
|
|
482
527
|
if (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === "compaction") {
|
|
483
528
|
return undefined;
|
|
484
529
|
}
|
|
@@ -499,7 +544,9 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
499
544
|
}
|
|
500
545
|
const boundaryEnd = pathEntries.length;
|
|
501
546
|
const tokensBefore = estimateContextTokens(buildSessionContext(pathEntries).messages).tokens;
|
|
502
|
-
const cutPoint = findCutPoint(pathEntries, boundaryStart, boundaryEnd, settings.keepRecentTokens
|
|
547
|
+
const cutPoint = findCutPoint(pathEntries, boundaryStart, boundaryEnd, settings.keepRecentTokens, {
|
|
548
|
+
phaseBoundary: options.phaseBoundary,
|
|
549
|
+
});
|
|
503
550
|
// Get UUID of first kept entry
|
|
504
551
|
const firstKeptEntry = pathEntries[cutPoint.firstKeptEntryIndex];
|
|
505
552
|
if (!firstKeptEntry?.id) {
|
|
@@ -531,6 +578,7 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
531
578
|
extractFileOpsFromMessage(msg, fileOps);
|
|
532
579
|
}
|
|
533
580
|
}
|
|
581
|
+
const compactionNumber = pathEntries.filter((entry) => entry.type === "compaction").length + 1;
|
|
534
582
|
return {
|
|
535
583
|
firstKeptEntryId,
|
|
536
584
|
messagesToSummarize,
|
|
@@ -540,7 +588,73 @@ export function prepareCompaction(pathEntries, settings) {
|
|
|
540
588
|
previousSummary,
|
|
541
589
|
fileOps,
|
|
542
590
|
settings,
|
|
591
|
+
compactionNumber,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
export function estimateTextTokens(text) {
|
|
595
|
+
if (!text.trim())
|
|
596
|
+
return 0;
|
|
597
|
+
return Math.ceil(text.length / 4);
|
|
598
|
+
}
|
|
599
|
+
function estimateMessagesTokens(messages) {
|
|
600
|
+
let tokens = 0;
|
|
601
|
+
for (const message of messages) {
|
|
602
|
+
tokens += estimateTokens(message);
|
|
603
|
+
}
|
|
604
|
+
return tokens;
|
|
605
|
+
}
|
|
606
|
+
export function estimateCompactionTokenMetrics(preparation, summary) {
|
|
607
|
+
const tokensCompacted = estimateMessagesTokens(preparation.messagesToSummarize) +
|
|
608
|
+
estimateMessagesTokens(preparation.turnPrefixMessages);
|
|
609
|
+
const summaryTokens = estimateTextTokens(summary);
|
|
610
|
+
const tokensRemoved = Math.max(0, tokensCompacted - summaryTokens);
|
|
611
|
+
const reductionPercent = tokensCompacted > 0
|
|
612
|
+
? Math.round((tokensRemoved / tokensCompacted) * 10_000) / 100
|
|
613
|
+
: 0;
|
|
614
|
+
return {
|
|
615
|
+
tokensCompacted,
|
|
616
|
+
tokensRemoved,
|
|
617
|
+
summaryTokens,
|
|
618
|
+
reductionPercent,
|
|
619
|
+
compactionNumber: preparation.compactionNumber,
|
|
620
|
+
largestTokenSources: collectLargestTokenSources(preparation.messagesToSummarize, preparation.turnPrefixMessages),
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function addBucket(buckets, bucket) {
|
|
624
|
+
const key = `${bucket.type}:${bucket.name}`;
|
|
625
|
+
const previous = buckets.get(key);
|
|
626
|
+
buckets.set(key, {
|
|
627
|
+
...bucket,
|
|
628
|
+
tokens: (previous?.tokens ?? 0) + bucket.tokens,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function collectLargestTokenSources(messagesToSummarize, turnPrefixMessages) {
|
|
632
|
+
const buckets = new Map();
|
|
633
|
+
const collect = (messages, source) => {
|
|
634
|
+
for (const message of messages) {
|
|
635
|
+
const messageTokens = estimateTokens(message);
|
|
636
|
+
addBucket(buckets, { type: "source", name: source, tokens: messageTokens });
|
|
637
|
+
addBucket(buckets, { type: "role", name: message.role, tokens: messageTokens });
|
|
638
|
+
if (message.role === "toolResult") {
|
|
639
|
+
addBucket(buckets, { type: "tool", name: message.toolName, tokens: messageTokens });
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (message.role === "assistant") {
|
|
643
|
+
for (const block of message.content) {
|
|
644
|
+
if (block.type !== "toolCall")
|
|
645
|
+
continue;
|
|
646
|
+
const toolTokens = estimateTextTokens(`${block.name} ${JSON.stringify(block.arguments)}`);
|
|
647
|
+
addBucket(buckets, { type: "tool", name: block.name, tokens: toolTokens });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
543
651
|
};
|
|
652
|
+
collect(messagesToSummarize, "history");
|
|
653
|
+
collect(turnPrefixMessages, "turn_prefix");
|
|
654
|
+
return [...buckets.values()]
|
|
655
|
+
.filter((bucket) => bucket.tokens > 0)
|
|
656
|
+
.sort((a, b) => b.tokens - a.tokens || a.type.localeCompare(b.type) || a.name.localeCompare(b.name))
|
|
657
|
+
.slice(0, 5);
|
|
544
658
|
}
|
|
545
659
|
// ============================================================================
|
|
546
660
|
// Tool Call Deduplication
|
|
@@ -716,6 +830,9 @@ Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
|
716
830
|
*
|
|
717
831
|
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
718
832
|
* @param customInstructions - Optional custom focus for the summary
|
|
833
|
+
* @deprecated The active AgentSession compaction flow uses deterministic
|
|
834
|
+
* DCP-lite (`compactDcpLite`) through `_buildDcpCompactionResult`. Keep this
|
|
835
|
+
* exported legacy LLM fallback for SDK/harness compatibility only.
|
|
719
836
|
*/
|
|
720
837
|
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn) {
|
|
721
838
|
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, settings, } = preparation;
|
|
@@ -744,6 +861,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
744
861
|
// Compute file lists and append to summary
|
|
745
862
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
746
863
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
|
864
|
+
const tokenMetrics = estimateCompactionTokenMetrics(preparation, summary);
|
|
747
865
|
if (!firstKeptEntryId) {
|
|
748
866
|
throw new Error("First kept entry has no UUID - session may need migration");
|
|
749
867
|
}
|
|
@@ -751,6 +869,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
751
869
|
summary,
|
|
752
870
|
firstKeptEntryId,
|
|
753
871
|
tokensBefore,
|
|
872
|
+
...tokenMetrics,
|
|
754
873
|
details: { readFiles, modifiedFiles },
|
|
755
874
|
};
|
|
756
875
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type CompactionDetails, type CompactionPreparation, type CompactionResult, type CompactionTokenSourceBucket } from "./compaction.js";
|
|
2
|
+
export type CompactionMode = "dcp-lite";
|
|
3
|
+
export interface DcpLiteCompactionDetails extends CompactionDetails {
|
|
4
|
+
method: "dcp-lite";
|
|
5
|
+
version: 1;
|
|
6
|
+
compactionNumber: number;
|
|
7
|
+
tokensCompacted?: number;
|
|
8
|
+
tokensRemoved?: number;
|
|
9
|
+
summaryTokens?: number;
|
|
10
|
+
reductionPercent?: number;
|
|
11
|
+
largestTokenSources?: CompactionTokenSourceBucket[];
|
|
12
|
+
summarizedMessages: number;
|
|
13
|
+
turnPrefixMessages: number;
|
|
14
|
+
isSplitTurn: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic, local-only compaction for manual DCP-lite mode.
|
|
18
|
+
*
|
|
19
|
+
* This intentionally performs no model, provider, extension, or network calls.
|
|
20
|
+
* It preserves bounded excerpts and counters from CompactionPreparation so the
|
|
21
|
+
* resulting summary is stable and usually much smaller than the discarded span.
|
|
22
|
+
*/
|
|
23
|
+
export declare function compactDcpLite(preparation: CompactionPreparation): CompactionResult<DcpLiteCompactionDetails>;
|
|
24
|
+
//# sourceMappingURL=dcp-lite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dcp-lite.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/compaction/dcp-lite.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,2BAA2B,EAGhC,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC;AAExC,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IAClE,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,CAAC,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACpD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;CACrB;AA8CD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,qBAAqB,GAAG,gBAAgB,CAAC,wBAAwB,CAAC,CA2D7G"}
|