diff --git a/src/ucode/smart_routing/routing.py b/src/ucode/smart_routing/routing.py index 6286c767d..79f48f41b 100644 --- a/src/ucode/smart_routing/routing.py +++ b/src/ucode/smart_routing/routing.py @@ -23,11 +23,13 @@ from pathlib import Path from typing import Any +from ucode import config_io from ucode.smart_routing.rationale import format_rationale ROUTER_NAME = "task_v3" ROUTER_NAME_ENV_VAR = "SMART_ROUTER_NAME" ROUTING_PATH = "/ai-gateway/routing/v1/routes:select" +ROUTING_REQUEST_LOG_NAME = "routing-requests.jsonl" REQUEST_TIMEOUT_S = 30.0 SUBAGENT_ROUTING_DISCLAIMER = ( "Spawned subagents are routed independently based on their own complexity." @@ -234,8 +236,14 @@ def select_route( "task": {"prompt": task}, "route_selector": {"router_name": router_name}, } + url = workspace.rstrip("/") + ROUTING_PATH + # Written to a file because stdout belongs to the TUI or the hook protocol. + _append_jsonl( + config_io.APP_DIR / ROUTING_REQUEST_LOG_NAME, + {"at": time.time(), "url": url, "body": body}, + ) request = urllib.request.Request( - workspace.rstrip("/") + ROUTING_PATH, + url, data=json.dumps(body).encode("utf-8"), headers={ "Authorization": f"Bearer {token}", diff --git a/tests/test_claude_routing.py b/tests/test_claude_routing.py index 5b2b5307d..1bb694124 100644 --- a/tests/test_claude_routing.py +++ b/tests/test_claude_routing.py @@ -5,6 +5,7 @@ import json import urllib.error +from ucode import config_io from ucode.smart_routing import claude_routing, routing WS = "https://example.databricks.com" @@ -79,6 +80,30 @@ def fake_urlopen(request, timeout): assert recorded["router_name"] == captured["body"]["route_selector"]["router_name"] +def test_routing_request_is_logged(monkeypatch): + sent = {} + + def fake_urlopen(request, timeout): + sent["url"] = request.full_url + sent["body"] = json.loads(request.data) + return _Response({"route_selection": []}) + + monkeypatch.setattr(claude_routing.urllib.request, "urlopen", fake_urlopen) + + claude_routing.request_routing_decision( + WS, + "dapi-secret", + "Refactor the parser", + ["system.ai.claude-opus-4-8", "system.ai.claude-sonnet-5"], + ) + + log_path = config_io.APP_DIR / routing.ROUTING_REQUEST_LOG_NAME + [record] = [json.loads(line) for line in log_path.read_text().splitlines()] + assert record["url"] == sent["url"] + assert record["body"] == sent["body"] + assert "dapi-secret" not in log_path.read_text() + + def test_missing_arm_short_circuits_without_calling_router(monkeypatch): def fail(*args, **kwargs): raise AssertionError("router must not be called when an arm is missing")