Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/docgen/ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,27 @@ def resolve_image_model(model: str, settings: AISettings | None = None, *, cfg:
return chosen


def _grok_voice_name(chosen: str) -> str:
"""Map a voice onto Grok. An unknown name must not silently become ``eve``."""
key = chosen.lower()
if not key:
return DEFAULT_GROK_TTS_VOICE
if key in _GROK_VOICES:
return key
mapped = _OPENAI_TO_GROK_VOICE.get(key)
if mapped is None:
raise AIError(
f"voice {chosen!r} is not a Grok voice and has no OpenAI alias"
)
return mapped


def resolve_tts_voice(voice: str, settings: AISettings | None = None, *, cfg: "Config | None" = None) -> str:
chosen = (voice or "").strip()
st = settings or resolve_ai_settings(cfg)
if not st.is_grok:
return chosen
if chosen.lower() in _GROK_VOICES:
return chosen.lower()
return _OPENAI_TO_GROK_VOICE.get(chosen.lower(), DEFAULT_GROK_TTS_VOICE)
return _grok_voice_name(chosen)


def chat_completion(
Expand Down
10 changes: 10 additions & 0 deletions tests/test_ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ def test_xai_alias_and_openai_model_remap(
assert resolve_tts_voice("eve", st) == "eve"


def test_unknown_grok_voice_is_refused(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("DOCGEN_AI_PROVIDER", "xai")
cfg = _cfg(tmp_path, {})
st = resolve_ai_settings(cfg)
with pytest.raises(AIError, match="not a Grok voice"):
resolve_tts_voice("banana", st)


def test_openai_client_grok_passes_base_url(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand Down
Loading