Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/opencode/src/tool/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import DESCRIPTION from "./read.txt"
import { InstanceState } from "@/effect/instance-state"
import { assertExternalDirectoryEffect } from "./external-directory"
import { Instruction } from "../session/instruction"
import { isImageAttachment, isPdfAttachment, sniffAttachmentMime } from "@/util/media"
import { isPdfAttachment, sniffAttachmentMime } from "@/util/media"

const DEFAULT_READ_LIMIT = 2000
const MAX_LINE_LENGTH = 2000
const MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`
const MAX_BYTES = 50 * 1024
const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB`
const SAMPLE_BYTES = 4096
const SUPPORTED_IMAGE_MIMES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"])

// `offset` and `limit` were originally `z.coerce.number()` — the runtime
// coercion was useful when the tool was called from a shell but serves no
Expand Down Expand Up @@ -220,7 +221,9 @@ export const ReadTool = Tool.define(
const sample = yield* readSample(filepath, Number(stat.size), SAMPLE_BYTES)

const mime = sniffAttachmentMime(sample, AppFileSystem.mimeType(filepath))
Comment thread
rekram1-node marked this conversation as resolved.
if (isImageAttachment(mime) || isPdfAttachment(mime)) {
const isImage = SUPPORTED_IMAGE_MIMES.has(mime)

if (isImage || isPdfAttachment(mime)) {
const bytes = yield* fs.readFile(filepath)
const msg = isPdfAttachment(mime) ? "PDF read successfully" : "Image read successfully"
return {
Expand Down
18 changes: 18 additions & 0 deletions packages/opencode/test/tool/read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,24 @@ root_type Monster;`
expect(result.output).toContain("table Monster")
}),
)

it.live("falls through unsupported image mime types to text", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped()
const cases = [
["image.bmp", "BM text content"],
["photo.tiff", "II text content"],
["photo.avif", "avif text content"],
] as const

for (const item of cases) {
yield* put(path.join(dir, item[0]), item[1])
const result = yield* exec(dir, { filePath: path.join(dir, item[0]) })
expect(result.attachments).toBeUndefined()
expect(result.output).toContain(item[1])
}
}),
)
})

describe("tool.read loaded instructions", () => {
Expand Down
Loading