diff --git a/src/hooks/session-recovery/storage.ts b/src/hooks/session-recovery/storage.ts index b9dbccb94..f83dadd49 100644 --- a/src/hooks/session-recovery/storage.ts +++ b/src/hooks/session-recovery/storage.ts @@ -1,7 +1,9 @@ export { generatePartId } from "./storage/part-id" export { getMessageDir } from "./storage/message-dir" export { readMessages } from "./storage/messages-reader" +export { readMessagesFromSDK } from "./storage/messages-reader" export { readParts } from "./storage/parts-reader" +export { readPartsFromSDK } from "./storage/parts-reader" export { hasContent, messageHasContent } from "./storage/part-content" export { injectTextPart } from "./storage/text-part-injector" diff --git a/src/hooks/session-recovery/storage/messages-reader.ts b/src/hooks/session-recovery/storage/messages-reader.ts index ad6c77833..0334a19eb 100644 --- a/src/hooks/session-recovery/storage/messages-reader.ts +++ b/src/hooks/session-recovery/storage/messages-reader.ts @@ -1,9 +1,42 @@ import { existsSync, readdirSync, readFileSync } from "node:fs" import { join } from "node:path" +import type { PluginInput } from "@opencode-ai/plugin" import type { StoredMessageMeta } from "../types" import { getMessageDir } from "./message-dir" +import { isSqliteBackend } from "../../../shared" + +type OpencodeClient = PluginInput["client"] + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function normalizeSDKMessage( + sessionID: string, + value: unknown +): StoredMessageMeta | null { + if (!isRecord(value)) return null + if (typeof value.id !== "string") return null + + const roleValue = value.role + const role: StoredMessageMeta["role"] = roleValue === "assistant" ? "assistant" : "user" + + const created = + isRecord(value.time) && typeof value.time.created === "number" + ? value.time.created + : 0 + + return { + id: value.id, + sessionID, + role, + time: { created }, + } +} export function readMessages(sessionID: string): StoredMessageMeta[] { + if (isSqliteBackend()) return [] + const messageDir = getMessageDir(sessionID) if (!messageDir || !existsSync(messageDir)) return [] @@ -25,3 +58,27 @@ export function readMessages(sessionID: string): StoredMessageMeta[] { return a.id.localeCompare(b.id) }) } + +export async function readMessagesFromSDK( + client: OpencodeClient, + sessionID: string +): Promise { + try { + const response = await client.session.messages({ path: { id: sessionID } }) + const data: unknown = response.data + if (!Array.isArray(data)) return [] + + const messages = data + .map((msg): StoredMessageMeta | null => normalizeSDKMessage(sessionID, msg)) + .filter((msg): msg is StoredMessageMeta => msg !== null) + + return messages.sort((a, b) => { + const aTime = a.time?.created ?? 0 + const bTime = b.time?.created ?? 0 + if (aTime !== bTime) return aTime - bTime + return a.id.localeCompare(b.id) + }) + } catch { + return [] + } +} diff --git a/src/hooks/session-recovery/storage/parts-reader.ts b/src/hooks/session-recovery/storage/parts-reader.ts index c4110a59d..9aca63ad7 100644 --- a/src/hooks/session-recovery/storage/parts-reader.ts +++ b/src/hooks/session-recovery/storage/parts-reader.ts @@ -1,9 +1,29 @@ import { existsSync, readdirSync, readFileSync } from "node:fs" import { join } from "node:path" +import type { PluginInput } from "@opencode-ai/plugin" import { PART_STORAGE } from "../constants" import type { StoredPart } from "../types" +import { isSqliteBackend } from "../../../shared" + +type OpencodeClient = PluginInput["client"] + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function isStoredPart(value: unknown): value is StoredPart { + if (!isRecord(value)) return false + return ( + typeof value.id === "string" && + typeof value.sessionID === "string" && + typeof value.messageID === "string" && + typeof value.type === "string" + ) +} export function readParts(messageID: string): StoredPart[] { + if (isSqliteBackend()) return [] + const partDir = join(PART_STORAGE, messageID) if (!existsSync(partDir)) return [] @@ -20,3 +40,25 @@ export function readParts(messageID: string): StoredPart[] { return parts } + +export async function readPartsFromSDK( + client: OpencodeClient, + sessionID: string, + messageID: string +): Promise { + try { + const response = await client.session.message({ + path: { id: sessionID, messageID }, + }) + + const data: unknown = response.data + if (!isRecord(data)) return [] + + const rawParts = data.parts + if (!Array.isArray(rawParts)) return [] + + return rawParts.filter(isStoredPart) + } catch { + return [] + } +}