Switch the LLM provider mid-session (the original's /provider). Rebuilds the provider + tool registry but keeps the conversation. Idle-only.
(self, request_id: object, name: object)
| 574 | logger.debug("[agent-server] knowledge record failed", exc_info=True) |
| 575 | |
| 576 | def _do_set_provider(self, request_id: object, name: object) -> None: |
| 577 | """Switch the LLM provider mid-session (the original's /provider). Rebuilds |
| 578 | the provider + tool registry but keeps the conversation. Idle-only.""" |
| 579 | with self._lock: |
| 580 | active = self._current_abort is not None |
| 581 | if active: |
| 582 | self._reply(request_id, {"ok": False, "error": "cannot switch provider during an active turn"}) |
| 583 | return |
| 584 | try: |
| 585 | if not isinstance(name, str) or not name: |
| 586 | self._reply(request_id, {"ok": False, "error": "missing provider"}) |
| 587 | return |
| 588 | from src.config import get_provider_config |
| 589 | from src.providers import get_provider_class, provider_requires_api_key, resolve_api_key |
| 590 | from src.tool_system.defaults import build_default_registry |
| 591 | |
| 592 | provider_cfg = get_provider_config(name) |
| 593 | api_key = resolve_api_key(name, provider_cfg) |
| 594 | if not api_key and provider_requires_api_key(name): |
| 595 | self._reply(request_id, {"ok": False, "error": f"provider '{name}' is not configured (no API key)"}) |
| 596 | return |
| 597 | provider_cls = get_provider_class(name) |
| 598 | model = provider_cfg.get("default_model") |
| 599 | provider = provider_cls(api_key=api_key, base_url=provider_cfg.get("base_url"), model=model) |
| 600 | registry = build_default_registry(provider=provider) |
| 601 | cfg = self.config |
| 602 | if cfg.allowed_tools: |
| 603 | allow = {n.lower() for n in cfg.allowed_tools} |
| 604 | _filter_registry(registry, keep=lambda n: n.lower() in allow) |
| 605 | if cfg.disallowed_tools: |
| 606 | deny = {n.lower() for n in cfg.disallowed_tools} |
| 607 | _filter_registry(registry, keep=lambda n: n.lower() not in deny) |
| 608 | if self._mcp_runtime is not None: # keep MCP tools across the switch |
| 609 | for mtool in self._mcp_runtime.tools: |
| 610 | try: |
| 611 | registry.register(mtool) |
| 612 | except Exception: # noqa: BLE001 |
| 613 | pass |
| 614 | self.provider = provider |
| 615 | self.provider_name = name |
| 616 | cfg.provider_name = name |
| 617 | cfg.model = model |
| 618 | self.tool_registry = registry |
| 619 | self._reply(request_id, {"ok": True, "provider": name, "model": model or ""}) |
| 620 | except Exception as exc: # noqa: BLE001 |
| 621 | logger.exception("[agent-server] set_provider failed") |
| 622 | self._reply(request_id, {"ok": False, "error": str(exc)}) |
| 623 | |
| 624 | def _compose_with_plan(self, base: Any) -> Any: |
| 625 | """Append the active /plan as a system-prompt section so the agent follows |
no test coverage detected