// Force strict mode and setup for ESM
"use strict";
import {
  formatUnsupportedVoiceModelMessage,
  isTranscribableVoiceModel,
  resolveVoiceTransport
} from "./chunk-AGPXXQGK.js";
import {
  escapeAnsiCtrlCodes
} from "./chunk-OTMXHDIT.js";
import {
  wrapper_default
} from "./chunk-H6BD2ELD.js";
import {
  createDebugLogger
} from "./chunk-C4K3FEQ2.js";
import {
  init_esbuild_shims
} from "./chunk-A4BMJM77.js";
import {
  __name
} from "./chunk-J2S4EL5Y.js";

// packages/cli/src/ui/voice/voice-transcriber.ts
init_esbuild_shims();
import process from "node:process";
import { lookup as dnsLookup } from "node:dns/promises";
import { isIP } from "node:net";

// packages/cli/src/ui/voice/voice-keyterms.ts
init_esbuild_shims();
var GLOBAL_KEYTERMS = [
  "Qwen",
  "MCP",
  "grep",
  "regex",
  "localhost",
  "codebase",
  "TypeScript",
  "JavaScript",
  "JSON",
  "YAML",
  "OAuth",
  "webhook",
  "gRPC",
  "dotfiles",
  "subagent",
  "worktree",
  "stdout",
  "stderr",
  "async",
  "await",
  "API",
  "CLI",
  "npm",
  "pnpm",
  "commit",
  "rebase",
  "refactor",
  "endpoint",
  "middleware",
  "schema",
  "tokenizer"
];
function buildVoiceKeyterms() {
  return [...GLOBAL_KEYTERMS];
}
__name(buildVoiceKeyterms, "buildVoiceKeyterms");

// packages/cli/src/ui/voice/voice-transcriber.ts
var DEFAULT_OPENAI_API_KEY = "OPENAI_API_KEY";
var INFERENCE_TIMEOUT_MS = 6e4;
var MIN_KEYTERM_ECHO_TOKENS = 8;
var MIN_KEYTERM_SET_ECHO_RATIO = 0.3;
var debugLogger = createDebugLogger("VOICE_TRANSCRIBER");
function readVoiceLanguage(settings) {
  const language = settings.merged.general?.voice?.language;
  if (typeof language !== "string") {
    return void 0;
  }
  const trimmed = language.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
__name(readVoiceLanguage, "readVoiceLanguage");
function trimTrailingSlashes(value) {
  return value.replace(/\/+$/, "");
}
__name(trimTrailingSlashes, "trimTrailingSlashes");
function readSettingsEnv(settings, envKey) {
  const env = settings.merged.env;
  const value = env?.[envKey];
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
}
__name(readSettingsEnv, "readSettingsEnv");
function isQwenBaseUrl(baseUrl) {
  try {
    const hostname = new URL(baseUrl).hostname.toLowerCase();
    return hostname === "dashscope.aliyuncs.com" || hostname === "dashscope-intl.aliyuncs.com" || hostname === "dashscope-us.aliyuncs.com" || hostname.endsWith(".dashscope.aliyuncs.com") || hostname.endsWith(".dashscope-intl.aliyuncs.com") || hostname.endsWith(".dashscope-us.aliyuncs.com");
  } catch {
    return false;
  }
}
__name(isQwenBaseUrl, "isQwenBaseUrl");
function normalizeBaseUrl(baseUrl, modelName) {
  let url;
  try {
    url = new URL(baseUrl);
  } catch {
    throw new Error(`Voice model '${modelName}' has an invalid baseUrl.`);
  }
  url.username = "";
  url.password = "";
  return trimTrailingSlashes(url.toString());
}
__name(normalizeBaseUrl, "normalizeBaseUrl");
function normalizeHostname(hostname) {
  return hostname.toLowerCase().replace(/^\[|\]$/g, "");
}
__name(normalizeHostname, "normalizeHostname");
function isLoopbackHost(hostname) {
  const host = normalizeHostname(hostname);
  return host === "localhost" || host === "127.0.0.1" || host === "::1";
}
__name(isLoopbackHost, "isLoopbackHost");
function isPrivateNetworkIp(hostname) {
  const host = normalizeHostname(hostname);
  if (isLoopbackHost(host)) {
    return false;
  }
  const ipv4Mapped = host.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
  if (ipv4Mapped) {
    return isPrivateNetworkIp(ipv4Mapped[1]);
  }
  if (host.startsWith("::ffff:")) {
    return true;
  }
  if (isIP(host) === 4) {
    const [first = 0, second = 0] = host.split(".").map(Number);
    return first === 0 || first === 10 || first === 127 || first === 169 && second === 254 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 100 && second >= 64 && second <= 127;
  }
  if (isIP(host) === 6) {
    return host === "::" || host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd");
  }
  return false;
}
__name(isPrivateNetworkIp, "isPrivateNetworkIp");
async function defaultLookupHost(hostname) {
  return dnsLookup(hostname, { all: true });
}
__name(defaultLookupHost, "defaultLookupHost");
async function assertVoiceBaseUrlNetworkAllowed(voiceConfig, lookupHost) {
  const hostname = new URL(voiceConfig.baseUrl).hostname;
  if (isLoopbackHost(hostname) || isIP(normalizeHostname(hostname)) !== 0) {
    return;
  }
  let result;
  try {
    result = await (lookupHost ?? defaultLookupHost)(hostname);
  } catch {
    throw new Error(
      `Voice model '${voiceConfig.model}': DNS lookup failed for ${hostname}. Cannot verify network safety.`
    );
  }
  const records = Array.isArray(result) ? result : [result];
  if (records.some((record) => isPrivateNetworkIp(record.address))) {
    throw new Error(
      `Voice model '${voiceConfig.model}' resolved to a private-network address.`
    );
  }
}
__name(assertVoiceBaseUrlNetworkAllowed, "assertVoiceBaseUrlNetworkAllowed");
function readApiKey(settings, model, baseUrl) {
  if (!model.envKey && !isQwenBaseUrl(baseUrl)) {
    return void 0;
  }
  const envKey = model.envKey ?? DEFAULT_OPENAI_API_KEY;
  const envValue = process.env[envKey];
  if (envValue && envValue.trim().length > 0) {
    return envValue.trim();
  }
  const settingsEnvValue = readSettingsEnv(settings, envKey);
  if (settingsEnvValue) {
    return settingsEnvValue;
  }
  if (!model.envKey && isQwenBaseUrl(baseUrl)) {
    const authApiKey = settings.merged.security?.auth?.apiKey;
    return typeof authApiKey === "string" && authApiKey.trim().length > 0 ? authApiKey.trim() : void 0;
  }
  return void 0;
}
__name(readApiKey, "readApiKey");
function resolveVoiceTranscriptionConfig({
  config,
  settings,
  voiceModel
}) {
  const matches = config.getAllConfiguredModels().filter((model2) => model2.id === voiceModel);
  if (matches.length === 0) {
    throw new Error(
      `Voice model '${voiceModel}' is not configured. Run /model --voice to choose a configured model.`
    );
  }
  if (matches.length > 1) {
    throw new Error(`Voice model '${voiceModel}' is ambiguous.`);
  }
  const model = matches[0];
  if (!isTranscribableVoiceModel(model)) {
    throw new Error(formatUnsupportedVoiceModelMessage(voiceModel));
  }
  const baseUrl = model.baseUrl?.trim();
  if (!baseUrl) {
    throw new Error(`Voice model '${voiceModel}' does not define a baseUrl.`);
  }
  const normalizedBaseUrl = normalizeBaseUrl(baseUrl, voiceModel);
  const parsedBaseUrl = new URL(normalizedBaseUrl);
  const isLocalhost = isLoopbackHost(parsedBaseUrl.hostname);
  if (parsedBaseUrl.protocol !== "https:" && !isLocalhost) {
    throw new Error(
      `Voice model '${voiceModel}' must use an https baseUrl. Voice audio must not be transmitted in cleartext.`
    );
  }
  if (isPrivateNetworkIp(parsedBaseUrl.hostname)) {
    throw new Error(
      `Voice model '${voiceModel}' must not use a private-network baseUrl.`
    );
  }
  const apiKey = readApiKey(settings, model, normalizedBaseUrl);
  if (model.envKey && !apiKey) {
    throw new Error(`Voice model '${voiceModel}' requires ${model.envKey}.`);
  }
  return {
    model: voiceModel,
    baseUrl: normalizedBaseUrl,
    ...apiKey ? { apiKey } : {}
  };
}
__name(resolveVoiceTranscriptionConfig, "resolveVoiceTranscriptionConfig");
function isStreamingVoiceModel(model) {
  const transport = resolveVoiceTransport(model);
  return transport === "qwen-asr-realtime" || transport === "dashscope-task-realtime";
}
__name(isStreamingVoiceModel, "isStreamingVoiceModel");
function resolveVoiceStreamConfig(args) {
  const base = resolveVoiceTranscriptionConfig(args);
  const transport = resolveVoiceTransport(base.model);
  if (transport !== "qwen-asr-realtime" && transport !== "dashscope-task-realtime") {
    throw new Error(
      `Voice model '${base.model}' does not support streaming transcription.`
    );
  }
  const language = resolveLanguageCode(readVoiceLanguage(args.settings));
  const keytermsContext = transport === "qwen-asr-realtime" ? buildKeytermsContext() : void 0;
  return {
    transport,
    baseUrl: base.baseUrl,
    model: base.model,
    ...base.apiKey ? { apiKey: base.apiKey } : {},
    ...language ? { language } : {},
    ...keytermsContext ? { keytermsContext } : {}
  };
}
__name(resolveVoiceStreamConfig, "resolveVoiceStreamConfig");
var LANGUAGE_CODES = {
  english: "en",
  chinese: "zh",
  mandarin: "zh",
  cantonese: "yue",
  japanese: "ja",
  korean: "ko",
  french: "fr",
  german: "de",
  spanish: "es",
  italian: "it",
  portuguese: "pt",
  russian: "ru",
  arabic: "ar"
};
function resolveLanguageCode(language) {
  if (!language) {
    return void 0;
  }
  const lower = language.toLowerCase();
  if (LANGUAGE_CODES[lower]) {
    return LANGUAGE_CODES[lower];
  }
  return /^[a-z]{2,3}$/.test(lower) ? lower : void 0;
}
__name(resolveLanguageCode, "resolveLanguageCode");
function buildKeytermsContext() {
  try {
    const keyterms = buildVoiceKeyterms();
    return keyterms.length > 0 ? keyterms.join(" ") : void 0;
  } catch {
    return void 0;
  }
}
__name(buildKeytermsContext, "buildKeytermsContext");
function tokenize(value) {
  return value.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
}
__name(tokenize, "tokenize");
function isKeytermEcho(transcript, keytermsContext) {
  if (!keytermsContext) {
    return false;
  }
  const tokens = tokenize(transcript);
  if (tokens.length < 4) {
    return false;
  }
  const keyset = new Set(tokenize(keytermsContext));
  const overlap = tokens.filter((t) => keyset.has(t)).length;
  const transcriptRatio = overlap / tokens.length;
  const keytermRatio = overlap / keyset.size;
  const isEcho = overlap >= MIN_KEYTERM_ECHO_TOKENS && transcriptRatio >= 0.9 && keytermRatio >= MIN_KEYTERM_SET_ECHO_RATIO;
  if (isEcho) {
    debugLogger.debug(
      `[voice] dropped likely keyterm echo: transcriptRatio=${transcriptRatio.toFixed(2)} keytermRatio=${keytermRatio.toFixed(2)} text="${transcript}"`
    );
  }
  return isEcho;
}
__name(isKeytermEcho, "isKeytermEcho");
var MAX_AUDIO_BYTES = 10 * 1024 * 1024;
function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
__name(escapeRegExp, "escapeRegExp");
function sanitizeResponseDetails(raw, apiKey) {
  let redacted = raw.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]");
  if (apiKey) {
    redacted = redacted.replace(
      new RegExp(escapeRegExp(apiKey), "g"),
      "[REDACTED]"
    );
  }
  return redacted.length > 200 ? `${redacted.slice(0, 200)}...` : redacted;
}
__name(sanitizeResponseDetails, "sanitizeResponseDetails");
function inputAudioFormat(mimeType) {
  return mimeType.split(";", 1)[0]?.replace(/^audio\//, "") || "wav";
}
__name(inputAudioFormat, "inputAudioFormat");
async function transcribeViaQwenAsr(audio, voiceConfig, options, fetchFn) {
  if (audio.data.byteLength > MAX_AUDIO_BYTES) {
    throw new Error(
      "Recording is too long for transcription (max ~5 minutes / 10 MB). Try a shorter dictation."
    );
  }
  const dataUrl = `data:${audio.mimeType};base64,${Buffer.from(audio.data).toString("base64")}`;
  const messages = [];
  if (options.keytermsContext) {
    messages.push({
      role: "system",
      content: [{ type: "text", text: options.keytermsContext }]
    });
  }
  messages.push({
    role: "user",
    content: [
      {
        type: "input_audio",
        input_audio: {
          data: dataUrl,
          format: inputAudioFormat(audio.mimeType)
        }
      }
    ]
  });
  const asrOptions = { enable_itn: true };
  if (options.language) {
    asrOptions["language"] = options.language;
  }
  const headers = {
    "Content-Type": "application/json"
  };
  if (voiceConfig.apiKey) {
    headers["Authorization"] = `Bearer ${voiceConfig.apiKey}`;
  }
  let response;
  try {
    response = await fetchFn(
      `${trimTrailingSlashes(voiceConfig.baseUrl)}/chat/completions`,
      {
        method: "POST",
        headers,
        body: JSON.stringify({
          model: voiceConfig.model,
          messages,
          asr_options: asrOptions
        }),
        signal: AbortSignal.timeout(INFERENCE_TIMEOUT_MS)
      }
    );
  } catch (error) {
    if (error instanceof DOMException && error.name === "TimeoutError") {
      throw new Error(
        `Voice transcription timed out after ${INFERENCE_TIMEOUT_MS / 1e3}s. Check ASR service health and retry.`
      );
    }
    throw error;
  }
  if (!response.ok) {
    let details = "";
    try {
      details = sanitizeResponseDetails(
        await response.text(),
        voiceConfig.apiKey
      );
    } catch {
      details = "";
    }
    if (/model_not_supported|unsupported model/i.test(details)) {
      throw new Error(
        "This voice model cannot be used for batch transcription. Use qwen3-asr-flash for batch or choose a realtime voice model such as qwen3-asr-flash-realtime / fun-asr-realtime / paraformer-realtime-v2."
      );
    }
    const suffix = details ? `: ${details}` : "";
    throw new Error(
      `Voice transcription request failed (${response.status} ${response.statusText})${suffix}`
    );
  }
  const json = await response.json();
  const content = json.choices?.[0]?.message?.content;
  if (typeof content !== "string") {
    throw new Error("Voice transcription response did not include text.");
  }
  const text = content.trim();
  if (isKeytermEcho(text, options.keytermsContext)) {
    return "";
  }
  return text;
}
__name(transcribeViaQwenAsr, "transcribeViaQwenAsr");
async function transcribeVoiceAudio(audio, args) {
  const voiceConfig = resolveVoiceTranscriptionConfig(args);
  await assertVoiceBaseUrlNetworkAllowed(voiceConfig, args.lookupHost);
  const fetchFn = args.fetchFn ?? fetch;
  const language = resolveLanguageCode(readVoiceLanguage(args.settings));
  const keytermsContext = buildKeytermsContext();
  const transport = resolveVoiceTransport(voiceConfig.model);
  switch (transport) {
    case "qwen-asr-chat":
      return transcribeViaQwenAsr(
        audio,
        voiceConfig,
        { language, keytermsContext },
        fetchFn
      );
    case "qwen-asr-realtime":
    case "dashscope-task-realtime":
      throw new Error(
        `Voice model '${voiceConfig.model}' requires streaming transcription.`
      );
    case "unsupported":
    default:
      throw new Error(
        `Voice model '${voiceConfig.model}' is not a supported transcription model.`
      );
  }
}
__name(transcribeVoiceAudio, "transcribeVoiceAudio");

// packages/cli/src/ui/voice/voice-stream-session.ts
init_esbuild_shims();
import { randomUUID } from "node:crypto";
var CONNECT_TIMEOUT_MS = 8e3;
var FINISH_TIMEOUT_MS = 6e4;
var MAX_BUFFERED_AUDIO_BYTES = 1024 * 1024;
var MAX_SERVER_ERROR_MESSAGE_LENGTH = 200;
var debugLogger2 = createDebugLogger("VOICE_STREAM_SESSION");
function deriveWebSocketBase(baseUrl) {
  const url = new URL(baseUrl);
  const wsScheme = url.protocol === "https:" ? "wss:" : "ws:";
  let prefix = url.pathname.replace(/\/+$/, "");
  if (prefix.endsWith("/compatible-mode/v1")) {
    prefix = prefix.slice(0, -"/compatible-mode/v1".length);
  } else if (prefix.endsWith("/v1")) {
    prefix = prefix.slice(0, -"/v1".length);
  }
  return `${wsScheme}//${url.host}${prefix}`;
}
__name(deriveWebSocketBase, "deriveWebSocketBase");
function deriveStreamUrl(baseUrl) {
  return `${deriveWebSocketBase(baseUrl)}/api-ws/v1/inference`;
}
__name(deriveStreamUrl, "deriveStreamUrl");
function formatServerErrorMessage(raw) {
  const text = typeof raw === "string" ? raw : "unknown";
  return escapeAnsiCtrlCodes(text).slice(0, MAX_SERVER_ERROR_MESSAGE_LENGTH);
}
__name(formatServerErrorMessage, "formatServerErrorMessage");
function openVoiceStream(config, callbacks = {}, deps = {}) {
  const createWebSocket = deps.createWebSocket ?? ((url, options) => new wrapper_default(url, {
    headers: options.headers
  }));
  return new Promise((resolve, reject) => {
    const streamUrl = deriveStreamUrl(config.baseUrl);
    const ws = createWebSocket(streamUrl, {
      headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
    });
    const taskId = randomUUID();
    let started = false;
    let settled = false;
    let committed = "";
    let finishPromise = null;
    let finishResolve = null;
    let finishReject = null;
    let finishTimer = null;
    let connectTimer = null;
    let terminalError = null;
    let finishedTranscript = null;
    let backpressureWarned = false;
    const clearFinishTimer = /* @__PURE__ */ __name(() => {
      if (finishTimer) {
        clearTimeout(finishTimer);
        finishTimer = null;
      }
    }, "clearFinishTimer");
    const clearConnectTimer = /* @__PURE__ */ __name(() => {
      if (connectTimer) {
        clearTimeout(connectTimer);
        connectTimer = null;
      }
    }, "clearConnectTimer");
    const fail = /* @__PURE__ */ __name((error) => {
      if (settled) return;
      settled = true;
      const normalized = error instanceof Error ? error : new Error(String(error));
      clearConnectTimer();
      clearFinishTimer();
      try {
        ws.close();
      } catch {
      }
      if (finishReject) {
        finishReject(normalized);
        finishResolve = null;
        finishReject = null;
      } else {
        terminalError = normalized;
        if (!started) {
          reject(normalized);
        } else {
          callbacks.onError?.(normalized);
        }
      }
    }, "fail");
    connectTimer = setTimeout(() => {
      if (!started) fail(new Error("Voice stream connection timed out."));
    }, CONNECT_TIMEOUT_MS);
    ws.on("open", () => {
      ws.send(
        JSON.stringify({
          header: { action: "run-task", task_id: taskId, streaming: "duplex" },
          payload: {
            task_group: "audio",
            task: "asr",
            function: "recognition",
            model: config.model,
            parameters: {
              format: "pcm",
              sample_rate: 16e3,
              ...config.language ? { language_hints: [config.language] } : {}
            },
            input: {}
          }
        })
      );
    });
    ws.on("message", (...args) => {
      const data = args[0];
      const isBinary = args[1] === true;
      if (isBinary) return;
      let msg;
      try {
        msg = JSON.parse(String(data));
      } catch (error) {
        debugLogger2.warn("[voice] failed to parse stream message:", error);
        return;
      }
      const event = msg.header?.event;
      if (event === "task-started") {
        started = true;
        clearConnectTimer();
        resolve({
          pushAudio: /* @__PURE__ */ __name((pcm) => {
            if (ws.readyState === ws.OPEN && pcm.length > 0 && (ws.bufferedAmount ?? 0) <= MAX_BUFFERED_AUDIO_BYTES) {
              backpressureWarned = false;
              ws.send(pcm);
            } else if (pcm.length > 0 && !backpressureWarned) {
              backpressureWarned = true;
              debugLogger2.warn(
                "[voice] dropping DashScope audio due to socket backpressure"
              );
            }
          }, "pushAudio"),
          finish: /* @__PURE__ */ __name(() => {
            if (finishPromise) return finishPromise;
            finishPromise = new Promise((res, rej) => {
              if (finishedTranscript !== null) {
                res(finishedTranscript);
                return;
              }
              if (terminalError) {
                rej(terminalError);
                return;
              }
              finishResolve = res;
              finishReject = rej;
              finishTimer = setTimeout(() => {
                fail(new Error("Voice stream finish timed out."));
              }, FINISH_TIMEOUT_MS);
              try {
                ws.send(
                  JSON.stringify({
                    header: {
                      action: "finish-task",
                      task_id: taskId,
                      streaming: "duplex"
                    },
                    payload: { input: {} }
                  })
                );
              } catch (error) {
                fail(error);
              }
            });
            return finishPromise;
          }, "finish"),
          abort: /* @__PURE__ */ __name(() => {
            try {
              ws.close();
            } catch {
            }
          }, "abort")
        });
      } else if (event === "result-generated") {
        const sentence = msg.payload?.output?.sentence;
        if (sentence && !sentence.heartbeat && typeof sentence.text === "string") {
          if (sentence.sentence_end) {
            committed = committed ? `${committed} ${sentence.text}` : sentence.text;
            callbacks.onInterim?.(committed);
          } else {
            const running = committed ? `${committed} ${sentence.text}` : sentence.text;
            callbacks.onInterim?.(running);
          }
        }
      } else if (event === "task-finished") {
        if (!started) {
          fail(new Error("Voice stream finished before it started."));
          return;
        }
        finishedTranscript = committed.trim();
        settled = true;
        clearConnectTimer();
        clearFinishTimer();
        try {
          ws.close();
        } catch {
        }
        finishResolve?.(finishedTranscript);
        finishResolve = null;
        finishReject = null;
      } else if (event === "task-failed") {
        clearConnectTimer();
        fail(
          new Error(
            `Voice stream failed at ${streamUrl} task ${taskId} (${msg.header?.error_code ?? "error"}): ${formatServerErrorMessage(
              msg.header?.error_message
            )}`
          )
        );
      }
    });
    ws.on("error", (...args) => {
      clearConnectTimer();
      const error = args[0];
      fail(error instanceof Error ? error : new Error(String(error)));
    });
    ws.on("close", () => {
      clearConnectTimer();
      clearFinishTimer();
      if (settled) return;
      if (started && finishReject) {
        settled = true;
        finishReject(
          new Error(
            "Voice stream connection closed unexpectedly. Transcript may be incomplete."
          )
        );
        finishResolve = null;
        finishReject = null;
      } else if (!started) {
        fail(new Error("Voice stream closed before it started."));
      } else {
        const err = new Error(
          "Voice stream connection closed unexpectedly. Transcript may be incomplete."
        );
        terminalError ??= err;
        callbacks.onError?.(err);
      }
    });
  });
}
__name(openVoiceStream, "openVoiceStream");

// packages/cli/src/ui/voice/qwen-asr-realtime-session.ts
init_esbuild_shims();
import { randomUUID as randomUUID2 } from "node:crypto";
var CONNECT_TIMEOUT_MS2 = 8e3;
var FINISH_TIMEOUT_MS2 = 6e4;
var MAX_BUFFERED_AUDIO_BYTES2 = 1024 * 1024;
var MAX_SERVER_ERROR_MESSAGE_LENGTH2 = 200;
var debugLogger3 = createDebugLogger("VOICE_QWEN_REALTIME");
function deriveQwenRealtimeUrl(baseUrl, model) {
  return `${deriveWebSocketBase(baseUrl)}/api-ws/v1/realtime?model=${encodeURIComponent(model)}`;
}
__name(deriveQwenRealtimeUrl, "deriveQwenRealtimeUrl");
function appendTranscript(existing, next) {
  const text = next.trim();
  if (!text) return existing;
  return existing ? `${existing} ${text}` : text;
}
__name(appendTranscript, "appendTranscript");
function toError(error) {
  return error instanceof Error ? error : new Error(String(error));
}
__name(toError, "toError");
function formatServerErrorMessage2(raw) {
  const text = typeof raw === "string" ? raw : "Qwen ASR realtime failed.";
  return escapeAnsiCtrlCodes(text).slice(0, MAX_SERVER_ERROR_MESSAGE_LENGTH2);
}
__name(formatServerErrorMessage2, "formatServerErrorMessage");
function openQwenAsrRealtimeStream(config, callbacks = {}, deps = {}) {
  const createWebSocket = deps.createWebSocket ?? ((url, options) => new wrapper_default(url, {
    headers: options.headers
  }));
  return new Promise((resolve, reject) => {
    const ws = createWebSocket(
      deriveQwenRealtimeUrl(config.baseUrl, config.model),
      {
        headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
      }
    );
    let opened = false;
    let openSettled = false;
    let committed = "";
    let finishPromise = null;
    let finishResolve = null;
    let finishReject = null;
    let finishTimer = null;
    let connectTimer = null;
    let finishedTranscript = null;
    let terminalError = null;
    let failed = false;
    let backpressureWarned = false;
    const sendJson = /* @__PURE__ */ __name((body) => {
      ws.send(JSON.stringify({ event_id: randomUUID2(), ...body }));
    }, "sendJson");
    const close = /* @__PURE__ */ __name(() => {
      try {
        ws.close();
      } catch {
      }
    }, "close");
    const clearFinishTimer = /* @__PURE__ */ __name(() => {
      if (finishTimer) {
        clearTimeout(finishTimer);
        finishTimer = null;
      }
    }, "clearFinishTimer");
    const clearConnectTimer = /* @__PURE__ */ __name(() => {
      if (connectTimer) {
        clearTimeout(connectTimer);
        connectTimer = null;
      }
    }, "clearConnectTimer");
    const fail = /* @__PURE__ */ __name((error) => {
      if (failed) return;
      failed = true;
      const normalized = toError(error);
      clearConnectTimer();
      clearFinishTimer();
      close();
      if (finishReject) {
        finishReject(normalized);
        finishResolve = null;
        finishReject = null;
        return;
      }
      if (!openSettled) {
        openSettled = true;
        reject(normalized);
        return;
      }
      terminalError = normalized;
      callbacks.onError?.(normalized);
    }, "fail");
    connectTimer = setTimeout(() => {
      if (!opened) fail(new Error("Qwen ASR realtime connection timed out."));
    }, CONNECT_TIMEOUT_MS2);
    const sendSessionUpdate = /* @__PURE__ */ __name(() => {
      sendJson({
        type: "session.update",
        session: {
          input_audio_format: "pcm",
          sample_rate: 16e3,
          input_audio_transcription: {
            ...config.language ? { language: config.language } : {},
            ...config.keytermsContext ? { corpus_text: config.keytermsContext } : {}
          },
          turn_detection: null
        }
      });
    }, "sendSessionUpdate");
    ws.on("message", (...args) => {
      const data = args[0];
      const isBinary = args[1] === true;
      if (isBinary) return;
      let msg;
      try {
        msg = JSON.parse(String(data));
      } catch (error) {
        debugLogger3.warn(
          "[voice] failed to parse Qwen ASR realtime message:",
          error
        );
        return;
      }
      switch (msg.type) {
        case "session.created":
          sendSessionUpdate();
          break;
        case "session.updated":
          opened = true;
          openSettled = true;
          clearConnectTimer();
          resolve({
            pushAudio: /* @__PURE__ */ __name((pcm) => {
              if (ws.readyState !== ws.OPEN || pcm.length === 0) return;
              if ((ws.bufferedAmount ?? 0) > MAX_BUFFERED_AUDIO_BYTES2) {
                if (!backpressureWarned) {
                  backpressureWarned = true;
                  debugLogger3.warn(
                    "[voice] dropping Qwen ASR realtime audio due to socket backpressure."
                  );
                }
                return;
              }
              backpressureWarned = false;
              sendJson({
                type: "input_audio_buffer.append",
                audio: Buffer.from(pcm).toString("base64")
              });
            }, "pushAudio"),
            finish: /* @__PURE__ */ __name(() => {
              if (finishPromise) return finishPromise;
              finishPromise = new Promise((res, rej) => {
                if (finishedTranscript !== null) {
                  res(finishedTranscript);
                  return;
                }
                if (terminalError) {
                  rej(terminalError);
                  return;
                }
                finishResolve = res;
                finishReject = rej;
                finishTimer = setTimeout(() => {
                  fail(new Error("Qwen ASR realtime finish timed out."));
                }, FINISH_TIMEOUT_MS2);
                try {
                  sendJson({ type: "input_audio_buffer.commit" });
                  sendJson({ type: "session.finish" });
                } catch (error) {
                  fail(error);
                }
              });
              return finishPromise;
            }, "finish"),
            abort: close
          });
          break;
        case "conversation.item.input_audio_transcription.text": {
          const text = typeof msg.text === "string" ? msg.text : "";
          const stash = typeof msg.stash === "string" ? msg.stash : "";
          const preview = `${text}${stash}`.trim();
          callbacks.onInterim?.([committed, preview].filter(Boolean).join(" "));
          break;
        }
        case "conversation.item.input_audio_transcription.completed":
          if (typeof msg.transcript === "string") {
            committed = appendTranscript(committed, msg.transcript);
            callbacks.onInterim?.(committed);
          }
          break;
        case "conversation.item.input_audio_transcription.failed":
          fail(
            new Error(
              formatServerErrorMessage2(
                msg.error?.message ?? msg.error?.code ?? "Qwen ASR realtime transcription failed."
              )
            )
          );
          break;
        case "session.finished":
          if (!openSettled) {
            fail(
              new Error(
                "Qwen ASR realtime session finished before it was ready."
              )
            );
            break;
          }
          failed = true;
          clearFinishTimer();
          finishedTranscript = committed.trim();
          finishResolve?.(finishedTranscript);
          finishResolve = null;
          finishReject = null;
          close();
          break;
        case "error":
          fail(
            new Error(
              formatServerErrorMessage2(
                msg.error?.message ?? msg.error?.code ?? "Qwen ASR realtime request failed."
              )
            )
          );
          break;
        default:
          break;
      }
    });
    ws.on("error", fail);
    ws.on("close", () => {
      clearConnectTimer();
      clearFinishTimer();
      if (failed) return;
      if (!openSettled) {
        openSettled = true;
        reject(new Error("Qwen ASR realtime connection closed."));
        return;
      }
      if (finishReject) {
        finishReject(
          new Error(
            "Qwen ASR realtime connection closed unexpectedly. Transcript may be incomplete."
          )
        );
        finishResolve = null;
        finishReject = null;
      } else {
        const error = new Error(
          "Qwen ASR realtime connection closed unexpectedly. Transcript may be incomplete."
        );
        terminalError ??= error;
        callbacks.onError?.(terminalError);
      }
    });
  });
}
__name(openQwenAsrRealtimeStream, "openQwenAsrRealtimeStream");

// packages/cli/src/ui/voice/voice-stream-retry.ts
init_esbuild_shims();
var RETRY_DELAY_MS = 200;
var debugLogger4 = createDebugLogger("VOICE_STREAM");
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
__name(delay, "delay");
function isRetryable(error) {
  const message = error instanceof Error ? error.message : String(error);
  if (/\b(400|401|403|404|410|422|429)\b|unauthori[sz]ed|forbidden|model_not_supported|rate.?limit/i.test(
    message
  )) {
    return false;
  }
  return true;
}
__name(isRetryable, "isRetryable");
async function openVoiceStreamWithRetry(open) {
  try {
    return await open();
  } catch (error) {
    if (!isRetryable(error)) {
      throw error;
    }
    debugLogger4.debug("[voice] stream open failed, retrying:", error);
    await delay(RETRY_DELAY_MS);
    return open();
  }
}
__name(openVoiceStreamWithRetry, "openVoiceStreamWithRetry");

export {
  assertVoiceBaseUrlNetworkAllowed,
  resolveVoiceTranscriptionConfig,
  isStreamingVoiceModel,
  resolveVoiceStreamConfig,
  isKeytermEcho,
  transcribeVoiceAudio,
  openVoiceStream,
  openQwenAsrRealtimeStream,
  openVoiceStreamWithRetry
};
/**
 * @license
 * Copyright 2025 Qwen
 * SPDX-License-Identifier: Apache-2.0
 */
