Skip to content

Commit a99bd3a

Browse files
authored
tweak: adjust file api to encode images (#3292)
1 parent 96efede commit a99bd3a

File tree

1 file changed

+63
-4
lines changed

1 file changed

+63
-4
lines changed

packages/opencode/src/file/index.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import z from "zod/v4"
22
import { Bus } from "../bus"
33
import { $ } from "bun"
4+
import type { BunFile } from "bun"
45
import { formatPatch, structuredPatch } from "diff"
56
import path from "path"
67
import fs from "fs"
@@ -41,6 +42,7 @@ export namespace File {
4142

4243
export const Content = z
4344
.object({
45+
type: z.literal("text"),
4446
content: z.string(),
4547
diff: z.string().optional(),
4648
patch: z
@@ -61,12 +63,53 @@ export namespace File {
6163
index: z.string().optional(),
6264
})
6365
.optional(),
66+
encoding: z.literal("base64").optional(),
67+
mimeType: z.string().optional(),
6468
})
6569
.meta({
6670
ref: "FileContent",
6771
})
6872
export type Content = z.infer<typeof Content>
6973

74+
async function shouldEncode(file: BunFile): Promise<boolean> {
75+
const type = file.type?.toLowerCase()
76+
if (!type) return false
77+
78+
if (type.startsWith("text/")) return false
79+
if (type.includes("charset=")) return false
80+
81+
const parts = type.split("/", 2)
82+
const top = parts[0]
83+
const rest = parts[1] ?? ""
84+
const sub = rest.split(";", 1)[0]
85+
86+
const tops = ["image", "audio", "video", "font", "model", "multipart"]
87+
if (tops.includes(top)) return true
88+
89+
if (type === "application/octet-stream") return true
90+
91+
const bins = [
92+
"zip",
93+
"gzip",
94+
"bzip",
95+
"compressed",
96+
"binary",
97+
"stream",
98+
"pdf",
99+
"msword",
100+
"powerpoint",
101+
"excel",
102+
"ogg",
103+
"exe",
104+
"dmg",
105+
"iso",
106+
"rar",
107+
]
108+
if (bins.some((mark) => sub.includes(mark))) return true
109+
110+
return false
111+
}
112+
70113
export const Event = {
71114
Edited: Bus.event(
72115
"file.edited",
@@ -188,14 +231,30 @@ export namespace File {
188231
}))
189232
}
190233

191-
export async function read(file: string) {
234+
export async function read(file: string): Promise<Content> {
192235
using _ = log.time("read", { file })
193236
const project = Instance.project
194237
const full = path.join(Instance.directory, file)
195-
const content = await Bun.file(full)
238+
const bunFile = Bun.file(full)
239+
240+
if (!(await bunFile.exists())) {
241+
return { type: "text", content: "" }
242+
}
243+
244+
const encode = await shouldEncode(bunFile)
245+
246+
if (encode) {
247+
const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0))
248+
const content = Buffer.from(buffer).toString("base64")
249+
const mimeType = bunFile.type || "application/octet-stream"
250+
return { type: "text", content, mimeType, encoding: "base64" }
251+
}
252+
253+
const content = await bunFile
196254
.text()
197255
.catch(() => "")
198256
.then((x) => x.trim())
257+
199258
if (project.vcs === "git") {
200259
let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text()
201260
if (!diff.trim()) diff = await $`git diff --staged ${file}`.cwd(Instance.directory).quiet().nothrow().text()
@@ -206,10 +265,10 @@ export namespace File {
206265
ignoreWhitespace: true,
207266
})
208267
const diff = formatPatch(patch)
209-
return { content, patch, diff }
268+
return { type: "text", content, patch, diff }
210269
}
211270
}
212-
return { content }
271+
return { type: "text", content }
213272
}
214273

215274
export async function list(dir?: string) {

0 commit comments

Comments
 (0)