export * as SessionV1 from "./session.js" import { Effect, Schema, Types } from "effect" import { durable, ephemeral, inventory } from "../event.js" import { Project } from "../project.js" import { Provider } from "../provider.js" import { Model } from "../model.js" import { NonNegativeInt, optional, statics } from "../schema.js" import { ascending } from "../identifier.js" import { SessionID } from "../session-id.js" import { WorkspaceID } from "../workspace-id.js" import { PermissionV1 } from "./permission.js" import { FileDiff } from "../file-diff.js" const Timestamp = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)) export const MessageID = Schema.String.check(Schema.isStartsWith("msg")).pipe( Schema.brand("MessageID"), statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "msg_" + ascending()) })), ) export type MessageID = typeof MessageID.Type export const PartID = Schema.String.check(Schema.isStartsWith("prt")).pipe( Schema.brand("PartID"), statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "prt_" + ascending()) })), ) export type PartID = typeof PartID.Type const namedError = (name: Name, fields: Fields) => { const schema = Schema.Struct({ name: Schema.Literal(name), data: Schema.Struct(fields) }).annotate({ identifier: name, }) return { Schema: schema, EffectSchema: schema } } export const OutputLengthError = namedError("MessageOutputLengthError", {}) export const AuthError = namedError("ProviderAuthError", { providerID: Schema.String, message: Schema.String, }) export const AbortedError = namedError("MessageAbortedError", { message: Schema.String }) export const StructuredOutputError = namedError("StructuredOutputError", { message: Schema.String, retries: NonNegativeInt, }) export const APIError = namedError("APIError", { message: Schema.String, statusCode: Schema.optional(NonNegativeInt), isRetryable: Schema.Boolean, responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)), responseBody: Schema.optional(Schema.String), metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) export type APIError = Schema.Schema.Type export const ContextOverflowError = namedError("ContextOverflowError", { message: Schema.String, responseBody: Schema.optional(Schema.String), }) export const ContentFilterError = namedError("ContentFilterError", { message: Schema.String, }) export class OutputFormatText extends Schema.Class("OutputFormatText")({ type: Schema.Literal("text"), }) {} export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), }) {} export const Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ discriminator: "type", identifier: "OutputFormat", }) export type OutputFormat = Schema.Schema.Type const partBase = { id: PartID, sessionID: SessionID, messageID: MessageID, } export const SnapshotPart = Schema.Struct({ ...partBase, type: Schema.Literal("snapshot"), snapshot: Schema.String, }).annotate({ identifier: "SnapshotPart" }) export type SnapshotPart = Types.DeepMutable> export const PatchPart = Schema.Struct({ ...partBase, type: Schema.Literal("patch"), hash: Schema.String, files: Schema.Array(Schema.String), }).annotate({ identifier: "PatchPart" }) export type PatchPart = Types.DeepMutable> export const TextPart = Schema.Struct({ ...partBase, type: Schema.Literal("text"), text: Schema.String, synthetic: Schema.optional(Schema.Boolean), ignored: Schema.optional(Schema.Boolean), time: Schema.optional( Schema.Struct({ start: NonNegativeInt, end: Schema.optional(NonNegativeInt), }), ), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), }).annotate({ identifier: "TextPart" }) export type TextPart = Types.DeepMutable> export const ReasoningPart = Schema.Struct({ ...partBase, type: Schema.Literal("reasoning"), text: Schema.String, metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), time: Schema.Struct({ start: NonNegativeInt, end: Schema.optional(NonNegativeInt), }), }).annotate({ identifier: "ReasoningPart" }) export type ReasoningPart = Types.DeepMutable> const filePartSourceBase = { text: Schema.Struct({ value: Schema.String, start: Schema.Finite, end: Schema.Finite, }).annotate({ identifier: "FilePartSourceText" }), } export const Range = Schema.Struct({ start: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }), end: Schema.Struct({ line: NonNegativeInt, character: NonNegativeInt }), }).annotate({ identifier: "Range" }) export type Range = typeof Range.Type export const FileSource = Schema.Struct({ ...filePartSourceBase, type: Schema.Literal("file"), path: Schema.String, }).annotate({ identifier: "FileSource" }) export const SymbolSource = Schema.Struct({ ...filePartSourceBase, type: Schema.Literal("symbol"), path: Schema.String, range: Range, name: Schema.String, kind: NonNegativeInt, }).annotate({ identifier: "SymbolSource" }) export const ResourceSource = Schema.Struct({ ...filePartSourceBase, type: Schema.Literal("resource"), clientName: Schema.String, uri: Schema.String, }).annotate({ identifier: "ResourceSource" }) export const FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({ discriminator: "type", identifier: "FilePartSource", }) export const FilePart = Schema.Struct({ ...partBase, type: Schema.Literal("file"), mime: Schema.String, filename: Schema.optional(Schema.String), url: Schema.String, source: Schema.optional(FilePartSource), }).annotate({ identifier: "FilePart" }) export type FilePart = Types.DeepMutable> export const AgentPart = Schema.Struct({ ...partBase, type: Schema.Literal("agent"), name: Schema.String, source: Schema.optional( Schema.Struct({ value: Schema.String, start: NonNegativeInt, end: NonNegativeInt, }), ), }).annotate({ identifier: "AgentPart" }) export type AgentPart = Types.DeepMutable> export const CompactionPart = Schema.Struct({ ...partBase, type: Schema.Literal("compaction"), auto: Schema.Boolean, overflow: Schema.optional(Schema.Boolean), tail_start_id: Schema.optional(MessageID), }).annotate({ identifier: "CompactionPart" }) export type CompactionPart = Types.DeepMutable> export const SubtaskPart = Schema.Struct({ ...partBase, type: Schema.Literal("subtask"), prompt: Schema.String, description: Schema.String, agent: Schema.String, model: Schema.optional( Schema.Struct({ providerID: Provider.ID, modelID: Model.ID, }), ), command: Schema.optional(Schema.String), }).annotate({ identifier: "SubtaskPart" }) export type SubtaskPart = Types.DeepMutable> export const RetryPart = Schema.Struct({ ...partBase, type: Schema.Literal("retry"), attempt: NonNegativeInt, error: APIError.EffectSchema, time: Schema.Struct({ created: NonNegativeInt, }), }).annotate({ identifier: "RetryPart" }) export type RetryPart = Omit>, "error"> & { error: APIError } export const StepStartPart = Schema.Struct({ ...partBase, type: Schema.Literal("step-start"), snapshot: Schema.optional(Schema.String), }).annotate({ identifier: "StepStartPart" }) export type StepStartPart = Types.DeepMutable> export const StepFinishPart = Schema.Struct({ ...partBase, type: Schema.Literal("step-finish"), reason: Schema.String, snapshot: Schema.optional(Schema.String), cost: Schema.Finite, tokens: Schema.Struct({ total: Schema.optional(Schema.Finite), input: Schema.Finite, output: Schema.Finite, reasoning: Schema.Finite, cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite, }), }), }).annotate({ identifier: "StepFinishPart" }) export type StepFinishPart = Types.DeepMutable> export const ToolStatePending = Schema.Struct({ status: Schema.Literal("pending"), input: Schema.Record(Schema.String, Schema.Any), raw: Schema.String, }).annotate({ identifier: "ToolStatePending" }) export type ToolStatePending = Types.DeepMutable> export const ToolStateRunning = Schema.Struct({ status: Schema.Literal("running"), input: Schema.Record(Schema.String, Schema.Any), title: Schema.optional(Schema.String), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), time: Schema.Struct({ start: NonNegativeInt, }), }).annotate({ identifier: "ToolStateRunning" }) export type ToolStateRunning = Types.DeepMutable> export const ToolStateCompleted = Schema.Struct({ status: Schema.Literal("completed"), input: Schema.Record(Schema.String, Schema.Any), output: Schema.String, title: Schema.String, metadata: Schema.Record(Schema.String, Schema.Any), time: Schema.Struct({ start: NonNegativeInt, end: NonNegativeInt, compacted: Schema.optional(NonNegativeInt), }), attachments: Schema.optional(Schema.Array(FilePart)), }).annotate({ identifier: "ToolStateCompleted" }) export type ToolStateCompleted = Types.DeepMutable> export const ToolStateError = Schema.Struct({ status: Schema.Literal("error"), input: Schema.Record(Schema.String, Schema.Any), error: Schema.String, metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), time: Schema.Struct({ start: NonNegativeInt, end: NonNegativeInt, }), }).annotate({ identifier: "ToolStateError" }) export type ToolStateError = Types.DeepMutable> export const ToolState = Schema.Union([ ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError, ]).annotate({ discriminator: "status", identifier: "ToolState", }) export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError export const ToolPart = Schema.Struct({ ...partBase, type: Schema.Literal("tool"), callID: Schema.String, tool: Schema.String, state: ToolState, metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), }).annotate({ identifier: "ToolPart" }) export type ToolPart = Omit>, "state"> & { state: ToolState } const messageBase = { id: MessageID, sessionID: partBase.sessionID, } export const User = Schema.Struct({ ...messageBase, role: Schema.Literal("user"), time: Schema.Struct({ created: Timestamp, }), format: Schema.optional(Format), summary: Schema.optional( Schema.Struct({ title: Schema.optional(Schema.String), body: Schema.optional(Schema.String), diffs: Schema.Array(FileDiff.LegacyInfo), }), ), agent: Schema.String, model: Schema.Struct({ providerID: Provider.ID, modelID: Model.ID, variant: Schema.optional(Schema.String), }), system: Schema.optional(Schema.String), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), }).annotate({ identifier: "UserMessage" }) export type User = Types.DeepMutable> export const Part = Schema.Union([ TextPart, SubtaskPart, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart, ]).annotate({ discriminator: "type", identifier: "Part" }) export type Part = | TextPart | SubtaskPart | ReasoningPart | FilePart | ToolPart | StepStartPart | StepFinishPart | SnapshotPart | PatchPart | AgentPart | RetryPart | CompactionPart const AssistantErrorSchema = Schema.Union([ AuthError.EffectSchema, namedError("UnknownError", { message: Schema.String, ref: Schema.optional(Schema.String) }).EffectSchema, OutputLengthError.EffectSchema, AbortedError.EffectSchema, StructuredOutputError.EffectSchema, ContextOverflowError.EffectSchema, ContentFilterError.EffectSchema, APIError.EffectSchema, ]).annotate({ discriminator: "name" }) type AssistantError = Schema.Schema.Type export const TextPartInput = Schema.Struct({ id: Schema.optional(PartID), type: Schema.Literal("text"), text: Schema.String, synthetic: Schema.optional(Schema.Boolean), ignored: Schema.optional(Schema.Boolean), time: Schema.optional( Schema.Struct({ start: NonNegativeInt, end: Schema.optional(NonNegativeInt), }), ), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), }).annotate({ identifier: "TextPartInput" }) export type TextPartInput = Types.DeepMutable> export const FilePartInput = Schema.Struct({ id: Schema.optional(PartID), type: Schema.Literal("file"), mime: Schema.String, filename: Schema.optional(Schema.String), url: Schema.String, source: Schema.optional(FilePartSource), }).annotate({ identifier: "FilePartInput" }) export type FilePartInput = Types.DeepMutable> export const AgentPartInput = Schema.Struct({ id: Schema.optional(PartID), type: Schema.Literal("agent"), name: Schema.String, source: Schema.optional( Schema.Struct({ value: Schema.String, start: NonNegativeInt, end: NonNegativeInt, }), ), }).annotate({ identifier: "AgentPartInput" }) export type AgentPartInput = Types.DeepMutable> export const SubtaskPartInput = Schema.Struct({ id: Schema.optional(PartID), type: Schema.Literal("subtask"), prompt: Schema.String, description: Schema.String, agent: Schema.String, model: Schema.optional( Schema.Struct({ providerID: Provider.ID, modelID: Model.ID, }), ), command: Schema.optional(Schema.String), }).annotate({ identifier: "SubtaskPartInput" }) export type SubtaskPartInput = Types.DeepMutable> export const Assistant = Schema.Struct({ ...messageBase, role: Schema.Literal("assistant"), time: Schema.Struct({ created: NonNegativeInt, completed: Schema.optional(NonNegativeInt), }), error: Schema.optional(AssistantErrorSchema), parentID: MessageID, modelID: Model.ID, providerID: Provider.ID, mode: Schema.String, agent: Schema.String, path: Schema.Struct({ cwd: Schema.String, root: Schema.String, }), summary: Schema.optional(Schema.Boolean), cost: Schema.Finite, tokens: Schema.Struct({ total: Schema.optional(Schema.Finite), input: Schema.Finite, output: Schema.Finite, reasoning: Schema.Finite, cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite, }), }), structured: Schema.optional(Schema.Any), variant: Schema.optional(Schema.String), finish: Schema.optional(Schema.String), }).annotate({ identifier: "AssistantMessage" }) export type Assistant = Omit>, "error"> & { error?: AssistantError } export const Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" }) export type Info = User | Assistant export const WithParts = Schema.Struct({ info: Info, parts: Schema.Array(Part), }) export type WithParts = { info: Info parts: Part[] } const options = { durable: { aggregate: "sessionID", version: 1, }, } as const const SessionSummary = Schema.Struct({ additions: Schema.Finite, deletions: Schema.Finite, files: Schema.Finite, diffs: optional(Schema.Array(FileDiff.LegacyInfo)), }) const SessionTokens = Schema.Struct({ input: Schema.Finite, output: Schema.Finite, reasoning: Schema.Finite, cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite, }), }) const SessionShare = Schema.Struct({ url: Schema.String, }) const SessionRevert = Schema.Struct({ messageID: MessageID, partID: optional(PartID), snapshot: optional(Schema.String), diff: optional(Schema.String), }) const SessionModel = Schema.Struct({ id: Model.ID, providerID: Provider.ID, variant: optional(Schema.String), }) export const SessionInfo = Schema.Struct({ id: SessionID, slug: Schema.String, projectID: Project.ID, workspaceID: optional(WorkspaceID), directory: Schema.String, path: optional(Schema.String), parentID: optional(SessionID), summary: optional(SessionSummary), cost: optional(Schema.Finite), tokens: optional(SessionTokens), share: optional(SessionShare), title: Schema.String, agent: optional(Schema.String), model: optional(SessionModel), version: Schema.String, metadata: optional(Schema.Record(Schema.String, Schema.Any)), time: Schema.Struct({ created: NonNegativeInt, updated: NonNegativeInt, compacting: optional(NonNegativeInt), archived: optional(Schema.Finite), }), permission: optional(PermissionV1.Ruleset), revert: optional(SessionRevert), }).annotate({ identifier: "SessionV1.Info" }) export type SessionInfo = typeof SessionInfo.Type const events = { Created: durable({ type: "session.created", ...options, schema: { sessionID: SessionID, info: SessionInfo, }, }), Updated: durable({ type: "session.updated", ...options, schema: { sessionID: SessionID, info: SessionInfo, }, }), Deleted: durable({ type: "session.deleted", ...options, schema: { sessionID: SessionID, info: SessionInfo, }, }), MessageUpdated: durable({ type: "message.updated", ...options, schema: { sessionID: SessionID, info: Info, }, }), MessageRemoved: durable({ type: "message.removed", ...options, schema: { sessionID: SessionID, messageID: MessageID, }, }), PartUpdated: durable({ type: "message.part.updated", ...options, schema: { sessionID: SessionID, part: Part, time: Schema.Finite, }, }), PartRemoved: durable({ type: "message.part.removed", ...options, schema: { sessionID: SessionID, messageID: MessageID, partID: PartID, }, }), } export const PartDelta = ephemeral({ type: "message.part.delta", schema: { sessionID: SessionID, messageID: MessageID, partID: PartID, field: Schema.String, delta: Schema.String, }, }) export const Diff = ephemeral({ type: "session.diff", schema: { sessionID: SessionID, diff: Schema.Array(FileDiff.LegacyInfo), }, }) export const Error = ephemeral({ type: "session.error", schema: { sessionID: Schema.optional(SessionID), error: Assistant.fields.error, }, }) export const Event = { ...events, PartDelta, Diff, Error, Definitions: inventory( events.Created, events.Updated, events.Deleted, events.MessageUpdated, events.MessageRemoved, events.PartUpdated, events.PartRemoved, PartDelta, Diff, Error, ), }