| 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 { |
| |
| return undefined; |
| } |
| } |
|
|
| function createVerboseGate( |
| params: VerboseGateParams, |
| shouldEmit: (level: VerboseLevel) => boolean, |
| ): () => boolean { |
| |
| 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 = <T>( |
| value: T, |
| queueKey: string, |
| runFollowupTurn: Parameters<typeof scheduleFollowupDrain>[1], |
| ): T => { |
| scheduleFollowupDrain(queueKey, runFollowupTurn); |
| return value; |
| }; |
|
|
| export const signalTypingIfNeeded = async ( |
| payloads: ReplyPayload[], |
| typingSignals: TypingSignaler, |
| ): Promise<void> => { |
| 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(); |
| } |
| }; |
|
|