Compare commits

...
12 changed files with 88 additions and 184 deletions

No files matched your search

+5 -2
View File
@@ -72,7 +72,10 @@ jobs:
TUI_NAME="strix-tui"
dist/strix --version
fi
uv run pyi-archive_viewer -l "$PYI_BINARY" | grep -E "strix[/\\]+bin[/\\]+$TUI_NAME" >/dev/null
uv run pyi-archive_viewer -l -r "$PYI_BINARY" > archive.txt
grep -E "strix[/\\]+bin[/\\]+$TUI_NAME" archive.txt >/dev/null
grep -E "'google\.auth\.transport\.requests'" archive.txt >/dev/null
grep -E "'boto3\.session'" archive.txt >/dev/null
if [[ "${{ matrix.target }}" == "linux-arm64" ]]; then
file dist/strix
@@ -104,6 +107,7 @@ jobs:
if-no-files-found: error
release:
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
permissions:
@@ -118,6 +122,5 @@ jobs:
- name: Create Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }}
generate_release_notes: true
files: release/**
+23 -2
View File
@@ -36,6 +36,8 @@ jobs:
uv pip install --python /tmp/strix-smoke/bin/python dist/*.whl
/tmp/strix-smoke/bin/strix --version
/tmp/strix-smoke/bin/strix --help >/dev/null
- name: Vertex AI and Bedrock dependencies install with the wheel
run: /tmp/strix-smoke/bin/python -c "import google.auth.transport.requests, boto3"
binary:
runs-on: ubuntu-latest
@@ -51,5 +53,24 @@ jobs:
- run: make tui-build
- run: uv run --frozen pyinstaller strix.spec --noconfirm
- run: dist/strix --version
- name: Binary bundles the TUI sidecar
run: uv run --frozen pyi-archive_viewer -l dist/strix | grep -E "strix/bin/strix-tui" >/dev/null
- name: Binary bundles the TUI sidecar and the provider dependencies
run: |
uv run --frozen pyi-archive_viewer -l -r dist/strix > archive.txt
grep -E "strix/bin/strix-tui" archive.txt >/dev/null
grep -E "'google\.auth\.transport\.requests'" archive.txt >/dev/null
grep -E "'boto3\.session'" archive.txt >/dev/null
- name: Binary reaches Vertex AI and Bedrock authentication
env:
STRIX_TELEMETRY: "false"
STRIX_IMAGE: alpine:3.20
VERTEXAI_PROJECT: strix-ci
VERTEXAI_LOCATION: us-central1
AWS_REGION: us-east-1
run: |
mkdir -p /tmp/target && echo 'print("hi")' > /tmp/target/app.py
STRIX_LLM=vertex_ai/gemini-2.5-pro dist/strix -n -t /tmp/target --scan-mode quick > vertex.log 2>&1 || true
grep -q "DefaultCredentialsError" vertex.log
if grep -q "No module named" vertex.log; then exit 1; fi
STRIX_LLM=bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0 dist/strix -n -t /tmp/target --scan-mode quick > bedrock.log 2>&1 || true
grep -q "Unable to locate credentials" bedrock.log
if grep -q "No module named" bedrock.log; then exit 1; fi
-8
View File
@@ -3,14 +3,6 @@ title: "AWS Bedrock"
description: "Configure Strix with models via AWS Bedrock"
---
## Installation
Bedrock requires the AWS SDK dependency. Install Strix with the bedrock extra:
```bash
pipx install "strix-agent[bedrock]"
```
## Setup
```bash
-8
View File
@@ -3,14 +3,6 @@ title: "Google Vertex AI"
description: "Configure Strix with Gemini models via Google Cloud"
---
## Installation
Vertex AI requires the Google Cloud dependency. Install Strix with the vertex extra:
```bash
pipx install "strix-agent[vertex]"
```
## Setup
```bash
+2 -4
View File
@@ -36,6 +36,8 @@ dependencies = [
"openai-agents[litellm]>=0.19.0,<0.20",
"openai>=2.45.0,<3",
"litellm>=1.101.0",
"google-auth>=2.0.0",
"boto3>=1.28.0",
"pydantic>=2.11.3",
"pydantic-settings>=2.13.0",
"rich",
@@ -52,10 +54,6 @@ dependencies = [
"pyyaml>=6.0",
]
[project.optional-dependencies]
vertex = ["google-auth>=2.0.0"]
bedrock = ["boto3>=1.28.0"]
[project.scripts]
strix = "strix.interface.main:main"
+2 -2
View File
@@ -180,6 +180,8 @@ hiddenimports = [
]
hiddenimports += collect_submodules('litellm')
hiddenimports += collect_submodules('google.auth')
hiddenimports += collect_submodules('google.oauth2')
hiddenimports += collect_submodules('rich')
hiddenimports += collect_submodules('pydantic')
hiddenimports += collect_submodules('pygments')
@@ -211,8 +213,6 @@ excludes = [
'google.cloud',
'google.cloud.aiplatform',
'google.api_core',
'google.auth',
'google.oauth2',
'google.protobuf',
'grpc',
'grpcio',
+3 -43
View File
@@ -6,6 +6,7 @@ Strix Agent Interface
import argparse
import asyncio
import contextlib
import logging
import sys
from pathlib import Path
from typing import Any
@@ -53,21 +54,6 @@ from strix.telemetry import posthog, report_error, scarf, set_scan_phase
from strix.telemetry.logging import setup_console_logging
BEDROCK_MODEL_PREFIX = "bedrock/"
BEDROCK_MISSING_MODULE_ERROR = "No module named 'boto3'"
BEDROCK_EXTRA_HINT = (
'Bedrock support is optional. Install it with: pipx install "strix-agent[bedrock]"'
)
VERTEX_MODEL_MARKER = "vertex"
VERTEX_MISSING_MODULE_ERROR = "No module named 'google"
VERTEX_EXTRA_HINT = (
'Vertex AI support is optional. Install it with: pipx install "strix-agent[vertex]"'
)
import logging # noqa: E402
logger = logging.getLogger(__name__)
_ROOT_SUBCOMMAND_HELP = """
@@ -96,29 +82,6 @@ def _exception_messages(exc: BaseException) -> tuple[str, ...]:
return tuple(messages)
def _provider_import_hint(exc: BaseException, model: str) -> str | None:
"""Return an install hint when *exc* is a missing provider dependency.
Bedrock and Vertex AI ship as optional extras: Bedrock needs ``boto3`` and
Vertex AI needs ``google-auth``. When either is absent, litellm may raise an
``ImportError``/``ModuleNotFoundError`` directly or wrap it in a connection
error. Map the missing module back to the matching extra so the user knows
what to install. Returns ``None`` for any unrelated error.
"""
model_name = model.lower()
messages = _exception_messages(exc)
if any(
BEDROCK_MISSING_MODULE_ERROR in message for message in messages
) and model_name.startswith(BEDROCK_MODEL_PREFIX):
return BEDROCK_EXTRA_HINT
if (
any(VERTEX_MISSING_MODULE_ERROR in message for message in messages)
and VERTEX_MODEL_MARKER in model_name
):
return VERTEX_EXTRA_HINT
return None
def _subscription_error_hint(exc: BaseException) -> str | None:
"""Return an actionable hint for a known ChatGPT-subscription error, or None."""
if not codex.subscription_model(load_settings().llm.model):
@@ -364,7 +327,7 @@ def _print_error_panel(title: str, message: str) -> None:
console.print()
def _print_model_connection_error(exc: BaseException, model_name: str) -> None:
def _print_model_connection_error(exc: BaseException) -> None:
console = Console()
error_text = Text()
sub_hint = _subscription_error_hint(exc)
@@ -380,9 +343,6 @@ def _print_model_connection_error(exc: BaseException, model_name: str) -> None:
error_text.append("\n\n", style="white")
error_text.append("Could not establish connection to the language model.\n", style="white")
error_text.append("Please check your configuration and try again.\n", style="white")
hint = _provider_import_hint(exc, model_name)
if hint is not None:
error_text.append(f"\n{hint}\n", style="bold yellow")
error_text.append(f"\nError: {exc}", style="dim white")
panel = Panel(
@@ -435,7 +395,7 @@ def _bootstrap_scan(args: argparse.Namespace) -> None:
asyncio.run(warm_up_llm())
except ModelConnectionError as exc:
report_error("model_connection_failed", exc)
_print_model_connection_error(exc, exc.model_name)
_print_model_connection_error(exc)
sys.exit(1)
persist_current()
try:
+2 -2
View File
@@ -79,7 +79,7 @@ def test_direct_launch_with_a_bad_key_prints_the_panel_and_exits_before_the_tui(
monkeypatch.setattr(
cli_main,
"_print_model_connection_error",
lambda exc, model: calls.append(f"panel:{model}:{exc}"),
lambda exc: calls.append(f"panel:{exc}"),
)
monkeypatch.setattr(cli_main, "persist_current", lambda: calls.append("persist"))
monkeypatch.setattr(cli_main, "prepare_run", lambda _args: calls.append("prepare"))
@@ -89,7 +89,7 @@ def test_direct_launch_with_a_bad_key_prints_the_panel_and_exits_before_the_tui(
cli_main._bootstrap_scan(argparse.Namespace(non_interactive=False, needs_setup=False))
assert exit_info.value.code == 1
assert calls == ["panel:openai/gpt-4o:Error code: 401"]
assert calls == ["panel:Error code: 401"]
def test_tui_startup_failure_marks_the_prepared_run_failed(monkeypatch: pytest.MonkeyPatch) -> None:
-27
View File
@@ -1,27 +0,0 @@
"""Tests for the optional-dependency extras declared in pyproject.toml."""
from __future__ import annotations
import tomllib
from pathlib import Path
PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"
def _optional_dependencies() -> dict[str, list[str]]:
data = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
extras: dict[str, list[str]] = data["project"]["optional-dependencies"]
return extras
def test_vertex_extra_pins_google_auth() -> None:
extras = _optional_dependencies()
assert "vertex" in extras
assert any(req.startswith("google-auth") for req in extras["vertex"])
def test_bedrock_extra_pins_boto3() -> None:
extras = _optional_dependencies()
assert "bedrock" in extras
assert any(req.startswith("boto3") for req in extras["bedrock"])
+46
View File
@@ -0,0 +1,46 @@
"""Vertex AI and Bedrock support ship with every install, not as extras."""
from __future__ import annotations
import importlib
import tomllib
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
def _pyproject() -> dict[str, object]:
return tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
@pytest.mark.parametrize("package", ["google-auth", "boto3"])
def test_provider_packages_are_regular_dependencies(package: str) -> None:
project = _pyproject()["project"]
assert isinstance(project, dict)
assert any(req.startswith(package) for req in project["dependencies"])
assert "optional-dependencies" not in project
@pytest.mark.parametrize(
"module",
[
"google.auth",
"google.auth.transport.requests",
"google.oauth2.service_account",
"boto3",
"litellm.llms.vertex_ai.vertex_llm_base",
"litellm.llms.bedrock.base_aws_llm",
],
)
def test_provider_modules_import(module: str) -> None:
importlib.import_module(module)
def test_pyinstaller_spec_bundles_google_auth() -> None:
spec = (ROOT / "strix.spec").read_text(encoding="utf-8")
assert "collect_submodules('google.auth')" in spec
assert "'google.auth'," not in spec
assert "'google.oauth2'," not in spec
-74
View File
@@ -1,74 +0,0 @@
"""Tests for the provider import-error hint helper in interface/main.py."""
from __future__ import annotations
from strix.interface.main import _provider_import_hint
VERTEX_MODEL = "vertex_ai/gemini-3-pro-preview"
BEDROCK_MODEL = "bedrock/anthropic.claude-4-5-sonnet"
VERTEX_EXTRA_NAME = "vertex"
BEDROCK_EXTRA_NAME = "bedrock"
INSTALL_EXTRA_COMMAND_FRAGMENT = 'pipx install "strix-agent['
WRAPPED_VERTEX_GOOGLE_ERROR = "litellm.APIConnectionError: No module named 'google'"
WRAPPED_BEDROCK_BOTO3_ERROR = "litellm.APIConnectionError: No module named 'boto3'"
def test_bedrock_boto3_hint() -> None:
exc = ModuleNotFoundError("No module named 'boto3'")
hint = _provider_import_hint(exc, BEDROCK_MODEL)
assert hint is not None
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
assert BEDROCK_EXTRA_NAME in hint
def test_vertex_google_hint() -> None:
exc = ImportError("No module named 'google'")
hint = _provider_import_hint(exc, VERTEX_MODEL)
assert hint is not None
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
assert VERTEX_EXTRA_NAME in hint
def test_vertex_google_hint_for_litellm_wrapped_connection_error() -> None:
exc = ConnectionError(WRAPPED_VERTEX_GOOGLE_ERROR)
hint = _provider_import_hint(exc, VERTEX_MODEL)
assert hint is not None
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
assert VERTEX_EXTRA_NAME in hint
def test_bedrock_boto3_hint_for_litellm_wrapped_connection_error() -> None:
exc = ConnectionError(WRAPPED_BEDROCK_BOTO3_ERROR)
hint = _provider_import_hint(exc, BEDROCK_MODEL)
assert hint is not None
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
assert BEDROCK_EXTRA_NAME in hint
def test_vertex_google_submodule_hint() -> None:
exc = ModuleNotFoundError("No module named 'google.auth'")
hint = _provider_import_hint(exc, VERTEX_MODEL)
assert hint is not None
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
assert VERTEX_EXTRA_NAME in hint
def test_vertex_google_hint_for_deeply_chained_error() -> None:
root = ModuleNotFoundError("No module named 'google.auth'")
middle = RuntimeError("provider init failed")
middle.__cause__ = root
exc = ConnectionError("litellm.APIConnectionError: request failed")
exc.__cause__ = middle
hint = _provider_import_hint(exc, VERTEX_MODEL)
assert hint is not None
assert VERTEX_EXTRA_NAME in hint
def test_non_import_error_returns_none() -> None:
assert _provider_import_hint(ConnectionError("boom"), "bedrock/whatever") is None
def test_unrelated_provider_returns_none() -> None:
exc = ImportError("No module named 'something'")
assert _provider_import_hint(exc, "openai/gpt-4") is None
Generated
+5 -12
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 5
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.15'",
@@ -2389,10 +2389,12 @@ name = "strix-agent"
version = "1.6.2"
source = { editable = "." }
dependencies = [
{ name = "boto3" },
{ name = "caido-sdk-client" },
{ name = "cryptography" },
{ name = "cvss" },
{ name = "docker" },
{ name = "google-auth" },
{ name = "litellm" },
{ name = "markdown-it-py" },
{ name = "openai" },
@@ -2406,14 +2408,6 @@ dependencies = [
{ name = "rich" },
]
[package.optional-dependencies]
bedrock = [
{ name = "boto3" },
]
vertex = [
{ name = "google-auth" },
]
[package.dev-dependencies]
dev = [
{ name = "bandit" },
@@ -2429,12 +2423,12 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.28.0" },
{ name = "boto3", specifier = ">=1.28.0" },
{ name = "caido-sdk-client", specifier = ">=0.2.0" },
{ name = "cryptography", specifier = ">=48.0.1,<49" },
{ name = "cvss", specifier = ">=3.2" },
{ name = "docker", specifier = ">=7.1.0" },
{ name = "google-auth", marker = "extra == 'vertex'", specifier = ">=2.0.0" },
{ name = "google-auth", specifier = ">=2.0.0" },
{ name = "litellm", specifier = ">=1.101.0" },
{ name = "markdown-it-py", specifier = ">=3.0.0" },
{ name = "openai", specifier = ">=2.45.0,<3" },
@@ -2447,7 +2441,6 @@ requires-dist = [
{ name = "requests", specifier = ">=2.32.0" },
{ name = "rich" },
]
provides-extras = ["vertex", "bedrock"]
[package.metadata.requires-dev]
dev = [