feat: keep image-heavy sessions within provider request-size limits - #1508
Conversation
🦋 Changeset detectedLatest commit: 9509645 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
commit: |
…action request is too large
…s the request body as too large
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bea536be3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let toDegrade = Math.max(0, mediaCount - keepRecent); | ||
| if (toDegrade === 0) return messages as Message[]; |
There was a problem hiding this comment.
Degrade media even when one recent item is too large
When the 413 is caused by one or two large media parts, this returns the original messages unchanged because mediaCount <= keepRecent. That can happen with default ReadMediaFile reads despite the new read budget, since the compressor still passes through guarded/unsupported inputs (for example a >64 MB image under the 100 MB media limit, or an inline video), so the single retry in executeLoopStep resends the same oversized body and the session remains stuck. The fallback needs a way to drop or shrink recent media when those parts alone exceed the provider request limit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0228ac3e92
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /payload too large/, | ||
| /content too large/, |
There was a problem hiding this comment.
Handle request-too-large wording
If a 413 response is worded as Request too large or Request body too large, it falls through this list as a plain APIStatusError, so executeLoopStep never takes the new media-degraded resend path and the session remains stuck on providers/proxies that use that unambiguous byte-limit wording. This is distinct from the Vertex token-overflow ambiguity called out above, so the body-size matcher should include the request-too-large/request-body-too-large forms as well.
Useful? React with 👍 / 👎.
- Introduced a new WebP decoding module using @jsquash/webp's wasm decoder. - Implemented functions to decode WebP images and check for animated WebP formats. - Updated image compression tests to include scenarios for WebP handling, including encoding and decoding. - Enhanced error handling for API request size limits to accommodate various error messages. - Updated pnpm lockfile to include new dependencies for WebP encoding and decoding.
|
❌ Nix build failed Hash mismatch in
Please update |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8022765a02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| originalError: errorMessage(error), | ||
| degradedError: errorMessage(degradedError), | ||
| }); | ||
| throw degradedError; |
There was a problem hiding this comment.
Fall back to strict repair after degraded resend 400s
When an oversized history also has a projection defect that only buildMessagesStrict repairs (for example duplicate tool-call ids, a leading assistant, or consecutive assistants), providers that enforce body size before message validation return the 413 first, so this branch sends the media-degraded normal projection. If that smaller request then gets a recoverable structure 400/422, this catch throws it immediately instead of trying a strict+media-degraded projection, leaving the session stuck even though the existing strict fallback could repair the same history once size is no longer the first failure.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea208ec980
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * owner (KimiCore) on load and reload. Processes that never load config | ||
| * (TUI paste, ACP adapter) leave this unset and get env/built-in behavior. | ||
| */ | ||
| let configuredMaxImageEdgePx: number | undefined; |
There was a problem hiding this comment.
Scope image limits to the owning core
Because this configured value is module-global (and configuredReadImageByteBudget below follows the same pattern), constructing or reloading a second KimiCore with a different homeDir/config overwrites the image limits used by all existing sessions and prompt-ingestion paths in the process; SDKRpcClient exposes that multi-client pattern with per-client homeDir. In an embedded app or parallel harness, a session expecting its own [image] max_edge_px / read_byte_budget will suddenly compress using another client's settings, changing request size and image detail. Keep these limits scoped to the core/session or pass them into compression calls instead of storing them globally.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9509645fcb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| originalError: errorMessage(error), | ||
| strictError: errorMessage(strictError), | ||
| }); | ||
| throw strictError; |
There was a problem hiding this comment.
Degrade strict resends that hit 413
If a history is both wire-invalid and too large on a provider that reports the structure 400 before applying the body-size check, the normal call enters this strict branch, but a 413 from the strict resend is thrown here without trying any media-degraded strict projection. That leaves sessions with, for example, duplicate tool-call ids plus accumulated ReadMediaFile images stuck even though repairing the structure and then dropping old media would make the request sendable.
Useful? React with 👍 / 👎.
Port v1 #1508 into v2: lower the longest-edge downscale cap back to 2000px (v2 was stuck on the 3000px it had ported from an earlier v1 change) and make it overridable, add the 256 KB read-image byte budget used by ReadMediaFile, and widen the over-budget fallback ladder to [2000, 1000, 768, 512, 384, 256]. - MAX_IMAGE_EDGE_PX 3000 -> 2000, with KIMI_IMAGE_MAX_EDGE_PX env and a config-pushed value resolved via resolveMaxImageEdgePx. - READ_IMAGE_BYTE_BUDGET=256KB with KIMI_IMAGE_READ_BYTE_BUDGET env and resolveReadImageByteBudget; ReadMediaFile's default compress path now uses it (region / full_resolution still honor IMAGE_BYTE_BUDGET). - Test expectations that hard-coded 3000px / 1500px updated to 2000 / 1000 to match the v1 behavior. The [image] config.toml section is intentionally not added: the env vars already cover the override path, and wiring a config section plus a runtime push would add v2-specific scaffolding beyond v1.
…oonshotAI#1508) Integrate upstream image request-size-limits with the local Rust native module architecture. Rust handles compute (pixel compression/crop), TS handles policy (image size limits via ImageLimits + global config setters). Key changes: - Add @jsquash/webp wasm decoder for WebP format support - Wire global config setters in KimiCore constructor and setRuntimeConfig - ImageLimits falls back to global config layer when no per-instance config - Native compress/crop fall through to TS path for unsupported formats (WebP)
Problem
A session that accumulates many model-read screenshots can push the serialized
request body over the provider's size ceiling (HTTP 413, e.g. "Request exceeds
the maximum size"), and a single provider-unsupported image (HEIC, oversized
WebP) can poison a session: once it lands in the history, every subsequent
request is rejected, /compact carries the same media and fails identically,
and resuming re-sends the same history.
What changed
APIRequestTooLargeError(matched on known wordings incl. plain "request (body) too large";
context-overflow detection runs first so Vertex-style 413 prompt-too-long
keeps routing to compaction).
model-initiated reads (ReadMediaFile default path) at a 256 KB per-image
budget — both configurable via
[image]in config.toml orKIMI_IMAGE_MAX_EDGE_PX/KIMI_IMAGE_READ_BYTE_BUDGETenv vars. Explicitregion/full_resolutionreads keep the provider-scale limit as thefull-fidelity readback escape hatch. The compression ladder gains sub-1000px
JPEG rungs so small budgets always converge.
WebP decodes via a bundled wasm codec (decoder wasm committed as base64 —
the published CLI is a single-file bundle with no runtime node_modules) and
re-encodes on the lossless-first PNG/JPEG ladder; animated WebP still passes
through whole to preserve animation;
regioncrops work on WebP.the execution environment (
kaos.osEnv.osKind): built-insipson macOS,heif-convert/ImageMagick on Linux, ImageMagick + winget install hint onWindows. The model runs the conversion through Bash under the normal
permission flow and reads the converted file — the unsupported payload never
reaches the provider. (A wasm HEIC decoder was evaluated and rejected:
libheif/libde265 are LGPL and HEVC decoding is patent-encumbered.)
summarizer request once with media replaced by
[image]/[audio]/[video]markers, then falls back to the existing oldest-first shrink. The main loop
mirrors the strict-resend fallback: the step is resent once with a
media-degraded projection (all but the two most recent media parts replaced
by markers; ReadMediaFile's
<image path>wrapper survives for re-reads),later steps of the turn reuse that projection, the underlying history keeps
its media, and a resumed session self-heals by re-triggering the same
deterministic fallback. Observable via
projection: 'media-degraded'on thellm.requestrecord.[image]section and both env vars documented in zh/en pages.All changes were developed test-first (413 wording matrix, budget/edge
precedence and scoping, ladder convergence, WebP decode/alpha/animation/crop,
HEIC per-platform guidance, compaction strip-and-retry, loop-level
degraded-resend once-only and stickiness, agent-level end-to-end recovery).
Full monorepo typecheck, lint, and test suite pass; the only failures are 6
pre-existing
preflight.test.tscases that fail identically on the basebranch. One consolidated changeset.