"""What the model sees: tool lists for a profile, backend lookup, context window."""
from typing import TYPE_CHECKING
import structlog
from navi.config import settings
from navi.llm.base import LLMBackend
from navi.tools._internal.base import (
Tool,
)
from ..tool_utils import build_tool_list
if TYPE_CHECKING:
from navi.profiles.base import AgentProfile
log = structlog.get_logger()
class AgentContextMixin:
def _tool_list(
self,
scope: "navi.profiles.base.ToolScopeConfig", # noqa: F821
) -> list[Tool]:
return build_tool_list(scope.native, scope.mcp, self._tools, self._mcp_manager)
def _tool_list_for_profile(self, profile_id: str) -> "list[Tool] | None":
"""Resolve another profile's tools by id; None when it is unknown.
Used mid-batch right after a switch_profile call, where the running
turn's own ``profile`` binding is still the one it started with.
"""
try:
profile = self._profiles.get(profile_id)
except Exception:
log.warning("agent.switch_profile_unknown", profile_id=profile_id)
return None
return self._tool_list(profile.get_agent_tools())
def _get_backend(self, backend_key: str) -> LLMBackend:
return self._backends.get(backend_key)
def _window_for(self, profile: "AgentProfile | None") -> int:
"""Context window this profile's model is accounted against.
One global OLLAMA_NUM_CTX used to stand in for every model — a local
65k model and a cloud model fed 115k prompts were held to the same
number. A profile may now declare its own ``context_window``.
The route's active entry wins over both: when the cloud is down and a
48k local model is answering, compressing against a cloud-sized window
would send it prompts it cannot hold.
"""
try:
backend = self._get_backend(getattr(profile, "llm_backend", "ollama"))
except KeyError:
backend = None
window_for_route = getattr(backend, "active_window", None)
if callable(window_for_route):
active = window_for_route(getattr(profile, "llm_route", None))
if active:
return active
return (
getattr(profile, "context_window", None)
or settings.ollama_num_ctx
)