Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
"""Background-review usage attribution (issue #87250).
|
||||
|
||||
Background-review forks run with ``_session_db = None`` (persistence
|
||||
isolation), so their provider-billed API calls were never recorded in
|
||||
``session_model_usage``. ``_record_review_usage_to_parent`` closes that gap
|
||||
by snapshotting the fork's in-memory counters and recording them against the
|
||||
parent session via the aux-accounting chokepoint.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from agent import background_review
|
||||
from hermes_state import SessionDB
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db(tmp_path):
|
||||
return SessionDB(tmp_path / "state.db")
|
||||
|
||||
|
||||
def _usage_rows(db, session_id):
|
||||
with db._lock:
|
||||
rows = db._conn.execute(
|
||||
"SELECT * FROM session_model_usage WHERE session_id = ? ORDER BY task",
|
||||
(session_id,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
class _FakeParent:
|
||||
def __init__(self, session_db, session_id="sess-parent"):
|
||||
self._session_db = session_db
|
||||
self.session_id = session_id
|
||||
|
||||
|
||||
def _usage(**overrides):
|
||||
base = {
|
||||
"model": "test-model",
|
||||
"provider": "test-provider",
|
||||
"base_url": "https://example.invalid/v1",
|
||||
"input_tokens": 12000,
|
||||
"output_tokens": 2400,
|
||||
"cache_read_tokens": 190000,
|
||||
"cache_write_tokens": 0,
|
||||
"reasoning_tokens": 0,
|
||||
"api_calls": 5,
|
||||
"estimated_cost_usd": 0.05,
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
def test_records_fork_usage_against_parent_session(db):
|
||||
db.create_session("sess-parent", source="cli")
|
||||
|
||||
background_review._record_review_usage_to_parent(_FakeParent(db), _usage())
|
||||
|
||||
rows = _usage_rows(db, "sess-parent")
|
||||
assert len(rows) == 1
|
||||
r = rows[0]
|
||||
assert r["task"] == "background_review"
|
||||
assert r["model"] == "test-model"
|
||||
assert r["billing_provider"] == "test-provider"
|
||||
assert r["input_tokens"] == 12000
|
||||
assert r["output_tokens"] == 2400
|
||||
assert r["cache_read_tokens"] == 190000
|
||||
assert r["api_call_count"] == 5
|
||||
assert r.get("estimated_cost_usd") == 0.05
|
||||
|
||||
|
||||
def test_accumulates_repeated_forks_same_model(db):
|
||||
db.create_session("sess-parent", source="cli")
|
||||
parent = _FakeParent(db)
|
||||
|
||||
background_review._record_review_usage_to_parent(parent, _usage(api_calls=5))
|
||||
background_review._record_review_usage_to_parent(parent, _usage(api_calls=7))
|
||||
|
||||
rows = _usage_rows(db, "sess-parent")
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["input_tokens"] == 24000
|
||||
assert rows[0]["api_call_count"] == 12
|
||||
|
||||
|
||||
def test_noop_when_fork_made_no_calls(db):
|
||||
db.create_session("sess-parent", source="cli")
|
||||
|
||||
background_review._record_review_usage_to_parent(
|
||||
_FakeParent(db),
|
||||
_usage(
|
||||
input_tokens=0,
|
||||
output_tokens=0,
|
||||
cache_read_tokens=0,
|
||||
cache_write_tokens=0,
|
||||
reasoning_tokens=0,
|
||||
api_calls=0,
|
||||
),
|
||||
)
|
||||
|
||||
assert _usage_rows(db, "sess-parent") == []
|
||||
|
||||
|
||||
def test_noop_when_parent_has_no_session_db():
|
||||
background_review._record_review_usage_to_parent(_FakeParent(None), _usage())
|
||||
|
||||
|
||||
def test_noop_when_parent_has_no_session_id(db):
|
||||
db.create_session("sess-parent", source="cli")
|
||||
|
||||
background_review._record_review_usage_to_parent(
|
||||
_FakeParent(db, session_id=""), _usage()
|
||||
)
|
||||
|
||||
assert _usage_rows(db, "sess-parent") == []
|
||||
|
||||
|
||||
def test_survives_accounting_failure():
|
||||
class _BoomDB:
|
||||
def record_auxiliary_usage(self, *args, **kwargs):
|
||||
raise RuntimeError("simulated accounting failure")
|
||||
|
||||
background_review._record_review_usage_to_parent(_FakeParent(_BoomDB()), _usage())
|
||||
|
||||
|
||||
def test_classify_review_result():
|
||||
assert background_review._classify_review_result([]) == "none"
|
||||
assert background_review._classify_review_result(["Memory updated"]) == "memory"
|
||||
assert background_review._classify_review_result(["Skill 'x' patched"]) == "skill"
|
||||
assert (
|
||||
background_review._classify_review_result(
|
||||
["Memory updated", "Skill 'x' created"]
|
||||
)
|
||||
== "skill+memory"
|
||||
)
|
||||
# Prefix-based — free-text "skill"/"memory" elsewhere must not misclassify.
|
||||
assert (
|
||||
background_review._classify_review_result(
|
||||
["Skipped: no skill worth saving"]
|
||||
)
|
||||
== "none"
|
||||
)
|
||||
assert (
|
||||
background_review._classify_review_result(
|
||||
["📝 Skill 'deploy' patched: \"a\" → \"b\""]
|
||||
)
|
||||
== "skill"
|
||||
)
|
||||
assert (
|
||||
background_review._classify_review_result(["User profile ➕ prefers terse"])
|
||||
== "memory"
|
||||
)
|
||||
|
||||
|
||||
def test_enabled_config_failure_logs_warning(caplog):
|
||||
with patch(
|
||||
"hermes_cli.config.load_config_readonly",
|
||||
side_effect=RuntimeError("boom"),
|
||||
), caplog.at_level(logging.WARNING, logger="agent.background_review"):
|
||||
assert background_review.is_background_review_enabled() is True
|
||||
assert any(
|
||||
"fail-open" in r.message.lower() or "leaving automatic" in r.message.lower()
|
||||
for r in caplog.records
|
||||
)
|
||||
|
||||
|
||||
def test_spawn_reuses_provided_task_cfg_without_rereading():
|
||||
"""One config load per spawn — the worker shares task_cfg."""
|
||||
task = {"enabled": True}
|
||||
agent = type("A", (), {})()
|
||||
with patch(
|
||||
"hermes_cli.config.load_config_readonly",
|
||||
side_effect=AssertionError("config must not be re-read when task_cfg is passed"),
|
||||
):
|
||||
_target, prompt = background_review.spawn_background_review_thread(
|
||||
agent,
|
||||
messages_snapshot=[{"role": "user", "content": "hi"}],
|
||||
review_skills=True,
|
||||
task_cfg=task,
|
||||
)
|
||||
assert prompt # built-in skill-review prompt selected
|
||||
assert callable(_target)
|
||||
|
||||
def test_log_review_completion_emits_thread_tag(caplog):
|
||||
with caplog.at_level(logging.INFO, logger="agent.background_review"):
|
||||
background_review._log_review_completion(
|
||||
_usage(api_calls=8, input_tokens=53000, output_tokens=400),
|
||||
"skill",
|
||||
)
|
||||
assert any(
|
||||
"thread=bg-review" in r.message
|
||||
and "calls=8" in r.message
|
||||
and "result=skill" in r.message
|
||||
for r in caplog.records
|
||||
)
|
||||
Reference in New Issue
Block a user