Newer
Older
navi-1 / navi / core / agent / context.py
"""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
        )