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

This commit is contained in:
Kit Langton
2026-03-11 19:40:50 -04:00
committed by GitHub
parent d26c6f80e1
commit 090f636354
21 changed files with 102 additions and 97 deletions

View File

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

View File

@@ -4,8 +4,7 @@ import { Agent } from "../../../agent/agent"
import { Provider } from "../../../provider/provider"
import { Session } from "../../../session"
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 { Instance } from "../../../project/instance"
import { PermissionNext } from "../../../permission/next"
@@ -151,7 +150,7 @@ async function createToolContext(agent: Agent.Info) {
return {
sessionID: session.id,
messageID,
callID: Identifier.ascending("part"),
callID: PartID.ascending(),
agent: agent.name,
abort: new AbortController().signal,
messages: [],

View File

@@ -23,8 +23,7 @@ import { Instance } from "@/project/instance"
import { bootstrap } from "../bootstrap"
import { Session } from "../../session"
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 { Bus } from "../../bus"
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"
parts: [
{
id: Identifier.ascending("part"),
id: PartID.ascending(),
type: "text",
text: message,
},
...files.flatMap((f) => [
{
id: Identifier.ascending("part"),
id: PartID.ascending(),
type: "file" as const,
mime: f.mime,
url: `data:${f.mime};base64,${f.content}`,
@@ -999,7 +998,7 @@ export const GithubRunCommand = cmd({
tools: { "*": false }, // Disable all tools to force text response
parts: [
{
id: Identifier.ascending("part"),
id: PartID.ascending(),
type: "text",
text: "Summarize the actions (tool calls & reasoning) you did for the user in 1-2 sentences.",
},

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
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 { Provider } from "../provider/provider"
import { MessageV2 } from "./message-v2"
@@ -256,7 +255,7 @@ When constructing the summary, try to stick to this template:
: part
await Session.updatePart({
...replayPart,
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: replayMsg.id,
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."
await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: continueMsg.id,
sessionID: input.sessionID,
type: "text",
@@ -317,7 +316,7 @@ When constructing the summary, try to stick to this template:
},
})
await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: msg.id,
sessionID: msg.sessionID,
type: "compaction",

View File

@@ -7,7 +7,6 @@ import z from "zod"
import { type ProviderMetadata } from "ai"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
import { Identifier } from "../id/id"
import { Installation } from "../installation"
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 { ProjectID } from "../project/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 { PermissionNext } from "@/permission/next"
@@ -152,7 +151,7 @@ export namespace Session {
revert: z
.object({
messageID: MessageID.zod,
partID: z.string().optional(),
partID: PartID.zod.optional(),
snapshot: z.string().optional(),
diff: z.string().optional(),
})
@@ -269,7 +268,7 @@ export namespace Session {
for (const part of msg.parts) {
await updatePart({
...part,
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: cloned.id,
sessionID: session.id,
})
@@ -731,7 +730,7 @@ export namespace Session {
z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod,
partID: Identifier.schema("part"),
partID: PartID.zod,
}),
async (input) => {
Database.use((db) => {
@@ -779,7 +778,7 @@ export namespace Session {
z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod,
partID: z.string(),
partID: PartID.zod,
field: z.string(),
delta: z.string(),
}),

View File

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

View File

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

View File

@@ -3,8 +3,7 @@ import os from "os"
import fs from "fs/promises"
import z from "zod"
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 { Log } from "../util/log"
import { SessionRevert } from "./revert"
@@ -380,7 +379,7 @@ export namespace SessionPrompt {
},
})) as MessageV2.Assistant
let part = (await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: assistantMessage.id,
sessionID: assistantMessage.sessionID,
type: "tool",
@@ -449,7 +448,7 @@ export namespace SessionPrompt {
})
const attachments = result?.attachments?.map((attachment) => ({
...attachment,
id: Identifier.ascending("part"),
id: PartID.ascending(),
sessionID,
messageID: assistantMessage.id,
}))
@@ -515,7 +514,7 @@ export namespace SessionPrompt {
}
await Session.updateMessage(summaryUserMsg)
await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: summaryUserMsg.id,
sessionID,
type: "text",
@@ -814,7 +813,7 @@ export namespace SessionPrompt {
...result,
attachments: result.attachments?.map((attachment) => ({
...attachment,
id: Identifier.ascending("part"),
id: PartID.ascending(),
sessionID: ctx.sessionID,
messageID: input.processor.message.id,
})),
@@ -917,7 +916,7 @@ export namespace SessionPrompt {
output: truncated.content,
attachments: attachments.map((attachment) => ({
...attachment,
id: Identifier.ascending("part"),
id: PartID.ascending(),
sessionID: ctx.sessionID,
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
const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
...part,
id: part.id ?? Identifier.ascending("part"),
id: part.id ? PartID.make(part.id) : PartID.ascending(),
})
const parts = await Promise.all(
@@ -1335,7 +1334,7 @@ export namespace SessionPrompt {
if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) {
if (input.agent.name === "plan") {
userMessage.parts.push({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
@@ -1346,7 +1345,7 @@ export namespace SessionPrompt {
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
if (wasPlan && input.agent.name === "build") {
userMessage.parts.push({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
@@ -1366,7 +1365,7 @@ export namespace SessionPrompt {
const exists = await Filesystem.exists(plan)
if (exists) {
const part = await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
@@ -1385,7 +1384,7 @@ export namespace SessionPrompt {
const exists = await Filesystem.exists(plan)
if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true })
const part = await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
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)
const userPart: MessageV2.Part = {
type: "text",
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMsg.id,
sessionID: input.sessionID,
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)
const part: MessageV2.Part = {
type: "tool",
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: msg.id,
sessionID: input.sessionID,
tool: "bash",

View File

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

View File

@@ -27,3 +27,15 @@ export const MessageID = messageIdSchema.pipe(
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>()),
})),
)

View File

@@ -4,7 +4,7 @@ import type { MessageV2 } from "./message-v2"
import type { Snapshot } from "../snapshot"
import type { PermissionNext } from "../permission/next"
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 { Timestamps } from "../storage/schema.sql"
@@ -30,7 +30,7 @@ export const SessionTable = sqliteTable(
summary_deletions: integer(),
summary_files: integer(),
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>(),
...Timestamps,
time_compacting: integer(),
@@ -60,7 +60,7 @@ export const MessageTable = sqliteTable(
export const PartTable = sqliteTable(
"part",
{
id: text().primaryKey(),
id: text().$type<PartID>().primaryKey(),
message_id: text()
.$type<MessageID>()
.notNull()

View File

@@ -31,7 +31,7 @@ export const BatchTool = Tool.define("batch", async () => {
},
async execute(params, ctx) {
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 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 callStartTime = Date.now()
const partID = Identifier.ascending("part")
const partID = PartID.ascending()
try {
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 attachments = result.attachments?.map((attachment) => ({
...attachment,
id: Identifier.ascending("part"),
id: PartID.ascending(),
sessionID: ctx.sessionID,
messageID: ctx.messageID,
}))
@@ -134,7 +134,7 @@ export const BatchTool = Tool.define("batch", async () => {
// Add discarded calls as errors
const now = Date.now()
for (const call of discardedCalls) {
const partID = Identifier.ascending("part")
const partID = PartID.ascending()
await Session.updatePart({
id: partID,
messageID: ctx.messageID,

View File

@@ -4,10 +4,9 @@ import { Tool } from "./tool"
import { Question } from "../question"
import { Session } from "../session"
import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Provider } from "../provider/provider"
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"
async function getLastModel(sessionID: SessionID) {
@@ -56,7 +55,7 @@ export const PlanExitTool = Tool.define("plan_exit", {
}
await Session.updateMessage(userMsg)
await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMsg.id,
sessionID: ctx.sessionID,
type: "text",
@@ -114,7 +113,7 @@ export const PlanEnterTool = Tool.define("plan_enter", {
}
await Session.updateMessage(userMsg)
await Session.updatePart({
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID: userMsg.id,
sessionID: ctx.sessionID,
type: "text",

View File

@@ -1,12 +1,12 @@
import { test, expect, describe } from "bun:test"
import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github"
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
function createTextPart(text: string): MessageV2.Part {
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
type: "text" as const,
@@ -16,7 +16,7 @@ function createTextPart(text: string): MessageV2.Part {
function createReasoningPart(text: string): MessageV2.Part {
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
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 {
if (status === "completed") {
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
type: "tool" as const,
@@ -45,7 +45,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
}
}
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
type: "tool" as const,
@@ -61,7 +61,7 @@ function createToolPart(tool: string, title: string, status: "completed" | "runn
function createStepStartPart(): MessageV2.Part {
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
type: "step-start" as const,
@@ -70,7 +70,7 @@ function createStepStartPart(): MessageV2.Part {
function createStepFinishPart(): MessageV2.Part {
return {
id: "1",
id: PartID.ascending(),
sessionID: SessionID.make("s"),
messageID: MessageID.make("m"),
type: "step-finish" as const,

View File

@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
import { APICallError } from "ai"
import { MessageV2 } from "../../src/session/message-v2"
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 model: Provider.Model = {
@@ -98,7 +98,7 @@ function assistantInfo(
function basePart(messageID: string, id: string) {
return {
id,
id: PartID.make(id),
sessionID,
messageID: MessageID.make(messageID),
}

View File

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

View File

@@ -5,8 +5,7 @@ import { Bus } from "../../src/bus"
import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance"
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, "../..")
Log.init({ print: false })
@@ -108,7 +107,7 @@ describe("step-finish token propagation via Bus event", () => {
}
const partInput = {
id: Identifier.ascending("part"),
id: PartID.ascending(),
messageID,
sessionID: session.id,
type: "step-finish" as const,

View File

@@ -11,7 +11,7 @@ import { ProjectTable } from "../../src/project/project.sql"
import { ProjectID } from "../../src/project/schema"
import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.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
const fixtures = {
@@ -259,7 +259,7 @@ describe("JSON to SQLite migration", () => {
const parts = db.select().from(PartTable).all()
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 () => {
@@ -302,7 +302,7 @@ describe("JSON to SQLite migration", () => {
const parts = db.select().from(PartTable).all()
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].session_id).toBe(SessionID.make("ses_test456def"))
expect(parts[0].data).not.toHaveProperty("id")
@@ -374,7 +374,7 @@ describe("JSON to SQLite migration", () => {
const db = drizzle({ client: sqlite })
const parts = db.select().from(PartTable).all()
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
})