File size: 2,995 Bytes
fb4d8fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import type { ReplyPayload } from "../types.js";
import type { TypingSignaler } from "./typing-mode.js";
import { loadSessionStore } from "../../config/sessions.js";
import { isAudioFileName } from "../../media/mime.js";
import { normalizeVerboseLevel, type VerboseLevel } from "../thinking.js";
import { scheduleFollowupDrain } from "./queue.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));

export const createShouldEmitToolResult = (params: {
  sessionKey?: string;
  storePath?: string;
  resolvedVerboseLevel: VerboseLevel;
}): (() => boolean) => {
  // Normalize verbose values from session store/config so false/"false" still means off.
  const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
  return () => {
    if (!params.sessionKey || !params.storePath) {
      return fallbackVerbose !== "off";
    }
    try {
      const store = loadSessionStore(params.storePath);
      const entry = store[params.sessionKey];
      const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
      if (current) {
        return current !== "off";
      }
    } catch {
      // ignore store read failures
    }
    return fallbackVerbose !== "off";
  };
};

export const createShouldEmitToolOutput = (params: {
  sessionKey?: string;
  storePath?: string;
  resolvedVerboseLevel: VerboseLevel;
}): (() => boolean) => {
  // Normalize verbose values from session store/config so false/"false" still means off.
  const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
  return () => {
    if (!params.sessionKey || !params.storePath) {
      return fallbackVerbose === "full";
    }
    try {
      const store = loadSessionStore(params.storePath);
      const entry = store[params.sessionKey];
      const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
      if (current) {
        return current === "full";
      }
    } catch {
      // ignore store read failures
    }
    return fallbackVerbose === "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();
  }
};