Files
claudecodeui/server/routes/messages.js
simosmik 7b3bc54d1b feat: unified message architecture with provider adapters and session store
- 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
2026-03-19 00:03:07 +00:00

62 lines
1.9 KiB
JavaScript

/**
* Unified messages endpoint.
*
* GET /api/sessions/:sessionId/messages?provider=claude&projectName=foo&limit=50&offset=0
*
* Replaces the four provider-specific session message endpoints with a single route
* that delegates to the appropriate adapter via the provider registry.
*
* @module routes/messages
*/
import express from 'express';
import { getProvider, getAllProviders } from '../providers/registry.js';
const router = express.Router();
/**
* GET /api/sessions/:sessionId/messages
*
* Auth: authenticateToken applied at mount level in index.js
*
* Query params:
* provider - 'claude' | 'cursor' | 'codex' | 'gemini' (default: 'claude')
* projectName - required for claude provider
* projectPath - required for cursor provider (absolute path used for cwdId hash)
* limit - page size (omit or null for all)
* offset - pagination offset (default: 0)
*/
router.get('/:sessionId/messages', async (req, res) => {
try {
const { sessionId } = req.params;
const provider = req.query.provider || 'claude';
const projectName = req.query.projectName || '';
const projectPath = req.query.projectPath || '';
const limitParam = req.query.limit;
const limit = limitParam !== undefined && limitParam !== null && limitParam !== ''
? parseInt(limitParam, 10)
: null;
const offset = parseInt(req.query.offset || '0', 10);
const adapter = getProvider(provider);
if (!adapter) {
const available = getAllProviders().join(', ');
return res.status(400).json({ error: `Unknown provider: ${provider}. Available: ${available}` });
}
const result = await adapter.fetchHistory(sessionId, {
projectName,
projectPath,
limit,
offset,
});
return res.json(result);
} catch (error) {
console.error('Error fetching unified messages:', error);
return res.status(500).json({ error: 'Failed to fetch messages' });
}
});
export default router;