refactor(schema): extract shared public schemas (#33571)

This commit is contained in:
Kit Langton 2026-06-24 04:31:06 +02:00 committed by GitHub
commit 516cfe4e09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 2014 additions and 1593 deletions

View file

@ -1,14 +1,14 @@
import { Schema } from "effect"
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
import { ProviderMetadata, ToolContent } from "@opencode-ai/schema/llm"
import { Delivery } from "@opencode-ai/schema/session-delivery"
import { EventV2 } from "../event"
import { ModelV2 } from "../model"
import { NonNegativeInt } from "../schema"
import { V2Schema } from "../v2-schema"
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "../schema"
import { FileAttachment, Prompt } from "./prompt"
import { SessionSchema } from "./schema"
import { Location } from "../location"
import { RelativePath } from "../schema"
import { SessionMessageID } from "./message-id"
import { SessionMessage } from "./message"
export { FileAttachment }
@ -22,14 +22,14 @@ export const Source = Schema.Struct({
export type Source = typeof Source.Type
const Base = {
timestamp: V2Schema.DateTimeUtcFromMillis,
timestamp: DateTimeUtcFromMillis,
sessionID: SessionSchema.ID,
}
const PromptFields = {
...Base,
messageID: SessionMessageID.ID,
prompt: Prompt,
delivery: Schema.Literals(["steer", "queue"]),
delivery: Delivery,
}
const options = {
@ -45,13 +45,8 @@ const stepSettlementOptions = {
},
} as const
export const UnknownError = Schema.Struct({
type: Schema.Literal("unknown"),
message: Schema.String,
}).annotate({
identifier: "Session.Error.Unknown",
})
export type UnknownError = typeof UnknownError.Type
export const UnknownError = SessionMessage.UnknownError
export type UnknownError = SessionMessage.UnknownError
export const AgentSwitched = EventV2.define({
type: "session.next.agent.switched",

View file

@ -2,10 +2,9 @@ export * as SessionInput from "./input"
import { and, asc, eq, isNull, lte } from "drizzle-orm"
import { DateTime, Effect, Schema } from "effect"
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
import type { Database } from "../database/database"
import type { EventV2 } from "../event"
import { NonNegativeInt } from "../schema"
import { V2Schema } from "../v2-schema"
import { SessionEvent } from "./event"
import { SessionMessage } from "./message"
import { Prompt } from "./prompt"
@ -14,24 +13,13 @@ import { SessionInputTable, SessionMessageTable } from "./sql"
type DatabaseService = Database.Interface["db"]
export const Delivery = Schema.Literals(["steer", "queue"])
export type Delivery = typeof Delivery.Type
export class Admitted extends Schema.Class<Admitted>("SessionInput.Admitted")({
admittedSeq: NonNegativeInt,
id: SessionMessage.ID,
sessionID: SessionSchema.ID,
prompt: Prompt,
delivery: Delivery,
timeCreated: V2Schema.DateTimeUtcFromMillis,
promotedSeq: NonNegativeInt.pipe(Schema.optional),
}) {}
export { Admitted, Delivery }
const decodePrompt = Schema.decodeUnknownSync(Prompt)
const encodePrompt = Schema.encodeSync(Prompt)
const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
new Admitted({
Admitted.make({
admittedSeq: row.admitted_seq,
id: SessionMessage.ID.make(row.id),
sessionID: SessionSchema.ID.make(row.session_id),
@ -76,7 +64,7 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
event.durable === undefined
? Effect.die("Prompt admission event is missing aggregate sequence")
: Effect.succeed(
new Admitted({
Admitted.make({
admittedSeq: event.durable.seq,
id: input.id,
sessionID: input.sessionID,

View file

@ -1,13 +1,2 @@
export * as SessionMessageID from "./message-id"
import { Schema } from "effect"
import { withStatics } from "../schema"
import { Identifier } from "../util/identifier"
export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
Schema.brand("Session.Message.ID"),
withStatics((schema) => ({
create: () => schema.make("msg_" + Identifier.ascending()),
})),
)
export type ID = typeof ID.Type
export { ID } from "@opencode-ai/schema/session-message-id"

View file

@ -102,7 +102,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
yield* SessionEvent.All.match(event, {
"session.next.agent.switched": (event) => {
return adapter.appendMessage(
new SessionMessage.AgentSwitched({
SessionMessage.AgentSwitched.make({
id: event.data.messageID,
type: "agent-switched",
metadata: event.metadata,
@ -113,7 +113,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
},
"session.next.model.switched": (event) => {
return adapter.appendMessage(
new SessionMessage.ModelSwitched({
SessionMessage.ModelSwitched.make({
id: event.data.messageID,
type: "model-switched",
metadata: event.metadata,
@ -125,7 +125,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.moved": () => Effect.void,
"session.next.prompted": (event) => {
return adapter.appendMessage(
new SessionMessage.User({
SessionMessage.User.make({
id: event.data.messageID,
type: "user",
metadata: event.metadata,
@ -139,7 +139,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.prompt.admitted": () => Effect.void,
"session.next.context.updated": (event) =>
adapter.appendMessage(
new SessionMessage.System({
SessionMessage.System.make({
id: event.data.messageID,
type: "system",
text: event.data.text,
@ -148,7 +148,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
),
"session.next.synthetic": (event) => {
return adapter.appendMessage(
new SessionMessage.Synthetic({
SessionMessage.Synthetic.make({
sessionID: event.data.sessionID,
text: event.data.text,
id: event.data.messageID,
@ -159,7 +159,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
},
"session.next.shell.started": (event) => {
return adapter.appendMessage(
new SessionMessage.Shell({
SessionMessage.Shell.make({
id: event.data.messageID,
type: "shell",
metadata: event.metadata,
@ -194,7 +194,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
)
}
yield* adapter.appendMessage(
new SessionMessage.Assistant({
SessionMessage.Assistant.make({
id: event.data.assistantMessageID,
type: "assistant",
agent: event.data.agent,
@ -225,7 +225,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.text.started": (event) => {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(new SessionMessage.AssistantText({ type: "text", id: event.data.textID, text: "" })),
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
)
})
},
@ -245,12 +245,12 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(
new SessionMessage.AssistantTool({
SessionMessage.AssistantTool.make({
type: "tool",
id: event.data.callID,
name: event.data.name,
time: { created: event.data.timestamp },
state: new SessionMessage.ToolStatePending({ status: "pending", input: "" }),
state: SessionMessage.ToolStatePending.make({ status: "pending", input: "" }),
}),
),
)
@ -270,7 +270,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
match.provider = event.data.provider
match.time.ran = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateRunning({
SessionMessage.ToolStateRunning.make({
status: "running",
input: event.data.input,
structured: {},
@ -300,7 +300,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
}
match.time.completed = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateCompleted({
SessionMessage.ToolStateCompleted.make({
status: "completed",
input: match.state.input,
structured: event.data.structured,
@ -323,7 +323,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
}
match.time.completed = event.data.timestamp
match.state = castDraft(
new SessionMessage.ToolStateError({
SessionMessage.ToolStateError.make({
status: "error",
error: event.data.error,
input: typeof match.state.input === "string" ? {} : match.state.input,
@ -339,7 +339,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
draft.content.push(
castDraft(
new SessionMessage.AssistantReasoning({
SessionMessage.AssistantReasoning.make({
type: "reasoning",
id: event.data.reasoningID,
text: "",
@ -369,7 +369,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
"session.next.compaction.delta": () => Effect.void,
"session.next.compaction.ended": (event) => {
return adapter.appendMessage(
new SessionMessage.Compaction({
SessionMessage.Compaction.make({
id: event.data.messageID,
type: "compaction",
metadata: event.metadata,

View file

@ -1,193 +1,2 @@
export * as SessionMessage from "./message"
import { Schema } from "effect"
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
import { ModelV2 } from "../model"
import { V2Schema } from "../v2-schema"
import { SessionEvent } from "./event"
import { Prompt } from "./prompt"
import { SessionMessageID } from "./message-id"
export const ID = SessionMessageID.ID
export type ID = typeof ID.Type
const Base = {
id: ID,
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
}),
}
export class AgentSwitched extends Schema.Class<AgentSwitched>("Session.Message.AgentSwitched")({
...Base,
type: Schema.Literal("agent-switched"),
agent: SessionEvent.AgentSwitched.data.fields.agent,
}) {}
export class ModelSwitched extends Schema.Class<ModelSwitched>("Session.Message.ModelSwitched")({
...Base,
type: Schema.Literal("model-switched"),
model: ModelV2.Ref,
}) {}
export class User extends Schema.Class<User>("Session.Message.User")({
...Base,
text: Prompt.fields.text,
files: Prompt.fields.files,
agents: Prompt.fields.agents,
type: Schema.Literal("user"),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
}),
}) {}
export class Synthetic extends Schema.Class<Synthetic>("Session.Message.Synthetic")({
...Base,
sessionID: SessionEvent.Synthetic.data.fields.sessionID,
text: SessionEvent.Synthetic.data.fields.text,
type: Schema.Literal("synthetic"),
}) {}
export class System extends Schema.Class<System>("Session.Message.System")({
...Base,
type: Schema.Literal("system"),
text: SessionEvent.ContextUpdated.data.fields.text,
}) {}
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
...Base,
type: Schema.Literal("shell"),
callID: SessionEvent.Shell.Started.data.fields.callID,
command: SessionEvent.Shell.Started.data.fields.command,
output: Schema.String,
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Message.ToolState.Pending")({
status: Schema.Literal("pending"),
input: Schema.String,
}) {}
export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Message.ToolState.Running")({
status: Schema.Literal("running"),
input: Schema.Record(Schema.String, Schema.Unknown),
structured: Schema.Record(Schema.String, Schema.Any),
content: ToolContent.pipe(Schema.Array),
}) {}
export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Message.ToolState.Completed")({
status: Schema.Literal("completed"),
input: Schema.Record(Schema.String, Schema.Unknown),
attachments: SessionEvent.FileAttachment.pipe(Schema.Array, Schema.optional),
content: ToolContent.pipe(Schema.Array),
outputPaths: SessionEvent.Tool.Success.data.fields.outputPaths,
structured: Schema.Record(Schema.String, Schema.Any),
result: SessionEvent.Tool.Success.data.fields.result,
}) {}
export class ToolStateError extends Schema.Class<ToolStateError>("Session.Message.ToolState.Error")({
status: Schema.Literal("error"),
input: Schema.Record(Schema.String, Schema.Unknown),
content: ToolContent.pipe(Schema.Array),
structured: Schema.Record(Schema.String, Schema.Any),
error: SessionEvent.UnknownError,
result: SessionEvent.Tool.Failed.data.fields.result,
}) {}
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe(
Schema.toTaggedUnion("status"),
)
export type ToolState = Schema.Schema.Type<typeof ToolState>
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Message.Assistant.Tool")({
type: Schema.Literal("tool"),
id: Schema.String,
name: Schema.String,
provider: Schema.Struct({
executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional),
resultMetadata: ProviderMetadata.pipe(Schema.optional),
}).pipe(Schema.optional),
state: ToolState,
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
ran: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
pruned: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class AssistantText extends Schema.Class<AssistantText>("Session.Message.Assistant.Text")({
type: Schema.Literal("text"),
id: Schema.String,
text: Schema.String,
}) {}
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Message.Assistant.Reasoning")({
type: Schema.Literal("reasoning"),
id: Schema.String,
text: Schema.String,
providerMetadata: ProviderMetadata.pipe(Schema.optional),
}) {}
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe(
Schema.toTaggedUnion("type"),
)
export type AssistantContent = Schema.Schema.Type<typeof AssistantContent>
export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistant")({
...Base,
type: Schema.Literal("assistant"),
agent: Schema.String,
model: SessionEvent.Step.Started.data.fields.model,
content: AssistantContent.pipe(Schema.Array),
snapshot: Schema.Struct({
start: Schema.String.pipe(Schema.optional),
end: Schema.String.pipe(Schema.optional),
}).pipe(Schema.optional),
finish: Schema.String.pipe(Schema.optional),
cost: Schema.Finite.pipe(Schema.optional),
tokens: Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
}).pipe(Schema.optional),
error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
}) {}
export class Compaction extends Schema.Class<Compaction>("Session.Message.Compaction")({
type: Schema.Literal("compaction"),
reason: SessionEvent.Compaction.Started.data.fields.reason,
summary: Schema.String,
recent: Schema.String,
...Base,
}) {}
export const Message = Schema.Union([
AgentSwitched,
ModelSwitched,
User,
Synthetic,
System,
Shell,
Assistant,
Compaction,
])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Session.Message" })
export type Message = Schema.Schema.Type<typeof Message>
export type Type = Message["type"]
export * from "@opencode-ai/schema/session-message"

View file

@ -1,46 +1 @@
import * as Schema from "effect/Schema"
export class Source extends Schema.Class<Source>("Prompt.Source")({
start: Schema.Finite,
end: Schema.Finite,
text: Schema.String,
}) {}
export class FileAttachment extends Schema.Class<FileAttachment>("Prompt.FileAttachment")({
uri: Schema.String,
mime: Schema.String,
name: Schema.String.pipe(Schema.optional),
description: Schema.String.pipe(Schema.optional),
source: Source.pipe(Schema.optional),
}) {
static create(input: FileAttachment) {
return new FileAttachment({
uri: input.uri,
mime: input.mime,
name: input.name,
description: input.description,
source: input.source,
})
}
}
export class AgentAttachment extends Schema.Class<AgentAttachment>("Prompt.AgentAttachment")({
name: Schema.String,
source: Source.pipe(Schema.optional),
}) {}
export class Prompt extends Schema.Class<Prompt>("Prompt")({
text: Schema.String,
files: Schema.Array(FileAttachment).pipe(Schema.optional),
agents: Schema.Array(AgentAttachment).pipe(Schema.optional),
}) {
static readonly equivalence = Schema.toEquivalence(Prompt)
static fromUserMessage(input: Pick<Prompt, "text" | "files" | "agents">) {
return new Prompt({
text: input.text,
...(input.files === undefined ? {} : { files: input.files }),
...(input.agents === undefined ? {} : { agents: input.agents }),
})
}
}
export { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"

View file

@ -1,49 +1,9 @@
export * as SessionSchema from "./schema"
import { Schema } from "effect"
import { Location } from "../location"
import { ModelV2 } from "../model"
import { ProjectV2 } from "../project"
import { externalID, type ExternalID, RelativePath, optionalOmitUndefined, withStatics } from "../schema"
import { Identifier } from "../util/identifier"
import { V2Schema } from "../v2-schema"
import { AgentV2 } from "../agent"
import { Session } from "@opencode-ai/schema/session"
export const ID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
Schema.brand("SessionID"),
withStatics((schema) => {
const create = () => schema.make("ses_" + Identifier.descending())
return {
create,
descending: (id?: string) => (id === undefined ? create() : schema.make(id)),
fromExternal: (input: ExternalID) => schema.make(externalID("ses", input)),
}
}),
)
export const ID = Session.ID
export type ID = typeof ID.Type
export class Info extends Schema.Class<Info>("SessionV2.Info")({
id: ID,
parentID: ID.pipe(optionalOmitUndefined),
projectID: ProjectV2.ID,
agent: AgentV2.ID.pipe(Schema.optional),
model: ModelV2.Ref.pipe(Schema.optional),
cost: Schema.Finite,
tokens: Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
}),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
updated: V2Schema.DateTimeUtcFromMillis,
archived: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
}),
title: Schema.String,
location: Location.Ref,
subpath: RelativePath.pipe(Schema.optional),
}) {}
export const Info = Session.Info
export type Info = Session.Info