mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db077a5488 | |||
| 6219fe1482 |
@@ -1,12 +1,16 @@
|
|||||||
import { lazy } from "@/util/lazy"
|
import { lazy } from "@/util/lazy"
|
||||||
|
import { Schema } from "effect"
|
||||||
|
import { zod } from "@/util/effect-zod"
|
||||||
|
import { withStatics } from "@/util/schema"
|
||||||
import type { ProjectID } from "@/project/schema"
|
import type { ProjectID } from "@/project/schema"
|
||||||
import type { WorkspaceAdaptor } from "../types"
|
import type { WorkspaceAdaptor } from "../types"
|
||||||
|
|
||||||
export type WorkspaceAdaptorEntry = {
|
export const WorkspaceAdaptorEntry = Schema.Struct({
|
||||||
type: string
|
type: Schema.String,
|
||||||
name: string
|
name: Schema.String,
|
||||||
description: string
|
description: Schema.String,
|
||||||
}
|
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||||
|
export type WorkspaceAdaptorEntry = Schema.Schema.Type<typeof WorkspaceAdaptorEntry>
|
||||||
|
|
||||||
const BUILTIN: Record<string, () => Promise<WorkspaceAdaptor>> = {
|
const BUILTIN: Record<string, () => Promise<WorkspaceAdaptor>> = {
|
||||||
worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor),
|
worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor),
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Worktree } from "@/worktree"
|
|||||||
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
|
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
|
||||||
|
|
||||||
const WorktreeConfig = z.object({
|
const WorktreeConfig = z.object({
|
||||||
name: WorkspaceInfo.shape.name,
|
name: z.string(),
|
||||||
branch: WorkspaceInfo.shape.branch.unwrap(),
|
branch: z.string(),
|
||||||
directory: WorkspaceInfo.shape.directory.unwrap(),
|
directory: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WorktreeAdaptor: WorkspaceAdaptor = {
|
export const WorktreeAdaptor: WorkspaceAdaptor = {
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
import { Schema } from "effect"
|
||||||
import { ProjectID } from "@/project/schema"
|
import { ProjectID } from "@/project/schema"
|
||||||
import { WorkspaceID } from "./schema"
|
import { WorkspaceID } from "./schema"
|
||||||
|
import { zod } from "@/util/effect-zod"
|
||||||
|
import { withStatics } from "@/util/schema"
|
||||||
|
|
||||||
export const WorkspaceInfo = z.object({
|
export const WorkspaceInfo = Schema.Struct({
|
||||||
id: WorkspaceID.zod,
|
id: WorkspaceID,
|
||||||
type: z.string(),
|
type: Schema.String,
|
||||||
name: z.string(),
|
name: Schema.String,
|
||||||
branch: z.string().nullable(),
|
branch: Schema.NullOr(Schema.String),
|
||||||
directory: z.string().nullable(),
|
directory: Schema.NullOr(Schema.String),
|
||||||
extra: z.unknown().nullable(),
|
extra: Schema.NullOr(Schema.Unknown),
|
||||||
projectID: ProjectID.zod,
|
projectID: ProjectID,
|
||||||
})
|
})
|
||||||
export type WorkspaceInfo = z.infer<typeof WorkspaceInfo>
|
.annotate({ identifier: "Workspace" })
|
||||||
|
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||||
|
export type WorkspaceInfo = Schema.Schema.Type<typeof WorkspaceInfo>
|
||||||
|
|
||||||
export type Target =
|
export type Target =
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -25,18 +25,19 @@ import { errorData } from "@/util/error"
|
|||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { EventSequenceTable } from "@/sync/event.sql"
|
import { EventSequenceTable } from "@/sync/event.sql"
|
||||||
import { waitEvent } from "./util"
|
import { waitEvent } from "./util"
|
||||||
|
import { Schema } from "effect"
|
||||||
|
import { zod } from "@/util/effect-zod"
|
||||||
|
import { withStatics } from "@/util/schema"
|
||||||
|
|
||||||
export const Info = WorkspaceInfo.meta({
|
export const Info = WorkspaceInfo
|
||||||
ref: "Workspace",
|
export type Info = WorkspaceInfo
|
||||||
})
|
|
||||||
export type Info = z.infer<typeof Info>
|
|
||||||
|
|
||||||
export const ConnectionStatus = z.object({
|
export const ConnectionStatus = Schema.Struct({
|
||||||
workspaceID: WorkspaceID.zod,
|
workspaceID: WorkspaceID,
|
||||||
status: z.enum(["connected", "connecting", "disconnected", "error"]),
|
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
|
||||||
error: z.string().optional(),
|
error: Schema.optional(Schema.String),
|
||||||
})
|
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||||
export type ConnectionStatus = z.infer<typeof ConnectionStatus>
|
export type ConnectionStatus = Schema.Schema.Type<typeof ConnectionStatus>
|
||||||
|
|
||||||
const Restore = z.object({
|
const Restore = z.object({
|
||||||
workspaceID: WorkspaceID.zod,
|
workspaceID: WorkspaceID.zod,
|
||||||
@@ -59,7 +60,7 @@ export const Event = {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Restore: BusEvent.define("workspace.restore", Restore),
|
Restore: BusEvent.define("workspace.restore", Restore),
|
||||||
Status: BusEvent.define("workspace.status", ConnectionStatus),
|
Status: BusEvent.define("workspace.status", ConnectionStatus.zod),
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
||||||
@@ -76,10 +77,10 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
|||||||
|
|
||||||
const CreateInput = z.object({
|
const CreateInput = z.object({
|
||||||
id: WorkspaceID.zod.optional(),
|
id: WorkspaceID.zod.optional(),
|
||||||
type: Info.shape.type,
|
type: zod(Schema.String),
|
||||||
branch: Info.shape.branch,
|
branch: zod(Schema.NullOr(Schema.String)),
|
||||||
projectID: ProjectID.zod,
|
projectID: ProjectID.zod,
|
||||||
extra: Info.shape.extra,
|
extra: zod(Schema.NullOr(Schema.Unknown)),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const create = fn(CreateInput, async (input) => {
|
export const create = fn(CreateInput, async (input) => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Filesystem } from "@/util"
|
|||||||
import { PermissionApi, permissionHandlers } from "./permission"
|
import { PermissionApi, permissionHandlers } from "./permission"
|
||||||
import { ProviderApi, providerHandlers } from "./provider"
|
import { ProviderApi, providerHandlers } from "./provider"
|
||||||
import { QuestionApi, questionHandlers } from "./question"
|
import { QuestionApi, questionHandlers } from "./question"
|
||||||
|
import { WorkspaceApi, workspaceHandlers } from "./workspace"
|
||||||
|
|
||||||
const Query = Schema.Struct({
|
const Query = Schema.Struct({
|
||||||
directory: Schema.optional(Schema.String),
|
directory: Schema.optional(Schema.String),
|
||||||
@@ -108,11 +109,13 @@ const instance = HttpRouter.middleware()(
|
|||||||
const QuestionSecured = QuestionApi.middleware(Authorization)
|
const QuestionSecured = QuestionApi.middleware(Authorization)
|
||||||
const PermissionSecured = PermissionApi.middleware(Authorization)
|
const PermissionSecured = PermissionApi.middleware(Authorization)
|
||||||
const ProviderSecured = ProviderApi.middleware(Authorization)
|
const ProviderSecured = ProviderApi.middleware(Authorization)
|
||||||
|
const WorkspaceSecured = WorkspaceApi.middleware(Authorization)
|
||||||
|
|
||||||
export const routes = Layer.mergeAll(
|
export const routes = Layer.mergeAll(
|
||||||
HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)),
|
HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)),
|
||||||
HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)),
|
HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)),
|
||||||
HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)),
|
HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)),
|
||||||
|
HttpApiBuilder.layer(WorkspaceSecured).pipe(Layer.provide(workspaceHandlers)),
|
||||||
).pipe(
|
).pipe(
|
||||||
Layer.provide(auth),
|
Layer.provide(auth),
|
||||||
Layer.provide(normalize),
|
Layer.provide(normalize),
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { listAdaptors, WorkspaceAdaptorEntry } from "@/control-plane/adaptors"
|
||||||
|
import { Workspace } from "@/control-plane/workspace"
|
||||||
|
import { Instance } from "@/project/instance"
|
||||||
|
import { Effect, Schema } from "effect"
|
||||||
|
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||||
|
|
||||||
|
const root = "/experimental/workspace"
|
||||||
|
|
||||||
|
export const WorkspaceApi = HttpApi.make("workspace")
|
||||||
|
.add(
|
||||||
|
HttpApiGroup.make("workspace")
|
||||||
|
.add(
|
||||||
|
HttpApiEndpoint.get("adaptors", `${root}/adaptor`, {
|
||||||
|
success: Schema.Array(WorkspaceAdaptorEntry),
|
||||||
|
}).annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "experimental.workspace.adaptor.list",
|
||||||
|
summary: "List workspace adaptors",
|
||||||
|
description: "List all available workspace adaptors for the current project.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
HttpApiEndpoint.get("list", root, {
|
||||||
|
success: Schema.Array(Workspace.Info),
|
||||||
|
}).annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "experimental.workspace.list",
|
||||||
|
summary: "List workspaces",
|
||||||
|
description: "List all workspaces.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
HttpApiEndpoint.get("status", `${root}/status`, {
|
||||||
|
success: Schema.Array(Workspace.ConnectionStatus),
|
||||||
|
}).annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "experimental.workspace.status",
|
||||||
|
summary: "Workspace status",
|
||||||
|
description: "Get connection status for workspaces in the current project.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
title: "workspace",
|
||||||
|
description: "Experimental HttpApi workspace routes.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
title: "opencode experimental HttpApi",
|
||||||
|
version: "0.0.1",
|
||||||
|
description: "Experimental HttpApi surface for selected instance routes.",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const adaptors = Effect.fn("WorkspaceHttpApi.adaptors")(function* () {
|
||||||
|
return yield* Effect.promise(() => listAdaptors(Instance.project.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = Effect.fn("WorkspaceHttpApi.list")(function* () {
|
||||||
|
return Workspace.list(Instance.project)
|
||||||
|
})
|
||||||
|
|
||||||
|
const status = Effect.fn("WorkspaceHttpApi.status")(function* () {
|
||||||
|
const ids = new Set(Workspace.list(Instance.project).map((item) => item.id))
|
||||||
|
return Workspace.status().filter((item) => ids.has(item.workspaceID))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const workspaceHandlers = HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) =>
|
||||||
|
handlers.handle("adaptors", adaptors).handle("list", list).handle("status", status),
|
||||||
|
)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import { Effect } from "effect"
|
import { Context, Effect } from "effect"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Format } from "../../format"
|
import { Format } from "../../format"
|
||||||
import { TuiRoutes } from "./tui"
|
import { TuiRoutes } from "./tui"
|
||||||
@@ -41,12 +41,16 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
|
|||||||
|
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
|
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
|
||||||
const handler = ExperimentalHttpApiServer.webHandler().handler
|
const handler = ExperimentalHttpApiServer.webHandler().handler
|
||||||
app
|
const context = Context.empty() as Context.Context<unknown>
|
||||||
.all("/question", (c) => handler(c.req.raw))
|
app.get("/question", (c) => handler(c.req.raw, context))
|
||||||
.all("/question/*", (c) => handler(c.req.raw))
|
app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context))
|
||||||
.all("/permission", (c) => handler(c.req.raw))
|
app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context))
|
||||||
.all("/permission/*", (c) => handler(c.req.raw))
|
app.get("/permission", (c) => handler(c.req.raw, context))
|
||||||
.all("/provider/auth", (c) => handler(c.req.raw))
|
app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context))
|
||||||
|
app.get("/experimental/workspace", (c) => handler(c.req.raw, context))
|
||||||
|
app.get("/experimental/workspace/adaptor", (c) => handler(c.req.raw, context))
|
||||||
|
app.get("/experimental/workspace/status", (c) => handler(c.req.raw, context))
|
||||||
|
app.get("/provider/auth", (c) => handler(c.req.raw, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { Hono } from "hono"
|
|||||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { listAdaptors } from "../../control-plane/adaptors"
|
import { listAdaptors } from "../../control-plane/adaptors"
|
||||||
|
import { WorkspaceID } from "../../control-plane/schema"
|
||||||
import { Workspace } from "../../control-plane/workspace"
|
import { Workspace } from "../../control-plane/workspace"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
|
import { WorkspaceAdaptorEntry } from "../../control-plane/adaptors"
|
||||||
import { errors } from "../error"
|
import { errors } from "../error"
|
||||||
import { lazy } from "../../util/lazy"
|
import { lazy } from "../../util/lazy"
|
||||||
import { Log } from "@/util"
|
import { Log } from "@/util"
|
||||||
@@ -24,15 +26,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
description: "Workspace adaptors",
|
description: "Workspace adaptors",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(
|
schema: resolver(WorkspaceAdaptorEntry.zod.array()),
|
||||||
z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -53,7 +47,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
description: "Workspace created",
|
description: "Workspace created",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(Workspace.Info),
|
schema: resolver(Workspace.Info.zod),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -86,7 +80,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
description: "Workspaces",
|
description: "Workspaces",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(z.array(Workspace.Info)),
|
schema: resolver(Workspace.Info.zod.array()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -107,7 +101,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
description: "Workspace status",
|
description: "Workspace status",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(z.array(Workspace.ConnectionStatus)),
|
schema: resolver(Workspace.ConnectionStatus.zod.array()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -129,7 +123,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
description: "Workspace removed",
|
description: "Workspace removed",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(Workspace.Info.optional()),
|
schema: resolver(Workspace.Info.zod.optional()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -139,7 +133,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
validator(
|
validator(
|
||||||
"param",
|
"param",
|
||||||
z.object({
|
z.object({
|
||||||
id: Workspace.Info.shape.id,
|
id: WorkspaceID.zod,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
@@ -169,7 +163,7 @@ export const WorkspaceRoutes = lazy(() =>
|
|||||||
...errors(400),
|
...errors(400),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
validator("param", z.object({ id: Workspace.Info.shape.id })),
|
validator("param", z.object({ id: WorkspaceID.zod })),
|
||||||
validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })),
|
validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { id } = c.req.valid("param")
|
const { id } = c.req.valid("param")
|
||||||
|
|||||||
Reference in New Issue
Block a user