Newer
Older
navi-1 / navi / tools / switch_profile.py
"""Built-in tool for switching the active agent profile mid-session."""

from navi.tools._internal.base import Tool, ToolContext, ToolResult, current_event_sink, current_session_id


class SwitchProfileTool(Tool):
    name = "switch_profile"
    description = (
        "Switch this session to a different agent profile. "
        "Call this on your own judgment when the task fits another profile's specialization — "
        "do not ask the user for permission first. "
        "The new profile (tools + system prompt) becomes active from the NEXT user message. "
        "After switching, briefly inform the user: which profile is now active and why you switched."
    )
    parameters = {
        "type": "object",
        "properties": {
            "profile_id": {
                "type": "string",
                "description": "ID of the profile to switch to.",
            }
        },
        "required": ["profile_id"],
    }

    def __init__(self, session_store, profile_registry) -> None:
        self._sessions = session_store
        self._profiles = profile_registry

    async def execute(self, params: dict, ctx: ToolContext | None = None) -> ToolResult:
        profile_id = (params.get("profile_id") or "").strip()
        available = ", ".join(p.id for p in self._profiles.all())

        try:
            profile = self._profiles.get(profile_id)
        except Exception:
            return ToolResult(
                success=False,
                output="",
                error=f"Profile '{profile_id}' not found. Available: {available}",
            )

        if getattr(profile, "is_subagent_only", False):
            return ToolResult(
                success=False,
                output=f"Profile '{profile_id}' is a sub-agent specialist and cannot be switched to directly. "
                       f"Use spawn_agent(profile_id='{profile_id}') to delegate tasks to it.",
                error="subagent_only",
            )

        sid = ctx.session_id if ctx else current_session_id.get()
        if not sid:
            return ToolResult(success=False, output="", error="No active session context.")

        session = await self._sessions.get(sid)
        if session is None:
            return ToolResult(success=False, output="", error="Session not found.")

        if session.profile_id == profile_id:
            return ToolResult(
                success=True,
                output=f"Already on profile '{profile.name}' — no change.",
            )

        session.profile_id = profile_id
        await self._sessions.save(session)

        # Notify the client immediately so it can update the UI.
        sink = ctx.event_sink if ctx else current_event_sink.get()
        if sink is not None:
            from navi.core.events import ProfileSwitched
            await sink.put(ProfileSwitched(profile_id=profile_id, profile_name=profile.name))

        return ToolResult(
            success=True,
            output=(
                f"Switched to profile '{profile.name}' ({profile_id}). "
                f"Its tools and system prompt are active from the next message."
            ),
        )