summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2026-04-13 13:47:33 -0400
committerGitHub <[email protected]>2026-04-13 13:47:33 -0400
commit7a6ce05d0939826aa6c8e1c481489a713b2d633f (patch)
treeb7894aa57c1e7a96486912d68acb8a8e2802935e
parent1dc69359d5351c4fb4dfbefef9e87a34c7c15b12 (diff)
downloadopencode-7a6ce05d0939826aa6c8e1c481489a713b2d633f.tar.gz
opencode-7a6ce05d0939826aa6c8e1c481489a713b2d633f.zip
2.0 exploration (#22335)
-rw-r--r--packages/opencode/src/id/id.ts1
-rw-r--r--packages/opencode/src/session/projectors.ts32
-rw-r--r--packages/opencode/src/session/session.sql.ts23
-rw-r--r--packages/opencode/src/v2/message.ts115
-rw-r--r--packages/opencode/src/v2/session-entry.ts186
-rw-r--r--packages/opencode/src/v2/session.ts8
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") {}