feat(core): resolve prompt attachments

This commit is contained in:
Dax Raad 2026-07-06 15:04:23 -04:00
commit 4d100de194
29 changed files with 610 additions and 426 deletions

View file

@ -1,41 +1,6 @@
export * as Mime from "./mime.js"
import { Effect, FileSystem, Option } from "effect"
import { fileURLToPath } from "url"
import { FSUtil } from "./fs-util"
const SAMPLE_BYTES = 8192
export const resolve = Effect.fn("Mime.resolve")(function* (uri: string) {
const data = dataSample(uri)
if (data) return detect(data)
const target = yield* Effect.try({
try: () => localPath(uri),
catch: () => new Error("Invalid file URI"),
}).pipe(Effect.catch(() => Effect.succeed(undefined)))
if (!target) return "application/octet-stream"
const fs = yield* FSUtil.Service
const local = yield* Effect.scoped(
Effect.gen(function* () {
const info = yield* fs.stat(target)
if (info.type === "Directory") return { type: "directory" as const }
if (info.type !== "File") return
const file = yield* fs.open(target)
return {
type: "file" as const,
sample: Option.getOrElse(yield* file.readAlloc(FileSystem.Size(SAMPLE_BYTES)), () => new Uint8Array()),
}
}),
).pipe(Effect.catch(() => Effect.succeed(undefined)))
if (local?.type === "directory") return "application/x-directory"
if (local?.type === "file") return detect(local.sample)
return "application/octet-stream"
})
function detect(bytes: Uint8Array) {
export function detect(bytes: Uint8Array) {
if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return "image/png"
if (startsWith(bytes, [0xff, 0xd8, 0xff])) return "image/jpeg"
if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38])) return "image/gif"
@ -52,25 +17,6 @@ function detect(bytes: Uint8Array) {
return isText(bytes) ? "text/plain" : "application/octet-stream"
}
function dataSample(uri: string) {
if (!uri.startsWith("data:")) return
const comma = uri.indexOf(",")
if (comma === -1) return new Uint8Array()
const metadata = uri.slice(5, comma)
const payload = uri.slice(comma + 1)
if (metadata.split(";").some((part) => part.toLowerCase() === "base64")) {
return Buffer.from(payload.slice(0, Math.ceil((SAMPLE_BYTES * 4) / 3) + 4), "base64").subarray(0, SAMPLE_BYTES)
}
return new TextEncoder().encode(payload.slice(0, SAMPLE_BYTES))
}
function localPath(uri: string) {
if (!URL.canParse(uri)) return
const url = new URL(uri)
if (url.protocol !== "file:") return
return fileURLToPath(url)
}
function startsWith(bytes: Uint8Array, prefix: number[]) {
return prefix.every((value, index) => bytes[index] === value)
}