diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 672b93f6ce..24bcb9c2d6 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 e52120f1af..26256a770f 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 8d421b9a33..9c080daa38 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 51c4699241..5815372219 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