/** * Provider Types & Interface * * Defines the normalized message format and the provider adapter interface. * All providers normalize their native formats into NormalizedMessage * before sending over REST or WebSocket. * * @module providers/types */ // ─── Session Provider ──────────────────────────────────────────────────────── /** * @typedef {'claude' | 'cursor' | 'codex' | 'gemini'} SessionProvider */ // ─── Message Kind ──────────────────────────────────────────────────────────── /** * @typedef {'text' | 'tool_use' | 'tool_result' | 'thinking' | 'stream_delta' | 'stream_end' * | 'error' | 'complete' | 'status' | 'permission_request' | 'permission_cancelled' * | 'session_created' | 'interactive_prompt' | 'task_notification'} MessageKind */ // ─── NormalizedMessage ─────────────────────────────────────────────────────── /** * @typedef {Object} NormalizedMessage * @property {string} id - Unique message id (for dedup between server + realtime) * @property {string} sessionId * @property {string} timestamp - ISO 8601 * @property {SessionProvider} provider * @property {MessageKind} kind * * Additional fields depending on kind: * - text: role ('user'|'assistant'), content, images? * - tool_use: toolName, toolInput, toolId * - tool_result: toolId, content, isError * - thinking: content * - stream_delta: content * - stream_end: (no extra fields) * - error: content * - complete: (no extra fields) * - status: text, tokens?, canInterrupt? * - permission_request: requestId, toolName, input, context? * - permission_cancelled: requestId * - session_created: newSessionId * - interactive_prompt: content * - task_notification: status, summary */ // ─── Fetch History ─────────────────────────────────────────────────────────── /** * @typedef {Object} FetchHistoryOptions * @property {string} [projectName] - Project name (required for Claude) * @property {string} [projectPath] - Absolute project path (required for Cursor cwdId hash) * @property {number|null} [limit] - Page size (null = all messages) * @property {number} [offset] - Pagination offset (default: 0) */ /** * @typedef {Object} FetchHistoryResult * @property {NormalizedMessage[]} messages - Normalized messages * @property {number} total - Total number of messages in the session * @property {boolean} hasMore - Whether more messages exist before the current page * @property {number} offset - Current offset * @property {number|null} limit - Page size used * @property {object} [tokenUsage] - Token usage data (provider-specific) */ // ─── Provider Adapter Interface ────────────────────────────────────────────── /** * Every provider adapter MUST implement this interface. * * @typedef {Object} ProviderAdapter * * @property {(sessionId: string, opts?: FetchHistoryOptions) => Promise} fetchHistory * Read persisted session messages from disk/database and return them as NormalizedMessage[]. * The backend calls this from the unified GET /api/sessions/:id/messages endpoint. * * Provider implementations: * - Claude: reads ~/.claude/projects/{projectName}/*.jsonl * - Cursor: reads from SQLite store.db (via normalizeCursorBlobs helper) * - Codex: reads ~/.codex/sessions/*.jsonl * - Gemini: reads from in-memory sessionManager or ~/.gemini/tmp/ JSON files * * @property {(raw: any, sessionId: string) => NormalizedMessage[]} normalizeMessage * Normalize a provider-specific event (JSONL entry or live SDK event) into NormalizedMessage[]. * Used by provider files to convert both history and realtime events. */ // ─── Runtime Helpers ───────────────────────────────────────────────────────── /** * Generate a unique message ID. * Uses crypto.randomUUID() to avoid collisions across server restarts and workers. * @param {string} [prefix='msg'] - Optional prefix * @returns {string} */ export function generateMessageId(prefix = 'msg') { return `${prefix}_${crypto.randomUUID()}`; } /** * Create a NormalizedMessage with common fields pre-filled. * @param {Partial & {kind: MessageKind, provider: SessionProvider}} fields * @returns {NormalizedMessage} */ export function createNormalizedMessage(fields) { return { ...fields, id: fields.id || generateMessageId(fields.kind), sessionId: fields.sessionId || '', timestamp: fields.timestamp || new Date().toISOString(), provider: fields.provider, }; }