diff --git a/packages/opencode/script/seed-e2e.ts b/packages/opencode/script/seed-e2e.ts index d2333414488..78bbca28661 100644 --- a/packages/opencode/script/seed-e2e.ts +++ b/packages/opencode/script/seed-e2e.ts @@ -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, diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index 47e1d36373f..297a7ec0212 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -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: [], diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index af9ff4a4289..edd9d756109 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -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.", }, diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index a5a21d1c3d7..8bd24cfec1b 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -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, diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 9923cd3fabb..c85426cc247 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/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 { 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, })), ], diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index 26468c52c51..39dd1b580ed 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -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), diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 37a7cad84da..1946eeee96a 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -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", diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 63495cd00cd..275b50e42b7 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -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(), }), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 0fa842b2999..90abf54526a 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -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 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, }), ), } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index c208663ca25..38dac41b058 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -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", diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 70c620dc512..b8be93b6be0 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -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 extends MessageV2.Part ? Omit & { id?: string } : never const assign = (part: Draft): 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", diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 2f25bdfed95..c5c9edbbdfa 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -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 diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index 78a0b35ec14..6504a1b3930 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -27,3 +27,15 @@ export const MessageID = messageIdSchema.pipe( zod: z.string().startsWith("msg").pipe(z.custom()), })), ) + +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()), + })), +) diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 7c456161e15..71abc8516bd 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -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(), - 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(), ...Timestamps, time_compacting: integer(), @@ -60,7 +60,7 @@ export const MessageTable = sqliteTable( export const PartTable = sqliteTable( "part", { - id: text().primaryKey(), + id: text().$type().primaryKey(), message_id: text() .$type() .notNull() diff --git a/packages/opencode/src/tool/batch.ts b/packages/opencode/src/tool/batch.ts index eecbfe29900..0e864b021b9 100644 --- a/packages/opencode/src/tool/batch.ts +++ b/packages/opencode/src/tool/batch.ts @@ -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, diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index 75ae4f5a50e..e91bc3faa22 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -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", diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index 48d7656ee3d..279ed27d082 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -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, diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index af1d0018bcc..1a7c75c05f8 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -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), } diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index 37b3e217717..1a5844bc91a 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/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 { 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", diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index f5512f240ef..23325862233 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -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, diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index f436037158b..a714f114734 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/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 { 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 })