Skip to content

Commit d105bd2

Browse files
bhavyausCopilot
andauthored
Add transcript line count to compaction summary (#4811)
* Add transcript line count to compaction summary Expose getLineCount() on ISessionTranscriptService so the summarization prompt can tell the model how many lines the transcript had before compaction. This helps the model target the right region when reading the transcript file for context recovery. - Track lineCount on IActiveSession, increment in _bufferEntry - On session resume, count existing lines from the file on disk - Pass transcriptLineCount through SummarizedConversationHistory props to SummaryMessageElement - Include 'The transcript had N lines before compaction' in the hint * Update src/extension/prompts/node/agent/summarizedConversationHistory.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 87d7c8a commit d105bd2

3 files changed

Lines changed: 30 additions & 4 deletions

File tree

src/extension/chat/vscode-node/sessionTranscriptService.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ interface IActiveSession {
3838
readonly buffer: string[];
3939
/** Chain of flush operations to serialize writes. */
4040
flushPromise: Promise<void>;
41+
/** Running count of lines in the transcript (flushed + buffered). */
42+
lineCount: number;
4143
}
4244

4345
export class SessionTranscriptService implements ISessionTranscriptService {
@@ -89,6 +91,7 @@ export class SessionTranscriptService implements ISessionTranscriptService {
8991
lastEntryId: null,
9092
buffer: [],
9193
flushPromise: Promise.resolve(),
94+
lineCount: 0,
9295
};
9396
this._activeSessions.set(sessionId, session);
9497

@@ -102,7 +105,12 @@ export class SessionTranscriptService implements ISessionTranscriptService {
102105
}
103106

104107
if (fileAlreadyExists) {
105-
// Session file exists — we're resuming; no need to replay history
108+
// Session file exists — we're resuming; count existing lines so getLineCount stays accurate
109+
try {
110+
const content = await fs.promises.readFile(fileUri.fsPath, 'utf-8');
111+
session.lineCount = content.split('\n').filter(l => l.length > 0).length;
112+
} catch {
113+
}
106114
return;
107115
}
108116

@@ -216,6 +224,10 @@ export class SessionTranscriptService implements ISessionTranscriptService {
216224
return this._activeSessions.get(sessionId)?.uri;
217225
}
218226

227+
getLineCount(sessionId: string): number | undefined {
228+
return this._activeSessions.get(sessionId)?.lineCount;
229+
}
230+
219231
isTranscriptUri(uri: URI): boolean {
220232
const dir = this._getTranscriptsDir();
221233
if (!dir) {
@@ -352,6 +364,7 @@ export class SessionTranscriptService implements ISessionTranscriptService {
352364
} as TranscriptEntry;
353365

354366
session.lastEntryId = id;
367+
session.lineCount++;
355368
session.buffer.push(JSON.stringify(fullEntry) + '\n');
356369
}
357370

src/extension/prompts/node/agent/summarizedConversationHistory.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class ConversationHistory extends PromptElement<SummarizedAgentHistoryProps> {
248248
}
249249

250250
if (summaryForCurrentTurn) {
251-
history.push(<SummaryMessageElement endpoint={this.props.endpoint} summaryText={summaryForCurrentTurn} transcriptPath={this.props.transcriptPath} />);
251+
history.push(<SummaryMessageElement endpoint={this.props.endpoint} summaryText={summaryForCurrentTurn} transcriptPath={this.props.transcriptPath} transcriptLineCount={this.props.transcriptLineCount} />);
252252

253253
return (<PrioritizedList priority={this.props.priority} descending={false} passPriority={true}>
254254
{history.reverse()}
@@ -308,7 +308,7 @@ class ConversationHistory extends PromptElement<SummarizedAgentHistoryProps> {
308308

309309
if (summaryForTurn) {
310310
// We have a summary for a tool call round that was part of this turn
311-
turnComponents.push(<SummaryMessageElement endpoint={this.props.endpoint} summaryText={summaryForTurn.text} transcriptPath={this.props.transcriptPath} />);
311+
turnComponents.push(<SummaryMessageElement endpoint={this.props.endpoint} summaryText={summaryForTurn.text} transcriptPath={this.props.transcriptPath} transcriptLineCount={this.props.transcriptLineCount} />);
312312
} else if (!turn.isContinuation) {
313313
turnComponents.push(<AgentUserMessage flexGrow={1} {...getUserMessagePropsFromTurn(turn, this.props.endpoint, {
314314
userQueryTagName: this.props.userQueryTagName,
@@ -410,6 +410,8 @@ export interface SummarizedAgentHistoryProps extends BasePromptElementProps, Age
410410
readonly summarizationSource?: 'background' | 'foreground';
411411
/** Path to the conversation transcript JSONL file, used to inform the model after summarization */
412412
readonly transcriptPath?: string;
413+
/** Number of lines in the transcript at the time of compaction */
414+
readonly transcriptLineCount?: number;
413415
}
414416

415417
/**
@@ -458,13 +460,15 @@ export class SummarizedConversationHistory extends PromptElement<SummarizedAgent
458460

459461
// Resolve transcript path and flush to disk so the model can read the up-to-date file
460462
let transcriptPath: string | undefined;
463+
let transcriptLineCount: number | undefined;
461464
if (transcriptLookupEnabled) {
462465
const sessionId = this.props.promptContext.conversation?.sessionId;
463466
if (sessionId) {
464467
const transcriptUri = this.sessionTranscriptService.getTranscriptPath(sessionId);
465468
if (transcriptUri) {
466469
await this.sessionTranscriptService.flush(sessionId);
467470
transcriptPath = transcriptUri.fsPath;
471+
transcriptLineCount = this.sessionTranscriptService.getLineCount(sessionId);
468472
}
469473
}
470474
}
@@ -475,6 +479,7 @@ export class SummarizedConversationHistory extends PromptElement<SummarizedAgent
475479
{...this.props}
476480
promptContext={promptContext}
477481
transcriptPath={transcriptPath}
482+
transcriptLineCount={transcriptLineCount}
478483
enableCacheBreakpoints={this.props.enableCacheBreakpoints} />
479484
</>;
480485
}
@@ -1002,6 +1007,7 @@ interface SummaryMessageProps extends BasePromptElementProps {
10021007
readonly summaryText: string;
10031008
readonly endpoint: IChatEndpoint;
10041009
readonly transcriptPath?: string;
1010+
readonly transcriptLineCount?: number;
10051011
}
10061012

10071013
class SummaryMessageElement extends PromptElement<SummaryMessageProps> {
@@ -1010,7 +1016,7 @@ class SummaryMessageElement extends PromptElement<SummaryMessageProps> {
10101016
<Tag name='conversation-summary'>
10111017
{this.props.summaryText}
10121018
</Tag>
1013-
{this.props.transcriptPath && <><br />If you need specific details from before compaction (such as exact code snippets, error messages, tool results, or content you previously generated), use the {ToolName.ReadFile} tool to look up the full uncompacted conversation transcript at: {this.props.transcriptPath}</>}
1019+
{this.props.transcriptPath && <><br />If you need specific details from before compaction (such as exact code snippets, error messages, tool results, or content you previously generated), use the {ToolName.ReadFile} tool to look up the full uncompacted conversation transcript at: "{this.props.transcriptPath}"{this.props.transcriptLineCount !== undefined && <><br />At the time of this request, the transcript has {this.props.transcriptLineCount} lines.</>}<br />Example usage: {ToolName.ReadFile}(filePath: "{this.props.transcriptPath}")</>}
10141020
{this.props.endpoint.family === 'gpt-4.1' && <Tag name='reminderInstructions'>
10151021
<DefaultOpenAIKeepGoingReminder />
10161022
</Tag>}

src/platform/chat/common/sessionTranscriptService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ export interface ISessionTranscriptService {
237237
*/
238238
getTranscriptPath(sessionId: string): URI | undefined;
239239

240+
/**
241+
* Get the current number of lines in the transcript for a session.
242+
* Returns `undefined` if the session is not active.
243+
*/
244+
getLineCount(sessionId: string): number | undefined;
245+
240246
/**
241247
* Remove transcript files for sessions that are no longer active,
242248
* keeping at most `maxRetained` most-recent ended sessions.
@@ -263,6 +269,7 @@ export class NullSessionTranscriptService implements ISessionTranscriptService {
263269
async flush(): Promise<void> { }
264270
async endSession(): Promise<void> { }
265271
getTranscriptPath(): URI | undefined { return undefined; }
272+
getLineCount(): number | undefined { return undefined; }
266273
async cleanupOldTranscripts(): Promise<void> { }
267274
isTranscriptUri(): boolean { return false; }
268275
}

0 commit comments

Comments
 (0)