diff --git a/services/asterisk_bridge_service/config.py b/services/asterisk_bridge_service/config.py index aa26fd0..6f44030 100644 --- a/services/asterisk_bridge_service/config.py +++ b/services/asterisk_bridge_service/config.py @@ -213,6 +213,16 @@ def ai_voice_queue_config() -> dict[str, dict[str, Any]]: return result +def _legacy_voice_start_profile(queue_code: str, *, stage: str | None, agent_profile: str) -> tuple[str | None, str]: + if stage or agent_profile != "voice_support": + return stage, agent_profile + # Backward compatibility for deployed IVR AI queues that predate the + # explicit voice_start stage/profile fields. + if queue_code.startswith("ivr_") and "_ai_" in queue_code: + return "voice_start", "voice_start" + return stage, agent_profile + + def ai_voice_config_for_queue(queue_code: str | None) -> dict[str, Any] | None: key = str(queue_code or "").strip() if not key or not ai_voice_enabled(): @@ -223,6 +233,12 @@ def ai_voice_config_for_queue(queue_code: str | None) -> dict[str, Any] | None: if str(item.get("mode") or "ai_first").strip().lower() != "ai_first": return None stage = str(item.get("stage") or "").strip() or None + agent_profile = str(item.get("agent_profile") or "voice_support").strip() or "voice_support" + stage, agent_profile = _legacy_voice_start_profile( + key, + stage=stage, + agent_profile=agent_profile, + ) next_queue_code = str(item.get("next_queue_code") or "").strip() or None next_queue_id = str(item.get("next_queue_id") or "").strip() or None handoff_queue_code = str(item.get("handoff_queue_code") or next_queue_code or key).strip() or key @@ -234,7 +250,7 @@ def ai_voice_config_for_queue(queue_code: str | None) -> dict[str, Any] | None: return { "queue_code": key, "mode": "ai_first", - "agent_profile": str(item.get("agent_profile") or "voice_support").strip() or "voice_support", + "agent_profile": agent_profile, "language": str(item.get("language") or "").strip() or None, "stage": stage, "next_queue_code": next_queue_code, diff --git a/tests/test_asterisk_bridge_service.py b/tests/test_asterisk_bridge_service.py index 24535b3..802fbd9 100644 --- a/tests/test_asterisk_bridge_service.py +++ b/tests/test_asterisk_bridge_service.py @@ -104,6 +104,56 @@ def test_browser_softphone_config_route_returns_403_when_mapping_missing(monkeyp assert response.json()["detail"] == "Browser softphone is not configured for this user" +def test_ai_voice_config_for_queue_promotes_legacy_ivr_ai_queues_to_voice_start(monkeypatch): + monkeypatch.setenv("AI_VOICE_ENABLED", "1") + monkeypatch.setenv( + "AI_VOICE_QUEUE_CONFIG_JSON", + json.dumps( + { + "ivr_support_ai_ru": { + "mode": "ai_first", + "agent_profile": "voice_support", + "handoff_queue_code": "ivr_support", + "language": "ru", + } + } + ), + ) + monkeypatch.setenv("ASTERISK_QUEUE_MAP_JSON", '{"ivr_support":"que_ivr_support"}') + + config = bridge_module._ai_voice_config_for_queue("ivr_support_ai_ru") + + assert config is not None + assert config["agent_profile"] == "voice_start" + assert config["stage"] == "voice_start" + assert config["handoff_queue_id"] == "que_ivr_support" + + +def test_ai_voice_config_for_queue_preserves_non_ivr_profiles(monkeypatch): + monkeypatch.setenv("AI_VOICE_ENABLED", "1") + monkeypatch.setenv( + "AI_VOICE_QUEUE_CONFIG_JSON", + json.dumps( + { + "voice_lab_ai": { + "mode": "ai_first", + "agent_profile": "voice_support", + "handoff_queue_code": "voice_lab", + "language": "ru", + } + } + ), + ) + monkeypatch.setenv("ASTERISK_QUEUE_MAP_JSON", '{"voice_lab":"que_voice_lab"}') + + config = bridge_module._ai_voice_config_for_queue("voice_lab_ai") + + assert config is not None + assert config["agent_profile"] == "voice_support" + assert config["stage"] is None + assert config["handoff_queue_id"] == "que_voice_lab" + + def test_post_json_bearer_first_falls_back_to_legacy(monkeypatch): class DummyResponse: def __init__(self, status_code: int, body: dict):