fix(agent-sdk): stop maxItems from dropping rows and reporting a stale cursor - #332
Open
V3RON wants to merge 1 commit into
Open
fix(agent-sdk): stop maxItems from dropping rows and reporting a stale cursor#332V3RON wants to merge 1 commit into
V3RON wants to merge 1 commit into
Conversation
…e cursor autoPaginate.maxItems trimmed the merged items down to the requested count but kept the underlying page's nextCursor, which pointed past the rows that were just discarded - both on the initial page and mid-loop. Callers following the reported cursor would silently skip real rows. Fix by never over-fetching: every call (including the first) now requests min(callerLimit, remaining budget) so a well-behaved producer's own cursor lands exactly where the caller stopped. For producers that clamp/ignore limit and still over-return, the extra rows are trimmed and nextCursor is dropped (it can't be trusted to resume past a truncation), signalled via a new truncated: true field on PageEnvelope (@rozenite/agent-shared) alongside hasMore: true.
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
callToolWithOptionalPaginationtrimmed collected items down tomaxItemsbut passed the producer's page envelope through unchanged, so the returnedpage.nextCursorpointed past the rows it had just discarded. Those rows became unreachable with no error and no gap marker.Two independent loss paths, both fixed:
limit: 20, maxItems: 5, the tool returned 20 rows, the caller received 5, and the cursor still pointed past row 19. Rows 5–19 vanished, and the result looked complete.limit: 5, maxItems: 7, rows 7–9 were fetched, discarded, and skipped by the propagated cursor.The fix is to stop over-fetching: every request, including the first, now asks for
min(callerLimit, maxItems - collectedSoFar), so a well-behaved producer's own cursor lands exactly on the truncation boundary. No cursors are synthesized.For producers that clamp or ignore
limit(console.getMessagesdocuments "Default 50, max 200"), truncation can still happen. In that case the cursor is untrustworthy, so it is dropped and a newtruncated: truefield is set onPageEnvelopealongsidehasMore: true, meaning "rows were dropped; resume position is unknown — restart or narrow the query."Related Issue
Closes #329
Context
The SDK cannot fix this by computing a cursor. Cursor encoding is producer-owned and opaque — built-in domains use
base64urlof{v,scope,index}, third-party plugins may use anything — so there is no way to express "row 5 of this page". Hence the two-part approach: make truncation not happen in the common case, and signal it honestly when a producer forces it.hasMoreis deliberatelytruewhenevertruncatedis set: we positively know unconsumed rows exist, since we just discarded them, even though the resume position is lost.Reviewer notes:
caps an oversized first page...) encoded the bug as expected behavior, asserting that a limit-ignoring producer's stale cursor was propagated past a discarded row. Its expectations were updated. Worth a look, since the bug had a test blessing it.merged.page.limitstill reports the producer's real page size, not the SDK's internally reduced request limit.page.resethandling is untouched and its existing tests pass unmodified.validateAutoPaginationstill requirespagesLimitalongsidemaxItems. That constraint is arguably now unnecessary, sincemaxItemsalone is well-defined once requests are budgeted, but relaxing it is a public API change and is left out of a bugfix.page.nextCursorinto a runnablenextcommand, which is what makes this bug user-visible; consuming the newtruncatedsignal there is a follow-up on that branch, since the code it needs does not exist onmain.Testing
packages/agent-sdk/src/__tests__/pagination.test.tsfor both loss paths, using a fake producer that mirrors the real cursor encoding inlocal-domains.ts. Each asserts on concrete row identities: the union of rows received plus rows reachable via the returned cursor. Both were confirmed failing before the fix (expected [5,6,...,19] to deeply equal []andexpected [7,8,9] to deeply equal []).npx turbo run typecheck lint test --forceacross the whole repo: 99 successful, 99 total..changeset/agent-sdk-max-items-row-loss.md(@rozenite/agent-sdkpatch,@rozenite/agent-sharedminor for the newPageEnvelopefield).