From 69b9c9b59aca9a77762e1d72ac38202ce929ee8c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 19:47:34 +0000 Subject: [PATCH 1/7] Optimize blocking Gemini OAuth file reads using asyncio.to_thread Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- router/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/router/main.py b/router/main.py index e6b492f8..7c6bce7d 100644 --- a/router/main.py +++ b/router/main.py @@ -1027,8 +1027,8 @@ async def classify_request( return "agent-advanced-core", latency, False, "advanced (exception)" -def get_live_gemini_oauth_token() -> str | None: - """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" +def _get_live_gemini_oauth_token_sync() -> str | None: + """Synchronous internal helper to retrieve the current valid Gemini OAuth access token.""" try: creds_path = "/config/gemini_auth/oauth_creds.json" if os.path.exists(creds_path): @@ -1054,6 +1054,11 @@ def get_live_gemini_oauth_token() -> str | None: return None +async def get_live_gemini_oauth_token() -> str | None: + """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" + return await asyncio.to_thread(_get_live_gemini_oauth_token_sync) + + def get_gemini_oauth_status() -> dict: """Returns structured OAuth status for the dashboard banner.""" creds_path = "/config/gemini_auth/oauth_creds.json" From 2ea2348e4268f64d724a5afecc6daa6b5ac6e08e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:47:01 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9A=A1=20Optimize=20blocking=20Gemini=20?= =?UTF-8?q?OAuth=20file=20reads=20using=20asyncio.to=5Fthread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- router/main.py | 21 ++++++++++++++++----- router/tests/test_dashboard_data.py | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/router/main.py b/router/main.py index 7c6bce7d..02cda1ae 100644 --- a/router/main.py +++ b/router/main.py @@ -1053,14 +1053,17 @@ def _get_live_gemini_oauth_token_sync() -> str | None: logger.error(f"Failed to read live OAuth token: {e}") return None - -async def get_live_gemini_oauth_token() -> str | None: +def get_live_gemini_oauth_token() -> str | None: """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" + return _get_live_gemini_oauth_token_sync() + +async def get_live_gemini_oauth_token_async() -> str | None: + """Async wrapper to retrieve the current valid Gemini OAuth access token from local storage if not expired.""" return await asyncio.to_thread(_get_live_gemini_oauth_token_sync) -def get_gemini_oauth_status() -> dict: - """Returns structured OAuth status for the dashboard banner.""" +def _get_gemini_oauth_status_sync() -> dict: + """Synchronous helper to retrieve structured OAuth status.""" creds_path = "/config/gemini_auth/oauth_creds.json" try: if not os.path.exists(creds_path): @@ -1111,6 +1114,14 @@ def get_gemini_oauth_status() -> dict: except Exception as e: return {"status": "error", "detail": str(e), "expiry_ms": 0} +def get_gemini_oauth_status() -> dict: + """Returns structured OAuth status for the dashboard banner.""" + return _get_gemini_oauth_status_sync() + +async def get_gemini_oauth_status_async() -> dict: + """Async wrapper to retrieve structured OAuth status for the dashboard banner.""" + return await asyncio.to_thread(_get_gemini_oauth_status_sync) + def map_tool_to_category(tool_name: str) -> str: """Groups low-level developer tool names into the five high-level dashboard metrics.""" @@ -2631,7 +2642,7 @@ async def get_dashboard_data(): check_http_endpoint("http://127.0.0.1:4000/"), check_http_endpoint("http://127.0.0.1:8080/health"), check_http_endpoint("http://127.0.0.1:3001"), - asyncio.to_thread(get_gemini_oauth_status), + get_gemini_oauth_status_async(), asyncio.wait_for(get_best_free_model(), timeout=5.0), asyncio.to_thread(get_goose_sessions), asyncio.wait_for(get_llamacpp_metrics(), timeout=5.0), diff --git a/router/tests/test_dashboard_data.py b/router/tests/test_dashboard_data.py index 643425f8..ee866921 100644 --- a/router/tests/test_dashboard_data.py +++ b/router/tests/test_dashboard_data.py @@ -17,7 +17,7 @@ async def test_get_dashboard_data_structure(): with patch("main.sync_cooldowns_from_valkey", new_callable=AsyncMock) as mock_sync, \ patch("main.check_tcp_port", new_callable=AsyncMock) as mock_tcp, \ patch("main.check_http_endpoint", new_callable=AsyncMock) as mock_http, \ - patch("main.get_gemini_oauth_status") as mock_oauth, \ + patch("main.get_gemini_oauth_status_async", new_callable=AsyncMock) as mock_oauth, \ patch("main.get_best_free_model", new_callable=AsyncMock) as mock_best_model, \ patch("main.get_goose_sessions") as mock_goose, \ patch("main.get_llamacpp_metrics", new_callable=AsyncMock) as mock_llamacpp, \ From cc73b9c057518afc6d6be901d427ba0bba78daa1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:14:07 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9A=A1=20Optimize=20blocking=20Gemini=20?= =?UTF-8?q?OAuth=20file=20reads=20using=20asyncio.to=5Fthread=20directly?= =?UTF-8?q?=20inside=20the=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- router/main.py | 68 ++++++++++++----------------- router/tests/test_dashboard_data.py | 2 +- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/router/main.py b/router/main.py index 02cda1ae..47d08f38 100644 --- a/router/main.py +++ b/router/main.py @@ -1027,43 +1027,42 @@ async def classify_request( return "agent-advanced-core", latency, False, "advanced (exception)" -def _get_live_gemini_oauth_token_sync() -> str | None: - """Synchronous internal helper to retrieve the current valid Gemini OAuth access token.""" +def _get_live_gemini_oauth_token_sync_read(creds_path: str) -> dict: + with open(creds_path, "r") as f: + return json.load(f) + +async def get_live_gemini_oauth_token() -> str | None: + """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" try: creds_path = "/config/gemini_auth/oauth_creds.json" if os.path.exists(creds_path): - with open(creds_path, "r") as f: - data = json.load(f) - access_token = data.get("access_token") - expiry_ms = data.get("expiry_date", 0) - # Convert current time to milliseconds - current_ms = int(time.time() * 1000) - if access_token and current_ms < expiry_ms: - logger.info( - "🔑 Found valid, unexpired Gemini OAuth token from host!" - ) - return access_token - else: - # agy CLI uses the OS system keyring (GNOME Keyring), not this - # stale disk file. The file being expired is expected — don't warn. - logger.debug( - "Gemini OAuth token on disk is expired — agy uses system keyring instead." - ) + data = await asyncio.to_thread(_get_live_gemini_oauth_token_sync_read, creds_path) + access_token = data.get("access_token") + expiry_ms = data.get("expiry_date", 0) + # Convert current time to milliseconds + current_ms = int(time.time() * 1000) + if access_token and current_ms < expiry_ms: + logger.info( + "🔑 Found valid, unexpired Gemini OAuth token from host!" + ) + return access_token + else: + # agy CLI uses the OS system keyring (GNOME Keyring), not this + # stale disk file. The file being expired is expected — don't warn. + logger.debug( + "Gemini OAuth token on disk is expired — agy uses system keyring instead." + ) except Exception as e: logger.error(f"Failed to read live OAuth token: {e}") return None -def get_live_gemini_oauth_token() -> str | None: - """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" - return _get_live_gemini_oauth_token_sync() - -async def get_live_gemini_oauth_token_async() -> str | None: - """Async wrapper to retrieve the current valid Gemini OAuth access token from local storage if not expired.""" - return await asyncio.to_thread(_get_live_gemini_oauth_token_sync) +def _get_gemini_oauth_status_sync_read(creds_path: str) -> dict: + with open(creds_path, "r") as f: + return json.load(f) -def _get_gemini_oauth_status_sync() -> dict: - """Synchronous helper to retrieve structured OAuth status.""" +async def get_gemini_oauth_status() -> dict: + """Returns structured OAuth status for the dashboard banner.""" creds_path = "/config/gemini_auth/oauth_creds.json" try: if not os.path.exists(creds_path): @@ -1072,8 +1071,7 @@ def _get_gemini_oauth_status_sync() -> dict: "detail": "No oauth_creds.json found", "expiry_ms": 0, } - with open(creds_path, "r") as f: - data = json.load(f) + data = await asyncio.to_thread(_get_gemini_oauth_status_sync_read, creds_path) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) current_ms = int(time.time() * 1000) @@ -1114,14 +1112,6 @@ def _get_gemini_oauth_status_sync() -> dict: except Exception as e: return {"status": "error", "detail": str(e), "expiry_ms": 0} -def get_gemini_oauth_status() -> dict: - """Returns structured OAuth status for the dashboard banner.""" - return _get_gemini_oauth_status_sync() - -async def get_gemini_oauth_status_async() -> dict: - """Async wrapper to retrieve structured OAuth status for the dashboard banner.""" - return await asyncio.to_thread(_get_gemini_oauth_status_sync) - def map_tool_to_category(tool_name: str) -> str: """Groups low-level developer tool names into the five high-level dashboard metrics.""" @@ -2642,7 +2632,7 @@ async def get_dashboard_data(): check_http_endpoint("http://127.0.0.1:4000/"), check_http_endpoint("http://127.0.0.1:8080/health"), check_http_endpoint("http://127.0.0.1:3001"), - get_gemini_oauth_status_async(), + get_gemini_oauth_status(), asyncio.wait_for(get_best_free_model(), timeout=5.0), asyncio.to_thread(get_goose_sessions), asyncio.wait_for(get_llamacpp_metrics(), timeout=5.0), diff --git a/router/tests/test_dashboard_data.py b/router/tests/test_dashboard_data.py index ee866921..695bbabc 100644 --- a/router/tests/test_dashboard_data.py +++ b/router/tests/test_dashboard_data.py @@ -17,7 +17,7 @@ async def test_get_dashboard_data_structure(): with patch("main.sync_cooldowns_from_valkey", new_callable=AsyncMock) as mock_sync, \ patch("main.check_tcp_port", new_callable=AsyncMock) as mock_tcp, \ patch("main.check_http_endpoint", new_callable=AsyncMock) as mock_http, \ - patch("main.get_gemini_oauth_status_async", new_callable=AsyncMock) as mock_oauth, \ + patch("main.get_gemini_oauth_status", new_callable=AsyncMock) as mock_oauth, \ patch("main.get_best_free_model", new_callable=AsyncMock) as mock_best_model, \ patch("main.get_goose_sessions") as mock_goose, \ patch("main.get_llamacpp_metrics", new_callable=AsyncMock) as mock_llamacpp, \ From 9a51defd9a30badbffbb42d03edbc94ea78814e1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:21:32 +0000 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9A=A1=20Optimize=20blocking=20Gemini=20?= =?UTF-8?q?OAuth=20file=20reads=20using=20asyncio.to=5Fthread=20directly?= =?UTF-8?q?=20inside=20the=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- router/main.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/router/main.py b/router/main.py index 47d08f38..ab39e64d 100644 --- a/router/main.py +++ b/router/main.py @@ -1027,8 +1027,9 @@ async def classify_request( return "agent-advanced-core", latency, False, "advanced (exception)" -def _get_live_gemini_oauth_token_sync_read(creds_path: str) -> dict: - with open(creds_path, "r") as f: +def _read_json_file_sync(file_path: str) -> dict: + """Helper to read JSON files synchronously.""" + with open(file_path, "r") as f: return json.load(f) async def get_live_gemini_oauth_token() -> str | None: @@ -1036,7 +1037,7 @@ async def get_live_gemini_oauth_token() -> str | None: try: creds_path = "/config/gemini_auth/oauth_creds.json" if os.path.exists(creds_path): - data = await asyncio.to_thread(_get_live_gemini_oauth_token_sync_read, creds_path) + data = await asyncio.to_thread(_read_json_file_sync, creds_path) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) # Convert current time to milliseconds @@ -1057,9 +1058,7 @@ async def get_live_gemini_oauth_token() -> str | None: return None -def _get_gemini_oauth_status_sync_read(creds_path: str) -> dict: - with open(creds_path, "r") as f: - return json.load(f) + async def get_gemini_oauth_status() -> dict: """Returns structured OAuth status for the dashboard banner.""" @@ -1071,7 +1070,7 @@ async def get_gemini_oauth_status() -> dict: "detail": "No oauth_creds.json found", "expiry_ms": 0, } - data = await asyncio.to_thread(_get_gemini_oauth_status_sync_read, creds_path) + data = await asyncio.to_thread(_read_json_file_sync, creds_path) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) current_ms = int(time.time() * 1000) From afccf9e643a7ccc01aa6756899b15101ca074902 Mon Sep 17 00:00:00 2001 From: sheepdestroyer Date: Wed, 1 Jul 2026 14:15:45 +0200 Subject: [PATCH 5/7] docs: document async gemini oauth checks in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a162e8c..4be7b49c 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ Navigate your web browser to: 👉 **`http://localhost:5000/dashboard`** The triage router hosts a beautiful, single-pane-of-glass **Glassmorphic Status Control Panel** styled with modern vanilla CSS featuring: -* **System Status Healthchecks**: Live connection status checks via TCP sockets (Valkey, Postgres) and HTTP pings (LiteLLM, Llama-server). +* **System Status Healthchecks**: Live connection status checks via TCP sockets (Valkey, Postgres), HTTP pings (LiteLLM, Llama-server), and non-blocking asynchronous checks for Gemini OAuth token validation status. * **Real-time Routing Metrics**: Active classification splits (simple vs complex), request logs, and processing latencies. * **Direct Application Portals**: One-click navigation links to target web utilities (LiteLLM administration console, Langfuse telemetry console, Llama-Server playground). From 2c84fc539506b70d675df542f8062c2ca7ca10a4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:06:37 +0000 Subject: [PATCH 6/7] Fix test failures for get_live_gemini_oauth_token Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- fix_import_aiofiles.py | 6 ++ fix_test.py | 19 +++++ .../tests/test_get_live_gemini_oauth_token.py | 80 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 fix_import_aiofiles.py create mode 100644 fix_test.py create mode 100644 router/tests/test_get_live_gemini_oauth_token.py diff --git a/fix_import_aiofiles.py b/fix_import_aiofiles.py new file mode 100644 index 00000000..70a50436 --- /dev/null +++ b/fix_import_aiofiles.py @@ -0,0 +1,6 @@ +with open("router/tests/test_get_live_gemini_oauth_token.py", "r") as f: + content = f.read() + +# I see the error is that `import aiofiles` fails in `router/main.py`. +# wait! Did I add `import aiofiles` to `router/main.py` when I tested earlier? +# NO I didn't add it in the final patch. Let me check `router/main.py` diff --git a/fix_test.py b/fix_test.py new file mode 100644 index 00000000..3b10d4f6 --- /dev/null +++ b/fix_test.py @@ -0,0 +1,19 @@ +import re + +with open("router/tests/test_get_live_gemini_oauth_token.py", "r") as f: + content = f.read() + +target = "token = main.get_live_gemini_oauth_token()" +new_content = "token = await main.get_live_gemini_oauth_token()" + +if target in content: + content = content.replace(target, new_content) + + # We also need to add @pytest.mark.asyncio and make the test functions async. + content = re.sub(r'def test_get_live_gemini', '@pytest.mark.asyncio\nasync def test_get_live_gemini', content) + + with open("router/tests/test_get_live_gemini_oauth_token.py", "w") as f: + f.write(content) + print("Patched test successfully") +else: + print("Target not found") diff --git a/router/tests/test_get_live_gemini_oauth_token.py b/router/tests/test_get_live_gemini_oauth_token.py new file mode 100644 index 00000000..42a1ee1f --- /dev/null +++ b/router/tests/test_get_live_gemini_oauth_token.py @@ -0,0 +1,80 @@ +import os +import sys +import json +import time +import pytest +from unittest.mock import patch, mock_open + +# Add router to sys.path properly +router_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +if router_path not in sys.path: + sys.path.insert(0, router_path) + +with patch('builtins.open', mock_open(read_data=''' +server: + host: 0.0.0.0 + port: 5000 +router: + router_model: + api_key: "dummy_key" + model: "dummy_model" +''')): + import main + +@pytest.mark.asyncio +async def test_get_live_gemini_oauth_token_valid(): + """Test retrieving a valid, unexpired token.""" + mock_data = { + "access_token": "valid_token_123", + "expiry_date": int(time.time() * 1000) + 3600000 # 1 hour in the future + } + with patch("os.path.exists", return_value=True), \ + patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \ + patch("main.logger.info") as mock_logger: + token = await main.get_live_gemini_oauth_token() + assert token == "valid_token_123" + mock_logger.assert_called_with("🔑 Found valid, unexpired Gemini OAuth token from host!") + +@pytest.mark.asyncio +async def test_get_live_gemini_oauth_token_expired(): + """Test that an expired token returns None.""" + mock_data = { + "access_token": "expired_token_456", + "expiry_date": int(time.time() * 1000) - 3600000 # 1 hour in the past + } + with patch("os.path.exists", return_value=True), \ + patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \ + patch("main.logger.debug") as mock_logger: + token = await main.get_live_gemini_oauth_token() + assert token is None + mock_logger.assert_called_with("Gemini OAuth token on disk is expired — agy uses system keyring instead.") + +@pytest.mark.asyncio +async def test_get_live_gemini_oauth_token_missing_file(): + """Test behavior when the credentials file does not exist.""" + with patch("os.path.exists", return_value=False): + token = await main.get_live_gemini_oauth_token() + assert token is None + +@pytest.mark.asyncio +async def test_get_live_gemini_oauth_token_invalid_json(): + """Test behavior when the credentials file contains invalid JSON.""" + with patch("os.path.exists", return_value=True), \ + patch("builtins.open", mock_open(read_data="invalid json")), \ + patch("main.logger.error") as mock_logger: + token = await main.get_live_gemini_oauth_token() + assert token is None + mock_logger.assert_called() + +@pytest.mark.asyncio +async def test_get_live_gemini_oauth_token_missing_access_token(): + """Test behavior when the JSON does not contain an access_token.""" + mock_data = { + "expiry_date": int(time.time() * 1000) + 3600000 + } + with patch("os.path.exists", return_value=True), \ + patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \ + patch("main.logger.debug") as mock_logger: + token = await main.get_live_gemini_oauth_token() + assert token is None + mock_logger.assert_called_with("Gemini OAuth token on disk is expired — agy uses system keyring instead.") From e8b048322541a17fc205b45067a1896823108a88 Mon Sep 17 00:00:00 2001 From: sheepdestroyer Date: Wed, 1 Jul 2026 22:55:49 +0200 Subject: [PATCH 7/7] chore: address reviews and comments on PR 158 --- README.md | 8 +++++++- fix_import_aiofiles.py | 6 ------ fix_test.py | 19 ------------------- router/main.py | 12 ++++++------ test_record_tool_usage.py | 24 +++++++++++++++++++++--- 5 files changed, 34 insertions(+), 35 deletions(-) delete mode 100644 fix_import_aiofiles.py delete mode 100644 fix_test.py diff --git a/README.md b/README.md index 4be7b49c..c093e9fe 100644 --- a/README.md +++ b/README.md @@ -571,6 +571,9 @@ Without Minio, Langfuse v3 **will not start** — it validates S3 connectivity a ### Configuration +> [!WARNING] +> The credentials `minioadmin` / `minioadmin` and settings specified below are strictly for **local development and testing**. You **must** override these default credentials before any shared, staging, or production use. + | Env Var | Value | |----------|-------| | `LANGFUSE_S3_EVENT_UPLOAD_BUCKET` | `langfuse-events` | @@ -579,12 +582,15 @@ Without Minio, Langfuse v3 **will not start** — it validates S3 connectivity a | `LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY` | `minioadmin` | | `S3_FORCE_PATH_STYLE` | `true` | -Minio runs on ports **9001** (web console) and **9002** (S3 API). Credentials: `minioadmin` / `minioadmin`. Image pinned to `docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z`. +Minio runs on ports **9001** (web console) and **9002** (S3 API). Default credentials (`minioadmin` / `minioadmin`) are only meant for local/dev setups. Image pinned to `docker.io/minio/minio:RELEASE.2025-10-15T17-29-55Z`. ### Health Check MinIO's health is monitored using its native structured endpoints `/minio/health/live` (liveness) and `/minio/health/ready` (readiness) on port 9002: +> [!IMPORTANT] +> When deploying to staging or production, ensure that custom credentials are configured (rather than the default `minioadmin` keys described in [Configuration](#configuration)), and ensure the deployment's probes and S3 configurations are updated to reference the new values. + ```yaml livenessProbe: httpGet: diff --git a/fix_import_aiofiles.py b/fix_import_aiofiles.py deleted file mode 100644 index 70a50436..00000000 --- a/fix_import_aiofiles.py +++ /dev/null @@ -1,6 +0,0 @@ -with open("router/tests/test_get_live_gemini_oauth_token.py", "r") as f: - content = f.read() - -# I see the error is that `import aiofiles` fails in `router/main.py`. -# wait! Did I add `import aiofiles` to `router/main.py` when I tested earlier? -# NO I didn't add it in the final patch. Let me check `router/main.py` diff --git a/fix_test.py b/fix_test.py deleted file mode 100644 index 3b10d4f6..00000000 --- a/fix_test.py +++ /dev/null @@ -1,19 +0,0 @@ -import re - -with open("router/tests/test_get_live_gemini_oauth_token.py", "r") as f: - content = f.read() - -target = "token = main.get_live_gemini_oauth_token()" -new_content = "token = await main.get_live_gemini_oauth_token()" - -if target in content: - content = content.replace(target, new_content) - - # We also need to add @pytest.mark.asyncio and make the test functions async. - content = re.sub(r'def test_get_live_gemini', '@pytest.mark.asyncio\nasync def test_get_live_gemini', content) - - with open("router/tests/test_get_live_gemini_oauth_token.py", "w") as f: - f.write(content) - print("Patched test successfully") -else: - print("Target not found") diff --git a/router/main.py b/router/main.py index ab39e64d..2bfb552e 100644 --- a/router/main.py +++ b/router/main.py @@ -25,6 +25,8 @@ "/" ) +GEMINI_OAUTH_CREDS_PATH = "/config/gemini_auth/oauth_creds.json" + _redis_client = None _redis_last_init_attempt = 0.0 @@ -1035,9 +1037,8 @@ def _read_json_file_sync(file_path: str) -> dict: async def get_live_gemini_oauth_token() -> str | None: """Retrieve the current valid Gemini OAuth access token from local storage if not expired.""" try: - creds_path = "/config/gemini_auth/oauth_creds.json" - if os.path.exists(creds_path): - data = await asyncio.to_thread(_read_json_file_sync, creds_path) + if await asyncio.to_thread(os.path.exists, GEMINI_OAUTH_CREDS_PATH): + data = await asyncio.to_thread(_read_json_file_sync, GEMINI_OAUTH_CREDS_PATH) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) # Convert current time to milliseconds @@ -1062,15 +1063,14 @@ async def get_live_gemini_oauth_token() -> str | None: async def get_gemini_oauth_status() -> dict: """Returns structured OAuth status for the dashboard banner.""" - creds_path = "/config/gemini_auth/oauth_creds.json" try: - if not os.path.exists(creds_path): + if not await asyncio.to_thread(os.path.exists, GEMINI_OAUTH_CREDS_PATH): return { "status": "missing", "detail": "No oauth_creds.json found", "expiry_ms": 0, } - data = await asyncio.to_thread(_read_json_file_sync, creds_path) + data = await asyncio.to_thread(_read_json_file_sync, GEMINI_OAUTH_CREDS_PATH) access_token = data.get("access_token") expiry_ms = data.get("expiry_date", 0) current_ms = int(time.time() * 1000) diff --git a/test_record_tool_usage.py b/test_record_tool_usage.py index ffe6ca00..d0651714 100644 --- a/test_record_tool_usage.py +++ b/test_record_tool_usage.py @@ -59,8 +59,20 @@ def test_record_tool_usage_none_mapping(): def test_record_tool_usage_accumulation(): """Test that tokens accumulate correctly over multiple calls.""" - router.main.record_tool_usage(ToolUsageRecord("write", 10, 10, "model1", 50.0)) - router.main.record_tool_usage(ToolUsageRecord("write", 20, 30, "model2", 60.0)) + router.main.record_tool_usage(ToolUsageRecord( + tool_name="write", + prompt_tokens=10, + completion_tokens=10, + model="model1", + latency_ms=50.0 + )) + router.main.record_tool_usage(ToolUsageRecord( + tool_name="write", + prompt_tokens=20, + completion_tokens=30, + model="model2", + latency_ms=60.0 + )) assert router.main.stats["tool_tokens"]["write"] == 70 assert router.main.stats["prompt_tokens"] == 30 @@ -71,7 +83,13 @@ def test_record_tool_usage_timeline_limit(): """Test that the timeline buffer is capped at 15 events.""" # Add 20 events for i in range(20): - router.main.record_tool_usage(ToolUsageRecord(f"tool_{i}", 1, 1, "model", 10.0)) + router.main.record_tool_usage(ToolUsageRecord( + tool_name=f"tool_{i}", + prompt_tokens=1, + completion_tokens=1, + model="model", + latency_ms=10.0 + )) assert len(router.main.stats["timeline"]) == 15 # The first 5 events should be popped off, so the oldest event in the timeline