sql.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
  2. import * as DatabasePath from "../database/path"
  3. import { ProjectTable } from "../project/sql"
  4. import type { SessionMessage } from "./message"
  5. import type { Prompt } from "./prompt"
  6. import type { SessionInput } from "./input"
  7. import type { Snapshot } from "../snapshot"
  8. import { PermissionV1 } from "../v1/permission"
  9. import { ProjectV2 } from "../project"
  10. import type { SessionSchema } from "./schema"
  11. import type { MessageID, PartID, SessionV1 } from "../v1/session"
  12. import { WorkspaceV2 } from "../workspace"
  13. import { Timestamps } from "../database/schema.sql"
  14. import type { SystemContext } from "../system-context/index"
  15. import { AgentV2 } from "../agent"
  16. import type { Revert } from "@opencode-ai/schema/revert"
  17. type SessionMessageData = Omit<(typeof SessionMessage.Message)["Encoded"], "type" | "id">
  18. type V1MessageData = Omit<SessionV1.Info, "id" | "sessionID">
  19. type V1PartData = Omit<SessionV1.Part, "id" | "sessionID" | "messageID">
  20. export const SessionTable = sqliteTable(
  21. "session",
  22. {
  23. id: text().$type<SessionSchema.ID>().primaryKey(),
  24. project_id: text()
  25. .$type<ProjectV2.ID>()
  26. .notNull()
  27. .references(() => ProjectTable.id, { onDelete: "cascade" }),
  28. workspace_id: text().$type<WorkspaceV2.ID>(),
  29. parent_id: text().$type<SessionSchema.ID>(),
  30. slug: text().notNull(),
  31. directory: DatabasePath.directoryColumn().notNull(),
  32. path: DatabasePath.pathColumn(),
  33. title: text().notNull(),
  34. version: text().notNull(),
  35. share_url: text(),
  36. summary_additions: integer(),
  37. summary_deletions: integer(),
  38. summary_files: integer(),
  39. summary_diffs: text({ mode: "json" }).$type<Snapshot.LegacyFileDiff[]>(),
  40. metadata: text({ mode: "json" }).$type<Record<string, unknown>>(),
  41. cost: real().notNull().default(0),
  42. tokens_input: integer().notNull().default(0),
  43. tokens_output: integer().notNull().default(0),
  44. tokens_reasoning: integer().notNull().default(0),
  45. tokens_cache_read: integer().notNull().default(0),
  46. tokens_cache_write: integer().notNull().default(0),
  47. revert: text({ mode: "json" }).$type<Revert.State>(),
  48. permission: text({ mode: "json" }).$type<PermissionV1.Ruleset>(),
  49. agent: text(),
  50. model: text({ mode: "json" }).$type<{
  51. id: string
  52. providerID: string
  53. variant?: string
  54. }>(),
  55. ...Timestamps,
  56. time_compacting: integer(),
  57. time_archived: integer(),
  58. },
  59. (table) => [
  60. index("session_project_idx").on(table.project_id),
  61. index("session_workspace_idx").on(table.workspace_id),
  62. index("session_parent_idx").on(table.parent_id),
  63. ],
  64. )
  65. export const MessageTable = sqliteTable(
  66. "message",
  67. {
  68. id: text().$type<MessageID>().primaryKey(),
  69. session_id: text()
  70. .$type<SessionSchema.ID>()
  71. .notNull()
  72. .references(() => SessionTable.id, { onDelete: "cascade" }),
  73. ...Timestamps,
  74. data: text({ mode: "json" }).notNull().$type<V1MessageData>(),
  75. },
  76. (table) => [index("message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id)],
  77. )
  78. export const PartTable = sqliteTable(
  79. "part",
  80. {
  81. id: text().$type<PartID>().primaryKey(),
  82. message_id: text()
  83. .$type<MessageID>()
  84. .notNull()
  85. .references(() => MessageTable.id, { onDelete: "cascade" }),
  86. session_id: text().$type<SessionSchema.ID>().notNull(),
  87. ...Timestamps,
  88. data: text({ mode: "json" }).notNull().$type<V1PartData>(),
  89. },
  90. (table) => [
  91. index("part_message_id_id_idx").on(table.message_id, table.id),
  92. index("part_session_idx").on(table.session_id),
  93. ],
  94. )
  95. export const TodoTable = sqliteTable(
  96. "todo",
  97. {
  98. session_id: text()
  99. .$type<SessionSchema.ID>()
  100. .notNull()
  101. .references(() => SessionTable.id, { onDelete: "cascade" }),
  102. content: text().notNull(),
  103. status: text().notNull(),
  104. priority: text().notNull(),
  105. position: integer().notNull(),
  106. ...Timestamps,
  107. },
  108. (table) => [
  109. primaryKey({ columns: [table.session_id, table.position] }),
  110. index("todo_session_idx").on(table.session_id),
  111. ],
  112. )
  113. export const SessionMessageTable = sqliteTable(
  114. "session_message",
  115. {
  116. id: text().$type<SessionMessage.ID>().primaryKey(),
  117. session_id: text()
  118. .$type<SessionSchema.ID>()
  119. .notNull()
  120. .references(() => SessionTable.id, { onDelete: "cascade" }),
  121. type: text().$type<SessionMessage.Type>().notNull(),
  122. seq: integer().notNull(),
  123. ...Timestamps,
  124. data: text({ mode: "json" }).notNull().$type<SessionMessageData>(),
  125. },
  126. (table) => [
  127. uniqueIndex("session_message_session_seq_idx").on(table.session_id, table.seq),
  128. index("session_message_session_type_seq_idx").on(table.session_id, table.type, table.seq),
  129. index("session_message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id),
  130. index("session_message_time_created_idx").on(table.time_created),
  131. ],
  132. )
  133. export const SessionInputTable = sqliteTable(
  134. "session_input",
  135. {
  136. id: text().$type<SessionMessage.ID>().primaryKey(),
  137. session_id: text()
  138. .$type<SessionSchema.ID>()
  139. .notNull()
  140. .references(() => SessionTable.id, { onDelete: "cascade" }),
  141. prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
  142. delivery: text().$type<SessionInput.Delivery>().notNull(),
  143. admitted_seq: integer().notNull(),
  144. promoted_seq: integer(),
  145. time_created: integer()
  146. .notNull()
  147. .$default(() => Date.now()),
  148. },
  149. (table) => [
  150. index("session_input_session_pending_delivery_seq_idx").on(
  151. table.session_id,
  152. table.promoted_seq,
  153. table.delivery,
  154. table.admitted_seq,
  155. ),
  156. uniqueIndex("session_input_session_admitted_seq_idx").on(table.session_id, table.admitted_seq),
  157. uniqueIndex("session_input_session_promoted_seq_idx").on(table.session_id, table.promoted_seq),
  158. ],
  159. )
  160. export const SessionContextEpochTable = sqliteTable("session_context_epoch", {
  161. session_id: text()
  162. .$type<SessionSchema.ID>()
  163. .primaryKey()
  164. .references(() => SessionTable.id, { onDelete: "cascade" }),
  165. baseline: text().notNull(),
  166. snapshot: text({ mode: "json" }).notNull().$type<SystemContext.Snapshot>(),
  167. baseline_seq: integer().notNull(),
  168. })