diff --git a/backend/apps/agents/agents.py b/backend/apps/agents/agents.py index abe8c6fe..87fcc367 100644 --- a/backend/apps/agents/agents.py +++ b/backend/apps/agents/agents.py @@ -29,6 +29,8 @@ from backend.apps.agents.agent_utils.create_sdk_hooks import create_sdk_hooks from backend.apps.agents.agent_utils.build_search_text import build_search_text from backend.apps.agents.COMMS_MANAGER.COMMS_MANAGER import COMMS_MANAGER from backend.apps.settings.settings import load_settings +from backend.apps.modes.modes import get_mode_by_id +from backend.apps.modes.Mode import Mode from backend.core.llm.resolve_sdk_env import resolve_sdk_env from backend.ports import NINE_ROUTER_PORT from claude_agent_sdk import ClaudeAgentOptions @@ -102,7 +104,12 @@ class LaunchBody(BaseModel): @agents.router.post("/launch") async def launch(body: LaunchBody) -> dict: + settings = load_settings() + mode_def: Optional[Mode] = await get_mode_by_id(body.mode) + system_prompt = compose_system_prompt( + global_default=settings.default_system_prompt, + mode_prompt=mode_def.system_prompt if mode_def else None, session_prompt=body.system_prompt or None, ) agent: Agent = Agent( @@ -119,9 +126,13 @@ async def launch(body: LaunchBody) -> dict: mcp_servers: Dict[str, McpServerConfig] = toolkit.collect_mcp_servers() allowed_tools, disallowed_tools = toolkit.collect_tool_permissions() + if mode_def and mode_def.tools is not None: + mode_tool_set: set[str] = set[str](mode_def.tools) + disallowed_tools += [t for t in allowed_tools if t not in mode_tool_set] + allowed_tools = [t for t in allowed_tools if t in mode_tool_set] + can_use_tool, pre_tool_hook, post_tool_hook = create_sdk_hooks(agent) - settings = load_settings() env = resolve_sdk_env( api_key=settings.anthropic_api_key, nine_router_port=NINE_ROUTER_PORT if not settings.anthropic_api_key else None, @@ -132,6 +143,7 @@ async def launch(body: LaunchBody) -> dict: model=body.model, system_prompt=system_prompt, max_turns=body.max_turns, + cwd=mode_def.default_folder if mode_def and mode_def.default_folder else None, mcp_servers=mcp_servers if mcp_servers else None, allowed_tools=allowed_tools, disallowed_tools=disallowed_tools, @@ -192,10 +204,36 @@ class MessageBody(BaseModel): @agents.router.post("/SESSIONS/{session_id}/message") async def send_message(session_id: str, body: MessageBody) -> dict: agent: Agent = get_agent(session_id) - if body.mode and body.mode != agent.mode: - agent.mode = body.mode - if body.model and body.model != agent.model: - agent.model = body.model + mode_changed: bool = bool(body.mode and body.mode != agent.mode) + model_changed: bool = bool(body.model and body.model != agent.model) + + if mode_changed: + agent.mode = body.mode # type: ignore[assignment] + if model_changed: + agent.model = body.model # type: ignore[assignment] + + if mode_changed or model_changed: + settings = load_settings() + mode_def: Optional[Mode] = await get_mode_by_id(agent.mode) + + system_prompt = compose_system_prompt( + global_default=settings.default_system_prompt, + mode_prompt=mode_def.system_prompt if mode_def else None, + session_prompt=agent.config.system_prompt, + ) + + allowed_tools, disallowed_tools = agent.toolkit.collect_tool_permissions() + if mode_def and mode_def.tools is not None: + mode_tool_set = set(mode_def.tools) + disallowed_tools += [t for t in allowed_tools if t not in mode_tool_set] + allowed_tools = [t for t in allowed_tools if t in mode_tool_set] + + agent.config.system_prompt = system_prompt + agent.config.model = agent.model + agent.config.allowed_tools = allowed_tools + agent.config.disallowed_tools = disallowed_tools + if mode_def and mode_def.default_folder: + agent.config.cwd = mode_def.default_folder msg: UserMessage = UserMessage( content=body.prompt, diff --git a/backend/apps/modes/modes.py b/backend/apps/modes/modes.py index 11058d06..099e21b4 100644 --- a/backend/apps/modes/modes.py +++ b/backend/apps/modes/modes.py @@ -102,3 +102,8 @@ async def delete_mode(mode_id: str): raise HTTPException(status_code=403, detail="Cannot delete built-in modes") MODE_STORE.delete(mode_id) return {"ok": True} + + +@modes.router.post("/get_mode_by_id") +async def get_mode_by_id(mode_id: str) -> Optional[Mode]: + return MODE_STORE.load_or_none(item_id=mode_id) \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 81725e7e..823be5e8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,14 +7,15 @@ import uvicorn from typeguard import typechecked from backend.ports import get_backend_port from backend.config.Apps import MainApp -from backend.apps.health.health import health from backend.apps.agents.agents import agents -from backend.apps.settings.settings import settings from backend.apps.dashboards.dashboards import dashboards +from backend.apps.health.health import health +from backend.apps.settings.settings import settings +from backend.apps.modes.modes import modes from fastapi.middleware.cors import CORSMiddleware main_app = MainApp([ - health, agents, settings, dashboards + health, agents, settings, dashboards, modes ]) app = main_app.app