Forráskód Böngészése

feat(id): brand PartID through Drizzle and Zod schemas (#16966)

Kit Langton 6 hónapja
szülő
commit
090f636354

+ 2 - 3
packages/opencode/script/seed-e2e.ts

@@ -11,8 +11,7 @@ const seed = async () => {
   const { Instance } = await import("../src/project/instance")
   const { Instance } = await import("../src/project/instance")
   const { InstanceBootstrap } = await import("../src/project/bootstrap")
   const { InstanceBootstrap } = await import("../src/project/bootstrap")
   const { Session } = await import("../src/session")
   const { Session } = await import("../src/session")
-  const { Identifier } = await import("../src/id/id")
-  const { MessageID } = await import("../src/session/schema")
+  const { MessageID, PartID } = await import("../src/session/schema")
   const { Project } = await import("../src/project/project")
   const { Project } = await import("../src/project/project")
 
 
   await Instance.provide({
   await Instance.provide({
@@ -21,7 +20,7 @@ const seed = async () => {
     fn: async () => {
     fn: async () => {
       const session = await Session.create({ title })
       const session = await Session.create({ title })
       const messageID = MessageID.ascending()
       const messageID = MessageID.ascending()
-      const partID = Identifier.descending("part")
+      const partID = PartID.ascending()
       const message = {
       const message = {
         id: messageID,
         id: messageID,
         sessionID: session.id,
         sessionID: session.id,

+ 2 - 3
packages/opencode/src/cli/cmd/debug/agent.ts

@@ -4,8 +4,7 @@ import { Agent } from "../../../agent/agent"
 import { Provider } from "../../../provider/provider"
 import { Provider } from "../../../provider/provider"
 import { Session } from "../../../session"
 import { Session } from "../../../session"
 import type { MessageV2 } from "../../../session/message-v2"
 import type { MessageV2 } from "../../../session/message-v2"
-import { Identifier } from "../../../id/id"
-import { MessageID } from "../../../session/schema"
+import { MessageID, PartID } from "../../../session/schema"
 import { ToolRegistry } from "../../../tool/registry"
 import { ToolRegistry } from "../../../tool/registry"
 import { Instance } from "../../../project/instance"
 import { Instance } from "../../../project/instance"
 import { PermissionNext } from "../../../permission/next"
 import { PermissionNext } from "../../../permission/next"
@@ -151,7 +150,7 @@ async function createToolContext(agent: Agent.Info) {
   return {
   return {
     sessionID: session.id,
     sessionID: session.id,
     messageID,
     messageID,
-    callID: Identifier.ascending("part"),
+    callID: PartID.ascending(),
     agent: agent.name,
     agent: agent.name,
     abort: new AbortController().signal,
     abort: new AbortController().signal,
     messages: [],
     messages: [],

+ 4 - 5
packages/opencode/src/cli/cmd/github.ts

@@ -23,8 +23,7 @@ import { Instance } from "@/project/instance"
 import { bootstrap } from "../bootstrap"
 import { bootstrap } from "../bootstrap"
 import { Session } from "../../session"
 import { Session } from "../../session"
 import type { SessionID } from "../../session/schema"
 import type { SessionID } from "../../session/schema"
-import { Identifier } from "../../id/id"
-import { MessageID } from "../../session/schema"
+import { MessageID, PartID } from "../../session/schema"
 import { Provider } from "../../provider/provider"
 import { Provider } from "../../provider/provider"
 import { Bus } from "../../bus"
 import { Bus } from "../../bus"
 import { MessageV2 } from "../../session/message-v2"
 import { MessageV2 } from "../../session/message-v2"
@@ -945,13 +944,13 @@ export const GithubRunCommand = cmd({
           // agent is omitted - server will use default_agent from config or fall back to "build"
           // agent is omitted - server will use default_agent from config or fall back to "build"
           parts: [
           parts: [
             {
             {
-              id: Identifier.ascending("part"),
+              id: PartID.ascending(),
               type: "text",
               type: "text",
               text: message,
               text: message,
             },
             },
             ...files.flatMap((f) => [
             ...files.flatMap((f) => [
               {
               {
-                id: Identifier.ascending("part"),
+                id: PartID.ascending(),
                 type: "file" as const,
                 type: "file" as const,
                 mime: f.mime,
                 mime: f.mime,
                 url: `data:${f.mime};base64,${f.content}`,
                 url: `data:${f.mime};base64,${f.content}`,
@@ -999,7 +998,7 @@ export const GithubRunCommand = cmd({
           tools: { "*": false }, // Disable all tools to force text response
           tools: { "*": false }, // Disable all tools to force text response
           parts: [
           parts: [
             {
             {
-              id: Identifier.ascending("part"),
+              id: PartID.ascending(),
               type: "text",
               type: "text",
               text: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences.",
               text: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences.",
             },
             },

+ 7 - 3
packages/opencode/src/cli/cmd/import.ts

@@ -1,7 +1,7 @@
 import type { Argv } from "yargs"
 import type { Argv } from "yargs"
 import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
 import type { Session as SDKSession, Message, Part } from "@opencode-ai/sdk/v2"
 import { Session } from "../../session"
 import { Session } from "../../session"
-import { SessionID, MessageID } from "../../session/schema"
+import { SessionID, MessageID, PartID } from "../../session/schema"
 import { WorkspaceID } from "../../control-plane/schema"
 import { WorkspaceID } from "../../control-plane/schema"
 import { cmd } from "./cmd"
 import { cmd } from "./cmd"
 import { bootstrap } from "../bootstrap"
 import { bootstrap } from "../bootstrap"
@@ -161,7 +161,11 @@ export const ImportCommand = cmd({
         workspaceID: exportData.info.workspaceID ? WorkspaceID.make(exportData.info.workspaceID) : undefined,
         workspaceID: exportData.info.workspaceID ? WorkspaceID.make(exportData.info.workspaceID) : undefined,
         projectID: Instance.project.id,
         projectID: Instance.project.id,
         revert: exportData.info.revert
         revert: exportData.info.revert
-          ? { ...exportData.info.revert, messageID: MessageID.make(exportData.info.revert.messageID) }
+          ? {
+              ...exportData.info.revert,
+              messageID: MessageID.make(exportData.info.revert.messageID),
+              partID: exportData.info.revert.partID ? PartID.make(exportData.info.revert.partID) : undefined,
+            }
           : undefined,
           : undefined,
       })
       })
       Database.use((db) =>
       Database.use((db) =>
@@ -193,7 +197,7 @@ export const ImportCommand = cmd({
             db
             db
               .insert(PartTable)
               .insert(PartTable)
               .values({
               .values({
-                id: part.id,
+                id: PartID.make(part.id),
                 message_id: MessageID.make(msg.info.id),
                 message_id: MessageID.make(msg.info.id),
                 session_id: row.id,
                 session_id: row.id,
                 data: partData,
                 data: partData,

+ 4 - 5
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -9,8 +9,7 @@ import { EmptyBorder } from "@tui/component/border"
 import { useSDK } from "@tui/context/sdk"
 import { useSDK } from "@tui/context/sdk"
 import { useRoute } from "@tui/context/route"
 import { useRoute } from "@tui/context/route"
 import { useSync } from "@tui/context/sync"
 import { useSync } from "@tui/context/sync"
-import { Identifier } from "@/id/id"
-import { MessageID } from "@/session/schema"
+import { MessageID, PartID } from "@/session/schema"
 import { createStore, produce } from "solid-js/store"
 import { createStore, produce } from "solid-js/store"
 import { useKeybind } from "@tui/context/keybind"
 import { useKeybind } from "@tui/context/keybind"
 import { usePromptHistory, type PromptInfo } from "./history"
 import { usePromptHistory, type PromptInfo } from "./history"
@@ -625,7 +624,7 @@ export function Prompt(props: PromptProps) {
         parts: nonTextParts
         parts: nonTextParts
           .filter((x) => x.type === "file")
           .filter((x) => x.type === "file")
           .map((x) => ({
           .map((x) => ({
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             ...x,
             ...x,
           })),
           })),
       })
       })
@@ -640,12 +639,12 @@ export function Prompt(props: PromptProps) {
           variant,
           variant,
           parts: [
           parts: [
             {
             {
-              id: Identifier.ascending("part"),
+              id: PartID.ascending(),
               type: "text",
               type: "text",
               text: inputText,
               text: inputText,
             },
             },
             ...nonTextParts.map((x) => ({
             ...nonTextParts.map((x) => ({
-              id: Identifier.ascending("part"),
+              id: PartID.ascending(),
               ...x,
               ...x,
             })),
             })),
           ],
           ],

+ 3 - 3
packages/opencode/src/server/routes/session.ts

@@ -1,7 +1,7 @@
 import { Hono } from "hono"
 import { Hono } from "hono"
 import { stream } from "hono/streaming"
 import { stream } from "hono/streaming"
 import { describeRoute, validator, resolver } from "hono-openapi"
 import { describeRoute, validator, resolver } from "hono-openapi"
-import { SessionID, MessageID } from "@/session/schema"
+import { SessionID, MessageID, PartID } from "@/session/schema"
 import z from "zod"
 import z from "zod"
 import { Session } from "../../session"
 import { Session } from "../../session"
 import { MessageV2 } from "../../session/message-v2"
 import { MessageV2 } from "../../session/message-v2"
@@ -677,7 +677,7 @@ export const SessionRoutes = lazy(() =>
         z.object({
         z.object({
           sessionID: SessionID.zod,
           sessionID: SessionID.zod,
           messageID: MessageID.zod,
           messageID: MessageID.zod,
-          partID: z.string(),
+          partID: PartID.zod,
         }),
         }),
       ),
       ),
       async (c) => {
       async (c) => {
@@ -712,7 +712,7 @@ export const SessionRoutes = lazy(() =>
         z.object({
         z.object({
           sessionID: SessionID.zod,
           sessionID: SessionID.zod,
           messageID: MessageID.zod,
           messageID: MessageID.zod,
-          partID: z.string(),
+          partID: PartID.zod,
         }),
         }),
       ),
       ),
       validator("json", MessageV2.Part),
       validator("json", MessageV2.Part),

+ 4 - 5
packages/opencode/src/session/compaction.ts

@@ -1,8 +1,7 @@
 import { BusEvent } from "@/bus/bus-event"
 import { BusEvent } from "@/bus/bus-event"
 import { Bus } from "@/bus"
 import { Bus } from "@/bus"
 import { Session } from "."
 import { Session } from "."
-import { Identifier } from "../id/id"
-import { SessionID, MessageID } from "./schema"
+import { SessionID, MessageID, PartID } from "./schema"
 import { Instance } from "../project/instance"
 import { Instance } from "../project/instance"
 import { Provider } from "../provider/provider"
 import { Provider } from "../provider/provider"
 import { MessageV2 } from "./message-v2"
 import { MessageV2 } from "./message-v2"
@@ -256,7 +255,7 @@ When constructing the summary, try to stick to this template:
               : part
               : part
           await Session.updatePart({
           await Session.updatePart({
             ...replayPart,
             ...replayPart,
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             messageID: replayMsg.id,
             messageID: replayMsg.id,
             sessionID: input.sessionID,
             sessionID: input.sessionID,
           })
           })
@@ -276,7 +275,7 @@ When constructing the summary, try to stick to this template:
             : "") +
             : "") +
           "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
           "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: continueMsg.id,
           messageID: continueMsg.id,
           sessionID: input.sessionID,
           sessionID: input.sessionID,
           type: "text",
           type: "text",
@@ -317,7 +316,7 @@ When constructing the summary, try to stick to this template:
         },
         },
       })
       })
       await Session.updatePart({
       await Session.updatePart({
-        id: Identifier.ascending("part"),
+        id: PartID.ascending(),
         messageID: msg.id,
         messageID: msg.id,
         sessionID: msg.sessionID,
         sessionID: msg.sessionID,
         type: "compaction",
         type: "compaction",

+ 5 - 6
packages/opencode/src/session/index.ts

@@ -7,7 +7,6 @@ import z from "zod"
 import { type ProviderMetadata } from "ai"
 import { type ProviderMetadata } from "ai"
 import { Config } from "../config/config"
 import { Config } from "../config/config"
 import { Flag } from "../flag/flag"
 import { Flag } from "../flag/flag"
-import { Identifier } from "../id/id"
 import { Installation } from "../installation"
 import { Installation } from "../installation"
 
 
 import { Database, NotFoundError, eq, and, or, gte, isNull, desc, like, inArray, lt } from "../storage/db"
 import { Database, NotFoundError, eq, and, or, gte, isNull, desc, like, inArray, lt } from "../storage/db"
@@ -25,7 +24,7 @@ import { Snapshot } from "@/snapshot"
 import { WorkspaceContext } from "../control-plane/workspace-context"
 import { WorkspaceContext } from "../control-plane/workspace-context"
 import { ProjectID } from "../project/schema"
 import { ProjectID } from "../project/schema"
 import { WorkspaceID } from "../control-plane/schema"
 import { WorkspaceID } from "../control-plane/schema"
-import { SessionID, MessageID } from "./schema"
+import { SessionID, MessageID, PartID } from "./schema"
 
 
 import type { Provider } from "@/provider/provider"
 import type { Provider } from "@/provider/provider"
 import { PermissionNext } from "@/permission/next"
 import { PermissionNext } from "@/permission/next"
@@ -152,7 +151,7 @@ export namespace Session {
       revert: z
       revert: z
         .object({
         .object({
           messageID: MessageID.zod,
           messageID: MessageID.zod,
-          partID: z.string().optional(),
+          partID: PartID.zod.optional(),
           snapshot: z.string().optional(),
           snapshot: z.string().optional(),
           diff: z.string().optional(),
           diff: z.string().optional(),
         })
         })
@@ -269,7 +268,7 @@ export namespace Session {
         for (const part of msg.parts) {
         for (const part of msg.parts) {
           await updatePart({
           await updatePart({
             ...part,
             ...part,
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             messageID: cloned.id,
             messageID: cloned.id,
             sessionID: session.id,
             sessionID: session.id,
           })
           })
@@ -731,7 +730,7 @@ export namespace Session {
     z.object({
     z.object({
       sessionID: SessionID.zod,
       sessionID: SessionID.zod,
       messageID: MessageID.zod,
       messageID: MessageID.zod,
-      partID: Identifier.schema("part"),
+      partID: PartID.zod,
     }),
     }),
     async (input) => {
     async (input) => {
       Database.use((db) => {
       Database.use((db) => {
@@ -779,7 +778,7 @@ export namespace Session {
     z.object({
     z.object({
       sessionID: SessionID.zod,
       sessionID: SessionID.zod,
       messageID: MessageID.zod,
       messageID: MessageID.zod,
-      partID: z.string(),
+      partID: PartID.zod,
       field: z.string(),
       field: z.string(),
       delta: z.string(),
       delta: z.string(),
     }),
     }),

+ 4 - 4
packages/opencode/src/session/message-v2.ts

@@ -1,5 +1,5 @@
 import { BusEvent } from "@/bus/bus-event"
 import { BusEvent } from "@/bus/bus-event"
-import { SessionID, MessageID } from "./schema"
+import { SessionID, MessageID, PartID } from "./schema"
 import z from "zod"
 import z from "zod"
 import { NamedError } from "@opencode-ai/util/error"
 import { NamedError } from "@opencode-ai/util/error"
 import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
 import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
@@ -78,7 +78,7 @@ export namespace MessageV2 {
   export type OutputFormat = z.infer<typeof Format>
   export type OutputFormat = z.infer<typeof Format>
 
 
   const PartBase = z.object({
   const PartBase = z.object({
-    id: z.string(),
+    id: PartID.zod,
     sessionID: SessionID.zod,
     sessionID: SessionID.zod,
     messageID: MessageID.zod,
     messageID: MessageID.zod,
   })
   })
@@ -472,7 +472,7 @@ export namespace MessageV2 {
       z.object({
       z.object({
         sessionID: SessionID.zod,
         sessionID: SessionID.zod,
         messageID: MessageID.zod,
         messageID: MessageID.zod,
-        partID: z.string(),
+        partID: PartID.zod,
         field: z.string(),
         field: z.string(),
         delta: z.string(),
         delta: z.string(),
       }),
       }),
@@ -482,7 +482,7 @@ export namespace MessageV2 {
       z.object({
       z.object({
         sessionID: SessionID.zod,
         sessionID: SessionID.zod,
         messageID: MessageID.zod,
         messageID: MessageID.zod,
-        partID: z.string(),
+        partID: PartID.zod,
       }),
       }),
     ),
     ),
   }
   }

+ 8 - 8
packages/opencode/src/session/processor.ts

@@ -1,6 +1,5 @@
 import { MessageV2 } from "./message-v2"
 import { MessageV2 } from "./message-v2"
 import { Log } from "@/util/log"
 import { Log } from "@/util/log"
-import { Identifier } from "@/id/id"
 import { Session } from "."
 import { Session } from "."
 import { Agent } from "@/agent/agent"
 import { Agent } from "@/agent/agent"
 import { Snapshot } from "@/snapshot"
 import { Snapshot } from "@/snapshot"
@@ -15,6 +14,7 @@ import { Config } from "@/config/config"
 import { SessionCompaction } from "./compaction"
 import { SessionCompaction } from "./compaction"
 import { PermissionNext } from "@/permission/next"
 import { PermissionNext } from "@/permission/next"
 import { Question } from "@/question"
 import { Question } from "@/question"
+import { PartID } from "./schema"
 import type { SessionID, MessageID } from "./schema"
 import type { SessionID, MessageID } from "./schema"
 
 
 export namespace SessionProcessor {
 export namespace SessionProcessor {
@@ -65,7 +65,7 @@ export namespace SessionProcessor {
                     continue
                     continue
                   }
                   }
                   const reasoningPart = {
                   const reasoningPart = {
-                    id: Identifier.ascending("part"),
+                    id: PartID.ascending(),
                     messageID: input.assistantMessage.id,
                     messageID: input.assistantMessage.id,
                     sessionID: input.assistantMessage.sessionID,
                     sessionID: input.assistantMessage.sessionID,
                     type: "reasoning" as const,
                     type: "reasoning" as const,
@@ -111,7 +111,7 @@ export namespace SessionProcessor {
 
 
                 case "tool-input-start":
                 case "tool-input-start":
                   const part = await Session.updatePart({
                   const part = await Session.updatePart({
-                    id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
+                    id: toolcalls[value.id]?.id ?? PartID.ascending(),
                     messageID: input.assistantMessage.id,
                     messageID: input.assistantMessage.id,
                     sessionID: input.assistantMessage.sessionID,
                     sessionID: input.assistantMessage.sessionID,
                     type: "tool",
                     type: "tool",
@@ -234,7 +234,7 @@ export namespace SessionProcessor {
                 case "start-step":
                 case "start-step":
                   snapshot = await Snapshot.track()
                   snapshot = await Snapshot.track()
                   await Session.updatePart({
                   await Session.updatePart({
-                    id: Identifier.ascending("part"),
+                    id: PartID.ascending(),
                     messageID: input.assistantMessage.id,
                     messageID: input.assistantMessage.id,
                     sessionID: input.sessionID,
                     sessionID: input.sessionID,
                     snapshot,
                     snapshot,
@@ -252,7 +252,7 @@ export namespace SessionProcessor {
                   input.assistantMessage.cost += usage.cost
                   input.assistantMessage.cost += usage.cost
                   input.assistantMessage.tokens = usage.tokens
                   input.assistantMessage.tokens = usage.tokens
                   await Session.updatePart({
                   await Session.updatePart({
-                    id: Identifier.ascending("part"),
+                    id: PartID.ascending(),
                     reason: value.finishReason,
                     reason: value.finishReason,
                     snapshot: await Snapshot.track(),
                     snapshot: await Snapshot.track(),
                     messageID: input.assistantMessage.id,
                     messageID: input.assistantMessage.id,
@@ -266,7 +266,7 @@ export namespace SessionProcessor {
                     const patch = await Snapshot.patch(snapshot)
                     const patch = await Snapshot.patch(snapshot)
                     if (patch.files.length) {
                     if (patch.files.length) {
                       await Session.updatePart({
                       await Session.updatePart({
-                        id: Identifier.ascending("part"),
+                        id: PartID.ascending(),
                         messageID: input.assistantMessage.id,
                         messageID: input.assistantMessage.id,
                         sessionID: input.sessionID,
                         sessionID: input.sessionID,
                         type: "patch",
                         type: "patch",
@@ -290,7 +290,7 @@ export namespace SessionProcessor {
 
 
                 case "text-start":
                 case "text-start":
                   currentText = {
                   currentText = {
-                    id: Identifier.ascending("part"),
+                    id: PartID.ascending(),
                     messageID: input.assistantMessage.id,
                     messageID: input.assistantMessage.id,
                     sessionID: input.assistantMessage.sessionID,
                     sessionID: input.assistantMessage.sessionID,
                     type: "text",
                     type: "text",
@@ -389,7 +389,7 @@ export namespace SessionProcessor {
             const patch = await Snapshot.patch(snapshot)
             const patch = await Snapshot.patch(snapshot)
             if (patch.files.length) {
             if (patch.files.length) {
               await Session.updatePart({
               await Session.updatePart({
-                id: Identifier.ascending("part"),
+                id: PartID.ascending(),
                 messageID: input.assistantMessage.id,
                 messageID: input.assistantMessage.id,
                 sessionID: input.sessionID,
                 sessionID: input.sessionID,
                 type: "patch",
                 type: "patch",

+ 13 - 14
packages/opencode/src/session/prompt.ts

@@ -3,8 +3,7 @@ import os from "os"
 import fs from "fs/promises"
 import fs from "fs/promises"
 import z from "zod"
 import z from "zod"
 import { Filesystem } from "../util/filesystem"
 import { Filesystem } from "../util/filesystem"
-import { Identifier } from "../id/id"
-import { SessionID, MessageID } from "./schema"
+import { SessionID, MessageID, PartID } from "./schema"
 import { MessageV2 } from "./message-v2"
 import { MessageV2 } from "./message-v2"
 import { Log } from "../util/log"
 import { Log } from "../util/log"
 import { SessionRevert } from "./revert"
 import { SessionRevert } from "./revert"
@@ -380,7 +379,7 @@ export namespace SessionPrompt {
           },
           },
         })) as MessageV2.Assistant
         })) as MessageV2.Assistant
         let part = (await Session.updatePart({
         let part = (await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: assistantMessage.id,
           messageID: assistantMessage.id,
           sessionID: assistantMessage.sessionID,
           sessionID: assistantMessage.sessionID,
           type: "tool",
           type: "tool",
@@ -449,7 +448,7 @@ export namespace SessionPrompt {
         })
         })
         const attachments = result?.attachments?.map((attachment) => ({
         const attachments = result?.attachments?.map((attachment) => ({
           ...attachment,
           ...attachment,
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           sessionID,
           sessionID,
           messageID: assistantMessage.id,
           messageID: assistantMessage.id,
         }))
         }))
@@ -515,7 +514,7 @@ export namespace SessionPrompt {
           }
           }
           await Session.updateMessage(summaryUserMsg)
           await Session.updateMessage(summaryUserMsg)
           await Session.updatePart({
           await Session.updatePart({
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             messageID: summaryUserMsg.id,
             messageID: summaryUserMsg.id,
             sessionID,
             sessionID,
             type: "text",
             type: "text",
@@ -814,7 +813,7 @@ export namespace SessionPrompt {
             ...result,
             ...result,
             attachments: result.attachments?.map((attachment) => ({
             attachments: result.attachments?.map((attachment) => ({
               ...attachment,
               ...attachment,
-              id: Identifier.ascending("part"),
+              id: PartID.ascending(),
               sessionID: ctx.sessionID,
               sessionID: ctx.sessionID,
               messageID: input.processor.message.id,
               messageID: input.processor.message.id,
             })),
             })),
@@ -917,7 +916,7 @@ export namespace SessionPrompt {
           output: truncated.content,
           output: truncated.content,
           attachments: attachments.map((attachment) => ({
           attachments: attachments.map((attachment) => ({
             ...attachment,
             ...attachment,
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             sessionID: ctx.sessionID,
             sessionID: ctx.sessionID,
             messageID: input.processor.message.id,
             messageID: input.processor.message.id,
           })),
           })),
@@ -989,7 +988,7 @@ export namespace SessionPrompt {
     type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
     type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
     const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
     const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
       ...part,
       ...part,
-      id: part.id ?? Identifier.ascending("part"),
+      id: part.id ? PartID.make(part.id) : PartID.ascending(),
     })
     })
 
 
     const parts = await Promise.all(
     const parts = await Promise.all(
@@ -1335,7 +1334,7 @@ export namespace SessionPrompt {
     if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) {
     if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) {
       if (input.agent.name === "plan") {
       if (input.agent.name === "plan") {
         userMessage.parts.push({
         userMessage.parts.push({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMessage.info.id,
           messageID: userMessage.info.id,
           sessionID: userMessage.info.sessionID,
           sessionID: userMessage.info.sessionID,
           type: "text",
           type: "text",
@@ -1346,7 +1345,7 @@ export namespace SessionPrompt {
       const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
       const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
       if (wasPlan && input.agent.name === "build") {
       if (wasPlan && input.agent.name === "build") {
         userMessage.parts.push({
         userMessage.parts.push({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMessage.info.id,
           messageID: userMessage.info.id,
           sessionID: userMessage.info.sessionID,
           sessionID: userMessage.info.sessionID,
           type: "text",
           type: "text",
@@ -1366,7 +1365,7 @@ export namespace SessionPrompt {
       const exists = await Filesystem.exists(plan)
       const exists = await Filesystem.exists(plan)
       if (exists) {
       if (exists) {
         const part = await Session.updatePart({
         const part = await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMessage.info.id,
           messageID: userMessage.info.id,
           sessionID: userMessage.info.sessionID,
           sessionID: userMessage.info.sessionID,
           type: "text",
           type: "text",
@@ -1385,7 +1384,7 @@ export namespace SessionPrompt {
       const exists = await Filesystem.exists(plan)
       const exists = await Filesystem.exists(plan)
       if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true })
       if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true })
       const part = await Session.updatePart({
       const part = await Session.updatePart({
-        id: Identifier.ascending("part"),
+        id: PartID.ascending(),
         messageID: userMessage.info.id,
         messageID: userMessage.info.id,
         sessionID: userMessage.info.sessionID,
         sessionID: userMessage.info.sessionID,
         type: "text",
         type: "text",
@@ -1520,7 +1519,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
     await Session.updateMessage(userMsg)
     await Session.updateMessage(userMsg)
     const userPart: MessageV2.Part = {
     const userPart: MessageV2.Part = {
       type: "text",
       type: "text",
-      id: Identifier.ascending("part"),
+      id: PartID.ascending(),
       messageID: userMsg.id,
       messageID: userMsg.id,
       sessionID: input.sessionID,
       sessionID: input.sessionID,
       text: "The following tool was executed by the user",
       text: "The following tool was executed by the user",
@@ -1555,7 +1554,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
     await Session.updateMessage(msg)
     await Session.updateMessage(msg)
     const part: MessageV2.Part = {
     const part: MessageV2.Part = {
       type: "tool",
       type: "tool",
-      id: Identifier.ascending("part"),
+      id: PartID.ascending(),
       messageID: msg.id,
       messageID: msg.id,
       sessionID: input.sessionID,
       sessionID: input.sessionID,
       tool: "bash",
       tool: "bash",

+ 2 - 3
packages/opencode/src/session/revert.ts

@@ -1,6 +1,5 @@
 import z from "zod"
 import z from "zod"
-import { Identifier } from "../id/id"
-import { SessionID, MessageID } from "./schema"
+import { SessionID, MessageID, PartID } from "./schema"
 import { Snapshot } from "../snapshot"
 import { Snapshot } from "../snapshot"
 import { MessageV2 } from "./message-v2"
 import { MessageV2 } from "./message-v2"
 import { Session } from "."
 import { Session } from "."
@@ -18,7 +17,7 @@ export namespace SessionRevert {
   export const RevertInput = z.object({
   export const RevertInput = z.object({
     sessionID: SessionID.zod,
     sessionID: SessionID.zod,
     messageID: MessageID.zod,
     messageID: MessageID.zod,
-    partID: Identifier.schema("part").optional(),
+    partID: PartID.zod.optional(),
   })
   })
   export type RevertInput = z.infer<typeof RevertInput>
   export type RevertInput = z.infer<typeof RevertInput>
 
 

+ 12 - 0
packages/opencode/src/session/schema.ts

@@ -27,3 +27,15 @@ export const MessageID = messageIdSchema.pipe(
     zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()),
     zod: z.string().startsWith("msg").pipe(z.custom<MessageID>()),
   })),
   })),
 )
 )
+
+const partIdSchema = Schema.String.pipe(Schema.brand("PartId"))
+
+export type PartID = typeof partIdSchema.Type
+
+export const PartID = partIdSchema.pipe(
+  withStatics((schema: typeof partIdSchema) => ({
+    make: (id: string) => schema.makeUnsafe(id),
+    ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("part", id)),
+    zod: z.string().startsWith("prt").pipe(z.custom<PartID>()),
+  })),
+)

+ 3 - 3
packages/opencode/src/session/session.sql.ts

@@ -4,7 +4,7 @@ import type { MessageV2 } from "./message-v2"
 import type { Snapshot } from "../snapshot"
 import type { Snapshot } from "../snapshot"
 import type { PermissionNext } from "../permission/next"
 import type { PermissionNext } from "../permission/next"
 import type { ProjectID } from "../project/schema"
 import type { ProjectID } from "../project/schema"
-import type { SessionID, MessageID } from "./schema"
+import type { SessionID, MessageID, PartID } from "./schema"
 import type { WorkspaceID } from "../control-plane/schema"
 import type { WorkspaceID } from "../control-plane/schema"
 import { Timestamps } from "../storage/schema.sql"
 import { Timestamps } from "../storage/schema.sql"
 
 
@@ -30,7 +30,7 @@ export const SessionTable = sqliteTable(
     summary_deletions: integer(),
     summary_deletions: integer(),
     summary_files: integer(),
     summary_files: integer(),
     summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(),
     summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(),
-    revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: string; snapshot?: string; diff?: string }>(),
+    revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(),
     permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(),
     permission: text({ mode: "json" }).$type<PermissionNext.Ruleset>(),
     ...Timestamps,
     ...Timestamps,
     time_compacting: integer(),
     time_compacting: integer(),
@@ -60,7 +60,7 @@ export const MessageTable = sqliteTable(
 export const PartTable = sqliteTable(
 export const PartTable = sqliteTable(
   "part",
   "part",
   {
   {
-    id: text().primaryKey(),
+    id: text().$type<PartID>().primaryKey(),
     message_id: text()
     message_id: text()
       .$type<MessageID>()
       .$type<MessageID>()
       .notNull()
       .notNull()

+ 4 - 4
packages/opencode/src/tool/batch.ts

@@ -31,7 +31,7 @@ export const BatchTool = Tool.define("batch", async () => {
     },
     },
     async execute(params, ctx) {
     async execute(params, ctx) {
       const { Session } = await import("../session")
       const { Session } = await import("../session")
-      const { Identifier } = await import("../id/id")
+      const { PartID } = await import("../session/schema")
 
 
       const toolCalls = params.tool_calls.slice(0, 25)
       const toolCalls = params.tool_calls.slice(0, 25)
       const discardedCalls = params.tool_calls.slice(25)
       const discardedCalls = params.tool_calls.slice(25)
@@ -42,7 +42,7 @@ export const BatchTool = Tool.define("batch", async () => {
 
 
       const executeCall = async (call: (typeof toolCalls)[0]) => {
       const executeCall = async (call: (typeof toolCalls)[0]) => {
         const callStartTime = Date.now()
         const callStartTime = Date.now()
-        const partID = Identifier.ascending("part")
+        const partID = PartID.ascending()
 
 
         try {
         try {
           if (DISALLOWED.has(call.tool)) {
           if (DISALLOWED.has(call.tool)) {
@@ -79,7 +79,7 @@ export const BatchTool = Tool.define("batch", async () => {
           const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
           const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
           const attachments = result.attachments?.map((attachment) => ({
           const attachments = result.attachments?.map((attachment) => ({
             ...attachment,
             ...attachment,
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             sessionID: ctx.sessionID,
             sessionID: ctx.sessionID,
             messageID: ctx.messageID,
             messageID: ctx.messageID,
           }))
           }))
@@ -134,7 +134,7 @@ export const BatchTool = Tool.define("batch", async () => {
       // Add discarded calls as errors
       // Add discarded calls as errors
       const now = Date.now()
       const now = Date.now()
       for (const call of discardedCalls) {
       for (const call of discardedCalls) {
-        const partID = Identifier.ascending("part")
+        const partID = PartID.ascending()
         await Session.updatePart({
         await Session.updatePart({
           id: partID,
           id: partID,
           messageID: ctx.messageID,
           messageID: ctx.messageID,

+ 3 - 4
packages/opencode/src/tool/plan.ts

@@ -4,10 +4,9 @@ import { Tool } from "./tool"
 import { Question } from "../question"
 import { Question } from "../question"
 import { Session } from "../session"
 import { Session } from "../session"
 import { MessageV2 } from "../session/message-v2"
 import { MessageV2 } from "../session/message-v2"
-import { Identifier } from "../id/id"
 import { Provider } from "../provider/provider"
 import { Provider } from "../provider/provider"
 import { Instance } from "../project/instance"
 import { Instance } from "../project/instance"
-import { type SessionID, MessageID } from "../session/schema"
+import { type SessionID, MessageID, PartID } from "../session/schema"
 import EXIT_DESCRIPTION from "./plan-exit.txt"
 import EXIT_DESCRIPTION from "./plan-exit.txt"
 
 
 async function getLastModel(sessionID: SessionID) {
 async function getLastModel(sessionID: SessionID) {
@@ -56,7 +55,7 @@ export const PlanExitTool = Tool.define("plan_exit", {
     }
     }
     await Session.updateMessage(userMsg)
     await Session.updateMessage(userMsg)
     await Session.updatePart({
     await Session.updatePart({
-      id: Identifier.ascending("part"),
+      id: PartID.ascending(),
       messageID: userMsg.id,
       messageID: userMsg.id,
       sessionID: ctx.sessionID,
       sessionID: ctx.sessionID,
       type: "text",
       type: "text",
@@ -114,7 +113,7 @@ export const PlanEnterTool = Tool.define("plan_enter", {
     }
     }
     await Session.updateMessage(userMsg)
     await Session.updateMessage(userMsg)
     await Session.updatePart({
     await Session.updatePart({
-      id: Identifier.ascending("part"),
+      id: PartID.ascending(),
       messageID: userMsg.id,
       messageID: userMsg.id,
       sessionID: ctx.sessionID,
       sessionID: ctx.sessionID,
       type: "text",
       type: "text",

+ 7 - 7
packages/opencode/test/cli/github-action.test.ts

@@ -1,12 +1,12 @@
 import { test, expect, describe } from "bun:test"
 import { test, expect, describe } from "bun:test"
 import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
 import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
 import type { MessageV2 } from "../../src/session/message-v2"
 import type { MessageV2 } from "../../src/session/message-v2"
-import { SessionID, MessageID } from "../../src/session/schema"
+import { SessionID, MessageID, PartID } from "../../src/session/schema"
 
 
 // Helper to create minimal valid parts
 // Helper to create minimal valid parts
 function createTextPart(text: string): MessageV2.Part {
 function createTextPart(text: string): MessageV2.Part {
   return {
   return {
-    id: "1",
+    id: PartID.ascending(),
     sessionID: SessionID.make("s"),
     sessionID: SessionID.make("s"),
     messageID: MessageID.make("m"),
     messageID: MessageID.make("m"),
     type: "text" as const,
     type: "text" as const,
@@ -16,7 +16,7 @@ function createTextPart(text: string): MessageV2.Part {
 
 
 function createReasoningPart(text: string): MessageV2.Part {
 function createReasoningPart(text: string): MessageV2.Part {
   return {
   return {
-    id: "1",
+    id: PartID.ascending(),
     sessionID: SessionID.make("s"),
     sessionID: SessionID.make("s"),
     messageID: MessageID.make("m"),
     messageID: MessageID.make("m"),
     type: "reasoning" as const,
     type: "reasoning" as const,
@@ -28,7 +28,7 @@ function createReasoningPart(text: string): MessageV2.Part {
 function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part {
 function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part {
   if (status === "completed") {
   if (status === "completed") {
     return {
     return {
-      id: "1",
+      id: PartID.ascending(),
       sessionID: SessionID.make("s"),
       sessionID: SessionID.make("s"),
       messageID: MessageID.make("m"),
       messageID: MessageID.make("m"),
       type: "tool" as const,
       type: "tool" as const,
@@ -45,7 +45,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
     }
     }
   }
   }
   return {
   return {
-    id: "1",
+    id: PartID.ascending(),
     sessionID: SessionID.make("s"),
     sessionID: SessionID.make("s"),
     messageID: MessageID.make("m"),
     messageID: MessageID.make("m"),
     type: "tool" as const,
     type: "tool" as const,
@@ -61,7 +61,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
 
 
 function createStepStartPart(): MessageV2.Part {
 function createStepStartPart(): MessageV2.Part {
   return {
   return {
-    id: "1",
+    id: PartID.ascending(),
     sessionID: SessionID.make("s"),
     sessionID: SessionID.make("s"),
     messageID: MessageID.make("m"),
     messageID: MessageID.make("m"),
     type: "step-start" as const,
     type: "step-start" as const,
@@ -70,7 +70,7 @@ function createStepStartPart(): MessageV2.Part {
 
 
 function createStepFinishPart(): MessageV2.Part {
 function createStepFinishPart(): MessageV2.Part {
   return {
   return {
-    id: "1",
+    id: PartID.ascending(),
     sessionID: SessionID.make("s"),
     sessionID: SessionID.make("s"),
     messageID: MessageID.make("m"),
     messageID: MessageID.make("m"),
     type: "step-finish" as const,
     type: "step-finish" as const,

+ 2 - 2
packages/opencode/test/session/message-v2.test.ts

@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
 import { APICallError } from "ai"
 import { APICallError } from "ai"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageV2 } from "../../src/session/message-v2"
 import type { Provider } from "../../src/provider/provider"
 import type { Provider } from "../../src/provider/provider"
-import { SessionID, MessageID } from "../../src/session/schema"
+import { SessionID, MessageID, PartID } from "../../src/session/schema"
 
 
 const sessionID = SessionID.make("session")
 const sessionID = SessionID.make("session")
 const model: Provider.Model = {
 const model: Provider.Model = {
@@ -98,7 +98,7 @@ function assistantInfo(
 
 
 function basePart(messageID: string, id: string) {
 function basePart(messageID: string, id: string) {
   return {
   return {
-    id,
+    id: PartID.make(id),
     sessionID,
     sessionID,
     messageID: MessageID.make(messageID),
     messageID: MessageID.make(messageID),
   }
   }

+ 7 - 8
packages/opencode/test/session/revert-compact.test.ts

@@ -6,8 +6,7 @@ import { SessionCompaction } from "../../src/session/compaction"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageV2 } from "../../src/session/message-v2"
 import { Log } from "../../src/util/log"
 import { Log } from "../../src/util/log"
 import { Instance } from "../../src/project/instance"
 import { Instance } from "../../src/project/instance"
-import { Identifier } from "../../src/id/id"
-import { MessageID } from "../../src/session/schema"
+import { MessageID, PartID } from "../../src/session/schema"
 import { tmpdir } from "../fixture/fixture"
 import { tmpdir } from "../fixture/fixture"
 
 
 const projectRoot = path.join(__dirname, "../..")
 const projectRoot = path.join(__dirname, "../..")
@@ -40,7 +39,7 @@ describe("revert + compact workflow", () => {
 
 
         // Add a text part to the user message
         // Add a text part to the user message
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMsg1.id,
           messageID: userMsg1.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",
@@ -77,7 +76,7 @@ describe("revert + compact workflow", () => {
 
 
         // Add a text part to the assistant message
         // Add a text part to the assistant message
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: assistantMsg1.id,
           messageID: assistantMsg1.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",
@@ -100,7 +99,7 @@ describe("revert + compact workflow", () => {
         })
         })
 
 
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMsg2.id,
           messageID: userMsg2.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",
@@ -136,7 +135,7 @@ describe("revert + compact workflow", () => {
         await Session.updateMessage(assistantMsg2)
         await Session.updateMessage(assistantMsg2)
 
 
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: assistantMsg2.id,
           messageID: assistantMsg2.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",
@@ -215,7 +214,7 @@ describe("revert + compact workflow", () => {
         })
         })
 
 
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: userMsg.id,
           messageID: userMsg.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",
@@ -250,7 +249,7 @@ describe("revert + compact workflow", () => {
         await Session.updateMessage(assistantMsg)
         await Session.updateMessage(assistantMsg)
 
 
         await Session.updatePart({
         await Session.updatePart({
-          id: Identifier.ascending("part"),
+          id: PartID.ascending(),
           messageID: assistantMsg.id,
           messageID: assistantMsg.id,
           sessionID,
           sessionID,
           type: "text",
           type: "text",

+ 2 - 3
packages/opencode/test/session/session.test.ts

@@ -5,8 +5,7 @@ import { Bus } from "../../src/bus"
 import { Log } from "../../src/util/log"
 import { Log } from "../../src/util/log"
 import { Instance } from "../../src/project/instance"
 import { Instance } from "../../src/project/instance"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageV2 } from "../../src/session/message-v2"
-import { Identifier } from "../../src/id/id"
-import { MessageID } from "../../src/session/schema"
+import { MessageID, PartID } from "../../src/session/schema"
 
 
 const projectRoot = path.join(__dirname, "../..")
 const projectRoot = path.join(__dirname, "../..")
 Log.init({ print: false })
 Log.init({ print: false })
@@ -108,7 +107,7 @@ describe("step-finish token propagation via Bus event", () => {
           }
           }
 
 
           const partInput = {
           const partInput = {
-            id: Identifier.ascending("part"),
+            id: PartID.ascending(),
             messageID,
             messageID,
             sessionID: session.id,
             sessionID: session.id,
             type: "step-finish" as const,
             type: "step-finish" as const,

+ 4 - 4
packages/opencode/test/storage/json-migration.test.ts

@@ -11,7 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql"
 import { ProjectID } from "../../src/project/schema"
 import { ProjectID } from "../../src/project/schema"
 import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql"
 import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql"
 import { SessionShareTable } from "../../src/share/share.sql"
 import { SessionShareTable } from "../../src/share/share.sql"
-import { SessionID, MessageID } from "../../src/session/schema"
+import { SessionID, MessageID, PartID } from "../../src/session/schema"
 
 
 // Test fixtures
 // Test fixtures
 const fixtures = {
 const fixtures = {
@@ -259,7 +259,7 @@ describe("JSON to SQLite migration", () => {
 
 
     const parts = db.select().from(PartTable).all()
     const parts = db.select().from(PartTable).all()
     expect(parts.length).toBe(1)
     expect(parts.length).toBe(1)
-    expect(parts[0].id).toBe("prt_testabc123")
+    expect(parts[0].id).toBe(PartID.make("prt_testabc123"))
   })
   })
 
 
   test("migrates legacy parts without ids in body", async () => {
   test("migrates legacy parts without ids in body", async () => {
@@ -302,7 +302,7 @@ describe("JSON to SQLite migration", () => {
 
 
     const parts = db.select().from(PartTable).all()
     const parts = db.select().from(PartTable).all()
     expect(parts.length).toBe(1)
     expect(parts.length).toBe(1)
-    expect(parts[0].id).toBe("prt_testabc123")
+    expect(parts[0].id).toBe(PartID.make("prt_testabc123"))
     expect(parts[0].message_id).toBe(MessageID.make("msg_test789ghi"))
     expect(parts[0].message_id).toBe(MessageID.make("msg_test789ghi"))
     expect(parts[0].session_id).toBe(SessionID.make("ses_test456def"))
     expect(parts[0].session_id).toBe(SessionID.make("ses_test456def"))
     expect(parts[0].data).not.toHaveProperty("id")
     expect(parts[0].data).not.toHaveProperty("id")
@@ -374,7 +374,7 @@ describe("JSON to SQLite migration", () => {
     const db = drizzle({ client: sqlite })
     const db = drizzle({ client: sqlite })
     const parts = db.select().from(PartTable).all()
     const parts = db.select().from(PartTable).all()
     expect(parts.length).toBe(1)
     expect(parts.length).toBe(1)
-    expect(parts[0].id).toBe("prt_from_filename") // Uses filename, not JSON id
+    expect(parts[0].id).toBe(PartID.make("prt_from_filename")) // Uses filename, not JSON id
     expect(parts[0].message_id).toBe(MessageID.make("msg_realmsgid")) // Uses parent dir, not JSON messageID
     expect(parts[0].message_id).toBe(MessageID.make("msg_realmsgid")) // Uses parent dir, not JSON messageID
   })
   })