* Apply the per-part 10 MB binary cap, independent of the text character * budget. Oversized parts collapse into a per-part notice so the model can * pick a smaller resource instead of silently losing the blob. Runs after * image compression, so a large but compressible image has already been *
(parts: readonly ContentPart[])
| 278 | * shrunk under the cap. |
| 279 | */ |
| 280 | function applyBinaryPartCap(parts: readonly ContentPart[]): { |
| 281 | readonly parts: ContentPart[]; |
| 282 | readonly truncated: boolean; |
| 283 | } { |
| 284 | let truncated = false; |
| 285 | const out: ContentPart[] = []; |
| 286 | |
| 287 | for (const part of parts) { |
| 288 | if (part.type === 'text' || part.type === 'think') { |
| 289 | out.push(part); |
| 290 | continue; |
| 291 | } |
| 292 | |
| 293 | const url = |
| 294 | part.type === 'image_url' |
| 295 | ? part.imageUrl.url |
| 296 | : part.type === 'audio_url' |
| 297 | ? part.audioUrl.url |
| 298 | : part.videoUrl.url; |
| 299 | if (url.length > MCP_MAX_BINARY_PART_CHARS) { |
| 300 | const kind = |
| 301 | part.type === 'image_url' ? 'image' : part.type === 'audio_url' ? 'audio' : 'video'; |
| 302 | out.push({ type: 'text', text: binaryPartTooLargeNotice(kind, url.length) }); |
| 303 | truncated = true; |
| 304 | continue; |
| 305 | } |
| 306 | out.push(part); |
| 307 | } |
| 308 | |
| 309 | return { parts: out, truncated }; |
| 310 | } |
| 311 | |
| 312 | function appendTruncationNotice(out: ContentPart[]): void { |
| 313 | // Merge the notice into the last text part so the very common |
no test coverage detected