feat(daemon): clamp oversized inline media on the prompt path#4646
Conversation
Replace inline image/audio/blob payloads exceeding a configurable byte ceiling (QWEN_CODE_MAX_INLINE_MEDIA_BYTES, default 10MB) with a sanitized text placeholder via clampInlineMediaPart, wired into Session.#resolvePrompt so oversized daemon media cannot blow up request size or token budget. Also advertise audio:true in the HTTP daemon promptCapabilities to match acpAgent and the actual #resolvePrompt handling. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
clampInlineMediaPart returns the genai Part type, widening the resolved parts union so fileData.fileUri is optional; assert it where resource_link file paths are collected (that branch always sets fileUri). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
📋 Review SummaryThis PR introduces a clamp mechanism for oversized inline media (image/audio/blob) payloads on the daemon prompt path, replacing them with a sanitized text placeholder when they exceed a configurable byte ceiling (default 10 MB). The implementation is well-tested, follows existing patterns, and addresses a real production risk of oversized requests blowing up token budgets. 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
There was a problem hiding this comment.
Pull request overview
Adds a prompt-path safeguard to prevent oversized inline media (image/audio/blob) from inflating daemon request sizes by clamping oversized inlineData parts to a sanitized text placeholder, and updates advertised ACP prompt capabilities accordingly.
Changes:
- Introduce
inlineMediaLimitutilities (approxBase64Bytes, env-configurable ceiling, andclampInlineMediaPart) and export them from@qwen-code/qwen-code-core. - Apply clamping during ACP Session prompt resolution for
image,audio, and embeddedblobresources. - Advertise
audio: truein HTTP daemonpromptCapabilitiesand add unit/integration tests covering the clamp behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/index.ts | Re-exports the new inline media limiting utilities from the core public API. |
| packages/core/src/core/inlineMediaLimit.ts | Implements env-configurable inline media size ceiling and clamping to a text placeholder. |
| packages/core/src/core/inlineMediaLimit.test.ts | Adds unit tests for base64 sizing, env override, clamping, and mime sanitization. |
| packages/cli/src/serve/acpHttp/dispatch.ts | Updates ACP initialize capabilities to advertise audio prompt support. |
| packages/cli/src/acp-integration/session/Session.ts | Wires clamping into #resolvePrompt for inline image/audio and embedded blob parts. |
| packages/cli/src/acp-integration/session/Session.test.ts | Adds an integration-style test ensuring oversized inline images are degraded before model send. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Keep both our inline-media-clamp test and upstream conversation_finished / tool-outcome telemetry tests. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Verification ReportBranch: Test Results
Code Review Notes
NoteInitial test run hit a workspace symlink staleness issue (not PR-related). After Verdict✅ Ready to merge — all test plan items pass, implementation is well-tested with 12 dedicated unit tests and 1 integration test covering the clamping path. Verified by wenshao |
The readManyFiles result path pushed non-string contentParts (binary files from @file references) directly into processedQueryParts without clamping, bypassing the inline media size guard this PR introduces. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
| const mime = sanitizeMimeForPlaceholder(mimeType); | ||
| return ( | ||
| `[Media omitted: ${mime} is ~${formatMb(bytes)}MB, exceeding the ` + | ||
| `${formatMb(limitBytes)}MB inline limit. Ask the user to resize/compress ` + |
There was a problem hiding this comment.
[Suggestion] The placeholder text advises "reference it via an @file path so it can be read from disk" but this is misleading for 2 of the 3 call sites:
Session.ts:2939(readManyFiles): the user already used@file— the advice is redundant.Session.ts:2961(MCP blob): the data comes from an MCP resource with no local file — the advice is nonsensical.
Only the direct user-input path (image/audio) benefits from this suggestion. Consider removing the @file advice from the generic placeholder — "Ask the user to resize/compress it" is universally correct.
| `${formatMb(limitBytes)}MB inline limit. Ask the user to resize/compress ` + | |
| return ( | |
| `[Media omitted: ${mime} is ~${formatMb(bytes)}MB, exceeding the ` + | |
| `${formatMb(limitBytes)}MB inline limit. Ask the user to resize or ` + | |
| `compress the file before attaching.]` | |
| ); |
— claude-opus-4-6 via Qwen Code /review
| /** | ||
| * Build the placeholder text substituted for an oversized inline media part. | ||
| */ | ||
| export function oversizedMediaPlaceholder( |
There was a problem hiding this comment.
[Suggestion] oversizedMediaPlaceholder is exported and re-exported through the barrel (export * from './core/inlineMediaLimit.js') but has zero external consumers — it is only called by clampInlineMediaPart in this same file. This exposes an implementation detail (including the exact string format) as public API surface, constraining future refactoring of placeholder wording.
| export function oversizedMediaPlaceholder( | |
| function oversizedMediaPlaceholder( |
— claude-opus-4-6 via Qwen Code /review
| if (bytes <= limitBytes) { | ||
| return part; | ||
| } | ||
| const mimeType = part.inlineData?.mimeType ?? 'application/octet-stream'; |
There was a problem hiding this comment.
[Suggestion] The mimeType ?? 'application/octet-stream' fallback is untested — every test case provides an explicit mimeType. Since the Part type makes mimeType optional, real callers (e.g., the blob path with a missing contextPart.mimeType) can hit this branch.
it('falls back to application/octet-stream when mimeType is missing', () => {
const part = { inlineData: { data: 'A'.repeat(2000) } };
const result = clampInlineMediaPart(part as Part, 1000);
expect(result.text).toContain('application/octet-stream');
expect(result.text?.toLowerCase()).toContain('omitted');
});— claude-opus-4-6 via Qwen Code /review
chiga0
left a comment
There was a problem hiding this comment.
QoderWork Code Review — PR #4646
Decision: APPROVED ✅
Summary
feat(daemon): clamp oversized inline media on the prompt path — prevents oversized inline image/audio/blob payloads from inflating daemon request size and token budget.
Review Notes
clampInlineMediaPart: Clean pure-function guard on individualPartobjects. Returns text placeholder for oversized media, passes through unchanged otherwise.approxBase64Bytes: O(1) estimation via string length arithmetic — no decode/copy, safe on multi-MB payloads on the prompt hot path. Correctly handles empty input, padding (==,=), anddata:URL prefix.- Wiring completeness: All 3 inline media entry points in
#resolvePromptcovered — direct image/audio parts,@filereadManyFiles binary parts, and blob embedded context. - Security: Mime type in the placeholder text is sanitized via
sanitizeMimeForPlaceholderagainst prompt injection (tested with[SYSTEM: hijackpayload). dispatch.ts:audio: truecorrectly mirrors actual#resolvePrompthandling (both image and audio becomeinlineDataParts).fileUri!assertion: Correct — only applies onresource_linkpath whereclampInlineMediaPartreturns the original part unchanged.- Env-configurable ceiling:
QWEN_CODE_MAX_INLINE_MEDIA_BYTESwith 10MB default. Lazy-evaluated at each call, so runtime env changes take effect immediately. - Test coverage: 8 unit tests + 1 integration test covering base64 sizing, env override edge cases, clamping behavior, mime sanitization, and end-to-end Session prompt degradation.
No findings. LGTM.
This review was generated by QoderWork AI
Summary
clampInlineMediaPartutility that replaces inline image/audio/blob payloads exceeding a configurable byte ceiling (QWEN_CODE_MAX_INLINE_MEDIA_BYTES, default 10 MB) with a sanitized text placeholder, preventing oversized daemon media from blowing up request size or token budget.Session.#resolvePromptand advertiseaudio: truein HTTP daemonpromptCapabilities.fileData.fileUribecame optional after clamping.Test plan
inlineMediaLimit.ts(covers base64/buffer thresholds, audio/image/blob, env-var override)🤖 Generated with Qwen Code