chore: generate
This commit is contained in:
parent
132ef57272
commit
cc52dc396c
@ -147,10 +147,7 @@ function collectPaths<T>(
|
||||
return [{ ...result, score: scores[index]?.total ?? 0 }]
|
||||
})
|
||||
rows.sort(
|
||||
(a, b) =>
|
||||
b.score - a.score ||
|
||||
a.path.length - b.path.length ||
|
||||
(a.path < b.path ? -1 : a.path > b.path ? 1 : 0),
|
||||
(a, b) => b.score - a.score || a.path.length - b.path.length || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0),
|
||||
)
|
||||
|
||||
const seen = new Set<string>()
|
||||
|
||||
@ -35,7 +35,10 @@ export interface Interface {
|
||||
readonly normalize: (
|
||||
resource: string,
|
||||
content: FileSystem.Content & { readonly encoding: "base64" },
|
||||
) => Effect.Effect<FileSystem.Content & { readonly encoding: "base64" }, ResizerUnavailableError | DecodeError | SizeError>
|
||||
) => Effect.Effect<
|
||||
FileSystem.Content & { readonly encoding: "base64" },
|
||||
ResizerUnavailableError | DecodeError | SizeError
|
||||
>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
||||
|
||||
@ -112,7 +112,8 @@ export const layer = Layer.effect(
|
||||
const root = yield* fs.realPath(directory).pipe(Effect.orDie)
|
||||
if (!FSUtil.contains(root, real)) return yield* Effect.die(new globalThis.Error("Path escapes the search root"))
|
||||
const info = yield* fs.stat(real).pipe(Effect.orDie)
|
||||
const type = info.type === "File" ? ("file" as const) : info.type === "Directory" ? ("directory" as const) : undefined
|
||||
const type =
|
||||
info.type === "File" ? ("file" as const) : info.type === "Directory" ? ("directory" as const) : undefined
|
||||
if (!type) return yield* Effect.die(new globalThis.Error("Search root is not a file or directory"))
|
||||
return { real, root, resource: slash(path.relative(root, real)) || ".", type }
|
||||
})
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
import {
|
||||
ToolOutput,
|
||||
type LLMEvent,
|
||||
type ProviderMetadata,
|
||||
type ToolResultValue,
|
||||
type Usage,
|
||||
} from "@opencode-ai/llm"
|
||||
import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
|
||||
import { DateTime, Effect } from "effect"
|
||||
import { EventV2 } from "../../event"
|
||||
import { ModelV2 } from "../../model"
|
||||
|
||||
@ -53,16 +53,45 @@ export class ListPage extends Schema.Class<ListPage>("ReadTool.ListPage")({
|
||||
|
||||
export interface Interface {
|
||||
readonly inspect: (path: AbsolutePath) => Effect.Effect<"file" | "directory">
|
||||
readonly read: (path: AbsolutePath, resource: string, page?: PageInput) => Effect.Effect<FileSystem.Content | TextPage>
|
||||
readonly read: (
|
||||
path: AbsolutePath,
|
||||
resource: string,
|
||||
page?: PageInput,
|
||||
) => Effect.Effect<FileSystem.Content | TextPage>
|
||||
readonly list: (path: AbsolutePath, page?: PageInput) => Effect.Effect<ListPage>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ReadToolFileSystem") {}
|
||||
|
||||
const extensions = new Set([
|
||||
".zip", ".tar", ".gz", ".exe", ".dll", ".so", ".class", ".jar", ".war", ".7z", ".doc", ".docx",
|
||||
".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp", ".bin", ".dat", ".obj", ".o", ".a",
|
||||
".lib", ".wasm", ".pyc", ".pyo",
|
||||
".zip",
|
||||
".tar",
|
||||
".gz",
|
||||
".exe",
|
||||
".dll",
|
||||
".so",
|
||||
".class",
|
||||
".jar",
|
||||
".war",
|
||||
".7z",
|
||||
".doc",
|
||||
".docx",
|
||||
".xls",
|
||||
".xlsx",
|
||||
".ppt",
|
||||
".pptx",
|
||||
".odt",
|
||||
".ods",
|
||||
".odp",
|
||||
".bin",
|
||||
".dat",
|
||||
".obj",
|
||||
".o",
|
||||
".a",
|
||||
".lib",
|
||||
".wasm",
|
||||
".pyc",
|
||||
".pyo",
|
||||
])
|
||||
const startsWith = (bytes: Uint8Array, prefix: number[]) => prefix.every((value, index) => bytes[index] === value)
|
||||
const imageMime = (bytes: Uint8Array) => {
|
||||
@ -113,7 +142,9 @@ export const read = Effect.fn("ReadTool.read")(function* (
|
||||
const chunks = [first]
|
||||
let total = first.length
|
||||
while (total <= MAX_MEDIA_INGEST_BYTES) {
|
||||
const chunk = yield* file.readAlloc(Math.min(64 * 1024, MAX_MEDIA_INGEST_BYTES + 1 - total)).pipe(Effect.orDie)
|
||||
const chunk = yield* file
|
||||
.readAlloc(Math.min(64 * 1024, MAX_MEDIA_INGEST_BYTES + 1 - total))
|
||||
.pipe(Effect.orDie)
|
||||
if (Option.isNone(chunk)) break
|
||||
chunks.push(chunk.value)
|
||||
total += chunk.value.length
|
||||
@ -123,7 +154,10 @@ export const read = Effect.fn("ReadTool.read")(function* (
|
||||
return {
|
||||
uri: pathToFileURL(real).href,
|
||||
name: path.basename(real),
|
||||
content: Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total).toString("base64"),
|
||||
content: Buffer.concat(
|
||||
chunks.map((chunk) => Buffer.from(chunk)),
|
||||
total,
|
||||
).toString("base64"),
|
||||
encoding: "base64" as const,
|
||||
mime,
|
||||
}
|
||||
@ -226,11 +260,7 @@ export const read = Effect.fn("ReadTool.read")(function* (
|
||||
)
|
||||
})
|
||||
|
||||
export const list = Effect.fn("ReadTool.list")(function* (
|
||||
fs: FSUtil.Interface,
|
||||
input: string,
|
||||
page: PageInput = {},
|
||||
) {
|
||||
export const list = Effect.fn("ReadTool.list")(function* (fs: FSUtil.Interface, input: string, page: PageInput = {}) {
|
||||
const real = yield* fs.realPath(input).pipe(Effect.orDie)
|
||||
const items = yield* fs.readDirectoryEntries(real).pipe(Effect.orDie)
|
||||
const offset = page.offset ?? 1
|
||||
|
||||
@ -59,7 +59,8 @@ export const layer = Layer.effectDiscard(
|
||||
return yield* Effect.die(new Error("Path escapes the allowed read root"))
|
||||
const real = yield* fs.realPath(absolute).pipe(Effect.orDie)
|
||||
const root = yield* fs.realPath(selected).pipe(Effect.orDie)
|
||||
if (!FSUtil.contains(root, real)) return yield* Effect.die(new Error("Path escapes the allowed read root"))
|
||||
if (!FSUtil.contains(root, real))
|
||||
return yield* Effect.die(new Error("Path escapes the allowed read root"))
|
||||
const resource = path.relative(root, real).replaceAll("\\", "/") || "."
|
||||
const target = AbsolutePath.make(real)
|
||||
const type = yield* reader.inspect(target)
|
||||
@ -71,8 +72,7 @@ export const layer = Layer.effectDiscard(
|
||||
agent: context.agent,
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
if (type === "directory")
|
||||
return yield* reader.list(target, { offset: input.offset, limit: input.limit })
|
||||
if (type === "directory") return yield* reader.list(target, { offset: input.offset, limit: input.limit })
|
||||
const content = yield* reader.read(target, resource, {
|
||||
offset: input.offset,
|
||||
limit: input.limit,
|
||||
|
||||
@ -92,11 +92,10 @@ export function make<Input extends SchemaType<any>, Output extends SchemaType<an
|
||||
),
|
||||
),
|
||||
),
|
||||
Effect.map((output) =>
|
||||
({
|
||||
structured: output,
|
||||
content:
|
||||
config.toModelOutput?.({ input, output }).map((part) =>
|
||||
Effect.map((output) => ({
|
||||
structured: output,
|
||||
content:
|
||||
config.toModelOutput?.({ input, output }).map((part) =>
|
||||
part.type === "text"
|
||||
? { type: "text" as const, text: part.text }
|
||||
: {
|
||||
@ -105,9 +104,8 @@ export function make<Input extends SchemaType<any>, Output extends SchemaType<an
|
||||
mime: part.mime,
|
||||
name: part.name,
|
||||
},
|
||||
) ?? (typeof output === "string" ? [{ type: "text" as const, text: output }] : []),
|
||||
}),
|
||||
),
|
||||
) ?? (typeof output === "string" ? [{ type: "text" as const, text: output }] : []),
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -73,8 +73,12 @@ describe("FileSystem", () => {
|
||||
{ path: RelativePath.make("src"), type: "directory", mime: "application/x-directory" },
|
||||
{ path: RelativePath.make("README.md"), type: "file", mime: "text/markdown" },
|
||||
])
|
||||
expect(yield* Effect.promise(() => Promise.all(entries.map((entry) => fs.realpath(fileURLToPath(entry.uri)))))).toEqual(
|
||||
yield* Effect.promise(() => Promise.all([fs.realpath(path.join(directory, "src")), fs.realpath(path.join(directory, "README.md"))])),
|
||||
expect(
|
||||
yield* Effect.promise(() => Promise.all(entries.map((entry) => fs.realpath(fileURLToPath(entry.uri))))),
|
||||
).toEqual(
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all([fs.realpath(path.join(directory, "src")), fs.realpath(path.join(directory, "README.md"))]),
|
||||
),
|
||||
)
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
@ -84,12 +88,16 @@ describe("FileSystem", () => {
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const service = yield* FileSystem.Service
|
||||
expect(Exit.isFailure(yield* service.read({ path: RelativePath.make("../outside.txt") }).pipe(Effect.exit))).toBe(true)
|
||||
expect(
|
||||
Exit.isFailure(yield* service.read({ path: RelativePath.make("../outside.txt") }).pipe(Effect.exit)),
|
||||
).toBe(true)
|
||||
if (process.platform === "win32") return
|
||||
const outside = `${directory}-outside.txt`
|
||||
yield* Effect.promise(() => fs.writeFile(outside, "outside"))
|
||||
yield* Effect.promise(() => fs.symlink(outside, path.join(directory, "link.txt")))
|
||||
expect(Exit.isFailure(yield* service.read({ path: RelativePath.make("link.txt") }).pipe(Effect.exit))).toBe(true)
|
||||
expect(Exit.isFailure(yield* service.read({ path: RelativePath.make("link.txt") }).pipe(Effect.exit))).toBe(
|
||||
true,
|
||||
)
|
||||
yield* Effect.promise(() => fs.rm(outside, { force: true }))
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
|
||||
@ -43,11 +43,7 @@ const search = Layer.succeed(
|
||||
)
|
||||
|
||||
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
|
||||
const glob = GlobTool.layer.pipe(
|
||||
Layer.provide(registry),
|
||||
Layer.provide(permission),
|
||||
Layer.provide(search),
|
||||
)
|
||||
const glob = GlobTool.layer.pipe(Layer.provide(registry), Layer.provide(permission), Layer.provide(search))
|
||||
const it = testEffect(Layer.mergeAll(registry, permission, search, glob))
|
||||
|
||||
const reset = () => {
|
||||
|
||||
@ -54,11 +54,7 @@ const permission = Layer.succeed(
|
||||
}),
|
||||
)
|
||||
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
|
||||
const grep = GrepTool.layer.pipe(
|
||||
Layer.provide(registry),
|
||||
Layer.provide(search),
|
||||
Layer.provide(permission),
|
||||
)
|
||||
const grep = GrepTool.layer.pipe(Layer.provide(registry), Layer.provide(search), Layer.provide(permission))
|
||||
const it = testEffect(Layer.mergeAll(registry, search, permission, grep))
|
||||
const sessionID = SessionV2.ID.make("ses_grep_tool_test")
|
||||
|
||||
|
||||
@ -37,10 +37,7 @@ let configEntries: Config.Entry[] = []
|
||||
const reader = Layer.succeed(
|
||||
ReadToolFileSystem.Service,
|
||||
ReadToolFileSystem.Service.of({
|
||||
inspect: () =>
|
||||
resolveFailure === undefined
|
||||
? Effect.succeed(resolvedType)
|
||||
: Effect.die(resolveFailure),
|
||||
inspect: () => (resolveFailure === undefined ? Effect.succeed(resolvedType) : Effect.die(resolveFailure)),
|
||||
read: (input, _resource, page = {}) => {
|
||||
readCalls.push({ input, page })
|
||||
if (readFailure !== undefined) return Effect.die(readFailure)
|
||||
@ -73,9 +70,7 @@ const config = Layer.succeed(Config.Service, Config.Service.of({ entries: () =>
|
||||
const image = Image.layer.pipe(Layer.provide(config))
|
||||
const testFileSystem = Layer.effect(
|
||||
FSUtil.Service,
|
||||
FSUtil.Service.use((fs) =>
|
||||
Effect.succeed(FSUtil.Service.of({ ...fs, realPath: (path) => Effect.succeed(path) })),
|
||||
),
|
||||
FSUtil.Service.use((fs) => Effect.succeed(FSUtil.Service.of({ ...fs, realPath: (path) => Effect.succeed(path) }))),
|
||||
).pipe(Layer.provide(FSUtil.defaultLayer))
|
||||
const infrastructure = Layer.mergeAll(
|
||||
testFileSystem,
|
||||
|
||||
@ -269,7 +269,12 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
|
||||
content.push({ text: item.text })
|
||||
continue
|
||||
}
|
||||
const media = yield* BedrockMedia.lower({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name })
|
||||
const media = yield* BedrockMedia.lower({
|
||||
type: "media",
|
||||
mediaType: item.mime,
|
||||
data: item.uri,
|
||||
filename: item.name,
|
||||
})
|
||||
if (!("image" in media))
|
||||
return yield* ProviderShared.invalidRequest("Bedrock Converse only supports image media in tool results")
|
||||
content.push(media)
|
||||
|
||||
@ -200,9 +200,7 @@ const lowerToolCall = (part: ToolCallPart): OpenAIChatAssistantToolCall => ({
|
||||
},
|
||||
})
|
||||
|
||||
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (
|
||||
part: MediaPart,
|
||||
) {
|
||||
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part: MediaPart) {
|
||||
const media = yield* ProviderShared.validateMedia("OpenAI Chat", part, IMAGE_MIMES)
|
||||
return { type: "image_url" as const, image_url: { url: media.dataUrl } }
|
||||
})
|
||||
|
||||
@ -327,12 +327,18 @@ describe("OpenAI Chat route", () => {
|
||||
Message.tool({
|
||||
id: "call_1",
|
||||
name: "read",
|
||||
result: { type: "content", value: [{ type: "file", uri: "data:image/png;base64,AAEC", mime: "image/png" }] },
|
||||
result: {
|
||||
type: "content",
|
||||
value: [{ type: "file", uri: "data:image/png;base64,AAEC", mime: "image/png" }],
|
||||
},
|
||||
}),
|
||||
Message.tool({
|
||||
id: "call_2",
|
||||
name: "read",
|
||||
result: { type: "content", value: [{ type: "file", uri: "data:image/webp;base64,UklG", mime: "image/webp" }] },
|
||||
result: {
|
||||
type: "content",
|
||||
value: [{ type: "file", uri: "data:image/webp;base64,UklG", mime: "image/webp" }],
|
||||
},
|
||||
}),
|
||||
Message.system("Inspect both images."),
|
||||
],
|
||||
|
||||
@ -241,16 +241,12 @@ describe("LLMClient tools", () => {
|
||||
uri: "data:image/png;base64,AAAA",
|
||||
mime: "image/png",
|
||||
})
|
||||
expect(
|
||||
decode({ type: "file", uri: "https://example.test/image.png", mime: "image/png" }),
|
||||
).toEqual({
|
||||
expect(decode({ type: "file", uri: "https://example.test/image.png", mime: "image/png" })).toEqual({
|
||||
type: "file",
|
||||
uri: "https://example.test/image.png",
|
||||
mime: "image/png",
|
||||
})
|
||||
expect(
|
||||
decode({ type: "file", uri: "file:///tmp/image.png", mime: "image/png" }),
|
||||
).toEqual({
|
||||
expect(decode({ type: "file", uri: "file:///tmp/image.png", mime: "image/png" })).toEqual({
|
||||
type: "file",
|
||||
uri: "file:///tmp/image.png",
|
||||
mime: "image/png",
|
||||
@ -270,9 +266,7 @@ describe("LLMClient tools", () => {
|
||||
})
|
||||
expect(
|
||||
ToolOutput.toResultValue(
|
||||
ToolOutput.make({}, [
|
||||
{ type: "file", uri: "https://example.test/image.png", mime: "image/png" },
|
||||
]),
|
||||
ToolOutput.make({}, [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }]),
|
||||
),
|
||||
).toEqual({
|
||||
type: "content",
|
||||
@ -280,9 +274,7 @@ describe("LLMClient tools", () => {
|
||||
})
|
||||
expect(
|
||||
ToolOutput.toResultValue(
|
||||
ToolOutput.make({}, [
|
||||
{ type: "file", uri: "file:///tmp/image.png", mime: "image/png" },
|
||||
]),
|
||||
ToolOutput.make({}, [{ type: "file", uri: "file:///tmp/image.png", mime: "image/png" }]),
|
||||
),
|
||||
).toEqual({
|
||||
type: "content",
|
||||
@ -307,9 +299,7 @@ describe("LLMClient tools", () => {
|
||||
parameters: Schema.Struct({}),
|
||||
success: Schema.Struct({ ok: Schema.Boolean }),
|
||||
execute: () => Effect.succeed({ ok: true }),
|
||||
toModelOutput: () => [
|
||||
{ type: "file", uri: "https://example.test/image.png", mime: "image/png" },
|
||||
],
|
||||
toModelOutput: () => [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }],
|
||||
})
|
||||
|
||||
const dispatched = yield* ToolRuntime.dispatch(
|
||||
|
||||
@ -91,9 +91,7 @@ console.log("--- Search service (warm) ---")
|
||||
for (const q of FILE_QUERIES) {
|
||||
const t = performance.now()
|
||||
const r = await run(Search.Service.use((svc) => svc.file({ cwd: dir, query: q, limit: FILE_LIMIT })))
|
||||
console.log(
|
||||
`[Search.file] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.length} results)`,
|
||||
)
|
||||
console.log(`[Search.file] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.length} results)`)
|
||||
}
|
||||
|
||||
for (const q of GREP_QUERIES) {
|
||||
|
||||
@ -38,9 +38,7 @@ const FileReadCommand = effectCmd({
|
||||
description: "File path to read",
|
||||
}),
|
||||
handler: Effect.fn("Cli.debug.file.read")(function* (args) {
|
||||
const content = yield* filesystem(
|
||||
FileSystem.Service.use((svc) => svc.read({ path: RelativePath.make(args.path) })),
|
||||
)
|
||||
const content = yield* filesystem(FileSystem.Service.use((svc) => svc.read({ path: RelativePath.make(args.path) })))
|
||||
process.stdout.write(JSON.stringify(content, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
@ -55,9 +53,7 @@ const FileListCommand = effectCmd({
|
||||
description: "File path to list",
|
||||
}),
|
||||
handler: Effect.fn("Cli.debug.file.list")(function* (args) {
|
||||
const files = yield* filesystem(
|
||||
FileSystem.Service.use((svc) => svc.list({ path: RelativePath.make(args.path) })),
|
||||
)
|
||||
const files = yield* filesystem(FileSystem.Service.use((svc) => svc.list({ path: RelativePath.make(args.path) })))
|
||||
process.stdout.write(JSON.stringify(files, null, 2) + EOL)
|
||||
}),
|
||||
})
|
||||
|
||||
@ -595,13 +595,14 @@ export const layer = Layer.effect(
|
||||
const assistantMessageID = yield* requireV2AssistantMessage(toolCall?.call)
|
||||
const content = [
|
||||
{ type: "text" as const, text: output.output },
|
||||
...(output.attachments?.map((item: SessionV1.FilePart) =>
|
||||
({
|
||||
type: "file",
|
||||
uri: item.url,
|
||||
mime: item.mime,
|
||||
name: item.filename,
|
||||
}) as const,
|
||||
...(output.attachments?.map(
|
||||
(item: SessionV1.FilePart) =>
|
||||
({
|
||||
type: "file",
|
||||
uri: item.url,
|
||||
mime: item.mime,
|
||||
name: item.filename,
|
||||
}) as const,
|
||||
) ?? []),
|
||||
]
|
||||
const unsupported = content.find((item) => item.type === "file" && !item.uri.startsWith("data:"))
|
||||
|
||||
@ -11371,14 +11371,7 @@
|
||||
"$ref": "#/components/schemas/LocationInfo"
|
||||
},
|
||||
"data": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTextContent"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemBinaryContent"
|
||||
}
|
||||
]
|
||||
"$ref": "#/components/schemas/FileSystemContent"
|
||||
}
|
||||
},
|
||||
"required": ["location", "data"],
|
||||
@ -11507,6 +11500,112 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/fs/find": {
|
||||
"get": {
|
||||
"tags": ["filesystem"],
|
||||
"operationId": "v2.fs.find",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "location",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"required": false,
|
||||
"style": "deepObject",
|
||||
"explode": true
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["file", "directory"]
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/LocationInfo"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/FileSystemEntry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["location", "data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/InvalidRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Find recursively ranked filesystem entries relative to the requested location.",
|
||||
"summary": "Find files",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.fs.find({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/command": {
|
||||
"get": {
|
||||
"tags": ["commands"],
|
||||
@ -21492,51 +21591,8 @@
|
||||
"type": "string",
|
||||
"enum": ["file"]
|
||||
},
|
||||
"source": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["data"]
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "data"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["url"]
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "url"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["file"]
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "uri"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"type": "string"
|
||||
@ -21545,7 +21601,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "source", "mime"],
|
||||
"required": ["type", "uri", "mime"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SessionNextRetry_error": {
|
||||
@ -24986,42 +25042,27 @@
|
||||
"required": ["id", "projectID", "action", "resource"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FileSystemTextContent": {
|
||||
"FileSystemContent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["text"]
|
||||
},
|
||||
"content": {
|
||||
"uri": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "content", "mime"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FileSystemBinaryContent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["binary"]
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"encoding": {
|
||||
"type": "string",
|
||||
"enum": ["base64"]
|
||||
"enum": ["utf8", "base64"]
|
||||
},
|
||||
"mime": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "content", "encoding", "mime"],
|
||||
"required": ["uri", "content", "encoding", "mime"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FileSystemEntry": {
|
||||
|
||||
@ -1073,8 +1073,7 @@ function toolOutput(content?: Array<ToolTextContent | ToolFileContent>) {
|
||||
return (content ?? [])
|
||||
.map((item) => {
|
||||
if (item.type === "text") return item.text.trim()
|
||||
const source =
|
||||
item.uri
|
||||
const source = item.uri
|
||||
return `[file ${item.name ?? source}]`
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user