package vision import "fmt" // 内联图像(base64 / data: URI)的体积预算:与网关 REST 入口 // internal/services/gateway/rest.go 的图片上限同口径 —— 5 MiB 解码后字节, // 外加 64 字符的 data: 前缀余量。网关是**入口层**实现,这里是**解码层**的 // 同一口径兜底:REST 入口已有 5 MiB 上限,但任何未来新入口,或把 WS 上限 // 放宽,都会重新打开「先按约 4/3 分配再报错」这个洞(G3 的纵深)。 // // 口径换算只有一处:(n+2)/3*4 是 base64 每 3 字节编码为 4 字符的膨胀系数 // (余 1、2 字节时末组仍需补满 4 字符),再加 data: 前缀余量 —— 恰好 // 5 MiB 的合法图片编码后正好等于该上限,不会被误拒。 // // 注意生效范围:网关的生效上限可由配置 CHAT_IMAGE_MAX_BYTES 调整(默认 // 5242880 与本常量同值),本包读不到网关配置,因此这里钉住的是**默认口径的 // 硬顶** —— 解码层不随入口配置放宽;入口层比它更严时以入口层为准。 const ( // MaxInlineImageBytes 是一张内联图像解码后的字节上限(5 MiB), // 与网关 POST /api/v1/upload 的 maxUploadBytes 同口径。 MaxInlineImageBytes = 5 << 20 // InlineImagePrefixAllowance 是 data: URI 前缀(如 // "data:image/png;base64,")所占的字符余量,不含 base64 载荷本身。 InlineImagePrefixAllowance = 64 // MaxInlineImageChars 是同一口径换算到 base64 字符串长度的上限。 MaxInlineImageChars = ((MaxInlineImageBytes+2)/3)*4 + InlineImagePrefixAllowance ) // checkInlineImageLength 对 base64 / data: URI 字符串做长度预检,在任何 // 解码或短路之前拦下超限载荷:解码要先按约 4/3 分配,不预检就是「先分配 // 再报错」,放宽了入口上限的新入口会因此被放大成内存压力。判定按**字符串 // 长度**(零分配),与网关入口 checkChatImageSize 同一口径。 // // 错误文案只带上限与本次长度,绝不包含图片内容。 func checkInlineImageLength(src string) error { if len(src) <= MaxInlineImageChars { return nil } return fmt.Errorf("vision: %w: 内联图像上限 %d 字符(解码后约 %d MiB),本次 %d 字符", ErrImageTooLarge, MaxInlineImageChars, MaxInlineImageBytes>>20, len(src)) }