Parcourir la source

refactor(schema): decode effect schemas directly (#24169)

Kit Langton il y a 4 mois
Parent
commit
cf45a8d807

+ 10 - 9
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 })
               }
             }
 

+ 5 - 2
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

+ 5 - 6
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,

+ 4 - 2
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