UNPKG

lanonasis-memory

Version:

Memory as a Service integration - AI-powered memory management with semantic search (Compatible with CLI v3.0.6+)

295 lines (253 loc) 7.88 kB
/** * Memory as a Service (MaaS) Client SDK * Aligned with sd-ghost-protocol schema */ import { MemoryEntry, MemoryTopic, CreateMemoryRequest, UpdateMemoryRequest, SearchMemoryRequest, CreateTopicRequest, MemorySearchResult, UserMemoryStats } from '../types/memory-aligned'; export interface MaaSClientConfig { apiUrl: string; apiKey?: string; authToken?: string; timeout?: number; } export interface ApiResponse<T> { data?: T; error?: string; message?: string; } export interface PaginatedResponse<T> { data: T[]; pagination: { page: number; limit: number; total: number; pages: number; }; } export class MaaSClient { private config: MaaSClientConfig; private baseHeaders: Record<string, string>; constructor(config: MaaSClientConfig) { this.config = { timeout: 30000, ...config }; this.baseHeaders = { 'Content-Type': 'application/json', }; if (config.authToken) { this.baseHeaders['Authorization'] = `Bearer ${config.authToken}`; } else if (config.apiKey) { this.baseHeaders['X-API-Key'] = config.apiKey; } } private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<ApiResponse<T>> { // Normalize base URL - remove trailing slash and any /api suffix let baseUrl = this.config.apiUrl.trim(); if (baseUrl.endsWith('/')) { baseUrl = baseUrl.slice(0, -1); } if (baseUrl.endsWith('/api') || baseUrl.endsWith('/api/v1')) { baseUrl = baseUrl.replace(/\/api(\/v1)?$/, ''); } // Ensure endpoint starts with / const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; // Build full URL const url = `${baseUrl}/api/v1${normalizedEndpoint}`; console.log('[MaaSClient] Request:', url); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout || 30000); const response = await fetch(url, { headers: { ...this.baseHeaders, ...options.headers }, ...options, signal: controller.signal }); clearTimeout(timeoutId); // Handle non-JSON responses const contentType = response.headers.get('content-type'); let data: any; if (contentType?.includes('application/json')) { data = await response.json(); } else { const text = await response.text(); data = { error: `Unexpected response: ${text.substring(0, 100)}` }; } if (!response.ok) { const errorMsg = data?.error || data?.message || `HTTP ${response.status}: ${response.statusText}`; console.error('[MaaSClient] Error:', errorMsg); return { error: errorMsg }; } return { data }; } catch (error) { if (error instanceof Error) { if (error.name === 'AbortError') { console.error('[MaaSClient] Request timeout:', url); return { error: 'Request timeout' }; } console.error('[MaaSClient] Fetch error:', error.message); return { error: `Network error: ${error.message}` }; } return { error: 'Unknown network error' }; } } // Memory Operations async createMemory(memory: CreateMemoryRequest): Promise<ApiResponse<MemoryEntry>> { return this.request<MemoryEntry>('/memory', { method: 'POST', body: JSON.stringify(memory) }); } async getMemory(id: string): Promise<ApiResponse<MemoryEntry>> { return this.request<MemoryEntry>(`/memory/${id}`); } async updateMemory(id: string, updates: UpdateMemoryRequest): Promise<ApiResponse<MemoryEntry>> { return this.request<MemoryEntry>(`/memory/${id}`, { method: 'PUT', body: JSON.stringify(updates) }); } async deleteMemory(id: string): Promise<ApiResponse<void>> { return this.request<void>(`/memory/${id}`, { method: 'DELETE' }); } async listMemories(options: { page?: number; limit?: number; memory_type?: string; topic_id?: string; project_ref?: string; status?: string; tags?: string[]; sort?: string; order?: 'asc' | 'desc'; } = {}): Promise<ApiResponse<PaginatedResponse<MemoryEntry>>> { const params = new URLSearchParams(); Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { if (Array.isArray(value)) { params.append(key, value.join(',')); } else { params.append(key, String(value)); } } }); return this.request<PaginatedResponse<MemoryEntry>>( `/memory?${params.toString()}` ); } async searchMemories(request: SearchMemoryRequest): Promise<ApiResponse<{ results: MemorySearchResult[]; total_results: number; search_time_ms: number; }>> { return this.request('/memory/search', { method: 'POST', body: JSON.stringify(request) }); } async bulkDeleteMemories(memoryIds: string[]): Promise<ApiResponse<{ deleted_count: number; failed_ids: string[]; }>> { return this.request('/memory/bulk/delete', { method: 'POST', body: JSON.stringify({ memory_ids: memoryIds }) }); } // Topic Operations async createTopic(topic: CreateTopicRequest): Promise<ApiResponse<MemoryTopic>> { return this.request<MemoryTopic>('/topics', { method: 'POST', body: JSON.stringify(topic) }); } async getTopics(): Promise<ApiResponse<MemoryTopic[]>> { return this.request<MemoryTopic[]>('/topics'); } async getTopic(id: string): Promise<ApiResponse<MemoryTopic>> { return this.request<MemoryTopic>(`/topics/${id}`); } async updateTopic(id: string, updates: Partial<CreateTopicRequest>): Promise<ApiResponse<MemoryTopic>> { return this.request<MemoryTopic>(`/topics/${id}`, { method: 'PUT', body: JSON.stringify(updates) }); } async deleteTopic(id: string): Promise<ApiResponse<void>> { return this.request<void>(`/topics/${id}`, { method: 'DELETE' }); } // Statistics async getMemoryStats(): Promise<ApiResponse<UserMemoryStats>> { return this.request<UserMemoryStats>('/memory/stats'); } // Health Check async getHealth(): Promise<ApiResponse<{ status: string; timestamp: string }>> { return this.request('/health'); } // Utility Methods setAuthToken(token: string): void { this.baseHeaders['Authorization'] = `Bearer ${token}`; delete this.baseHeaders['X-API-Key']; } setApiKey(apiKey: string): void { this.baseHeaders['X-API-Key'] = apiKey; delete this.baseHeaders['Authorization']; } clearAuth(): void { delete this.baseHeaders['Authorization']; delete this.baseHeaders['X-API-Key']; } } // Factory function for easy initialization export function createMaaSClient(config: MaaSClientConfig): MaaSClient { return new MaaSClient(config); } // React Hook for MaaS Client (if using React) export function useMaaSClient(config: MaaSClientConfig): MaaSClient { // In a real React app, you'd use useMemo here return new MaaSClient(config); } // Browser/Node.js detection export const isBrowser = typeof globalThis !== 'undefined' && 'window' in globalThis; export const isNode = typeof process !== 'undefined' && process.versions?.node; // Default configurations for different environments export const defaultConfigs = { development: { apiUrl: 'http://localhost:3001', timeout: 30000 }, production: { apiUrl: 'https://mcp.lanonasis.com', timeout: 10000 }, gateway: { apiUrl: 'https://mcp.lanonasis.com', timeout: 15000 } }; // Type exports for consumers export type { MemoryEntry, MemoryTopic, CreateMemoryRequest, UpdateMemoryRequest, SearchMemoryRequest, CreateTopicRequest, MemorySearchResult, UserMemoryStats };