import { loadSessionStore } from "../../config/sessions.js"; import { isAudioFileName } from "../../media/mime.js"; import { normalizeVerboseLevel, type VerboseLevel } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; import { scheduleFollowupDrain } from "./queue.js"; import type { TypingSignaler } from "./typing-mode.js"; const hasAudioMedia = (urls?: string[]): boolean => Boolean(urls?.some((url) => isAudioFileName(url))); export const isAudioPayload = (payload: ReplyPayload): boolean => hasAudioMedia(payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : undefined)); type VerboseGateParams = { sessionKey?: string; storePath?: string; resolvedVerboseLevel: VerboseLevel; }; function resolveCurrentVerboseLevel(params: VerboseGateParams): VerboseLevel | undefined { if (!params.sessionKey || !params.storePath) { return undefined; } try { const store = loadSessionStore(params.storePath); const entry = store[params.sessionKey]; return normalizeVerboseLevel(String(entry?.verboseLevel ?? "")); } catch { // ignore store read failures return undefined; } } function createVerboseGate( params: VerboseGateParams, shouldEmit: (level: VerboseLevel) => boolean, ): () => boolean { // Normalize verbose values from session store/config so false/"false" still means off. const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off"; return () => { return shouldEmit(resolveCurrentVerboseLevel(params) ?? fallbackVerbose); }; } export const createShouldEmitToolResult = (params: VerboseGateParams): (() => boolean) => { return createVerboseGate(params, (level) => level !== "off"); }; export const createShouldEmitToolOutput = (params: VerboseGateParams): (() => boolean) => { return createVerboseGate(params, (level) => level === "full"); }; export const finalizeWithFollowup = ( value: T, queueKey: string, runFollowupTurn: Parameters[1], ): T => { scheduleFollowupDrain(queueKey, runFollowupTurn); return value; }; export const signalTypingIfNeeded = async ( payloads: ReplyPayload[], typingSignals: TypingSignaler, ): Promise => { const shouldSignalTyping = payloads.some((payload) => { const trimmed = payload.text?.trim(); if (trimmed) { return true; } if (payload.mediaUrl) { return true; } if (payload.mediaUrls && payload.mediaUrls.length > 0) { return true; } return false; }); if (shouldSignalTyping) { await typingSignals.signalRunStart(); } };