diff --git a/pyproject.toml b/pyproject.toml index 4485d779..5c884f98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "docker>=7.1.0", "textual>=6.0.0", "requests>=2.32.0", + "socksio>=1.0.0", "cvss>=3.2", "caido-sdk-client>=0.2.0", ] @@ -105,6 +106,7 @@ module = [ "docker.*", "caido_sdk_client.*", "pydantic_settings.*", + "socksio.*", ] ignore_missing_imports = true disable_error_code = ["import-untyped"] diff --git a/strix/config/models.py b/strix/config/models.py index 7f6227f3..f308b542 100644 --- a/strix/config/models.py +++ b/strix/config/models.py @@ -2,6 +2,7 @@ from __future__ import annotations +import logging import os from typing import TYPE_CHECKING @@ -20,6 +21,9 @@ if TYPE_CHECKING: from strix.config.settings import Settings +logger = logging.getLogger(__name__) + + class StrixProvider(MultiProvider): """Route any non-OpenAI prefix through LiteLLM with the prefix preserved, so users type ``deepseek/deepseek-chat`` rather than @@ -63,7 +67,13 @@ DEFAULT_MODEL_RETRY = ModelRetrySettings( def configure_sdk_model_defaults(settings: Settings) -> None: """Apply Strix config to SDK-native defaults.""" llm = settings.llm - set_tracing_disabled(True) + try: + set_tracing_disabled(True) + except ImportError as exc: + # Disabling tracing eagerly initializes an httpx client that may need an + # optional transport (e.g. socksio for SOCKS proxies). Tracing is already + # being turned off, so a failure here is non-fatal — log and continue. + logger.warning("Could not disable SDK tracing: %s", exc) _configure_litellm_compatibility() if llm.api_key: set_default_openai_key(llm.api_key, use_for_tracing=False) diff --git a/tests/test_model_config.py b/tests/test_model_config.py new file mode 100644 index 00000000..3d82437b --- /dev/null +++ b/tests/test_model_config.py @@ -0,0 +1,43 @@ +"""Tests for SDK model configuration, including SOCKS-proxy startup resilience.""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import TYPE_CHECKING + +import socksio + +from strix.config import models + + +if TYPE_CHECKING: + import pytest + + +def _make_settings() -> SimpleNamespace: + """Build a minimal settings stub with no API key / base configured.""" + llm = SimpleNamespace(api_key=None, api_base=None, model=None) + return SimpleNamespace(llm=llm) + + +def test_socksio_importable() -> None: + """socksio must be declared so httpx can build a SOCKS transport at startup.""" + assert socksio is not None + + +def test_configure_tolerates_tracing_import_error(monkeypatch: pytest.MonkeyPatch) -> None: + """A missing SOCKS transport during tracing init must not crash startup. + + With a SOCKS proxy env var set, ``set_tracing_disabled`` eagerly builds an + httpx client that can raise ImportError. That failure is non-fatal and must + be swallowed so the rest of the SDK configuration still runs. + """ + + def _raise(_disabled: bool) -> None: + raise ImportError("Using SOCKS proxy, but the 'socksio' package is not installed") + + monkeypatch.setattr(models, "set_tracing_disabled", _raise) + monkeypatch.setattr(models, "_configure_litellm_compatibility", lambda: None) + + # Should not raise despite set_tracing_disabled blowing up. + models.configure_sdk_model_defaults(_make_settings()) diff --git a/uv.lock b/uv.lock index cc7cb0a1..70f7ef56 100644 --- a/uv.lock +++ b/uv.lock @@ -2045,6 +2045,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + [[package]] name = "sse-starlette" version = "3.3.3" @@ -2093,6 +2102,7 @@ dependencies = [ { name = "pydantic-settings" }, { name = "requests" }, { name = "rich" }, + { name = "socksio" }, { name = "textual" }, ] @@ -2118,6 +2128,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.13.0" }, { name = "requests", specifier = ">=2.32.0" }, { name = "rich" }, + { name = "socksio", specifier = ">=1.0.0" }, { name = "textual", specifier = ">=6.0.0" }, ]