Browse Source

tweak: adjust file api to encode images (#3292)

kcrommett 4 months ago
parent
commit
a99bd3aa2c
1 changed files with 63 additions and 4 deletions
  1. 63 4
      packages/opencode/src/file/index.ts

+ 63 - 4
packages/opencode/src/file/index.ts

@@ -1,6 +1,7 @@
 import z from "zod/v4"
 import { Bus } from "../bus"
 import { $ } from "bun"
+import type { BunFile } from "bun"
 import { formatPatch, structuredPatch } from "diff"
 import path from "path"
 import fs from "fs"
@@ -41,6 +42,7 @@ export namespace File {
 
   export const Content = z
     .object({
+      type: z.literal("text"),
       content: z.string(),
       diff: z.string().optional(),
       patch: z
@@ -61,12 +63,53 @@ export namespace File {
           index: z.string().optional(),
         })
         .optional(),
+      encoding: z.literal("base64").optional(),
+      mimeType: z.string().optional(),
     })
     .meta({
       ref: "FileContent",
     })
   export type Content = z.infer<typeof Content>
 
+  async function shouldEncode(file: BunFile): Promise<boolean> {
+    const type = file.type?.toLowerCase()
+    if (!type) return false
+
+    if (type.startsWith("text/")) return false
+    if (type.includes("charset=")) return false
+
+    const parts = type.split("/", 2)
+    const top = parts[0]
+    const rest = parts[1] ?? ""
+    const sub = rest.split(";", 1)[0]
+
+    const tops = ["image", "audio", "video", "font", "model", "multipart"]
+    if (tops.includes(top)) return true
+
+    if (type === "application/octet-stream") return true
+
+    const bins = [
+      "zip",
+      "gzip",
+      "bzip",
+      "compressed",
+      "binary",
+      "stream",
+      "pdf",
+      "msword",
+      "powerpoint",
+      "excel",
+      "ogg",
+      "exe",
+      "dmg",
+      "iso",
+      "rar",
+    ]
+    if (bins.some((mark) => sub.includes(mark))) return true
+
+    return false
+  }
+
   export const Event = {
     Edited: Bus.event(
       "file.edited",
@@ -188,14 +231,30 @@ export namespace File {
     }))
   }
 
-  export async function read(file: string) {
+  export async function read(file: string): Promise<Content> {
     using _ = log.time("read", { file })
     const project = Instance.project
     const full = path.join(Instance.directory, file)
-    const content = await Bun.file(full)
+    const bunFile = Bun.file(full)
+
+    if (!(await bunFile.exists())) {
+      return { type: "text", content: "" }
+    }
+
+    const encode = await shouldEncode(bunFile)
+
+    if (encode) {
+      const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0))
+      const content = Buffer.from(buffer).toString("base64")
+      const mimeType = bunFile.type || "application/octet-stream"
+      return { type: "text", content, mimeType, encoding: "base64" }
+    }
+
+    const content = await bunFile
       .text()
       .catch(() => "")
       .then((x) => x.trim())
+
     if (project.vcs === "git") {
       let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text()
       if (!diff.trim()) diff = await $`git diff --staged ${file}`.cwd(Instance.directory).quiet().nothrow().text()
@@ -206,10 +265,10 @@ export namespace File {
           ignoreWhitespace: true,
         })
         const diff = formatPatch(patch)
-        return { content, patch, diff }
+        return { type: "text", content, patch, diff }
       }
     }
-    return { content }
+    return { type: "text", content }
   }
 
   export async function list(dir?: string) {