Files
Sakurasan d5dac5a6fc feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
2026-10-26 00:54:46 +08:00

115 lines
2.8 KiB
Go

package tools
import (
"fmt"
"image"
"image/color"
"image/jpeg"
_ "image/png" // 解码 PNG 截图
"os"
"time"
)
// 视觉压缩策略:视觉模型会把图片缩到 ~1568px 再处理,原始 Retina 全屏 PNG
// (2-3MB)纯属浪费且容易顶爆网关的单请求字节上限。超过阈值就降采样到
// maxVisionWidth 并转 JPEG(截图场景质量足够,体积约 1/10)。
const (
shrinkThresholdBytes = 600 << 10 // 600KB
maxVisionWidth = 1280
visionJPEGQuality = 80
)
// ShrinkForVision 尽力压缩大图:PNG 且超过阈值时降采样 + 转 JPEG,
// 返回新路径;小图或解码失败时原样返回(调用方无需关心错误)。
func ShrinkForVision(path string) (string, error) {
st, err := os.Stat(path)
if err != nil || st.Size() <= shrinkThresholdBytes {
return path, nil
}
f, err := os.Open(path)
if err != nil {
return path, nil
}
src, _, err := image.Decode(f)
f.Close()
if err != nil {
return path, nil // 非图片/损坏:保持原样
}
scaled := scaleToWidth(src, maxVisionWidth)
out := fmt.Sprintf("%s/vision-%d.jpg", os.TempDir(), time.Now().UnixNano())
of, err := os.Create(out)
if err != nil {
return path, nil
}
if err := jpeg.Encode(of, scaled, &jpeg.Options{Quality: visionJPEGQuality}); err != nil {
of.Close()
os.Remove(out)
return path, nil
}
if err := of.Close(); err != nil {
os.Remove(out)
return path, nil
}
// 压缩后反而更大(异常情况)则回退原图
if ost, err2 := os.Stat(out); err2 != nil || ost.Size() >= st.Size() {
os.Remove(out)
return path, nil
}
return out, nil
}
// scaleToWidth 等比缩到 maxW 宽(box 平均采样,stdlib 实现)。
func scaleToWidth(src image.Image, maxW int) image.Image {
b := src.Bounds()
w, h := b.Dx(), b.Dy()
if w <= maxW || w == 0 || h == 0 {
return src
}
nh := h * maxW / w
if nh < 1 {
nh = 1
}
dst := image.NewRGBA(image.Rect(0, 0, maxW, nh))
for y := 0; y < nh; y++ {
sy0, sy1 := y*h/nh, (y+1)*h/nh
if sy1 <= sy0 {
sy1 = sy0 + 1
}
for x := 0; x < maxW; x++ {
sx0, sx1 := x*w/maxW, (x+1)*w/maxW
if sx1 <= sx0 {
sx1 = sx0 + 1
}
var rs, gs, bs, as, n uint64
stepY := (sy1 - sy0) / 4
if stepY < 1 {
stepY = 1
}
stepX := (sx1 - sx0) / 4
if stepX < 1 {
stepX = 1
}
for sy := sy0; sy < sy1; sy += stepY {
for sx := sx0; sx < sx1; sx += stepX {
r, g, bl, a := src.At(b.Min.X+sx, b.Min.Y+sy).RGBA()
rs += uint64(r)
gs += uint64(g)
bs += uint64(bl)
as += uint64(a)
n++
}
}
if n == 0 {
n = 1
}
dst.Set(x, y, rgbaFrom64(rs/n, gs/n, bs/n, as/n))
}
}
return dst
}
func rgbaFrom64(r, g, b, a uint64) color.RGBA {
return color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
}