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

This commit is contained in:
Kit Langton 2026-04-24 17:11:52 -04:00 committed by GitHub
parent 435becbea0
commit cf45a8d807
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 24 additions and 19 deletions

View file

@ -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 })
}
}

View file

@ -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

View file

@ -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,

View file

@ -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