184 lines
6.9 KiB
TypeScript
184 lines
6.9 KiB
TypeScript
import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
|
|
import { sql } from "drizzle-orm"
|
|
import { directoryColumn, pathColumn } from "../database/path"
|
|
import { ProjectTable } from "../project/sql"
|
|
import type { SessionMessage } from "./message"
|
|
import type { SessionPending } from "./pending"
|
|
import type { FileDiff } from "@opencode-ai/schema/file-diff"
|
|
import { PermissionV1 } from "../v1/permission"
|
|
import { ProjectV2 } from "../project"
|
|
import type { SessionSchema } from "./schema"
|
|
import type { MessageID, PartID, SessionV1 } from "../v1/session"
|
|
import { WorkspaceV2 } from "../workspace"
|
|
import { Timestamps } from "../database/schema.sql"
|
|
import type { Instruction } from "@opencode-ai/schema/instruction"
|
|
import type { Session } from "@opencode-ai/schema/session"
|
|
import type { SyntheticData, UserData } from "@opencode-ai/schema/session-pending"
|
|
import type { RevertV1 } from "@opencode-ai/schema/session-revert"
|
|
import type { Schema } from "effect"
|
|
|
|
type SessionMessageData = Omit<(typeof SessionMessage.Info)["Encoded"], "type" | "id">
|
|
type V1MessageData = Omit<SessionV1.Info, "id" | "sessionID">
|
|
type V1PartData = Omit<SessionV1.Part, "id" | "sessionID" | "messageID">
|
|
|
|
export const SessionTable = sqliteTable(
|
|
"session",
|
|
{
|
|
id: text().$type<SessionSchema.ID>().primaryKey(),
|
|
project_id: text()
|
|
.$type<ProjectV2.ID>()
|
|
.notNull()
|
|
.references(() => ProjectTable.id, { onDelete: "cascade" }),
|
|
workspace_id: text().$type<WorkspaceV2.ID>(),
|
|
parent_id: text().$type<SessionSchema.ID>(),
|
|
fork_session_id: text().$type<SessionSchema.ID>(),
|
|
fork_message_id: text().$type<SessionMessage.ID>(),
|
|
fork_seq: integer(),
|
|
slug: text().notNull(),
|
|
directory: directoryColumn().notNull(),
|
|
path: pathColumn(),
|
|
title: text().notNull(),
|
|
version: text().notNull(),
|
|
share_url: text(),
|
|
summary_additions: integer(),
|
|
summary_deletions: integer(),
|
|
summary_files: integer(),
|
|
summary_diffs: text({ mode: "json" }).$type<FileDiff.LegacyInfo[]>(),
|
|
metadata: text({ mode: "json" }).$type<Record<string, unknown>>(),
|
|
cost: real().notNull().default(0),
|
|
tokens_input: integer().notNull().default(0),
|
|
tokens_output: integer().notNull().default(0),
|
|
tokens_reasoning: integer().notNull().default(0),
|
|
tokens_cache_read: integer().notNull().default(0),
|
|
tokens_cache_write: integer().notNull().default(0),
|
|
revert: text({ mode: "json" }).$type<Session.Revert | RevertV1>(),
|
|
permission: text({ mode: "json" }).$type<PermissionV1.Ruleset>(),
|
|
agent: text(),
|
|
model: text({ mode: "json" }).$type<{
|
|
id: string
|
|
providerID: string
|
|
variant?: string
|
|
}>(),
|
|
...Timestamps,
|
|
time_compacting: integer(),
|
|
time_archived: integer(),
|
|
time_suspended: integer(),
|
|
},
|
|
(table) => [
|
|
index("session_project_idx").on(table.project_id),
|
|
index("session_workspace_idx").on(table.workspace_id),
|
|
index("session_parent_idx").on(table.parent_id),
|
|
index("session_time_suspended_idx")
|
|
.on(table.time_suspended)
|
|
.where(sql`${table.time_suspended} is not null`),
|
|
],
|
|
)
|
|
|
|
export const MessageTable = sqliteTable(
|
|
"message",
|
|
{
|
|
id: text().$type<MessageID>().primaryKey(),
|
|
session_id: text()
|
|
.$type<SessionSchema.ID>()
|
|
.notNull()
|
|
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
|
...Timestamps,
|
|
data: text({ mode: "json" }).notNull().$type<V1MessageData>(),
|
|
},
|
|
(table) => [index("message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id)],
|
|
)
|
|
|
|
export const PartTable = sqliteTable(
|
|
"part",
|
|
{
|
|
id: text().$type<PartID>().primaryKey(),
|
|
message_id: text()
|
|
.$type<MessageID>()
|
|
.notNull()
|
|
.references(() => MessageTable.id, { onDelete: "cascade" }),
|
|
session_id: text().$type<SessionSchema.ID>().notNull(),
|
|
...Timestamps,
|
|
data: text({ mode: "json" }).notNull().$type<V1PartData>(),
|
|
},
|
|
(table) => [
|
|
index("part_message_id_id_idx").on(table.message_id, table.id),
|
|
index("part_session_idx").on(table.session_id),
|
|
],
|
|
)
|
|
|
|
export const SessionMessageTable = sqliteTable(
|
|
"session_message",
|
|
{
|
|
id: text().$type<SessionMessage.ID>().primaryKey(),
|
|
session_id: text()
|
|
.$type<SessionSchema.ID>()
|
|
.notNull()
|
|
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
|
type: text().$type<SessionMessage.Type>().notNull(),
|
|
seq: integer().notNull(),
|
|
...Timestamps,
|
|
data: text({ mode: "json" }).notNull().$type<SessionMessageData>(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("session_message_session_seq_idx").on(table.session_id, table.seq),
|
|
index("session_message_session_type_seq_idx").on(table.session_id, table.type, table.seq),
|
|
index("session_message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id),
|
|
index("session_message_time_created_idx").on(table.time_created),
|
|
],
|
|
)
|
|
|
|
export const SessionPendingTable = sqliteTable(
|
|
"session_pending",
|
|
{
|
|
id: text().$type<SessionMessage.ID>().primaryKey(),
|
|
session_id: text()
|
|
.$type<SessionSchema.ID>()
|
|
.notNull()
|
|
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
|
type: text().$type<SessionPending.Info["type"]>().notNull(),
|
|
data: text({ mode: "json" }).$type<UserData | SyntheticData | Record<string, never>>().notNull(),
|
|
delivery: text().$type<SessionPending.Delivery>(),
|
|
admitted_seq: integer().notNull(),
|
|
time_created: integer()
|
|
.notNull()
|
|
.$default(() => Date.now()),
|
|
},
|
|
(table) => [
|
|
index("session_pending_session_delivery_seq_idx").on(table.session_id, table.delivery, table.admitted_seq),
|
|
uniqueIndex("session_pending_session_compaction_idx")
|
|
.on(table.session_id)
|
|
.where(sql`${table.type} = 'compaction'`),
|
|
uniqueIndex("session_pending_session_admitted_seq_idx").on(table.session_id, table.admitted_seq),
|
|
],
|
|
)
|
|
|
|
export const InstructionEntryTable = sqliteTable(
|
|
"instruction_entry",
|
|
{
|
|
session_id: text()
|
|
.$type<SessionSchema.ID>()
|
|
.notNull()
|
|
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
|
key: text().notNull(),
|
|
value: text({ mode: "json" }).$type<Schema.Json>(),
|
|
removed: integer({ mode: "boolean" }).notNull().default(false),
|
|
...Timestamps,
|
|
},
|
|
(table) => [primaryKey({ columns: [table.session_id, table.key] })],
|
|
)
|
|
|
|
export const InstructionBlobTable = sqliteTable("instruction_blob", {
|
|
hash: text().$type<Instruction.Hash>().primaryKey(),
|
|
value: text({ mode: "json" }).$type<Schema.Json>(),
|
|
})
|
|
|
|
export const InstructionStateTable = sqliteTable("instruction_state", {
|
|
session_id: text()
|
|
.$type<SessionSchema.ID>()
|
|
.primaryKey()
|
|
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
|
epoch_start: integer().notNull(),
|
|
through_seq: integer().notNull(),
|
|
initial_values: text({ mode: "json" }).notNull().$type<Instruction.Values>(),
|
|
current_values: text({ mode: "json" }).notNull().$type<Instruction.Values>(),
|
|
})
|