refactor(discord): share voice log preview formatting

This commit is contained in:
Vincent Koc
2026-06-22 14:04:36 +08:00
parent 4506d8bad6
commit 5fbb5f75ed
5 changed files with 28 additions and 30 deletions

View File

@@ -0,0 +1,13 @@
import { describe, expect, it } from "vitest";
import { formatVoiceLogPreview } from "./log-preview.js";
describe("formatVoiceLogPreview", () => {
it("collapses whitespace and trims the preview", () => {
expect(formatVoiceLogPreview(" hello \n world\t")).toBe("hello world");
});
it("truncates long previews after 500 characters", () => {
const preview = formatVoiceLogPreview("x".repeat(501));
expect(preview).toBe(`${"x".repeat(500)}...`);
});
});

View File

@@ -0,0 +1,9 @@
const DISCORD_VOICE_LOG_PREVIEW_CHARS = 500;
export function formatVoiceLogPreview(text: string): string {
const oneLine = text.replace(/\s+/g, " ").trim();
if (oneLine.length <= DISCORD_VOICE_LOG_PREVIEW_CHARS) {
return oneLine;
}
return `${oneLine.slice(0, DISCORD_VOICE_LOG_PREVIEW_CHARS)}...`;
}

View File

@@ -35,6 +35,7 @@ import {
resolveDiscordVoiceIngressContext,
runDiscordVoiceAgentTurn,
} from "./ingress.js";
import { formatVoiceLogPreview } from "./log-preview.js";
import {
DiscordRealtimeVoiceSession,
type DiscordVoiceMode,
@@ -67,7 +68,6 @@ import {
import { DiscordVoiceSpeakerContextResolver } from "./speaker-context.js";
const logger = createSubsystemLogger("discord/voice");
const VOICE_LOG_PREVIEW_CHARS = 500;
const FOLLOW_USERS_RECONCILE_INTERVAL_MS = 10_000;
const FOLLOW_USERS_RECONCILE_MAX_GUILDS_PER_RUN = 4;
const FOLLOW_USERS_RECONCILE_MAX_REST_LOOKUPS_PER_RUN = 32;
@@ -96,14 +96,6 @@ type VoiceChannelResidency = {
channelId: string;
};
function formatVoiceLogPreview(text: string): string {
const oneLine = text.replace(/\s+/g, " ").trim();
if (oneLine.length <= VOICE_LOG_PREVIEW_CHARS) {
return oneLine;
}
return `${oneLine.slice(0, VOICE_LOG_PREVIEW_CHARS)}...`;
}
function isVoiceConnectionDestroyed(
connection: DiscordVoiceConnection,
voiceSdk: DiscordVoiceSdk,

View File

@@ -51,6 +51,7 @@ import {
convertDiscordPcm48kStereoToRealtimePcm24kMono,
convertRealtimePcm24kMonoToDiscordPcm48kStereo,
} from "./audio.js";
import { formatVoiceLogPreview } from "./log-preview.js";
import { formatVoiceIngressPrompt } from "./prompt.js";
import { loadDiscordVoiceSdk } from "./sdk-runtime.js";
import {
@@ -81,7 +82,6 @@ const DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_LIMIT = 16;
const DISCORD_REALTIME_RECENT_AGENT_PROXY_CONSULT_TTL_MS = 15_000;
const DISCORD_REALTIME_IGNORED_WAKE_NAME_CONTEXT_TTL_MS = 10_000;
const DISCORD_REALTIME_WAKE_NAME_FOLLOWUP_TTL_MS = 10_000;
const DISCORD_REALTIME_LOG_PREVIEW_CHARS = 500;
const DISCORD_REALTIME_DEFAULT_MIN_BARGE_IN_AUDIO_END_MS = 250;
const DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS = 200;
const DISCORD_REALTIME_DUPLICATE_ERROR_SUPPRESS_MS = 60_000;
@@ -139,14 +139,6 @@ type AgentProxyConsultState = {
type AgentProxyConsultHandle = RealtimeVoiceForcedConsultHandle<AgentProxyConsultState>;
function formatRealtimeLogPreview(text: string): string {
const oneLine = text.replace(/\s+/g, " ").trim();
if (oneLine.length <= DISCORD_REALTIME_LOG_PREVIEW_CHARS) {
return oneLine;
}
return `${oneLine.slice(0, DISCORD_REALTIME_LOG_PREVIEW_CHARS)}...`;
}
function formatRealtimeInterruptionLog(event: RealtimeVoiceBridgeEvent): string | undefined {
const detail = event.detail ? ` ${event.detail}` : "";
if (event.direction === "client") {
@@ -522,7 +514,7 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
onTranscript: (role, text, isFinal) => {
if (isFinal && text.trim()) {
logger.info(
`discord voice: realtime ${role} transcript (${text.length} chars): ${formatRealtimeLogPreview(text)}`,
`discord voice: realtime ${role} transcript (${text.length} chars): ${formatVoiceLogPreview(text)}`,
);
}
if (isFinal && role === "assistant") {
@@ -1420,7 +1412,7 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
if (skipReason) {
const context = this.consumePendingSpeakerContext();
logger.info(
`discord voice: realtime forced agent consult skipped reason=${skipReason} chars=${question.length} speaker=${context?.speakerLabel ?? "unknown"} transcript=${formatRealtimeLogPreview(question)}`,
`discord voice: realtime forced agent consult skipped reason=${skipReason} chars=${question.length} speaker=${context?.speakerLabel ?? "unknown"} transcript=${formatVoiceLogPreview(question)}`,
);
return undefined;
}

View File

@@ -8,6 +8,7 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
import { maybeControlDiscordVoiceAgentRun } from "./agent-control.js";
import { createDiscordOpusPlaybackStream } from "./audio.js";
import { resolveDiscordVoiceIngressContext, runDiscordVoiceAgentTurn } from "./ingress.js";
import { formatVoiceLogPreview } from "./log-preview.js";
import { formatVoiceIngressPrompt } from "./prompt.js";
import { loadDiscordVoiceSdk } from "./sdk-runtime.js";
import {
@@ -19,17 +20,8 @@ import {
import type { DiscordVoiceSpeakerContextResolver } from "./speaker-context.js";
import { synthesizeVoiceReplyAudio, transcribeVoiceAudio } from "./tts.js";
const VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS = 500;
const logger = createSubsystemLogger("discord/voice");
function formatVoiceTranscriptLogPreview(text: string): string {
const oneLine = text.replace(/\s+/g, " ").trim();
if (oneLine.length <= VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS) {
return oneLine;
}
return `${oneLine.slice(0, VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS)}...`;
}
export async function processDiscordVoiceSegment(params: {
entry: VoiceSessionEntry;
wavPath: string;
@@ -78,7 +70,7 @@ export async function processDiscordVoiceSegment(params: {
`transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`,
);
logVoiceVerbose(
`transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceTranscriptLogPreview(transcript)}`,
`transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceLogPreview(transcript)}`,
);
if (params.transcripts) {
await params.transcripts.onUtterance({