Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/app/src/context/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ function errorMessage(error: unknown, fallback: string) {
return fallback
}

function errorStatus(error: unknown) {
if (!(error instanceof Error)) return
const cause = error.cause
if (typeof cause !== "object" || cause === null) return
const status = "status" in cause ? cause.status : undefined
return typeof status === "number" ? status : undefined
}

export const { use: useFile, provider: FileProvider } = createSimpleContext({
name: "File",
gate: false,
Expand Down Expand Up @@ -82,7 +90,11 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
list: (dir) =>
sdk()
.client.file.list({ path: dir })
.then((x) => x.data ?? []),
.then((x) => x.data ?? [])
.catch((error) => {
if (errorStatus(error) === 404) return [] // cause: directory not found
throw error
}),
onError: (message) => {
showToast({
variant: "error",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Schema } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { Authorization } from "../middleware/authorization"
import { InstanceContextMiddleware } from "../middleware/instance-context"
import { ApiNotFoundError } from "../errors"
import {
WorkspaceRoutingMiddleware,
WorkspaceRoutingQuery,
Expand Down Expand Up @@ -138,6 +139,7 @@ export const FileApi = HttpApi.make("file")
HttpApiEndpoint.get("list", FilePaths.list, {
query: FileQuery,
success: described(Schema.Array(LegacyEntry), "Files and directories"),
error: ApiNotFoundError,
}).annotateMerge(
OpenApi.annotations({
identifier: "file.list",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ignore from "ignore"
import path from "path"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
import { notFound } from "../errors"

export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handlers) =>
Effect.gen(function* () {
Expand Down Expand Up @@ -65,6 +66,9 @@ export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handl

const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) {
const directory = (yield* InstanceState.context).directory
const target = path.resolve(directory, ctx.query.path)
if (!FSUtil.contains(directory, target)) return yield* Effect.die(new Error("Path escapes the location"))
if (!(yield* FSUtil.Service.use((fs) => fs.isDir(target)))) return yield* notFound("Directory not found")
return yield* filesystem(
Effect.gen(function* () {
const fs = yield* FileSystem.Service
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/test/server/httpapi-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ describe("file HttpApi", () => {
expect(await status.json()).toEqual([])
})

test("returns not found for missing directory", async () => {
await using tmp = await tmpdir({ git: true })

const response = await request(FilePaths.list, tmp.path, { path: path.join("does", "not", "exist") })

expect(response.status).toBe(404)
expect(await response.json()).toMatchObject({ name: "NotFoundError", data: { message: "Directory not found" } })
})

test("serves search endpoints", async () => {
await using tmp = await tmpdir({ git: true })
await Bun.write(path.join(tmp.path, "hello.txt"), "needle")
Expand Down
18 changes: 11 additions & 7 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2266,6 +2266,13 @@ export type FileNode = {
ignored: boolean
}

export type NotFoundError = {
name: "NotFoundError"
data: {
message: string
}
}

export type FileContent = {
type: "text" | "binary"
content: string
Expand Down Expand Up @@ -2535,13 +2542,6 @@ export type ProviderAuthError1 = {
}
}

export type NotFoundError = {
name: "NotFoundError"
data: {
message: string
}
}

export type TextPartInput = {
id?: string
type: "text"
Expand Down Expand Up @@ -8549,6 +8549,10 @@ export type FileListErrors = {
* Bad request
*/
400: BadRequestError
/**
* NotFoundError
*/
404: NotFoundError
}

export type FileListError = FileListErrors[keyof FileListErrors]
Expand Down
Loading