import { z } from "zod";
export const DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL = "gpt-4o-transcribe";
export const DEFAULT_OPENAI_TTS_MODEL = "tts-1";
const OpenAiTtsVoiceSchema = z.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]);
const OpenAiTtsModelSchema = z.enum(["tts-1", "tts-1-hd"]);
const NumberLikeSchema = z.union([z.number(), z.string().trim().min(1)]);
const OptionalFiniteNumberSchema = NumberLikeSchema.pipe(z.coerce.number().finite()).optional();
const OptionalTrimmedStringSchema = z
    .string()
    .trim()
    .optional()
    .transform((value) => (value && value.length > 0 ? value : undefined));
const OpenAiSpeechResolutionSchema = z.object({
    apiKey: OptionalTrimmedStringSchema,
    dictationApiKey: OptionalTrimmedStringSchema,
    dictationBaseURL: OptionalTrimmedStringSchema,
    dictationModel: OptionalTrimmedStringSchema,
    sttConfidenceThreshold: OptionalFiniteNumberSchema,
    voiceSttModel: OptionalTrimmedStringSchema,
    ttsVoice: z.string().trim().toLowerCase().pipe(OpenAiTtsVoiceSchema).default("alloy"),
    ttsModel: z
        .string()
        .trim()
        .toLowerCase()
        .pipe(OpenAiTtsModelSchema)
        .default(DEFAULT_OPENAI_TTS_MODEL),
    realtimeTranscriptionModel: OptionalTrimmedStringSchema.default(DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL),
});
export function resolveOpenAiSpeechConfig(params) {
    const persistedOpenAi = params.persisted.providers?.openai;
    const persistedDictationOpenAi = params.persisted.features?.dictation?.openai;
    // The legacy providers.openai.{apiKey,baseURL} fields predate the
    // dictation override and were originally documented as voice
    // credentials. We preserve that meaning unless the user also set a
    // baseURL — in that case, the credentials clearly target an
    // OpenAI-compatible endpoint (e.g. Groq) that voice mode (TTS,
    // Realtime) does not support, so we treat the whole legacy block as
    // dictation-scoped to avoid 401-ing voice mode.
    const legacyOpenAiApiKey = persistedOpenAi?.apiKey;
    const legacyOpenAiBaseURL = persistedOpenAi?.baseURL;
    // The legacy block counts as a dictation override only when both
    // apiKey AND baseURL are present together — that's the only shape
    // that unambiguously targets a non-OpenAI endpoint. A standalone
    // legacy baseURL (no apiKey) is a corrupt half-config and must NOT
    // pair the custom URL with the env/top-level OpenAI key.
    const legacyIsDictationScoped = Boolean(legacyOpenAiApiKey && legacyOpenAiBaseURL);
    // When features.dictation.openai is present, treat it as the entire
    // dictation override block — do NOT mix-and-match with the legacy
    // providers.openai fields. Otherwise a half-migrated config could
    // produce nonsensical combinations like "OpenAI key + Groq baseURL".
    let dictationApiKey;
    let dictationBaseURL;
    if (persistedDictationOpenAi) {
        dictationApiKey = persistedDictationOpenAi.apiKey;
        dictationBaseURL = persistedDictationOpenAi.baseURL;
    }
    else if (legacyIsDictationScoped) {
        dictationApiKey = legacyOpenAiApiKey;
        dictationBaseURL = legacyOpenAiBaseURL;
    }
    const parsed = OpenAiSpeechResolutionSchema.parse({
        // Top-level apiKey is the credential for voiceStt / voiceTts / Realtime.
        // Env wins (desktop's gateway injection), with persisted
        // providers.openai.apiKey as a fallback for self-hosted users who
        // configured voice via config.json. The fallback is suppressed when a
        // legacy baseURL is also set, since that signals the credentials
        // belong to a non-OpenAI endpoint that the voice stack can't talk to.
        apiKey: params.env.OPENAI_API_KEY ?? (legacyIsDictationScoped ? undefined : legacyOpenAiApiKey),
        dictationApiKey,
        dictationBaseURL,
        // Dictation model has its own dedicated source — it must not borrow
        // from voiceMode.stt.model, otherwise dictation would silently inherit
        // a model intended for voice (e.g. gpt-4o-transcribe instead of
        // whisper-large-v3 on Groq).
        dictationModel: params.providers.dictationStt.enabled !== false &&
            params.providers.dictationStt.provider === "openai"
            ? params.persisted.features?.dictation?.stt?.model
            : undefined,
        sttConfidenceThreshold: params.env.STT_CONFIDENCE_THRESHOLD ??
            params.persisted.features?.dictation?.stt?.confidenceThreshold,
        voiceSttModel: params.env.STT_MODEL ??
            (params.providers.voiceStt.enabled !== false &&
                params.providers.voiceStt.provider === "openai"
                ? params.persisted.features?.voiceMode?.stt?.model
                : undefined),
        ttsVoice: params.env.TTS_VOICE ??
            (params.providers.voiceTts.enabled !== false &&
                params.providers.voiceTts.provider === "openai"
                ? params.persisted.features?.voiceMode?.tts?.voice
                : undefined) ??
            "alloy",
        ttsModel: params.env.TTS_MODEL ??
            (params.providers.voiceTts.enabled !== false &&
                params.providers.voiceTts.provider === "openai"
                ? params.persisted.features?.voiceMode?.tts?.model
                : undefined) ??
            DEFAULT_OPENAI_TTS_MODEL,
        realtimeTranscriptionModel: params.env.OPENAI_REALTIME_TRANSCRIPTION_MODEL ??
            (params.providers.dictationStt.enabled !== false &&
                params.providers.dictationStt.provider === "openai"
                ? params.persisted.features?.dictation?.stt?.model
                : undefined) ??
            DEFAULT_OPENAI_REALTIME_TRANSCRIPTION_MODEL,
    });
    // Dictation override is independent: it can be present without the
    // top-level apiKey (e.g. user only configured Groq for dictation, no
    // env OPENAI_API_KEY).
    const hasDictationOverride = Boolean(parsed.dictationApiKey || parsed.dictationBaseURL);
    if (!parsed.apiKey && !hasDictationOverride) {
        return undefined;
    }
    return {
        ...(parsed.apiKey ? { apiKey: parsed.apiKey } : {}),
        ...(parsed.apiKey
            ? {
                stt: {
                    apiKey: parsed.apiKey,
                    ...(parsed.sttConfidenceThreshold !== undefined
                        ? { confidenceThreshold: parsed.sttConfidenceThreshold }
                        : {}),
                    ...(parsed.voiceSttModel ? { model: parsed.voiceSttModel } : {}),
                },
                tts: {
                    apiKey: parsed.apiKey,
                    voice: parsed.ttsVoice,
                    model: parsed.ttsModel,
                    responseFormat: "pcm",
                },
            }
            : {}),
        realtimeTranscriptionModel: parsed.realtimeTranscriptionModel,
        ...(hasDictationOverride
            ? {
                dictation: {
                    ...(parsed.dictationApiKey ? { apiKey: parsed.dictationApiKey } : {}),
                    ...(parsed.dictationBaseURL ? { baseURL: parsed.dictationBaseURL } : {}),
                    ...(parsed.dictationModel ? { model: parsed.dictationModel } : {}),
                    // Confidence threshold is dictation-only by design (voice mode
                    // doesn't enforce it). Carry it on the dictation block so
                    // runtime can read it even when there is no top-level voice
                    // apiKey (i.e. dictation-only deployments).
                    ...(parsed.sttConfidenceThreshold !== undefined
                        ? { confidenceThreshold: parsed.sttConfidenceThreshold }
                        : {}),
                },
            }
            : {}),
    };
}
//# sourceMappingURL=config.js.map