227 lines
5.3 KiB
TypeScript
227 lines
5.3 KiB
TypeScript
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<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Workspace state (current user context)
|
|
*/
|
|
export interface WorkspaceContext {
|
|
activeIndicators: string[];
|
|
activeStrategies: string[];
|
|
watchlist: string[];
|
|
recentQueries: string[];
|
|
preferences: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Memory chunk from RAG retrieval
|
|
*/
|
|
export interface MemoryChunk {
|
|
id: string;
|
|
content: string;
|
|
role: 'user' | 'assistant' | 'system';
|
|
timestamp: number;
|
|
relevanceScore: number;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* 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<ChannelCapabilities>;
|
|
}): 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<UserContext> {
|
|
const parsed = JSON.parse(data);
|
|
return {
|
|
...parsed,
|
|
createdAt: new Date(parsed.createdAt),
|
|
lastActivity: new Date(parsed.lastActivity),
|
|
conversationHistory: [], // Will be loaded from checkpoint
|
|
};
|
|
}
|