From daefac80b0f9625173ee185e876472259733cc96 Mon Sep 17 00:00:00 2001 From: Ibrahim Aliyu Saddique Date: Sun, 31 May 2026 12:19:57 +0100 Subject: [PATCH] [spark-compete] fix(providers): send User-Agent on OpenAI-compatible HTTP calls Cloudflare-fronted OpenAI-compatible endpoints reject bare urllib requests without User-Agent. Add Spark-CLI UA header and regression test. Co-authored-by: Cursor --- src/spark_cli/cli.py | 2 ++ tests/test_cli.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/spark_cli/cli.py b/src/spark_cli/cli.py index d7f57a2b..7226e50e 100644 --- a/src/spark_cli/cli.py +++ b/src/spark_cli/cli.py @@ -201,6 +201,7 @@ def discover_repo_root() -> Path: "/root", "/var/run/docker.sock", ) +OPENAI_COMPAT_HTTP_USER_AGENT = "Spark-CLI/1.0 (+https://github.com/vibeforge1111/spark-cli)" TRUST_TIERS = ("builtin", "trusted", "community", "untrusted") TRUST_BLOCK_THRESHOLD = { "builtin": "critical", @@ -10409,6 +10410,7 @@ def openai_compatible_chat_completion(target: dict[str, Any], prompt: str) -> st headers={ "Authorization": f"Bearer {target['api_key']}", "Content-Type": "application/json", + "User-Agent": OPENAI_COMPAT_HTTP_USER_AGENT, }, method="POST", ) diff --git a/tests/test_cli.py b/tests/test_cli.py index d0aeafe3..c60861e7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,6 +10,7 @@ import tempfile import unittest import urllib.error +import urllib.request from argparse import Namespace from contextlib import redirect_stdout from io import StringIO @@ -148,7 +149,8 @@ step_previously_completed, manifest_schema_version, manual_telegram_profiles, - needs_capabilities, + openai_compatible_chat_completion, + OPENAI_COMPAT_HTTP_USER_AGENT, parse_secret_pairs, parse_version_constraint, parse_version_tuple, @@ -9958,6 +9960,33 @@ def test_provider_status_payload_uses_legacy_secret_keys_for_api_auth(self) -> N self.assertEqual(payload["roles"][role]["auth_mode"], "api_key") self.assertEqual(payload["roles"][role]["model"], "glm-5.1") + def test_openai_compatible_chat_completion_sends_user_agent(self) -> None: + captured: dict[str, str] = {} + + class FakeResponse: + def read(self) -> bytes: + return json.dumps({"choices": [{"message": {"content": "PING_OK"}}]}).encode("utf-8") + + def __enter__(self): + return self + + def __exit__(self, *args: object) -> None: + return None + + def fake_urlopen(request: urllib.request.Request, timeout: float = 0) -> FakeResponse: + captured["User-Agent"] = request.headers.get("User-agent") or request.headers.get("User-Agent", "") + return FakeResponse() + + target = { + "base_url": "https://api.example.test/v1", + "api_key": "test-key", + "model": "test-model", + } + with patch("urllib.request.urlopen", side_effect=fake_urlopen): + result = openai_compatible_chat_completion(target, "ping") + self.assertEqual(result, "PING_OK") + self.assertEqual(captured["User-Agent"], OPENAI_COMPAT_HTTP_USER_AGENT) + def test_collect_verify_payload_reports_launch_ready_stack(self) -> None: expected = [ "spark-researcher",