UNPKG

recallrai

Version:

Official Node.js SDK for RecallrAI - Revolutionary contextual memory system that enables AI assistants to form meaningful connections between conversations, just like human memory.

540 lines (539 loc) 26 kB
"use strict"; /** * User management functionality for the RecallrAI SDK. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.User = void 0; const models_1 = require("./models"); const session_1 = require("./session"); const merge_conflict_1 = require("./merge-conflict"); const errors_1 = require("./errors"); /** * Represents a user in the RecallrAI system with methods for user management. * * This class wraps a user object and provides methods for updating user data, * and for creating and managing sessions. */ class User { /** * Initialize a user. * * @param httpClient - HTTP client for API communication. * @param userData - User data model with user information. */ constructor(httpClient, userData) { this.http = httpClient; this.userData = userData; this.userId = userData.userId; this.metadata = userData.metadata; this.mergeConflictEnabled = userData.mergeConflictEnabled; this.createdAt = userData.createdAt; this.lastActiveAt = userData.lastActiveAt; } /** * Update this user's metadata or ID. * * @param newMetadata - New metadata to associate with the user. * @param newUserId - New ID for the user. * @param mergeConflictEnabled - Per-user merge conflict override. * `true` = always raise merge conflicts for this user. * `false` = never raise merge conflicts for this user. * `undefined` = do not change the current setting. * @throws {UserNotFoundError} If the user is not found. * @throws {UserAlreadyExistsError} If a user with the new_user_id already exists. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async update({ newMetadata, newUserId, mergeConflictEnabled, } = {}) { const data = {}; if (newMetadata !== undefined) { data.new_metadata = newMetadata; } if (newUserId !== undefined) { data.new_custom_user_id = newUserId; } if (mergeConflictEnabled !== undefined) { data.merge_conflict_enabled = mergeConflictEnabled; } const response = await this.http.put(`/api/v1/users/${this.userId}`, data); if (response.status === 404) { const detail = response.data?.detail || `User with ID ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status === 409) { const detail = response.data?.detail || `User with ID ${newUserId} already exists`; throw new errors_1.UserAlreadyExistsError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } const updatedData = this.parseUserResponse(response.data); this.userData = updatedData; this.userId = updatedData.userId; this.metadata = updatedData.metadata; this.mergeConflictEnabled = updatedData.mergeConflictEnabled; this.lastActiveAt = updatedData.lastActiveAt; } /** * Refresh this user's data from the server. * * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async refresh() { const response = await this.http.get(`/api/v1/users/${this.userId}`); if (response.status === 404) { const detail = response.data?.detail || `User with ID ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } const refreshedData = this.parseUserResponse(response.data); this.userData = refreshedData; this.userId = refreshedData.userId; this.metadata = refreshedData.metadata; this.mergeConflictEnabled = refreshedData.mergeConflictEnabled; this.createdAt = refreshedData.createdAt; this.lastActiveAt = refreshedData.lastActiveAt; } /** * Delete this user. * * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async delete() { const response = await this.http.delete(`/api/v1/users/${this.userId}`); if (response.status === 404) { const detail = response.data?.detail || `User with ID ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 204) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } } /** * Create a new session for this user. * * @param autoProcessAfterSeconds - Seconds of inactivity allowed before automatically processing the session (min 600). Defaults to 600. * @param metadata - Optional metadata for the session. * @param customCreatedAtUtc - Optional custom timestamp for when the session was created (must be UTC). Useful for importing historical data. * @returns A Session object to interact with the created session. * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async createSession({ autoProcessAfterSeconds = 600, metadata, customCreatedAtUtc, } = {}) { const payload = { auto_process_after_seconds: autoProcessAfterSeconds, metadata: metadata || {}, }; if (customCreatedAtUtc) { payload.custom_created_at_utc = customCreatedAtUtc.toISOString(); } const response = await this.http.post(`/api/v1/users/${this.userId}/sessions`, payload); if (response.status === 404) { const detail = response.data?.detail || `User ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 201) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } const sessionData = this.parseSessionResponse(response.data); return new session_1.Session(this.http, this.userId, sessionData); } /** * Get an existing session for this user. * * @param sessionId - ID of the session to retrieve. * @param options - Optional behavior flags. * @param options.validate - Whether to validate session existence via API before creating the instance. * Defaults to true. Set to false when sessionId is trusted. * @returns A Session object to interact with the session. * @throws {UserNotFoundError} If the user is not found. * @throws {SessionNotFoundError} If the session is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async getSession(sessionId, { validate = true } = {}) { if (!validate) { return new session_1.Session(this.http, this.userId, { sessionId, status: models_1.UNAVAILABLE, createdAt: models_1.UNAVAILABLE, metadata: models_1.UNAVAILABLE, }); } const response = await this.http.get(`/api/v1/users/${this.userId}/sessions/${sessionId}`); if (response.status === 404) { const detail = response.data?.detail || ""; if (detail.includes(`User ${this.userId} not found`)) { throw new errors_1.UserNotFoundError(detail, response.status); } else { throw new errors_1.SessionNotFoundError(detail, response.status); } } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } const sessionData = this.parseSessionResponse(response.data); return new session_1.Session(this.http, this.userId, sessionData); } /** * List sessions for this user with pagination. * * @param offset - Number of records to skip. Defaults to 0. * @param limit - Maximum number of records to return. Defaults to 10. * @param metadataFilter - Optional metadata filter for sessions. * @param statusFilter - Optional list of session statuses to filter by (e.g., ["pending", "processing", "processed", "insufficient_balance"]). * @returns List of sessions with pagination info. * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async listSessions({ offset = 0, limit = 10, metadataFilter, statusFilter, } = {}) { const params = { offset, limit, }; if (metadataFilter) { params.metadata_filter = JSON.stringify(metadataFilter); } if (statusFilter) { params.status_filter = statusFilter; } const response = await this.http.get(`/api/v1/users/${this.userId}/sessions`, params); if (response.status === 404) { const detail = response.data?.detail || `User ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } return { sessions: response.data.sessions.map((session) => new session_1.Session(this.http, this.userId, this.parseSessionResponse(session))), total: response.data.total, hasMore: response.data.has_more, }; } /** * List memories for this user with optional category and session filters. * * @param offset - Number of records to skip. Defaults to 0. * @param limit - Maximum number of records to return (1-200). Defaults to 20. * @param categories - Optional list of category names to filter by. * @param sessionIdFilter - Optional list of session IDs to filter by. * @param sessionMetadataFilter - Optional object to filter by session metadata (exact match on keys -> values). * @param includePreviousVersions - Include full version history for each memory. Defaults to true. * @param includeConnectedMemories - Include connected memories. Defaults to true. * @returns UserMemoriesList: Paginated list of memory items. * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async listMemories({ offset = 0, limit = 20, categories, sessionIdFilter, sessionMetadataFilter, includePreviousVersions = true, includeConnectedMemories = true, } = {}) { const params = { offset, limit, include_previous_versions: includePreviousVersions, include_connected_memories: includeConnectedMemories, }; if (categories) { params.categories = categories; } if (sessionIdFilter) { params.session_id_filter = sessionIdFilter; } if (sessionMetadataFilter) { params.session_metadata_filter = JSON.stringify(sessionMetadataFilter); } const response = await this.http.get(`/api/v1/users/${this.userId}/memories`, params); if (response.status === 404) { const detail = response.data?.detail || `User ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status === 400) { const detailData = response.data?.detail; const message = detailData.message; const invalidCats = detailData.invalid_categories; throw new errors_1.InvalidCategoriesError(message, response.status, invalidCats); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } return { items: response.data.items.map((item) => this.parseMemoryItem(item)), total: response.data.total, hasMore: response.data.has_more, }; } /** * Retrieve a specific memory by ID. * * @param memoryId - UUID of the memory to retrieve. * @param includePreviousVersions - Include full version history. Defaults to true. * @param includeConnectedMemories - Include connected memories. Defaults to true. * @returns UserMemoryItem: Complete memory object. * @throws {RecallrAIError} If the memory is not found or another error occurs. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. */ async getMemory(memoryId, { includePreviousVersions = true, includeConnectedMemories = true, } = {}) { const response = await this.http.get(`/api/v1/users/${this.userId}/memory/${memoryId}`, { include_previous_versions: includePreviousVersions, include_connected_memories: includeConnectedMemories, }); if (response.status === 404) { const detail = response.data?.detail || `Memory ${memoryId} not found`; throw new errors_1.RecallrAIError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } return this.parseMemoryItem(response.data); } /** * Delete a memory (or its version history) for this user. * * @param memoryId - UUID of the memory to delete (can be any version in the chain). * @param deletePreviousVersions - If true, deletes the specified version and all * previous versions in the chain. If false (default), deletes only the specified version. * @throws {RecallrAIError} If the memory is not found or another error occurs. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. */ async deleteMemory(memoryId, deletePreviousVersions = false) { const response = await this.http.delete(`/api/v1/users/${this.userId}/memory/${memoryId}`, { delete_previous_versions: deletePreviousVersions, }); if (response.status === 404) { const detail = response.data?.detail || `Memory ${memoryId} not found`; throw new errors_1.RecallrAIError(detail, response.status); } else if (response.status !== 204) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } } /** * List merge conflicts for this user. * * @param offset - Number of records to skip. Defaults to 0. * @param limit - Maximum number of records to return. Defaults to 10. * @param status - Optional filter by conflict status. * @param sortBy - Field to sort by (created_at, resolved_at). Defaults to "created_at". * @param sortOrder - Sort order (asc, desc). Defaults to "desc". * @returns MergeConflictList: Paginated list of merge conflicts. * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async listMergeConflicts({ offset = 0, limit = 10, status, sortBy = "created_at", sortOrder = "desc", } = {}) { const params = { offset, limit, sort_by: sortBy, sort_order: sortOrder, }; if (status) { params.status = status; } const response = await this.http.get(`/api/v1/users/${this.userId}/merge-conflicts`, params); if (response.status === 404) { const detail = response.data?.detail || `User ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } return { conflicts: response.data.conflicts.map((conflict) => new merge_conflict_1.MergeConflict(this.http, this.userId, this.parseMergeConflictResponse(conflict))), total: response.data.total, hasMore: response.data.has_more, }; } /** * Get a specific merge conflict by ID. * * @param conflictId - Unique identifier of the merge conflict. * @returns MergeConflict: The merge conflict object. * @throws {UserNotFoundError} If the user is not found. * @throws {MergeConflictNotFoundError} If the merge conflict is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. */ async getMergeConflict(conflictId) { const response = await this.http.get(`/api/v1/users/${this.userId}/merge-conflicts/${conflictId}`); if (response.status === 404) { const detail = response.data?.detail || ""; if (detail.includes(`User ${this.userId} not found`)) { throw new errors_1.UserNotFoundError(detail, response.status); } else { throw new errors_1.MergeConflictNotFoundError(detail, response.status); } } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } const conflictData = this.parseMergeConflictResponse(response.data); return new merge_conflict_1.MergeConflict(this.http, this.userId, conflictData); } /** * Get the last N messages for this user across all their sessions. * * This method is useful for chatbot applications where you want to see * the recent conversation history for context. * * @param n - Number of recent messages to retrieve (1-100, default: 10). * @returns UserMessagesList: List of the most recent messages. * @throws {UserNotFoundError} If the user is not found. * @throws {AuthenticationError} If the API key or project ID is invalid. * @throws {InternalServerError} If the server encounters an error. * @throws {NetworkError} If there are network issues. * @throws {TimeoutError} If the request times out. * @throws {RecallrAIError} For other API-related errors. * @throws {Error} If n is not between 1 and 100. */ async getLastNMessages(n) { if (n < 1 || n > 100) { throw new Error("n must be between 1 and 100"); } const response = await this.http.get(`/api/v1/users/${this.userId}/messages`, { limit: n, }); if (response.status === 404) { const detail = response.data?.detail || `User with ID ${this.userId} not found`; throw new errors_1.UserNotFoundError(detail, response.status); } else if (response.status !== 200) { throw new errors_1.RecallrAIError(response.data?.detail || "Unknown error", response.status); } return { messages: response.data.messages.map((msg) => ({ role: msg.role, content: msg.content, timestamp: new Date(msg.timestamp), sessionId: msg.session_id, })), }; } parseUserResponse(data) { const userData = data.user || data; return { userId: userData.custom_user_id, metadata: userData.metadata, mergeConflictEnabled: userData.merge_conflict_enabled, createdAt: new Date(userData.created_at), lastActiveAt: new Date(userData.last_active_at), }; } parseSessionResponse(data) { const sessionData = data.session || data; return { sessionId: sessionData.session_id, status: sessionData.status, createdAt: new Date(sessionData.created_at), metadata: sessionData.metadata, }; } parseMemoryItem(data) { return { memoryId: data.memory_id, categories: data.categories, content: data.content, eventDateStart: new Date(data.event_date_start), eventDateEnd: new Date(data.event_date_end), createdAt: new Date(data.created_at), expiredAt: data.expired_at ? new Date(data.expired_at) : undefined, expirationReason: data.expiration_reason ?? undefined, versionNumber: data.version_number, totalVersions: data.total_versions, hasPreviousVersions: data.has_previous_versions, previousVersions: data.previous_versions ? data.previous_versions.map((v) => ({ memoryId: v.memory_id, versionNumber: v.version_number, content: v.content, eventDateStart: new Date(v.event_date_start), eventDateEnd: new Date(v.event_date_end), createdAt: new Date(v.created_at), expiredAt: new Date(v.expired_at), expirationReason: v.expiration_reason, mergeConflictId: v.merge_conflict_id ?? null, })) : undefined, connectedMemories: data.connected_memories ? data.connected_memories.map((c) => ({ memoryId: c.memory_id, content: c.content, })) : undefined, mergeConflictInProgress: data.merge_conflict_in_progress, mergeConflictId: data.merge_conflict_id ?? null, sessionId: data.session_id, }; } parseMergeConflictResponse(data) { const conflictData = data.conflict || data; return { id: conflictData.id, projectUserSessionId: conflictData.project_user_session_id, proposedMemoryContent: conflictData.proposed_memory_content, conflictingMemories: conflictData.conflicting_memories.map((mem) => ({ memoryId: mem.memory_id, content: mem.content, reason: mem.reason, eventDateStart: new Date(mem.event_date_start), eventDateEnd: new Date(mem.event_date_end), createdAt: new Date(mem.created_at), })), clarifyingQuestions: conflictData.clarifying_questions.map((q) => ({ question: q.question, options: q.options, })), status: conflictData.status, resolutionData: conflictData.resolution_data, createdAt: new Date(conflictData.created_at), resolvedAt: conflictData.resolved_at ? new Date(conflictData.resolved_at) : undefined, }; } toString() { return `<User id=${this.userId} created_at=${this.createdAt} last_active_at=${this.lastActiveAt}>`; } } exports.User = User;