refactor(schema): extract public event definitions (#33579)

This commit is contained in:
Kit Langton 2026-06-24 22:43:17 +02:00 committed by GitHub
commit 24b0132bc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 2743 additions and 2127 deletions

View file

@ -86,6 +86,7 @@
"@openauthjs/openauth": "catalog:",
"@opencode-ai/llm": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/server": "workspace:*",

View file

@ -2,29 +2,20 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { InstanceState } from "@/effect/instance-state"
import { EffectBridge } from "@/effect/bridge"
import type { InstanceContext } from "@/project/instance-context"
import { SessionID, MessageID } from "@/session/schema"
import { Effect, Layer, Context, Schema } from "effect"
import { Config } from "@/config/config"
import { MCP } from "../mcp"
import { Skill } from "../skill"
import { EventV2 } from "@opencode-ai/core/event"
import PROMPT_INITIALIZE from "./template/initialize.txt"
import PROMPT_REVIEW from "./template/review.txt"
import { LegacyEvent } from "@opencode-ai/schema/legacy-event"
type State = {
commands: Record<string, Info>
}
export const Event = {
Executed: EventV2.define({
type: "command.executed",
schema: {
name: Schema.String,
sessionID: SessionID,
arguments: Schema.String,
messageID: MessageID,
},
}),
Executed: LegacyEvent.CommandExecuted,
}
export const Info = Schema.Struct({

View file

@ -33,6 +33,7 @@ import { Vcs } from "@/project/vcs"
import { InstanceStore } from "@/project/instance-store"
import { InstanceBootstrap } from "@/project/bootstrap"
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
import { WorkspaceEvent } from "@opencode-ai/schema/workspace-event"
export const Info = Schema.Struct({
...WorkspaceInfoSchema.fields,
@ -40,27 +41,10 @@ export const Info = Schema.Struct({
}).annotate({ identifier: "Workspace" })
export type Info = WorkspaceInfo & { timeUsed: number }
export const ConnectionStatus = Schema.Struct({
workspaceID: WorkspaceV2.ID,
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
})
export type ConnectionStatus = Schema.Schema.Type<typeof ConnectionStatus>
export const ConnectionStatus = WorkspaceEvent.ConnectionStatus
export type ConnectionStatus = WorkspaceEvent.ConnectionStatus
export const Event = {
Ready: EventV2.define({
type: "workspace.ready",
schema: {
name: Schema.String,
},
}),
Failed: EventV2.define({
type: "workspace.failed",
schema: {
message: Schema.String,
},
}),
Status: EventV2.define({ type: "workspace.status", schema: ConnectionStatus.fields }),
}
export const Event = WorkspaceEvent
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
return {

View file

@ -0,0 +1,3 @@
export * as EventManifest from "./event-manifest"
export { Definitions, Durable, Latest } from "@opencode-ai/schema/event-manifest"

View file

@ -7,9 +7,6 @@ import { EventV2 } from "@opencode-ai/core/event"
import { Location } from "@opencode-ai/core/location"
import { Project } from "@opencode-ai/core/project"
import { AbsolutePath } from "@opencode-ai/core/schema"
import "@opencode-ai/core/account"
import "@opencode-ai/core/catalog"
import "@opencode-ai/core/session/event"
import { Context, Effect, Layer } from "effect"
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
@ -45,10 +42,7 @@ export const layer = Layer.effect(
workspace: workspaceID,
payload: { id: event.id, type: event.type, properties: event.data },
})
const durable = EventV2.registry.get(event.type)?.durable
if (durable === undefined || event.durable === undefined) return
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
if (typeof aggregateID !== "string") return
if (event.durable === undefined) return
GlobalBus.emit("event", {
directory: event.location?.directory ?? ctx?.directory,
project: ctx?.project.id,
@ -59,7 +53,7 @@ export const layer = Layer.effect(
id: event.id,
type: EventV2.versionedType(event.type, event.durable.version),
seq: event.durable.seq,
aggregateID,
aggregateID: event.durable.aggregateID,
data: event.data,
},
},

View file

@ -1,7 +1,7 @@
import { EventV2 } from "@opencode-ai/core/event"
import { Schema } from "effect"
import { NamedError } from "@opencode-ai/core/util/error"
import { Process } from "@/util/process"
import { IdeEvent } from "@opencode-ai/schema/ide-event"
const SUPPORTED_IDES = [
{ name: "Windsurf" as const, cmd: "windsurf" },
@ -11,14 +11,7 @@ const SUPPORTED_IDES = [
{ name: "VSCodium" as const, cmd: "codium" },
]
export const Event = {
Installed: EventV2.define({
type: "ide.installed",
schema: {
ide: Schema.String,
},
}),
}
export const Event = IdeEvent
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})

View file

@ -8,30 +8,17 @@ import { errorMessage } from "@/util/error"
import { ChildProcess } from "effect/unstable/process"
import { AppProcess } from "@opencode-ai/core/process"
import path from "path"
import { EventV2 } from "@opencode-ai/core/event"
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
import semver from "semver"
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
import { NpmConfig } from "@opencode-ai/core/npm-config"
import { InstallationEvent } from "@opencode-ai/schema/installation-event"
export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown"
export type ReleaseType = "patch" | "minor" | "major"
export const Event = {
Updated: EventV2.define({
type: "installation.updated",
schema: {
version: Schema.String,
},
}),
UpdateAvailable: EventV2.define({
type: "installation.update-available",
schema: {
version: Schema.String,
},
}),
}
export const Event = InstallationEvent
export function getReleaseType(current: string, latest: string): ReleaseType {
const currMajor = semver.major(current)

View file

@ -1,7 +1,6 @@
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import * as LSPClient from "./client"
import path from "path"
import { pathToFileURL, fileURLToPath } from "url"
@ -14,10 +13,9 @@ import { InstanceState } from "@/effect/instance-state"
import { containsPath } from "@/project/instance-context"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { LspEvent } from "@opencode-ai/schema/lsp-event"
export const Event = {
Updated: EventV2.define({ type: "lsp.updated", schema: {} }),
}
export const Event = LspEvent
const Position = Schema.Struct({
line: NonNegativeInt,

View file

@ -26,7 +26,6 @@ import { McpOAuthProvider, OAUTH_CALLBACK_PATH } from "./oauth-provider"
import { McpOAuthCallback } from "./oauth-callback"
import { McpAuth } from "./auth"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { TuiEvent } from "@/server/tui-event"
import open from "open"
import { Cause, Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect"
@ -35,6 +34,7 @@ import { InstanceState } from "@/effect/instance-state"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { McpCatalog } from "./catalog"
import { McpEvent } from "@opencode-ai/schema/mcp-event"
const DEFAULT_TIMEOUT = 30_000
const CLIENT_OPTIONS = {
@ -59,20 +59,9 @@ export const Resource = Schema.Struct({
}).annotate({ identifier: "McpResource" })
export type Resource = Schema.Schema.Type<typeof Resource>
export const ToolsChanged = EventV2.define({
type: "mcp.tools.changed",
schema: {
server: Schema.String,
},
})
export const ToolsChanged = McpEvent.ToolsChanged
export const BrowserOpenFailed = EventV2.define({
type: "mcp.browser.open.failed",
schema: {
mcpName: Schema.String,
url: Schema.String,
},
})
export const BrowserOpenFailed = McpEvent.BrowserOpenFailed
export const Failed = NamedError.create("MCPFailed", {
name: Schema.String,

View file

@ -6,19 +6,9 @@ import { Deferred, Effect, Layer, Context } from "effect"
import os from "os"
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { PermissionV1Event } from "@opencode-ai/schema/permission-v1"
export const Event = {
Asked: EventV2.define({ type: "permission.asked", schema: PermissionV1.Request.fields }),
Replied: EventV2.define({
type: "permission.replied",
schema: {
sessionID: PermissionV1.Request.fields.sessionID,
requestID: PermissionV1.ID,
reply: PermissionV1.Reply,
},
}),
}
export const Event = PermissionV1Event
export interface Interface {
readonly ask: (input: PermissionV1.AskInput) => Effect.Effect<void, PermissionV1.Error>

View file

@ -16,46 +16,18 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
import { AppProcess } from "@opencode-ai/core/process"
import { ProjectV2 } from "@opencode-ai/core/project"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { serviceUse } from "@opencode-ai/core/effect/service-use"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { Project } from "@opencode-ai/schema/project"
const ProjectVcs = Schema.Literal("git")
const ProjectIcon = Schema.Struct({
url: optionalOmitUndefined(Schema.String),
override: optionalOmitUndefined(Schema.String),
color: optionalOmitUndefined(Schema.String),
})
const ProjectCommands = Schema.Struct({
start: optionalOmitUndefined(
Schema.String.annotate({ description: "Startup script to run when creating a new workspace (worktree)" }),
),
})
const ProjectTime = Schema.Struct({
created: NonNegativeInt,
updated: NonNegativeInt,
initialized: optionalOmitUndefined(NonNegativeInt),
})
export const Info = Schema.Struct({
id: ProjectV2.ID,
worktree: Schema.String,
vcs: optionalOmitUndefined(ProjectVcs),
name: optionalOmitUndefined(Schema.String),
icon: optionalOmitUndefined(ProjectIcon),
commands: optionalOmitUndefined(ProjectCommands),
time: ProjectTime,
sandboxes: Schema.Array(Schema.String),
}).annotate({ identifier: "Project" })
export const Info = Project.Info
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
export const Event = {
Updated: EventV2.define({ type: "project.updated", schema: Info.fields }),
Updated: Project.Event.Updated,
}
type Row = typeof ProjectTable.$inferSelect
@ -72,7 +44,7 @@ export function fromRow(row: Row): Info {
return {
id: row.id,
worktree: row.worktree,
vcs: row.vcs ? Schema.decodeUnknownSync(ProjectVcs)(row.vcs) : undefined,
vcs: row.vcs ? Schema.decodeUnknownSync(Project.Vcs)(row.vcs) : undefined,
name: row.name ?? undefined,
icon,
time: {
@ -88,15 +60,15 @@ export function fromRow(row: Row): Info {
export const UpdateInput = Schema.Struct({
projectID: ProjectV2.ID,
name: Schema.optional(Schema.String),
icon: Schema.optional(ProjectIcon),
commands: Schema.optional(ProjectCommands),
icon: Schema.optional(Project.Icon),
commands: Schema.optional(Project.Commands),
})
export type UpdateInput = Types.DeepMutable<Schema.Schema.Type<typeof UpdateInput>>
export const UpdatePayload = Schema.Struct({
name: Schema.optional(Schema.String),
icon: Schema.optional(ProjectIcon),
commands: Schema.optional(ProjectCommands),
icon: Schema.optional(Project.Icon),
commands: Schema.optional(Project.Commands),
}).annotate({ identifier: "ProjectUpdateInput" })
export type UpdatePayload = Types.DeepMutable<Schema.Schema.Type<typeof UpdatePayload>>
@ -135,7 +107,6 @@ export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const fs = yield* FSUtil.Service
const proc = yield* AppProcess.Service
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const projectV2 = yield* ProjectV2.Service
const projectDirectories = yield* ProjectDirectories.Service
@ -168,7 +139,7 @@ export const layer = Layer.effect(
}),
)
const fakeVcs = Schema.decodeUnknownSync(Schema.optional(ProjectVcs))(Flag.OPENCODE_FAKE_VCS)
const fakeVcs = Schema.decodeUnknownSync(Schema.optional(Project.Vcs))(Flag.OPENCODE_FAKE_VCS)
const scope = yield* Scope.Scope

View file

@ -1,11 +1,12 @@
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Effect, Layer, Context, Schema, Stream, Scope } from "effect"
import { Effect, Layer, Context, Schema, Scope } from "effect"
import { formatPatch, structuredPatch } from "diff"
import { InstanceState } from "@/effect/instance-state"
import { Watcher } from "@opencode-ai/core/filesystem/watcher"
import { Git } from "@/git"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { VcsEvent } from "@opencode-ai/schema/vcs-event"
const PATCH_CONTEXT_LINES = 2_147_483_647
const MAX_PATCH_BYTES = 10_000_000
@ -234,14 +235,7 @@ const track = Effect.fnUntraced(function* (
export const Mode = Schema.Literals(["git", "branch"])
export type Mode = Schema.Schema.Type<typeof Mode>
export const Event = {
BranchUpdated: EventV2.define({
type: "vcs.branch.updated",
schema: {
branch: Schema.optional(Schema.String),
},
}),
}
export const Event = VcsEvent
export const Info = Schema.Struct({
branch: Schema.optional(Schema.String),

View file

@ -1,94 +1,28 @@
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Deferred, Effect, Layer, Schema, Context } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { SessionID, MessageID } from "@/session/schema"
import { SessionID } from "@/session/schema"
import { QuestionID } from "./schema"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { QuestionV1 } from "@opencode-ai/schema/question-v1"
// Schemas — these are pure data; nothing checks class identity (see PR
// description) so they're plain `Schema.Struct` + type alias. That lets
// `Question.ask` and other internal sites trust the type contract without a
// re-decode to coerce nested class instances.
export const Option = Schema.Struct({
label: Schema.String.annotate({
description: "Display text (1-5 words, concise)",
}),
description: Schema.String.annotate({
description: "Explanation of choice",
}),
}).annotate({ identifier: "QuestionOption" })
export type Option = Schema.Schema.Type<typeof Option>
const base = {
question: Schema.String.annotate({
description: "Complete question",
}),
header: Schema.String.annotate({
description: "Very short label (max 30 chars)",
}),
options: Schema.Array(Option).annotate({
description: "Available choices",
}),
multiple: Schema.optional(Schema.Boolean).annotate({
description: "Allow selecting multiple choices",
}),
}
export const Info = Schema.Struct({
...base,
custom: Schema.optional(Schema.Boolean).annotate({
description: "Allow typing a custom answer (default: true)",
}),
}).annotate({ identifier: "QuestionInfo" })
export type Info = Schema.Schema.Type<typeof Info>
export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionPrompt" })
export type Prompt = Schema.Schema.Type<typeof Prompt>
export const Tool = Schema.Struct({
messageID: MessageID,
callID: Schema.String,
}).annotate({ identifier: "QuestionTool" })
export type Tool = Schema.Schema.Type<typeof Tool>
export const Request = Schema.Struct({
id: QuestionID,
sessionID: SessionID,
questions: Schema.Array(Info).annotate({
description: "Questions to ask",
}),
tool: Schema.optional(Tool),
}).annotate({ identifier: "QuestionRequest" })
export type Request = Schema.Schema.Type<typeof Request>
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
export type Answer = Schema.Schema.Type<typeof Answer>
export const Reply = Schema.Struct({
answers: Schema.Array(Answer).annotate({
description: "User answers in order of questions (each answer is an array of selected labels)",
}),
}).annotate({ identifier: "QuestionReply" })
export type Reply = Schema.Schema.Type<typeof Reply>
export const Replied = Schema.Struct({
sessionID: SessionID,
requestID: QuestionID,
answers: Schema.Array(Answer),
}).annotate({ identifier: "QuestionReplied" })
export const Rejected = Schema.Struct({
sessionID: SessionID,
requestID: QuestionID,
}).annotate({ identifier: "QuestionRejected" })
export const Event = {
Asked: EventV2.define({ type: "question.asked", schema: Request.fields }),
Replied: EventV2.define({ type: "question.replied", schema: Replied.fields }),
Rejected: EventV2.define({ type: "question.rejected", schema: Rejected.fields }),
}
export const Option = QuestionV1.Option
export type Option = typeof Option.Type
export const Info = QuestionV1.Info
export type Info = typeof Info.Type
export const Prompt = QuestionV1.Prompt
export type Prompt = typeof Prompt.Type
export const Tool = QuestionV1.Tool
export type Tool = typeof Tool.Type
export const Request = QuestionV1.Request
export type Request = typeof Request.Type
export const Answer = QuestionV1.Answer
export type Answer = typeof Answer.Type
export const Reply = QuestionV1.Reply
export type Reply = typeof Reply.Type
export const Replied = QuestionV1.Replied
export const Rejected = QuestionV1.Rejected
export const Event = QuestionV1.Event
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("QuestionRejectedError", {}) {
override get message() {

View file

@ -1,10 +1,4 @@
import { Schema } from "effect"
import { QuestionV1 } from "@opencode-ai/schema/question-v1"
import { Identifier } from "@/id/id"
import { Newtype } from "@opencode-ai/core/schema"
export class QuestionID extends Newtype<QuestionID>()("QuestionID", Schema.String.check(Schema.isStartsWith("que"))) {
static ascending(id?: string): QuestionID {
return this.make(Identifier.ascending("question", id))
}
}
export const QuestionID = QuestionV1.ID
export type QuestionID = typeof QuestionID.Type

View file

@ -1,10 +1,7 @@
import { EventV2 } from "@opencode-ai/core/event"
import { Schema } from "effect"
import { ServerEvent } from "@opencode-ai/schema/server-event"
export const Event = {
Connected: EventV2.define({ type: "server.connected", schema: {} }),
Disposed: EventV2.define({ type: "global.disposed", schema: {} }),
}
export const Event = ServerEvent
export const InstanceDisposed = Schema.Struct({
id: Schema.String,

View file

@ -1,6 +1,7 @@
import { Schema } from "effect"
import { HttpApi } from "effect/unstable/httpapi"
import { EventV2 } from "@opencode-ai/core/event"
import { EventManifest } from "@/event-manifest"
import { Credential } from "@opencode-ai/core/credential"
import { Integration } from "@opencode-ai/core/integration"
import { SkillV2 } from "@opencode-ai/core/skill"
@ -24,15 +25,13 @@ import { SessionApi } from "./groups/session"
import { SyncApi } from "./groups/sync"
import { TuiApi } from "./groups/tui"
import { WorkspaceApi } from "./groups/workspace"
import { Api } from "@opencode-ai/server/api"
// GlobalEventSchema snapshots the registry after event-producing groups register their variants.
import { makeApi } from "@opencode-ai/server/api"
import { GlobalApi } from "./groups/global"
import { Authorization } from "./middleware/authorization"
import { SchemaErrorMiddleware } from "./middleware/schema-error"
const EventSchema = Schema.Union([
...EventV2.registry
.values()
...EventManifest.Latest.values()
.map((definition) =>
Schema.Struct({
id: EventV2.ID,
@ -44,6 +43,8 @@ const EventSchema = Schema.Union([
InstanceDisposed,
]).annotate({ identifier: "Event" })
export const ServerApi = makeApi(EventManifest.Latest.values().toArray())
export const RootHttpApi = HttpApi.make("opencode-root")
.addHttpApi(ControlApi)
.addHttpApi(ControlPlaneApi)
@ -73,7 +74,7 @@ export const OpenCodeHttpApi = HttpApi.make("opencode")
.addHttpApi(RootHttpApi)
.addHttpApi(EventApi)
.addHttpApi(InstanceHttpApi)
.addHttpApi(Api)
.addHttpApi(ServerApi)
.addHttpApi(PtyConnectApi)
.annotate(HttpApi.AdditionalSchemas, [
EventSchema,

View file

@ -1,6 +1,6 @@
import { Config } from "@/config/config"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { EventV2 } from "@opencode-ai/core/event"
import { EventManifest } from "@/event-manifest"
import { InstanceDisposed } from "@/server/event"
import "@opencode-ai/core/account"
import "@/server/event"
@ -13,8 +13,7 @@ const GlobalHealth = Schema.Struct({
version: Schema.String,
})
const SyncEventSchemas = EventV2.registry
.values()
const SyncEventSchemas = EventManifest.Latest.values()
.flatMap((definition) => {
if (!definition.durable) return []
return [
@ -38,8 +37,7 @@ const GlobalEventSchema = Schema.Struct({
project: Schema.optional(Schema.String),
workspace: Schema.optional(Schema.String),
payload: Schema.Union([
...EventV2.registry
.values()
...EventManifest.Latest.values()
.map((definition) =>
Schema.Struct({ id: EventV2.ID, type: Schema.Literal(definition.type), properties: definition.data }),
)

View file

@ -1,53 +1 @@
import { SessionID } from "@/session/schema"
import { PositiveInt } from "@opencode-ai/core/schema"
import { EventV2 } from "@opencode-ai/core/event"
import { Effect, Schema } from "effect"
const DEFAULT_TOAST_DURATION = 5000
export const TuiEvent = {
PromptAppend: EventV2.define({ type: "tui.prompt.append", schema: { text: Schema.String } }),
CommandExecute: EventV2.define({
type: "tui.command.execute",
schema: {
command: Schema.Union([
Schema.Literals([
"session.list",
"session.new",
"session.share",
"session.interrupt",
"session.compact",
"session.page.up",
"session.page.down",
"session.line.up",
"session.line.down",
"session.half.page.up",
"session.half.page.down",
"session.first",
"session.last",
"prompt.clear",
"prompt.submit",
"agent.cycle",
]),
Schema.String,
]),
},
}),
ToastShow: EventV2.define({
type: "tui.toast.show",
schema: {
title: Schema.optional(Schema.String),
message: Schema.String,
variant: Schema.Literals(["info", "success", "warning", "error"]),
duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
description: "Duration in milliseconds",
}),
},
}),
SessionSelect: EventV2.define({
type: "tui.session.select",
schema: {
sessionID: SessionID.annotate({ description: "Session ID to navigate to" }),
},
}),
}
export { TuiEvent } from "@opencode-ai/schema/tui-event"

View file

@ -23,17 +23,10 @@ import { SessionEvent } from "@opencode-ai/core/session/event"
import { SessionMessage } from "@opencode-ai/core/session/message"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { EventV2 } from "@opencode-ai/core/event"
import { buildPrompt } from "@opencode-ai/core/session/compaction"
import { SessionCompactionEvent } from "@opencode-ai/schema/session-compaction-event"
export const Event = {
Compacted: EventV2.define({
type: "session.compacted",
schema: {
sessionID: SessionID,
},
}),
}
export const Event = SessionCompactionEvent
export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000

View file

@ -1,5 +1,4 @@
import { EventV2 } from "@opencode-ai/core/event"
import { SessionID, MessageID, PartID } from "./schema"
import { SessionID, MessageID } from "./schema"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { ProviderV2 } from "@opencode-ai/core/provider"
import {
@ -12,11 +11,9 @@ import {
Info,
OutputLengthError,
Part,
StructuredOutputError,
SubtaskPart,
User,
WithParts,
type ToolPart,
} from "@opencode-ai/core/v1/session"
import { NamedError } from "@opencode-ai/core/util/error"
@ -61,16 +58,7 @@ export const Event = {
Updated: SessionV1.Event.MessageUpdated,
Removed: SessionV1.Event.MessageRemoved,
PartUpdated: SessionV1.Event.PartUpdated,
PartDelta: EventV2.define({
type: "message.part.delta",
schema: {
sessionID: SessionID,
messageID: MessageID,
partID: PartID,
field: Schema.String,
delta: Schema.String,
},
}),
PartDelta: SessionV1.Event.PartDelta,
PartRemoved: SessionV1.Event.PartRemoved,
}

View file

@ -11,7 +11,6 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { Database } from "@opencode-ai/core/database/database"
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { SessionV2 } from "@opencode-ai/core/session"
import { SessionExecution } from "@opencode-ai/core/session/execution"
@ -38,7 +37,6 @@ import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { SessionID, MessageID, PartID } from "./schema"
import type { Provider } from "@/provider/provider"
import { Permission } from "@/permission"
import { Global } from "@opencode-ai/core/global"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
@ -309,69 +307,12 @@ export type GlobalListInput = {
archived?: boolean
}
const CreatedEventSchema = Schema.Struct({
sessionID: SessionID,
info: Info,
})
const UpdatedShare = Schema.Struct({
url: Schema.optional(Schema.NullOr(Schema.String)),
})
const UpdatedTime = Schema.Struct({
created: Schema.optional(Schema.NullOr(NonNegativeInt)),
updated: Schema.optional(Schema.NullOr(NonNegativeInt)),
compacting: Schema.optional(Schema.NullOr(NonNegativeInt)),
archived: Schema.optional(Schema.NullOr(ArchivedTimestamp)),
})
const UpdatedInfo = Schema.Struct({
id: Schema.optional(Schema.NullOr(SessionID)),
slug: Schema.optional(Schema.NullOr(Schema.String)),
projectID: Schema.optional(Schema.NullOr(ProjectV2.ID)),
workspaceID: Schema.optional(Schema.NullOr(WorkspaceV2.ID)),
directory: Schema.optional(Schema.NullOr(Schema.String)),
path: Schema.optional(Schema.NullOr(Schema.String)),
parentID: Schema.optional(Schema.NullOr(SessionID)),
summary: Schema.optional(Schema.NullOr(Summary)),
cost: Schema.optional(Schema.Finite),
tokens: Schema.optional(Tokens),
share: Schema.optional(UpdatedShare),
title: Schema.optional(Schema.NullOr(Schema.String)),
agent: Schema.optional(Schema.NullOr(Schema.String)),
model: Schema.optional(Schema.NullOr(Model)),
version: Schema.optional(Schema.NullOr(Schema.String)),
metadata: Schema.optional(Schema.NullOr(Metadata)),
time: Schema.optional(UpdatedTime),
permission: Schema.optional(Schema.NullOr(PermissionV1.Ruleset)),
revert: Schema.optional(Schema.NullOr(Revert)),
})
const UpdatedEventSchema = Schema.Struct({
sessionID: SessionID,
info: UpdatedInfo,
})
export const Event = {
Created: SessionV1.Event.Created,
Updated: SessionV1.Event.Updated,
Deleted: SessionV1.Event.Deleted,
Diff: EventV2.define({
type: "session.diff",
schema: {
sessionID: SessionID,
diff: Schema.Array(Snapshot.FileDiff),
},
}),
Error: EventV2.define({
type: "session.error",
schema: {
sessionID: Schema.optional(SessionID),
// Reuses SessionV1.Assistant.fields.error (already Schema.optional) so
// the derived schema keeps the same discriminated-union shape on the event stream.
error: SessionV1.Assistant.fields.error,
},
}),
Diff: SessionV1.Event.Diff,
Error: SessionV1.Event.Error,
}
export function plan(input: { slug: string; time: { created: number } }, instance: InstanceContext) {

View file

@ -1,53 +1,14 @@
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { InstanceState } from "@/effect/instance-state"
import { SessionID } from "./schema"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { Effect, Layer, Context, Schema } from "effect"
import { Effect, Layer, Context } from "effect"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { SessionStatusEvent } from "@opencode-ai/schema/session-status-event"
export const Info = Schema.Union([
Schema.Struct({
type: Schema.Literal("idle"),
}),
Schema.Struct({
type: Schema.Literal("retry"),
attempt: NonNegativeInt,
message: Schema.String,
action: Schema.optional(
Schema.Struct({
reason: Schema.String,
provider: Schema.String,
title: Schema.String,
message: Schema.String,
label: Schema.String,
link: Schema.optional(Schema.String),
}),
),
next: NonNegativeInt,
}),
Schema.Struct({
type: Schema.Literal("busy"),
}),
]).annotate({ identifier: "SessionStatus" })
export type Info = Schema.Schema.Type<typeof Info>
export const Info = SessionStatusEvent.Info
export type Info = SessionStatusEvent.Info
export const Event = {
Status: EventV2.define({
type: "session.status",
schema: {
sessionID: SessionID,
status: Info,
},
}),
// deprecated
Idle: EventV2.define({
type: "session.idle",
schema: {
sessionID: SessionID,
},
}),
}
export const Event = SessionStatusEvent
export interface Interface {
readonly get: (sessionID: SessionID) => Effect.Effect<Info>

View file

@ -1,31 +1,17 @@
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { SessionID } from "./schema"
import { Effect, Layer, Context, Schema } from "effect"
import { Effect, Layer, Context } from "effect"
import { Database } from "@opencode-ai/core/database/database"
import { eq } from "drizzle-orm"
import { asc } from "drizzle-orm"
import { TodoTable } from "@opencode-ai/core/session/sql"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { SessionTodo } from "@opencode-ai/schema/session-todo"
export const Info = Schema.Struct({
content: Schema.String.annotate({ description: "Brief description of the task" }),
status: Schema.String.annotate({
description: "Current status of the task: pending, in_progress, completed, cancelled",
}),
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }),
}).annotate({ identifier: "Todo" })
export type Info = Schema.Schema.Type<typeof Info>
export const Info = SessionTodo.Info
export type Info = SessionTodo.Info
export const Event = {
Updated: EventV2.define({
type: "todo.updated",
schema: {
sessionID: SessionID,
todos: Schema.Array(Info),
},
}),
}
export const Event = SessionTodo.Event
export interface Interface {
readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect<void>

View file

@ -9,6 +9,7 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
import { Hash } from "@opencode-ai/core/util/hash"
import { Config } from "@/config/config"
import { Global } from "@opencode-ai/core/global"
import { Info } from "@opencode-ai/schema/file-diff"
export const Patch = Schema.Struct({
hash: Schema.String,
@ -16,16 +17,7 @@ export const Patch = Schema.Struct({
})
export type Patch = typeof Patch.Type
export const FileDiff = Schema.Struct({
// Optional because legacy/imported `summary_diffs` on disk may omit
// file details and patch text. Required Schema rejected the whole
// session response and broke session loading on Desktop.
file: Schema.optional(Schema.String),
patch: Schema.optional(Schema.String),
additions: Schema.Finite,
deletions: Schema.Finite,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
}).annotate({ identifier: "SnapshotFileDiff" })
export const FileDiff = Info
export type FileDiff = typeof FileDiff.Type
const prune = "7.days"

View file

@ -10,7 +10,6 @@ import { ProjectTable } from "@opencode-ai/core/project/sql"
import type { ProjectV2 } from "@opencode-ai/core/project"
import { Slug } from "@opencode-ai/core/util/slug"
import { errorMessage } from "../util/error"
import { EventV2 } from "@opencode-ai/core/event"
import { GlobalBus } from "@/bus/global"
import { Git } from "@/git"
import { Effect, Layer, Path, Schema, Scope, Context } from "effect"
@ -19,22 +18,9 @@ import { NodePath } from "@effect/platform-node"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { AppProcess } from "@opencode-ai/core/process"
import { InstanceState } from "@/effect/instance-state"
import { WorktreeEvent } from "@opencode-ai/schema/worktree-event"
export const Event = {
Ready: EventV2.define({
type: "worktree.ready",
schema: {
name: Schema.String,
branch: Schema.optional(Schema.String),
},
}),
Failed: EventV2.define({
type: "worktree.failed",
schema: {
message: Schema.String,
},
}),
}
export const Event = WorktreeEvent
export const Info = Schema.Struct({
name: Schema.String,

View file

@ -0,0 +1,24 @@
import { describe, expect, test } from "bun:test"
import { SessionEvent } from "@opencode-ai/core/session/event"
import { EventManifest as SchemaEventManifest } from "@opencode-ai/schema/event-manifest"
import { Todo } from "@/session/todo"
import { EventManifest } from "@/event-manifest"
describe("public event manifest", () => {
test("contains every latest public wire type once", () => {
expect(EventManifest.Definitions).toBe(SchemaEventManifest.Definitions)
expect(EventManifest.Latest).toBe(SchemaEventManifest.Latest)
expect(EventManifest.Durable).toBe(SchemaEventManifest.Durable)
expect(EventManifest.Latest.size).toBe(85)
expect(EventManifest.Latest.get("session.next.step.ended")).toBe(SessionEvent.Step.Ended)
expect(EventManifest.Latest.get("todo.updated")).toBe(Todo.Event.Updated)
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
expect(EventManifest.Latest.has("server.connected")).toBe(true)
expect(EventManifest.Latest.has("global.disposed")).toBe(true)
})
test("contains only the current step settlement versions", () => {
expect(EventManifest.Durable.has("session.next.step.ended.1")).toBe(false)
expect(EventManifest.Durable.get("session.next.step.ended.2")).toBe(SessionEvent.Step.Ended)
})
})