refactor(schema): decode effect schemas directly (#24169)
This commit is contained in:
parent
435becbea0
commit
cf45a8d807
4 changed files with 24 additions and 19 deletions
|
|
@ -46,7 +46,7 @@ import { MessageV2 } from "@/session/message-v2"
|
||||||
import { Config } from "@/config"
|
import { Config } from "@/config"
|
||||||
import { ConfigMCP } from "@/config/mcp"
|
import { ConfigMCP } from "@/config/mcp"
|
||||||
import { Todo } from "@/session/todo"
|
import { Todo } from "@/session/todo"
|
||||||
import { z } from "zod"
|
import { Result, Schema } from "effect"
|
||||||
import { LoadAPIKeyError } from "ai"
|
import { LoadAPIKeyError } from "ai"
|
||||||
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { applyPatch } from "diff"
|
import { applyPatch } from "diff"
|
||||||
|
|
@ -54,6 +54,7 @@ import { InstallationVersion } from "@/installation/version"
|
||||||
|
|
||||||
type ModeOption = { id: string; name: string; description?: string }
|
type ModeOption = { id: string; name: string; description?: string }
|
||||||
type ModelOption = { modelId: string; name: string }
|
type ModelOption = { modelId: string; name: string }
|
||||||
|
const decodeTodos = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Array(Todo.Info)))
|
||||||
|
|
||||||
const DEFAULT_VARIANT_VALUE = "default"
|
const DEFAULT_VARIANT_VALUE = "default"
|
||||||
|
|
||||||
|
|
@ -372,14 +373,14 @@ export class Agent implements ACPAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.tool === "todowrite") {
|
if (part.tool === "todowrite") {
|
||||||
const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
|
const parsedTodos = decodeTodos(part.state.output)
|
||||||
if (parsedTodos.success) {
|
if (Result.isSuccess(parsedTodos)) {
|
||||||
await this.connection
|
await this.connection
|
||||||
.sessionUpdate({
|
.sessionUpdate({
|
||||||
sessionId,
|
sessionId,
|
||||||
update: {
|
update: {
|
||||||
sessionUpdate: "plan",
|
sessionUpdate: "plan",
|
||||||
entries: parsedTodos.data.map((todo) => {
|
entries: parsedTodos.success.map((todo) => {
|
||||||
const status: PlanEntry["status"] =
|
const status: PlanEntry["status"] =
|
||||||
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
||||||
return {
|
return {
|
||||||
|
|
@ -394,7 +395,7 @@ export class Agent implements ACPAgent {
|
||||||
log.error("failed to send session update for todo", { error })
|
log.error("failed to send session update for todo", { error })
|
||||||
})
|
})
|
||||||
} else {
|
} 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") {
|
if (part.tool === "todowrite") {
|
||||||
const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
|
const parsedTodos = decodeTodos(part.state.output)
|
||||||
if (parsedTodos.success) {
|
if (Result.isSuccess(parsedTodos)) {
|
||||||
await this.connection
|
await this.connection
|
||||||
.sessionUpdate({
|
.sessionUpdate({
|
||||||
sessionId,
|
sessionId,
|
||||||
update: {
|
update: {
|
||||||
sessionUpdate: "plan",
|
sessionUpdate: "plan",
|
||||||
entries: parsedTodos.data.map((todo) => {
|
entries: parsedTodos.success.map((todo) => {
|
||||||
const status: PlanEntry["status"] =
|
const status: PlanEntry["status"] =
|
||||||
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
||||||
return {
|
return {
|
||||||
|
|
@ -923,7 +924,7 @@ export class Agent implements ACPAgent {
|
||||||
log.error("failed to send session update for todo", { error: err })
|
log.error("failed to send session update for todo", { error: err })
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
log.error("failed to parse todo output", { error: parsedTodos.error })
|
log.error("failed to parse todo output", { error: parsedTodos.failure })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import { Filesystem } from "../../util"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { Schema } from "effect"
|
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) */
|
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
|
||||||
export type ShareData =
|
export type ShareData =
|
||||||
| { type: "session"; data: SDKSession }
|
| { type: "session"; data: SDKSession }
|
||||||
|
|
@ -169,7 +172,7 @@ export const ImportCommand = cmd({
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const msg of exportData.messages) {
|
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
|
const { id, sessionID: _, ...msgData } = msgInfo
|
||||||
Database.use((db) =>
|
Database.use((db) =>
|
||||||
db
|
db
|
||||||
|
|
@ -185,7 +188,7 @@ export const ImportCommand = cmd({
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const part of msg.parts) {
|
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
|
const { id: partId, sessionID: _s, messageID, ...partData } = partInfo
|
||||||
Database.use((db) =>
|
Database.use((db) =>
|
||||||
db
|
db
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ import { Schema } from "effect"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { Worktree } from "@/worktree"
|
import { Worktree } from "@/worktree"
|
||||||
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
|
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
|
||||||
import { zod } from "@/util/effect-zod"
|
|
||||||
import { withStatics } from "@/util/schema"
|
|
||||||
|
|
||||||
const WorktreeConfig = Schema.Struct({
|
const WorktreeConfig = Schema.Struct({
|
||||||
name: WorkspaceInfo.fields.name,
|
name: WorkspaceInfo.fields.name,
|
||||||
branch: Schema.String,
|
branch: Schema.String,
|
||||||
directory: Schema.String,
|
directory: Schema.String,
|
||||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
})
|
||||||
|
const decodeWorktreeConfig = Schema.decodeUnknownSync(WorktreeConfig)
|
||||||
|
|
||||||
export const WorktreeAdaptor: WorkspaceAdaptor = {
|
export const WorktreeAdaptor: WorkspaceAdaptor = {
|
||||||
name: "Worktree",
|
name: "Worktree",
|
||||||
|
|
@ -24,7 +23,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async create(info) {
|
async create(info) {
|
||||||
const config = WorktreeConfig.zod.parse(info)
|
const config = decodeWorktreeConfig(info)
|
||||||
await AppRuntime.runPromise(
|
await AppRuntime.runPromise(
|
||||||
Worktree.Service.use((svc) =>
|
Worktree.Service.use((svc) =>
|
||||||
svc.createFromInfo({
|
svc.createFromInfo({
|
||||||
|
|
@ -36,11 +35,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
async remove(info) {
|
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 })))
|
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory })))
|
||||||
},
|
},
|
||||||
target(info) {
|
target(info) {
|
||||||
const config = WorktreeConfig.zod.parse(info)
|
const config = decodeWorktreeConfig(info)
|
||||||
return {
|
return {
|
||||||
type: "local",
|
type: "local",
|
||||||
directory: config.directory,
|
directory: config.directory,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { describeRoute, validator, resolver } from "hono-openapi"
|
import { describeRoute, validator, resolver } from "hono-openapi"
|
||||||
import type { UpgradeWebSocket } from "hono/ws"
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import { Effect } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { Pty } from "@/pty"
|
import { Pty } from "@/pty"
|
||||||
|
|
@ -10,6 +10,8 @@ import { NotFoundError } from "@/storage"
|
||||||
import { errors } from "../../error"
|
import { errors } from "../../error"
|
||||||
import { jsonRequest, runRequest } from "./trace"
|
import { jsonRequest, runRequest } from "./trace"
|
||||||
|
|
||||||
|
const decodePtyID = Schema.decodeUnknownSync(PtyID)
|
||||||
|
|
||||||
export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
|
export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
|
||||||
return new Hono()
|
return new Hono()
|
||||||
.get(
|
.get(
|
||||||
|
|
@ -171,7 +173,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = PtyID.zod.parse(c.req.param("ptyID"))
|
const id = decodePtyID(c.req.param("ptyID"))
|
||||||
const cursor = (() => {
|
const cursor = (() => {
|
||||||
const value = c.req.query("cursor")
|
const value = c.req.query("cursor")
|
||||||
if (!value) return
|
if (!value) return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue