summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-24 17:11:52 -0400
committerGitHub <[email protected]>2026-04-24 17:11:52 -0400
commitcf45a8d80752fd4f29a57c6b696c40c3b332c3e9 (patch)
tree2d2dcefe94e3eddd8922f0b0a1b7885a7ed9eae4
parent435becbea01183ab3dd5581067c64f885b93c2d8 (diff)
downloadopencode-cf45a8d80752fd4f29a57c6b696c40c3b332c3e9.tar.gz
opencode-cf45a8d80752fd4f29a57c6b696c40c3b332c3e9.zip
refactor(schema): decode effect schemas directly (#24169)
-rw-r--r--packages/opencode/src/acp/agent.ts19
-rw-r--r--packages/opencode/src/cli/cmd/import.ts7
-rw-r--r--packages/opencode/src/control-plane/adaptors/worktree.ts11
-rw-r--r--packages/opencode/src/server/routes/instance/pty.ts6
4 files changed, 24 insertions, 19 deletions
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index 672b93f6c..24bcb9c2d 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -46,7 +46,7 @@ import { MessageV2 } from "@/session/message-v2"
import { Config } from "@/config"
import { ConfigMCP } from "@/config/mcp"
import { Todo } from "@/session/todo"
-import { z } from "zod"
+import { Result, Schema } from "effect"
import { LoadAPIKeyError } from "ai"
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
import { applyPatch } from "diff"
@@ -54,6 +54,7 @@ import { InstallationVersion } from "@/installation/version"
type ModeOption = { id: string; name: string; description?: string }
type ModelOption = { modelId: string; name: string }
+const decodeTodos = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Array(Todo.Info)))
const DEFAULT_VARIANT_VALUE = "default"
@@ -372,14 +373,14 @@ export class Agent implements ACPAgent {
}
if (part.tool === "todowrite") {
- const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
- if (parsedTodos.success) {
+ const parsedTodos = decodeTodos(part.state.output)
+ if (Result.isSuccess(parsedTodos)) {
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "plan",
- entries: parsedTodos.data.map((todo) => {
+ entries: parsedTodos.success.map((todo) => {
const status: PlanEntry["status"] =
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
return {
@@ -394,7 +395,7 @@ export class Agent implements ACPAgent {
log.error("failed to send session update for todo", { error })
})
} else {
- log.error("failed to parse todo output", { error: parsedTodos.error })
+ log.error("failed to parse todo output", { error: parsedTodos.failure })
}
}
@@ -901,14 +902,14 @@ export class Agent implements ACPAgent {
}
if (part.tool === "todowrite") {
- const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
- if (parsedTodos.success) {
+ const parsedTodos = decodeTodos(part.state.output)
+ if (Result.isSuccess(parsedTodos)) {
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "plan",
- entries: parsedTodos.data.map((todo) => {
+ entries: parsedTodos.success.map((todo) => {
const status: PlanEntry["status"] =
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
return {
@@ -923,7 +924,7 @@ export class Agent implements ACPAgent {
log.error("failed to send session update for todo", { error: err })
})
} else {
- log.error("failed to parse todo output", { error: parsedTodos.error })
+ log.error("failed to parse todo output", { error: parsedTodos.failure })
}
}
diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts
index e52120f1a..26256a770 100644
--- a/packages/opencode/src/cli/cmd/import.ts
+++ b/packages/opencode/src/cli/cmd/import.ts
@@ -13,6 +13,9 @@ import { Filesystem } from "../../util"
import { AppRuntime } from "@/effect/app-runtime"
import { Schema } from "effect"
+const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info)
+const decodePart = Schema.decodeUnknownSync(MessageV2.Part)
+
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
export type ShareData =
| { type: "session"; data: SDKSession }
@@ -169,7 +172,7 @@ export const ImportCommand = cmd({
)
for (const msg of exportData.messages) {
- const msgInfo = MessageV2.Info.zod.parse(msg.info)
+ const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info
const { id, sessionID: _, ...msgData } = msgInfo
Database.use((db) =>
db
@@ -185,7 +188,7 @@ export const ImportCommand = cmd({
)
for (const part of msg.parts) {
- const partInfo = MessageV2.Part.zod.parse(part)
+ const partInfo = decodePart(part) as MessageV2.Part
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
Database.use((db) =>
db
diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts
index 8d421b9a3..9c080daa3 100644
--- a/packages/opencode/src/control-plane/adaptors/worktree.ts
+++ b/packages/opencode/src/control-plane/adaptors/worktree.ts
@@ -2,14 +2,13 @@ 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 = Schema.Struct({
name: WorkspaceInfo.fields.name,
branch: Schema.String,
directory: Schema.String,
-}).pipe(withStatics((s) => ({ zod: zod(s) })))
+})
+const decodeWorktreeConfig = Schema.decodeUnknownSync(WorktreeConfig)
export const WorktreeAdaptor: WorkspaceAdaptor = {
name: "Worktree",
@@ -24,7 +23,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
}
},
async create(info) {
- const config = WorktreeConfig.zod.parse(info)
+ const config = decodeWorktreeConfig(info)
await AppRuntime.runPromise(
Worktree.Service.use((svc) =>
svc.createFromInfo({
@@ -36,11 +35,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
)
},
async remove(info) {
- const config = WorktreeConfig.zod.parse(info)
+ const config = decodeWorktreeConfig(info)
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory })))
},
target(info) {
- const config = WorktreeConfig.zod.parse(info)
+ const config = decodeWorktreeConfig(info)
return {
type: "local",
directory: config.directory,
diff --git a/packages/opencode/src/server/routes/instance/pty.ts b/packages/opencode/src/server/routes/instance/pty.ts
index 51c469924..581537221 100644
--- a/packages/opencode/src/server/routes/instance/pty.ts
+++ b/packages/opencode/src/server/routes/instance/pty.ts
@@ -1,7 +1,7 @@
import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import type { UpgradeWebSocket } from "hono/ws"
-import { Effect } from "effect"
+import { Effect, Schema } from "effect"
import z from "zod"
import { AppRuntime } from "@/effect/app-runtime"
import { Pty } from "@/pty"
@@ -10,6 +10,8 @@ import { NotFoundError } from "@/storage"
import { errors } from "../../error"
import { jsonRequest, runRequest } from "./trace"
+const decodePtyID = Schema.decodeUnknownSync(PtyID)
+
export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
return new Hono()
.get(
@@ -171,7 +173,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
onClose: () => void
}
- const id = PtyID.zod.parse(c.req.param("ptyID"))
+ const id = decodePtyID(c.req.param("ptyID"))
const cursor = (() => {
const value = c.req.query("cursor")
if (!value) return