# retoor import json from devplacepy.services.devii.agents.runner import AgentRunner from devplacepy.services.devii.cost.tracker import CostTracker, Pricing from tests.conftest import run_async class _Ledger: def __init__(self): self.spent = 0.0 def spent_24h(self, owner_kind, owner_id): return self.spent class _Settings: daily_limit_usd = 1.0 plan_required = True verify_required = True class _StubSession: def __init__(self): self.cost = CostTracker(pricing=Pricing()) self.settings = _Settings() self._ledger = _Ledger() self._llm = None self.dispatcher = None self.owner_kind = "user" self.owner_id = "owner-1" def _runner(**overrides): args = { "session": _StubSession(), "uid": "run-1", "label": "run", "category_key": "research", "effort": "standard", "task": "Compare static site generators.", "output_format": "A markdown table.", "tools": [], "task_scope": False, } args.update(overrides) return AgentRunner(**args) def _ask_call(call_id, question): return { "id": call_id, "function": {"name": "ask_user", "arguments": json.dumps({"question": question})}, } def test_system_prompt_carries_contract_and_behavior(): runner = _runner(behavior_text="Be terse.") prompt = runner.messages[0]["content"] assert "no browser" in prompt assert "ask_user" in prompt assert "Be terse." in prompt assert "Compare static site generators." in prompt assert "A markdown table." in prompt def test_pending_ask_found_and_answered(): runner = _runner() runner.messages.append( {"role": "assistant", "content": "", "tool_calls": [_ask_call("c1", "Budget?")]} ) runner.messages.append( { "role": "tool", "tool_call_id": "c1", "name": "ask_user", "content": json.dumps({"status": "paused_for_user"}), } ) assert runner._pending_ask() == ("c1", "Budget?") runner.apply_answer("zero") assert runner._pending_ask() is None assert runner.messages[-1]["content"] == "The user answered: zero" def test_quota_spent_short_circuits_without_llm(): session = _StubSession() session._ledger.spent = 5.0 runner = _runner(session=session) outcome, text = run_async(runner.run_once()) assert outcome == "error" assert "quota" in text def test_trace_tracks_progress_and_streak(): runner = _runner() runner._trace("call", "fetch_url", "example.com") assert runner.tool_calls == 1 runner._trace("err", "fetch_url", "boom") assert runner.error_streak == 1 runner._trace("ok", "fetch_url", "") assert runner.error_streak == 0 assert "fetch_url" in runner.progress_note def test_task_scope_wraps_the_loop(monkeypatch): from devplacepy.services.devii.tasks.context import in_task_run seen = {} async def fake_loop(**kwargs): seen["scoped"] = in_task_run() return "done" monkeypatch.setattr( "devplacepy.services.devii.agents.runner.react_loop", fake_loop ) run_async(_runner(task_scope=True).run_once()) assert seen == {"scoped": True} run_async(_runner(task_scope=False).run_once()) assert seen == {"scoped": False} def test_counts_and_usage_start_at_zero(): runner = _runner() assert runner.counts() == (0, 0, 0.0) assert runner.usage() == { "prompt_tokens": 0, "completion_tokens": 0, "cache_hit_tokens": 0, "cache_miss_tokens": 0, }