mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
fix(codex): announce automatic context compaction when it starts (#1999)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user