UNPKG

@jeanmemory/node

Version:

Node.js SDK for Jean Memory - Power your Next.js and other Node.js backends with a perfect memory

329 lines 12.8 kB
"use strict"; /** * Jean Memory Node.js SDK Client * Main client for interacting with Jean Memory API */ Object.defineProperty(exports, "__esModule", { value: true }); exports.JeanMemoryClient = exports.JeanMemoryError = void 0; const auth_1 = require("./auth"); const mcp_1 = require("./mcp"); class JeanMemoryError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.name = 'JeanMemoryError'; } } exports.JeanMemoryError = JeanMemoryError; class JeanMemoryClient { constructor(config) { /** * Direct tool access namespace with OAuth support */ this.tools = { add_memory: async (params) => { // Handle both signatures for flexibility const isObject = typeof params === 'object'; const content = isObject ? params.content : params; const userToken = isObject && params.user_token ? params.user_token : await this.getTestUserToken(); const mcpResponse = await (0, mcp_1.makeMCPRequest)(userToken, this.apiKey, 'add_memories', { text: content, tags: [], priority: false }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } return mcpResponse.result; }, search_memory: async (params) => { // Handle both signatures for flexibility const isObject = typeof params === 'object'; const query = isObject ? params.query : params; const userToken = isObject && params.user_token ? params.user_token : await this.getTestUserToken(); const mcpResponse = await (0, mcp_1.makeMCPRequest)(userToken, this.apiKey, 'search_memory', { query: query, limit: 10, tags_filter: null, deep_search: false }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } return mcpResponse.result; }, deep_memory_query: async (params) => { // Handle both signatures for flexibility const isObject = typeof params === 'object'; const query = isObject ? params.query : params; const userToken = isObject && params.user_token ? params.user_token : await this.getTestUserToken(); const mcpResponse = await (0, mcp_1.makeMCPRequest)(userToken, this.apiKey, 'deep_memory_query', { query: query }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } return mcpResponse.result; }, store_document: async (params) => { const { user_token, title, content, document_type = 'markdown' } = params; const userToken = user_token || await this.getTestUserToken(); const mcpResponse = await (0, mcp_1.makeMCPRequest)(userToken, this.apiKey, 'store_document', { title, content, document_type }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } return mcpResponse.result; } }; if (!config.apiKey) { throw new Error('API key is required'); } if (!config.apiKey.startsWith('jean_sk_')) { throw new Error('Invalid API key format. Must start with "jean_sk_"'); } this.apiKey = config.apiKey; this.apiBase = config.apiBase || 'https://jean-memory-api-virginia.onrender.com'; this.userAgent = config.userAgent || 'JeanMemory-Node-SDK/1.2.4'; } /** * Make authenticated HTTP request to Jean Memory API */ async makeRequest(method, endpoint, data) { const url = new URL(endpoint, this.apiBase).toString(); const options = { method, headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', 'User-Agent': this.userAgent, }, }; if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) { options.body = JSON.stringify(data); } else if (data && method === 'GET') { const searchParams = new URLSearchParams(); Object.entries(data).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, String(value)); } }); const urlWithParams = new URL(url); urlWithParams.search = searchParams.toString(); const finalUrl = urlWithParams.toString(); const response = await fetch(finalUrl, options); return this.handleResponse(response); } const response = await fetch(url, options); return this.handleResponse(response); } /** * Handle HTTP response and error cases */ async handleResponse(response) { if (!response.ok) { let errorMessage = `Request failed with status ${response.status}`; try { const errorData = await response.json(); errorMessage = errorData.error || errorData.message || errorMessage; } catch { // If JSON parsing fails, use status text errorMessage = response.statusText || errorMessage; } throw new JeanMemoryError(errorMessage, response.status); } try { return await response.json(); } catch { throw new JeanMemoryError('Invalid JSON response'); } } /** * Store a new memory */ async storeMemory(content, context) { if (!content || !content.trim()) { throw new Error('Content cannot be empty'); } const request = { content: content.trim(), context: context || {} }; return this.makeRequest('POST', '/api/v1/memories', request); } /** * Retrieve memories based on search query */ async retrieveMemories(query, options = {}) { if (!query || !query.trim()) { throw new Error('Query cannot be empty'); } const { limit = 10, offset = 0 } = options; if (limit < 1 || limit > 100) { throw new Error('Limit must be between 1 and 100'); } if (offset < 0) { throw new Error('Offset must be non-negative'); } const params = { query: query.trim(), limit, offset }; const response = await this.makeRequest('GET', '/api/v1/memories/search', params); return response.data?.memories || []; } /** * Get formatted context for a query (legacy method) */ async getContextLegacy(query) { if (!query || !query.trim()) { throw new Error('Query cannot be empty'); } const memories = await this.retrieveMemories(query, { limit: 5 }); if (memories.length === 0) { return 'No relevant context found.'; } const contextParts = memories.map((memory, index) => { const content = memory.content || ''; const timestamp = memory.created_at || ''; return `${index + 1}. ${content} (${timestamp})`; }); return 'Relevant context:\\n' + contextParts.join('\\n'); } /** * Get or create auto test user for this API key */ async getTestUserToken() { try { const response = await fetch(`${this.apiBase}/api/v1/test-user`, { method: 'GET', headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', 'User-Agent': this.userAgent } }); if (!response.ok) { throw new Error(`Failed to get test user: ${response.statusText}`); } const data = await response.json(); // Add null checking for the response data if (!data || !data.user_token) { throw new Error('Invalid test user response: missing user_token'); } return data.user_token; } catch (error) { throw new JeanMemoryError(`Failed to create test user: ${error?.message || 'Unknown error'}`); } } async getContext(paramsOrQuery) { // Handle backward compatibility - simple string query if (typeof paramsOrQuery === 'string') { const userToken = await this.getTestUserToken(); const mcpResponse = await (0, mcp_1.makeMCPRequest)(userToken, this.apiKey, 'jean_memory', { user_message: paramsOrQuery, is_new_conversation: false, needs_context: true }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } return mcpResponse.result?.content?.[0]?.text || ''; } // Handle OAuth API - object with user_token and message const { user_token, message, speed = 'balanced', tool = 'jean_memory', format = 'enhanced' } = paramsOrQuery; const mcpResponse = await (0, mcp_1.makeMCPRequest)(user_token, this.apiKey, tool, { user_message: message, is_new_conversation: false, needs_context: true, speed, format }, this.apiBase); if (mcpResponse.error) { throw new JeanMemoryError(mcpResponse.error.message, mcpResponse.error.code); } // Return ContextResponse object return { text: mcpResponse.result?.content?.[0]?.text || '', metadata: mcpResponse.result }; } /** * List all memories with pagination */ async listMemories(options = {}) { const { limit = 20, offset = 0 } = options; if (limit < 1 || limit > 100) { throw new Error('Limit must be between 1 and 100'); } if (offset < 0) { throw new Error('Offset must be non-negative'); } const params = { limit, offset }; const response = await this.makeRequest('GET', '/api/v1/memories', params); return { memories: response.data?.memories || [], total: response.data?.total || 0, hasNext: response.data?.pagination?.has_next || false, hasPrev: response.data?.pagination?.has_prev || false }; } /** * Delete a specific memory */ async deleteMemory(memoryId) { if (!memoryId) { throw new Error('Memory ID is required'); } await this.makeRequest('DELETE', `/api/v1/memories/${memoryId}`); } /** * Stream memories (for large result sets) */ async streamMemories(query) { if (!query || !query.trim()) { throw new Error('Query cannot be empty'); } // For now, implement as a batch fetch with streaming interface // This can be enhanced when backend supports true streaming const memories = await this.retrieveMemories(query, { limit: 100 }); return new ReadableStream({ start(controller) { memories.forEach(memory => controller.enqueue(memory)); controller.close(); } }); } /** * Check API health and authentication */ async healthCheck() { return this.makeRequest('GET', '/api/v1/health'); } /** * Get current user information */ async getCurrentUser() { return this.makeRequest('GET', '/api/v1/user/me'); } /** * Get authentication helper for OAuth flows */ createAuth(config) { return new auth_1.JeanMemoryAuth({ apiKey: this.apiKey, oauthBase: config?.oauthBase, redirectPort: config?.redirectPort }); } } exports.JeanMemoryClient = JeanMemoryClient; //# sourceMappingURL=client.js.map