Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231import { Schema, SchemaGetter } from "effect"import { checksum } from "@opencode/util/encode"import { SessionMessage } from "@opencode/schema/session-message"import { Skill } from "@opencode/schema/skill"import { Persistence } from "@/runtime/persistence/schema"import { FileSelection, SelectedLineRange } from "@/workspaces/files/types"
const PartBase = { content: Schema.String, start: Schema.Number, end: Schema.Number,}
const SourceText = Schema.Struct({ value: Schema.String, start: Schema.Number, end: Schema.Number })const Position = Schema.Struct({ line: Schema.Number, character: Schema.Number })const FilePartSource = Schema.Union([ Schema.Struct({ type: Schema.Literal("file"), text: SourceText, path: Schema.String }), Schema.Struct({ type: Schema.Literal("symbol"), text: SourceText, path: Schema.String, range: Schema.Struct({ start: Position, end: Position }), name: Schema.String, kind: Schema.Number, }), Schema.Struct({ type: Schema.Literal("resource"), text: SourceText, clientName: Schema.String, uri: Schema.String }),])
export const TextPart = Persistence.struct({ type: Schema.Literal("text"), ...PartBase })export type TextPart = typeof TextPart.Type
export const FileAttachmentPart = Persistence.struct({ type: Schema.Literal("file"), ...PartBase, path: Schema.String, selection: Persistence.optional(FileSelection), mime: Persistence.optional(Schema.String), filename: Persistence.optional(Schema.String), url: Persistence.optional(Schema.String), source: Persistence.optional(FilePartSource),})export type FileAttachmentPart = typeof FileAttachmentPart.Type
export const AgentPart = Persistence.struct({ type: Schema.Literal("agent"), ...PartBase, name: Schema.String })export type AgentPart = typeof AgentPart.Type
export const SkillPart = Persistence.struct({ type: Schema.Literal("skill"), ...PartBase, id: Skill.ID, name: Skill.Name,})export type SkillPart = typeof SkillPart.Type
const ImageFields = { type: Schema.Literal("image"), id: Schema.String, filename: Schema.String, sourcePath: Persistence.optional(Schema.String), mime: Schema.String,}const Image = Persistence.struct({ ...ImageFields, // An empty URL is an image whose bytes are still in the draft store; see `resolveBlobUrl`. blob: Schema.Struct({ id: Schema.NonEmptyString, url: Schema.String.check(Schema.isPattern(/^(blob:|data:|$)/)) }),})
// Draft storage keeps content-addressed blobs in the store until an image is shown or sent; a// reference without a URL resolves through `resolveBlobUrl`. Legacy inline data remains usable.export const ImageAttachmentPart = Schema.Struct({ ...ImageFields, blob: Persistence.optional( Schema.Struct({ id: Persistence.optional(Schema.String), url: Persistence.optional(Schema.String) }), ), dataUrl: Persistence.optional(Schema.String),}).pipe( Schema.decodeTo(Schema.toType(Image), { decode: SchemaGetter.transform((value) => { const id = value.blob?.id ?? value.dataUrl ?? "" const url = value.blob?.url return { type: value.type, id: value.id, filename: value.filename, sourcePath: value.sourcePath, mime: value.mime, blob: { id, url: url?.startsWith("blob:") || url?.startsWith("data:") ? url : id.startsWith("data:") ? id : "", }, } }), encode: SchemaGetter.transform((value) => value), }),)export type ImageAttachmentPart = typeof ImageAttachmentPart.Type
// A file the model receives as a path on the server: its bytes never enter the draft store.export const PathAttachmentPart = Persistence.struct({ type: Schema.Literal("path"), id: Schema.String, filename: Schema.String, mime: Schema.String, path: Schema.String,})export type PathAttachmentPart = typeof PathAttachmentPart.Type
export const ContentPart = Schema.Union([ TextPart, FileAttachmentPart, AgentPart, SkillPart, ImageAttachmentPart, PathAttachmentPart,])export type ContentPart = typeof ContentPart.Typeexport const Prompt = Persistence.array(ContentPart)export type Prompt = typeof Prompt.Type
export const PromptModel = Persistence.struct({ providerID: Schema.String, modelID: Schema.String, variant: Persistence.optional(Schema.NullOr(Schema.String)),})export type PromptModel = typeof PromptModel.Type
export const FileContextItem = Persistence.struct({ type: Schema.Literal("file"), path: Schema.String, selection: Persistence.optional(FileSelection), comment: Persistence.optional(Schema.String), commentID: Persistence.optional(Schema.String), commentOrigin: Persistence.optional(Schema.Literals(["review", "file"])), preview: Persistence.optional(Schema.String),})export type FileContextItem = typeof FileContextItem.Typeexport type ContextItem = FileContextItem
export function contextItemKey(item: ContextItem) { const key = `${item.type}:${item.path}:${item.selection?.startLine}:${item.selection?.endLine}` if (item.commentID) return `${key}:c=${item.commentID}` const comment = item.comment?.trim() if (!comment) return key const digest = checksum(comment) ?? comment return `${key}:c=${digest.slice(0, 8)}`}
const ContextEntry = Schema.Struct({ ...FileContextItem.fields, key: Persistence.optional(Schema.String) }).pipe( Schema.decodeTo(Persistence.struct({ ...FileContextItem.fields, key: Schema.String }).pipe(Schema.toType), { decode: SchemaGetter.transform((item) => ({ ...item, key: contextItemKey(item) })), encode: SchemaGetter.transform((item) => item), }),)
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export const ComposerStore = Persistence.struct({ prompt: Prompt.pipe( Schema.decode({ decode: SchemaGetter.transform((prompt) => prompt.length ? prompt : DEFAULT_PROMPT.map((part) => ({ ...part })), ), encode: SchemaGetter.transform((prompt) => prompt), }), ), cursor: Persistence.optional( Schema.Finite.pipe( Schema.decode({ decode: SchemaGetter.transform((cursor) => Math.max(0, cursor)), encode: SchemaGetter.transform((cursor) => cursor), }), ), ), model: Persistence.optional(PromptModel), mode: Persistence.optional(Schema.Literals(["normal", "shell"])), retry: Persistence.optional( Schema.Struct({ id: SessionMessage.ID, agent: Schema.String, providerID: Schema.String, modelID: Schema.String, variant: Persistence.optional(Schema.String), }), ), context: Persistence.struct({ items: Persistence.array(ContextEntry) }),})export type ComposerStore = typeof ComposerStore.Type
export const LineComment = Persistence.struct({ id: Schema.String, file: Schema.String, selection: SelectedLineRange, comment: Schema.String, time: Schema.Number,})export type LineComment = typeof LineComment.Type
export const CommentStore = Persistence.struct({ comments: Schema.Record(Schema.String, Schema.mutableKey(Persistence.array(LineComment))),})export type CommentStore = typeof CommentStore.Type
export const PromptHistoryComment = Persistence.struct({ id: Schema.String, path: Schema.String, selection: SelectedLineRange, comment: Schema.String, time: Schema.Number, origin: Persistence.optional(Schema.Literals(["review", "file"])), preview: Persistence.optional(Schema.String),})export type PromptHistoryComment = typeof PromptHistoryComment.Type
// History entries require a prompt array; only its individual parts recover.const HistoryPrompt = Schema.Array(Persistence.fallback(Schema.UndefinedOr(ContentPart), () => undefined)).pipe( Schema.decodeTo(Schema.toType(Prompt), { decode: SchemaGetter.transform((parts) => parts.filter((part) => part !== undefined)), encode: SchemaGetter.transform((parts) => parts), }),)const HistoryEntry = Schema.Struct({ prompt: HistoryPrompt, comments: Persistence.array(PromptHistoryComment) })export const PromptHistoryEntry = Schema.Union([HistoryEntry, HistoryPrompt]).pipe( Schema.decodeTo(Schema.toType(HistoryEntry), { decode: SchemaGetter.transform((entry) => ("prompt" in entry ? entry : { prompt: entry, comments: [] })), encode: SchemaGetter.transform((entry) => entry), }),)export type PromptHistoryEntry = typeof PromptHistoryEntry.Type
export const PromptHistoryState = Persistence.struct({ entries: Persistence.array(PromptHistoryEntry) })