Skip to content

feat(agent-core): announce image compression and keep originals readable - #1304

Merged
RealKai42 merged 4 commits into
mainfrom
kaiyi/harrisburg
Jul 2, 2026
Merged

feat(agent-core): announce image compression and keep originals readable#1304
RealKai42 merged 4 commits into
mainfrom
kaiyi/harrisburg

Conversation

@RealKai42

Copy link
Copy Markdown
Collaborator

Why

Large images are downsampled before reaching the model, but nothing in the context said so. The model would look at a blurry 2000px copy of a 5000px screenshot, fail to read small text, and not know why — silent quality degradation with no way to recover. (Re-reading the original file didn't help either: the same deterministic compression produced the same downsampled copy.)

What

1. Compression is announced next to the image, at every ingestion point.
A shared <system> caption (buildImageCompressionCaption) states the original vs. delivered dimensions, byte size, and format, plus where the original lives and how to read detail back:

  • ReadMediaFile: the existing <system> block now reports the delivery mode (untouched / downsampled / crop / native resolution)
  • MCP tool results, REST upload & inline base64, TUI clipboard paste, ACP prompts: a caption text part is inserted immediately before each compressed image

2. Originals stay readable.

  • File uploads: the caption points at the stored blob (original bytes already on disk)
  • In-memory images (MCP / paste / inline base64 / ACP): originals are persisted into the session's media-originals/ dir — content-addressed (sha256), size-capped with oldest-first eviction, removed with the session. Temp-dir fallback when no session context exists.

3. ReadMediaFile can actually recover detail.

  • region {x, y, width, height} (original-image pixel coordinates): crops before the pipeline, so any region within the edge cap arrives at full fidelity; the <system> block explains crop→original coordinate mapping
  • full_resolution: skips downscaling; fails with an explicit error (suggesting region) when the file exceeds the per-image byte limit, instead of silently degrading

Detail delivered to the model is bounded by pixels-per-request, so cropping is the only mechanism that genuinely increases detail per unit area — the caption steers the model to it.

Notes

  • No conflict with the BlobStore wire.jsonl offload: that layer externalizes the compressed copy for transcript persistence, transparently; media-originals/ holds the pre-compression bytes for model-driven readback. Session-dir placement also means readback paths survive --resume.
  • MAX_IMAGE_EDGE_PX = 2000 is unchanged; worth calibrating once we know the vision encoder's effective pixel cap.
  • Session dirs grow more when large images flow through (compressed copy in blobs/ + original in media-originals/); exportSession zips include both.

Test plan

  • TDD throughout: every new behavior landed with a failing test first
  • New/updated coverage: crop & skip-resize pipeline, caption formatting, originals store (idempotency, eviction, failure), ReadMediaFile region/full_resolution + delivery reporting, MCP caption+persistence, server e2e (original verifiably lands in the real session dir), TUI paste/expansion, ACP conversion
  • Full suites green: agent-core 3438, server 21/21 (prompt e2e), acp-adapter 289, node-sdk 185, kimi-code TUI; typecheck clean on all five packages; oxlint 0 errors

Every image ingestion point (ReadMediaFile, MCP tool results, clipboard
paste, REST upload/inline base64, ACP) now places a <system> caption next
to a compressed image stating the original vs. delivered dimensions, byte
size, and format, so downsampling is never silent to the model.

Originals stay readable: file uploads point at the stored blob, and
in-memory images are persisted into the session's media-originals dir
(content-addressed, size-capped, removed with the session; temp-dir
fallback when no session is known).

ReadMediaFile gains region (crop in original-image pixel coordinates,
delivered at full fidelity) and full_resolution (skip downscaling, with
an explicit error over the per-image byte limit), so the model can zoom
into fine detail instead of silently degrading on large images.
@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 89cf617

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

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

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@89cf617
npx https://pkg.pr.new/@moonshot-ai/kimi-code@89cf617

commit: 89cf617

The caption announcing an image's compression was inserted before the
shared 100K text budget was applied, so a chatty MCP result (page text +
screenshot) consumed the budget first and the caption was evicted — or
sliced mid-string into an unclosed <system> fragment — while the
downsampled image survived, silently reintroducing the exact degradation
the caption exists to report, and orphaning the persisted original.

Split the size-limit pass in two and reorder the pipeline: the text
budget now runs on the tool's own text BEFORE compression inserts
captions (exempt by construction), and the per-part binary cap still
runs after compression so compressible screenshots are kept.
@RealKai42

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a68258c42

ℹ️ 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".

Comment on lines +499 to +501
return {
ok: true,
data: new Uint8Array(encoded.data),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refuse crops that still exceed the byte budget

When a requested region is high-entropy enough that encodeWithinBudget cannot get under IMAGE_BYTE_BUDGET, that helper deliberately returns the smallest attempt even if it is still over budget. This branch then returns ok: true without checking encoded.data.length, so ReadMediaFile can send an over-limit crop despite the new contract that crops are fitted or explicitly refused; mirror the skipResize over-budget error before returning success.

Useful? React with 👍 / 👎.

…mantics

- cropImageForModel rejects non-finite region coordinates with a clean
  message instead of surfacing the codec's internal validation dump
- the full_resolution and cropped-region over-budget errors now include
  exact byte counts alongside the rounded sizes, so a file a hair over
  budget no longer reads "is 3.8 MB, over the 3.8 MB limit"
- read-media.md notes that re-reading a file without region or
  full_resolution reproduces the same downsampled image

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52aabafdf1

ℹ️ 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".

const existing = await stat(path).catch(() => null);
// Content-addressed: an existing entry with the right size IS this image.
if (existing === null || existing.size !== bytes.length) {
await writeFile(path, bytes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict permissions on cached original images

When options.dir is absent, originals are stored under the OS temp directory, but this write uses the process-default file mode (and the directory is created just above with the default mode as well). On typical shared systems with umask 022 this makes pasted/MCP image originals world-readable under /tmp/kimi-code-original-images, and the generated captions expose the exact path; create/chmod the fallback cache privately and write files with owner-only permissions.

Useful? React with 👍 / 👎.

Resolve overlap with #1295 (paste-test editor stub gains setHistoryFilter
alongside the new sessionDir harness option) and fold #1296's read-media
coordinate warning into the downsampling tip to avoid a duplicated bullet.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89cf6179ea

ℹ️ 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".

homedir,
// Session-level, shared across agents: originals persisted for
// compression captions live with the session, not the agent.
mediaOriginalsDir: sessionMediaOriginalsDir(this.options.homedir),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist originals in the agent filesystem

When a session is created with a non-local Kaos (the SDK accepts caller-supplied Kaos, including SSH-backed implementations), this passes a local session-store path to MCP image persistence while ReadMediaFileTool later reads paths through the agent's kaos.readBytes. A compressed MCP image will therefore get a caption pointing at <local sessionDir>/media-originals/..., but the advertised readback path does not exist in the remote/tool filesystem, so the model cannot actually inspect the original. Persist via the same filesystem the media tool reads from, or omit the readback path unless it is readable there.

Useful? React with 👍 / 👎.

@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the SDK in the release changeset

This PR adds new public exports from @moonshot-ai/kimi-code-sdk (buildImageCompressionCaption, persistOriginalImage, sessionMediaOriginalsDir, and the caption input type), but the changeset only selects the CLI package. The repo's changeset rules require selecting @moonshot-ai/kimi-code-sdk when an internal capability is exposed through the SDK; otherwise the next release will not version/changelog/publish the SDK package containing these new symbols for npm consumers.

Useful? React with 👍 / 👎.

@RealKai42
RealKai42 merged commit 0fc0ae3 into main Jul 2, 2026
9 checks passed
@RealKai42
RealKai42 deleted the kaiyi/harrisburg branch July 2, 2026 11:07
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
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.

1 participant