import type { PersonaMessage, Session, ThreadData } from '../types'; import type { SessionStorage } from './base'; import type { PersonaLogger } from '../logging'; type PersonaSessionStorageOptions = { endpoint: string; apiKey?: string; pageSize?: number; logger?: PersonaLogger; }; export class PersonaSessionStorage implements SessionStorage { private endpoint: string; private apiKey?: string; private pageSize: number; private logger?: PersonaLogger; constructor(opts: PersonaSessionStorageOptions) { this.endpoint = opts.endpoint.replace(/\/$/, ''); this.apiKey = opts.apiKey; this.pageSize = opts.pageSize ?? 200; this.logger = opts.logger; } private buildHeaders() { const headers: Record = { 'Content-Type': 'application/json' }; if (this.apiKey) headers['x-persona-apikey'] = this.apiKey; return headers; } /** * Fetch messages for a session from the Persona REST API */ async sync(sessionId: Session): Promise { if (!sessionId) return []; const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/messages?page=1&size=${this.pageSize}`; try { const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() }); if (!res.ok) { // Log and return empty list on error this.logger?.warn('PersonaSessionStorage: failed to fetch messages', res.status, await res.text()); return []; } const data = await res.json(); if (!Array.isArray(data.items)) return []; // Try to normalize createdAt strings to Date objects // eslint-disable-next-line @typescript-eslint/no-explicit-any const messages: PersonaMessage[] = data.items.map((m: any) => { const msg: PersonaMessage = { id: m.id ?? m.messageId ?? undefined, protocol: m.protocol, thought: m.thought, text: m.text ?? '', image: m.image, type: m.type ?? 'text', role: m.role ?? 'assistant', sender: m.sender, file: m.file, sources: m.sources, sessionId: m.sessionId ?? sessionId, finishReason: m.finishReason, functionCalls: m.functionCalls, functionResponse: m.functionResponse, status: m.status, createdAt: m.createdAt ? new Date(m.createdAt) : undefined, metadata: m.metadata, }; return msg; }); return messages; } catch (err) { // Network or parsing error this.logger?.warn('PersonaSessionStorage: error fetching messages', err); return []; } } async archive(sessionId: Session): Promise { return this._visiblility(sessionId, 'archived'); } async unarchive(sessionId: Session): Promise { return this._visiblility(sessionId, 'active'); } async delete(sessionId: Session): Promise { return this._visiblility(sessionId, 'deleted'); } async _visiblility(sessionId: Session, visibility: 'active' | 'deleted' | 'archived'): Promise { if (!sessionId) return; const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/visibility`; const body = JSON.stringify({ visibility }); const res = await fetch(url, { method: 'PUT', headers: this.buildHeaders(), body }); if (!res.ok) { const text = await res.text(); this.logger?.warn('PersonaSessionStorage: failed to set session visibility', res.status, text); throw new Error(`Failed to set session visibility: ${res.status}`); } } async rename(sessionId: Session, name: string): Promise { if (!sessionId) return; const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/name`; const res = await fetch(url, { method: 'PUT', headers: this.buildHeaders(), body: JSON.stringify({ name }) }); if (!res.ok) { const text = await res.text(); this.logger?.warn('PersonaSessionStorage: failed to rename session', res.status, text); throw new Error(`Failed to rename session: ${res.status}`); } } async clearAll(): Promise { return Promise.reject(new Error('PersonaAPIMessageStorage: clearAll not supported')); } async list(agentId: string, userId?: string): Promise { const params = new URLSearchParams({ page: '1', size: '100', visibility: 'active' }); if (userId) params.set('userId', userId); if (agentId) params.set('agentId', agentId); const url = `${this.endpoint}/sessions?${params.toString()}`; try { const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() }); if (!res.ok) { return []; } const data = await res.json(); if (!Array.isArray(data.items)) return []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const threads: ThreadData[] = data.items.map((item: any) => ({ threadId: item.code || item.id, title: item.name || `Session ${item.code || item.id}`, status: item.status === 'archived' ? 'archived' : 'regular', })); return threads; } catch (err) { return []; } } }