diff options
| author | Dax <[email protected]> | 2026-04-13 13:47:33 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-13 13:47:33 -0400 |
| commit | 7a6ce05d0939826aa6c8e1c481489a713b2d633f (patch) | |
| tree | b7894aa57c1e7a96486912d68acb8a8e2802935e | |
| parent | 1dc69359d5351c4fb4dfbefef9e87a34c7c15b12 (diff) | |
| download | opencode-7a6ce05d0939826aa6c8e1c481489a713b2d633f.tar.gz opencode-7a6ce05d0939826aa6c8e1c481489a713b2d633f.zip | |
2.0 exploration (#22335)
| -rw-r--r-- | packages/opencode/src/id/id.ts | 1 | ||||
| -rw-r--r-- | packages/opencode/src/session/projectors.ts | 32 | ||||
| -rw-r--r-- | packages/opencode/src/session/session.sql.ts | 23 | ||||
| -rw-r--r-- | packages/opencode/src/v2/message.ts | 115 | ||||
| -rw-r--r-- | packages/opencode/src/v2/session-entry.ts | 186 | ||||
| -rw-r--r-- | packages/opencode/src/v2/session.ts | 8 |
6 files changed, 245 insertions, 120 deletions
diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index 9e324962b..d86b99250 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -13,6 +13,7 @@ export namespace Identifier { pty: "pty", tool: "tool", workspace: "wrk", + entry: "ent", } as const export function schema(prefix: keyof typeof prefixes) { diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 81929cddc..8d8982c8d 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -1,10 +1,11 @@ -import { NotFoundError, eq, and } from "../storage/db" +import { NotFoundError, eq, and, sql } from "../storage/db" import { SyncEvent } from "@/sync" import { Session } from "./index" import { MessageV2 } from "./message-v2" import { SessionTable, MessageTable, PartTable } from "./session.sql" import { ProjectTable } from "../project/project.sql" import { Log } from "../util/log" +import { DateTime } from "effect" const log = Log.create({ service: "session.projector" }) @@ -132,4 +133,33 @@ export default [ log.warn("ignored late part update", { partID: id, messageID, sessionID }) } }), + + // Experimental + SyncEvent.project(MessageV2.Event.PartUpdated, (db, data) => { + /* + const id = SessionEntry.ID.make(data.part.id.replace("prt", "ent")) + switch (data.part.type) { + case "text": + db.insert(SessionEntryTable) + .values({ + id, + session_id: data.sessionID, + type: "text", + data: new SessionEntry.Text({ + id, + text: data.part.text, + type: "text", + time: { + created: DateTime.makeUnsafe(data.part.time?.start ?? Date.now()), + completed: data.part.time?.end ? DateTime.makeUnsafe(data.part.time.end) : undefined, + }, + }), + time_created: Date.now(), + time_updated: Date.now(), + }) + .onConflictDoUpdate({ target: SessionEntryTable.id, set: { data: sql`excluded.data` } }) + .run() + } + */ + }), ] diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 189a59687..49b051ca7 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -1,6 +1,7 @@ import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core" import { ProjectTable } from "../project/project.sql" import type { MessageV2 } from "./message-v2" +import type { SessionEntry } from "../v2/session-entry" import type { Snapshot } from "../snapshot" import type { Permission } from "../permission" import type { ProjectID } from "../project/schema" @@ -10,6 +11,7 @@ import { Timestamps } from "../storage/schema.sql" type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID"> type InfoData = Omit<MessageV2.Info, "id" | "sessionID"> +type EntryData = Omit<SessionEntry.Entry, "id" | "type"> export const SessionTable = sqliteTable( "session", @@ -94,6 +96,27 @@ export const TodoTable = sqliteTable( ], ) +/* +export const SessionEntryTable = sqliteTable( + "session_entry", + { + id: text().$type<SessionEntry.ID>().primaryKey(), + session_id: text() + .$type<SessionID>() + .notNull() + .references(() => SessionTable.id, { onDelete: "cascade" }), + type: text().notNull(), + ...Timestamps, + data: text({ mode: "json" }).notNull().$type<SessionEntry.Entry>(), + }, + (table) => [ + index("session_entry_session_idx").on(table.session_id), + index("session_entry_session_type_idx").on(table.session_id, table.type), + index("session_entry_time_created_idx").on(table.time_created), + ], +) +*/ + export const PermissionTable = sqliteTable("permission", { project_id: text() .primaryKey() diff --git a/packages/opencode/src/v2/message.ts b/packages/opencode/src/v2/message.ts deleted file mode 100644 index 868ab8280..000000000 --- a/packages/opencode/src/v2/message.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Identifier } from "@/id/id" -import { withStatics } from "@/util/schema" -import { DateTime, Effect, Schema } from "effect" - -export namespace Message { - export const ID = Schema.String.pipe(Schema.brand("Message.ID")).pipe( - withStatics((s) => ({ - create: () => s.make(Identifier.ascending("message")), - prefix: "msg", - })), - ) - - export class Source extends Schema.Class<Source>("Message.Source")({ - start: Schema.Number, - end: Schema.Number, - text: Schema.String, - }) {} - - export class FileAttachment extends Schema.Class<FileAttachment>("Message.File.Attachment")({ - uri: Schema.String, - mime: Schema.String, - name: Schema.String.pipe(Schema.optional), - description: Schema.String.pipe(Schema.optional), - source: Source.pipe(Schema.optional), - }) { - static create(url: string) { - return new FileAttachment({ - uri: url, - mime: "text/plain", - }) - } - } - - export class AgentAttachment extends Schema.Class<AgentAttachment>("Message.Agent.Attachment")({ - name: Schema.String, - source: Source.pipe(Schema.optional), - }) {} - - export class User extends Schema.Class<User>("Message.User")({ - id: ID, - type: Schema.Literal("user"), - text: Schema.String, - files: Schema.Array(FileAttachment).pipe(Schema.optional), - agents: Schema.Array(AgentAttachment).pipe(Schema.optional), - time: Schema.Struct({ - created: Schema.DateTimeUtc, - }), - }) { - static create(input: { text: User["text"]; files?: User["files"]; agents?: User["agents"] }) { - const msg = new User({ - id: ID.create(), - type: "user", - ...input, - time: { - created: Effect.runSync(DateTime.now), - }, - }) - return msg - } - } - - export class Synthetic extends Schema.Class<Synthetic>("Message.Synthetic")({ - id: ID, - type: Schema.Literal("synthetic"), - text: Schema.String, - time: Schema.Struct({ - created: Schema.DateTimeUtc, - }), - }) {} - - export class Request extends Schema.Class<Request>("Message.Request")({ - id: ID, - type: Schema.Literal("start"), - model: Schema.Struct({ - id: Schema.String, - providerID: Schema.String, - variant: Schema.String.pipe(Schema.optional), - }), - time: Schema.Struct({ - created: Schema.DateTimeUtc, - }), - }) {} - - export class Text extends Schema.Class<Text>("Message.Text")({ - id: ID, - type: Schema.Literal("text"), - text: Schema.String, - time: Schema.Struct({ - created: Schema.DateTimeUtc, - completed: Schema.DateTimeUtc.pipe(Schema.optional), - }), - }) {} - - export class Complete extends Schema.Class<Complete>("Message.Complete")({ - id: ID, - type: Schema.Literal("complete"), - time: Schema.Struct({ - created: Schema.DateTimeUtc, - }), - cost: Schema.Number, - tokens: Schema.Struct({ - total: Schema.Number, - input: Schema.Number, - output: Schema.Number, - reasoning: Schema.Number, - cache: Schema.Struct({ - read: Schema.Number, - write: Schema.Number, - }), - }), - }) {} - - export const Info = Schema.Union([User, Text]) - export type Info = Schema.Schema.Type<typeof Info> -} diff --git a/packages/opencode/src/v2/session-entry.ts b/packages/opencode/src/v2/session-entry.ts new file mode 100644 index 000000000..b931a4c49 --- /dev/null +++ b/packages/opencode/src/v2/session-entry.ts @@ -0,0 +1,186 @@ +import { Identifier } from "@/id/id" +import { withStatics } from "@/util/schema" +import { DateTime, Effect, Schema } from "effect" + +export namespace SessionEntry { + export const ID = Schema.String.pipe(Schema.brand("Session.Entry.ID")).pipe( + withStatics((s) => ({ + create: () => s.make(Identifier.ascending("entry")), + prefix: "ent", + })), + ) + export type ID = Schema.Schema.Type<typeof ID> + + const Base = { + id: ID, + metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), + time: Schema.Struct({ + created: Schema.DateTimeUtc, + }), + } + + export class Source extends Schema.Class<Source>("Session.Entry.Source")({ + start: Schema.Number, + end: Schema.Number, + text: Schema.String, + }) {} + + export class FileAttachment extends Schema.Class<FileAttachment>("Session.Entry.File.Attachment")({ + uri: Schema.String, + mime: Schema.String, + name: Schema.String.pipe(Schema.optional), + description: Schema.String.pipe(Schema.optional), + source: Source.pipe(Schema.optional), + }) { + static create(url: string) { + return new FileAttachment({ + uri: url, + mime: "text/plain", + }) + } + } + + export class AgentAttachment extends Schema.Class<AgentAttachment>("Session.Entry.Agent.Attachment")({ + name: Schema.String, + source: Source.pipe(Schema.optional), + }) {} + + export class User extends Schema.Class<User>("Session.Entry.User")({ + ...Base, + type: Schema.Literal("user"), + text: Schema.String, + files: Schema.Array(FileAttachment).pipe(Schema.optional), + agents: Schema.Array(AgentAttachment).pipe(Schema.optional), + }) { + static create(input: { text: User["text"]; files?: User["files"]; agents?: User["agents"] }) { + const msg = new User({ + id: ID.create(), + type: "user", + ...input, + time: { + created: Effect.runSync(DateTime.now), + }, + }) + return msg + } + } + + export class Synthetic extends Schema.Class<Synthetic>("Session.Entry.Synthetic")({ + ...Base, + type: Schema.Literal("synthetic"), + text: Schema.String, + }) {} + + export class Request extends Schema.Class<Request>("Session.Entry.Request")({ + ...Base, + type: Schema.Literal("start"), + model: Schema.Struct({ + id: Schema.String, + providerID: Schema.String, + variant: Schema.String.pipe(Schema.optional), + }), + }) {} + + export class Text extends Schema.Class<Text>("Session.Entry.Text")({ + ...Base, + type: Schema.Literal("text"), + text: Schema.String, + time: Schema.Struct({ + ...Base.time.fields, + completed: Schema.DateTimeUtc.pipe(Schema.optional), + }), + }) {} + + export class Reasoning extends Schema.Class<Reasoning>("Session.Entry.Reasoning")({ + ...Base, + type: Schema.Literal("reasoning"), + text: Schema.String, + time: Schema.Struct({ + ...Base.time.fields, + completed: Schema.DateTimeUtc.pipe(Schema.optional), + }), + }) {} + + export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Entry.ToolState.Pending")({ + status: Schema.Literal("pending"), + input: Schema.Record(Schema.String, Schema.Unknown), + raw: Schema.String, + }) {} + + export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Entry.ToolState.Running")({ + status: Schema.Literal("running"), + input: Schema.Record(Schema.String, Schema.Unknown), + title: Schema.String.pipe(Schema.optional), + metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), + }) {} + + export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Entry.ToolState.Completed")({ + status: Schema.Literal("completed"), + input: Schema.Record(Schema.String, Schema.Unknown), + output: Schema.String, + title: Schema.String, + metadata: Schema.Record(Schema.String, Schema.Unknown), + attachments: Schema.Array(FileAttachment).pipe(Schema.optional), + }) {} + + export class ToolStateError extends Schema.Class<ToolStateError>("Session.Entry.ToolState.Error")({ + status: Schema.Literal("error"), + input: Schema.Record(Schema.String, Schema.Unknown), + error: Schema.String, + metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + }), + }) {} + + export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]) + export type ToolState = Schema.Schema.Type<typeof ToolState> + + export class Tool extends Schema.Class<Tool>("Session.Entry.Tool")({ + ...Base, + type: Schema.Literal("tool"), + callID: Schema.String, + name: Schema.String, + state: ToolState, + time: Schema.Struct({ + ...Base.time.fields, + ran: Schema.DateTimeUtc.pipe(Schema.optional), + completed: Schema.DateTimeUtc.pipe(Schema.optional), + pruned: Schema.DateTimeUtc.pipe(Schema.optional), + }), + }) {} + + export class Complete extends Schema.Class<Complete>("Session.Entry.Complete")({ + ...Base, + type: Schema.Literal("complete"), + cost: Schema.Number, + reason: Schema.String, + tokens: Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + reasoning: Schema.Number, + cache: Schema.Struct({ + read: Schema.Number, + write: Schema.Number, + }), + }), + }) {} + + export class Retry extends Schema.Class<Retry>("Session.Entry.Retry")({ + ...Base, + type: Schema.Literal("retry"), + attempt: Schema.Number, + error: Schema.String, + }) {} + + export class Compaction extends Schema.Class<Compaction>("Session.Entry.Compaction")({ + ...Base, + type: Schema.Literal("compaction"), + auto: Schema.Boolean, + overflow: Schema.Boolean.pipe(Schema.optional), + }) {} + + export const Entry = Schema.Union([User, Synthetic, Request, Tool, Text, Reasoning, Complete, Retry, Compaction]) + export type Entry = Schema.Schema.Type<typeof Entry> +} diff --git a/packages/opencode/src/v2/session.ts b/packages/opencode/src/v2/session.ts index 4b4fa1978..b7191a4c9 100644 --- a/packages/opencode/src/v2/session.ts +++ b/packages/opencode/src/v2/session.ts @@ -1,5 +1,5 @@ import { Context, Layer, Schema, Effect } from "effect" -import { Message } from "./message" +import { SessionEntry } from "./session-entry" import { Struct } from "effect" import { Identifier } from "@/id/id" import { withStatics } from "@/util/schema" @@ -12,8 +12,8 @@ export namespace SessionV2 { export type ID = Schema.Schema.Type<typeof ID> export class PromptInput extends Schema.Class<PromptInput>("Session.PromptInput")({ - ...Struct.omit(Message.User.fields, ["time", "type"]), - id: Schema.optionalKey(Message.ID), + ...Struct.omit(SessionEntry.User.fields, ["time", "type"]), + id: Schema.optionalKey(SessionEntry.ID), sessionID: SessionV2.ID, }) {} @@ -33,7 +33,7 @@ export namespace SessionV2 { export interface Interface { fromID: (id: SessionV2.ID) => Effect.Effect<Info> create: (input: CreateInput) => Effect.Effect<Info> - prompt: (input: PromptInput) => Effect.Effect<Message.User> + prompt: (input: PromptInput) => Effect.Effect<SessionEntry.User> } export class Service extends Context.Service<Service, Interface>()("Session.Service") {} |
