mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-16 14:13:30 +00:00
feat(core): improve read tool parity (#39126)
This commit is contained in:
parent
7d4de3d9e4
commit
65d2a4e00c
7 changed files with 97 additions and 13 deletions
|
|
@ -120,6 +120,8 @@ const layer = Layer.effect(
|
|||
|
||||
const resolve = Effect.fn("LocationMutation.resolve")(function* (input: ResolveInput) {
|
||||
const absolute = path.resolve(location.directory, input.path)
|
||||
// External access follows the requested path boundary. Symlinks reached through an
|
||||
// internal path intentionally retain internal permission semantics after canonicalization.
|
||||
const lexicallyInternal = FSUtil.contains(location.directory, absolute)
|
||||
|
||||
const resolved = yield* resolvePath(absolute)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { ReadToolFileSystem } from "../read-filesystem"
|
|||
|
||||
export const name = "read"
|
||||
const FILENAME = "AGENTS.md"
|
||||
const SUPPORTED_IMAGE_MIMES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"])
|
||||
const SUPPORTED_MEDIA_MIMES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf"])
|
||||
const LocationInput = Schema.Struct({
|
||||
path: Schema.String,
|
||||
offset: ReadToolFileSystem.PageInput.fields.offset.annotate({
|
||||
|
|
@ -110,7 +110,7 @@ export const Plugin = {
|
|||
Effect.catch(() => Effect.void),
|
||||
Effect.catchDefect(() => Effect.void),
|
||||
)
|
||||
if (content.type === "file" && content.encoding === "base64" && !SUPPORTED_IMAGE_MIMES.has(content.mime))
|
||||
if (content.type === "file" && content.encoding === "base64" && !SUPPORTED_MEDIA_MIMES.has(content.mime))
|
||||
return yield* Effect.fail(new ReadToolFileSystem.BinaryFileError({ resource }))
|
||||
return content
|
||||
}).pipe(
|
||||
|
|
@ -119,9 +119,13 @@ export const Plugin = {
|
|||
// unresized copy in model text.
|
||||
const content =
|
||||
output.type === "file" && output.encoding === "base64"
|
||||
? SUPPORTED_IMAGE_MIMES.has(output.mime)
|
||||
? SUPPORTED_MEDIA_MIMES.has(output.mime)
|
||||
? ([
|
||||
{ type: "text", text: "Image read successfully" },
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
output.mime === "application/pdf" ? "PDF read successfully" : "Image read successfully",
|
||||
},
|
||||
{
|
||||
type: "file",
|
||||
uri: `data:${output.mime};base64,${output.content}`,
|
||||
|
|
@ -136,7 +140,10 @@ export const Plugin = {
|
|||
Effect.mapError((error) => {
|
||||
const message =
|
||||
error instanceof ReadToolFileSystem.BinaryFileError ||
|
||||
error instanceof ReadToolFileSystem.MediaIngestLimitError
|
||||
error instanceof ReadToolFileSystem.MediaIngestLimitError ||
|
||||
error instanceof ReadToolFileSystem.MalformedUtf8Error ||
|
||||
error instanceof ReadToolFileSystem.OffsetOutOfRangeError ||
|
||||
error instanceof ReadToolFileSystem.PathKindError
|
||||
? error.message
|
||||
: `Unable to read ${input.path}`
|
||||
return new ToolFailure({ message, error })
|
||||
|
|
|
|||
|
|
@ -140,12 +140,13 @@ const extensions = new Set([
|
|||
".pyo",
|
||||
])
|
||||
const startsWith = (bytes: Uint8Array, prefix: number[]) => prefix.every((value, index) => bytes[index] === value)
|
||||
const imageMime = (bytes: Uint8Array) => {
|
||||
const mediaMime = (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"
|
||||
if (startsWith(bytes, [0x52, 0x49, 0x46, 0x46]) && startsWith(bytes.subarray(8), [0x57, 0x45, 0x42, 0x50]))
|
||||
return "image/webp"
|
||||
if (startsWith(bytes, [0x25, 0x50, 0x44, 0x46, 0x2d])) return "application/pdf"
|
||||
}
|
||||
const binary = (resource: string, bytes: Uint8Array) => {
|
||||
if (extensions.has(path.extname(resource).toLowerCase())) return true
|
||||
|
|
@ -191,7 +192,7 @@ export const read = Effect.fn("ReadTool.read")(function* (
|
|||
yield* file.readAlloc(Math.min(64 * 1024, Number(info.size) || 4 * 1024)),
|
||||
() => new Uint8Array(),
|
||||
)
|
||||
const mime = imageMime(first)
|
||||
const mime = mediaMime(first)
|
||||
if (mime) {
|
||||
if (info.size > MAX_MEDIA_INGEST_BYTES)
|
||||
return yield* Effect.fail(new MediaIngestLimitError({ resource, maximumBytes: MAX_MEDIA_INGEST_BYTES }))
|
||||
|
|
@ -217,7 +218,7 @@ export const read = Effect.fn("ReadTool.read")(function* (
|
|||
mime,
|
||||
}
|
||||
}
|
||||
if (startsWith(first, [0x25, 0x50, 0x44, 0x46]) || extensions.has(path.extname(resource).toLowerCase()))
|
||||
if (extensions.has(path.extname(resource).toLowerCase()))
|
||||
return yield* Effect.fail(new BinaryFileError({ resource }))
|
||||
const paged = info.size > MAX_READ_BYTES || page.offset !== undefined || page.limit !== undefined
|
||||
if (!paged) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue