summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-23 16:48:56 -0400
committerGitHub <[email protected]>2026-04-23 16:48:56 -0400
commit31d01d404afd4f82628b3f388d5c73debf250ffd (patch)
tree298572ef30a3dc94dd58b4ce022d0fac88a5d16f /packages
parent814e83ffecfddeb60cc88bba03b0c00629eecf5e (diff)
downloadopencode-31d01d404afd4f82628b3f388d5c73debf250ffd.tar.gz
opencode-31d01d404afd4f82628b3f388d5c73debf250ffd.zip
refactor(control-plane): migrate workspace DTO schemas (#24056)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/specs/effect/schema.md6
-rw-r--r--packages/opencode/src/control-plane/adaptors/index.ts8
-rw-r--r--packages/opencode/src/control-plane/adaptors/worktree.ts20
-rw-r--r--packages/opencode/src/control-plane/types.ts31
-rw-r--r--packages/opencode/src/control-plane/workspace.ts40
-rw-r--r--packages/opencode/src/server/routes/control/workspace.ts27
6 files changed, 67 insertions, 65 deletions
diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md
index 04f994e31..0319df4a0 100644
--- a/packages/opencode/specs/effect/schema.md
+++ b/packages/opencode/specs/effect/schema.md
@@ -354,9 +354,9 @@ piecewise.
- [ ] `src/cli/cmd/tui/event.ts`
- [ ] `src/cli/ui.ts`
- [ ] `src/command/index.ts`
-- [ ] `src/control-plane/adaptors/worktree.ts`
-- [ ] `src/control-plane/types.ts`
-- [ ] `src/control-plane/workspace.ts`
+- [x] `src/control-plane/adaptors/worktree.ts`
+- [x] `src/control-plane/types.ts`
+- [x] `src/control-plane/workspace.ts`
- [ ] `src/file/index.ts`
- [ ] `src/file/ripgrep.ts`
- [ ] `src/file/watcher.ts`
diff --git a/packages/opencode/src/control-plane/adaptors/index.ts b/packages/opencode/src/control-plane/adaptors/index.ts
index 291e392ea..651d09cc2 100644
--- a/packages/opencode/src/control-plane/adaptors/index.ts
+++ b/packages/opencode/src/control-plane/adaptors/index.ts
@@ -1,12 +1,6 @@
import { lazy } from "@/util/lazy"
import type { ProjectID } from "@/project/schema"
-import type { WorkspaceAdaptor } from "../types"
-
-export type WorkspaceAdaptorEntry = {
- type: string
- name: string
- description: string
-}
+import type { WorkspaceAdaptor, WorkspaceAdaptorEntry } from "../types"
const BUILTIN: Record<string, () => Promise<WorkspaceAdaptor>> = {
worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor),
diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts
index 2bfb7deba..8d421b9a3 100644
--- a/packages/opencode/src/control-plane/adaptors/worktree.ts
+++ b/packages/opencode/src/control-plane/adaptors/worktree.ts
@@ -1,13 +1,15 @@
-import z from "zod"
+import { Schema } from "effect"
import { AppRuntime } from "@/effect/app-runtime"
import { Worktree } from "@/worktree"
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
+import { zod } from "@/util/effect-zod"
+import { withStatics } from "@/util/schema"
-const WorktreeConfig = z.object({
- name: WorkspaceInfo.shape.name,
- branch: WorkspaceInfo.shape.branch.unwrap(),
- directory: WorkspaceInfo.shape.directory.unwrap(),
-})
+const WorktreeConfig = Schema.Struct({
+ name: WorkspaceInfo.fields.name,
+ branch: Schema.String,
+ directory: Schema.String,
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const WorktreeAdaptor: WorkspaceAdaptor = {
name: "Worktree",
@@ -22,7 +24,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
}
},
async create(info) {
- const config = WorktreeConfig.parse(info)
+ const config = WorktreeConfig.zod.parse(info)
await AppRuntime.runPromise(
Worktree.Service.use((svc) =>
svc.createFromInfo({
@@ -34,11 +36,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
)
},
async remove(info) {
- const config = WorktreeConfig.parse(info)
+ const config = WorktreeConfig.zod.parse(info)
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory })))
},
target(info) {
- const config = WorktreeConfig.parse(info)
+ const config = WorktreeConfig.zod.parse(info)
return {
type: "local",
directory: config.directory,
diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts
index 07acd5ce5..af16c0490 100644
--- a/packages/opencode/src/control-plane/types.ts
+++ b/packages/opencode/src/control-plane/types.ts
@@ -1,17 +1,28 @@
-import z from "zod"
+import { Schema } from "effect"
import { ProjectID } from "@/project/schema"
import { WorkspaceID } from "./schema"
+import { zod } from "@/util/effect-zod"
+import { type DeepMutable, withStatics } from "@/util/schema"
-export const WorkspaceInfo = z.object({
- id: WorkspaceID.zod,
- type: z.string(),
- name: z.string(),
- branch: z.string().nullable(),
- directory: z.string().nullable(),
- extra: z.unknown().nullable(),
- projectID: ProjectID.zod,
+export const WorkspaceInfo = Schema.Struct({
+ id: WorkspaceID,
+ type: Schema.String,
+ name: Schema.String,
+ branch: Schema.NullOr(Schema.String),
+ directory: Schema.NullOr(Schema.String),
+ extra: Schema.NullOr(Schema.Unknown),
+ projectID: ProjectID,
})
-export type WorkspaceInfo = z.infer<typeof WorkspaceInfo>
+ .annotate({ identifier: "Workspace" })
+ .pipe(withStatics((s) => ({ zod: zod(s) })))
+export type WorkspaceInfo = DeepMutable<Schema.Schema.Type<typeof WorkspaceInfo>>
+
+export const WorkspaceAdaptorEntry = Schema.Struct({
+ type: Schema.String,
+ name: Schema.String,
+ description: Schema.String,
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
+export type WorkspaceAdaptorEntry = Schema.Schema.Type<typeof WorkspaceAdaptorEntry>
export type Target =
| {
diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts
index 8519cb06c..107f2d990 100644
--- a/packages/opencode/src/control-plane/workspace.ts
+++ b/packages/opencode/src/control-plane/workspace.ts
@@ -1,4 +1,3 @@
-import z from "zod"
import { Schema } from "effect"
import { setTimeout as sleep } from "node:timers/promises"
import { fn } from "@/util/fn"
@@ -16,7 +15,7 @@ import { ProjectID } from "@/project/schema"
import { Slug } from "@opencode-ai/shared/util/slug"
import { WorkspaceTable } from "./workspace.sql"
import { getAdaptor } from "./adaptors"
-import { WorkspaceInfo } from "./types"
+import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types"
import { WorkspaceID } from "./schema"
import { parseSSE } from "./sse"
import { Session } from "@/session"
@@ -26,12 +25,11 @@ import { errorData } from "@/util/error"
import { AppRuntime } from "@/effect/app-runtime"
import { waitEvent } from "./util"
import { WorkspaceContext } from "./workspace-context"
-import { NonNegativeInt } from "@/util/schema"
+import { NonNegativeInt, withStatics } from "@/util/schema"
+import { zod as effectZod, zodObject } from "@/util/effect-zod"
-export const Info = WorkspaceInfo.meta({
- ref: "Workspace",
-})
-export type Info = z.infer<typeof Info>
+export const Info = WorkspaceInfoSchema
+export type Info = WorkspaceInfo
export const ConnectionStatus = Schema.Struct({
workspaceID: WorkspaceID,
@@ -75,15 +73,16 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
}
}
-const CreateInput = z.object({
- id: WorkspaceID.zod.optional(),
- type: Info.shape.type,
- branch: Info.shape.branch,
- projectID: ProjectID.zod,
- extra: Info.shape.extra,
-})
+export const CreateInput = Schema.Struct({
+ id: Schema.optional(WorkspaceID),
+ type: Info.fields.type,
+ branch: Info.fields.branch,
+ projectID: ProjectID,
+ extra: Info.fields.extra,
+}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) })))
+export type CreateInput = Schema.Schema.Type<typeof CreateInput>
-export const create = fn(CreateInput, async (input) => {
+export const create = fn(CreateInput.zod, async (input) => {
const id = WorkspaceID.ascending(input.id)
const adaptor = await getAdaptor(input.projectID, input.type)
@@ -139,12 +138,13 @@ export const create = fn(CreateInput, async (input) => {
return info
})
-const SessionRestoreInput = z.object({
- workspaceID: WorkspaceID.zod,
- sessionID: SessionID.zod,
-})
+export const SessionRestoreInput = Schema.Struct({
+ workspaceID: WorkspaceID,
+ sessionID: SessionID,
+}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) })))
+export type SessionRestoreInput = Schema.Schema.Type<typeof SessionRestoreInput>
-export const sessionRestore = fn(SessionRestoreInput, async (input) => {
+export const sessionRestore = fn(SessionRestoreInput.zod, async (input) => {
log.info("session restore requested", {
workspaceID: input.workspaceID,
sessionID: input.sessionID,
diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts
index d48c687f3..ffbaa6009 100644
--- a/packages/opencode/src/server/routes/control/workspace.ts
+++ b/packages/opencode/src/server/routes/control/workspace.ts
@@ -3,6 +3,7 @@ import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod"
import { listAdaptors } from "@/control-plane/adaptors"
import { Workspace } from "@/control-plane/workspace"
+import { WorkspaceAdaptorEntry } from "@/control-plane/types"
import { zodObject } from "@/util/effect-zod"
import { Instance } from "@/project/instance"
import { errors } from "../../error"
@@ -26,13 +27,7 @@ export const WorkspaceRoutes = lazy(() =>
content: {
"application/json": {
schema: resolver(
- z.array(
- z.object({
- type: z.string(),
- name: z.string(),
- description: z.string(),
- }),
- ),
+ z.array(zodObject(WorkspaceAdaptorEntry)),
),
},
},
@@ -54,7 +49,7 @@ export const WorkspaceRoutes = lazy(() =>
description: "Workspace created",
content: {
"application/json": {
- schema: resolver(Workspace.Info),
+ schema: resolver(Workspace.Info.zod),
},
},
},
@@ -63,12 +58,12 @@ export const WorkspaceRoutes = lazy(() =>
}),
validator(
"json",
- Workspace.create.schema.omit({
+ Workspace.CreateInput.zodObject.omit({
projectID: true,
}),
),
async (c) => {
- const body = c.req.valid("json")
+ const body = c.req.valid("json") as Omit<Workspace.CreateInput, "projectID">
const workspace = await Workspace.create({
projectID: Instance.project.id,
...body,
@@ -87,7 +82,7 @@ export const WorkspaceRoutes = lazy(() =>
description: "Workspaces",
content: {
"application/json": {
- schema: resolver(z.array(Workspace.Info)),
+ schema: resolver(z.array(Workspace.Info.zod)),
},
},
},
@@ -130,7 +125,7 @@ export const WorkspaceRoutes = lazy(() =>
description: "Workspace removed",
content: {
"application/json": {
- schema: resolver(Workspace.Info.optional()),
+ schema: resolver(Workspace.Info.zod.optional()),
},
},
},
@@ -140,7 +135,7 @@ export const WorkspaceRoutes = lazy(() =>
validator(
"param",
z.object({
- id: Workspace.Info.shape.id,
+ id: zodObject(Workspace.Info).shape.id,
}),
),
async (c) => {
@@ -170,11 +165,11 @@ export const WorkspaceRoutes = lazy(() =>
...errors(400),
},
}),
- validator("param", z.object({ id: Workspace.Info.shape.id })),
- validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })),
+ validator("param", z.object({ id: zodObject(Workspace.Info).shape.id })),
+ validator("json", Workspace.SessionRestoreInput.zodObject.omit({ workspaceID: true })),
async (c) => {
const { id } = c.req.valid("param")
- const body = c.req.valid("json")
+ const body = c.req.valid("json") as Omit<Workspace.SessionRestoreInput, "workspaceID">
log.info("session restore route requested", {
workspaceID: id,
sessionID: body.sessionID,