Skip to content

πŸ–ΌοΈ fix: Keep Inline Images That Fit the Tool Result Cap - #675

Open
owengo wants to merge 1 commit into
LibreChat-AI:mainfrom
openwengo:fix/base64-data-url-sizing
Open

owengo wants to merge 1 commit into
LibreChat-AI:mainfrom
openwengo:fix/base64-data-url-sizing

Conversation

@owengo

@owengo owengo commented Nov 11, 2026

Copy link
Copy Markdown

jsonStringLength walks every character against MAX_STRUCTURED_CHARACTER_WORK (1M), and compactToolContent measures the same content more than once with one shared allowance (originalChars, then the normalized length). A data URL over ~500k characters exhausts it. The content then measures as Number.MAX_SAFE_INTEGER, and the atomic block is omitted whatever the cap allows.

  • A base64 data URL needs no JSON escaping, so its JSON length is length + 2. jsonStringLength now returns that for a string that fully matches a base64 data URL and is no longer than HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, the ceiling isProviderImageUrl already applies to inline images.
  • Every other string, including a data URL with an escaped character or one above that ceiling, keeps the bounded walk. The caller's cap still applies: an image larger than the cap is still omitted.
projectArtifactPayload / projectAnthropicArtifactContent
  β”” appendBoundedContentSegment
      β”” compactToolContent              one StructuredWorkState for every measurement
          β”œ estimateStructuredChars     originalChars
          β”œ estimateStructuredChars     normalized length
          β”” estimateStructuredChars     each atomic block against the atomic budget
              β”” jsonStringLength        base64 data URLs no longer spend the allowance

Related to #602 (the same report) and #603 (the same approach). Their author withdrew both without review, and the behavior is unchanged in 4.2.1. We have run the equivalent change as a patch on 4.0.4 in a LibreChat deployment since 2026-11-04.

Verification

Check Result
New regressions on main at 708c9118: 600k and 3.2M-character images kept under an 8M cap, and both artifact projections keep a 600k-character image 3 failed before the fix, passed after
Guard regression: a data URL with a ", a data URL above the ceiling and a plain 1.1M-character string still exhaust the walk Passed before and after
npx jest src/utils src/messages src/graphs/__tests__/Graph.contextOverflow.test.ts 1,338 passed, 1 skipped across 45 suites
Full unit suite with CI's exclusions (--testPathIgnorePatterns as in validate.yml) 8,007 passed, 68 skipped; 328 suites passed, 21 skipped
npm run typecheck, ESLint and sort-imports --check on the touched files, git diff --check Passed
npm run build:dev, npx tsc -p tsconfig.build.json, npm run check:circular-deps, test:circular-deps, test:light-entries, test:typecheck Passed

Each measurement of a data URL now costs one native regex pass instead of a character walk capped at 1M: about 14 ms for a 3.2M-character URL on our machine. The ceiling bounds that cost. The changed lines follow the pre-commit Prettier and ESLint pass. Existing formatting drift elsewhere in formatAgentMessages.test.ts is left untouched.

Not run: live provider requests or Langfuse export. The change only alters how a string is measured, not what is sent or traced.

An inline image larger than about 500k base64 characters never reached the
model, however high maxToolResultChars was set: projectArtifactPayload and
projectAnthropicArtifactContent replaced it with "[omitted 1 oversized atomic
tool-content block: image_url]".

jsonStringLength walked every character against MAX_STRUCTURED_CHARACTER_WORK,
and compactToolContent shares one allowance between its measurements of the
same content. A large data URL exhausted it, measured as
Number.MAX_SAFE_INTEGER and was omitted.

A base64 data URL needs no JSON escaping, so jsonStringLength now returns its
length plus the quotes when the whole string matches one and stays within
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, the ceiling isProviderImageUrl already applies
to inline images. Every other string keeps the bounded walk, and the caller's
cap still applies.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants