refactor(session): migrate session domain to Effect Schema (#24005)

This commit is contained in:
Kit Langton
2026-04-23 11:30:02 -04:00
committed by GitHub
parent bbf67d0fff
commit 0517ab4695
18 changed files with 812 additions and 442 deletions

View File

@@ -1,191 +1,192 @@
import z from "zod"
import { Schema } from "effect"
import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema"
import { NamedError } from "@opencode-ai/shared/util/error"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AuthError = NamedError.create(
"ProviderAuthError",
z.object({
providerID: z.string(),
message: z.string(),
}),
)
export const ToolCall = z
.object({
state: z.literal("call"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.meta({
ref: "ToolCall",
})
export type ToolCall = z.infer<typeof ToolCall>
export const ToolPartialCall = z
.object({
state: z.literal("partial-call"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.meta({
ref: "ToolPartialCall",
})
export type ToolPartialCall = z.infer<typeof ToolPartialCall>
export const ToolResult = z
.object({
state: z.literal("result"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
result: z.string(),
})
.meta({
ref: "ToolResult",
})
export type ToolResult = z.infer<typeof ToolResult>
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
ref: "ToolInvocation",
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
export const AuthError = namedSchemaError("ProviderAuthError", {
providerID: Schema.String,
message: Schema.String,
})
export type ToolInvocation = z.infer<typeof ToolInvocation>
export const TextPart = z
.object({
type: z.literal("text"),
text: z.string(),
})
.meta({
ref: "TextPart",
})
export type TextPart = z.infer<typeof TextPart>
const AuthErrorEffect = Schema.Struct({
name: Schema.Literal("ProviderAuthError"),
data: Schema.Struct({
providerID: Schema.String,
message: Schema.String,
}),
})
export const ReasoningPart = z
.object({
type: z.literal("reasoning"),
text: z.string(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.meta({
ref: "ReasoningPart",
})
export type ReasoningPart = z.infer<typeof ReasoningPart>
const OutputLengthErrorEffect = Schema.Struct({
name: Schema.Literal("MessageOutputLengthError"),
data: Schema.Struct({}),
})
export const ToolInvocationPart = z
.object({
type: z.literal("tool-invocation"),
toolInvocation: ToolInvocation,
})
.meta({
ref: "ToolInvocationPart",
})
export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
const UnknownErrorEffect = Schema.Struct({
name: Schema.Literal("UnknownError"),
data: Schema.Struct({
message: Schema.String,
}),
})
export const SourceUrlPart = z
.object({
type: z.literal("source-url"),
sourceId: z.string(),
url: z.string(),
title: z.string().optional(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.meta({
ref: "SourceUrlPart",
})
export type SourceUrlPart = z.infer<typeof SourceUrlPart>
export const ToolCall = Schema.Struct({
state: Schema.Literal("call"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolCall = Schema.Schema.Type<typeof ToolCall>
export const FilePart = z
.object({
type: z.literal("file"),
mediaType: z.string(),
filename: z.string().optional(),
url: z.string(),
})
.meta({
ref: "FilePart",
})
export type FilePart = z.infer<typeof FilePart>
export const ToolPartialCall = Schema.Struct({
state: Schema.Literal("partial-call"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolPartialCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolPartialCall = Schema.Schema.Type<typeof ToolPartialCall>
export const StepStartPart = z
.object({
type: z.literal("step-start"),
})
.meta({
ref: "StepStartPart",
})
export type StepStartPart = z.infer<typeof StepStartPart>
export const ToolResult = Schema.Struct({
state: Schema.Literal("result"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
result: Schema.String,
})
.annotate({ identifier: "ToolResult" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolResult = Schema.Schema.Type<typeof ToolResult>
export const MessagePart = z
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
.meta({
ref: "MessagePart",
})
export type MessagePart = z.infer<typeof MessagePart>
export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult])
.annotate({ identifier: "ToolInvocation", discriminator: "state" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolInvocation = Schema.Schema.Type<typeof ToolInvocation>
export const Info = z
.object({
id: z.string(),
role: z.enum(["user", "assistant"]),
parts: z.array(MessagePart),
metadata: z
.object({
time: z.object({
created: z.number(),
completed: z.number().optional(),
export const TextPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
})
.annotate({ identifier: "TextPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
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) })))
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) })))
export type ToolInvocationPart = Schema.Schema.Type<typeof ToolInvocationPart>
export const SourceUrlPart = Schema.Struct({
type: Schema.Literal("source-url"),
sourceId: Schema.String,
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) })))
export type SourceUrlPart = Schema.Schema.Type<typeof SourceUrlPart>
export const FilePart = Schema.Struct({
type: Schema.Literal("file"),
mediaType: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
})
.annotate({ identifier: "FilePart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
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) })))
export type StepStartPart = Schema.Schema.Type<typeof StepStartPart>
export const MessagePart = Schema.Union([
TextPart,
ReasoningPart,
ToolInvocationPart,
SourceUrlPart,
FilePart,
StepStartPart,
])
.annotate({ identifier: "MessagePart", discriminator: "type" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type MessagePart = Schema.Schema.Type<typeof MessagePart>
export const Info = Schema.Struct({
id: Schema.String,
role: Schema.Literals(["user", "assistant"]),
parts: Schema.Array(MessagePart),
metadata: Schema.Struct({
time: Schema.Struct({
created: Schema.Number,
completed: Schema.optional(Schema.Number),
}),
error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])),
sessionID: SessionID,
tool: Schema.Record(
Schema.String,
Schema.StructWithRest(
Schema.Struct({
title: Schema.String,
snapshot: Schema.optional(Schema.String),
time: Schema.Struct({
start: Schema.Number,
end: Schema.Number,
}),
}),
error: z
.discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
.optional(),
sessionID: SessionID.zod,
tool: z.record(
z.string(),
z
.object({
title: z.string(),
snapshot: z.string().optional(),
time: z.object({
start: z.number(),
end: z.number(),
}),
})
.catchall(z.any()),
),
assistant: z
.object({
system: z.string().array(),
modelID: ModelID.zod,
providerID: ProviderID.zod,
path: z.object({
cwd: z.string(),
root: z.string(),
}),
cost: z.number(),
summary: z.boolean().optional(),
tokens: z.object({
input: z.number(),
output: z.number(),
reasoning: z.number(),
cache: z.object({
read: z.number(),
write: z.number(),
}),
}),
})
.optional(),
snapshot: z.string().optional(),
})
.meta({ ref: "MessageMetadata" }),
})
.meta({
ref: "Message",
})
export type Info = z.infer<typeof Info>
[Schema.Record(Schema.String, Schema.Unknown)],
),
),
assistant: Schema.optional(
Schema.Struct({
system: Schema.Array(Schema.String),
modelID: ModelID,
providerID: ProviderID,
path: Schema.Struct({
cwd: Schema.String,
root: Schema.String,
}),
cost: Schema.Number,
summary: Schema.optional(Schema.Boolean),
tokens: Schema.Struct({
input: Schema.Number,
output: Schema.Number,
reasoning: Schema.Number,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
}),
}),
}),
),
snapshot: Schema.optional(Schema.String),
}).annotate({ identifier: "MessageMetadata" }),
})
.annotate({ identifier: "Message" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export * as Message from "./message"

View File

@@ -62,7 +62,7 @@ export function toPartialRow(info: DeepPartial<Session.Info>) {
export default [
SyncEvent.project(Session.Event.Created, (db, data) => {
db.insert(SessionTable).values(Session.toRow(data.info)).run()
db.insert(SessionTable).values(Session.toRow(data.info as Session.Info)).run()
}),
SyncEvent.project(Session.Event.Updated, (db, data) => {

View File

@@ -43,7 +43,9 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Truncate } from "@/tool"
import { decodeDataUrl } from "@/util/data-url"
import { Process } from "@/util"
import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect"
import { Cause, Effect, Exit, Layer, Option, Scope, Context, Schema } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { EffectLogger } from "@/effect"
import { InstanceState } from "@/effect"
import { TaskTool, type TaskPromptOps } from "@/tool/task"
@@ -69,7 +71,7 @@ const elog = EffectLogger.create({ service: "session.prompt" })
export interface Interface {
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
readonly loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts>
readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts>
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
@@ -1532,9 +1534,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
},
)
const loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts> = Effect.fn(
"SessionPrompt.loop",
)(function* (input: z.infer<typeof LoopInput>) {
const loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.loop")(function* (
input: LoopInput,
) {
return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID))
})
@@ -1701,91 +1703,88 @@ export const defaultLayer = Layer.suspend(() =>
),
),
)
export const PromptInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
model: z
.object({
providerID: ProviderID.zod,
modelID: ModelID.zod,
})
.optional(),
agent: z.string().optional(),
noReply: z.boolean().optional(),
tools: z
.record(z.string(), z.boolean())
.optional()
.describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"),
format: MessageV2.Format.zod.optional(),
system: z.string().optional(),
variant: z.string().optional(),
parts: z.array(
z.discriminatedUnion("type", [
MessageV2.TextPartInput.zod as unknown as z.ZodObject<any>,
MessageV2.FilePartInput.zod as unknown as z.ZodObject<any>,
MessageV2.AgentPartInput.zod as unknown as z.ZodObject<any>,
MessageV2.SubtaskPartInput.zod as unknown as z.ZodObject<any>,
]),
),
const ModelRef = Schema.Struct({
providerID: ProviderID,
modelID: ModelID,
})
export const PromptInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
model: Schema.optional(ModelRef),
agent: Schema.optional(Schema.String),
noReply: Schema.optional(Schema.Boolean),
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
description:
"@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
}),
format: Schema.optional(MessageV2.Format),
system: Schema.optional(Schema.String),
variant: Schema.optional(Schema.String),
parts: Schema.Array(
Schema.Union([
MessageV2.TextPartInput,
MessageV2.FilePartInput,
MessageV2.AgentPartInput,
MessageV2.SubtaskPartInput,
]).annotate({ discriminator: "type" }),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
// `z.discriminatedUnion` erases the discriminated members' shapes back to
// `{}` because the derived `.zod` on each input is typed as an opaque
// `z.ZodType`. Restore the precise `parts` type from the exported Schema
// input types so callers see a proper tagged union.
// `{}` when walked from the generic `z.ZodType` input. Restore the precise
// `parts` type from the exported Schema input types so callers see a proper
// tagged union.
type PartInputUnion =
| MessageV2.TextPartInput
| MessageV2.FilePartInput
| MessageV2.AgentPartInput
| MessageV2.SubtaskPartInput
export type PromptInput = Omit<z.infer<typeof PromptInput>, "parts"> & {
export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts"> & {
parts: PartInputUnion[]
}
export const LoopInput = z.object({
sessionID: SessionID.zod,
})
export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
sessionID: SessionID,
}) {
static readonly zod = zod(this)
}
export const ShellInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
agent: z.string(),
model: z
.object({
providerID: ProviderID.zod,
modelID: ModelID.zod,
})
.optional(),
command: z.string(),
})
export type ShellInput = z.infer<typeof ShellInput>
export const ShellInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
agent: Schema.String,
model: Schema.optional(ModelRef),
command: Schema.String,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type ShellInput = Schema.Schema.Type<typeof ShellInput>
export const CommandInput = z.object({
messageID: MessageID.zod.optional(),
sessionID: SessionID.zod,
agent: z.string().optional(),
model: z.string().optional(),
arguments: z.string(),
command: z.string(),
variant: z.string().optional(),
// Inlined (no `.meta({ ref })`) to keep the original SDK output — the
export const CommandInput = Schema.Struct({
messageID: Schema.optional(MessageID),
sessionID: SessionID,
agent: Schema.optional(Schema.String),
model: Schema.optional(Schema.String),
arguments: Schema.String,
command: Schema.String,
variant: Schema.optional(Schema.String),
// Inlined (no identifier annotation) to keep the original SDK output — the
// PromptInput call site below references FilePartInput by ref via the
// Schema export in message-v2.ts.
parts: z
.array(
z.discriminatedUnion("type", [
z.object({
id: PartID.zod.optional(),
type: z.literal("file"),
mime: z.string(),
filename: z.string().optional(),
url: z.string(),
source: MessageV2.FilePartSource.zod.optional(),
parts: Schema.optional(
Schema.Array(
Schema.Union([
Schema.Struct({
id: Schema.optional(PartID),
type: Schema.Literal("file"),
mime: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
source: Schema.optional(MessageV2.FilePartSource),
}),
]),
)
.optional(),
})
export type CommandInput = z.infer<typeof CommandInput>
]).annotate({ discriminator: "type" }),
),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CommandInput = Schema.Schema.Type<typeof CommandInput>
/** @internal Exported for testing */
export function createStructuredOutputTool(input: {

View File

@@ -1,10 +1,11 @@
import z from "zod"
import { Effect, Layer, Context } from "effect"
import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "../bus"
import { Snapshot } from "../snapshot"
import { Storage } from "@/storage"
import { SyncEvent } from "../sync"
import { Log } from "../util"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID, PartID } from "./schema"
@@ -13,12 +14,12 @@ import { SessionSummary } from "./summary"
const log = Log.create({ service: "session.revert" })
export const RevertInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod,
partID: PartID.zod.optional(),
})
export type RevertInput = z.infer<typeof RevertInput>
export const RevertInput = Schema.Struct({
sessionID: SessionID,
messageID: MessageID,
partID: Schema.optional(PartID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type RevertInput = Schema.Schema.Type<typeof RevertInput>
export interface Interface {
readonly revert: (input: RevertInput) => Effect.Effect<Session.Info>

View File

@@ -27,7 +27,9 @@ import { SessionID, MessageID, PartID } from "./schema"
import type { Provider } from "@/provider"
import { Permission } from "@/permission"
import { Global } from "@/global"
import { Effect, Layer, Option, Context } from "effect"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { zod, zodObject } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const log = Log.create({ service: "session" })
@@ -114,91 +116,104 @@ function getForkedTitle(title: string): string {
return `${title} (fork #1)`
}
export const Info = z
.object({
id: SessionID.zod,
slug: z.string(),
projectID: ProjectID.zod,
workspaceID: WorkspaceID.zod.optional(),
directory: z.string(),
parentID: SessionID.zod.optional(),
summary: z
.object({
additions: z.number(),
deletions: z.number(),
files: z.number(),
diffs: Snapshot.FileDiff.zod.array().optional(),
})
.optional(),
share: z
.object({
url: z.string(),
})
.optional(),
title: z.string(),
version: z.string(),
time: z.object({
created: z.number(),
updated: z.number(),
compacting: z.number().optional(),
archived: z.number().optional(),
}),
permission: Permission.Ruleset.zod.optional(),
revert: z
.object({
messageID: MessageID.zod,
partID: PartID.zod.optional(),
snapshot: z.string().optional(),
diff: z.string().optional(),
})
.optional(),
})
.meta({
ref: "Session",
})
export type Info = z.output<typeof Info>
export const ProjectInfo = z
.object({
id: ProjectID.zod,
name: z.string().optional(),
worktree: z.string(),
})
.meta({
ref: "ProjectSummary",
})
export type ProjectInfo = z.output<typeof ProjectInfo>
export const GlobalInfo = Info.extend({
project: ProjectInfo.nullable(),
}).meta({
ref: "GlobalSession",
const Summary = Schema.Struct({
additions: Schema.Number,
deletions: Schema.Number,
files: Schema.Number,
diffs: Schema.optional(Schema.Array(Snapshot.FileDiff)),
})
export type GlobalInfo = z.output<typeof GlobalInfo>
export const CreateInput = z
.object({
parentID: SessionID.zod.optional(),
title: z.string().optional(),
permission: Info.shape.permission,
workspaceID: WorkspaceID.zod.optional(),
})
.optional()
export type CreateInput = z.output<typeof CreateInput>
export const ForkInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() })
export const GetInput = SessionID.zod
export const ChildrenInput = SessionID.zod
export const RemoveInput = SessionID.zod
export const SetTitleInput = z.object({ sessionID: SessionID.zod, title: z.string() })
export const SetArchivedInput = z.object({ sessionID: SessionID.zod, time: z.number().optional() })
export const SetPermissionInput = z.object({ sessionID: SessionID.zod, permission: Permission.Ruleset.zod })
export const SetRevertInput = z.object({
sessionID: SessionID.zod,
revert: Info.shape.revert,
summary: Info.shape.summary,
const Share = Schema.Struct({
url: Schema.String,
})
export const MessagesInput = z.object({ sessionID: SessionID.zod, limit: z.number().optional() })
const Time = Schema.Struct({
created: Schema.Number,
updated: Schema.Number,
compacting: Schema.optional(Schema.Number),
archived: Schema.optional(Schema.Number),
})
const Revert = Schema.Struct({
messageID: MessageID,
partID: Schema.optional(PartID),
snapshot: Schema.optional(Schema.String),
diff: Schema.optional(Schema.String),
})
export const Info = Schema.Struct({
id: SessionID,
slug: Schema.String,
projectID: ProjectID,
workspaceID: Schema.optional(WorkspaceID),
directory: Schema.String,
parentID: Schema.optional(SessionID),
summary: Schema.optional(Summary),
share: Schema.optional(Share),
title: Schema.String,
version: Schema.String,
time: Time,
permission: Schema.optional(Permission.Ruleset),
revert: Schema.optional(Revert),
})
.annotate({ identifier: "Session" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
export const ProjectInfo = Schema.Struct({
id: ProjectID,
name: Schema.optional(Schema.String),
worktree: Schema.String,
})
.annotate({ identifier: "ProjectSummary" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ProjectInfo = Types.DeepMutable<Schema.Schema.Type<typeof ProjectInfo>>
export const GlobalInfo = Schema.Struct({
...Info.fields,
project: Schema.NullOr(ProjectInfo),
})
.annotate({ identifier: "GlobalSession" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type GlobalInfo = Types.DeepMutable<Schema.Schema.Type<typeof GlobalInfo>>
export const CreateInput = Schema.optional(
Schema.Struct({
parentID: Schema.optional(SessionID),
title: Schema.optional(Schema.String),
permission: Schema.optional(Permission.Ruleset),
workspaceID: Schema.optional(WorkspaceID),
}),
).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CreateInput = Types.DeepMutable<Schema.Schema.Type<typeof CreateInput>>
export const ForkInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const GetInput = SessionID
export const ChildrenInput = SessionID
export const RemoveInput = SessionID
export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe(
withStatics((s) => ({ zod: zod(s) })),
)
export const SetArchivedInput = Schema.Struct({
sessionID: SessionID,
time: Schema.optional(Schema.Number),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const SetPermissionInput = Schema.Struct({
sessionID: SessionID,
permission: Permission.Ruleset,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const SetRevertInput = Schema.Struct({
sessionID: SessionID,
revert: Schema.optional(Revert),
summary: Schema.optional(Summary),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const MessagesInput = Schema.Struct({
sessionID: SessionID,
limit: Schema.optional(Schema.Number),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Event = {
Created: SyncEvent.define({
@@ -207,7 +222,7 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Updated: SyncEvent.define({
@@ -216,14 +231,14 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: updateSchema(Info).extend({
share: updateSchema(Info.shape.share.unwrap()).optional(),
time: updateSchema(Info.shape.time).optional(),
info: updateSchema(zodObject(Info)).extend({
share: updateSchema(zodObject(Share)).optional(),
time: updateSchema(zodObject(Time)).optional(),
}),
}),
busSchema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Deleted: SyncEvent.define({
@@ -232,7 +247,7 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Diff: BusEvent.define(

View File

@@ -2,35 +2,35 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { InstanceState } from "@/effect"
import { SessionID } from "./schema"
import { Effect, Layer, Context } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
export const Info = z
.union([
z.object({
type: z.literal("idle"),
}),
z.object({
type: z.literal("retry"),
attempt: z.number(),
message: z.string(),
next: z.number(),
}),
z.object({
type: z.literal("busy"),
}),
])
.meta({
ref: "SessionStatus",
})
export type Info = z.infer<typeof Info>
export const Info = Schema.Union([
Schema.Struct({
type: Schema.Literal("idle"),
}),
Schema.Struct({
type: Schema.Literal("retry"),
attempt: Schema.Number,
message: Schema.String,
next: Schema.Number,
}),
Schema.Struct({
type: Schema.Literal("busy"),
}),
])
.annotate({ identifier: "SessionStatus" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export const Event = {
Status: BusEvent.define(
"session.status",
z.object({
sessionID: SessionID.zod,
status: Info,
status: Info.zod,
}),
),
// deprecated

View File

@@ -1,8 +1,9 @@
import z from "zod"
import { Effect, Layer, Context } from "effect"
import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "@/bus"
import { Snapshot } from "@/snapshot"
import { Storage } from "@/storage"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID } from "./schema"
@@ -155,9 +156,10 @@ export const defaultLayer = Layer.suspend(() =>
),
)
export const DiffInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
})
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

@@ -1,26 +1,30 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { SessionID } from "./schema"
import { Effect, Layer, Context } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
import { Database, eq, asc } from "../storage"
import { TodoTable } from "./session.sql"
export const Info = z
.object({
content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"),
})
.meta({ ref: "Todo" })
export type Info = z.infer<typeof Info>
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" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export const Event = {
Updated: BusEvent.define(
"todo.updated",
z.object({
sessionID: SessionID.zod,
todos: z.array(Info),
todos: z.array(Info.zod),
}),
),
}