1 Commits

Author SHA1 Message Date
dependabot[bot]
0b27804472 chore(deps): bump pypdf from 6.9.1 to 6.10.0
Bumps [pypdf](https://github.com/py-pdf/pypdf) from 6.9.1 to 6.10.0.
- [Release notes](https://github.com/py-pdf/pypdf/releases)
- [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/pypdf/compare/6.9.1...6.10.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-version: 6.10.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 22:01:14 +00:00
7 changed files with 42 additions and 160 deletions

View File

@@ -32,8 +32,9 @@
</div> </div>
> [!TIP] > [!TIP]
> **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production - [Get started with no setup required](https://app.strix.ai). > **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production!
--- ---

View File

@@ -134,7 +134,6 @@ class BaseAgent(metaclass=AgentMeta):
} }
agents_graph_actions._agent_graph["nodes"][self.state.agent_id] = node agents_graph_actions._agent_graph["nodes"][self.state.agent_id] = node
with agents_graph_actions._agent_llm_stats_lock:
agents_graph_actions._agent_instances[self.state.agent_id] = self agents_graph_actions._agent_instances[self.state.agent_id] = self
agents_graph_actions._agent_states[self.state.agent_id] = self.state agents_graph_actions._agent_states[self.state.agent_id] = self.state

View File

@@ -799,25 +799,17 @@ class Tracer:
) )
def get_total_llm_stats(self) -> dict[str, Any]: def get_total_llm_stats(self) -> dict[str, Any]:
from strix.tools.agents_graph.agents_graph_actions import ( from strix.tools.agents_graph.agents_graph_actions import _agent_instances
_agent_instances,
_completed_agent_llm_totals,
_agent_llm_stats_lock,
)
with _agent_llm_stats_lock:
completed_totals = dict(_completed_agent_llm_totals)
active_agents = list(_agent_instances.values())
total_stats = { total_stats = {
"input_tokens": int(completed_totals.get("input_tokens", 0) or 0), "input_tokens": 0,
"output_tokens": int(completed_totals.get("output_tokens", 0) or 0), "output_tokens": 0,
"cached_tokens": int(completed_totals.get("cached_tokens", 0) or 0), "cached_tokens": 0,
"cost": float(completed_totals.get("cost", 0.0) or 0.0), "cost": 0.0,
"requests": int(completed_totals.get("requests", 0) or 0), "requests": 0,
} }
for agent_instance in active_agents: for agent_instance in _agent_instances.values():
if hasattr(agent_instance, "llm") and hasattr(agent_instance.llm, "_total_stats"): if hasattr(agent_instance, "llm") and hasattr(agent_instance.llm, "_total_stats"):
agent_stats = agent_instance.llm._total_stats agent_stats = agent_instance.llm._total_stats
total_stats["input_tokens"] += agent_stats.input_tokens total_stats["input_tokens"] += agent_stats.input_tokens

View File

@@ -19,55 +19,9 @@ _running_agents: dict[str, threading.Thread] = {}
_agent_instances: dict[str, Any] = {} _agent_instances: dict[str, Any] = {}
_agent_llm_stats_lock = threading.Lock()
def _empty_llm_stats_totals() -> dict[str, int | float]:
return {
"input_tokens": 0,
"output_tokens": 0,
"cached_tokens": 0,
"cost": 0.0,
"requests": 0,
}
_completed_agent_llm_totals: dict[str, int | float] = _empty_llm_stats_totals()
_agent_states: dict[str, Any] = {} _agent_states: dict[str, Any] = {}
def _snapshot_agent_llm_stats(agent: Any) -> dict[str, int | float] | None:
if not hasattr(agent, "llm") or not hasattr(agent.llm, "_total_stats"):
return None
stats = agent.llm._total_stats
return {
"input_tokens": stats.input_tokens,
"output_tokens": stats.output_tokens,
"cached_tokens": stats.cached_tokens,
"cost": stats.cost,
"requests": stats.requests,
}
def _finalize_agent_llm_stats(agent_id: str, agent: Any) -> None:
stats = _snapshot_agent_llm_stats(agent)
with _agent_llm_stats_lock:
if stats is not None:
_completed_agent_llm_totals["input_tokens"] += int(stats["input_tokens"])
_completed_agent_llm_totals["output_tokens"] += int(stats["output_tokens"])
_completed_agent_llm_totals["cached_tokens"] += int(stats["cached_tokens"])
_completed_agent_llm_totals["cost"] += float(stats["cost"])
_completed_agent_llm_totals["requests"] += int(stats["requests"])
node = _agent_graph["nodes"].get(agent_id)
if node is not None:
node["llm_stats"] = stats
_agent_instances.pop(agent_id, None)
def _is_whitebox_agent(agent_id: str) -> bool: def _is_whitebox_agent(agent_id: str) -> bool:
agent = _agent_instances.get(agent_id) agent = _agent_instances.get(agent_id)
return bool(getattr(getattr(agent, "llm_config", None), "is_whitebox", False)) return bool(getattr(getattr(agent, "llm_config", None), "is_whitebox", False))
@@ -283,7 +237,7 @@ def _run_agent_in_thread(
_agent_graph["nodes"][state.agent_id]["finished_at"] = datetime.now(UTC).isoformat() _agent_graph["nodes"][state.agent_id]["finished_at"] = datetime.now(UTC).isoformat()
_agent_graph["nodes"][state.agent_id]["result"] = {"error": str(e)} _agent_graph["nodes"][state.agent_id]["result"] = {"error": str(e)}
_running_agents.pop(state.agent_id, None) _running_agents.pop(state.agent_id, None)
_finalize_agent_llm_stats(state.agent_id, agent) _agent_instances.pop(state.agent_id, None)
raise raise
else: else:
if state.stop_requested: if state.stop_requested:
@@ -293,7 +247,7 @@ def _run_agent_in_thread(
_agent_graph["nodes"][state.agent_id]["finished_at"] = datetime.now(UTC).isoformat() _agent_graph["nodes"][state.agent_id]["finished_at"] = datetime.now(UTC).isoformat()
_agent_graph["nodes"][state.agent_id]["result"] = result _agent_graph["nodes"][state.agent_id]["result"] = result
_running_agents.pop(state.agent_id, None) _running_agents.pop(state.agent_id, None)
_finalize_agent_llm_stats(state.agent_id, agent) _agent_instances.pop(state.agent_id, None)
return {"result": result} return {"result": result}
@@ -464,7 +418,6 @@ def create_agent(
if inherit_context: if inherit_context:
inherited_messages = agent_state.get_conversation_history() inherited_messages = agent_state.get_conversation_history()
with _agent_llm_stats_lock:
_agent_instances[state.agent_id] = agent _agent_instances[state.agent_id] = agent
thread = threading.Thread( thread = threading.Thread(

View File

@@ -10,7 +10,6 @@ from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExportResult
from strix.telemetry import tracer as tracer_module from strix.telemetry import tracer as tracer_module
from strix.telemetry import utils as telemetry_utils from strix.telemetry import utils as telemetry_utils
from strix.telemetry.tracer import Tracer, set_global_tracer from strix.telemetry.tracer import Tracer, set_global_tracer
from strix.tools.agents_graph import agents_graph_actions
def _load_events(events_path: Path) -> list[dict[str, Any]]: def _load_events(events_path: Path) -> list[dict[str, Any]]:
@@ -256,75 +255,6 @@ def test_events_with_agent_id_include_agent_name(monkeypatch, tmp_path) -> None:
assert chat_event["actor"]["agent_name"] == "Root Agent" assert chat_event["actor"]["agent_name"] == "Root Agent"
def test_get_total_llm_stats_includes_completed_subagents(monkeypatch, tmp_path) -> None:
monkeypatch.chdir(tmp_path)
class DummyStats:
def __init__(
self,
*,
input_tokens: int,
output_tokens: int,
cached_tokens: int,
cost: float,
requests: int,
) -> None:
self.input_tokens = input_tokens
self.output_tokens = output_tokens
self.cached_tokens = cached_tokens
self.cost = cost
self.requests = requests
class DummyLLM:
def __init__(self, stats: DummyStats) -> None:
self._total_stats = stats
class DummyAgent:
def __init__(self, stats: DummyStats) -> None:
self.llm = DummyLLM(stats)
tracer = Tracer("cost-rollup")
set_global_tracer(tracer)
monkeypatch.setattr(
agents_graph_actions,
"_agent_instances",
{
"root-agent": DummyAgent(
DummyStats(
input_tokens=1_000,
output_tokens=250,
cached_tokens=100,
cost=0.12345,
requests=2,
)
)
},
)
monkeypatch.setattr(
agents_graph_actions,
"_completed_agent_llm_totals",
{
"input_tokens": 2_000,
"output_tokens": 500,
"cached_tokens": 400,
"cost": 0.54321,
"requests": 3,
},
)
stats = tracer.get_total_llm_stats()
assert stats["total"] == {
"input_tokens": 3_000,
"output_tokens": 750,
"cached_tokens": 500,
"cost": 0.6667,
"requests": 5,
}
assert stats["total_tokens"] == 3_750
def test_run_metadata_is_only_on_run_lifecycle_events(monkeypatch, tmp_path) -> None: def test_run_metadata_is_only_on_run_lifecycle_events(monkeypatch, tmp_path) -> None:
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)

View File

@@ -5,24 +5,16 @@ from strix.llm.config import LLMConfig
from strix.tools.agents_graph import agents_graph_actions from strix.tools.agents_graph import agents_graph_actions
def _reset_agent_graph_state() -> None: def test_create_agent_inherits_parent_whitebox_flag(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
agents_graph_actions._agent_graph["nodes"].clear() agents_graph_actions._agent_graph["nodes"].clear()
agents_graph_actions._agent_graph["edges"].clear() agents_graph_actions._agent_graph["edges"].clear()
agents_graph_actions._agent_messages.clear() agents_graph_actions._agent_messages.clear()
agents_graph_actions._running_agents.clear() agents_graph_actions._running_agents.clear()
agents_graph_actions._agent_instances.clear() agents_graph_actions._agent_instances.clear()
agents_graph_actions._completed_agent_llm_totals.clear()
agents_graph_actions._completed_agent_llm_totals.update(
agents_graph_actions._empty_llm_stats_totals()
)
agents_graph_actions._agent_states.clear() agents_graph_actions._agent_states.clear()
def test_create_agent_inherits_parent_whitebox_flag(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state()
parent_id = "parent-agent" parent_id = "parent-agent"
parent_llm = LLMConfig(timeout=123, scan_mode="standard", is_whitebox=True) parent_llm = LLMConfig(timeout=123, scan_mode="standard", is_whitebox=True)
agents_graph_actions._agent_instances[parent_id] = SimpleNamespace( agents_graph_actions._agent_instances[parent_id] = SimpleNamespace(
@@ -74,7 +66,12 @@ def test_create_agent_inherits_parent_whitebox_flag(monkeypatch) -> None:
def test_delegation_prompt_includes_wiki_memory_instruction_in_whitebox(monkeypatch) -> None: def test_delegation_prompt_includes_wiki_memory_instruction_in_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5") monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state() agents_graph_actions._agent_graph["nodes"].clear()
agents_graph_actions._agent_graph["edges"].clear()
agents_graph_actions._agent_messages.clear()
agents_graph_actions._running_agents.clear()
agents_graph_actions._agent_instances.clear()
agents_graph_actions._agent_states.clear()
parent_id = "parent-1" parent_id = "parent-1"
child_id = "child-1" child_id = "child-1"
@@ -119,7 +116,12 @@ def test_delegation_prompt_includes_wiki_memory_instruction_in_whitebox(monkeypa
def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None: def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5") monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state() agents_graph_actions._agent_graph["nodes"].clear()
agents_graph_actions._agent_graph["edges"].clear()
agents_graph_actions._agent_messages.clear()
agents_graph_actions._running_agents.clear()
agents_graph_actions._agent_instances.clear()
agents_graph_actions._agent_states.clear()
parent_id = "parent-2" parent_id = "parent-2"
child_id = "child-2" child_id = "child-2"
@@ -190,7 +192,12 @@ def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None:
def test_run_agent_in_thread_injects_shared_wiki_context_in_whitebox(monkeypatch) -> None: def test_run_agent_in_thread_injects_shared_wiki_context_in_whitebox(monkeypatch) -> None:
monkeypatch.setenv("STRIX_LLM", "openai/gpt-5") monkeypatch.setenv("STRIX_LLM", "openai/gpt-5")
_reset_agent_graph_state() agents_graph_actions._agent_graph["nodes"].clear()
agents_graph_actions._agent_graph["edges"].clear()
agents_graph_actions._agent_messages.clear()
agents_graph_actions._running_agents.clear()
agents_graph_actions._agent_instances.clear()
agents_graph_actions._agent_states.clear()
parent_id = "parent-3" parent_id = "parent-3"
child_id = "child-3" child_id = "child-3"

16
uv.lock generated
View File

@@ -2374,7 +2374,7 @@ name = "macholib"
version = "1.16.4" version = "1.16.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "altgraph" }, { name = "altgraph", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" }
wheels = [ wheels = [
@@ -3815,7 +3815,7 @@ name = "pexpect"
version = "4.9.0" version = "4.9.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "ptyprocess" }, { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
wheels = [ wheels = [
@@ -4433,11 +4433,11 @@ wheels = [
[[package]] [[package]]
name = "pypdf" name = "pypdf"
version = "6.9.1" version = "6.10.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/fb/dc2e8cb006e80b0020ed20d8649106fe4274e82d8e756ad3e24ade19c0df/pypdf-6.9.1.tar.gz", hash = "sha256:ae052407d33d34de0c86c5c729be6d51010bf36e03035a8f23ab449bca52377d", size = 5311551, upload-time = "2026-03-17T10:46:07.876Z" } sdist = { url = "https://files.pythonhosted.org/packages/b8/9f/ca96abf18683ca12602065e4ed2bec9050b672c87d317f1079abc7b6d993/pypdf-6.10.0.tar.gz", hash = "sha256:4c5a48ba258c37024ec2505f7e8fd858525f5502784a2e1c8d415604af29f6ef", size = 5314833, upload-time = "2026-04-10T09:34:57.102Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/f4/75543fa802b86e72f87e9395440fe1a89a6d149887e3e55745715c3352ac/pypdf-6.9.1-py3-none-any.whl", hash = "sha256:f35a6a022348fae47e092a908339a8f3dc993510c026bb39a96718fc7185e89f", size = 333661, upload-time = "2026-03-17T10:46:06.286Z" }, { url = "https://files.pythonhosted.org/packages/55/f2/7ebe366f633f30a6ad105f650f44f24f98cb1335c4157d21ae47138b3482/pypdf-6.10.0-py3-none-any.whl", hash = "sha256:90005e959e1596c6e6c84c8b0ad383285b3e17011751cedd17f2ce8fcdfc86de", size = 334459, upload-time = "2026-04-10T09:34:54.966Z" },
] ]
[[package]] [[package]]
@@ -4458,7 +4458,7 @@ name = "pyroscope-io"
version = "0.8.16" version = "0.8.16"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cffi" }, { name = "cffi", marker = "sys_platform != 'win32'" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/50/607b38b120ba8adad954119ba512c53590c793f0cf7f009ba6549e4e1d77/pyroscope_io-0.8.16-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e07edcfd59f5bdce42948b92c9b118c824edbd551730305f095a6b9af401a9e8", size = 3138869, upload-time = "2026-01-22T06:23:24.664Z" }, { url = "https://files.pythonhosted.org/packages/a8/50/607b38b120ba8adad954119ba512c53590c793f0cf7f009ba6549e4e1d77/pyroscope_io-0.8.16-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e07edcfd59f5bdce42948b92c9b118c824edbd551730305f095a6b9af401a9e8", size = 3138869, upload-time = "2026-01-22T06:23:24.664Z" },
@@ -5439,7 +5439,7 @@ wheels = [
[[package]] [[package]]
name = "strix-agent" name = "strix-agent"
version = "0.8.2" version = "0.8.3"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "cvss" }, { name = "cvss" },
@@ -5511,7 +5511,7 @@ requires-dist = [
{ name = "rich" }, { name = "rich" },
{ name = "scrubadub", specifier = ">=2.0.1" }, { name = "scrubadub", specifier = ">=2.0.1" },
{ name = "tenacity", specifier = ">=9.0.0" }, { name = "tenacity", specifier = ">=9.0.0" },
{ name = "textual", specifier = ">=4.0.0" }, { name = "textual", specifier = ">=6.0.0" },
{ name = "traceloop-sdk", specifier = ">=0.53.0" }, { name = "traceloop-sdk", specifier = ">=0.53.0" },
{ name = "uvicorn", marker = "extra == 'sandbox'" }, { name = "uvicorn", marker = "extra == 'sandbox'" },
{ name = "xmltodict", specifier = ">=0.13.0" }, { name = "xmltodict", specifier = ">=0.13.0" },