1
0
Эх сурвалжийг харах

refactor(schema): isolate v1 contracts (#33769)

Kit Langton 1 сар өмнө
parent
commit
a267222080

+ 1 - 18
packages/schema/src/legacy-event.ts

@@ -1,18 +1 @@
-export * as LegacyEvent from "./legacy-event"
-
-import { Schema } from "effect"
-import { define, inventory } from "./event"
-import { SessionID } from "./session-id"
-import { SessionV1 } from "./session-v1"
-
-export const CommandExecuted = define({
-  type: "command.executed",
-  schema: {
-    name: Schema.String,
-    sessionID: SessionID,
-    arguments: Schema.String,
-    messageID: SessionV1.MessageID,
-  },
-})
-
-export const Definitions = inventory(CommandExecuted)
+export * from "./v1/legacy-event"

+ 1 - 66
packages/schema/src/permission-v1.ts

@@ -1,66 +1 @@
-export * as PermissionV1 from "./permission-v1"
-
-import { Schema } from "effect"
-import { define, inventory } from "./event"
-import { ascending } from "./identifier"
-import { Project } from "./project"
-import { statics } from "./schema"
-import { SessionID } from "./session-id"
-
-export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
-  Schema.brand("PermissionID"),
-  statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "per_" + ascending()) })),
-)
-export type ID = typeof ID.Type
-
-export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" })
-export type Action = typeof Action.Type
-
-export const Rule = Schema.Struct({ permission: Schema.String, pattern: Schema.String, action: Action }).annotate({
-  identifier: "PermissionRule",
-})
-export type Rule = typeof Rule.Type
-
-export const Ruleset = Schema.Array(Rule).annotate({ identifier: "PermissionRuleset" })
-export type Ruleset = typeof Ruleset.Type
-
-export const Request = Schema.Struct({
-  id: ID,
-  sessionID: SessionID,
-  permission: Schema.String,
-  patterns: Schema.Array(Schema.String),
-  metadata: Schema.Record(Schema.String, Schema.Unknown),
-  always: Schema.Array(Schema.String),
-  tool: Schema.optional(Schema.Struct({ messageID: Schema.String, callID: Schema.String })),
-}).annotate({ identifier: "PermissionRequest" })
-export type Request = typeof Request.Type
-
-export const Reply = Schema.Literals(["once", "always", "reject"])
-export type Reply = typeof Reply.Type
-
-export const ReplyBody = Schema.Struct({ reply: Reply, message: Schema.optional(Schema.String) }).annotate({
-  identifier: "PermissionReplyBody",
-})
-export type ReplyBody = typeof ReplyBody.Type
-
-export const Approval = Schema.Struct({ projectID: Project.ID, patterns: Schema.Array(Schema.String) }).annotate({
-  identifier: "PermissionApproval",
-})
-export type Approval = typeof Approval.Type
-
-export const AskInput = Schema.Struct({ ...Request.fields, id: Schema.optional(ID), ruleset: Ruleset }).annotate({
-  identifier: "PermissionAskInput",
-})
-export type AskInput = typeof AskInput.Type
-
-export const ReplyInput = Schema.Struct({ requestID: ID, ...ReplyBody.fields }).annotate({
-  identifier: "PermissionReplyInput",
-})
-export type ReplyInput = typeof ReplyInput.Type
-
-const Asked = define({ type: "permission.asked", schema: Request.fields })
-const Replied = define({
-  type: "permission.replied",
-  schema: { sessionID: SessionID, requestID: ID, reply: Reply },
-})
-export const Event = { Asked, Replied, Definitions: inventory(Asked, Replied) }
+export * from "./v1/permission"

+ 1 - 66
packages/schema/src/question-v1.ts

@@ -1,66 +1 @@
-export * as QuestionV1 from "./question-v1"
-
-import { Schema } from "effect"
-import { define, inventory } from "./event"
-import { ascending } from "./identifier"
-import { statics } from "./schema"
-import { SessionID } from "./session-id"
-import { SessionV1 } from "./session-v1"
-
-export const ID = Schema.String.check(Schema.isStartsWith("que")).pipe(
-  Schema.brand("QuestionID"),
-  statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "que_" + ascending()) })),
-)
-
-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" })
-
-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 const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionPrompt" })
-export const Tool = Schema.Struct({ messageID: SessionV1.MessageID, callID: Schema.String }).annotate({
-  identifier: "QuestionTool",
-})
-export const Request = Schema.Struct({
-  id: ID,
-  sessionID: SessionID,
-  questions: Schema.Array(Info).annotate({ description: "Questions to ask" }),
-  tool: Schema.optional(Tool),
-}).annotate({ identifier: "QuestionRequest" })
-export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
-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 const Replied = Schema.Struct({
-  sessionID: SessionID,
-  requestID: ID,
-  answers: Schema.Array(Answer),
-}).annotate({
-  identifier: "QuestionReplied",
-})
-export const Rejected = Schema.Struct({ sessionID: SessionID, requestID: ID }).annotate({
-  identifier: "QuestionRejected",
-})
-
-const Asked = define({ type: "question.asked", schema: Request.fields })
-const RepliedEvent = define({ type: "question.replied", schema: Replied.fields })
-const RejectedEvent = define({ type: "question.rejected", schema: Rejected.fields })
-export const Event = {
-  Asked,
-  Replied: RepliedEvent,
-  Rejected: RejectedEvent,
-  Definitions: inventory(Asked, RepliedEvent, RejectedEvent),
-}
+export * from "./v1/question"

+ 1 - 676
packages/schema/src/session-v1.ts

@@ -1,676 +1 @@
-export * as SessionV1 from "./session-v1"
-
-import { Effect, Schema, Types } from "effect"
-import { define, inventory } from "./event"
-import { FileDiff } from "./file-diff"
-import { PermissionV1 } from "./permission-v1"
-import { Project } from "./project"
-import { Provider } from "./provider"
-import { Model } from "./model"
-import { NonNegativeInt, optional, statics } from "./schema"
-import { ascending } from "./identifier"
-import { SessionID } from "./session-id"
-import { WorkspaceID } from "./workspace-id"
-
-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 extends string, Fields extends Schema.Struct.Fields>(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<typeof APIError.Schema>
-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>("OutputFormatText")({
-  type: Schema.Literal("text"),
-}) {}
-
-export class OutputFormatJsonSchema extends Schema.Class<OutputFormatJsonSchema>("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<typeof Format>
-
-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<Schema.Schema.Type<typeof SnapshotPart>>
-
-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<Schema.Schema.Type<typeof PatchPart>>
-
-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<Schema.Schema.Type<typeof TextPart>>
-
-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<Schema.Schema.Type<typeof ReasoningPart>>
-
-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<Schema.Schema.Type<typeof FilePart>>
-
-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<Schema.Schema.Type<typeof AgentPart>>
-
-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<Schema.Schema.Type<typeof CompactionPart>>
-
-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<Schema.Schema.Type<typeof SubtaskPart>>
-
-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<Types.DeepMutable<Schema.Schema.Type<typeof RetryPart>>, "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<Schema.Schema.Type<typeof StepStartPart>>
-
-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<Schema.Schema.Type<typeof StepFinishPart>>
-
-export const ToolStatePending = Schema.Struct({
-  status: Schema.Literal("pending"),
-  input: Schema.Record(Schema.String, Schema.Any),
-  raw: Schema.String,
-}).annotate({ identifier: "ToolStatePending" })
-export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
-
-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<Schema.Schema.Type<typeof ToolStateRunning>>
-
-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<Schema.Schema.Type<typeof ToolStateCompleted>>
-
-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<Schema.Schema.Type<typeof ToolStateError>>
-
-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<Types.DeepMutable<Schema.Schema.Type<typeof ToolPart>>, "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.Info),
-    }),
-  ),
-  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<Schema.Schema.Type<typeof User>>
-
-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<typeof AssistantErrorSchema>
-
-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<Schema.Schema.Type<typeof TextPartInput>>
-
-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<Schema.Schema.Type<typeof FilePartInput>>
-
-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<Schema.Schema.Type<typeof AgentPartInput>>
-
-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<Schema.Schema.Type<typeof SubtaskPartInput>>
-
-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<Types.DeepMutable<Schema.Schema.Type<typeof Assistant>>, "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.Info)),
-})
-
-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: "Session" })
-export type SessionInfo = typeof SessionInfo.Type
-
-const events = {
-  Created: define({
-    type: "session.created",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      info: SessionInfo,
-    },
-  }),
-  Updated: define({
-    type: "session.updated",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      info: SessionInfo,
-    },
-  }),
-  Deleted: define({
-    type: "session.deleted",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      info: SessionInfo,
-    },
-  }),
-  MessageUpdated: define({
-    type: "message.updated",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      info: Info,
-    },
-  }),
-  MessageRemoved: define({
-    type: "message.removed",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      messageID: MessageID,
-    },
-  }),
-  PartUpdated: define({
-    type: "message.part.updated",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      part: Part,
-      time: Schema.Finite,
-    },
-  }),
-  PartRemoved: define({
-    type: "message.part.removed",
-    ...options,
-    schema: {
-      sessionID: SessionID,
-      messageID: MessageID,
-      partID: PartID,
-    },
-  }),
-}
-
-export const PartDelta = define({
-  type: "message.part.delta",
-  schema: {
-    sessionID: SessionID,
-    messageID: MessageID,
-    partID: PartID,
-    field: Schema.String,
-    delta: Schema.String,
-  },
-})
-
-export const Diff = define({
-  type: "session.diff",
-  schema: {
-    sessionID: SessionID,
-    diff: Schema.Array(FileDiff.Info),
-  },
-})
-
-export const Error = define({
-  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,
-  ),
-}
+export * from "./v1/session"

+ 18 - 0
packages/schema/src/v1/legacy-event.ts

@@ -0,0 +1,18 @@
+export * as LegacyEvent from "./legacy-event"
+
+import { Schema } from "effect"
+import { define, inventory } from "../event"
+import { SessionID } from "../session-id"
+import { SessionV1 } from "./session"
+
+export const CommandExecuted = define({
+  type: "command.executed",
+  schema: {
+    name: Schema.String,
+    sessionID: SessionID,
+    arguments: Schema.String,
+    messageID: SessionV1.MessageID,
+  },
+})
+
+export const Definitions = inventory(CommandExecuted)

+ 66 - 0
packages/schema/src/v1/permission.ts

@@ -0,0 +1,66 @@
+export * as PermissionV1 from "./permission"
+
+import { Schema } from "effect"
+import { define, inventory } from "../event"
+import { ascending } from "../identifier"
+import { Project } from "../project"
+import { statics } from "../schema"
+import { SessionID } from "../session-id"
+
+export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
+  Schema.brand("PermissionID"),
+  statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "per_" + ascending()) })),
+)
+export type ID = typeof ID.Type
+
+export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" })
+export type Action = typeof Action.Type
+
+export const Rule = Schema.Struct({ permission: Schema.String, pattern: Schema.String, action: Action }).annotate({
+  identifier: "PermissionRule",
+})
+export type Rule = typeof Rule.Type
+
+export const Ruleset = Schema.Array(Rule).annotate({ identifier: "PermissionRuleset" })
+export type Ruleset = typeof Ruleset.Type
+
+export const Request = Schema.Struct({
+  id: ID,
+  sessionID: SessionID,
+  permission: Schema.String,
+  patterns: Schema.Array(Schema.String),
+  metadata: Schema.Record(Schema.String, Schema.Unknown),
+  always: Schema.Array(Schema.String),
+  tool: Schema.optional(Schema.Struct({ messageID: Schema.String, callID: Schema.String })),
+}).annotate({ identifier: "PermissionRequest" })
+export type Request = typeof Request.Type
+
+export const Reply = Schema.Literals(["once", "always", "reject"])
+export type Reply = typeof Reply.Type
+
+export const ReplyBody = Schema.Struct({ reply: Reply, message: Schema.optional(Schema.String) }).annotate({
+  identifier: "PermissionReplyBody",
+})
+export type ReplyBody = typeof ReplyBody.Type
+
+export const Approval = Schema.Struct({ projectID: Project.ID, patterns: Schema.Array(Schema.String) }).annotate({
+  identifier: "PermissionApproval",
+})
+export type Approval = typeof Approval.Type
+
+export const AskInput = Schema.Struct({ ...Request.fields, id: Schema.optional(ID), ruleset: Ruleset }).annotate({
+  identifier: "PermissionAskInput",
+})
+export type AskInput = typeof AskInput.Type
+
+export const ReplyInput = Schema.Struct({ requestID: ID, ...ReplyBody.fields }).annotate({
+  identifier: "PermissionReplyInput",
+})
+export type ReplyInput = typeof ReplyInput.Type
+
+const Asked = define({ type: "permission.asked", schema: Request.fields })
+const Replied = define({
+  type: "permission.replied",
+  schema: { sessionID: SessionID, requestID: ID, reply: Reply },
+})
+export const Event = { Asked, Replied, Definitions: inventory(Asked, Replied) }

+ 66 - 0
packages/schema/src/v1/question.ts

@@ -0,0 +1,66 @@
+export * as QuestionV1 from "./question"
+
+import { Schema } from "effect"
+import { define, inventory } from "../event"
+import { ascending } from "../identifier"
+import { statics } from "../schema"
+import { SessionID } from "../session-id"
+import { SessionV1 } from "./session"
+
+export const ID = Schema.String.check(Schema.isStartsWith("que")).pipe(
+  Schema.brand("QuestionID"),
+  statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "que_" + ascending()) })),
+)
+
+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" })
+
+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 const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionPrompt" })
+export const Tool = Schema.Struct({ messageID: SessionV1.MessageID, callID: Schema.String }).annotate({
+  identifier: "QuestionTool",
+})
+export const Request = Schema.Struct({
+  id: ID,
+  sessionID: SessionID,
+  questions: Schema.Array(Info).annotate({ description: "Questions to ask" }),
+  tool: Schema.optional(Tool),
+}).annotate({ identifier: "QuestionRequest" })
+export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
+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 const Replied = Schema.Struct({
+  sessionID: SessionID,
+  requestID: ID,
+  answers: Schema.Array(Answer),
+}).annotate({
+  identifier: "QuestionReplied",
+})
+export const Rejected = Schema.Struct({ sessionID: SessionID, requestID: ID }).annotate({
+  identifier: "QuestionRejected",
+})
+
+const Asked = define({ type: "question.asked", schema: Request.fields })
+const RepliedEvent = define({ type: "question.replied", schema: Replied.fields })
+const RejectedEvent = define({ type: "question.rejected", schema: Rejected.fields })
+export const Event = {
+  Asked,
+  Replied: RepliedEvent,
+  Rejected: RejectedEvent,
+  Definitions: inventory(Asked, RepliedEvent, RejectedEvent),
+}

+ 676 - 0
packages/schema/src/v1/session.ts

@@ -0,0 +1,676 @@
+export * as SessionV1 from "./session"
+
+import { Effect, Schema, Types } from "effect"
+import { define, inventory } from "../event"
+import { FileDiff } from "../file-diff"
+import { Project } from "../project"
+import { Provider } from "../provider"
+import { Model } from "../model"
+import { NonNegativeInt, optional, statics } from "../schema"
+import { ascending } from "../identifier"
+import { SessionID } from "../session-id"
+import { WorkspaceID } from "../workspace-id"
+import { PermissionV1 } from "./permission"
+
+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 extends string, Fields extends Schema.Struct.Fields>(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<typeof APIError.Schema>
+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>("OutputFormatText")({
+  type: Schema.Literal("text"),
+}) {}
+
+export class OutputFormatJsonSchema extends Schema.Class<OutputFormatJsonSchema>("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<typeof Format>
+
+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<Schema.Schema.Type<typeof SnapshotPart>>
+
+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<Schema.Schema.Type<typeof PatchPart>>
+
+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<Schema.Schema.Type<typeof TextPart>>
+
+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<Schema.Schema.Type<typeof ReasoningPart>>
+
+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<Schema.Schema.Type<typeof FilePart>>
+
+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<Schema.Schema.Type<typeof AgentPart>>
+
+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<Schema.Schema.Type<typeof CompactionPart>>
+
+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<Schema.Schema.Type<typeof SubtaskPart>>
+
+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<Types.DeepMutable<Schema.Schema.Type<typeof RetryPart>>, "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<Schema.Schema.Type<typeof StepStartPart>>
+
+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<Schema.Schema.Type<typeof StepFinishPart>>
+
+export const ToolStatePending = Schema.Struct({
+  status: Schema.Literal("pending"),
+  input: Schema.Record(Schema.String, Schema.Any),
+  raw: Schema.String,
+}).annotate({ identifier: "ToolStatePending" })
+export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
+
+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<Schema.Schema.Type<typeof ToolStateRunning>>
+
+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<Schema.Schema.Type<typeof ToolStateCompleted>>
+
+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<Schema.Schema.Type<typeof ToolStateError>>
+
+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<Types.DeepMutable<Schema.Schema.Type<typeof ToolPart>>, "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.Info),
+    }),
+  ),
+  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<Schema.Schema.Type<typeof User>>
+
+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<typeof AssistantErrorSchema>
+
+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<Schema.Schema.Type<typeof TextPartInput>>
+
+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<Schema.Schema.Type<typeof FilePartInput>>
+
+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<Schema.Schema.Type<typeof AgentPartInput>>
+
+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<Schema.Schema.Type<typeof SubtaskPartInput>>
+
+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<Types.DeepMutable<Schema.Schema.Type<typeof Assistant>>, "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.Info)),
+})
+
+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: "Session" })
+export type SessionInfo = typeof SessionInfo.Type
+
+const events = {
+  Created: define({
+    type: "session.created",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      info: SessionInfo,
+    },
+  }),
+  Updated: define({
+    type: "session.updated",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      info: SessionInfo,
+    },
+  }),
+  Deleted: define({
+    type: "session.deleted",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      info: SessionInfo,
+    },
+  }),
+  MessageUpdated: define({
+    type: "message.updated",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      info: Info,
+    },
+  }),
+  MessageRemoved: define({
+    type: "message.removed",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      messageID: MessageID,
+    },
+  }),
+  PartUpdated: define({
+    type: "message.part.updated",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      part: Part,
+      time: Schema.Finite,
+    },
+  }),
+  PartRemoved: define({
+    type: "message.part.removed",
+    ...options,
+    schema: {
+      sessionID: SessionID,
+      messageID: MessageID,
+      partID: PartID,
+    },
+  }),
+}
+
+export const PartDelta = define({
+  type: "message.part.delta",
+  schema: {
+    sessionID: SessionID,
+    messageID: MessageID,
+    partID: PartID,
+    field: Schema.String,
+    delta: Schema.String,
+  },
+})
+
+export const Diff = define({
+  type: "session.diff",
+  schema: {
+    sessionID: SessionID,
+    diff: Schema.Array(FileDiff.Info),
+  },
+})
+
+export const Error = define({
+  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,
+  ),
+}

+ 28 - 0
packages/schema/test/v1-isolation.test.ts

@@ -0,0 +1,28 @@
+import { expect, test } from "bun:test"
+import { LegacyEvent } from "../src/legacy-event"
+import { PermissionV1 } from "../src/permission-v1"
+import { QuestionV1 } from "../src/question-v1"
+import { SessionV1 } from "../src/session-v1"
+import { LegacyEvent as IsolatedLegacyEvent } from "../src/v1/legacy-event"
+import { PermissionV1 as IsolatedPermissionV1 } from "../src/v1/permission"
+import { QuestionV1 as IsolatedQuestionV1 } from "../src/v1/question"
+import { SessionV1 as IsolatedSessionV1 } from "../src/v1/session"
+
+test("compatibility entrypoints preserve isolated V1 schema identity", () => {
+  expect(LegacyEvent).toBe(IsolatedLegacyEvent)
+  expect(PermissionV1).toBe(IsolatedPermissionV1)
+  expect(QuestionV1).toBe(IsolatedQuestionV1)
+  expect(SessionV1).toBe(IsolatedSessionV1)
+})
+
+test("current source does not import the V1 subtree directly", async () => {
+  const allowed = new Set(["legacy-event.ts", "permission-v1.ts", "question-v1.ts", "session-v1.ts"])
+  const files = [...new Bun.Glob("*.ts").scanSync(new URL("../src", import.meta.url).pathname)].filter(
+    (file) => !allowed.has(file),
+  )
+  const directImports = await Promise.all(
+    files.map(async (file) => ({ file, source: await Bun.file(new URL(`../src/${file}`, import.meta.url)).text() })),
+  ).then((values) => values.filter((value) => value.source.includes('from "./v1/')))
+
+  expect(directImports).toEqual([])
+})