mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-21 13:57:24 +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
80 lines
2.0 KiB
JavaScript
80 lines
2.0 KiB
JavaScript
// Gemini Response Handler - JSON Stream processing
|
|
import { geminiAdapter } from './providers/gemini/adapter.js';
|
|
|
|
class GeminiResponseHandler {
|
|
constructor(ws, options = {}) {
|
|
this.ws = ws;
|
|
this.buffer = '';
|
|
this.onContentFragment = options.onContentFragment || null;
|
|
this.onInit = options.onInit || null;
|
|
this.onToolUse = options.onToolUse || null;
|
|
this.onToolResult = options.onToolResult || null;
|
|
}
|
|
|
|
// Process incoming raw data from Gemini stream-json
|
|
processData(data) {
|
|
this.buffer += data;
|
|
|
|
// Split by newline
|
|
const lines = this.buffer.split('\n');
|
|
|
|
// Keep the last incomplete line in the buffer
|
|
this.buffer = lines.pop() || '';
|
|
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
|
|
try {
|
|
const event = JSON.parse(line);
|
|
this.handleEvent(event);
|
|
} catch (err) {
|
|
// Not a JSON line, probably debug output or CLI warnings
|
|
}
|
|
}
|
|
}
|
|
|
|
handleEvent(event) {
|
|
const sid = typeof this.ws.getSessionId === 'function' ? this.ws.getSessionId() : null;
|
|
|
|
if (event.type === 'init') {
|
|
if (this.onInit) {
|
|
this.onInit(event);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Invoke per-type callbacks for session tracking
|
|
if (event.type === 'message' && event.role === 'assistant') {
|
|
const content = event.content || '';
|
|
if (this.onContentFragment && content) {
|
|
this.onContentFragment(content);
|
|
}
|
|
} else if (event.type === 'tool_use' && this.onToolUse) {
|
|
this.onToolUse(event);
|
|
} else if (event.type === 'tool_result' && this.onToolResult) {
|
|
this.onToolResult(event);
|
|
}
|
|
|
|
// Normalize via adapter and send all resulting messages
|
|
const normalized = geminiAdapter.normalizeMessage(event, sid);
|
|
for (const msg of normalized) {
|
|
this.ws.send(msg);
|
|
}
|
|
}
|
|
|
|
forceFlush() {
|
|
if (this.buffer.trim()) {
|
|
try {
|
|
const event = JSON.parse(this.buffer);
|
|
this.handleEvent(event);
|
|
} catch (err) { }
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.buffer = '';
|
|
}
|
|
}
|
|
|
|
export default GeminiResponseHandler;
|