UNPKG

@hashgraphonline/conversational-agent

Version:

Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera

651 lines (650 loc) 22.5 kB
import { ReferenceIdGenerator } from "./index24.js"; import { DEFAULT_CONTENT_REFERENCE_CONFIG, ContentReferenceError } from "./index25.js"; const _ContentStorage = class _ContentStorage { constructor(maxStorage = _ContentStorage.DEFAULT_MAX_STORAGE, referenceConfig) { this.messages = []; this.idCounter = 0; this.contentStore = /* @__PURE__ */ new Map(); this.maxStorage = maxStorage; this.referenceConfig = { ...DEFAULT_CONTENT_REFERENCE_CONFIG, ...referenceConfig }; this.referenceStats = { activeReferences: 0, totalStorageBytes: 0, recentlyCleanedUp: 0, totalResolutions: 0, failedResolutions: 0, averageContentSize: 0, storageUtilization: 0, performanceMetrics: { averageCreationTimeMs: 0, averageResolutionTimeMs: 0, averageCleanupTimeMs: 0, creationTimes: [], resolutionTimes: [], cleanupTimes: [] } }; if (this.referenceConfig.enableAutoCleanup) { this.startReferenceCleanupTimer(); } } /** * Store messages in the content storage * Automatically drops oldest messages if storage limit is exceeded * @param messages - Messages to store * @returns Result indicating how many messages were stored and dropped */ storeMessages(messages) { if (messages.length === 0) { return { stored: 0, dropped: 0 }; } const now = /* @__PURE__ */ new Date(); let dropped = 0; const storedMessages = messages.map((message) => ({ message, storedAt: now, id: this.generateId() })); this.messages.push(...storedMessages); while (this.messages.length > this.maxStorage) { this.messages.shift(); dropped++; } return { stored: storedMessages.length, dropped }; } /** * Get the most recent messages from storage * @param count - Number of recent messages to retrieve * @returns Array of recent messages in chronological order */ getRecentMessages(count) { if (count <= 0 || this.messages.length === 0) { return []; } const startIndex = Math.max(0, this.messages.length - count); return this.messages.slice(startIndex).map((stored) => stored.message); } /** * Search for messages containing specific text or patterns * @param query - Search term or regex pattern * @param options - Search configuration options * @returns Array of matching messages */ searchMessages(query, options = {}) { if (!query || this.messages.length === 0) { return []; } const { caseSensitive = false, limit, useRegex = false } = options; let matches = []; if (useRegex) { try { const regex = new RegExp(query, caseSensitive ? "g" : "gi"); matches = this.messages.filter((stored) => regex.test(stored.message.content)).map((stored) => stored.message); } catch (error) { return []; } } else { const searchTerm = caseSensitive ? query : query.toLowerCase(); matches = this.messages.filter((stored) => { const content = stored.message.content; const searchContent = caseSensitive ? content : content.toLowerCase(); return searchContent.includes(searchTerm); }).map((stored) => stored.message); } return limit ? matches.slice(0, limit) : matches; } /** * Get messages from a specific time range * @param startTime - Start of time range (inclusive) * @param endTime - End of time range (inclusive) * @returns Array of messages within the time range */ getMessagesFromTimeRange(startTime, endTime) { if (startTime > endTime || this.messages.length === 0) { return []; } return this.messages.filter( (stored) => stored.storedAt >= startTime && stored.storedAt <= endTime ).map((stored) => stored.message); } /** * Get storage statistics and usage information * @returns Current storage statistics */ getStorageStats() { const totalMessages = this.messages.length; const usagePercentage = totalMessages > 0 ? Math.round(totalMessages / this.maxStorage * 100) : 0; let oldestMessageTime; let newestMessageTime; if (totalMessages > 0) { oldestMessageTime = this.messages[0].storedAt; newestMessageTime = this.messages[totalMessages - 1].storedAt; } return { totalMessages, maxStorageLimit: this.maxStorage, usagePercentage, oldestMessageTime, newestMessageTime }; } /** * Clear all stored messages */ clear() { this.messages = []; this.idCounter = 0; } /** * Get total number of stored messages * @returns Number of messages currently in storage */ getTotalStoredMessages() { return this.messages.length; } /** * Update the maximum storage limit * @param newLimit - New maximum storage limit */ updateStorageLimit(newLimit) { if (newLimit <= 0) { throw new Error("Storage limit must be greater than 0"); } this.maxStorage = newLimit; while (this.messages.length > this.maxStorage) { this.messages.shift(); } } /** * Get messages by message type * @param messageType - Type of messages to retrieve ('human', 'ai', 'system', etc.) * @param limit - Maximum number of messages to return * @returns Array of messages of the specified type */ getMessagesByType(messageType, limit) { const filtered = this.messages.filter((stored) => stored.message._getType() === messageType).map((stored) => stored.message); return limit ? filtered.slice(0, limit) : filtered; } /** * Get the current storage configuration * @returns Storage configuration object */ getConfig() { return { maxStorage: this.maxStorage, currentUsage: this.messages.length, utilizationPercentage: this.messages.length / this.maxStorage * 100 }; } /** * Generate a unique ID for stored messages * @returns Unique string identifier */ generateId() { return `msg_${++this.idCounter}_${Date.now()}`; } /** * Get messages stored within the last N minutes * @param minutes - Number of minutes to look back * @returns Array of messages from the last N minutes */ getRecentMessagesByTime(minutes) { if (minutes <= 0 || this.messages.length === 0) { return []; } const cutoffTime = new Date(Date.now() - minutes * 60 * 1e3); return this.messages.filter((stored) => stored.storedAt >= cutoffTime).map((stored) => stored.message); } /** * Export messages to a JSON-serializable format * @returns Serializable representation of stored messages */ exportMessages() { return this.messages.map((stored) => ({ content: stored.message.content, type: stored.message._getType(), storedAt: stored.storedAt.toISOString(), id: stored.id })); } /** * Determine if content should be stored as a reference based on size */ shouldUseReference(content) { const size = Buffer.isBuffer(content) ? content.length : Buffer.byteLength(content, "utf8"); return size > this.referenceConfig.sizeThresholdBytes; } /** * Store content and return a reference if it exceeds the size threshold * Otherwise returns null to indicate direct content should be used */ async storeContentIfLarge(content, metadata) { const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8"); if (!this.shouldUseReference(buffer)) { return null; } const storeMetadata = { contentType: metadata.contentType || this.detectContentType(buffer, metadata.mimeType), sizeBytes: buffer.length, source: metadata.source, tags: [] }; if (metadata.mimeType !== void 0) { storeMetadata.mimeType = metadata.mimeType; } if (metadata.mcpToolName !== void 0) { storeMetadata.mcpToolName = metadata.mcpToolName; } if (metadata.fileName !== void 0) { storeMetadata.fileName = metadata.fileName; } if (metadata.tags !== void 0) { storeMetadata.tags = metadata.tags; } if (metadata.customMetadata !== void 0) { storeMetadata.customMetadata = metadata.customMetadata; } return await this.storeContent(buffer, storeMetadata); } /** * Store content and return a reference (implements ContentReferenceStore) */ async storeContent(content, metadata) { const startTime = Date.now(); try { const now = /* @__PURE__ */ new Date(); const referenceId = ReferenceIdGenerator.generateId(content); const fullMetadata = { ...metadata, createdAt: now, lastAccessedAt: now, accessCount: 0 }; const storedContent = { content, metadata: fullMetadata, state: "active" }; const expirationTime = this.calculateExpirationTime(metadata.source); if (expirationTime !== void 0) { storedContent.expiresAt = expirationTime; } this.contentStore.set(referenceId, storedContent); this.updateStatsAfterStore(content.length); await this.enforceReferenceStorageLimits(); const preview = this.createContentPreview(content, fullMetadata.contentType); const referenceMetadata = { contentType: fullMetadata.contentType, sizeBytes: fullMetadata.sizeBytes, source: fullMetadata.source }; if (fullMetadata.fileName !== void 0) { referenceMetadata.fileName = fullMetadata.fileName; } if (fullMetadata.mimeType !== void 0) { referenceMetadata.mimeType = fullMetadata.mimeType; } const reference = { referenceId, state: "active", preview, metadata: referenceMetadata, createdAt: now, format: "ref://{id}" }; const duration = Date.now() - startTime; this.recordPerformanceMetric("creation", duration); return reference; } catch (error) { const duration = Date.now() - startTime; this.recordPerformanceMetric("creation", duration); throw new ContentReferenceError( `Failed to store content: ${error instanceof Error ? error.message : "Unknown error"}`, "system_error", void 0, ["Try again", "Check storage limits", "Contact administrator"] ); } } /** * Resolve a reference to its content (implements ContentReferenceStore) */ async resolveReference(referenceId) { const startTime = Date.now(); try { if (!ReferenceIdGenerator.isValidReferenceId(referenceId)) { this.referenceStats.failedResolutions++; return { success: false, error: "Invalid reference ID format", errorType: "not_found", suggestedActions: ["Check the reference ID format", "Ensure the reference ID is complete"] }; } const storedContent = this.contentStore.get(referenceId); if (!storedContent) { this.referenceStats.failedResolutions++; return { success: false, error: "Reference not found", errorType: "not_found", suggestedActions: ["Verify the reference ID", "Check if the content has expired", "Request fresh content"] }; } if (storedContent.expiresAt && storedContent.expiresAt < /* @__PURE__ */ new Date()) { storedContent.state = "expired"; this.referenceStats.failedResolutions++; return { success: false, error: "Reference has expired", errorType: "expired", suggestedActions: ["Request fresh content", "Use alternative content source"] }; } if (storedContent.state !== "active") { this.referenceStats.failedResolutions++; return { success: false, error: `Reference is ${storedContent.state}`, errorType: storedContent.state === "expired" ? "expired" : "corrupted", suggestedActions: ["Request fresh content", "Check reference validity"] }; } storedContent.metadata.lastAccessedAt = /* @__PURE__ */ new Date(); storedContent.metadata.accessCount++; this.referenceStats.totalResolutions++; const duration = Date.now() - startTime; this.recordPerformanceMetric("resolution", duration); return { success: true, content: storedContent.content, metadata: storedContent.metadata }; } catch (error) { const duration = Date.now() - startTime; this.recordPerformanceMetric("resolution", duration); this.referenceStats.failedResolutions++; return { success: false, error: `System error resolving reference: ${error instanceof Error ? error.message : "Unknown error"}`, errorType: "system_error", suggestedActions: ["Try again", "Contact administrator"] }; } } /** * Check if a reference exists and is valid */ async hasReference(referenceId) { if (!ReferenceIdGenerator.isValidReferenceId(referenceId)) { return false; } const storedContent = this.contentStore.get(referenceId); if (!storedContent) { return false; } if (storedContent.expiresAt && storedContent.expiresAt < /* @__PURE__ */ new Date()) { storedContent.state = "expired"; return false; } return storedContent.state === "active"; } /** * Mark a reference for cleanup */ async cleanupReference(referenceId) { const storedContent = this.contentStore.get(referenceId); if (!storedContent) { return false; } this.referenceStats.totalStorageBytes -= storedContent.content.length; this.referenceStats.activeReferences--; this.referenceStats.recentlyCleanedUp++; this.contentStore.delete(referenceId); return true; } /** * Get current reference storage statistics (implements ContentReferenceStore) */ async getStats() { this.updateReferenceStorageStats(); return { ...this.referenceStats, performanceMetrics: { averageCreationTimeMs: this.calculateAverage(this.referenceStats.performanceMetrics.creationTimes), averageResolutionTimeMs: this.calculateAverage(this.referenceStats.performanceMetrics.resolutionTimes), averageCleanupTimeMs: this.calculateAverage(this.referenceStats.performanceMetrics.cleanupTimes) } }; } /** * Update reference configuration */ async updateConfig(config) { this.referenceConfig = { ...this.referenceConfig, ...config }; if (this.cleanupTimer) { clearInterval(this.cleanupTimer); delete this.cleanupTimer; } if (this.referenceConfig.enableAutoCleanup) { this.startReferenceCleanupTimer(); } } /** * Perform cleanup based on current policies (implements ContentReferenceStore) */ async performCleanup() { const startTime = Date.now(); const errors = []; let cleanedUp = 0; try { const now = /* @__PURE__ */ new Date(); const toCleanup = []; for (const [referenceId, storedContent] of this.contentStore.entries()) { let shouldCleanup = false; if (storedContent.expiresAt && storedContent.expiresAt < now) { shouldCleanup = true; storedContent.state = "expired"; } const ageMs = now.getTime() - storedContent.metadata.createdAt.getTime(); const policy = this.getCleanupPolicy(storedContent.metadata.source); if (ageMs > policy.maxAgeMs) { shouldCleanup = true; } if (storedContent.state === "cleanup_pending") { shouldCleanup = true; } if (shouldCleanup) { toCleanup.push(referenceId); } } toCleanup.sort((a, b) => { const aContent = this.contentStore.get(a); const bContent = this.contentStore.get(b); const aPriority = this.getCleanupPolicy(aContent.metadata.source).priority; const bPriority = this.getCleanupPolicy(bContent.metadata.source).priority; return bPriority - aPriority; }); for (const referenceId of toCleanup) { try { const success = await this.cleanupReference(referenceId); if (success) { cleanedUp++; } } catch (error) { errors.push(`Failed to cleanup ${referenceId}: ${error instanceof Error ? error.message : "Unknown error"}`); } } if (this.contentStore.size > this.referenceConfig.maxReferences) { const sortedByAge = Array.from(this.contentStore.entries()).sort(([, a], [, b]) => a.metadata.lastAccessedAt.getTime() - b.metadata.lastAccessedAt.getTime()); const excessCount = this.contentStore.size - this.referenceConfig.maxReferences; for (let i = 0; i < excessCount && i < sortedByAge.length; i++) { const [referenceId] = sortedByAge[i]; try { const success = await this.cleanupReference(referenceId); if (success) { cleanedUp++; } } catch (error) { errors.push(`Failed to cleanup excess reference ${referenceId}: ${error instanceof Error ? error.message : "Unknown error"}`); } } } const duration = Date.now() - startTime; this.recordPerformanceMetric("cleanup", duration); return { cleanedUp, errors }; } catch (error) { const duration = Date.now() - startTime; this.recordPerformanceMetric("cleanup", duration); const errorMessage = `Cleanup process failed: ${error instanceof Error ? error.message : "Unknown error"}`; errors.push(errorMessage); return { cleanedUp, errors }; } } /** * Get reference configuration for debugging */ getReferenceConfig() { return { ...this.referenceConfig }; } async enforceReferenceStorageLimits() { if (this.contentStore.size >= this.referenceConfig.maxReferences) { await this.performCleanup(); } if (this.referenceStats.totalStorageBytes >= this.referenceConfig.maxTotalStorageBytes) { await this.performCleanup(); } } calculateExpirationTime(source) { const policy = this.getCleanupPolicy(source); return new Date(Date.now() + policy.maxAgeMs); } getCleanupPolicy(source) { switch (source) { case "mcp_tool": return this.referenceConfig.cleanupPolicies.recent; case "user_upload": return this.referenceConfig.cleanupPolicies.userContent; case "agent_generated": return this.referenceConfig.cleanupPolicies.agentGenerated; default: return this.referenceConfig.cleanupPolicies.default; } } detectContentType(content, mimeType) { if (mimeType) { if (mimeType === "text/html") return "html"; if (mimeType === "text/markdown") return "markdown"; if (mimeType === "application/json") return "json"; if (mimeType.startsWith("text/")) return "text"; return "binary"; } const contentStr = content.toString("utf8", 0, Math.min(content.length, 1e3)); if (contentStr.startsWith("{") || contentStr.startsWith("[")) return "json"; if (contentStr.includes("<html>") || contentStr.includes("<!DOCTYPE")) return "html"; if (contentStr.includes("#") && contentStr.includes("\n")) return "markdown"; return "text"; } createContentPreview(content, contentType) { const maxLength = 200; let preview = content.toString("utf8", 0, Math.min(content.length, maxLength * 2)); if (contentType === "html") { preview = preview.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim(); } else if (contentType === "json") { try { const parsed = JSON.parse(preview); preview = JSON.stringify(parsed, null, 0); } catch { } } preview = preview.trim(); if (preview.length > maxLength) { preview = preview.substring(0, maxLength) + "..."; } return preview || "[Binary content]"; } updateStatsAfterStore(sizeBytes) { this.referenceStats.activeReferences++; this.referenceStats.totalStorageBytes += sizeBytes; this.updateReferenceStorageStats(); } updateReferenceStorageStats() { if (this.referenceStats.activeReferences > 0) { this.referenceStats.averageContentSize = this.referenceStats.totalStorageBytes / this.referenceStats.activeReferences; } this.referenceStats.storageUtilization = this.referenceStats.totalStorageBytes / this.referenceConfig.maxTotalStorageBytes * 100; let mostAccessedId; let maxAccess = 0; for (const [referenceId, storedContent] of this.contentStore.entries()) { if (storedContent.metadata.accessCount > maxAccess) { maxAccess = storedContent.metadata.accessCount; mostAccessedId = referenceId; } } if (mostAccessedId !== void 0) { this.referenceStats.mostAccessedReferenceId = mostAccessedId; } else { delete this.referenceStats.mostAccessedReferenceId; } } recordPerformanceMetric(type, timeMs) { const metrics = this.referenceStats.performanceMetrics; const maxRecords = 100; switch (type) { case "creation": metrics.creationTimes.push(timeMs); if (metrics.creationTimes.length > maxRecords) { metrics.creationTimes.shift(); } break; case "resolution": metrics.resolutionTimes.push(timeMs); if (metrics.resolutionTimes.length > maxRecords) { metrics.resolutionTimes.shift(); } break; case "cleanup": metrics.cleanupTimes.push(timeMs); if (metrics.cleanupTimes.length > maxRecords) { metrics.cleanupTimes.shift(); } break; } } calculateAverage(times) { if (times.length === 0) return 0; return times.reduce((sum, time) => sum + time, 0) / times.length; } startReferenceCleanupTimer() { this.cleanupTimer = setInterval(async () => { try { await this.performCleanup(); } catch (error) { } }, this.referenceConfig.cleanupIntervalMs); } /** * Clean up resources (enhanced to include reference cleanup) */ async dispose() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); delete this.cleanupTimer; } this.contentStore.clear(); this.clear(); } }; _ContentStorage.DEFAULT_MAX_STORAGE = 1e3; let ContentStorage = _ContentStorage; export { ContentStorage }; //# sourceMappingURL=index14.js.map