mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-20 21:37:25 +00:00
- Add provider adapter layer (server/providers/) with registry pattern
- Claude, Cursor, Codex, Gemini adapters normalize native formats to NormalizedMessage
- Shared types.js defines ProviderAdapter interface and message kinds
- Registry enables polymorphic provider lookup
- Add unified REST endpoint: GET /api/sessions/:id/messages?provider=...
- Replaces four provider-specific message endpoints with one
- Delegates to provider adapters via registry
- Add frontend session-keyed store (useSessionStore)
- Per-session Map with serverMessages/realtimeMessages/merged
- Dedup by ID, stale threshold for re-fetch, background session accumulation
- No localStorage for messages — backend JSONL is source of truth
- Add normalizedToChatMessages converter (useChatMessages)
- Converts NormalizedMessage[] to existing ChatMessage[] UI format
- Wire unified store into ChatInterface, useChatSessionState, useChatRealtimeHandlers
- Session switch uses store cache for instant render
- Background WebSocket messages routed to correct session slot
120 lines
5.2 KiB
JavaScript
120 lines
5.2 KiB
JavaScript
/**
|
|
* 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<FetchHistoryResult>} 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<NormalizedMessage> & {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,
|
|
};
|
|
}
|