refactor(core): tag read outputs (#39122)

This commit is contained in:
Aiden Cline 2026-07-27 09:09:44 -05:00 committed by GitHub
commit 9977ef0160
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 9 deletions

View file

@ -4,7 +4,6 @@ import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin
import { dirname } from "path"
import { ToolFailure } from "@opencode-ai/ai"
import { Effect, Schema } from "effect"
import { FileSystem } from "../../filesystem"
import { FSUtil } from "@opencode-ai/util/fs-util"
import { Location } from "../../location"
import { LocationMutation } from "../../location-mutation"
@ -26,7 +25,11 @@ const LocationInput = Schema.Struct({
}),
})
const Input = LocationInput
const Output = Schema.Union([FileSystem.Content, ReadToolFileSystem.TextPage, ReadToolFileSystem.ListPage])
const Output = Schema.Union([
ReadToolFileSystem.FileContent,
ReadToolFileSystem.TextPage,
ReadToolFileSystem.ListPage,
])
export const Plugin = {
id: "opencode.tool.read",
@ -107,7 +110,7 @@ export const Plugin = {
Effect.catch(() => Effect.void),
Effect.catchDefect(() => Effect.void),
)
if ("encoding" in content && content.encoding === "base64" && !SUPPORTED_IMAGE_MIMES.has(content.mime))
if (content.type === "file" && content.encoding === "base64" && !SUPPORTED_IMAGE_MIMES.has(content.mime))
return yield* Effect.fail(new ReadToolFileSystem.BinaryFileError({ resource }))
return content
}).pipe(
@ -115,7 +118,7 @@ export const Plugin = {
// Image base64 reaches the model through content items; avoid a second
// unresized copy in model text.
const content =
"encoding" in output && output.encoding === "base64"
output.type === "file" && output.encoding === "base64"
? SUPPORTED_IMAGE_MIMES.has(output.mime)
? ([
{ type: "text", text: "Image read successfully" },

View file

@ -75,6 +75,12 @@ export const PageInput = Schema.Struct({
})
export type PageInput = typeof PageInput.Type
export const FileContent = Schema.Struct({
type: Schema.Literal("file"),
...FileSystem.Content.fields,
}).annotate({ identifier: "ReadTool.FileContent" })
export type FileContent = typeof FileContent.Type
export class TextPage extends Schema.Class<TextPage>("ReadTool.TextPage")({
type: Schema.Literal("text-page"),
content: Schema.String,
@ -85,6 +91,7 @@ export class TextPage extends Schema.Class<TextPage>("ReadTool.TextPage")({
}) {}
export class ListPage extends Schema.Class<ListPage>("ReadTool.ListPage")({
type: Schema.Literal("list-page"),
entries: Schema.Array(FileSystem.Entry),
truncated: Schema.Boolean,
next: PositiveInt.pipe(Schema.optional),
@ -96,7 +103,7 @@ export interface Interface {
path: AbsolutePath,
resource: string,
page?: PageInput,
) => Effect.Effect<FileSystem.Content | TextPage, ReadError>
) => Effect.Effect<FileContent | TextPage, ReadError>
readonly list: (path: AbsolutePath, page?: PageInput) => Effect.Effect<ListPage, FSUtil.Error>
}
@ -199,6 +206,7 @@ export const read = Effect.fn("ReadTool.read")(function* (
if (total > MAX_MEDIA_INGEST_BYTES)
return yield* Effect.fail(new MediaIngestLimitError({ resource, maximumBytes: MAX_MEDIA_INGEST_BYTES }))
return {
type: "file" as const,
uri: pathToFileURL(real).href,
name: path.basename(real),
content: Buffer.concat(
@ -223,6 +231,7 @@ export const read = Effect.fn("ReadTool.read")(function* (
}
text.push(yield* decodeUtf8(resource, decoder))
return {
type: "file" as const,
uri: pathToFileURL(real).href,
name: path.basename(real),
content: text.join(""),
@ -348,7 +357,12 @@ export const list = Effect.fn("ReadTool.list")(function* (fs: FSUtil.Interface,
.sort((a, b) => (a.type === b.type ? a.path.localeCompare(b.path) : a.type === "directory" ? -1 : 1))
const selected = visible.slice(offset - 1, offset - 1 + limit)
const truncated = offset - 1 + selected.length < visible.length
return new ListPage({ entries: selected, truncated, ...(truncated ? { next: offset + selected.length } : {}) })
return new ListPage({
type: "list-page",
entries: selected,
truncated,
...(truncated ? { next: offset + selected.length } : {}),
})
})
const layer = Layer.effect(