import type { License, ChannelType } from '../../types/user.js'; import type { BaseMessage } from '@langchain/core/messages'; /** * Channel capabilities (what the channel supports) */ export interface ChannelCapabilities { supportsMarkdown: boolean; supportsImages: boolean; supportsButtons: boolean; supportsVoice: boolean; supportsFiles: boolean; maxMessageLength: number; } /** * Active channel information for multi-channel routing */ export interface ActiveChannel { type: ChannelType; channelUserId: string; // Platform-specific ID (telegram_id, discord_id, etc) capabilities: ChannelCapabilities; metadata?: Record; } /** * Workspace state (current user context) */ export interface WorkspaceContext { activeIndicators: string[]; activeStrategies: string[]; watchlist: string[]; recentQueries: string[]; preferences: Record; } /** * Memory chunk from RAG retrieval */ export interface MemoryChunk { id: string; content: string; role: 'user' | 'assistant' | 'system'; timestamp: number; relevanceScore: number; metadata?: Record; } /** * Enhanced user context for agent harness * * Contains all necessary context for an agent session: * - User identity and license * - Active channel info (for multi-channel support) * - Conversation state and history * - RAG-retrieved relevant memories * - Workspace state * * This object is passed to all agent nodes and tools. */ export interface UserContext { // Identity userId: string; sessionId: string; license: License; // Channel context (for multi-channel routing) activeChannel: ActiveChannel; // Conversation state conversationHistory: BaseMessage[]; currentMessage?: string; // RAG context relevantMemories: MemoryChunk[]; // Workspace state workspaceState: WorkspaceContext; // Metadata createdAt: Date; lastActivity: Date; } /** * Get default channel capabilities based on type */ export function getDefaultCapabilities(channelType: ChannelType): ChannelCapabilities { switch (channelType) { case 'websocket': return { supportsMarkdown: true, supportsImages: true, supportsButtons: true, supportsVoice: false, supportsFiles: true, maxMessageLength: 100000, }; case 'telegram': return { supportsMarkdown: true, supportsImages: true, supportsButtons: true, supportsVoice: true, supportsFiles: true, maxMessageLength: 4096, }; case 'slack': return { supportsMarkdown: true, supportsImages: true, supportsButtons: true, supportsVoice: false, supportsFiles: true, maxMessageLength: 40000, }; case 'discord': return { supportsMarkdown: true, supportsImages: true, supportsButtons: true, supportsVoice: true, supportsFiles: true, maxMessageLength: 2000, }; default: // Default fallback return { supportsMarkdown: false, supportsImages: false, supportsButtons: false, supportsVoice: false, supportsFiles: false, maxMessageLength: 1000, }; } } /** * Create a new user context */ export function createUserContext(params: { userId: string; sessionId: string; license: License; channelType: ChannelType; channelUserId: string; channelCapabilities?: Partial; }): UserContext { const defaultCapabilities = getDefaultCapabilities(params.channelType); const capabilities: ChannelCapabilities = { ...defaultCapabilities, ...params.channelCapabilities, }; return { userId: params.userId, sessionId: params.sessionId, license: params.license, activeChannel: { type: params.channelType, channelUserId: params.channelUserId, capabilities, }, conversationHistory: [], relevantMemories: [], workspaceState: { activeIndicators: [], activeStrategies: [], watchlist: [], recentQueries: [], preferences: {}, }, createdAt: new Date(), lastActivity: new Date(), }; } /** * Update last activity timestamp */ export function touchContext(context: UserContext): UserContext { return { ...context, lastActivity: new Date(), }; } /** * Check if context has expired (for TTL management) */ export function isContextExpired(context: UserContext, ttlSeconds: number): boolean { const now = Date.now(); const lastActivity = context.lastActivity.getTime(); return (now - lastActivity) / 1000 > ttlSeconds; } /** * Serialize context for Redis storage */ export function serializeContext(context: UserContext): string { return JSON.stringify({ ...context, createdAt: context.createdAt.toISOString(), lastActivity: context.lastActivity.toISOString(), // Don't serialize conversation history (too large, use checkpoint instead) conversationHistory: undefined, }); } /** * Deserialize context from Redis storage */ export function deserializeContext(data: string): Partial { const parsed = JSON.parse(data); return { ...parsed, createdAt: new Date(parsed.createdAt), lastActivity: new Date(parsed.lastActivity), conversationHistory: [], // Will be loaded from checkpoint }; }