Drop unused opencode Zod statics (#26935)

This commit is contained in:
Kit Langton
2026-05-11 17:14:18 -04:00
committed by GitHub
parent d3caac5270
commit fd65d29dcc
34 changed files with 161 additions and 389 deletions

View File

@@ -1,6 +1,5 @@
import path from "path"
import { Effect, Layer, Record, Result, Schema, Context } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { Global } from "@opencode-ai/core/global"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -32,9 +31,8 @@ export class WellKnown extends Schema.Class<WellKnown>("WellKnownAuth")({
token: Schema.String,
}) {}
const _Info = Schema.Union([Oauth, Api, WellKnown]).annotate({ discriminator: "type", identifier: "Auth" })
export const Info = Object.assign(_Info, { zod: zod(_Info) })
export type Info = Schema.Schema.Type<typeof _Info>
export const Info = Schema.Union([Oauth, Api, WellKnown]).annotate({ discriminator: "type", identifier: "Auth" })
export type Info = Schema.Schema.Type<typeof Info>
export class AuthError extends Schema.TaggedErrorClass<AuthError>()("AuthError", {
message: Schema.String,

View File

@@ -5,6 +5,7 @@ import { createBindingLookup } from "@opentui/keymap/extras"
import { mergeDeep, unique } from "remeda"
import { Context, Effect, Fiber, Layer } from "effect"
import { ConfigParse } from "@/config/parse"
import { InvalidError } from "@/config/error"
import * as ConfigPaths from "@/config/paths"
import { migrateTuiConfig } from "./tui-migrate"
import { KeymapLeaderTimeoutDefault, TuiInfo, TuiJsonSchemaInfo } from "./tui-schema"
@@ -91,10 +92,12 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
if (!isRecord(data)) return {} as Info
// Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json
// (mirroring the old opencode.json shape) still get their settings applied.
const validated = ConfigParse.schema(Info, normalize(data), configFilepath)
const parsed = Info.safeParse(normalize(data))
if (!parsed.success) throw new InvalidError({ path: configFilepath, issues: parsed.error.issues })
const validated = parsed.data
return yield* resolvePlugins(validated, configFilepath)
}).pipe(
// catchCause (not tapErrorCause + orElseSucceed) because ConfigParse.jsonc/.schema
// catchCause (not tapErrorCause + orElseSucceed) because JSONC parsing and validation
// can sync-throw — those become defects, which orElseSucceed wouldn't catch.
Effect.catchCause((cause) =>
Effect.sync(() => {

View File

@@ -5,8 +5,7 @@ import type { InstanceContext } from "@/project/instance"
import { SessionID, MessageID } from "@/session/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { ZodOverride } from "@opencode-ai/core/effect-zod"
import { Config } from "@/config/config"
import { MCP } from "../mcp"
import { Skill } from "../skill"
@@ -39,9 +38,7 @@ export const Info = Schema.Struct({
template: Schema.Unknown.annotate({ [ZodOverride]: z.promise(z.string()).or(z.string()) }),
subtask: Schema.optional(Schema.Boolean),
hints: Schema.Array(Schema.String),
})
.annotate({ identifier: "Command" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Command" })
// for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it
export type Info = Omit<Schema.Schema.Type<typeof Info>, "template"> & { template: Promise<string> | string }

View File

@@ -2,8 +2,7 @@ export * as ConfigAgent from "./agent"
import { Exit, Schema, SchemaGetter } from "effect"
import { Bus } from "@/bus"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
import * as Log from "@opencode-ai/core/util/log"
import { NamedError } from "@opencode-ai/core/util/error"
import { Glob } from "@opencode-ai/core/util/glob"
@@ -102,9 +101,7 @@ export const Info = AgentSchema.pipe(
decode: SchemaGetter.transform(normalize),
encode: SchemaGetter.passthrough({ strict: false }),
}),
)
.annotate({ identifier: "AgentConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
).annotate({ identifier: "AgentConfig" })
export type Info = Schema.Schema.Type<typeof Info>
export async function load(dir: string) {
@@ -134,7 +131,7 @@ export async function load(dir: string) {
...md.data,
prompt: md.content.trim(),
}
result[config.name] = ConfigParse.effectSchema(Info, config, item)
result[config.name] = ConfigParse.schema(Info, config, item)
}
return result
}

View File

@@ -1,8 +1,7 @@
export * as ConfigAttachment from "./attachment"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
export const Image = Schema.Struct({
auto_resize: Schema.optional(Schema.Boolean).annotate({
@@ -17,14 +16,10 @@ export const Image = Schema.Struct({
max_base64_bytes: Schema.optional(PositiveInt).annotate({
description: "Maximum base64 payload bytes for an image attachment (default: 4718592)",
}),
})
.annotate({ identifier: "ImageAttachmentConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ImageAttachmentConfig" })
export type Image = Schema.Schema.Type<typeof Image>
export const Info = Schema.Struct({
image: Schema.optional(Image).annotate({ description: "Image attachment configuration" }),
})
.annotate({ identifier: "AttachmentConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "AttachmentConfig" })
export type Info = Schema.Schema.Type<typeof Info>

View File

@@ -388,7 +388,7 @@ export const layer = Layer.effect(
),
)
const parsed = ConfigParse.jsonc(expanded, source)
const data = ConfigParse.effectSchema(Info, normalizeLoadedConfig(parsed, source), source)
const data = ConfigParse.schema(Info, normalizeLoadedConfig(parsed, source), source)
if (!("path" in options)) return data
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
@@ -786,7 +786,7 @@ export const layer = Layer.effect(
let next: Info
let changed: boolean
if (!file.endsWith(".jsonc")) {
const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file)
const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file)
const merged = mergeDeep(writable(existing), patch)
const serialized = JSON.stringify(merged, null, 2)
changed = serialized !== before
@@ -794,7 +794,7 @@ export const layer = Layer.effect(
next = merged
} else {
const updated = patchJsonc(before, patch)
next = ConfigParse.effectSchema(Info, ConfigParse.jsonc(updated, file), file)
next = ConfigParse.schema(Info, ConfigParse.jsonc(updated, file), file)
changed = updated !== before
if (changed) yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
}

View File

@@ -1,14 +1,11 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt } from "@opencode-ai/core/schema"
export class ConsoleState extends Schema.Class<ConsoleState>("ConsoleState")({
consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)),
activeOrgName: Schema.optional(Schema.String),
switchableOrgCount: NonNegativeInt,
}) {
static readonly zod = zod(this)
}
}) {}
export const emptyConsoleState: ConsoleState = ConsoleState.make({
consoleManagedProviders: [],

View File

@@ -1,17 +1,13 @@
export * as ConfigFormatter from "./formatter"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
export const Entry = Schema.Struct({
disabled: Schema.optional(Schema.Boolean),
command: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
environment: Schema.optional(Schema.Record(Schema.String, Schema.String)),
extensions: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)]).pipe(
withStatics((s) => ({ zod: zod(s) })),
)
export const Info = Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Entry)])
export type Info = Schema.Schema.Type<typeof Info>

View File

@@ -1,10 +1,6 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
export const Layout = Schema.Literals(["auto", "stretch"])
.annotate({ identifier: "LayoutConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Layout = Schema.Literals(["auto", "stretch"]).annotate({ identifier: "LayoutConfig" })
export type Layout = Schema.Schema.Type<typeof Layout>
export * as ConfigLayout from "./layout"

View File

@@ -1,6 +1,5 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
export const Local = Schema.Struct({
type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }),
@@ -16,9 +15,7 @@ export const Local = Schema.Struct({
timeout: Schema.optional(PositiveInt).annotate({
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
}),
})
.annotate({ identifier: "McpLocalConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "McpLocalConfig" })
export type Local = Schema.Schema.Type<typeof Local>
export const OAuth = Schema.Struct({
@@ -32,9 +29,7 @@ export const OAuth = Schema.Struct({
redirectUri: Schema.optional(Schema.String).annotate({
description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).",
}),
})
.annotate({ identifier: "McpOAuthConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "McpOAuthConfig" })
export type OAuth = Schema.Schema.Type<typeof OAuth>
export const Remote = Schema.Struct({
@@ -52,14 +47,10 @@ export const Remote = Schema.Struct({
timeout: Schema.optional(PositiveInt).annotate({
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
}),
})
.annotate({ identifier: "McpRemoteConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "McpRemoteConfig" })
export type Remote = Schema.Schema.Type<typeof Remote>
export const Info = Schema.Union([Local, Remote])
.annotate({ discriminator: "type" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Info = Schema.Union([Local, Remote]).annotate({ discriminator: "type" })
export type Info = Schema.Schema.Type<typeof Info>
export * as ConfigMCP from "./mcp"

View File

@@ -1,7 +1,6 @@
import { Schema } from "effect"
import z from "zod"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import { ZodOverride } from "@opencode-ai/core/effect-zod"
// The original Zod schema carried an external $ref pointing at the models.dev
// JSON schema. That external reference is not a named SDK component — it is a
@@ -9,6 +8,6 @@ import { withStatics } from "@opencode-ai/core/schema"
// from AST metadata. Preserve the exact original Zod via ZodOverride.
export const ConfigModelID = Schema.String.annotate({
[ZodOverride]: z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type ConfigModelID = Schema.Schema.Type<typeof ConfigModelID>

View File

@@ -2,12 +2,10 @@ export * as ConfigParse from "./parse"
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
import z from "zod"
import type z from "zod"
import type { DeepMutable } from "@opencode-ai/core/schema"
import { InvalidError, JsonError } from "./error"
type ZodSchema<T> = z.ZodType<T>
export function jsonc(text: string, filepath: string): unknown {
const errors: JsoncParseError[] = []
const data = parseJsoncImpl(text, errors, { allowTrailingComma: true })
@@ -35,17 +33,7 @@ export function jsonc(text: string, filepath: string): unknown {
return data
}
export function schema<T>(schema: ZodSchema<T>, data: unknown, source: string): T {
const parsed = schema.safeParse(data)
if (parsed.success) return parsed.data
throw new InvalidError({
path: source,
issues: parsed.error.issues,
})
}
export function effectSchema<S extends EffectSchema.Decoder<unknown, never>>(
export function schema<S extends EffectSchema.Decoder<unknown, never>>(
schema: S,
data: unknown,
source: string,

View File

@@ -1,21 +1,13 @@
export * as ConfigPermission from "./permission"
import { Schema, SchemaGetter } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
export const Action = Schema.Literals(["ask", "allow", "deny"])
.annotate({ identifier: "PermissionActionConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" })
export type Action = Schema.Schema.Type<typeof Action>
export const Object = Schema.Record(Schema.String, Action)
.annotate({ identifier: "PermissionObjectConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Object = Schema.Record(Schema.String, Action).annotate({ identifier: "PermissionObjectConfig" })
export type Object = Schema.Schema.Type<typeof Object>
export const Rule = Schema.Union([Action, Object])
.annotate({ identifier: "PermissionRuleConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Rule = Schema.Union([Action, Object]).annotate({ identifier: "PermissionRuleConfig" })
export type Rule = Schema.Schema.Type<typeof Rule>
// Known permission keys get explicit types in the Effect schema for generated
@@ -62,12 +54,6 @@ export const Info = InputSchema.pipe(
// of the same rules.
encode: SchemaGetter.passthrough({ strict: false }),
}),
)
.annotate({ identifier: "PermissionConfig" })
.pipe(
// Walker already emits the decodeTo transform into the derived zod (see
// `encoded()` in effect-zod.ts), so just expose that directly.
withStatics((s) => ({ zod: zod(s) })),
)
).annotate({ identifier: "PermissionConfig" })
type _Info = Schema.Schema.Type<typeof InputObject>
export type Info = { -readonly [K in keyof _Info]: _Info[K] }

View File

@@ -6,7 +6,7 @@ import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import path from "path"
export const Options = Schema.Record(Schema.String, Schema.Unknown).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Options = Schema.Record(Schema.String, Schema.Unknown)
export type Options = Schema.Schema.Type<typeof Options>
// Spec is the user-config value: either just a plugin identifier, or the identifier plus inline options.

View File

@@ -1,6 +1,5 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
import { ModelStatus } from "@/provider/model-status"
export const Model = Schema.Struct({
@@ -67,7 +66,7 @@ export const Model = Schema.Struct({
),
).annotate({ description: "Variant-specific configuration" }),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export const Info = Schema.Struct({
api: Schema.optional(Schema.String),
@@ -106,9 +105,7 @@ export const Info = Schema.Struct({
),
),
models: Schema.optional(Schema.Record(Schema.String, Model)),
})
.annotate({ identifier: "ProviderConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ProviderConfig" })
export type Info = Schema.Schema.Type<typeof Info>
export * as ConfigProvider from "./provider"

View File

@@ -1,8 +1,6 @@
export * as ConfigReference from "./reference"
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
const Git = Schema.Struct({
repository: Schema.String.annotate({
@@ -21,7 +19,5 @@ const Local = Schema.Struct({
export const Entry = Schema.Union([Schema.String, Git, Local]).annotate({ identifier: "ReferenceConfigEntry" })
export const Info = Schema.Record(Schema.String, Entry)
.annotate({ identifier: "ReferenceConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Info = Schema.Record(Schema.String, Entry).annotate({ identifier: "ReferenceConfig" })
export type Info = Schema.Schema.Type<typeof Info>

View File

@@ -1,6 +1,5 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { PositiveInt, withStatics } from "@opencode-ai/core/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
export const Server = Schema.Struct({
port: Schema.optional(PositiveInt).annotate({
@@ -14,9 +13,7 @@ export const Server = Schema.Struct({
cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({
description: "Additional domains to allow for CORS",
}),
})
.annotate({ identifier: "ServerConfig" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ServerConfig" })
export type Server = Schema.Schema.Type<typeof Server>
export * as ConfigServer from "./server"

View File

@@ -1,6 +1,4 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
export const Info = Schema.Struct({
paths: Schema.optional(Schema.Array(Schema.String)).annotate({
@@ -9,7 +7,7 @@ export const Info = Schema.Struct({
urls: Schema.optional(Schema.Array(Schema.String)).annotate({
description: "URLs to fetch skills from (e.g., https://example.com/.well-known/skills/)",
}),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type Info = Schema.Schema.Type<typeof Info>

View File

@@ -13,8 +13,8 @@ import { spawn as lspspawn } from "./launch"
import { Effect, Layer, Context, Schema } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { containsPath } from "@/project/instance-context"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { zod, ZodOverride } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { ZodOverride } from "@opencode-ai/core/effect-zod"
const log = Log.create({ service: "lsp" })
@@ -30,9 +30,7 @@ const Position = Schema.Struct({
export const Range = Schema.Struct({
start: Position,
end: Position,
})
.annotate({ identifier: "Range" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Range" })
export type Range = typeof Range.Type
export const Symbol = Schema.Struct({
@@ -42,9 +40,7 @@ export const Symbol = Schema.Struct({
uri: Schema.String,
range: Range,
}),
})
.annotate({ identifier: "Symbol" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Symbol" })
export type Symbol = typeof Symbol.Type
export const DocumentSymbol = Schema.Struct({
@@ -53,9 +49,7 @@ export const DocumentSymbol = Schema.Struct({
kind: NonNegativeInt,
range: Range,
selectionRange: Range,
})
.annotate({ identifier: "DocumentSymbol" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "DocumentSymbol" })
export type DocumentSymbol = typeof DocumentSymbol.Type
export const Status = Schema.Struct({
@@ -65,9 +59,7 @@ export const Status = Schema.Struct({
status: Schema.Literals(["connected", "error"]).annotate({
[ZodOverride]: z.union([z.literal("connected"), z.literal("error")]),
}),
})
.annotate({ identifier: "LSPStatus" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "LSPStatus" })
export type Status = typeof Status.Type
enum SymbolKind {

View File

@@ -31,8 +31,6 @@ import { EffectBridge } from "@/effect/bridge"
import { InstanceState } from "@/effect/instance-state"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { zod as effectZod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
const log = Log.create({ service: "mcp" })
const DEFAULT_TIMEOUT = 30_000
@@ -52,9 +50,7 @@ export const Resource = Schema.Struct({
description: Schema.optional(Schema.String),
mimeType: Schema.optional(Schema.String),
client: Schema.String,
})
.annotate({ identifier: "McpResource" })
.pipe(withStatics((s) => ({ zod: effectZod(s) })))
}).annotate({ identifier: "McpResource" })
export type Resource = Schema.Schema.Type<typeof Resource>
export const ToolsChanged = BusEvent.define(
@@ -104,9 +100,7 @@ export const Status = Schema.Union([
StatusFailed,
StatusNeedsAuth,
StatusNeedsClientRegistration,
])
.annotate({ identifier: "MCPStatus", discriminator: "status" })
.pipe(withStatics((s) => ({ zod: effectZod(s) })))
]).annotate({ identifier: "MCPStatus", discriminator: "status" })
export type Status = Schema.Schema.Type<typeof Status>
// Store transports for OAuth servers to allow finishing auth

View File

@@ -7,9 +7,7 @@ import { MessageID, SessionID } from "@/session/schema"
import { PermissionTable } from "@/session/session.sql"
import { Database } from "@/storage/db"
import { eq } from "drizzle-orm"
import { zod } from "@opencode-ai/core/effect-zod"
import * as Log from "@opencode-ai/core/util/log"
import { withStatics } from "@opencode-ai/core/schema"
import { Wildcard } from "@/util/wildcard"
import { Deferred, Effect, Layer, Schema, Context } from "effect"
import os from "os"
@@ -18,23 +16,17 @@ import { PermissionID } from "./schema"
const log = Log.create({ service: "permission" })
export const Action = Schema.Literals(["allow", "deny", "ask"])
.annotate({ identifier: "PermissionAction" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" })
export type Action = Schema.Schema.Type<typeof Action>
export const Rule = Schema.Struct({
permission: Schema.String,
pattern: Schema.String,
action: Action,
})
.annotate({ identifier: "PermissionRule" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "PermissionRule" })
export type Rule = Schema.Schema.Type<typeof Rule>
export const Ruleset = Schema.mutable(Schema.Array(Rule))
.annotate({ identifier: "PermissionRuleset" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionRuleset" })
export type Ruleset = Schema.Schema.Type<typeof Ruleset>
export class Request extends Schema.Class<Request>("PermissionRequest")({
@@ -50,11 +42,9 @@ export class Request extends Schema.Class<Request>("PermissionRequest")({
callID: Schema.String,
}),
),
}) {
static readonly zod = zod(this)
}
}) {}
export const Reply = Schema.Literals(["once", "always", "reject"]).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Reply = Schema.Literals(["once", "always", "reject"])
export type Reply = Schema.Schema.Type<typeof Reply>
const reply = {
@@ -62,17 +52,13 @@ const reply = {
message: Schema.optional(Schema.String),
}
export const ReplyBody = Schema.Struct(reply)
.annotate({ identifier: "PermissionReplyBody" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const ReplyBody = Schema.Struct(reply).annotate({ identifier: "PermissionReplyBody" })
export type ReplyBody = Schema.Schema.Type<typeof ReplyBody>
export class Approval extends Schema.Class<Approval>("PermissionApproval")({
projectID: ProjectID,
patterns: Schema.Array(Schema.String),
}) {
static readonly zod = zod(this)
}
}) {}
export const Event = {
Asked: BusEvent.define("permission.asked", Request),
@@ -114,17 +100,13 @@ export const AskInput = Schema.Struct({
...Request.fields,
id: Schema.optional(PermissionID),
ruleset: Ruleset,
})
.annotate({ identifier: "PermissionAskInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "PermissionAskInput" })
export type AskInput = Schema.Schema.Type<typeof AskInput>
export const ReplyInput = Schema.Struct({
requestID: PermissionID,
...reply,
})
.annotate({ identifier: "PermissionReplyInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "PermissionReplyInput" })
export type ReplyInput = Schema.Schema.Type<typeof ReplyInput>
export interface Interface {

View File

@@ -17,8 +17,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { NodePath } from "@effect/platform-node"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
import { serviceUse } from "@/effect/service-use"
const log = Log.create({ service: "project" })
@@ -52,9 +51,7 @@ export const Info = Schema.Struct({
commands: optionalOmitUndefined(ProjectCommands),
time: ProjectTime,
sandboxes: Schema.Array(Schema.String),
})
.annotate({ identifier: "Project" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Project" })
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
export const Event = {
@@ -100,9 +97,7 @@ export const UpdatePayload = Schema.Struct({
name: Schema.optional(Schema.String),
icon: Schema.optional(ProjectIcon),
commands: Schema.optional(ProjectCommands),
})
.annotate({ identifier: "ProjectUpdateInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ProjectUpdateInput" })
export type UpdatePayload = Types.DeepMutable<Schema.Schema.Type<typeof UpdatePayload>>
// ---------------------------------------------------------------------------

View File

@@ -6,8 +6,6 @@ import { InstanceState } from "@/effect/instance-state"
import { FileWatcher } from "@/file/watcher"
import { Git } from "@/git"
import * as Log from "@opencode-ai/core/util/log"
import { zod, zodObject } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
const log = Log.create({ service: "vcs" })
const PATCH_CONTEXT_LINES = 2_147_483_647
@@ -208,7 +206,7 @@ const track = Effect.fnUntraced(function* (git: Git.Interface, cwd: string, ref:
return yield* diffAgainstRef(git, cwd, ref)
})
export const Mode = Schema.Literals(["git", "branch"]).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Mode = Schema.Literals(["git", "branch"])
export type Mode = Schema.Schema.Type<typeof Mode>
export const Event = {
@@ -223,9 +221,7 @@ export const Event = {
export const Info = Schema.Struct({
branch: Schema.optional(Schema.String),
default_branch: Schema.optional(Schema.String),
})
.annotate({ identifier: "VcsInfo" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "VcsInfo" })
export type Info = Schema.Schema.Type<typeof Info>
export const FileDiff = Schema.Struct({
@@ -237,9 +233,7 @@ export const FileDiff = Schema.Struct({
additions: Schema.Finite,
deletions: Schema.Finite,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
})
.annotate({ identifier: "VcsFileDiff" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "VcsFileDiff" })
export type FileDiff = Schema.Schema.Type<typeof FileDiff>
export const FileStatus = Schema.Struct({
@@ -247,19 +241,17 @@ export const FileStatus = Schema.Struct({
additions: Schema.Finite,
deletions: Schema.Finite,
status: Schema.Literals(["added", "deleted", "modified"]),
})
.annotate({ identifier: "VcsFileStatus" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "VcsFileStatus" })
export type FileStatus = Schema.Schema.Type<typeof FileStatus>
export const ApplyInput = Schema.Struct({
patch: Schema.String,
}).pipe(withStatics((s) => ({ zod: zod(s), zodObject: zodObject(s) })))
})
export type ApplyInput = Schema.Schema.Type<typeof ApplyInput>
export const ApplyResult = Schema.Struct({
applied: Schema.Boolean,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type ApplyResult = Schema.Schema.Type<typeof ApplyResult>
export class PatchApplyError extends Schema.TaggedErrorClass<PatchApplyError>()("VcsPatchApplyError", {

View File

@@ -1,9 +1,8 @@
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
import { Auth } from "@/auth"
import { InstanceState } from "@/effect/instance-state"
import { zod } from "@opencode-ai/core/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
import { Plugin } from "../plugin"
import { ProviderID } from "./schema"
import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect"
@@ -42,31 +41,27 @@ export class Method extends Schema.Class<Method>("ProviderAuthMethod")({
type: Schema.Literals(["oauth", "api"]),
label: Schema.String,
prompts: optionalOmitUndefined(Schema.Array(Prompt)),
}) {
static readonly zod = zod(this)
}
}) {}
export const Methods = Schema.Record(Schema.String, Schema.Array(Method)).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Methods = Schema.Record(Schema.String, Schema.Array(Method))
export type Methods = typeof Methods.Type
export class Authorization extends Schema.Class<Authorization>("ProviderAuthAuthorization")({
url: Schema.String,
method: Schema.Literals(["auto", "code"]),
instructions: Schema.String,
}) {
static readonly zod = zod(this)
}
}) {}
export const AuthorizeInput = Schema.Struct({
method: Schema.Finite.annotate({ description: "Auth method index" }),
inputs: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ description: "Prompt inputs" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type AuthorizeInput = Schema.Schema.Type<typeof AuthorizeInput>
export const CallbackInput = Schema.Struct({
method: Schema.Finite.annotate({ description: "Auth method index" }),
code: Schema.optional(Schema.String).annotate({ description: "OAuth authorization code" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type CallbackInput = Schema.Schema.Type<typeof CallbackInput>
export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID })

View File

@@ -13,7 +13,6 @@ import { Auth } from "../auth"
import { Env } from "../env"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { Flag } from "@opencode-ai/core/flag/flag"
import { zod } from "@opencode-ai/core/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { iife } from "@/util/iife"
import { Global } from "@opencode-ai/core/global"
@@ -24,7 +23,7 @@ import { EffectBridge } from "@/effect/bridge"
import { InstanceState } from "@/effect/instance-state"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { isRecord } from "@/util/record"
import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema"
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
import * as ProviderTransform from "./transform"
import { ModelID, ProviderID } from "./schema"
@@ -903,9 +902,7 @@ export const Model = Schema.Struct({
headers: Schema.Record(Schema.String, Schema.String),
release_date: Schema.String,
variants: optionalOmitUndefined(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
})
.annotate({ identifier: "Model" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Model" })
export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
export const Info = Schema.Struct({
@@ -916,9 +913,7 @@ export const Info = Schema.Struct({
key: optionalOmitUndefined(Schema.String),
options: Schema.Record(Schema.String, Schema.Any),
models: Schema.Record(Schema.String, Model),
})
.annotate({ identifier: "Provider" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Provider" })
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
const DefaultModelIDs = Schema.Record(Schema.String, Schema.String)
@@ -927,13 +922,13 @@ export const ListResult = Schema.Struct({
all: Schema.Array(Info),
default: DefaultModelIDs,
connected: Schema.Array(Schema.String),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type ListResult = Types.DeepMutable<Schema.Schema.Type<typeof ListResult>>
export const ConfigProvidersResult = Schema.Struct({
providers: Schema.Array(Info),
default: DefaultModelIDs,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>
export function toPublicInfo(provider: Info): Info {

View File

@@ -1,6 +1,5 @@
import { Schema } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
const providerIdSchema = Schema.String.pipe(Schema.brand("ProviderID"))
@@ -9,7 +8,6 @@ export type ProviderID = typeof providerIdSchema.Type
export const ProviderID = providerIdSchema.pipe(
withStatics((schema: typeof providerIdSchema) => ({
zod: zod(schema),
// Well-known providers
opencode: schema.make("opencode"),
anthropic: schema.make("anthropic"),
@@ -29,8 +27,4 @@ const modelIdSchema = Schema.String.pipe(Schema.brand("ModelID"))
export type ModelID = typeof modelIdSchema.Type
export const ModelID = modelIdSchema.pipe(
withStatics((schema: typeof modelIdSchema) => ({
zod: zod(schema),
})),
)
export const ModelID = modelIdSchema

View File

@@ -3,9 +3,7 @@ import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { InstanceState } from "@/effect/instance-state"
import { SessionID, MessageID } from "@/session/schema"
import { zod } from "@opencode-ai/core/effect-zod"
import * as Log from "@opencode-ai/core/util/log"
import { withStatics } from "@opencode-ai/core/schema"
import { QuestionID } from "./schema"
const log = Log.create({ service: "question" })
@@ -19,9 +17,7 @@ export class Option extends Schema.Class<Option>("QuestionOption")({
description: Schema.String.annotate({
description: "Explanation of choice",
}),
}) {
static readonly zod = zod(this)
}
}) {}
const base = {
question: Schema.String.annotate({
@@ -43,20 +39,14 @@ export class Info extends Schema.Class<Info>("QuestionInfo")({
custom: Schema.optional(Schema.Boolean).annotate({
description: "Allow typing a custom answer (default: true)",
}),
}) {
static readonly zod = zod(this)
}
}) {}
export class Prompt extends Schema.Class<Prompt>("QuestionPrompt")(base) {
static readonly zod = zod(this)
}
export class Prompt extends Schema.Class<Prompt>("QuestionPrompt")(base) {}
export class Tool extends Schema.Class<Tool>("QuestionTool")({
messageID: MessageID,
callID: Schema.String,
}) {
static readonly zod = zod(this)
}
}) {}
export class Request extends Schema.Class<Request>("QuestionRequest")({
id: QuestionID,
@@ -65,22 +55,16 @@ export class Request extends Schema.Class<Request>("QuestionRequest")({
description: "Questions to ask",
}),
tool: Schema.optional(Tool),
}) {
static readonly zod = zod(this)
}
}) {}
export const Answer = Schema.Array(Schema.String)
.annotate({ identifier: "QuestionAnswer" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
export type Answer = Schema.Schema.Type<typeof Answer>
export class Reply extends Schema.Class<Reply>("QuestionReply")({
answers: Schema.Array(Answer).annotate({
description: "User answers in order of questions (each answer is an array of selected labels)",
}),
}) {
static readonly zod = zod(this)
}
}) {}
class Replied extends Schema.Class<Replied>("QuestionReplied")({
sessionID: SessionID,

View File

@@ -23,8 +23,7 @@ import type { SystemError } from "bun"
import type { Provider } from "@/provider/provider"
import { ModelID, ProviderID } from "@/provider/schema"
import { Effect, Schema, Types } from "effect"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { namedSchemaError } from "@/util/named-schema-error"
import * as EffectLogger from "@opencode-ai/core/effect/logger"
@@ -88,9 +87,7 @@ export const SnapshotPart = Schema.Struct({
...partBase,
type: Schema.Literal("snapshot"),
snapshot: Schema.String,
})
.annotate({ identifier: "SnapshotPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "SnapshotPart" })
export type SnapshotPart = Types.DeepMutable<Schema.Schema.Type<typeof SnapshotPart>>
export const PatchPart = Schema.Struct({
@@ -98,9 +95,7 @@ export const PatchPart = Schema.Struct({
type: Schema.Literal("patch"),
hash: Schema.String,
files: Schema.Array(Schema.String),
})
.annotate({ identifier: "PatchPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "PatchPart" })
export type PatchPart = Types.DeepMutable<Schema.Schema.Type<typeof PatchPart>>
export const TextPart = Schema.Struct({
@@ -116,9 +111,7 @@ export const TextPart = Schema.Struct({
}),
),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
})
.annotate({ identifier: "TextPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "TextPart" })
export type TextPart = Types.DeepMutable<Schema.Schema.Type<typeof TextPart>>
export const ReasoningPart = Schema.Struct({
@@ -130,9 +123,7 @@ export const ReasoningPart = Schema.Struct({
start: NonNegativeInt,
end: Schema.optional(NonNegativeInt),
}),
})
.annotate({ identifier: "ReasoningPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ReasoningPart" })
export type ReasoningPart = Types.DeepMutable<Schema.Schema.Type<typeof ReasoningPart>>
const filePartSourceBase = {
@@ -147,9 +138,7 @@ export const FileSource = Schema.Struct({
...filePartSourceBase,
type: Schema.Literal("file"),
path: Schema.String,
})
.annotate({ identifier: "FileSource" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "FileSource" })
export const SymbolSource = Schema.Struct({
...filePartSourceBase,
@@ -158,24 +147,19 @@ export const SymbolSource = Schema.Struct({
range: LSP.Range,
name: Schema.String,
kind: NonNegativeInt,
})
.annotate({ identifier: "SymbolSource" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "SymbolSource" })
export const ResourceSource = Schema.Struct({
...filePartSourceBase,
type: Schema.Literal("resource"),
clientName: Schema.String,
uri: Schema.String,
})
.annotate({ identifier: "ResourceSource" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ResourceSource" })
const _FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({
export const FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({
discriminator: "type",
identifier: "FilePartSource",
})
export const FilePartSource = Object.assign(_FilePartSource, { zod: zod(_FilePartSource) })
export const FilePart = Schema.Struct({
...partBase,
@@ -183,10 +167,8 @@ export const FilePart = Schema.Struct({
mime: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
source: Schema.optional(_FilePartSource),
})
.annotate({ identifier: "FilePart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
source: Schema.optional(FilePartSource),
}).annotate({ identifier: "FilePart" })
export type FilePart = Types.DeepMutable<Schema.Schema.Type<typeof FilePart>>
export const AgentPart = Schema.Struct({
@@ -200,9 +182,7 @@ export const AgentPart = Schema.Struct({
end: NonNegativeInt,
}),
),
})
.annotate({ identifier: "AgentPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "AgentPart" })
export type AgentPart = Types.DeepMutable<Schema.Schema.Type<typeof AgentPart>>
export const CompactionPart = Schema.Struct({
@@ -211,9 +191,7 @@ export const CompactionPart = Schema.Struct({
auto: Schema.Boolean,
overflow: Schema.optional(Schema.Boolean),
tail_start_id: Schema.optional(MessageID),
})
.annotate({ identifier: "CompactionPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "CompactionPart" })
export type CompactionPart = Types.DeepMutable<Schema.Schema.Type<typeof CompactionPart>>
export const SubtaskPart = Schema.Struct({
@@ -229,9 +207,7 @@ export const SubtaskPart = Schema.Struct({
}),
),
command: Schema.optional(Schema.String),
})
.annotate({ identifier: "SubtaskPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "SubtaskPart" })
export type SubtaskPart = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPart>>
export const RetryPart = Schema.Struct({
@@ -242,9 +218,7 @@ export const RetryPart = Schema.Struct({
time: Schema.Struct({
created: NonNegativeInt,
}),
})
.annotate({ identifier: "RetryPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "RetryPart" })
export type RetryPart = Omit<Types.DeepMutable<Schema.Schema.Type<typeof RetryPart>>, "error"> & {
error: APIError
}
@@ -253,9 +227,7 @@ export const StepStartPart = Schema.Struct({
...partBase,
type: Schema.Literal("step-start"),
snapshot: Schema.optional(Schema.String),
})
.annotate({ identifier: "StepStartPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "StepStartPart" })
export type StepStartPart = Types.DeepMutable<Schema.Schema.Type<typeof StepStartPart>>
export const StepFinishPart = Schema.Struct({
@@ -274,18 +246,14 @@ export const StepFinishPart = Schema.Struct({
write: Schema.Finite,
}),
}),
})
.annotate({ identifier: "StepFinishPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "StepFinishPart" })
export type StepFinishPart = Types.DeepMutable<Schema.Schema.Type<typeof StepFinishPart>>
export const ToolStatePending = Schema.Struct({
status: Schema.Literal("pending"),
input: Schema.Record(Schema.String, Schema.Any),
raw: Schema.String,
})
.annotate({ identifier: "ToolStatePending" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolStatePending" })
export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
export const ToolStateRunning = Schema.Struct({
@@ -296,9 +264,7 @@ export const ToolStateRunning = Schema.Struct({
time: Schema.Struct({
start: NonNegativeInt,
}),
})
.annotate({ identifier: "ToolStateRunning" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolStateRunning" })
export type ToolStateRunning = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateRunning>>
export const ToolStateCompleted = Schema.Struct({
@@ -313,9 +279,7 @@ export const ToolStateCompleted = Schema.Struct({
compacted: Schema.optional(NonNegativeInt),
}),
attachments: Schema.optional(Schema.Array(FilePart)),
})
.annotate({ identifier: "ToolStateCompleted" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolStateCompleted" })
export type ToolStateCompleted = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateCompleted>>
function truncateToolOutput(text: string, maxChars?: number) {
@@ -333,22 +297,18 @@ export const ToolStateError = Schema.Struct({
start: NonNegativeInt,
end: NonNegativeInt,
}),
})
.annotate({ identifier: "ToolStateError" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolStateError" })
export type ToolStateError = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateError>>
const _ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).annotate({
export const ToolState = Schema.Union([
ToolStatePending,
ToolStateRunning,
ToolStateCompleted,
ToolStateError,
]).annotate({
discriminator: "status",
identifier: "ToolState",
})
// Cast the derived zod so downstream z.infer sees the same mutable shape that
// our exported TS types expose (the pre-migration Zod inferences were mutable).
export const ToolState = Object.assign(_ToolState, {
zod: zod(_ToolState) as unknown as z.ZodType<
ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
>,
})
export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
export const ToolPart = Schema.Struct({
@@ -356,11 +316,9 @@ export const ToolPart = Schema.Struct({
type: Schema.Literal("tool"),
callID: Schema.String,
tool: Schema.String,
state: _ToolState,
state: ToolState,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
})
.annotate({ identifier: "ToolPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolPart" })
export type ToolPart = Omit<Types.DeepMutable<Schema.Schema.Type<typeof ToolPart>>, "state"> & {
state: ToolState
}
@@ -441,8 +399,7 @@ type AssistantError = Schema.Schema.Type<typeof AssistantErrorSchema>
// Consumers of `SessionPrompt.PromptInput.parts` send part drafts without the
// ambient IDs (`messageID`, `sessionID`) that live on stored parts, and may
// omit `id` to let the server allocate one. These Schema-Struct variants
// carry that shape, and `SessionPrompt.PromptInput` just references the
// derived `.zod` (no omit/partial gymnastics needed at the call site).
// carry that shape so prompt decoding can accept drafts without stored IDs.
export const TextPartInput = Schema.Struct({
id: Schema.optional(PartID),
@@ -457,9 +414,7 @@ export const TextPartInput = Schema.Struct({
}),
),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
})
.annotate({ identifier: "TextPartInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "TextPartInput" })
export type TextPartInput = Types.DeepMutable<Schema.Schema.Type<typeof TextPartInput>>
export const FilePartInput = Schema.Struct({
@@ -468,10 +423,8 @@ export const FilePartInput = Schema.Struct({
mime: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
source: Schema.optional(_FilePartSource),
})
.annotate({ identifier: "FilePartInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
source: Schema.optional(FilePartSource),
}).annotate({ identifier: "FilePartInput" })
export type FilePartInput = Types.DeepMutable<Schema.Schema.Type<typeof FilePartInput>>
export const AgentPartInput = Schema.Struct({
@@ -485,9 +438,7 @@ export const AgentPartInput = Schema.Struct({
end: NonNegativeInt,
}),
),
})
.annotate({ identifier: "AgentPartInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "AgentPartInput" })
export type AgentPartInput = Types.DeepMutable<Schema.Schema.Type<typeof AgentPartInput>>
export const SubtaskPartInput = Schema.Struct({
@@ -503,9 +454,7 @@ export const SubtaskPartInput = Schema.Struct({
}),
),
command: Schema.optional(Schema.String),
})
.annotate({ identifier: "SubtaskPartInput" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "SubtaskPartInput" })
export type SubtaskPartInput = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPartInput>>
export const Assistant = Schema.Struct({
@@ -613,7 +562,7 @@ export const Event = {
export const WithParts = Schema.Struct({
info: Info,
parts: Schema.Array(Part),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type WithParts = {
info: Info
parts: Part[]

View File

@@ -1,8 +1,7 @@
import { Schema } from "effect"
import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema"
import { zod } from "@opencode-ai/core/effect-zod"
import { NonNegativeInt, withStatics } from "@opencode-ai/core/schema"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { namedSchemaError } from "@/util/named-schema-error"
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
@@ -37,9 +36,7 @@ export const ToolCall = Schema.Struct({
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolCall" })
export type ToolCall = Schema.Schema.Type<typeof ToolCall>
export const ToolPartialCall = Schema.Struct({
@@ -48,9 +45,7 @@ export const ToolPartialCall = Schema.Struct({
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolPartialCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolPartialCall" })
export type ToolPartialCall = Schema.Schema.Type<typeof ToolPartialCall>
export const ToolResult = Schema.Struct({
@@ -60,39 +55,32 @@ export const ToolResult = Schema.Struct({
toolName: Schema.String,
args: Schema.Unknown,
result: Schema.String,
})
.annotate({ identifier: "ToolResult" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolResult" })
export type ToolResult = Schema.Schema.Type<typeof ToolResult>
export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult])
.annotate({ identifier: "ToolInvocation", discriminator: "state" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult]).annotate({
identifier: "ToolInvocation",
discriminator: "state",
})
export type ToolInvocation = Schema.Schema.Type<typeof ToolInvocation>
export const TextPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
})
.annotate({ identifier: "TextPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "TextPart" })
export type TextPart = Schema.Schema.Type<typeof TextPart>
export const ReasoningPart = Schema.Struct({
type: Schema.Literal("reasoning"),
text: Schema.String,
providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})
.annotate({ identifier: "ReasoningPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ReasoningPart" })
export type ReasoningPart = Schema.Schema.Type<typeof ReasoningPart>
export const ToolInvocationPart = Schema.Struct({
type: Schema.Literal("tool-invocation"),
toolInvocation: ToolInvocation,
})
.annotate({ identifier: "ToolInvocationPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "ToolInvocationPart" })
export type ToolInvocationPart = Schema.Schema.Type<typeof ToolInvocationPart>
export const SourceUrlPart = Schema.Struct({
@@ -101,9 +89,7 @@ export const SourceUrlPart = Schema.Struct({
url: Schema.String,
title: Schema.optional(Schema.String),
providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})
.annotate({ identifier: "SourceUrlPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "SourceUrlPart" })
export type SourceUrlPart = Schema.Schema.Type<typeof SourceUrlPart>
export const FilePart = Schema.Struct({
@@ -111,16 +97,12 @@ export const FilePart = Schema.Struct({
mediaType: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
})
.annotate({ identifier: "FilePart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "FilePart" })
export type FilePart = Schema.Schema.Type<typeof FilePart>
export const StepStartPart = Schema.Struct({
type: Schema.Literal("step-start"),
})
.annotate({ identifier: "StepStartPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "StepStartPart" })
export type StepStartPart = Schema.Schema.Type<typeof StepStartPart>
export const MessagePart = Schema.Union([
@@ -130,9 +112,7 @@ export const MessagePart = Schema.Union([
SourceUrlPart,
FilePart,
StepStartPart,
])
.annotate({ identifier: "MessagePart", discriminator: "type" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
]).annotate({ identifier: "MessagePart", discriminator: "type" })
export type MessagePart = Schema.Schema.Type<typeof MessagePart>
export const Info = Schema.Struct({
@@ -184,9 +164,7 @@ export const Info = Schema.Struct({
),
snapshot: Schema.optional(Schema.String),
}).annotate({ identifier: "MessageMetadata" }),
})
.annotate({ identifier: "Message" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
}).annotate({ identifier: "Message" })
export type Info = Schema.Schema.Type<typeof Info>
export * as Message from "./message"

View File

@@ -2,8 +2,6 @@ import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "@/bus"
import { Snapshot } from "@/snapshot"
import { Storage } from "@/storage/storage"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID } from "./schema"
@@ -160,7 +158,7 @@ export const defaultLayer = Layer.suspend(() =>
export const DiffInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
})
export type DiffInput = Schema.Schema.Type<typeof DiffInput>
export * as SessionSummary from "./summary"

View File

@@ -55,9 +55,9 @@ There are now "sync events" which are different than "bus events". Bus events ar
```ts
const Diff = BusEvent.define(
"session.diff",
z.object({
sessionID: SessionID.zod,
diff: Snapshot.FileDiff.array(),
Schema.Struct({
sessionID: SessionID,
diff: Schema.Array(Snapshot.FileDiff),
}),
)
```
@@ -71,8 +71,8 @@ const Created = SyncEvent.define({
type: "session.created",
version: 1,
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
schema: Schema.Struct({
sessionID: SessionID,
info: Info,
}),
})
@@ -114,7 +114,7 @@ This allows you to "reshape" an event from the sync system before it's published
The only time we use this is the `session.updated` event. Previously this event contained the entire session object. The sync event only contains the fields updated. We convert the event to contain the full object for backwards compatibility (but ideally we'd remove this).
It's very important that types are correct when working with events. Event definitions have a `schema` which carries the definition of the event shape (provided by a zod schema, inferred into a TypeScript type). Examples:
It's very important that types are correct when working with events. Event definitions have a `schema` which carries the definition of the event shape. Examples:
```ts
// The schema from `Updated` typechecks the object correctly
@@ -149,12 +149,12 @@ const Update = SyncEvent.define({
type: "session.updated",
version: 1,
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
schema: Schema.Struct({
sessionID: SessionID,
info: partialSchema(Info),
}),
busSchema: z.object({
sessionID: SessionID.zod,
busSchema: Schema.Struct({
sessionID: SessionID,
info: Info,
}),
})

View File

@@ -1,7 +1,6 @@
import { Schema } from "effect"
import { Identifier } from "@/id/id"
import { zod } from "@opencode-ai/core/effect-zod"
import { withStatics } from "@opencode-ai/core/schema"
const toolIdSchema = Schema.String.check(Schema.isStartsWith("tool")).pipe(Schema.brand("ToolID"))
@@ -11,6 +10,5 @@ export type ToolID = typeof toolIdSchema.Type
export const ToolID = toolIdSchema.pipe(
withStatics((schema: typeof toolIdSchema) => ({
ascending: (id?: string) => schema.make(Identifier.ascending("tool", id)),
zod: zod(schema),
})),
)

View File

@@ -251,7 +251,7 @@ test("updates global config and omits empty shell key in jsonc", async () => {
const file = path.join(tmp.path, "opencode.jsonc")
const writtenConfig = await Filesystem.readText(file)
const parsed = ConfigParse.effectSchema(Config.Info, ConfigParse.jsonc(writtenConfig, file), file)
const parsed = ConfigParse.schema(Config.Info, ConfigParse.jsonc(writtenConfig, file), file)
expect(writtenConfig).not.toContain('"shell"')
expect(parsed.shell).toBeUndefined()
expect(parsed.model).toBe("test/model")
@@ -1681,8 +1681,8 @@ test("permission config preserves user key order", async () => {
})
})
test("Effect config parser preserves permission order while rejecting unknown top-level keys", () => {
const config = ConfigParse.effectSchema(
test("config parser preserves permission order while rejecting unknown top-level keys", () => {
const config = ConfigParse.schema(
Config.Info,
{
permission: {
@@ -1696,7 +1696,7 @@ test("Effect config parser preserves permission order while rejecting unknown to
expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"])
try {
ConfigParse.effectSchema(Config.Info, { invalid_field: true }, "test")
ConfigParse.schema(Config.Info, { invalid_field: true }, "test")
throw new Error("expected config parse to fail")
} catch (err) {
const error = err as { data?: { issues?: Array<{ code?: string; keys?: string[]; path?: string[] }> } }
@@ -2463,7 +2463,7 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
// parseManagedPlist unit tests — pure function, no OS interaction
test("parseManagedPlist strips MDM metadata keys", async () => {
const config = ConfigParse.effectSchema(
const config = ConfigParse.schema(
Config.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
@@ -2491,7 +2491,7 @@ test("parseManagedPlist strips MDM metadata keys", async () => {
})
test("parseManagedPlist parses server settings", async () => {
const config = ConfigParse.effectSchema(
const config = ConfigParse.schema(
Config.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
@@ -2511,7 +2511,7 @@ test("parseManagedPlist parses server settings", async () => {
})
test("parseManagedPlist parses permission rules", async () => {
const config = ConfigParse.effectSchema(
const config = ConfigParse.schema(
Config.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
@@ -2541,7 +2541,7 @@ test("parseManagedPlist parses permission rules", async () => {
})
test("parseManagedPlist parses enabled_providers", async () => {
const config = ConfigParse.effectSchema(
const config = ConfigParse.schema(
Config.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(
@@ -2558,7 +2558,7 @@ test("parseManagedPlist parses enabled_providers", async () => {
})
test("parseManagedPlist handles empty config", async () => {
const config = ConfigParse.effectSchema(
const config = ConfigParse.schema(
Config.Info,
ConfigParse.jsonc(
await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })),

View File

@@ -14,12 +14,7 @@ import { WorkspaceID } from "../../src/control-plane/schema"
// Covers the session-domain Effect Schema migration. For each migrated
// schema we assert:
// 1. The Effect decoder (`Schema.decodeUnknownSync`) accepts valid input.
// 2. The derived Zod (`X.zod.parse`) accepts the same input and returns the
// same shape for schemas that still expose Zod statics.
// 3. Clearly-invalid input is rejected by both paths where both exist.
//
// The point is to lock down the Schema <-> Zod bridge so a future edit to
// any input schema can't silently drop or widen a field on one side.
// 2. Clearly-invalid input is rejected.
// Representative valid IDs — the branded schemas require the right prefix
// (see src/id/id.ts).