fix(native-chat): show Codex compaction markers in transcripts (#14765)

Codex paginated rollouts persist an item_completed ContextCompaction item
with no content, and legacy rollouts persist an event_msg context_compacted.
The Codex decoder dropped both, so the native Chat timeline never showed
that context was compacted. Emit a system row with the shared compaction
copy and presentation so it renders as the compaction divider.

Fixes #14188

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
erish
2026-10-27 05:52:02 -07:00
committed by GitHub
co-authored by Neil
parent 5666ad4a84
commit d08aa8f1fb
2 changed files with 33 additions and 1 deletions
@@ -3,6 +3,7 @@ import {
type NativeChatBlock,
type NativeChatMessage
} from '../../shared/native-chat-types'
import { NATIVE_CHAT_NOTICE_COPY } from '../../shared/native-chat-notice'
import {
asRecord,
extractString,
@@ -132,6 +133,10 @@ function codexEventMessage(
source: 'transcript'
}
}
// Legacy rollouts persist this event; paginated rollouts persist a ContextCompaction item instead.
if (payload.type === 'context_compacted') {
return codexCompactionMarker(id, timestamp)
}
if (payload.type === 'item_completed') {
return codexCompletedTurnItem(payload, id, timestamp)
}
@@ -160,6 +165,9 @@ function codexCompletedTurnItem(
return null
}
const id = extractString(item.id) ?? fallbackId
if (item.type === 'ContextCompaction') {
return codexCompactionMarker(id, timestamp)
}
const blocks = codexTurnItemBlocks(item.content)
if (blocks.length === 0) {
return null
@@ -173,6 +181,18 @@ function codexCompletedTurnItem(
return null
}
function codexCompactionMarker(id: string, timestamp: number | null): NativeChatMessage {
return {
id,
role: 'system',
blocks: [
{ type: 'text', text: NATIVE_CHAT_NOTICE_COPY.compaction, presentation: 'compaction' }
],
timestamp,
source: 'transcript'
}
}
function codexTurnItemBlocks(content: unknown): NativeChatBlock[] {
if (!Array.isArray(content)) {
return []
@@ -134,6 +134,7 @@ describe('Codex transcript history modes', () => {
},
'2026-08-09T10:00:01.000Z'
),
completedItem({ type: 'ContextCompaction', id: 'compaction-1' }, '2026-08-09T10:00:01.500Z'),
{
type: 'response_item',
payload: {
@@ -172,6 +173,12 @@ describe('Codex transcript history modes', () => {
{ type: 'image-ref', path: '/tmp/reference.png' }
]
},
{
id: 'compaction-1',
role: 'system',
timestamp: Date.parse('2026-08-09T10:00:01.500Z'),
blocks: [{ type: 'text', text: 'Context compacted', presentation: 'compaction' }]
},
{
id: 'assistant-1',
role: 'assistant',
@@ -179,7 +186,6 @@ describe('Codex transcript history modes', () => {
}
]
})
expect('messages' in result && result.messages).toHaveLength(2)
expect(tail).toMatchObject({ messages: 'messages' in result ? result.messages : [] })
})
@@ -194,6 +200,8 @@ describe('Codex transcript history modes', () => {
}
},
{ type: 'event_msg', payload: { type: 'user_message', message: 'Prompt' } },
{ type: 'compacted', payload: { message: 'summary', replacement_history: [] } },
{ type: 'event_msg', payload: { type: 'context_compacted' } },
{ type: 'event_msg', payload: { type: 'agent_message', message: 'Response' } },
{
type: 'response_item',
@@ -210,6 +218,10 @@ describe('Codex transcript history modes', () => {
expect(result).toMatchObject({
messages: [
{ role: 'user', blocks: [{ type: 'text', text: 'Prompt' }] },
{
role: 'system',
blocks: [{ type: 'text', text: 'Context compacted', presentation: 'compaction' }]
},
{ role: 'assistant', blocks: [{ type: 'text', text: 'Response' }] }
]
})