|
@@ -1,45 +1,121 @@
|
|
|
/**
|
|
/**
|
|
|
- * SessionReader - Read OpenCode session data from local storage
|
|
|
|
|
|
|
+ * SessionReader - Read OpenCode session data
|
|
|
*
|
|
*
|
|
|
- * Reads session info, messages, and parts from the OpenCode session storage.
|
|
|
|
|
- * Handles project path encoding and graceful error handling.
|
|
|
|
|
|
|
+ * SIMPLIFIED APPROACH:
|
|
|
|
|
+ * 1. Use SDK client to get session data (primary method)
|
|
|
|
|
+ * 2. Fallback to disk scan by session ID (when SDK unavailable)
|
|
|
|
|
+ *
|
|
|
|
|
+ * This avoids complex path calculations and hash discovery.
|
|
|
|
|
+ * Works for any agent, any project structure.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
import * as fs from 'fs';
|
|
import * as fs from 'fs';
|
|
|
import * as path from 'path';
|
|
import * as path from 'path';
|
|
|
-import { SessionInfo, Message, Part } from '../types/index.js';
|
|
|
|
|
-import {
|
|
|
|
|
- getSessionInfoPath,
|
|
|
|
|
- getSessionMessagePath,
|
|
|
|
|
- getSessionPartPath,
|
|
|
|
|
-} from '../config.js';
|
|
|
|
|
|
|
+import * as os from 'os';
|
|
|
|
|
+import { SessionInfo, Message, Part, MessageWithParts } from '../types/index.js';
|
|
|
|
|
+
|
|
|
|
|
+// SDK client type (optional dependency)
|
|
|
|
|
+type OpencodeClient = any;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Read and parse OpenCode session data
|
|
* Read and parse OpenCode session data
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses SDK client when available, falls back to simple file scanning.
|
|
|
*/
|
|
*/
|
|
|
export class SessionReader {
|
|
export class SessionReader {
|
|
|
- private projectPath: string;
|
|
|
|
|
- private sessionStoragePath?: string;
|
|
|
|
|
|
|
+ private sdkClient?: OpencodeClient;
|
|
|
|
|
+ private sessionStoragePath: string;
|
|
|
|
|
|
|
|
- constructor(projectPath: string, sessionStoragePath?: string) {
|
|
|
|
|
- this.projectPath = projectPath;
|
|
|
|
|
- this.sessionStoragePath = sessionStoragePath;
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Create a SessionReader
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sdkClient - Optional SDK client for retrieving session data
|
|
|
|
|
+ * @param sessionStoragePath - Base storage path (defaults to ~/.local/share/opencode)
|
|
|
|
|
+ */
|
|
|
|
|
+ constructor(sdkClient?: OpencodeClient, sessionStoragePath?: string) {
|
|
|
|
|
+ this.sdkClient = sdkClient;
|
|
|
|
|
+ this.sessionStoragePath = sessionStoragePath || path.join(os.homedir(), '.local', 'share', 'opencode');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Get session metadata
|
|
|
|
|
|
|
+ * Find a session file by scanning all session directories
|
|
|
|
|
+ *
|
|
|
|
|
+ * Simple approach: Just look for the session ID in any hash directory.
|
|
|
|
|
+ * No need to calculate hashes or match project paths.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID to find
|
|
|
|
|
+ * @returns Full path to session file or null if not found
|
|
|
*/
|
|
*/
|
|
|
- getSessionInfo(sessionId: string): SessionInfo | null {
|
|
|
|
|
|
|
+ private findSessionFile(sessionId: string): string | null {
|
|
|
try {
|
|
try {
|
|
|
- const infoPath = getSessionInfoPath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
- const filePath = path.join(infoPath, `${sessionId}.json`);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(filePath)) {
|
|
|
|
|
|
|
+ const sessionBasePath = path.join(this.sessionStoragePath, 'storage', 'session');
|
|
|
|
|
+
|
|
|
|
|
+ if (!fs.existsSync(sessionBasePath)) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
|
- return JSON.parse(content) as SessionInfo;
|
|
|
|
|
|
|
+ // Scan all hash directories
|
|
|
|
|
+ const hashDirs = fs.readdirSync(sessionBasePath);
|
|
|
|
|
+
|
|
|
|
|
+ for (const hashDir of hashDirs) {
|
|
|
|
|
+ const hashPath = path.join(sessionBasePath, hashDir);
|
|
|
|
|
+
|
|
|
|
|
+ // Skip if not a directory
|
|
|
|
|
+ if (!fs.statSync(hashPath).isDirectory()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if session file exists in this hash directory
|
|
|
|
|
+ const sessionFile = path.join(hashPath, `${sessionId}.json`);
|
|
|
|
|
+ if (fs.existsSync(sessionFile)) {
|
|
|
|
|
+ return sessionFile;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Error finding session file for ${sessionId}:`, error);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get session metadata
|
|
|
|
|
+ *
|
|
|
|
|
+ * SIMPLIFIED APPROACH:
|
|
|
|
|
+ * 1. Try SDK client first (if available)
|
|
|
|
|
+ * 2. Fallback to scanning disk for session file by ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * No complex path calculations, no hash discovery, no project path matching.
|
|
|
|
|
+ * Just find the session by ID, regardless of where it's stored.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID to retrieve
|
|
|
|
|
+ * @returns SessionInfo object or null if not found
|
|
|
|
|
+ */
|
|
|
|
|
+ async getSessionInfo(sessionId: string): Promise<SessionInfo | null> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Method 1: Use SDK client (preferred - always up to date)
|
|
|
|
|
+ if (this.sdkClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.sdkClient.session.get({ path: { id: sessionId } });
|
|
|
|
|
+ if (response.data) {
|
|
|
|
|
+ return response.data as SessionInfo;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ // SDK failed, fall through to disk scan
|
|
|
|
|
+ console.warn(`SDK session.get() failed for ${sessionId}, falling back to disk scan`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Method 2: Scan disk for session file (fallback)
|
|
|
|
|
+ const sessionFile = this.findSessionFile(sessionId);
|
|
|
|
|
+ if (sessionFile) {
|
|
|
|
|
+ const content = fs.readFileSync(sessionFile, 'utf-8');
|
|
|
|
|
+ return JSON.parse(content) as SessionInfo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Session not found
|
|
|
|
|
+ return null;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`Error reading session info for ${sessionId}:`, error);
|
|
console.error(`Error reading session info for ${sessionId}:`, error);
|
|
|
return null;
|
|
return null;
|
|
@@ -48,25 +124,55 @@ export class SessionReader {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* List all available sessions
|
|
* List all available sessions
|
|
|
|
|
+ *
|
|
|
|
|
+ * SIMPLIFIED APPROACH:
|
|
|
|
|
+ * 1. Try SDK client first (if available)
|
|
|
|
|
+ * 2. Fallback to scanning all session directories
|
|
|
|
|
+ *
|
|
|
|
|
+ * @returns Array of SessionInfo objects sorted by creation time (newest first)
|
|
|
*/
|
|
*/
|
|
|
- listSessions(): SessionInfo[] {
|
|
|
|
|
|
|
+ async listSessions(): Promise<SessionInfo[]> {
|
|
|
try {
|
|
try {
|
|
|
- const infoPath = getSessionInfoPath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(infoPath)) {
|
|
|
|
|
- return [];
|
|
|
|
|
|
|
+ // Method 1: Use SDK client (preferred)
|
|
|
|
|
+ if (this.sdkClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.sdkClient.session.list();
|
|
|
|
|
+ if (response.data) {
|
|
|
|
|
+ return response.data.sort((a: SessionInfo, b: SessionInfo) =>
|
|
|
|
|
+ b.time.created - a.time.created
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('SDK session.list() failed, falling back to disk scan');
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const files = fs.readdirSync(infoPath);
|
|
|
|
|
|
|
+ // Method 2: Scan all session directories (fallback)
|
|
|
const sessions: SessionInfo[] = [];
|
|
const sessions: SessionInfo[] = [];
|
|
|
|
|
+ const sessionBasePath = path.join(this.sessionStoragePath, 'storage', 'session');
|
|
|
|
|
|
|
|
- for (const file of files) {
|
|
|
|
|
- if (file.endsWith('.json')) {
|
|
|
|
|
- const sessionId = file.replace('.json', '');
|
|
|
|
|
- const info = this.getSessionInfo(sessionId);
|
|
|
|
|
- if (info) {
|
|
|
|
|
- sessions.push(info);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!fs.existsSync(sessionBasePath)) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Scan all hash directories
|
|
|
|
|
+ const hashDirs = fs.readdirSync(sessionBasePath);
|
|
|
|
|
+
|
|
|
|
|
+ for (const hashDir of hashDirs) {
|
|
|
|
|
+ const hashPath = path.join(sessionBasePath, hashDir);
|
|
|
|
|
+
|
|
|
|
|
+ if (!fs.statSync(hashPath).isDirectory()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Read all session files in this directory
|
|
|
|
|
+ const files = fs.readdirSync(hashPath).filter(f => f.endsWith('.json'));
|
|
|
|
|
+
|
|
|
|
|
+ for (const file of files) {
|
|
|
|
|
+ const sessionFile = path.join(hashPath, file);
|
|
|
|
|
+ const content = fs.readFileSync(sessionFile, 'utf-8');
|
|
|
|
|
+ const session = JSON.parse(content) as SessionInfo;
|
|
|
|
|
+ sessions.push(session);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -79,31 +185,50 @@ export class SessionReader {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Get all messages for a session
|
|
|
|
|
|
|
+ * Get all messages for a session (info only, without parts)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @deprecated Use getMessagesWithParts() instead for full message data
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses SDK client when available, falls back to disk scan.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @returns Array of Message objects sorted by creation time
|
|
|
*/
|
|
*/
|
|
|
- getMessages(sessionId: string): Message[] {
|
|
|
|
|
- try {
|
|
|
|
|
- const messagePath = getSessionMessagePath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
- const sessionMessagePath = path.join(messagePath, sessionId);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(sessionMessagePath)) {
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const files = fs.readdirSync(sessionMessagePath);
|
|
|
|
|
- const messages: Message[] = [];
|
|
|
|
|
|
|
+ async getMessages(sessionId: string): Promise<Message[]> {
|
|
|
|
|
+ const messagesWithParts = await this.getMessagesWithParts(sessionId);
|
|
|
|
|
+ return messagesWithParts.map(m => m.info);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- for (const file of files) {
|
|
|
|
|
- if (file.endsWith('.json')) {
|
|
|
|
|
- const filePath = path.join(sessionMessagePath, file);
|
|
|
|
|
- const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
|
- const message = JSON.parse(content) as Message;
|
|
|
|
|
- messages.push(message);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get all messages for a session WITH their parts included
|
|
|
|
|
+ *
|
|
|
|
|
+ * This is the preferred method as the SDK returns messages with parts embedded.
|
|
|
|
|
+ * Using this avoids the need for separate getParts() calls.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @returns Array of MessageWithParts objects sorted by creation time
|
|
|
|
|
+ */
|
|
|
|
|
+ async getMessagesWithParts(sessionId: string): Promise<MessageWithParts[]> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Method 1: Use SDK client (preferred)
|
|
|
|
|
+ if (this.sdkClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.sdkClient.session.messages({ path: { id: sessionId } });
|
|
|
|
|
+ if (response.data) {
|
|
|
|
|
+ // SDK returns { info: Message, parts: Part[] } for each message
|
|
|
|
|
+ return response.data.map((m: any) => ({
|
|
|
|
|
+ info: m.info,
|
|
|
|
|
+ parts: m.parts || [],
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn(`SDK session.messages() failed for ${sessionId}, falling back to disk scan`);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Sort by creation time
|
|
|
|
|
- return messages.sort((a, b) => a.time.created - b.time.created);
|
|
|
|
|
|
|
+ // Method 2: Scan disk (fallback - not commonly used)
|
|
|
|
|
+ // Note: SDK sessions typically don't have separate message files
|
|
|
|
|
+ return [];
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`Error reading messages for session ${sessionId}:`, error);
|
|
console.error(`Error reading messages for session ${sessionId}:`, error);
|
|
|
return [];
|
|
return [];
|
|
@@ -112,18 +237,31 @@ export class SessionReader {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get a specific message
|
|
* Get a specific message
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses SDK client when available.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @param messageId - Message ID
|
|
|
|
|
+ * @returns Message object or null if not found
|
|
|
*/
|
|
*/
|
|
|
- getMessage(sessionId: string, messageId: string): Message | null {
|
|
|
|
|
|
|
+ async getMessage(sessionId: string, messageId: string): Promise<Message | null> {
|
|
|
try {
|
|
try {
|
|
|
- const messagePath = getSessionMessagePath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
- const filePath = path.join(messagePath, sessionId, `${messageId}.json`);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(filePath)) {
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ // Method 1: Use SDK client (preferred)
|
|
|
|
|
+ if (this.sdkClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.sdkClient.session.message({
|
|
|
|
|
+ path: { id: sessionId, messageID: messageId }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (response.data) {
|
|
|
|
|
+ return response.data.info;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn(`SDK session.message() failed for ${messageId}`);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
|
- return JSON.parse(content) as Message;
|
|
|
|
|
|
|
+ // Method 2: Disk scan not implemented (SDK sessions don't use separate message files)
|
|
|
|
|
+ return null;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`Error reading message ${messageId}:`, error);
|
|
console.error(`Error reading message ${messageId}:`, error);
|
|
|
return null;
|
|
return null;
|
|
@@ -132,34 +270,31 @@ export class SessionReader {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get all parts for a message
|
|
* Get all parts for a message
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses SDK client when available.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @param messageId - Message ID
|
|
|
|
|
+ * @returns Array of Part objects sorted by creation time
|
|
|
*/
|
|
*/
|
|
|
- getParts(sessionId: string, messageId: string): Part[] {
|
|
|
|
|
|
|
+ async getParts(sessionId: string, messageId: string): Promise<Part[]> {
|
|
|
try {
|
|
try {
|
|
|
- const partPath = getSessionPartPath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
- const messagePartPath = path.join(partPath, sessionId, messageId);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(messagePartPath)) {
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const files = fs.readdirSync(messagePartPath);
|
|
|
|
|
- const parts: Part[] = [];
|
|
|
|
|
-
|
|
|
|
|
- for (const file of files) {
|
|
|
|
|
- if (file.endsWith('.json')) {
|
|
|
|
|
- const filePath = path.join(messagePartPath, file);
|
|
|
|
|
- const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
|
- const part = JSON.parse(content) as Part;
|
|
|
|
|
- parts.push(part);
|
|
|
|
|
|
|
+ // Method 1: Use SDK client (preferred)
|
|
|
|
|
+ if (this.sdkClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await this.sdkClient.session.message({
|
|
|
|
|
+ path: { id: sessionId, messageID: messageId }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (response.data && response.data.parts) {
|
|
|
|
|
+ return response.data.parts;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn(`SDK session.message() failed for parts of ${messageId}`);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Sort by creation time if available
|
|
|
|
|
- return parts.sort((a, b) => {
|
|
|
|
|
- const aTime = a.time?.created || 0;
|
|
|
|
|
- const bTime = b.time?.created || 0;
|
|
|
|
|
- return aTime - bTime;
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // Method 2: Disk scan not implemented (SDK sessions don't use separate part files)
|
|
|
|
|
+ return [];
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`Error reading parts for message ${messageId}:`, error);
|
|
console.error(`Error reading parts for message ${messageId}:`, error);
|
|
|
return [];
|
|
return [];
|
|
@@ -168,18 +303,19 @@ export class SessionReader {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get a specific part
|
|
* Get a specific part
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses SDK client when available.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @param messageId - Message ID
|
|
|
|
|
+ * @param partId - Part ID
|
|
|
|
|
+ * @returns Part object or null if not found
|
|
|
*/
|
|
*/
|
|
|
- getPart(sessionId: string, messageId: string, partId: string): Part | null {
|
|
|
|
|
|
|
+ async getPart(sessionId: string, messageId: string, partId: string): Promise<Part | null> {
|
|
|
try {
|
|
try {
|
|
|
- const partPath = getSessionPartPath(this.projectPath, this.sessionStoragePath);
|
|
|
|
|
- const filePath = path.join(partPath, sessionId, messageId, `${partId}.json`);
|
|
|
|
|
-
|
|
|
|
|
- if (!fs.existsSync(filePath)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
|
- return JSON.parse(content) as Part;
|
|
|
|
|
|
|
+ // Get all parts and find the specific one
|
|
|
|
|
+ const parts = await this.getParts(sessionId, messageId);
|
|
|
|
|
+ return parts.find(p => p.id === partId) || null;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(`Error reading part ${partId}:`, error);
|
|
console.error(`Error reading part ${partId}:`, error);
|
|
|
return null;
|
|
return null;
|
|
@@ -188,21 +324,28 @@ export class SessionReader {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Get complete session data (info + messages + parts)
|
|
* Get complete session data (info + messages + parts)
|
|
|
|
|
+ *
|
|
|
|
|
+ * Retrieves all session data in one call.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sessionId - Session ID
|
|
|
|
|
+ * @returns Complete session data
|
|
|
*/
|
|
*/
|
|
|
- getCompleteSession(sessionId: string): {
|
|
|
|
|
|
|
+ async getCompleteSession(sessionId: string): Promise<{
|
|
|
info: SessionInfo | null;
|
|
info: SessionInfo | null;
|
|
|
messages: Array<{
|
|
messages: Array<{
|
|
|
message: Message;
|
|
message: Message;
|
|
|
parts: Part[];
|
|
parts: Part[];
|
|
|
}>;
|
|
}>;
|
|
|
- } {
|
|
|
|
|
- const info = this.getSessionInfo(sessionId);
|
|
|
|
|
- const messages = this.getMessages(sessionId);
|
|
|
|
|
-
|
|
|
|
|
- const messagesWithParts = messages.map(message => ({
|
|
|
|
|
- message,
|
|
|
|
|
- parts: this.getParts(sessionId, message.id),
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ }> {
|
|
|
|
|
+ const info = await this.getSessionInfo(sessionId);
|
|
|
|
|
+ const messages = await this.getMessages(sessionId);
|
|
|
|
|
+
|
|
|
|
|
+ const messagesWithParts = await Promise.all(
|
|
|
|
|
+ messages.map(async message => ({
|
|
|
|
|
+ message,
|
|
|
|
|
+ parts: await this.getParts(sessionId, message.id),
|
|
|
|
|
+ }))
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
info,
|
|
info,
|