


























@@ -59,8 +59,8 @@ export type ParsedMessageWithImages = {
5959 * do not receive these as inline image blocks.
6060 *
6161 * ⚠️ Call sites (chat.ts, agent.ts, server-node-events.ts) MUST also pass
62- * `supportsImages: modelSupportsImages(model)` so that text-only model runs
63- * do not inject unresolvable media:// markers into prompt text.
62+ * `supportsImages: modelSupportsImages(model)` so text-only model runs
63+ * offload images as media refs instead of passing inline image blocks.
6464 */
6565offloadedRefs: OffloadedRef[];
6666};
@@ -82,6 +82,7 @@ type SavedMedia = {
8282};
83838484const OFFLOAD_THRESHOLD_BYTES = 2_000_000;
85+const TEXT_ONLY_OFFLOAD_LIMIT = 10;
85868687const MIME_TO_EXT: Record<string, string> = {
8788"image/jpeg": ".jpg",
@@ -271,12 +272,9 @@ function validateAttachmentBase64OrThrow(
271272 * because they are not passed inline to the model.
272273 *
273274 * ## Text-only model runs
274- * Pass `supportsImages: false` for text-only model runs so that no media://
275- * markers are injected into prompt text.
276- *
277- * ⚠️ Call sites in chat.ts, agent.ts, and server-node-events.ts MUST be
278- * updated to pass `supportsImages: modelSupportsImages(model)`. Until they do,
279- * text-only model runs receive unresolvable media:// markers in their prompt.
275+ * Pass `supportsImages: false` for text-only model runs so images are offloaded
276+ * as `media://inbound/<id>` refs instead of being sent as inline image blocks.
277+ * The agent runner can then resolve the refs through the normal media path.
280278 *
281279 * ## Cleanup on failure
282280 * On any parse failure after files have already been offloaded, best-effort
@@ -304,22 +302,11 @@ export async function parseMessageWithAttachments(
304302return { message, images: [], imageOrder: [], offloadedRefs: [] };
305303}
306304307-// For text-only models drop all attachments cleanly. Do not save files or
308-// inject media:// markers that would never be resolved and would leak
309-// internal path references into the model's prompt.
310-if (opts?.supportsImages === false) {
311-if (attachments.length > 0) {
312-log?.warn(
313-`parseMessageWithAttachments: ${attachments.length} attachment(s) dropped — model does not support images`,
314-);
315-}
316-return { message, images: [], imageOrder: [], offloadedRefs: [] };
317-}
318-319305const images: ChatImageContent[] = [];
320306const imageOrder: PromptImageOrderEntry[] = [];
321307const offloadedRefs: OffloadedRef[] = [];
322308let updatedMessage = message;
309+const shouldForceOffload = opts?.supportsImages === false;
323310324311// Track IDs of files saved during this request for cleanup if a later
325312// attachment fails validation and the entire parse is aborted.
@@ -377,10 +364,26 @@ export async function parseMessageWithAttachments(
377364378365let isOffloaded = false;
379366380-if (sizeBytes > OFFLOAD_THRESHOLD_BYTES) {
367+if (shouldForceOffload && offloadedRefs.length >= TEXT_ONLY_OFFLOAD_LIMIT) {
368+log?.warn(
369+`attachment ${label}: dropping image because text-only offload limit ` +
370+`${TEXT_ONLY_OFFLOAD_LIMIT} was reached`,
371+);
372+updatedMessage += "\n[image attachment omitted: text-only attachment limit reached]";
373+continue;
374+}
375+376+if (shouldForceOffload || sizeBytes > OFFLOAD_THRESHOLD_BYTES) {
381377const isSupportedForOffload = SUPPORTED_OFFLOAD_MIMES.has(finalMime);
382378383379if (!isSupportedForOffload) {
380+if (shouldForceOffload) {
381+log?.warn(
382+`attachment ${label}: format ${finalMime} cannot be offloaded for ` +
383+"text-only model, dropping",
384+);
385+continue;
386+}
384387// Passing this inline would reintroduce the OOM risk this PR prevents.
385388throw new Error(
386389`attachment ${label}: format ${finalMime} is too large to pass inline ` +
@@ -418,7 +421,11 @@ export async function parseMessageWithAttachments(
418421const mediaRef = `media://inbound/${savedMedia.id}`;
419422420423updatedMessage += `\n[media attached: ${mediaRef}]`;
421-log?.info?.(`[Gateway] Intercepted large image payload. Saved: ${mediaRef}`);
424+log?.info?.(
425+shouldForceOffload
426+ ? `[Gateway] Offloaded image for text-only model. Saved: ${mediaRef}`
427+ : `[Gateway] Intercepted large image payload. Saved: ${mediaRef}`,
428+);
422429423430// Record for transcript metadata — separate from `images` because
424431// these are not passed inline to the model.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。