import type { MessageCost } from '@@/api/chat/types'; import { getUserSettings } from 'officerdb'; import { logger } from '@@/api/chat/logger'; import { sendClaudeCode, clearClaudeCodeSession } from './send-claude-code'; const DEFAULT_MODEL = 'claude-code'; type SendAndAwaitParams = { userId: number; email: string; username: string; prompt: string; context: string; contextId: string; model?: string; }; type SendAndAwaitResult = { text: string; sessionId: string; model: string; cost: MessageCost; }; // Per-session mutex to serialize concurrent prompts const sessionLocks = new Map>(); // Channel model overrides — survive session eviction/recreation const channelModelOverrides = new Map(); function buildSessionId(context: string, userId: number, contextId: string): string { return `channel-${context}-${userId}-${contextId}`; } async function getUserDefaultModel(userId: number): Promise { try { const settings = await getUserSettings(userId); const chat = settings?.chat as Record | undefined; return (chat?.defaultModel as string) || null; } catch { return null; } } export function getSessionModel(context: string, userId: number, contextId: string): string | null { return channelModelOverrides.get(buildSessionId(context, userId, contextId)) ?? null; } export function setSessionModel(context: string, userId: number, contextId: string, model: string): void { const sessionId = buildSessionId(context, userId, contextId); channelModelOverrides.set(sessionId, model); // Reset the Claude session so the next prompt starts fresh under the new model. clearClaudeCodeSession(sessionId); logger.info('Channel model override stored', { sessionId, model }); } export async function sendAndAwait(params: SendAndAwaitParams): Promise { const { userId, context, contextId } = params; const sessionId = buildSessionId(context, userId, contextId); // Serialize per session — if two messages arrive at once, the second waits for the first. const existing = sessionLocks.get(sessionId) ?? Promise.resolve(); let releaseLock: () => void; const lockPromise = new Promise((resolve) => { releaseLock = resolve; }); const chained = existing.then(() => lockPromise); sessionLocks.set(sessionId, chained); await existing; try { const override = channelModelOverrides.get(sessionId); let model = params.model ?? override ?? (await getUserDefaultModel(userId)) ?? DEFAULT_MODEL; // Claude-only: coerce any legacy non-Claude model preference to the Claude default. if (!model.startsWith('claude-code')) model = DEFAULT_MODEL; return await sendClaudeCode({ userId: params.userId, email: params.email, username: params.username, prompt: params.prompt, sessionKey: sessionId, model, }); } finally { releaseLock!(); if (sessionLocks.get(sessionId) === chained) sessionLocks.delete(sessionId); } }