Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/backgroundSummarizer.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken, CancellationTokenSource } from '../../../../util/vs/base/common/cancellation';
7
8
/**
9
* State machine for background conversation summarization.
10
*
11
* Lifecycle:
12
* Idle → InProgress → Completed / Failed
13
* ↓ ↓
14
* (consumeAndReset → Idle)
15
* Failed → InProgress (retry)
16
*/
17
18
export const enum BackgroundSummarizationState {
19
/** No summarization running. */
20
Idle = 'Idle',
21
/** An LLM summarization request is in flight. */
22
InProgress = 'InProgress',
23
/** Summarization finished successfully — summary text is available. */
24
Completed = 'Completed',
25
/** Summarization failed. */
26
Failed = 'Failed',
27
}
28
29
export interface IBackgroundSummarizationResult {
30
readonly summary: string;
31
readonly toolCallRoundId: string;
32
readonly promptTokens?: number;
33
readonly promptCacheTokens?: number;
34
readonly outputTokens?: number;
35
readonly durationMs?: number;
36
readonly model?: string;
37
readonly summarizationMode?: string;
38
readonly numRounds?: number;
39
readonly numRoundsSinceLastSummarization?: number;
40
}
41
42
/**
43
* Thresholds used by {@link shouldKickOffBackgroundSummarization}. Exported so
44
* tests can reference the same numbers without repeating them.
45
*/
46
export const BackgroundSummarizationThresholds = {
47
/** Trigger ratio for the non-inline path (no prompt-cache benefit). */
48
base: 0.80,
49
/** Minimum of the jittered warm-cache range for the inline path. */
50
warmJitterMin: 0.78,
51
/** Width of the jittered warm-cache range; together with `warmJitterMin` yields [0.78, 0.82). */
52
warmJitterSpan: 0.04,
53
/**
54
* Cold-cache emergency ratio for the inline path. Above this we kick off
55
* even without a warmed cache to avoid forcing a foreground sync compaction
56
* on the next render. Tuned low enough that long-running sessions stay
57
* ahead of the budget without relying on foreground compaction.
58
*/
59
emergency: 0.90,
60
} as const;
61
62
/**
63
* Decide whether to kick off post-render background compaction.
64
*
65
* For the inline-summarization path prompt-cache parity matters, so we:
66
* - require a completed tool call in this turn ("warm" cache) before
67
* firing at the normal, jittered ~0.80 threshold;
68
* - allow an emergency kick-off at >= 0.90 even with a cold cache to
69
* avoid forcing a foreground sync compaction on the next render.
70
*
71
* The jitter range straddles the historical 0.80 threshold (not "lower the
72
* bar") — the goal is to avoid always firing at the exact same boundary,
73
* not to kick off systematically earlier.
74
*
75
* The non-inline path forks its own prompt (no cache benefit) and keeps the
76
* simple >= 0.80 behavior. `rng` is only consumed on the warm-cache inline
77
* branch, which keeps deterministic tests straightforward.
78
*/
79
export function shouldKickOffBackgroundSummarization(
80
postRenderRatio: number,
81
useInlineSummarization: boolean,
82
cacheWarm: boolean,
83
rng: () => number,
84
): boolean {
85
const t = BackgroundSummarizationThresholds;
86
if (!useInlineSummarization) {
87
return postRenderRatio >= t.base;
88
}
89
if (!cacheWarm) {
90
return postRenderRatio >= t.emergency;
91
}
92
const jittered = t.warmJitterMin + rng() * t.warmJitterSpan;
93
return postRenderRatio >= jittered;
94
}
95
96
/**
97
* Tracks a single background summarization pass for one chat session.
98
*
99
* The singleton `AgentIntent` owns one instance per session (keyed by
100
* `sessionId`). `AgentIntentInvocation.buildPrompt` queries the state
101
* on every tool-call iteration to decide whether to start, wait for, or
102
* apply a background summary.
103
*/
104
export class BackgroundSummarizer {
105
106
private _state: BackgroundSummarizationState = BackgroundSummarizationState.Idle;
107
private _result: IBackgroundSummarizationResult | undefined;
108
private _error: unknown;
109
private _promise: Promise<void> | undefined;
110
private _cts: CancellationTokenSource | undefined;
111
112
readonly modelMaxPromptTokens: number;
113
114
get state(): BackgroundSummarizationState { return this._state; }
115
get error(): unknown { return this._error; }
116
117
get token() { return this._cts?.token; }
118
119
constructor(modelMaxPromptTokens: number) {
120
this.modelMaxPromptTokens = modelMaxPromptTokens;
121
}
122
123
start(work: (token: CancellationToken) => Promise<IBackgroundSummarizationResult>, parentToken?: CancellationToken): void {
124
if (this._state !== BackgroundSummarizationState.Idle && this._state !== BackgroundSummarizationState.Failed) {
125
return; // already running or completed
126
}
127
128
this._state = BackgroundSummarizationState.InProgress;
129
this._error = undefined;
130
this._cts = new CancellationTokenSource(parentToken);
131
const token = this._cts.token;
132
this._promise = work(token).then(
133
result => {
134
if (this._state !== BackgroundSummarizationState.InProgress) {
135
return; // cancelled while in flight
136
}
137
this._result = result;
138
this._state = BackgroundSummarizationState.Completed;
139
},
140
err => {
141
if (this._state !== BackgroundSummarizationState.InProgress) {
142
return; // cancelled while in flight
143
}
144
this._error = err;
145
this._state = BackgroundSummarizationState.Failed;
146
},
147
);
148
}
149
150
async waitForCompletion(): Promise<void> {
151
if (this._promise) {
152
await this._promise;
153
}
154
}
155
156
consumeAndReset(): IBackgroundSummarizationResult | undefined {
157
if (this._state === BackgroundSummarizationState.InProgress) {
158
return undefined;
159
}
160
const result = this._result;
161
this._state = BackgroundSummarizationState.Idle;
162
this._result = undefined;
163
this._error = undefined;
164
this._promise = undefined;
165
this._cts?.dispose();
166
this._cts = undefined;
167
return result;
168
}
169
170
cancel(): void {
171
this._cts?.cancel();
172
this._cts?.dispose();
173
this._cts = undefined;
174
this._state = BackgroundSummarizationState.Idle;
175
this._result = undefined;
176
this._error = undefined;
177
this._promise = undefined;
178
}
179
}
180
181