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。
115 lines
2.8 KiB
Go
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)}
|
|
}
|