"""Tests para budget efectivo de contexto e integracion del ContextEngine.""" from __future__ import annotations import asyncio import enum import sys import types import pytest if not hasattr(enum, "StrEnum"): class _CompatStrEnum(str, enum.Enum): pass enum.StrEnum = _CompatStrEnum if "anthropic" not in sys.modules: anthropic_stub = types.ModuleType("anthropic") class _AsyncAnthropic: pass anthropic_stub.AsyncAnthropic = _AsyncAnthropic sys.modules["anthropic"] = anthropic_stub if "openai" not in sys.modules: openai_stub = types.ModuleType("openai") class _AsyncOpenAI: pass openai_stub.AsyncOpenAI = _AsyncOpenAI sys.modules["openai"] = openai_stub from src.config import Settings, settings from src.context.engine import ContextEngine from src.models.agent import AgentProfile from src.models.artifacts import ArtifactSummary from src.models.session import SessionState from src.orchestrator.engine import OrchestratorEngine class TestSettingsBudget: def test_effective_budget_uses_explicit_override(self): cfg = Settings( context_max_tokens=120_000, model_context_window=200_000, model_max_output_tokens=8_192, _env_file=None, ) assert cfg.effective_context_budget == 120_000 def test_effective_budget_uses_model_window_when_no_override(self): cfg = Settings( context_max_tokens=0, model_context_window=200_000, model_max_output_tokens=8_000, context_reserve_ratio=0.10, _env_file=None, ) assert cfg.reserve_tokens == 20_000 assert cfg.effective_context_budget == 172_000 assert cfg.effective_compaction_threshold == 137_600 class TestContextEngine: def test_build_context_keeps_task_history_and_current_task(self): session = SessionState( immutable_rules=["No romper el proyecto"], project_profile={"name": "demo"}, task_history=[ { "task_id": "prev1", "objective": "Crear banner", "status": "completed", "summary": "User: Crear banner → Agent: Banner creado", "facts": ["Section: home"], "key_data": {"sections": ["home"]}, "tools_used": ["create_module"], } ], ) session.begin_task("Actualizar hero") agent = AgentProfile( role="acai", name="Acai", system_prompt="Haz el trabajo.", ) package = asyncio.run(ContextEngine().build_context(session=session, agent=agent)) assert "# Session History" in package.system_prompt assert "# Current Task" in package.system_prompt def test_build_context_includes_artifact_memory_with_task_state_agents(self): session = SessionState( immutable_rules=["No romper el proyecto"], project_profile={"name": "demo"}, ) task = session.begin_task("Revisar modulo") agent = AgentProfile( role="acai", name="Acai", system_prompt="Haz el trabajo.", context_sections=[ "immutable_rules", "project_profile", "task_state", ], ) artifacts = [ ArtifactSummary( artifact_id="art-1", session_id=session.session_id, task_id=task.task_id, artifact_type="code", title="Output of read_file", summary="Resumen del archivo", facts=["Status: ok"], source_tool="read_file", char_count=120, ) ] package = asyncio.run( ContextEngine().build_context( session=session, agent=agent, artifacts=artifacts, ) ) assert "## Artifacts" in package.system_prompt assert "Resumen del archivo" in package.system_prompt class TestTaskHistoryTrim: def test_trim_respects_entry_limit_and_token_budget(self, monkeypatch): monkeypatch.setattr(settings, "task_history_max_entries", 3) monkeypatch.setattr(settings, "task_history_max_tokens", 60) history = [ {"objective": "old", "summary": "muy antiguo", "facts": [], "tools_used": [], "key_data": {}}, { "objective": "medio", "summary": "contenido " * 20, "facts": [], "tools_used": [], "key_data": {}, }, {"objective": "nuevo", "summary": "corto", "facts": [], "tools_used": [], "key_data": {}}, {"objective": "final", "summary": "ultimo", "facts": [], "tools_used": [], "key_data": {}}, ] trimmed = OrchestratorEngine._trim_task_history(history) assert len(trimmed) <= 3 assert trimmed[-1]["objective"] == "final" assert all(entry["objective"] != "old" for entry in trimmed)