diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 922299bec198..0b32fdd17c33 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -325,7 +325,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => { return } - await sdk.client.session.revert({ sessionID, messageID: next.id }) + const mode = info()?.revert?.mode + await sdk.client.session.revert({ sessionID, messageID: next.id, mode }) const prev = findLast(userMessages(), (x) => x.id < next.id) setActiveMessage(prev) } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx index aeea2f52ad0b..661cd7d27cf4 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/dialog-message.tsx @@ -17,40 +17,51 @@ export function DialogMessage(props: { const message = createMemo(() => sync.data.message[props.sessionID]?.find((x) => x.id === props.messageID)) const route = useRoute() + function revert(mode: "conversation" | "conversation_and_code") { + return (dialog: { clear: () => void }) => { + const msg = message() + if (!msg) return + + sdk.client.session.revert({ + sessionID: props.sessionID, + messageID: msg.id, + mode, + }) + + if (props.setPrompt) { + const parts = sync.data.part[msg.id] + const promptInfo = parts.reduce( + (agg, part) => { + if (part.type === "text") { + if (!part.synthetic) agg.input += part.text + } + if (part.type === "file") agg.parts.push(part) + return agg + }, + { input: "", parts: [] as PromptInfo["parts"] }, + ) + props.setPrompt(promptInfo) + } + + dialog.clear() + } + } + return ( { - const msg = message() - if (!msg) return - - void sdk.client.session.revert({ - sessionID: props.sessionID, - messageID: msg.id, - }) - - if (props.setPrompt) { - const parts = sync.data.part[msg.id] - const promptInfo = parts.reduce( - (agg, part) => { - if (part.type === "text") { - if (!part.synthetic) agg.input += part.text - } - if (part.type === "file") agg.parts.push(strip(part)) - return agg - }, - { input: "", parts: [] as PromptInfo["parts"] }, - ) - props.setPrompt(promptInfo) - } - - dialog.clear() - }, + onSelect: revert("conversation_and_code"), }, { title: "Copy", @@ -88,7 +99,7 @@ export function DialogMessage(props: { if (part.type === "text") { if (!part.synthetic) agg.input += part.text } - if (part.type === "file") agg.parts.push(part) + if (part.type === "file") agg.parts.push(strip(part)) return agg }, { input: "", parts: [] as PromptInfo["parts"] }, diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 183ce52cde4e..47fc7ef8a7a8 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -596,9 +596,11 @@ export function Session() { prompt?.set({ input: "", parts: [] }) return } + const mode = session()?.revert?.mode void sdk.client.session.revert({ sessionID: route.sessionID, messageID: message.id, + mode, }) }, }, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 911f58efd0b9..e09368264f42 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -980,6 +980,85 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( tools, }, ), + } + + export const stream = fn(Identifier.schema("session"), async function* (sessionID) { + const size = 50 + let offset = 0 + while (true) { + const rows = Database.use((db) => + db + .select() + .from(MessageTable) + .where(eq(MessageTable.session_id, sessionID)) + .orderBy(desc(MessageTable.time_created), desc(MessageTable.id)) + .limit(size) + .offset(offset) + .all(), + ) + if (rows.length === 0) break + + const ids = rows.map((row) => row.id) + const partsByMessage = new Map() + if (ids.length > 0) { + const partRows = Database.use((db) => + db + .select() + .from(PartTable) + .where(inArray(PartTable.message_id, ids)) + .orderBy(PartTable.message_id, PartTable.id) + .all(), + ) + for (const row of partRows) { + const part = { + ...row.data, + id: row.id, + sessionID: row.session_id, + messageID: row.message_id, + } as MessageV2.Part + const list = partsByMessage.get(row.message_id) + if (list) list.push(part) + else partsByMessage.set(row.message_id, [part]) + } + } + + for (const row of rows) { + const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info + yield { + info, + parts: partsByMessage.get(row.id) ?? [], + } + } + + offset += rows.length + if (rows.length < size) break + } + }) + + export const parts = fn(Identifier.schema("message"), async (message_id) => { + const rows = Database.use((db) => + db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(), + ) + return rows.map( + (row) => ({ ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id }) as MessageV2.Part, + ) + }) + + export const get = fn( + z.object({ + sessionID: Identifier.schema("session"), + messageID: Identifier.schema("message"), + }), + async (input): Promise => { + const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get()) + if (!row) throw new Error(`Message not found: ${input.messageID}`) + const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info + return { + info, + parts: await parts(input.messageID), + } + }, +>>>>>>> 01d5f3f (fix(session): deterministic message ordering to fix revert flakiness) ) }) diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index da9952ccb245..78d14b8b4f9d 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -18,6 +18,7 @@ export const RevertInput = Schema.Struct({ sessionID: SessionID, messageID: MessageID, partID: Schema.optional(PartID), + mode: Schema.optional(Schema.Literal("conversation", "conversation_and_code")), }).pipe(withStatics((s) => ({ zod: zod(s) }))) export type RevertInput = Schema.Schema.Type @@ -45,6 +46,8 @@ export const layer = Layer.effect( let lastUser: MessageV2.User | undefined const session = yield* sessions.get(input.sessionID) + const mode = input.mode ?? session.revert?.mode ?? "conversation_and_code" + let rev: Session.Info["revert"] const patches: Snapshot.Patch[] = [] for (const msg of all) { @@ -71,17 +74,30 @@ export const layer = Layer.effect( if (!rev) return session + const range = all.filter((msg) => msg.info.id >= rev!.messageID) + const diffs = yield* summary.computeDiff({ messages: range }) + + if (mode === "conversation") { + return yield* sessions.setRevert({ + sessionID: input.sessionID, + revert: { ...rev, mode }, + summary: { + additions: diffs.reduce((sum, x) => sum + x.additions, 0), + deletions: diffs.reduce((sum, x) => sum + x.deletions, 0), + files: diffs.length, + }, + }) + } + rev.snapshot = session.revert?.snapshot ?? (yield* snap.track()) if (session.revert?.snapshot) yield* snap.restore(session.revert.snapshot) yield* snap.revert(patches) if (rev.snapshot) rev.diff = yield* snap.diff(rev.snapshot as string) - const range = all.filter((msg) => msg.info.id >= rev!.messageID) - const diffs = yield* summary.computeDiff({ messages: range }) yield* storage.write(["session_diff", input.sessionID], diffs).pipe(Effect.ignore) yield* bus.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs }) yield* sessions.setRevert({ sessionID: input.sessionID, - revert: rev, + revert: { ...rev, mode }, summary: { additions: diffs.reduce((sum, x) => sum + x.additions, 0), deletions: diffs.reduce((sum, x) => sum + x.deletions, 0), diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 863fb21d65c7..278db9dd44b2 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -32,7 +32,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?: PartID; snapshot?: string; diff?: string }>(), + revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string; mode?: "conversation" | "conversation_and_code" }>(), permission: text({ mode: "json" }).$type(), ...Timestamps, time_compacting: integer(), diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 1be5dfffd42d..11699d46ab84 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -154,6 +154,7 @@ const Revert = Schema.Struct({ partID: optionalOmitUndefined(PartID), snapshot: optionalOmitUndefined(Schema.String), diff: optionalOmitUndefined(Schema.String), + mode: optionalOmitUndefined(Schema.Literal("conversation", "conversation_and_code")), }) export const Info = Schema.Struct({ diff --git a/packages/opencode/test/session/revert-mode.test.ts b/packages/opencode/test/session/revert-mode.test.ts new file mode 100644 index 000000000000..8be2a178c6ce --- /dev/null +++ b/packages/opencode/test/session/revert-mode.test.ts @@ -0,0 +1,628 @@ +import { afterEach, beforeEach, expect, test } from "bun:test" +import path from "path" +import { Session } from "../../src/session" +import { SessionRevert } from "../../src/session/revert" +import { MessageV2 } from "../../src/session/message-v2" +import { Instance } from "../../src/project/instance" +import { Identifier } from "../../src/id/id" +import { Snapshot } from "../../src/snapshot" +import { tmpdir } from "../fixture/fixture" + +let cfg: string | undefined + +beforeEach(() => { + cfg = process.env.OPENCODE_CONFIG_CONTENT + process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({ snapshot: true }) +}) + +afterEach(() => { + if (cfg === undefined) { + delete process.env.OPENCODE_CONFIG_CONTENT + return + } + process.env.OPENCODE_CONFIG_CONTENT = cfg +}) + +test("conversation mode reverts messages but keeps files", async () => { + await using tmp = await tmpdir({ git: true, config: { snapshot: true } }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + // Create a session + const session = await Session.create({}) + const sessionID = session.id + + // Create a user message with file modification intent + const userMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + // Add text part to user message + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID, + type: "text", + text: "Modify test.txt", + }) + + // Create assistant response with patch + const assistantMsg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: userMsg.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(assistantMsg) + + // Create test file + const filePath = path.join(tmp.path, "test.txt") + const originalContent = "ORIGINAL" + await Bun.write(filePath, originalContent) + + // Add patch part to assistant message + const patchPart: MessageV2.PatchPart = { + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg.id, + type: "patch", + hash: "abc123", + files: ["test.txt"], + } + await Session.updatePart(patchPart) + + // Modify file + const modifiedContent = "MODIFIED" + await Bun.write(filePath, modifiedContent) + + // Verify file is modified + expect(await Bun.file(filePath).text()).toBe(modifiedContent) + + // Revert with conversation mode + await SessionRevert.revert({ + sessionID, + messageID: assistantMsg.id, + mode: "conversation", + }) + + // Verify file content is unchanged + expect(await Bun.file(filePath).text()).toBe(modifiedContent) + + // Verify session.revert.mode is "conversation" + const updatedSession = await Session.get(sessionID) + expect(updatedSession.revert?.mode).toBe("conversation") + }, + }) +}) + +test("default revert reverts code when messages share timestamp", async () => { + await using tmp = await tmpdir({ git: true, config: { snapshot: true } }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await Session.create({}) + const sessionID = session.id + const now = Date.now() + + const userMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: now, + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID, + type: "text", + text: "Modify test.txt", + }) + + const assistantMsg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: userMsg.id, + time: { + created: now, + }, + finish: "end_turn", + } + await Session.updateMessage(assistantMsg) + + const filePath = path.join(tmp.path, "test.txt") + await Bun.write(filePath, "ORIGINAL") + + const hash = await Snapshot.track() + expect(hash).toBeTruthy() + + await Bun.write(filePath, "MODIFIED") + + const patch = await Snapshot.patch(hash!) + expect(patch.files.length).toBeGreaterThan(0) + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg.id, + type: "patch", + hash: patch.hash, + files: patch.files.map((x) => path.relative(tmp.path, x).replaceAll("\\", "/")), + }) + + const messages = await Session.messages({ sessionID }) + expect(messages.map((x) => x.info.id)).toEqual([userMsg.id, assistantMsg.id]) + + await SessionRevert.revert({ + sessionID, + messageID: userMsg.id, + }) + + expect(await Bun.file(filePath).text()).toBe("ORIGINAL") + }, + }) +}) + +test("default revert still reverts conversation and code", async () => { + await using tmp = await tmpdir({ git: true, config: { snapshot: true } }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await Session.create({}) + const sessionID = session.id + + const userMsg = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID, + type: "text", + text: "Modify test.txt", + }) + + const assistantMsg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: userMsg.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(assistantMsg) + + const filePath = path.join(tmp.path, "test.txt") + const originalContent = "ORIGINAL" + await Bun.write(filePath, originalContent) + + const hash = await Snapshot.track() + expect(hash).toBeTruthy() + + const modifiedContent = "MODIFIED" + await Bun.write(filePath, modifiedContent) + + const patch = await Snapshot.patch(hash!) + expect(patch.files.length).toBeGreaterThan(0) + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg.id, + type: "patch", + hash: patch.hash, + files: patch.files.map((x) => path.relative(tmp.path, x).replaceAll("\\", "/")), + }) + + expect(await Bun.file(filePath).text()).toBe(modifiedContent) + + await SessionRevert.revert({ + sessionID, + messageID: userMsg.id, + }) + + expect(await Bun.file(filePath).text()).toBe(originalContent) + + const updatedSession = await Session.get(sessionID) + expect(updatedSession.revert?.mode).toBe("conversation_and_code") + }, + }) +}) + +test("sequential revert keeps prior conversation mode", async () => { + await using tmp = await tmpdir({ git: true, config: { snapshot: true } }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await Session.create({}) + const sessionID = session.id + + const userMsg1 = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: userMsg1.id, + sessionID, + type: "text", + text: "First message", + }) + + const assistantMsg1: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: userMsg1.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(assistantMsg1) + + const filePath = path.join(tmp.path, "test.txt") + const originalContent = "ORIGINAL" + await Bun.write(filePath, originalContent) + + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg1.id, + type: "text", + text: "First response", + }) + + const userMsg2 = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: userMsg2.id, + sessionID, + type: "text", + text: "Second message", + }) + + const assistantMsg2: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: userMsg2.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(assistantMsg2) + + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg2.id, + type: "text", + text: "Second response", + }) + + const snap = await Snapshot.track() + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: assistantMsg2.id, + type: "patch", + hash: snap ?? "", + files: ["test.txt"], + }) + + const modifiedContent = "MODIFIED" + await Bun.write(filePath, modifiedContent) + + await SessionRevert.revert({ + sessionID, + messageID: userMsg2.id, + mode: "conversation", + }) + + let updatedSession = await Session.get(sessionID) + expect(updatedSession.revert?.mode).toBe("conversation") + + await SessionRevert.revert({ + sessionID, + messageID: userMsg1.id, + }) + + updatedSession = await Session.get(sessionID) + expect(updatedSession.revert?.mode).toBe("conversation") + expect(await Bun.file(filePath).text()).toBe(modifiedContent) + }, + }) +}) + +test("redo to prior conversation-only point restores code state", async () => { + await using tmp = await tmpdir({ git: true, config: { snapshot: true } }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await Session.create({}) + const sessionID = session.id + const file = path.join(tmp.path, "test.txt") + await Bun.write(file, "ORIGINAL") + + const user1 = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: user1.id, + sessionID, + type: "text", + text: "first", + }) + + const ass1: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: user1.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(ass1) + + const hash1 = await Snapshot.track() + expect(hash1).toBeTruthy() + await Bun.write(file, "STEP1") + const patch1 = await Snapshot.patch(hash1!) + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: ass1.id, + type: "patch", + hash: patch1.hash, + files: patch1.files.map((x) => path.relative(tmp.path, x).replaceAll("\\", "/")), + }) + + const user2 = await Session.updateMessage({ + id: Identifier.ascending("message"), + role: "user", + sessionID, + agent: "default", + model: { + providerID: "openai", + modelID: "gpt-4", + }, + time: { + created: Date.now(), + }, + }) + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: user2.id, + sessionID, + type: "text", + text: "second", + }) + + const ass2: MessageV2.Assistant = { + id: Identifier.ascending("message"), + role: "assistant", + sessionID, + mode: "default", + agent: "default", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "gpt-4", + providerID: "openai", + parentID: user2.id, + time: { + created: Date.now(), + }, + finish: "end_turn", + } + await Session.updateMessage(ass2) + + const hash2 = await Snapshot.track() + expect(hash2).toBeTruthy() + await Bun.write(file, "STEP2") + const patch2 = await Snapshot.patch(hash2!) + await Session.updatePart({ + id: Identifier.ascending("part"), + sessionID, + messageID: ass2.id, + type: "patch", + hash: patch2.hash, + files: patch2.files.map((x) => path.relative(tmp.path, x).replaceAll("\\", "/")), + }) + + await SessionRevert.revert({ + sessionID, + messageID: user2.id, + mode: "conversation", + }) + expect(await Bun.file(file).text()).toBe("STEP2") + + await SessionRevert.revert({ + sessionID, + messageID: user1.id, + mode: "conversation_and_code", + }) + expect(await Bun.file(file).text()).toBe("ORIGINAL") + + await SessionRevert.revert({ + sessionID, + messageID: user2.id, + mode: "conversation_and_code", + }) + + expect(await Bun.file(file).text()).toBe("STEP2") + expect((await Session.get(sessionID)).revert?.mode).toBe("conversation") + }, + }) +}) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 2da7c865d770..1fb69f5d6032 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -2527,6 +2527,7 @@ export class Session2 extends HeyApiClient { workspace?: string messageID?: string partID?: string + mode?: "conversation" | "conversation_and_code" }, options?: Options, ) { @@ -2540,6 +2541,7 @@ export class Session2 extends HeyApiClient { { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, { in: "body", key: "partID" }, + { in: "body", key: "mode" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index b6ab684678e9..0620d707abcd 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -961,6 +961,7 @@ export type Session = { partID?: string snapshot?: string diff?: string + mode?: "conversation" | "conversation_and_code" } } @@ -1909,6 +1910,7 @@ export type GlobalSession = { partID?: string snapshot?: string diff?: string + mode?: "conversation" | "conversation_and_code" } project: ProjectSummary | null } @@ -4183,6 +4185,7 @@ export type SessionRevertData = { body?: { messageID: string partID?: string + mode?: "conversation" | "conversation_and_code" } path: { sessionID: string