@codebolt/agent 6.0.0 → 6.1.2
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/README.md +118 -153
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/processor-pieces/messageModifiers/chatHistoryMessageModifier.js +2 -4
- package/dist/processor-pieces/messageModifiers/contextAssemblyModifier.d.ts +81 -0
- package/dist/processor-pieces/messageModifiers/contextAssemblyModifier.js +316 -0
- package/dist/processor-pieces/messageModifiers/coreSystemPromptModifier.js +2 -19
- package/dist/processor-pieces/messageModifiers/directoryContextModifier.js +2 -6
- package/dist/processor-pieces/messageModifiers/environmentContextModifier.js +2 -7
- package/dist/processor-pieces/messageModifiers/ideContextModifier.js +2 -21
- package/dist/processor-pieces/messageModifiers/index.d.ts +3 -0
- package/dist/processor-pieces/messageModifiers/index.js +7 -1
- package/dist/processor-pieces/messageModifiers/toolInjectionModifier.js +2 -1
- package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.d.ts +34 -5
- package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.js +374 -89
- package/dist/processor-pieces/preInferenceProcessors/chatCompressionModifier.d.ts +3 -0
- package/dist/processor-pieces/preInferenceProcessors/chatCompressionModifier.js +50 -27
- package/dist/unified/agent/agent.d.ts +10 -0
- package/dist/unified/agent/agent.js +198 -10
- package/dist/unified/agent/codeboltAgent.d.ts +19 -100
- package/dist/unified/agent/codeboltAgent.js +209 -109
- package/dist/unified/base/agentStep.js +17 -17
- package/dist/unified/base/initialPromptGenerator.js +13 -31
- package/dist/unified/base/promptContext.d.ts +13 -0
- package/dist/unified/base/promptContext.js +213 -0
- package/dist/unified/base/responseExecutor.d.ts +7 -19
- package/dist/unified/base/responseExecutor.js +280 -259
- package/dist/unified/index.d.ts +9 -0
- package/dist/unified/index.js +20 -13
- package/dist/unified/services/CompressionCoordinator.d.ts +67 -0
- package/dist/unified/services/CompressionCoordinator.js +214 -0
- package/dist/unified/services/compaction/autoCompact.d.ts +52 -0
- package/dist/unified/services/compaction/autoCompact.js +294 -0
- package/dist/unified/services/compaction/compactionOrchestrator.d.ts +78 -0
- package/dist/unified/services/compaction/compactionOrchestrator.js +230 -0
- package/dist/unified/services/compaction/contextCollapse.d.ts +63 -0
- package/dist/unified/services/compaction/contextCollapse.js +291 -0
- package/dist/unified/services/compaction/microCompact.d.ts +34 -0
- package/dist/unified/services/compaction/microCompact.js +195 -0
- package/dist/unified/services/compaction/postCompactCleanup.d.ts +15 -0
- package/dist/unified/services/compaction/postCompactCleanup.js +37 -0
- package/dist/unified/services/compaction/reactiveCompact.d.ts +65 -0
- package/dist/unified/services/compaction/reactiveCompact.js +301 -0
- package/dist/unified/services/compaction/snipCompact.d.ts +31 -0
- package/dist/unified/services/compaction/snipCompact.js +124 -0
- package/dist/unified/services/compaction/types.d.ts +66 -0
- package/dist/unified/services/compaction/types.js +39 -0
- package/package.json +25 -29
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates the 5-layer defense-in-depth compaction system.
|
|
5
|
+
* Layers execute cheapest-first: snip -> micro -> collapse -> auto -> reactive
|
|
6
|
+
*
|
|
7
|
+
* The orchestrator:
|
|
8
|
+
* 1. Runs each layer's shouldApply() check
|
|
9
|
+
* 2. Applies layers in priority order
|
|
10
|
+
* 3. Passes each layer's savings to subsequent layers via CompactionContext
|
|
11
|
+
* 4. Runs post-compact cleanup after any layer is applied
|
|
12
|
+
* 5. Provides reactive recovery for API 413 errors
|
|
13
|
+
*/
|
|
14
|
+
import type { MessageObject } from '@codebolt/types/sdk';
|
|
15
|
+
import { type CompactionBoundary, type CompactionLayerKind, type CompactionOrchestratorOptions } from './types';
|
|
16
|
+
import { SnipCompact } from './snipCompact';
|
|
17
|
+
import { MicroCompact } from './microCompact';
|
|
18
|
+
import { ContextCollapse } from './contextCollapse';
|
|
19
|
+
import { AutoCompact } from './autoCompact';
|
|
20
|
+
import { ReactiveCompact } from './reactiveCompact';
|
|
21
|
+
export interface CompactionPipelineResult {
|
|
22
|
+
/** Final messages after all applied layers */
|
|
23
|
+
messages: MessageObject[];
|
|
24
|
+
/** Total tokens freed across all layers */
|
|
25
|
+
totalTokensFreed: number;
|
|
26
|
+
/** Layers that were applied, in order */
|
|
27
|
+
layersApplied: CompactionLayerKind[];
|
|
28
|
+
/** Compaction boundaries for telemetry */
|
|
29
|
+
boundaries: CompactionBoundary[];
|
|
30
|
+
/** Whether any compaction occurred */
|
|
31
|
+
wasCompacted: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare class CompactionOrchestrator {
|
|
34
|
+
private readonly snip;
|
|
35
|
+
private readonly micro;
|
|
36
|
+
private readonly collapse;
|
|
37
|
+
private readonly auto;
|
|
38
|
+
private readonly reactive;
|
|
39
|
+
private readonly cleanup;
|
|
40
|
+
private readonly options;
|
|
41
|
+
/** Layer execution order (cheapest first) */
|
|
42
|
+
private readonly layerOrder;
|
|
43
|
+
private readonly layers;
|
|
44
|
+
constructor(options?: CompactionOrchestratorOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Run the full compaction pipeline.
|
|
47
|
+
* Each layer is checked and applied in priority order.
|
|
48
|
+
* If any layer reduces messages, subsequent layers see the reduced set.
|
|
49
|
+
*/
|
|
50
|
+
compact(messages: MessageObject[]): Promise<CompactionPipelineResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Attempt reactive recovery from an API error.
|
|
53
|
+
* Only called when the proactive pipeline didn't prevent overflow.
|
|
54
|
+
*/
|
|
55
|
+
recoverFromError(messages: MessageObject[], error: unknown): Promise<CompactionPipelineResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Get the current auto-compact tracking state.
|
|
58
|
+
*/
|
|
59
|
+
getAutoCompactTracking(): import("./autoCompact").AutoCompactTracking | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of consecutive auto-compact failures.
|
|
62
|
+
*/
|
|
63
|
+
getConsecutiveFailures(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Reset the reactive compact's per-turn guard.
|
|
66
|
+
* Call at the start of each new turn.
|
|
67
|
+
*/
|
|
68
|
+
resetForTurn(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Reset all compaction state.
|
|
71
|
+
*/
|
|
72
|
+
resetAll(): void;
|
|
73
|
+
getSnipLayer(): SnipCompact;
|
|
74
|
+
getMicroLayer(): MicroCompact;
|
|
75
|
+
getCollapseLayer(): ContextCollapse;
|
|
76
|
+
getAutoLayer(): AutoCompact;
|
|
77
|
+
getReactiveLayer(): ReactiveCompact;
|
|
78
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Compaction Orchestrator
|
|
4
|
+
*
|
|
5
|
+
* Coordinates the 5-layer defense-in-depth compaction system.
|
|
6
|
+
* Layers execute cheapest-first: snip -> micro -> collapse -> auto -> reactive
|
|
7
|
+
*
|
|
8
|
+
* The orchestrator:
|
|
9
|
+
* 1. Runs each layer's shouldApply() check
|
|
10
|
+
* 2. Applies layers in priority order
|
|
11
|
+
* 3. Passes each layer's savings to subsequent layers via CompactionContext
|
|
12
|
+
* 4. Runs post-compact cleanup after any layer is applied
|
|
13
|
+
* 5. Provides reactive recovery for API 413 errors
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CompactionOrchestrator = void 0;
|
|
17
|
+
const snipCompact_1 = require("./snipCompact");
|
|
18
|
+
const microCompact_1 = require("./microCompact");
|
|
19
|
+
const contextCollapse_1 = require("./contextCollapse");
|
|
20
|
+
const autoCompact_1 = require("./autoCompact");
|
|
21
|
+
const reactiveCompact_1 = require("./reactiveCompact");
|
|
22
|
+
const postCompactCleanup_1 = require("./postCompactCleanup");
|
|
23
|
+
const DEFAULT_MODEL_TOKEN_LIMIT = 128000;
|
|
24
|
+
class CompactionOrchestrator {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
var _a, _b, _c, _d;
|
|
27
|
+
/** Layer execution order (cheapest first) */
|
|
28
|
+
this.layerOrder = [
|
|
29
|
+
'snip',
|
|
30
|
+
'micro',
|
|
31
|
+
'collapse',
|
|
32
|
+
'auto',
|
|
33
|
+
];
|
|
34
|
+
this.options = {
|
|
35
|
+
modelTokenLimit: (_a = options === null || options === void 0 ? void 0 : options.modelTokenLimit) !== null && _a !== void 0 ? _a : DEFAULT_MODEL_TOKEN_LIMIT,
|
|
36
|
+
autoCompactEnabled: (_b = options === null || options === void 0 ? void 0 : options.autoCompactEnabled) !== null && _b !== void 0 ? _b : true,
|
|
37
|
+
contextCollapseEnabled: (_c = options === null || options === void 0 ? void 0 : options.contextCollapseEnabled) !== null && _c !== void 0 ? _c : false,
|
|
38
|
+
enableLogging: (_d = options === null || options === void 0 ? void 0 : options.enableLogging) !== null && _d !== void 0 ? _d : false,
|
|
39
|
+
};
|
|
40
|
+
this.snip = new snipCompact_1.SnipCompact({ enableLogging: this.options.enableLogging });
|
|
41
|
+
this.micro = new microCompact_1.MicroCompact({ enableLogging: this.options.enableLogging });
|
|
42
|
+
this.collapse = new contextCollapse_1.ContextCollapse({
|
|
43
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
44
|
+
enableLogging: this.options.enableLogging,
|
|
45
|
+
});
|
|
46
|
+
this.auto = new autoCompact_1.AutoCompact({
|
|
47
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
48
|
+
enableLogging: this.options.enableLogging,
|
|
49
|
+
});
|
|
50
|
+
this.reactive = new reactiveCompact_1.ReactiveCompact({
|
|
51
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
52
|
+
enableLogging: this.options.enableLogging,
|
|
53
|
+
});
|
|
54
|
+
this.cleanup = new postCompactCleanup_1.PostCompactCleanup({
|
|
55
|
+
enableLogging: this.options.enableLogging,
|
|
56
|
+
});
|
|
57
|
+
const layerEntries = [
|
|
58
|
+
['snip', this.snip],
|
|
59
|
+
['micro', this.micro],
|
|
60
|
+
['collapse', this.collapse],
|
|
61
|
+
['auto', this.auto],
|
|
62
|
+
['reactive', this.reactive],
|
|
63
|
+
];
|
|
64
|
+
this.layers = new Map(layerEntries);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run the full compaction pipeline.
|
|
68
|
+
* Each layer is checked and applied in priority order.
|
|
69
|
+
* If any layer reduces messages, subsequent layers see the reduced set.
|
|
70
|
+
*/
|
|
71
|
+
async compact(messages) {
|
|
72
|
+
var _a;
|
|
73
|
+
let ctx = {
|
|
74
|
+
messages,
|
|
75
|
+
snipTokensFreed: 0,
|
|
76
|
+
contextCollapseEnabled: this.options.contextCollapseEnabled,
|
|
77
|
+
compactionHistory: [],
|
|
78
|
+
autoCompactTracking: {
|
|
79
|
+
compacted: false,
|
|
80
|
+
turnId: `turn-${Date.now()}`,
|
|
81
|
+
turnCounter: 0,
|
|
82
|
+
consecutiveFailures: 0,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const layersApplied = [];
|
|
86
|
+
const boundaries = [];
|
|
87
|
+
let totalTokensFreed = 0;
|
|
88
|
+
for (const layerName of this.layerOrder) {
|
|
89
|
+
// Skip auto-compact if disabled or if context collapse is handling it
|
|
90
|
+
if (layerName === 'auto' && !this.options.autoCompactEnabled)
|
|
91
|
+
continue;
|
|
92
|
+
if (layerName === 'auto' && this.options.contextCollapseEnabled)
|
|
93
|
+
continue;
|
|
94
|
+
if (layerName === 'collapse' && !this.options.contextCollapseEnabled)
|
|
95
|
+
continue;
|
|
96
|
+
const layer = this.layers.get(layerName);
|
|
97
|
+
if (!layer)
|
|
98
|
+
continue;
|
|
99
|
+
if (layer.shouldApply(ctx)) {
|
|
100
|
+
try {
|
|
101
|
+
const prevLength = ctx.messages.length;
|
|
102
|
+
ctx = await layer.apply(ctx);
|
|
103
|
+
// Track what was applied
|
|
104
|
+
if (ctx.messages.length !== prevLength || ctx.compactionHistory) {
|
|
105
|
+
layersApplied.push(layerName);
|
|
106
|
+
// Get the latest boundary
|
|
107
|
+
const latestBoundary = (_a = ctx.compactionHistory) === null || _a === void 0 ? void 0 : _a.at(-1);
|
|
108
|
+
if (latestBoundary) {
|
|
109
|
+
boundaries.push(latestBoundary);
|
|
110
|
+
totalTokensFreed += latestBoundary.tokensFreed;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (this.options.enableLogging) {
|
|
116
|
+
console.error(`[CompactionOrchestrator] Layer ${layerName} failed:`, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Run post-compact cleanup
|
|
122
|
+
if (layersApplied.length > 0) {
|
|
123
|
+
this.cleanup.runCleanup(this.layers, layersApplied);
|
|
124
|
+
}
|
|
125
|
+
const wasCompacted = layersApplied.length > 0;
|
|
126
|
+
if (wasCompacted && this.options.enableLogging) {
|
|
127
|
+
console.log(`[CompactionOrchestrator] Compacted: layers=[${layersApplied.join(',')}], tokensFreed=${totalTokensFreed}`);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
messages: ctx.messages,
|
|
131
|
+
totalTokensFreed,
|
|
132
|
+
layersApplied,
|
|
133
|
+
boundaries,
|
|
134
|
+
wasCompacted,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Attempt reactive recovery from an API error.
|
|
139
|
+
* Only called when the proactive pipeline didn't prevent overflow.
|
|
140
|
+
*/
|
|
141
|
+
async recoverFromError(messages, error) {
|
|
142
|
+
const ctx = {
|
|
143
|
+
messages,
|
|
144
|
+
contextCollapseEnabled: this.options.contextCollapseEnabled,
|
|
145
|
+
compactionHistory: [],
|
|
146
|
+
};
|
|
147
|
+
// First try context collapse recovery (cheap: drain staged collapses)
|
|
148
|
+
if (this.options.contextCollapseEnabled) {
|
|
149
|
+
const collapseResult = this.collapse.recoverFromOverflow(messages);
|
|
150
|
+
if (collapseResult.committed > 0) {
|
|
151
|
+
return {
|
|
152
|
+
messages: collapseResult.messages,
|
|
153
|
+
totalTokensFreed: 0, // Already accounted for in collapse
|
|
154
|
+
layersApplied: ['collapse'],
|
|
155
|
+
boundaries: [{
|
|
156
|
+
layer: 'collapse',
|
|
157
|
+
tokensFreed: 0,
|
|
158
|
+
messagesRemoved: 0,
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
committed: collapseResult.committed,
|
|
161
|
+
}],
|
|
162
|
+
wasCompacted: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Fall through to reactive compact
|
|
167
|
+
const result = await this.reactive.tryRecoverFromError(ctx, error);
|
|
168
|
+
if (result.recovered) {
|
|
169
|
+
this.reactive.resetForTurn();
|
|
170
|
+
return {
|
|
171
|
+
messages: result.messages,
|
|
172
|
+
totalTokensFreed: result.tokensBefore - result.tokensAfter,
|
|
173
|
+
layersApplied: ['reactive'],
|
|
174
|
+
boundaries: [{
|
|
175
|
+
layer: 'reactive',
|
|
176
|
+
tokensFreed: result.tokensBefore - result.tokensAfter,
|
|
177
|
+
messagesRemoved: 0,
|
|
178
|
+
timestamp: new Date().toISOString(),
|
|
179
|
+
}],
|
|
180
|
+
wasCompacted: true,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
messages,
|
|
185
|
+
totalTokensFreed: 0,
|
|
186
|
+
layersApplied: [],
|
|
187
|
+
boundaries: [],
|
|
188
|
+
wasCompacted: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get the current auto-compact tracking state.
|
|
193
|
+
*/
|
|
194
|
+
getAutoCompactTracking() {
|
|
195
|
+
return this.auto.getTracking();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the number of consecutive auto-compact failures.
|
|
199
|
+
*/
|
|
200
|
+
getConsecutiveFailures() {
|
|
201
|
+
return this.auto.getConsecutiveFailures();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Reset the reactive compact's per-turn guard.
|
|
205
|
+
* Call at the start of each new turn.
|
|
206
|
+
*/
|
|
207
|
+
resetForTurn() {
|
|
208
|
+
this.reactive.resetForTurn();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Reset all compaction state.
|
|
212
|
+
*/
|
|
213
|
+
resetAll() {
|
|
214
|
+
for (const [, layer] of this.layers) {
|
|
215
|
+
try {
|
|
216
|
+
layer.reset();
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ─── Convenience Accessors ──────────────────────────────────────
|
|
224
|
+
getSnipLayer() { return this.snip; }
|
|
225
|
+
getMicroLayer() { return this.micro; }
|
|
226
|
+
getCollapseLayer() { return this.collapse; }
|
|
227
|
+
getAutoLayer() { return this.auto; }
|
|
228
|
+
getReactiveLayer() { return this.reactive; }
|
|
229
|
+
}
|
|
230
|
+
exports.CompactionOrchestrator = CompactionOrchestrator;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3: Context Collapse
|
|
3
|
+
*
|
|
4
|
+
* Incrementally archives messages into a collapse store with granular summaries.
|
|
5
|
+
* Unlike full auto-compact which produces one monolithic summary, context collapse
|
|
6
|
+
* preserves granular per-range summaries so more context is retained.
|
|
7
|
+
*
|
|
8
|
+
* Key properties:
|
|
9
|
+
* - Commits at ~90% of context window
|
|
10
|
+
* - Blocks at ~95% (hard limit)
|
|
11
|
+
* - Archives messages incrementally (not all at once)
|
|
12
|
+
* - Read-time projection over full history
|
|
13
|
+
* - Recovery: drains staged collapses on 413 errors (cheap)
|
|
14
|
+
* - When enabled, suppresses auto-compact to avoid racing
|
|
15
|
+
*/
|
|
16
|
+
import type { MessageObject } from '@codebolt/types/sdk';
|
|
17
|
+
import { type CompactionContext, type CompactionLayer, type CompactionLayerKind } from './types';
|
|
18
|
+
export interface ContextCollapseOptions {
|
|
19
|
+
/** Commit threshold as fraction of context window (default: 0.9) */
|
|
20
|
+
commitThreshold?: number;
|
|
21
|
+
/** Blocking threshold as fraction of context window (default: 0.95) */
|
|
22
|
+
blockingThreshold?: number;
|
|
23
|
+
/** Model token limit (default: 128000) */
|
|
24
|
+
modelTokenLimit?: number;
|
|
25
|
+
/** LLM role for granular summarization (default: 'summarizer') */
|
|
26
|
+
llmRole?: string;
|
|
27
|
+
/** Max summaries to keep in the collapse store (default: 20) */
|
|
28
|
+
maxSummaries?: number;
|
|
29
|
+
/** Enable logging (default: false) */
|
|
30
|
+
enableLogging?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare class ContextCollapse implements CompactionLayer {
|
|
33
|
+
readonly name: CompactionLayerKind;
|
|
34
|
+
private readonly options;
|
|
35
|
+
/** The collapse store: ordered list of collapsed ranges */
|
|
36
|
+
private store;
|
|
37
|
+
/** Staged collapses waiting for API confirmation */
|
|
38
|
+
private staged;
|
|
39
|
+
constructor(options?: ContextCollapseOptions);
|
|
40
|
+
shouldApply(ctx: CompactionContext): boolean;
|
|
41
|
+
apply(ctx: CompactionContext): Promise<CompactionContext>;
|
|
42
|
+
/**
|
|
43
|
+
* Drain staged collapses on API 413 errors (recovery path).
|
|
44
|
+
* Confirms all staged entries into the permanent store.
|
|
45
|
+
*/
|
|
46
|
+
drainStaged(): number;
|
|
47
|
+
/**
|
|
48
|
+
* Recovery from overflow: drain staged collapses and rebuild.
|
|
49
|
+
*/
|
|
50
|
+
recoverFromOverflow(messages: MessageObject[]): {
|
|
51
|
+
messages: MessageObject[];
|
|
52
|
+
committed: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Check if at blocking threshold.
|
|
56
|
+
*/
|
|
57
|
+
isAtBlockingLimit(messages: MessageObject[]): boolean;
|
|
58
|
+
reset(): void;
|
|
59
|
+
private findSplitPoint;
|
|
60
|
+
private buildSummaryMessages;
|
|
61
|
+
private generateGranularSummary;
|
|
62
|
+
private createStructuralSummary;
|
|
63
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 3: Context Collapse
|
|
4
|
+
*
|
|
5
|
+
* Incrementally archives messages into a collapse store with granular summaries.
|
|
6
|
+
* Unlike full auto-compact which produces one monolithic summary, context collapse
|
|
7
|
+
* preserves granular per-range summaries so more context is retained.
|
|
8
|
+
*
|
|
9
|
+
* Key properties:
|
|
10
|
+
* - Commits at ~90% of context window
|
|
11
|
+
* - Blocks at ~95% (hard limit)
|
|
12
|
+
* - Archives messages incrementally (not all at once)
|
|
13
|
+
* - Read-time projection over full history
|
|
14
|
+
* - Recovery: drains staged collapses on 413 errors (cheap)
|
|
15
|
+
* - When enabled, suppresses auto-compact to avoid racing
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ContextCollapse = void 0;
|
|
19
|
+
const types_1 = require("./types");
|
|
20
|
+
/** Default commit threshold (fraction of context window) */
|
|
21
|
+
const DEFAULT_COMMIT_THRESHOLD = 0.9;
|
|
22
|
+
/** Default blocking threshold */
|
|
23
|
+
const DEFAULT_BLOCKING_THRESHOLD = 0.95;
|
|
24
|
+
/** Minimum tokens to archive per collapse commit */
|
|
25
|
+
const MIN_COLLAPSE_TOKENS = 2000;
|
|
26
|
+
class ContextCollapse {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
var _a, _b, _c, _d, _e, _f;
|
|
29
|
+
this.name = 'collapse';
|
|
30
|
+
/** The collapse store: ordered list of collapsed ranges */
|
|
31
|
+
this.store = [];
|
|
32
|
+
/** Staged collapses waiting for API confirmation */
|
|
33
|
+
this.staged = [];
|
|
34
|
+
this.options = {
|
|
35
|
+
commitThreshold: (_a = options === null || options === void 0 ? void 0 : options.commitThreshold) !== null && _a !== void 0 ? _a : DEFAULT_COMMIT_THRESHOLD,
|
|
36
|
+
blockingThreshold: (_b = options === null || options === void 0 ? void 0 : options.blockingThreshold) !== null && _b !== void 0 ? _b : DEFAULT_BLOCKING_THRESHOLD,
|
|
37
|
+
modelTokenLimit: (_c = options === null || options === void 0 ? void 0 : options.modelTokenLimit) !== null && _c !== void 0 ? _c : 128000,
|
|
38
|
+
llmRole: (_d = options === null || options === void 0 ? void 0 : options.llmRole) !== null && _d !== void 0 ? _d : 'summarizer',
|
|
39
|
+
maxSummaries: (_e = options === null || options === void 0 ? void 0 : options.maxSummaries) !== null && _e !== void 0 ? _e : 20,
|
|
40
|
+
enableLogging: (_f = options === null || options === void 0 ? void 0 : options.enableLogging) !== null && _f !== void 0 ? _f : false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
shouldApply(ctx) {
|
|
44
|
+
if (!ctx.contextCollapseEnabled)
|
|
45
|
+
return false;
|
|
46
|
+
const estimator = new types_1.TokenEstimator();
|
|
47
|
+
const tokenCount = estimator.estimateForMessages(ctx.messages);
|
|
48
|
+
const threshold = this.options.commitThreshold * this.options.modelTokenLimit;
|
|
49
|
+
return tokenCount >= threshold;
|
|
50
|
+
}
|
|
51
|
+
async apply(ctx) {
|
|
52
|
+
if (!ctx.contextCollapseEnabled)
|
|
53
|
+
return ctx;
|
|
54
|
+
const estimator = new types_1.TokenEstimator();
|
|
55
|
+
const tokenCount = estimator.estimateForMessages(ctx.messages);
|
|
56
|
+
const commitThreshold = this.options.commitThreshold * this.options.modelTokenLimit;
|
|
57
|
+
if (tokenCount < commitThreshold) {
|
|
58
|
+
return ctx;
|
|
59
|
+
}
|
|
60
|
+
// Determine how many messages to archive
|
|
61
|
+
const messages = ctx.messages;
|
|
62
|
+
const targetTokens = commitThreshold * 0.8; // Target 80% of threshold after collapse
|
|
63
|
+
const currentTokens = tokenCount;
|
|
64
|
+
const tokensToArchive = currentTokens - targetTokens;
|
|
65
|
+
if (tokensToArchive < MIN_COLLAPSE_TOKENS) {
|
|
66
|
+
return ctx;
|
|
67
|
+
}
|
|
68
|
+
// Find the split point: archive from the beginning, after system messages
|
|
69
|
+
const splitIdx = this.findSplitPoint(messages, tokensToArchive, estimator);
|
|
70
|
+
if (splitIdx <= 0) {
|
|
71
|
+
return ctx;
|
|
72
|
+
}
|
|
73
|
+
// Archive the range
|
|
74
|
+
const toArchive = messages.slice(0, splitIdx);
|
|
75
|
+
const preserved = messages.slice(splitIdx);
|
|
76
|
+
// Generate granular summary
|
|
77
|
+
const summary = await this.generateGranularSummary(toArchive);
|
|
78
|
+
const entry = {
|
|
79
|
+
startIdx: 0,
|
|
80
|
+
endIdx: splitIdx,
|
|
81
|
+
summary,
|
|
82
|
+
originalTokens: estimator.estimateForMessages(toArchive),
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
staged: true,
|
|
85
|
+
};
|
|
86
|
+
// Add to staged (will be confirmed after successful API call)
|
|
87
|
+
this.staged.push(entry);
|
|
88
|
+
// Build projected view: summaries + preserved messages
|
|
89
|
+
const systemMessages = toArchive.filter(m => m.role === 'system');
|
|
90
|
+
const projectedMessages = [
|
|
91
|
+
...systemMessages,
|
|
92
|
+
...this.buildSummaryMessages(),
|
|
93
|
+
...preserved,
|
|
94
|
+
];
|
|
95
|
+
const tokensFreed = currentTokens - estimator.estimateForMessages(projectedMessages);
|
|
96
|
+
const boundary = {
|
|
97
|
+
layer: 'collapse',
|
|
98
|
+
tokensFreed: Math.max(0, tokensFreed),
|
|
99
|
+
messagesRemoved: toArchive.length - systemMessages.length,
|
|
100
|
+
timestamp: entry.timestamp,
|
|
101
|
+
};
|
|
102
|
+
if (this.options.enableLogging) {
|
|
103
|
+
console.log(`[ContextCollapse] Collapsed ${toArchive.length} messages (~${entry.originalTokens} tokens). Freed ~${tokensFreed} tokens.`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
...ctx,
|
|
107
|
+
messages: projectedMessages,
|
|
108
|
+
compactionHistory: [...(ctx.compactionHistory || []), boundary],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Drain staged collapses on API 413 errors (recovery path).
|
|
113
|
+
* Confirms all staged entries into the permanent store.
|
|
114
|
+
*/
|
|
115
|
+
drainStaged() {
|
|
116
|
+
if (this.staged.length === 0)
|
|
117
|
+
return 0;
|
|
118
|
+
const count = this.staged.length;
|
|
119
|
+
for (const entry of this.staged) {
|
|
120
|
+
entry.staged = false;
|
|
121
|
+
this.store.push(entry);
|
|
122
|
+
}
|
|
123
|
+
// Trim store to max size
|
|
124
|
+
if (this.store.length > this.options.maxSummaries) {
|
|
125
|
+
this.store = this.store.slice(-this.options.maxSummaries);
|
|
126
|
+
}
|
|
127
|
+
this.staged = [];
|
|
128
|
+
return count;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Recovery from overflow: drain staged collapses and rebuild.
|
|
132
|
+
*/
|
|
133
|
+
recoverFromOverflow(messages) {
|
|
134
|
+
const committed = this.drainStaged();
|
|
135
|
+
if (committed === 0) {
|
|
136
|
+
return { messages, committed: 0 };
|
|
137
|
+
}
|
|
138
|
+
// Rebuild with confirmed summaries
|
|
139
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
140
|
+
const nonSystem = messages.filter(m => m.role !== 'system');
|
|
141
|
+
const rebuilt = [
|
|
142
|
+
...systemMessages,
|
|
143
|
+
...this.buildSummaryMessages(),
|
|
144
|
+
...nonSystem,
|
|
145
|
+
];
|
|
146
|
+
return { messages: rebuilt, committed };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if at blocking threshold.
|
|
150
|
+
*/
|
|
151
|
+
isAtBlockingLimit(messages) {
|
|
152
|
+
const estimator = new types_1.TokenEstimator();
|
|
153
|
+
const tokenCount = estimator.estimateForMessages(messages);
|
|
154
|
+
const blockingLimit = this.options.blockingThreshold * this.options.modelTokenLimit;
|
|
155
|
+
return tokenCount >= blockingLimit;
|
|
156
|
+
}
|
|
157
|
+
reset() {
|
|
158
|
+
this.store = [];
|
|
159
|
+
this.staged = [];
|
|
160
|
+
}
|
|
161
|
+
// ─── Private Helpers ──────────────────────────────────────────────
|
|
162
|
+
findSplitPoint(messages, tokensToArchive, estimator) {
|
|
163
|
+
var _a, _b;
|
|
164
|
+
let accumulated = 0;
|
|
165
|
+
// Skip system messages at the beginning
|
|
166
|
+
let startIdx = 0;
|
|
167
|
+
while (startIdx < messages.length && ((_a = messages[startIdx]) === null || _a === void 0 ? void 0 : _a.role) === 'system') {
|
|
168
|
+
startIdx++;
|
|
169
|
+
}
|
|
170
|
+
for (let i = startIdx; i < messages.length; i++) {
|
|
171
|
+
const msg = messages[i];
|
|
172
|
+
if (!msg)
|
|
173
|
+
continue;
|
|
174
|
+
const content = typeof msg.content === 'string'
|
|
175
|
+
? msg.content
|
|
176
|
+
: JSON.stringify((_b = msg.content) !== null && _b !== void 0 ? _b : '');
|
|
177
|
+
accumulated += estimator.estimate(content) + 4;
|
|
178
|
+
// Don't split in the middle of a tool call/response pair
|
|
179
|
+
if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (msg.role === 'tool' || msg.tool_call_id) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Safe split point: user message or assistant without pending tools
|
|
186
|
+
if (accumulated >= tokensToArchive) {
|
|
187
|
+
return i + 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return 0; // No safe split point found
|
|
191
|
+
}
|
|
192
|
+
buildSummaryMessages() {
|
|
193
|
+
const allEntries = [...this.store, ...this.staged];
|
|
194
|
+
if (allEntries.length === 0)
|
|
195
|
+
return [];
|
|
196
|
+
const combinedSummary = allEntries
|
|
197
|
+
.map((entry, i) => `--- Collapsed Section ${i + 1} ---\n${entry.summary}`)
|
|
198
|
+
.join('\n\n');
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
role: 'user',
|
|
202
|
+
content: `[Context Summary - The following sections were collapsed to manage context length. Key information has been preserved.]\n\n${combinedSummary}`,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
role: 'assistant',
|
|
206
|
+
content: 'Understood. I have the collapsed context summary and will continue from the current state.',
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
async generateGranularSummary(messages) {
|
|
211
|
+
var _a, _b, _c, _d, _e;
|
|
212
|
+
const estimator = new types_1.TokenEstimator();
|
|
213
|
+
// For small ranges, create a structural summary without LLM
|
|
214
|
+
const tokenCount = estimator.estimateForMessages(messages);
|
|
215
|
+
if (tokenCount < 5000) {
|
|
216
|
+
return this.createStructuralSummary(messages);
|
|
217
|
+
}
|
|
218
|
+
// For larger ranges, try LLM summarization
|
|
219
|
+
try {
|
|
220
|
+
const codebolt = require('@codebolt/codeboltjs');
|
|
221
|
+
const historyText = messages
|
|
222
|
+
.map((msg, i) => {
|
|
223
|
+
var _a;
|
|
224
|
+
const content = typeof msg.content === 'string'
|
|
225
|
+
? msg.content
|
|
226
|
+
: JSON.stringify((_a = msg.content) !== null && _a !== void 0 ? _a : '');
|
|
227
|
+
const truncated = content.length > 500
|
|
228
|
+
? content.slice(0, 250) + '...[truncated]...' + content.slice(-250)
|
|
229
|
+
: content;
|
|
230
|
+
return `[${i}] ${msg.role}: ${truncated}`;
|
|
231
|
+
})
|
|
232
|
+
.join('\n');
|
|
233
|
+
const prompt = `Summarize this conversation segment concisely. Preserve: key decisions, file paths, code changes, errors encountered, current task state. Be factual and specific.
|
|
234
|
+
|
|
235
|
+
${historyText}`;
|
|
236
|
+
const response = await codebolt.llm.inference({
|
|
237
|
+
messages: [
|
|
238
|
+
{ role: 'system', content: 'You are a precise conversation summarizer. Be concise but complete.' },
|
|
239
|
+
{ role: 'user', content: prompt },
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
const summary = ((_a = response === null || response === void 0 ? void 0 : response.completion) === null || _a === void 0 ? void 0 : _a.content) || ((_e = (_d = (_c = (_b = response === null || response === void 0 ? void 0 : response.completion) === null || _b === void 0 ? void 0 : _b.choices) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content);
|
|
243
|
+
if (summary && typeof summary === 'string' && summary.trim().length > 0) {
|
|
244
|
+
return summary.trim();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Fall back to structural summary
|
|
249
|
+
}
|
|
250
|
+
return this.createStructuralSummary(messages);
|
|
251
|
+
}
|
|
252
|
+
createStructuralSummary(messages) {
|
|
253
|
+
var _a, _b;
|
|
254
|
+
const parts = [];
|
|
255
|
+
let userMessages = 0;
|
|
256
|
+
let toolCalls = 0;
|
|
257
|
+
const files = new Set();
|
|
258
|
+
for (const msg of messages) {
|
|
259
|
+
if (!msg)
|
|
260
|
+
continue;
|
|
261
|
+
if (msg.role === 'user' && !msg.tool_call_id)
|
|
262
|
+
userMessages++;
|
|
263
|
+
if (msg.tool_calls)
|
|
264
|
+
toolCalls += msg.tool_calls.length;
|
|
265
|
+
// Extract file paths from content
|
|
266
|
+
const content = typeof msg.content === 'string'
|
|
267
|
+
? msg.content
|
|
268
|
+
: JSON.stringify((_a = msg.content) !== null && _a !== void 0 ? _a : '');
|
|
269
|
+
const filePathMatches = content.match(/['"`](\/?[^\s'"`]+\.[a-zA-Z0-9]+)['"`]/g);
|
|
270
|
+
if (filePathMatches) {
|
|
271
|
+
for (const match of filePathMatches) {
|
|
272
|
+
files.add(match.replace(/['"`]/g, ''));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
parts.push(`Messages: ${messages.length} (${userMessages} user, ${toolCalls} tool calls)`);
|
|
277
|
+
if (files.size > 0) {
|
|
278
|
+
parts.push(`Files referenced: ${Array.from(files).slice(0, 10).join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
// Include first and last user message content (truncated)
|
|
281
|
+
const firstUser = messages.find(m => m.role === 'user' && !m.tool_call_id);
|
|
282
|
+
if (firstUser) {
|
|
283
|
+
const content = typeof firstUser.content === 'string'
|
|
284
|
+
? firstUser.content
|
|
285
|
+
: JSON.stringify((_b = firstUser.content) !== null && _b !== void 0 ? _b : '');
|
|
286
|
+
parts.push(`First user message: ${content.slice(0, 200)}${content.length > 200 ? '...' : ''}`);
|
|
287
|
+
}
|
|
288
|
+
return parts.join('\n');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
exports.ContextCollapse = ContextCollapse;
|