feat(hooks): add read-image-resizer hook
Intercepts Read tool output with image attachments and resizes to comply with Anthropic API limits (≤1568px long edge, ≤5MB). Only activates for Anthropic provider sessions and appends resize metadata (original/new resolution, token count) to tool output. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { describe, expect, it } from "bun:test"
|
||||
|
||||
import { parseImageDimensions } from "./image-dimensions"
|
||||
|
||||
const PNG_1X1_DATA_URL =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
|
||||
const GIF_1X1_DATA_URL =
|
||||
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
|
||||
function createPngDataUrl(width: number, height: number): string {
|
||||
const buf = Buffer.alloc(33)
|
||||
buf.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0)
|
||||
buf.writeUInt32BE(13, 8)
|
||||
buf.set([0x49, 0x48, 0x44, 0x52], 12)
|
||||
buf.writeUInt32BE(width, 16)
|
||||
buf.writeUInt32BE(height, 20)
|
||||
return `data:image/png;base64,${buf.toString("base64")}`
|
||||
}
|
||||
|
||||
function createGifDataUrl(width: number, height: number): string {
|
||||
const buf = Buffer.alloc(10)
|
||||
buf.set([0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 0)
|
||||
buf.writeUInt16LE(width, 6)
|
||||
buf.writeUInt16LE(height, 8)
|
||||
return `data:image/gif;base64,${buf.toString("base64")}`
|
||||
}
|
||||
|
||||
describe("parseImageDimensions", () => {
|
||||
it("parses PNG 1x1 dimensions", () => {
|
||||
//#given
|
||||
const dataUrl = PNG_1X1_DATA_URL
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/png")
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ width: 1, height: 1 })
|
||||
})
|
||||
|
||||
it("parses PNG dimensions from IHDR", () => {
|
||||
//#given
|
||||
const dataUrl = createPngDataUrl(3000, 2000)
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/png")
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ width: 3000, height: 2000 })
|
||||
})
|
||||
|
||||
it("parses GIF 1x1 dimensions", () => {
|
||||
//#given
|
||||
const dataUrl = GIF_1X1_DATA_URL
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/gif")
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ width: 1, height: 1 })
|
||||
})
|
||||
|
||||
it("parses GIF dimensions from logical screen descriptor", () => {
|
||||
//#given
|
||||
const dataUrl = createGifDataUrl(320, 240)
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/gif")
|
||||
|
||||
//#then
|
||||
expect(result).toEqual({ width: 320, height: 240 })
|
||||
})
|
||||
|
||||
it("returns null for empty input", () => {
|
||||
//#given
|
||||
const dataUrl = ""
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/png")
|
||||
|
||||
//#then
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("returns null for too-short PNG buffer", () => {
|
||||
//#given
|
||||
const dataUrl = "data:image/png;base64,AAAA"
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/png")
|
||||
|
||||
//#then
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("returns null for unsupported mime type", () => {
|
||||
//#given
|
||||
const dataUrl = PNG_1X1_DATA_URL
|
||||
|
||||
//#when
|
||||
const result = parseImageDimensions(dataUrl, "image/heic")
|
||||
|
||||
//#then
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user