Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,45 @@ describe('runAgentStep - unfinished tool calls', () => {
capturedMessages.filter((message) => message.role === 'tool'),
).toHaveLength(2)
})

it('stubs stale bulky read results but keeps small and newest ones verbatim', async () => {
const readResult = (toolCallId: string, content: string): Message => ({
role: 'tool',
toolCallId,
toolName: 'read_files',
content: [{ type: 'json', value: [{ path: 'src/app.ts', content }] }],
})
const bigRead = (marker: string) => `${marker}\n`.repeat(1_000)
const agentState = await runWithHistory([
userMessage('do the thing'),
// Five bulky reads (~17k each) so past-window savings clear the
// pass's 20k floor; the newest-3 window is r3, r4 and the small
// read, which rides along despite its size floor being separate.
...['first', 'second', 'third', 'fourth', 'fifth'].flatMap((name, i) => [
toolCallMessage(`r${i}`),
readResult(`r${i}`, bigRead(`const ${name} = ${i};`)),
]),
toolCallMessage('small'),
readResult('small', 'export const tiny = 1'),
userMessage('are you there?'),
])

const request = JSON.stringify(capturedMessages)
// The three oldest bulky reads (newest-3 window = r3, r4, small) were
// replaced by the omit stub already understood by getPreviouslyReadFiles
// and the fitToolResults re-read hints...
expect(request).not.toContain('const first = 0;')
expect(request).not.toContain('const second = 1;')
expect(request).not.toContain('const third = 2;')
expect(request).toContain('contentOmittedForLength')
// ...the two newest bulky reads stay verbatim...
expect(request.split('const fourth = 3;').length).toBeGreaterThan(10)
expect(request.split('const fifth = 4;').length).toBeGreaterThan(10)
// ...and the small read rides along inside the window.
expect(request).toContain('export const tiny = 1')
// The stored history is healed to the same state.
const stored = JSON.stringify(agentState.messageHistory)
expect(stored).not.toContain('const first = 0;')
expect(stored).toContain('export const tiny = 1')
})
})
22 changes: 17 additions & 5 deletions packages/agent-runtime/src/run-agent-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ import {
assertUserContentSize,
UserContentTooLargeError,
} from './util/context-size-guard'
import { simplifyOldToolResults } from './util/simplify-tool-results'
import {
countTokens,
countTokensJson,
Expand Down Expand Up @@ -384,9 +385,21 @@ export const runAgentStep = async (
// and replayed, that one orphan would fail every later turn, so drop it here
// — the single point every step's request is built — and assign the cleaned
// history back so the checkpointed state is valid too.
const history = dropUnansweredToolCalls(
expireMessages(agentState.messageHistory, 'agentStep'),
)
// In a long session, stub tool results older than the newest few with the
// omit shapes fitToolResults/getPreviouslyReadFiles already understand:
// read_files and terminal outputs up to 100k/50k chars otherwise stay
// verbatim in history and are re-sent on every request until compaction.
// The pass waits for real past-window volume (see simplifyOldToolResults),
// so a history that still fits the window comfortably keeps everything;
// the newest results and anything this step later appends are untouched,
// and unreplaced messages keep reference identity for the extension check
// below.
const history = simplifyOldToolResults({
messages: dropUnansweredToolCalls(
expireMessages(agentState.messageHistory, 'agentStep'),
),
logger,
})

const agentMessagesUntruncated = buildArray<Message>(
...history,
Expand Down Expand Up @@ -437,8 +450,7 @@ export const runAgentStep = async (
const systemTokens = countTokens(system)

let cacheDebugCorrelation:
| ReturnType<typeof createCacheDebugSnapshot>
| undefined
ReturnType<typeof createCacheDebugSnapshot> | undefined
if (CACHE_DEBUG_FULL_LOGGING) {
try {
cacheDebugCorrelation = createCacheDebugSnapshot({
Expand Down
Loading
Loading