From 3d42df5749cb6f43a4a6801e756e1ea204db8abe Mon Sep 17 00:00:00 2001 From: Alvaro Parker Date: Tue, 6 Oct 2026 17:44:43 -0300 Subject: [PATCH] fix(proxy): pass the ApiPilot catalog with the correct argument --- s2s-client/server.py | 2 +- s2s-client/tests/test_server.py | 61 ++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/s2s-client/server.py b/s2s-client/server.py index 529abaf..d9c2a7e 100644 --- a/s2s-client/server.py +++ b/s2s-client/server.py @@ -4549,7 +4549,7 @@ async def realtime_proxy(ws: WebSocket): pass session_ctx_apipilot_defaults = dict(_apipilot_defaults_p1) injected_apipilot_pass1 = _resolve_apipilot_reasoning_context_block( - tools=apipilot_tools_pass1, context_defaults=_apipilot_defaults_p1, + tools_list=apipilot_tools_pass1, context_defaults=_apipilot_defaults_p1, ) or "" if injected_apipilot_pass1.strip(): logger.info( diff --git a/s2s-client/tests/test_server.py b/s2s-client/tests/test_server.py index 7e74c81..1e68832 100644 --- a/s2s-client/tests/test_server.py +++ b/s2s-client/tests/test_server.py @@ -9,7 +9,7 @@ from pathlib import Path import sys import threading import unittest -from unittest.mock import Mock, patch +from unittest.mock import ANY, AsyncMock, Mock, patch import httpx from fastapi.testclient import TestClient @@ -173,6 +173,65 @@ class BackendContractTests(unittest.TestCase): class VoiceProxyTests(unittest.TestCase): + def test_apipilot_catalog_and_caller_defaults_reach_the_voice_session(self): + closed = threading.Event() + catalog = AsyncMock(return_value=[{ + "name": "get_caller_briefing", + "description": "Retrieve the caller's local test briefing.", + "requires_auth": True, + "parameters": {"required": ["phone", "episode_id", "email"]}, + }]) + login = AsyncMock(return_value=("fixture-token", {})) + profile = AsyncMock(return_value={"user": {"email": "caller@example.test"}}) + + def voice(socket): + try: + socket.send(json.dumps({"type": "session.created"})) + for frame in socket: + socket.send(frame) + finally: + closed.set() + + with serve(voice, "127.0.0.1", 0) as upstream: + thread = threading.Thread(target=upstream.serve_forever, daemon=True) + thread.start() + self.addCleanup(thread.join, 5) + target = f"ws://127.0.0.1:{upstream.socket.getsockname()[1]}/v1/realtime" + with ( + patch.object(server, "S2S_PROXY_TARGET", target), + patch.object(server, "S2S_INJECT_PROFILE", False), + patch.object(server, "S2S_APIPILOT_TOOLS_URL", "http://catalog.test/tools"), + patch.object(server, "S2S_LOGIN_BY_PHONE", "http://profile.test/login"), + patch.object(server, "VOCERO_USER_PHONE_AUTH_URL", "http://profile.test/user"), + patch.object(server, "_apipilot_fetch_tools", catalog), + patch.object(server, "_login_by_phone", login), + patch.object(server, "_fetch_user_profile_for_phone", profile), + patch.object(server.httpx, "AsyncClient", side_effect=AssertionError("Unexpected HTTP request")), + TestClient(server.app) as client, + ): + with client.websocket_connect( + "/v1/realtime?phoneNumber=%2B56000000000&episode_id=fixture-episode", + ) as socket: + self.assertEqual(socket.receive_json(), {"type": "session.created"}) + socket.send_json({"type": "session.update", "session": {"instructions": "Local fixture"}}) + update = socket.receive_json() + self.assertEqual(update["type"], "session.update") + instructions = update["session"]["instructions"] + for expected in ( + "Local fixture", server.APIPILOT_PASS1_LIST_MARKER, "name: get_caller_briefing", + 'phone = "+56000000000"', 'episode_id = "fixture-episode"', + 'email = "caller@example.test"', + ): + self.assertIn(expected, instructions) + # Configuration injection must preserve ordinary audio forwarding. + audio = {"type": "input_audio_buffer.append", "audio": "AAA="} + socket.send_json(audio) + self.assertEqual(socket.receive_json(), audio) + self.assertTrue(closed.wait(3), "Client disconnect must close the upstream WebSocket") + login.assert_awaited_once_with("+56000000000") + profile.assert_awaited_once_with("+56000000000", auth_token="fixture-token") + catalog.assert_awaited_once_with(auth_header="Bearer fixture-token", demo_session=ANY, refresh=False) + def test_proxy_voice_frames_keepalive_and_client_disconnect(self): closed = threading.Event() received = []