fix(codex): announce automatic context compaction when it starts (#1999)

This commit is contained in:
Junmo Kim
2026-10-13 08:20:02 +08:00
committed by GitHub
parent b3a9c0cddf
commit 5847b034e5
6 changed files with 398 additions and 11 deletions
@@ -1042,6 +1042,75 @@ describe('AppServerEventConverter', () => {
]);
});
it('converts compaction start notifications into scoped compaction_started events', () => {
const converter = new AppServerEventConverter();
const events = converter.handleNotification('item/started', {
threadId: 'thread-1',
turnId: 'turn-1',
item: { id: 'compact-item-1', type: 'contextCompaction' }
});
expect(events).toEqual([{
type: 'compaction_started',
thread_id: 'thread-1',
turn_id: 'turn-1',
compaction_id: 'compact-item-1'
}]);
});
it('dedups duplicate compaction starts per compaction id but allows successive compactions', () => {
const converter = new AppServerEventConverter();
const params = {
threadId: 'thread-1',
turnId: 'turn-1',
item: { id: 'compact-item-1', type: 'contextCompaction' }
};
expect(converter.handleNotification('item/started', params)).toHaveLength(1);
expect(converter.handleNotification('item/started', params)).toEqual([]);
expect(converter.handleNotification('codex/event/item_started', {
msg: {
type: 'item_started',
thread_id: 'thread-1',
turn_id: 'turn-1',
item_id: 'compact-item-1',
item: { id: 'compact-item-1', type: 'contextCompaction' }
}
})).toEqual([]);
expect(converter.handleNotification('item/started', {
threadId: 'thread-1',
turnId: 'turn-1',
item: { id: 'compact-item-2', type: 'contextCompaction' }
})).toHaveLength(1);
});
it('drops malformed compaction starts without an item id', () => {
const converter = new AppServerEventConverter();
expect(converter.handleNotification('item/started', {
threadId: 'thread-1',
item: { type: 'contextCompaction' }
})).toEqual([]);
});
it('scopes compaction start dedup by thread so a reused item id still announces', () => {
const converter = new AppServerEventConverter();
expect(converter.handleNotification('item/started', {
threadId: 'thread-1',
turnId: 'turn-1',
item: { id: 'compact-item-1', type: 'contextCompaction' }
})).toHaveLength(1);
expect(converter.handleNotification('item/started', {
threadId: 'thread-2',
turnId: 'turn-9',
item: { id: 'compact-item-1', type: 'contextCompaction' }
})).toEqual([{
type: 'compaction_started',
thread_id: 'thread-2',
turn_id: 'turn-9',
compaction_id: 'compact-item-1'
}]);
});
it('ignores compacted notifications without thread ids', () => {
const converter = new AppServerEventConverter();
@@ -1118,4 +1187,18 @@ describe('AppServerEventConverter', () => {
debug.mockRestore();
});
it('clears compaction start dedup on reset', () => {
const converter = new AppServerEventConverter();
const params = { threadId: 'thread-1', turnId: 'turn-1', item: { id: 'compact-item-1', type: 'contextCompaction' } };
expect(converter.handleNotification('item/started', params)).toHaveLength(1);
converter.reset();
expect(converter.handleNotification('item/started', params)).toHaveLength(1);
});
it('announces legacy compaction start without scope', () => {
const converter = new AppServerEventConverter();
const events = converter.handleNotification('item/started', { item: { id: 'compact-legacy', type: 'contextCompaction' } });
expect(events).toEqual([{ type: 'compaction_started', compaction_id: 'compact-legacy' }]);
});
});
@@ -502,6 +502,7 @@ export class AppServerEventConverter {
private readonly lastCommandOutputDeltaByItemId = new Map<string, string>();
private readonly rawAgentToolCallIds = new Set<string>();
private readonly rawAgentToolNames = new Map<string, string>();
private readonly announcedCompactionStarts = new Set<string>();
private handleWrappedCodexEvent(paramsRecord: Record<string, unknown>): ConvertedEvent[] | null {
const msg = asRecord(paramsRecord.msg);
@@ -970,6 +971,15 @@ export class AppServerEventConverter {
}
if (itemType === 'contextcompaction') {
if (method === 'item/started') {
const compactionKey = `${asString(eventScope.thread_id) ?? ''}\0${itemId}`;
if (this.announcedCompactionStarts.has(compactionKey)) {
return events;
}
this.announcedCompactionStarts.add(compactionKey);
events.push(scoped({ type: 'compaction_started', compaction_id: itemId }));
return events;
}
if (method === 'item/completed') {
const threadId = asString(eventScope.thread_id);
const turnId = asString(eventScope.turn_id);
@@ -1218,5 +1228,6 @@ export class AppServerEventConverter {
this.lastCommandOutputDeltaByItemId.clear();
this.rawAgentToolCallIds.clear();
this.rawAgentToolNames.clear();
this.announcedCompactionStarts.clear();
}
}