2026-02-14 18:16:18 +09:00
|
|
|
import type { PluginInput } from "@opencode-ai/plugin"
|
|
|
|
|
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
2026-04-04 16:42:51 +09:00
|
|
|
import { log } from "../../shared"
|
|
|
|
|
import { getFileAllSessions, getFileMainSessions, fileSessionExists, getFileSessionInfo, getFileSessionMessages, getFileSessionTodos, getFileSessionTranscript } from "./file-storage"
|
|
|
|
|
import { getSdkAllSessions, getSdkMainSessions, getSdkSessionMessages, getSdkSessionTodos, sdkSessionExists, shouldFallbackFromSdkError } from "./sdk-storage"
|
|
|
|
|
import type { SessionInfo, SessionMessage, SessionMetadata, TodoItem } from "./types"
|
2025-12-31 12:42:22 +09:00
|
|
|
|
|
|
|
|
export interface GetMainSessionsOptions {
|
|
|
|
|
directory?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 09:30:19 +09:00
|
|
|
function mergeSessionMetadataLists(
|
|
|
|
|
sdkSessions: SessionMetadata[],
|
|
|
|
|
fileSessions: SessionMetadata[],
|
|
|
|
|
): SessionMetadata[] {
|
|
|
|
|
const merged = new Map<string, SessionMetadata>()
|
|
|
|
|
|
|
|
|
|
for (const session of fileSessions) {
|
|
|
|
|
merged.set(session.id, session)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const session of sdkSessions) {
|
|
|
|
|
merged.set(session.id, session)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [...merged.values()].sort((a, b) => b.time.updated - a.time.updated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mergeSessionIds(sdkSessionIds: string[], fileSessionIds: string[]): string[] {
|
|
|
|
|
return [...new Set([...sdkSessionIds, ...fileSessionIds])]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 18:16:18 +09:00
|
|
|
// SDK client reference for beta mode
|
|
|
|
|
let sdkClient: PluginInput["client"] | null = null
|
|
|
|
|
|
|
|
|
|
export function setStorageClient(client: PluginInput["client"]): void {
|
|
|
|
|
sdkClient = client
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 18:56:21 +09:00
|
|
|
export function resetStorageClient(): void {
|
|
|
|
|
sdkClient = null
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 12:42:22 +09:00
|
|
|
export async function getMainSessions(options: GetMainSessionsOptions): Promise<SessionMetadata[]> {
|
2026-02-14 18:16:18 +09:00
|
|
|
if (isSqliteBackend() && sdkClient) {
|
|
|
|
|
try {
|
2026-04-05 09:30:19 +09:00
|
|
|
const sdkSessions = await getSdkMainSessions(sdkClient, options.directory)
|
|
|
|
|
const fileSessions = await getFileMainSessions(options.directory)
|
|
|
|
|
return mergeSessionMetadataLists(sdkSessions, fileSessions)
|
2026-04-04 16:42:51 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
if (!shouldFallbackFromSdkError(error)) throw error
|
|
|
|
|
log("[session-manager] falling back to file session list after SDK unavailable error", { error: String(error) })
|
2026-02-14 18:16:18 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileMainSessions(options.directory)
|
2025-12-31 12:42:22 +09:00
|
|
|
}
|
2025-12-25 17:04:16 +09:00
|
|
|
|
2025-12-28 14:33:47 +09:00
|
|
|
export async function getAllSessions(): Promise<string[]> {
|
2026-02-14 18:16:18 +09:00
|
|
|
if (isSqliteBackend() && sdkClient) {
|
|
|
|
|
try {
|
2026-04-05 09:30:19 +09:00
|
|
|
const sdkSessionIds = await getSdkAllSessions(sdkClient)
|
|
|
|
|
const fileSessionIds = await getFileAllSessions()
|
|
|
|
|
return mergeSessionIds(sdkSessionIds, fileSessionIds)
|
2026-04-04 16:42:51 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
if (!shouldFallbackFromSdkError(error)) throw error
|
|
|
|
|
log("[session-manager] falling back to file session ids after SDK unavailable error", { error: String(error) })
|
2026-02-14 18:16:18 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileAllSessions()
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 15:54:29 +09:00
|
|
|
export { getMessageDir } from "../../shared/opencode-message-dir"
|
2025-12-25 17:04:16 +09:00
|
|
|
|
2026-02-15 19:23:05 +09:00
|
|
|
export async function sessionExists(sessionID: string): Promise<boolean> {
|
|
|
|
|
if (isSqliteBackend() && sdkClient) {
|
2026-04-04 16:42:51 +09:00
|
|
|
try {
|
2026-04-05 09:30:19 +09:00
|
|
|
const existsInSdk = await sdkSessionExists(sdkClient, sessionID)
|
|
|
|
|
if (existsInSdk) return true
|
2026-04-04 16:42:51 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
if (!shouldFallbackFromSdkError(error)) throw error
|
|
|
|
|
log("[session-manager] falling back to file sessionExists after SDK unavailable error", { error: String(error), sessionID })
|
|
|
|
|
}
|
2026-02-15 19:23:05 +09:00
|
|
|
}
|
2026-04-04 16:42:51 +09:00
|
|
|
return fileSessionExists(sessionID)
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-28 14:33:47 +09:00
|
|
|
export async function readSessionMessages(sessionID: string): Promise<SessionMessage[]> {
|
2026-02-14 18:16:18 +09:00
|
|
|
if (isSqliteBackend() && sdkClient) {
|
|
|
|
|
try {
|
2026-04-05 09:30:19 +09:00
|
|
|
const sdkMessages = await getSdkSessionMessages(sdkClient, sessionID)
|
|
|
|
|
if (sdkMessages.length > 0) return sdkMessages
|
2026-04-04 16:42:51 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
if (!shouldFallbackFromSdkError(error)) throw error
|
|
|
|
|
log("[session-manager] falling back to file session messages after SDK unavailable error", { error: String(error), sessionID })
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileSessionMessages(sessionID)
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-28 14:33:47 +09:00
|
|
|
export async function readSessionTodos(sessionID: string): Promise<TodoItem[]> {
|
2026-02-14 18:16:18 +09:00
|
|
|
if (isSqliteBackend() && sdkClient) {
|
|
|
|
|
try {
|
2026-04-05 09:30:19 +09:00
|
|
|
const sdkTodos = await getSdkSessionTodos(sdkClient, sessionID)
|
|
|
|
|
if (sdkTodos.length > 0) return sdkTodos
|
2026-04-04 16:42:51 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
if (!shouldFallbackFromSdkError(error)) throw error
|
|
|
|
|
log("[session-manager] falling back to file session todos after SDK unavailable error", { error: String(error), sessionID })
|
2026-02-14 18:16:18 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileSessionTodos(sessionID)
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-28 14:33:47 +09:00
|
|
|
export async function readSessionTranscript(sessionID: string): Promise<number> {
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileSessionTranscript(sessionID)
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-28 14:33:47 +09:00
|
|
|
export async function getSessionInfo(sessionID: string): Promise<SessionInfo | null> {
|
2026-04-04 16:42:51 +09:00
|
|
|
return getFileSessionInfo(sessionID)
|
2025-12-25 17:04:16 +09:00
|
|
|
}
|