Skip to content

Commit bce9434

Browse files
committed
Rename removedCallIds to inputCallIds and test multi-call orphaning
Addresses PR #1292 review: the set is populated from all call ids in the input history and narrowed by diffing against survivingCallIds, so inputCallIds makes the two-set diff obvious. Adds explicit tests for the parallel-tool-call shape (one assistant message, several calls): orphaned results are dropped per toolCallId while a generous budget keeps every result of a surviving multi-call message.
1 parent f1d7c7b commit bce9434

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

packages/agent-runtime/src/util/__tests__/messages.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,84 @@ describe('trimMessagesToFitTokenLimit', () => {
655655
expect(ghost).toBeDefined()
656656
})
657657

658+
it('drops only the orphaned results when one assistant message carries multiple tool calls', () => {
659+
// Parallel tool calls: a single assistant message with calls c1 and c2.
660+
// The trim removes the call message; the drop matches per toolCallId, so
661+
// every result whose call was removed is dropped and nothing keyed to a
662+
// surviving call is touched.
663+
const messages: Message[] = [
664+
userMessage('write two files'),
665+
assistantMessage({
666+
content: [
667+
toolCallPart('c1', 'x'.repeat(3000)),
668+
toolCallPart('c2', 'y'.repeat(3000)),
669+
],
670+
}),
671+
toolResultMessage('c1', 'ok1'),
672+
userMessage({ content: 'steer', keepDuringTruncation: true }),
673+
toolResultMessage('c2', 'ok2'),
674+
assistantMessage('done'),
675+
]
676+
677+
const result = trimMessagesToFitTokenLimit({
678+
messages,
679+
systemTokens: 0,
680+
maxTotalTokens: 600,
681+
logger,
682+
})
683+
684+
expectNoOrphanedToolResults(result)
685+
// Neither orphaned result may survive in any form...
686+
expect(
687+
result.filter(
688+
(message) =>
689+
message.role === 'tool' &&
690+
(message.toolCallId === 'c1' || message.toolCallId === 'c2'),
691+
),
692+
).toEqual([])
693+
// ...while the kept steer message and the final reply still do.
694+
expect(
695+
result.some(
696+
(message) =>
697+
message.role === 'user' &&
698+
message.content.some(
699+
(part) => part.type === 'text' && part.text === 'steer',
700+
),
701+
),
702+
).toBe(true)
703+
expect(
704+
result.some(
705+
(message) =>
706+
message.role === 'assistant' &&
707+
message.content.some(
708+
(part) => part.type === 'text' && part.text === 'done',
709+
),
710+
),
711+
).toBe(true)
712+
})
713+
714+
it('keeps every result of a multi-call assistant message that survives the trim', () => {
715+
const messages: Message[] = [
716+
userMessage('write two files'),
717+
assistantMessage({
718+
content: [toolCallPart('c1', 'ok'), toolCallPart('c2', 'ok')],
719+
}),
720+
toolResultMessage('c1', 'ok1'),
721+
toolResultMessage('c2', 'ok2'),
722+
assistantMessage('done'),
723+
]
724+
725+
// Generous budget: the multi-call message and both results survive.
726+
const result = trimMessagesToFitTokenLimit({
727+
messages,
728+
systemTokens: 0,
729+
maxTotalTokens: 60_000,
730+
logger,
731+
})
732+
733+
expect(result).toEqual(messages)
734+
})
735+
658736
it('keeps the invariant across a sweep of budgets', () => {
659737
const messages: Message[] = [
660738
userMessage('please write the file'),

packages/agent-runtime/src/util/messages.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export function trimMessagesToFitTokenLimit(params: {
238238
// keepDuringTruncation on a tool result whose call was removed: keeping the
239239
// pair together would exceed the budget and keeping the result alone would
240240
// be rejected, so a result cannot outlive its call.
241-
const removedCallIds = new Set<string>()
241+
const inputCallIds = new Set<string>()
242242
const survivingCallIds = new Set<string>()
243243
const collectCallIds = (message: Message, into: Set<string>) => {
244244
if (message.role !== 'assistant' || !Array.isArray(message.content)) {
@@ -251,9 +251,9 @@ export function trimMessagesToFitTokenLimit(params: {
251251
}
252252
}
253253
for (const message of messages) {
254-
collectCallIds(message, removedCallIds)
254+
collectCallIds(message, inputCallIds)
255255
}
256-
if (removedCallIds.size > 0) {
256+
if (inputCallIds.size > 0) {
257257
for (const message of filteredMessages) {
258258
if (message === placeholder) continue
259259
collectCallIds(message, survivingCallIds)
@@ -262,7 +262,7 @@ export function trimMessagesToFitTokenLimit(params: {
262262
const message = filteredMessages[i]
263263
if (message === placeholder || message.role !== 'tool') continue
264264
if (
265-
removedCallIds.has(message.toolCallId) &&
265+
inputCallIds.has(message.toolCallId) &&
266266
!survivingCallIds.has(message.toolCallId)
267267
) {
268268
filteredMessages.splice(i, 1)

0 commit comments

Comments
 (0)