#!/usr/bin/env python3
from __future__ import annotations

import argparse
import base64
import contextlib
import getpass
import hashlib
import html as html_lib
import importlib.util
import json
import math
import mimetypes
import os
import queue
import re
import select
import signal
import shlex
import shutil
import socket
import subprocess
import sys
import threading
import time
import unicodedata
import urllib.error
import urllib.parse
import urllib.request
import uuid
from email.utils import parsedate_to_datetime
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path, PureWindowsPath
from typing import Any, Callable, Iterable

from claude_any_support.observability import EventBus, render_events_html
from claude_any_support.transcript_filter import (
    is_claude_code_transcript_event,
)

try:
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")
except Exception:
    pass

HOME = Path.home()


def platform_path(value: str | os.PathLike[str]) -> Any:
    if os.name == "nt" and sys.platform != "win32":
        return PureWindowsPath(value)
    return Path(value)


def windows_appdata_root() -> Path:
    for env_name in ("APPDATA", "LOCALAPPDATA"):
        raw = os.environ.get(env_name)
        if raw:
            return platform_path(raw)
    return HOME / "AppData" / "Roaming"


def windows_local_appdata_root() -> Path:
    raw = os.environ.get("LOCALAPPDATA")
    if raw:
        return platform_path(raw)
    return HOME / "AppData" / "Local"


def platform_config_dir(app_name: str) -> Path:
    if os.name == "nt":
        return windows_appdata_root() / app_name
    return HOME / ".config" / app_name


def claude_any_user_bin_dir() -> Path:
    if os.name == "nt":
        return windows_local_appdata_root() / "claude-any" / "bin"
    return HOME / ".local" / "bin"


def path_with_claude_any_user_dirs(env: dict[str, str]) -> str:
    dirs = [claude_any_user_bin_dir()]
    if os.name == "nt":
        appdata = env.get("APPDATA") or os.environ.get("APPDATA")
        if appdata:
            dirs.append(platform_path(appdata) / "npm")
        local_appdata = env.get("LOCALAPPDATA") or os.environ.get("LOCALAPPDATA")
        if local_appdata:
            dirs.append(platform_path(local_appdata) / "Programs" / "nodejs")
    existing = env.get("PATH", "")
    prefix = os.pathsep.join(str(path) for path in dirs if str(path))
    return prefix + (os.pathsep + existing if existing else "")


def default_router_port() -> int:
    configured = str(os.environ.get("CLAUDE_ANY_ROUTER_PORT") or "").strip()
    if configured:
        try:
            port = int(configured)
            if 1 <= port <= 65535:
                return port
        except ValueError:
            pass
    base = 8799
    try:
        getuid = getattr(os, "getuid")
    except AttributeError:
        getuid = None
    if callable(getuid):
        try:
            return base + (int(getuid()) % 1000)
        except Exception:
            pass
    seed = f"{getpass.getuser()}|{HOME}"
    digest = hashlib.sha256(seed.encode("utf-8", errors="replace")).hexdigest()
    return base + (int(digest[:8], 16) % 1000)


CONFIG_DIR = Path(os.environ.get("CLAUDE_ANY_CONFIG_DIR") or platform_config_dir("claude-any"))
CONFIG_PATH = CONFIG_DIR / "config.json"
LOG_PATH = CONFIG_DIR / "router.log"
LOG_LEVEL_PATH = CONFIG_DIR / "log-level"
REQUEST_DUMP_PATH = CONFIG_DIR / "requests.jsonl"
RESPONSE_DUMP_PATH = CONFIG_DIR / "responses.jsonl"
SSE_TRACE_PATH = CONFIG_DIR / "router-sse-trace.jsonl"
SSE_LAST_PATH = CONFIG_DIR / "router-last-sse.json"
TOOL_CALL_LOG_PATH = CONFIG_DIR / "tool-calls.jsonl"
RATE_LIMIT_STATE_PATH = CONFIG_DIR / "rate-limit-state.json"
ROUTER_ACTIVITY_PATH = CONFIG_DIR / "router-activity.json"
CONTEXT_USAGE_PATH = CONFIG_DIR / "context-usage.json"
OLLAMA_MODEL_CATALOG_PATH = CONFIG_DIR / "ollama-model-catalog.json"
CHAT_MESSAGES_PATH = CONFIG_DIR / "chat-messages.jsonl"
CHAT_FILES_DIR = CONFIG_DIR / "chat-files"
MENU_KEY_DEBUG_PATH = CONFIG_DIR / "ca-key-debug.log"
PLAN_ARTIFACTS_DIR = CONFIG_DIR / "plan-artifacts"
PID_PATH = CONFIG_DIR / "router.pid"
ROUTER_CLIENTS_DIR = CONFIG_DIR / "router-clients"
MODEL_LIST_CACHE_PATH = CONFIG_DIR / "model-list-cache.json"
MODEL_REGISTRY_PATH = CONFIG_DIR / "model-registry.json"
LAUNCH_STATE_PATH = CONFIG_DIR / "launch-state.json"
WEB_TOOLS_MCP_CONFIG = CONFIG_DIR / "web-tools-mcp.json"
DUCKDUCKGO_MCP_CONFIG = CONFIG_DIR / "duckduckgo-mcp.json"
CHANNEL_MCP_CONFIG = CONFIG_DIR / "channel-mcp.json"
NATIVE_MCP_CONFIG = CONFIG_DIR / "native-mcp.json"
CHANNEL_MCP_CURSOR_PATH = CONFIG_DIR / "channel-mcp-cursor.json"
CHANNEL_LLM_CURSOR_PATH = CONFIG_DIR / "channel-llm-cursor.json"
CHANNEL_LLM_SUMMARY_QUEUE_PATH = CONFIG_DIR / "channel-llm-summary-queue.jsonl"
CHANNEL_LLM_SUMMARY_CURSOR_PATH = CONFIG_DIR / "channel-llm-summary-cursor.json"
CHANNEL_PROBE_CACHE_PATH = CONFIG_DIR / "channel-probe-cache.json"
MCP_PROXY_CONFIG = CONFIG_DIR / "mcp-proxy.json"
ROUTER_HOST = os.environ.get("CLAUDE_ANY_ROUTER_CLIENT_HOST", "127.0.0.1").strip() or "127.0.0.1"
ROUTER_PORT = default_router_port()
ROUTER_BASE = f"http://{ROUTER_HOST}:{ROUTER_PORT}"
CLAUDE_GATEWAY_CACHE = HOME / ".claude" / "cache" / "gateway-models.json"
CLAUDE_SETTINGS_PATH = HOME / ".claude" / "settings.json"
CLAUDE_COMMANDS_DIR = HOME / ".claude" / "commands"
CLAUDE_ANY_STATUSLINE_PATH = claude_any_user_bin_dir() / "claude-any-statusline.py"
NCP_ENV = platform_config_dir("nvd-claude-proxy") / ".env"
NCP_LOG = platform_config_dir("nvd-claude-proxy") / "proxy.log"
MODEL_CACHE_TTL_SECONDS = 300
OLLAMA_MODEL_CATALOG_URL = "https://ollama.com/api/tags"
OLLAMA_MODEL_CATALOG_TTL_SECONDS = 24 * 60 * 60
ANTHROPIC_MODEL_DOCS_URL = "https://docs.anthropic.com/en/docs/about-claude/models/overview"
ANTHROPIC_MODEL_DOCS_URLS = (
    ANTHROPIC_MODEL_DOCS_URL,
    "https://platform.claude.com/docs/en/about-claude/models/overview",
)
ANTHROPIC_PUBLIC_MODEL_FALLBACK_IDS: tuple[str, ...] = (
    "claude-fable-5",
    "claude-opus-4-8",
    "claude-sonnet-4-6",
    "claude-haiku-4-5-20251001",
    "claude-haiku-4-5",
)
ANTHROPIC_PUBLIC_MODEL_DEFAULT_IDS: tuple[str, ...] = ANTHROPIC_PUBLIC_MODEL_FALLBACK_IDS
ANTHROPIC_LIMITED_ACCESS_MODEL_IDS: tuple[str, ...] = (
    "claude-mythos-5",
    "claude-mythos-preview",
)
OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen"
OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go"
FIREWORKS_INFERENCE_BASE_URL = "https://api.fireworks.ai/inference"
FIREWORKS_API_BASE_URL = "https://api.fireworks.ai"
FIREWORKS_DEFAULT_ACCOUNT_ID = "fireworks"
NCP_PYPI_PACKAGE = "nvd-claude-proxy"

PROVIDER_ALIASES = {
    "anthropic": "anthropic",
    "claude": "anthropic",
    "claude-native": "anthropic",
    "native": "anthropic",
    "claude-code": "anthropic",
    "ollama": "ollama",
    "ollama-cloud": "ollama-cloud",
    "cloud-ollama": "ollama-cloud",
    "deepseek": "deepseek",
    "deepseek.com": "deepseek",
    "deepseek-com": "deepseek",
    "deepseek-api": "deepseek",
    "ds": "deepseek",
    "opencode": "opencode",
    "opencode.ai": "opencode",
    "opencode-ai": "opencode",
    "opencode-zen": "opencode",
    "zen": "opencode",
    "opencode-go": "opencode-go",
    "opencode.go": "opencode-go",
    "opencode_go": "opencode-go",
    "opencodego": "opencode-go",
    "vllm": "vllm",
    "vllm-local": "vllm",
    "lm-studio": "lm-studio",
    "lmstudio": "lm-studio",
    "lm": "lm-studio",
    "nvidia": "nvidia-hosted",
    "nvidia-hosted": "nvidia-hosted",
    "hosted-nvidia": "nvidia-hosted",
    "nim": "self-hosted-nim",
    "self-hosted-nim": "self-hosted-nim",
    "self-nim": "self-hosted-nim",
    "openrouter": "openrouter",
    "open-router": "openrouter",
    "openrouter.ai": "openrouter",
    "or": "openrouter",
    "fireworks": "fireworks",
    "fireworks.ai": "fireworks",
    "fireworks-ai": "fireworks",
    "fw": "fireworks",
}

PROVIDER_LABELS = {
    "anthropic": "Claude Native",
    "ollama": "Ollama",
    "ollama-cloud": "Ollama Cloud",
    "deepseek": "DeepSeek.com",
    "opencode": "OpenCode Zen",
    "opencode-go": "OpenCode Go",
    "vllm": "vLLM",
    "lm-studio": "LM Studio",
    "nvidia-hosted": "Nvidia Hosted",
    "self-hosted-nim": "Self Hosted NIM",
    "openrouter": "OpenRouter",
    "fireworks": "Fireworks.ai",
}

ANTHROPIC_NATIVE_PROVIDER_CHOICE = "anthropic:native"
ANTHROPIC_ROUTED_PROVIDER_CHOICE = "anthropic:routed"

OPENCODE_PROVIDER_NAMES = ("opencode", "opencode-go")
OPENCODE_ENDPOINT_ALIASES = {
    "messages": "anthropic-messages",
    "anthropic": "anthropic-messages",
    "anthropic-messages": "anthropic-messages",
    "chat": "openai-chat",
    "openai-chat": "openai-chat",
    "chat-completions": "openai-chat",
    "responses": "openai-responses",
    "openai-responses": "openai-responses",
    "gemini": "google-generative",
    "google": "google-generative",
    "google-generative": "google-generative",
}
OFFICIAL_CHANNEL_PLUGINS = {
    "telegram": "plugin:telegram@claude-plugins-official",
    "discord": "plugin:discord@claude-plugins-official",
    "imessage": "plugin:imessage@claude-plugins-official",
    "fakechat": "plugin:fakechat@claude-plugins-official",
}
APP_NAME = "Claude Any"
VERSION = "0.1.107-nightly.20260613-042306.3e07ff4"
DEFAULT_UPSTREAM_USER_AGENT = "claude-cli"


def upstream_user_agent() -> str:
    """Return the User-Agent used for upstream provider HTTP calls.

    Some provider gateways/WAFs treat Python's default urllib identity as a
    non-CLI browser signature. claude-any is acting as the Claude CLI transport
    here, so keep that identity explicit and generic across providers.
    """
    configured = str(os.environ.get("CLAUDE_ANY_UPSTREAM_USER_AGENT") or "").strip()
    return configured or DEFAULT_UPSTREAM_USER_AGENT


def with_upstream_user_agent(headers: dict[str, str] | None = None) -> dict[str, str]:
    out = dict(headers or {})
    if not any(str(k).lower() == "user-agent" for k in out):
        out["user-agent"] = upstream_user_agent()
    return out


IP_FAMILY_ALIASES = {
    "": "auto",
    "auto": "auto",
    "default": "auto",
    "system": "auto",
    "any": "auto",
    "4": "ipv4",
    "v4": "ipv4",
    "ipv4": "ipv4",
    "inet": "ipv4",
    "6": "ipv6",
    "v6": "ipv6",
    "ipv6": "ipv6",
    "inet6": "ipv6",
    "prefer4": "ipv4-preferred",
    "prefer-v4": "ipv4-preferred",
    "preferred4": "ipv4-preferred",
    "ipv4-preferred": "ipv4-preferred",
    "ipv4_preferred": "ipv4-preferred",
    "prefer-ipv4": "ipv4-preferred",
    "prefer6": "ipv6-preferred",
    "prefer-v6": "ipv6-preferred",
    "preferred6": "ipv6-preferred",
    "ipv6-preferred": "ipv6-preferred",
    "ipv6_preferred": "ipv6-preferred",
    "prefer-ipv6": "ipv6-preferred",
}
IP_FAMILY_CHOICES = ("auto", "ipv4", "ipv6", "ipv4-preferred", "ipv6-preferred")


def normalize_ip_family(value: Any, default: str = "auto") -> str:
    text = str(value if value is not None else default).strip().lower().replace("_", "-")
    family = IP_FAMILY_ALIASES.get(text)
    if family:
        return family
    raise SystemExit(f"ip_family must be one of: {', '.join(IP_FAMILY_CHOICES)}")


def default_provider_ip_family(provider: str) -> str:
    return "ipv6-preferred" if provider in OPENCODE_PROVIDER_NAMES else "auto"


def provider_ip_family(provider: str | None, pcfg: dict[str, Any] | None) -> str:
    if not provider or not isinstance(pcfg, dict):
        return "auto"
    return normalize_ip_family(pcfg.get("ip_family"), default_provider_ip_family(provider))


def _ip_family_sort_key(family: int, preferred: int) -> tuple[int, int]:
    return (0 if family == preferred else 1, family)


@contextlib.contextmanager
def socket_getaddrinfo_ip_family_policy(ip_family: str) -> Iterable[None]:
    policy = normalize_ip_family(ip_family)
    if policy == "auto":
        yield
        return
    strict = policy in ("ipv4", "ipv6")
    desired = socket.AF_INET6 if policy in ("ipv6", "ipv6-preferred") else socket.AF_INET
    with _IP_FAMILY_LOCK:
        original_getaddrinfo = socket.getaddrinfo

        def filtered_getaddrinfo(host: Any, port: Any, family: int = 0, type: int = 0, proto: int = 0, flags: int = 0) -> Any:
            infos = original_getaddrinfo(host, port, family, type, proto, flags)
            matches = [info for info in infos if info and info[0] == desired]
            if strict:
                if not matches:
                    raise socket.gaierror(socket.EAI_NONAME, f"no {policy} address for {host}")
                return matches
            others = [info for info in infos if not info or info[0] != desired]
            return sorted(matches, key=lambda info: _ip_family_sort_key(info[0], desired)) + others

        socket.getaddrinfo = filtered_getaddrinfo
        try:
            yield
        finally:
            socket.getaddrinfo = original_getaddrinfo


def provider_urlopen(
    req: urllib.request.Request,
    timeout: float,
    provider: str | None = None,
    pcfg: dict[str, Any] | None = None,
) -> Any:
    policy = provider_ip_family(provider, pcfg)
    if policy != "auto":
        router_log("DEBUG", f"upstream_ip_family provider={provider or ''} policy={policy}")
    with socket_getaddrinfo_ip_family_policy(policy):
        return urllib.request.urlopen(req, timeout=timeout)


def ip_family_connectivity(host: str, port: int, family: int, timeout: float = 1.5) -> tuple[bool, str]:
    try:
        infos = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP)
    except Exception as exc:
        return False, f"dns:{type(exc).__name__}"
    if not infos:
        return False, "dns:no-address"
    last_error = ""
    for info in infos:
        af, socktype, proto, _canonname, sockaddr = info
        sock = socket.socket(af, socktype, proto)
        sock.settimeout(timeout)
        try:
            sock.connect(sockaddr)
            return True, "connect:ok"
        except Exception as exc:
            last_error = f"connect:{type(exc).__name__}"
        finally:
            try:
                sock.close()
            except Exception:
                pass
    return False, last_error or "connect:failed"


def provider_ip_family_probe_lines(provider: str, pcfg: dict[str, Any]) -> list[str]:
    if provider not in OPENCODE_PROVIDER_NAMES:
        return []
    base = str(pcfg.get("base_url") or default_base_url(provider) or "").strip()
    parsed = urllib.parse.urlparse(base)
    host = parsed.hostname
    if not host:
        return [f"IP family: {provider_ip_family(provider, pcfg)}; probe unavailable (base URL host missing)"]
    port = parsed.port or (443 if parsed.scheme == "https" else 80)
    ipv4_ok, ipv4_reason = ip_family_connectivity(host, port, socket.AF_INET)
    ipv6_ok, ipv6_reason = ip_family_connectivity(host, port, socket.AF_INET6)
    return [
        f"IP family: {provider_ip_family(provider, pcfg)}",
        f"IPv4 connectivity: {'OK' if ipv4_ok else 'FAIL'} ({ipv4_reason})",
        f"IPv6 connectivity: {'OK' if ipv6_ok else 'FAIL'} ({ipv6_reason})",
    ]


def claude_any_source_fingerprint() -> str:
    try:
        return hashlib.sha256(Path(__file__).read_bytes()).hexdigest()[:16]
    except Exception:
        try:
            stat = Path(__file__).stat()
            return f"{int(stat.st_mtime_ns)}-{int(stat.st_size)}"
        except Exception:
            return "unknown"


SOURCE_FINGERPRINT = claude_any_source_fingerprint()
CREDITS = "Credits: One Ciel LLC"

LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
LOG_LEVEL_NAMES = {v: k for k, v in LOG_LEVELS.items()}
LOG_LEVEL_DEFAULT = LOG_LEVELS["ERROR"]
ROUTER_LOG_MAX_BYTES = 1_000_000
REQUEST_DUMP_MAX_BYTES = 5_000_000
RESPONSE_DUMP_MAX_BYTES = 5_000_000
RESPONSE_DUMP_TEXT_LIMIT = 16_000
SSE_TRACE_MAX_BYTES = 2 * 1024 * 1024
SSE_TRACE_EVENT_LIMIT = 240
SSE_TRACE_PAYLOAD_LIMIT = 4_000
CHAT_MESSAGES_MAX_BYTES = 20_000_000
CHAT_MESSAGE_DEDUPE_SCAN_LIMIT = 500
CHAT_MESSAGE_FALLBACK_DEDUPE_TTL_SECONDS = 30.0
DEFAULT_REQUEST_TIMEOUT_MS = 300000
_LOG_LEVEL_CACHE: dict[str, Any] = {"value": None, "checked_at": 0.0, "file_mtime": 0.0}
_RATE_LIMIT_LOCK = threading.Lock()
_API_KEY_ROTATION_LOCK = threading.Lock()
_API_KEY_ROTATION_CURSOR: dict[str, int] = {}
_IP_FAMILY_LOCK = threading.RLock()
_CHAT_CONDITION = threading.Condition()
_CHAT_NEXT_ID: int | None = None
_CHANNEL_SSE_LOCK = threading.Lock()
_CHANNEL_SSE_CONNECTIONS: dict[str, dict[str, Any]] = {}
_CHANNEL_SSE_RPC_CONDITION = threading.Condition()
_CHANNEL_MCP_LOCK = threading.Lock()
_CHANNEL_MCP_SESSIONS: dict[str, dict[str, Any]] = {}
_CHANNEL_MCP_CURSOR_LOCK = threading.Lock()
_CHANNEL_MCP_CURSOR_LAST_ID: int | None = None
_CHANNEL_LLM_CURSOR_LOCK = threading.Lock()
_CHANNEL_LLM_CURSOR_LAST_ID: int | None = None
_CHANNEL_LLM_SUMMARY_LOCK = threading.Lock()
_CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID: int | None = None
_CHANNEL_LLM_DIRECT_LOCK = threading.Lock()
_CHANNEL_LLM_DIRECT_INFLIGHT: set[int] = set()
_CHANNEL_LLM_DIRECT_DELIVERED: set[int] = set()
_CHANNEL_LLM_DIRECT_QUEUE: queue.Queue[dict[str, Any]] = queue.Queue()
_CHANNEL_LLM_DIRECT_WORKERS_STARTED = 0
_CHANNEL_STDIN_WAKE_LOCK = threading.Lock()
_CHANNEL_STDIN_WAKE_DELIVERED: set[int] = set()
_NATIVE_CHANNEL_NOTIFICATION_METHOD = "notifications/claude/channel"
BUILTIN_CHANNEL_SPEC = "server:claude-any-router"
_NATIVE_ROUTER_CHANNEL_NAMES = {"claude-any-router", "mcp-claude-any-router"}
_MCP_NOTIFICATION_DEDUP_TTL_SECONDS = 3.0
_MCP_NOTIFICATION_DEDUP_LOCK = threading.Lock()
_MCP_NOTIFICATION_DEDUP_RECENT: dict[str, tuple[str, float]] = {}
EVENT_BUS = EventBus()
ADVISOR_FEEDBACK_MARKER = "CLAUDE_ANY_ADVISOR_FEEDBACK"
PLAN_GUARD_MARKER = "[claude-any-plan-guard]"
TASK_UPDATE_STATUSES = {"pending", "in_progress", "completed", "deleted"}
TASK_UPDATE_STATUS_ALIASES = {
    "active": "in_progress",
    "assigned": "in_progress",
    "current": "in_progress",
    "doing": "in_progress",
    "inprogress": "in_progress",
    "in_progress": "in_progress",
    "in-progress": "in_progress",
    "in progress": "in_progress",
    "ongoing": "in_progress",
    "processing": "in_progress",
    "running": "in_progress",
    "started": "in_progress",
    "working": "in_progress",
    "complete": "completed",
    "completed": "completed",
    "done": "completed",
    "finished": "completed",
    "resolved": "completed",
    "success": "completed",
    "closed": "completed",
    "open": "pending",
    "pending": "pending",
    "queued": "pending",
    "todo": "pending",
    "to_do": "pending",
    "to-do": "pending",
    "waiting": "pending",
    "cancel": "deleted",
    "cancelled": "deleted",
    "canceled": "deleted",
    "delete": "deleted",
    "deleted": "deleted",
    "remove": "deleted",
    "removed": "deleted",
}

# Tools Claude Code injects into every model's tool list that misfire when called
# by non-Anthropic models. See docs/notes from anthropics/claude-code issues
# #25720, #29950 and Piebald-AI/claude-code-system-prompts for tool semantics.
PLAN_MODE_SELF_TOOLS: tuple[str, ...] = ("EnterPlanMode", "ExitPlanMode")
ANTHROPIC_THINKING_BLOCK_TYPES: tuple[str, ...] = ("thinking", "redacted_thinking")


def positive_env_int(name: str, default: int) -> int:
    raw = str(os.environ.get(name) or "").strip()
    if raw:
        try:
            value = int(raw)
            if value > 0:
                return value
        except ValueError:
            pass
    return default


SUPPRESSED_THINKING_PASSBACK_MAX = positive_env_int("CLAUDE_ANY_THINKING_PASSBACK_MAX", 4096)
SUPPRESSED_THINKING_PASSBACK_CACHE: list[dict[str, Any]] = []
DEFAULT_BLOCKED_TOOLS_NON_ANTHROPIC: tuple[str, ...] = (
    "EnterWorktree",
    "ExitWorktree",
    "TeamCreate",
    "TeamDelete",
    "TeammateTool",
    "SendMessage",
    "SendMessageTool",
    "ScheduleWakeup",
    "WaitForMcpServers",
    "RemoteTrigger",
    "PushNotification",
)
NON_ANTHROPIC_COMPAT_PROMPT = (
    "You are running inside Claude Code through a non-Anthropic model provider. "
    "Do not stop after announcing what you plan to do. When the user asks you to create, edit, or run code, "
    "immediately use the available Claude Code tools such as Write, Edit, Read, and Bash as appropriate, "
    "except while Claude Code is in Plan Mode. In Plan Mode, first explore/read as needed, write or update the plan file named "
    "by the plan_mode attachment, and only then call ExitPlanMode to leave Plan Mode; when bypass permissions is active, "
    "claude-any auto-approves that plan exit, so do not ask the user separately and do not call EnterPlanMode again. "
    "then report the concrete result. If the task has several reasonable implementation parts, do all in-scope parts; "
    "do not ask the user which part to start or whether to do all unless the user explicitly requested a choice. "
    "If you decide not to use tools, provide the complete requested code or answer in the same turn. "
    "Use skills only when the user's request clearly matches that skill; never invoke keybindings-help unless the user asks about keybindings. "
    "Keep final answers concise and do not expose hidden chain-of-thought. "
    "When calling Claude Code tools, use exactly the tool schema and do not invent extra fields. "
    "Bash: command (string), description (string), timeout (integer), run_in_background (boolean). "
    "Read: file_path (string), offset (integer), limit (integer). "
    "Write: file_path (string), content (string). "
    "Edit: file_path (string), old_string (string), new_string (string), replace_all (boolean). "
    "TaskList: no input. TaskUpdate: taskId (string), optional status enum exactly one of pending, in_progress, completed, deleted. "
    "CronCreate: cron (standard 5-field local-time cron string), prompt (string), optional recurring (boolean), optional durable (boolean). "
    "CronDelete: id (string returned by CronCreate). CronList: no input. "
    "Do not call WaitForMcpServers; it is a Claude Code lifecycle tool that may exist but is often not enabled in the current routed context. "
    "If an MCP server appears disconnected, use only tools present in the current tool list, retry ordinary MCP tools when available, or report the concrete connection state. "
    "Never write pseudo tool calls, partial JSON, or markdown code fences when a real Claude Code tool call is required."
)
LANGUAGES = {
    "en": "English",
    "ko": "한국어",
    "ja": "日本語",
    "zh": "中文",
}

MODEL_PRESETS: dict[str, dict[str, Any]] = {
    "glm-4.7": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 131072},
    "glm-5.1": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 131072},
    "glm-4.7:cloud": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 131072},
    "glm-5.1:cloud": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 131072},
    "qwen3-coder": {"compat_max_tokens": 16, "thinking": False, "num_ctx_min": 32768, "num_ctx_max": 65536},
    "qwen3-coder:30b": {"compat_max_tokens": 16, "thinking": False, "num_ctx_min": 32768, "num_ctx_max": 65536},
    "qwen3.6:27b": {"compat_max_tokens": 16, "thinking": False, "num_ctx_min": 32768, "num_ctx_max": 65536},
    "deepseek-r1": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 131072},
    "llama3.3:70b": {"compat_max_tokens": 16, "thinking": False, "num_ctx_min": 32768, "num_ctx_max": 131072},
}
LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT = 32768
LM_STUDIO_DEFAULT_CLAUDE_CODE_CONTEXT = 65536


def nvidia_hosted_context_default(model_id: str) -> int:
    model = model_id.lower()
    if "kimi-k2.6" in model or "kimi_k2.6" in model:
        return 262144
    if "deepseek" in model:
        return 131072
    if "glm" in model or "qwen" in model:
        return 65536
    return 65536


def model_lookup_ids(model_id: str) -> list[str]:
    raw = (model_id or "").strip()
    if not raw:
        return []
    out = [raw]
    low = raw.lower().replace("_", "-")
    compact = re.sub(r"[^a-z0-9]+", "", low)

    def add(value: str) -> None:
        if value and value not in out:
            out.append(value)

    if ("qwen3.6" in low or "qwen36" in compact) and "27b" in low:
        add("qwen3.6:27b")
    if ("qwen3.6" in low or "qwen36" in compact) and "35b" in low:
        add("qwen3.6:35b-a3b")
        add("qwen3.6:35b")
    return out


def model_preset(model_id: str) -> dict[str, Any]:
    """Return preset dict for a model ID, checking exact match then prefix match."""
    for candidate in model_lookup_ids(model_id):
        if candidate in MODEL_PRESETS:
            return MODEL_PRESETS[candidate]
        for key, value in MODEL_PRESETS.items():
            candidate_base = candidate.split(":", 1)[0]
            if candidate.startswith(key) or (":" not in candidate and key.startswith(candidate_base)):
                return value
    return {}


def compat_max_tokens_for_model(model_id: str) -> int:
    return model_preset(model_id).get("compat_max_tokens", 16)


def ollama_library_model_parts(model_id: str) -> tuple[str, str] | None:
    model = (model_id or "").strip()
    if not model:
        return None
    base, tag = (model.split(":", 1) + ["latest"])[:2] if ":" in model else (model, "latest")
    base = base.strip()
    tag = tag.strip() or "latest"
    # Ollama library pages are /library/<model>/tags. Skip custom namespaces
    # such as hf.co/... or registry paths that do not map cleanly to this URL.
    if "/" in base or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", base):
        return None
    return base, tag


def context_label_to_tokens(number: str, unit: str | None) -> int | None:
    try:
        value = float(number)
    except Exception:
        return None
    if value <= 0 or not math.isfinite(value):
        return None
    unit = (unit or "").strip().lower()
    multiplier = {"k": 1024, "m": 1024 * 1024, "g": 1024 * 1024 * 1024}.get(unit, 1)
    return int(round(value * multiplier))


def recommended_timeout_ms_for_context(context_tokens: int | None) -> int:
    """Return the default upstream wait timeout for a model/context size."""
    tokens = positive_int(context_tokens)
    if not tokens:
        return DEFAULT_REQUEST_TIMEOUT_MS
    if tokens and tokens >= 1024 * 1024:
        return 300000
    if tokens and tokens >= 512 * 1024:
        return 180000
    return 120000


def ollama_model_catalog_key(model_id: str) -> tuple[str, str, str] | None:
    parts = ollama_library_model_parts(model_id)
    if not parts:
        return None
    base, tag = parts
    return base.lower(), base, tag.lower()


def load_ollama_model_catalog() -> dict[str, Any]:
    try:
        data = json.loads(OLLAMA_MODEL_CATALOG_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return data if isinstance(data, dict) else {}


def save_ollama_model_catalog(catalog: dict[str, Any]) -> None:
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        tmp = OLLAMA_MODEL_CATALOG_PATH.with_name(f"{OLLAMA_MODEL_CATALOG_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(catalog, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
        os.chmod(tmp, 0o600)
        tmp.replace(OLLAMA_MODEL_CATALOG_PATH)
    except Exception as exc:
        router_log("WARN", f"ollama catalog: failed to save cache: {exc}")


def ollama_catalog_model_ids(provider: str = "ollama-cloud", catalog: dict[str, Any] | None = None) -> list[str]:
    catalog = catalog if isinstance(catalog, dict) else load_ollama_model_catalog()
    models = catalog.get("models") if isinstance(catalog, dict) else None
    if not isinstance(models, dict):
        return []
    ids: list[str] = []
    for entry in models.values():
        if not isinstance(entry, dict):
            continue
        raw_models = entry.get("models")
        if isinstance(raw_models, list):
            for raw_model in raw_models:
                mid = normalize_model_id(provider, str(raw_model))
                if mid:
                    ids.append(mid)
        base = str(entry.get("id") or "").strip()
        if base:
            ids.append(normalize_model_id(provider, base))
            tags = entry.get("tags")
            if isinstance(tags, list):
                for tag in tags:
                    tag_text = str(tag or "").strip()
                    if tag_text and tag_text.lower() not in ("latest", ""):
                        ids.append(normalize_model_id(provider, f"{base}:{tag_text}"))
    return sorted_model_ids(unique_model_ids(provider, ids))


def ollama_catalog_is_stale(catalog: dict[str, Any], ttl_seconds: int = OLLAMA_MODEL_CATALOG_TTL_SECONDS) -> bool:
    if not isinstance(catalog, dict) or not isinstance(catalog.get("models"), dict):
        return True
    try:
        updated_at = float(catalog.get("updated_at") or 0)
    except Exception:
        updated_at = 0.0
    return updated_at <= 0 or time.time() - updated_at > ttl_seconds


def fetch_json_url(url: str, timeout: float = 12.0) -> Any:
    req = urllib.request.Request(url, headers=with_upstream_user_agent())
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read(5_000_000).decode("utf-8", errors="replace"))


def context_tokens_from_ollama_snippet(snippet: str, table_fallback: bool = True) -> int | None:
    match = re.search(r"\b(\d+(?:\.\d+)?)\s*([KMG])\s+context\s+window\b", snippet, re.IGNORECASE)
    if match:
        return context_label_to_tokens(match.group(1), match.group(2))
    if table_fallback:
        match = re.search(r">\s*(\d+(?:\.\d+)?)\s*([KM])\s*</p>", snippet, re.IGNORECASE)
        if match:
            return context_label_to_tokens(match.group(1), match.group(2))
    return None


def parse_ollama_library_context_map(page_html: str, base_model: str) -> dict[str, int]:
    text = html_lib.unescape(page_html or "")
    base = (base_model or "").strip()
    if not text or not base:
        return {}
    contexts: dict[str, int] = {}
    pattern = re.compile(r"(?<![A-Za-z0-9._/-])" + re.escape(base) + r":([A-Za-z0-9][A-Za-z0-9._-]*)", re.IGNORECASE)
    for match in pattern.finditer(text):
        tag = match.group(1).strip().lower()
        if not tag:
            continue
        snippet = text[max(0, match.start() - 700): match.end() + 3000]
        tokens = context_tokens_from_ollama_snippet(snippet)
        if tokens:
            contexts[tag] = max(contexts.get(tag, 0), tokens)

    if not contexts:
        match = re.search(r"\b(\d+(?:\.\d+)?)\s*([KMG])\s+context\s+window\b", text, re.IGNORECASE)
        tokens = context_label_to_tokens(match.group(1), match.group(2)) if match else None
        if tokens:
            contexts["latest"] = tokens
    return contexts


def fetch_ollama_library_context_map(base_model: str, timeout: float = 10.0) -> tuple[dict[str, int], str | None]:
    parts = ollama_library_model_parts(base_model)
    if not parts:
        return {}, None
    base, _ = parts
    urls = [
        f"https://ollama.com/library/{urllib.parse.quote(base, safe='')}/tags",
        f"https://ollama.com/library/{urllib.parse.quote(base, safe='')}",
    ]
    merged: dict[str, int] = {}
    source_url: str | None = None
    for url in urls:
        req = urllib.request.Request(url, headers=with_upstream_user_agent())
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                raw = resp.read(3_000_000)
        except Exception:
            continue
        page_map = parse_ollama_library_context_map(raw.decode("utf-8", errors="replace"), base)
        for tag, tokens in page_map.items():
            merged[tag] = max(merged.get(tag, 0), tokens)
        if page_map and not source_url:
            source_url = url
    return merged, source_url


def refresh_ollama_model_catalog(include_contexts: bool = True, timeout: float = 10.0) -> dict[str, Any]:
    old_catalog = load_ollama_model_catalog()
    old_models = old_catalog.get("models") if isinstance(old_catalog.get("models"), dict) else {}
    data = fetch_json_url(OLLAMA_MODEL_CATALOG_URL, timeout=timeout)
    raw_models = data.get("models") if isinstance(data, dict) else None
    if raw_models is None and isinstance(data, dict):
        raw_models = data.get("data")
    if not isinstance(raw_models, list):
        raw_models = []

    models: dict[str, dict[str, Any]] = {}
    for item in raw_models:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name") or item.get("model") or item.get("id") or "").strip()
        parts = ollama_model_catalog_key(name)
        if not parts:
            continue
        key, base, tag = parts
        entry = models.setdefault(
            key,
            {
                "id": base,
                "models": [],
                "tags": [],
                "raw": [],
                "context_windows": {},
                "context_source": None,
            },
        )
        if name not in entry["models"]:
            entry["models"].append(name)
        if tag not in entry["tags"]:
            entry["tags"].append(tag)
        entry["raw"].append(item)

    if not include_contexts and isinstance(old_models, dict):
        for key, entry in models.items():
            old_entry = old_models.get(key)
            if not isinstance(old_entry, dict):
                continue
            old_windows = old_entry.get("context_windows")
            if isinstance(old_windows, dict) and old_windows:
                entry["context_windows"] = old_windows
                entry["context_window"] = positive_int(old_entry.get("context_window")) or max(
                    positive_int(v) or 0 for v in old_windows.values()
                )
                if isinstance(old_entry.get("recommended_timeout_ms_by_tag"), dict):
                    entry["recommended_timeout_ms_by_tag"] = old_entry["recommended_timeout_ms_by_tag"]
                if positive_int(old_entry.get("recommended_timeout_ms")):
                    entry["recommended_timeout_ms"] = positive_int(old_entry.get("recommended_timeout_ms"))
                entry["context_source"] = old_entry.get("context_source")

    if include_contexts:
        for key in sorted(models):
            entry = models[key]
            context_map, source = fetch_ollama_library_context_map(str(entry["id"]), timeout=timeout)
            if context_map:
                entry["context_windows"] = context_map
                entry["context_window"] = max(context_map.values())
                entry["recommended_timeout_ms_by_tag"] = {
                    tag: recommended_timeout_ms_for_context(tokens)
                    for tag, tokens in context_map.items()
                    if positive_int(tokens)
                }
                entry["recommended_timeout_ms"] = recommended_timeout_ms_for_context(entry["context_window"])
                entry["context_source"] = source

    catalog = {
        "schema": 1,
        "source": OLLAMA_MODEL_CATALOG_URL,
        "updated_at": time.time(),
        "model_count": len(raw_models),
        "base_model_count": len(models),
        "models": models,
    }
    save_ollama_model_catalog(catalog)
    return catalog


def ollama_catalog_context_for_model(model_id: str) -> tuple[int | None, str | None, str | None]:
    catalog = load_ollama_model_catalog()
    models = catalog.get("models", {})
    if not isinstance(models, dict):
        return None, None, None
    for candidate_model in model_lookup_ids(model_id):
        parts = ollama_model_catalog_key(candidate_model)
        if not parts:
            continue
        key, base, tag = parts
        entry = models.get(key)
        if not isinstance(entry, dict):
            continue
        windows = entry.get("context_windows")
        source = str(entry.get("context_source") or catalog.get("source") or "")
        if isinstance(windows, dict):
            candidates = [tag]
            if tag in ("latest", ""):
                candidates.extend(["cloud", "latest"])
            candidates.extend(["cloud", "latest"])
            for candidate in candidates:
                value = positive_int(windows.get(candidate))
                if value:
                    return value, f"{base}:{candidate}", source or None
        value = positive_int(entry.get("context_window"))
        if value:
            return value, base, source or None
    return None, None, None


def ollama_catalog_timeout_for_model(model_id: str) -> int | None:
    catalog = load_ollama_model_catalog()
    models = catalog.get("models", {})
    if not isinstance(models, dict):
        return None
    for candidate_model in model_lookup_ids(model_id):
        parts = ollama_model_catalog_key(candidate_model)
        if not parts:
            continue
        key, _, tag = parts
        entry = models.get(key)
        if not isinstance(entry, dict):
            continue
        per_tag = entry.get("recommended_timeout_ms_by_tag")
        if isinstance(per_tag, dict):
            candidates = [tag]
            if tag in ("latest", ""):
                candidates.extend(["cloud", "latest"])
            candidates.extend(["cloud", "latest"])
            for candidate in candidates:
                value = positive_int(per_tag.get(candidate))
                if value:
                    return value
        value = positive_int(entry.get("recommended_timeout_ms"))
        if value:
            return value
    return None


def update_ollama_catalog_context(model_id: str, limit: int, matched_model: str | None, source_url: str | None) -> None:
    parts = ollama_model_catalog_key(model_id)
    if not parts or not limit:
        return
    key, base, tag = parts
    catalog = load_ollama_model_catalog()
    if not isinstance(catalog.get("models"), dict):
        catalog = {
            "schema": 1,
            "source": OLLAMA_MODEL_CATALOG_URL,
            "updated_at": time.time(),
            "model_count": 0,
            "base_model_count": 0,
            "models": {},
        }
    entry = catalog["models"].setdefault(
        key,
        {"id": base, "models": [], "tags": [], "raw": [], "context_windows": {}, "context_source": None},
    )
    if model_id not in entry["models"]:
        entry["models"].append(model_id)
    tag_to_store = tag
    matched_parts = ollama_model_catalog_key(matched_model or "")
    if matched_parts:
        tag_to_store = matched_parts[2]
    entry.setdefault("tags", [])
    if tag_to_store not in entry["tags"]:
        entry["tags"].append(tag_to_store)
    windows = entry.setdefault("context_windows", {})
    if isinstance(windows, dict):
        windows[tag_to_store] = int(limit)
    recommended_by_tag = entry.setdefault("recommended_timeout_ms_by_tag", {})
    if isinstance(recommended_by_tag, dict):
        recommended_by_tag[tag_to_store] = recommended_timeout_ms_for_context(limit)
    entry["context_window"] = max([positive_int(v) or 0 for v in entry.get("context_windows", {}).values()] + [int(limit)])
    entry["recommended_timeout_ms"] = recommended_timeout_ms_for_context(positive_int(entry.get("context_window")))
    entry["context_source"] = source_url or entry.get("context_source")
    catalog["updated_at"] = time.time()
    catalog["base_model_count"] = len(catalog.get("models", {}))
    save_ollama_model_catalog(catalog)


def parse_ollama_library_context_limit(tags_html: str, full_model_id: str) -> int | None:
    text = html_lib.unescape(tags_html or "")
    target = (full_model_id or "").strip()
    if not text or not target:
        return None
    lower = text.lower()
    target_lower = target.lower()
    positions: list[int] = []
    start = 0
    while True:
        idx = lower.find(target_lower, start)
        if idx < 0:
            break
        positions.append(idx)
        start = idx + len(target_lower)
    for idx in positions:
        snippet = text[max(0, idx - 500): idx + 2500]
        tokens = context_tokens_from_ollama_snippet(snippet)
        if tokens:
            return tokens
    return None


def fetch_ollama_library_context_limit(model_id: str, timeout: float = 6.0) -> tuple[int | None, str | None, str | None]:
    parts = ollama_library_model_parts(model_id)
    if not parts:
        return None, None, None
    base, tag = parts
    full_model = f"{base}:{tag}"
    context_map, url = fetch_ollama_library_context_map(base, timeout=timeout)
    limit = positive_int(context_map.get(tag.lower()))
    if not limit and tag == "latest":
        cloud_limit = positive_int(context_map.get("cloud"))
        if cloud_limit:
            return cloud_limit, f"{base}:cloud", url
    return limit, full_model, url


def ollama_context_model_matches(current_model: str, cached_model: str | None) -> bool:
    current = (current_model or "").strip().lower()
    cached = (cached_model or "").strip().lower()
    if not current or not cached:
        return False

    def aliases(value: str) -> set[str]:
        result = {value}
        if ":" not in value:
            result.add(f"{value}:latest")
            result.add(f"{value}:cloud")
        if value.endswith(":latest") or value.endswith(":cloud"):
            result.add(value.rsplit(":", 1)[0])
        return result

    return bool(aliases(current) & aliases(cached))


def sync_ollama_library_context_limit(provider: str, pcfg: dict[str, Any], model_id: str) -> list[str]:
    if provider not in ("ollama", "ollama-cloud"):
        return []
    catalog = load_ollama_model_catalog()
    if ollama_catalog_is_stale(catalog):
        try:
            catalog = refresh_ollama_model_catalog(include_contexts=False)
        except Exception as exc:
            router_log("WARN", f"ollama catalog: api refresh failed: {exc}")
    limit, matched_model, url = ollama_catalog_context_for_model(model_id)
    if not limit:
        limit, matched_model, url = fetch_ollama_library_context_limit(model_id)
        if limit:
            update_ollama_catalog_context(model_id, limit, matched_model, url)
    if not limit:
        hint = model_context_hint_from_model_id(model_id)
        if hint:
            limit = hint
            matched_model = normalize_model_id(provider, model_id)
        else:
            if not ollama_context_model_matches(model_id, str(pcfg.get("model_context_model") or "")):
                pcfg.pop("model_context_max", None)
                pcfg.pop("model_context_model", None)
            return []
    old_max = positive_int(pcfg.get("num_ctx_max"))
    pcfg["model_context_max"] = limit
    pcfg["model_context_model"] = matched_model
    pcfg["num_ctx_max"] = limit
    minimum = positive_int(pcfg.get("num_ctx_min"))
    if minimum and minimum > limit:
        pcfg["num_ctx_min"] = limit
    fixed_ctx = positive_int(pcfg.get("num_ctx"))
    if fixed_ctx and fixed_ctx > limit:
        pcfg["num_ctx"] = limit
    label = f"{limit:,}"
    if old_max and old_max == limit:
        return [f"Ollama library context verified: {matched_model} -> {label} tokens."]
    detail = f" from {url}" if url else ""
    return [f"Ollama library context detected: {matched_model} -> {label} tokens{detail}."]


# ---------------------------------------------------------------------------
# Tool schema registry and parameter validation
# ---------------------------------------------------------------------------

_TOOL_SCHEMA_REGISTRY: dict[str, dict[str, Any]] = {}
_MCP_NOTIFICATION_WAIT_RECENT: dict[str, float] = {}
_MCP_NOTIFICATION_WAIT_RECENT_LOCK = threading.Lock()

_BUILTIN_TOOL_SCHEMAS: dict[str, dict[str, Any]] = {
    "Bash": {
        "required": ["command"],
        "properties": {
            "command": {"type": "string"},
            "description": {"type": "string"},
            "timeout": {"type": "integer"},
            "run_in_background": {"type": "boolean"},
        },
    },
    "Read": {
        "required": ["file_path"],
        "properties": {
            "file_path": {"type": "string"},
            "offset": {"type": "integer"},
            "limit": {"type": "integer"},
        },
    },
    "Write": {
        "required": ["file_path", "content"],
        "properties": {
            "file_path": {"type": "string"},
            "content": {"type": "string"},
        },
    },
    "Edit": {
        "required": ["file_path", "old_string", "new_string"],
        "properties": {
            "file_path": {"type": "string"},
            "old_string": {"type": "string"},
            "new_string": {"type": "string"},
            "replace_all": {"type": "boolean"},
        },
    },
    "Glob": {
        "required": ["pattern"],
        "properties": {
            "pattern": {"type": "string"},
            "path": {"type": "string"},
        },
    },
    "Grep": {
        "required": ["pattern"],
        "properties": {
            "pattern": {"type": "string"},
            "path": {"type": "string"},
            "output_mode": {"type": "string"},
        },
    },
    "TaskList": {
        "required": [],
        "properties": {},
    },
    "TaskUpdate": {
        "required": ["taskId"],
        "properties": {
            "taskId": {"type": "string"},
            "subject": {"type": "string"},
            "description": {"type": "string"},
            "activeForm": {"type": "string"},
            "status": {"type": "string", "enum": ["pending", "in_progress", "completed", "deleted"]},
            "owner": {"type": "string"},
            "addBlocks": {"type": "array"},
            "addBlockedBy": {"type": "array"},
            "metadata": {"type": "object"},
        },
    },
    "TaskCreate": {
        "required": ["subject", "description"],
        "properties": {
            "subject": {"type": "string"},
            "description": {"type": "string"},
        },
    },
    "TaskGet": {
        "required": ["taskId"],
        "properties": {
            "taskId": {"type": "string"},
        },
    },
    "TaskStop": {
        "required": ["task_id"],
        "properties": {
            "task_id": {"type": "string"},
        },
    },
    "CronCreate": {
        "required": ["cron", "prompt"],
        "properties": {
            "cron": {"type": "string"},
            "prompt": {"type": "string"},
            "recurring": {"type": "boolean"},
            "durable": {"type": "boolean"},
        },
    },
    "CronDelete": {
        "required": ["id"],
        "properties": {
            "id": {"type": "string"},
        },
    },
    "CronList": {
        "required": [],
        "properties": {},
    },
    "advisor": {
        "required": ["question"],
        "properties": {
            "question": {"type": "string"},
        },
    },
}


def _update_tool_schema_registry(tools: Any) -> None:
    """Cache tool schemas from incoming Anthropic requests."""
    if not isinstance(tools, list):
        return
    for tool in tools:
        if not isinstance(tool, dict):
            continue
        name = tool.get("name")
        if not name:
            continue
        _TOOL_SCHEMA_REGISTRY[name] = tool.get("input_schema") or {}


def _lookup_tool_schema(tool_name: str) -> dict[str, Any] | None:
    """Look up a tool schema by name, checking registry then builtins."""
    if tool_name in _TOOL_SCHEMA_REGISTRY:
        return _TOOL_SCHEMA_REGISTRY[tool_name]
    if tool_name in _BUILTIN_TOOL_SCHEMAS:
        return _BUILTIN_TOOL_SCHEMAS[tool_name]
    return None


def _fuzzy_match_tool_name(name: str) -> str | None:
    """Fuzzy match a tool name against known schemas (case-insensitive, prefix)."""
    low = name.lower()
    candidates = list(_TOOL_SCHEMA_REGISTRY.keys()) + list(_BUILTIN_TOOL_SCHEMAS.keys())
    # Exact match first
    for c in candidates:
        if c == name:
            return c
    # Case-insensitive
    for c in candidates:
        if c.lower() == low:
            return c
    # Prefix/substring match
    for c in candidates:
        if low in c.lower() or c.lower() in low:
            return c
    return None


def _coerce_value(value: Any, expected_type: str | None) -> Any:
    """Coerce a value to the expected JSON schema type."""
    if expected_type is None:
        return value
    if isinstance(value, bool) and expected_type == "boolean":
        return value
    if isinstance(value, (int, float)) and expected_type == "integer":
        return int(value)
    if isinstance(value, (int, float)) and expected_type == "number":
        return float(value)
    if isinstance(value, str) and expected_type == "string":
        return value
    if expected_type == "array":
        if isinstance(value, list):
            return value
        if isinstance(value, tuple):
            return list(value)
        if isinstance(value, str) and value.strip():
            return [value.strip()]
        if value is None:
            return []
        return value
    if expected_type == "object":
        if isinstance(value, dict):
            return value
        if isinstance(value, str) and value.strip():
            try:
                parsed = json.loads(value)
                if isinstance(parsed, dict):
                    return parsed
            except Exception:
                pass
        return value
    # Coerce string -> integer
    if isinstance(value, str) and expected_type in ("integer", "number"):
        try:
            return int(value) if expected_type == "integer" else float(value)
        except Exception:
            pass
    # Coerce string -> boolean
    if isinstance(value, str) and expected_type == "boolean":
        low = value.lower()
        if low in ("true", "yes", "on", "1"):
            return True
        if low in ("false", "no", "off", "0"):
            return False
    # Coerce int/float -> string
    if isinstance(value, (int, float)) and expected_type == "string":
        return str(value)
    # Coerce anything -> string as last resort
    if expected_type == "string" and value is not None:
        return str(value)
    return value


def normalize_task_update_status(value: Any) -> str | None:
    if value is None:
        return None
    text = str(value).strip()
    if not text:
        return None
    normalized = re.sub(r"[\s\-]+", "_", text.lower())
    normalized = re.sub(r"[^a-z0-9_]", "", normalized)
    if normalized in TASK_UPDATE_STATUSES:
        return normalized
    return TASK_UPDATE_STATUS_ALIASES.get(text.lower()) or TASK_UPDATE_STATUS_ALIASES.get(normalized)


def _default_for_missing_required(tool_name: str, field: str) -> Any:
    """Return a safe default for known required fields."""
    defaults: dict[str, dict[str, Any]] = {
        "Bash": {"command": "true", "timeout": 30000, "description": "", "run_in_background": False},
        "Read": {"offset": 0, "limit": 0},
        "Edit": {"replace_all": False},
        "Glob": {"path": "."},
        "Grep": {"output_mode": "content"},
        "TaskCreate": {"description": ""},
        "TaskStop": {},
    }
    return defaults.get(tool_name, {}).get(field)


def _is_empty_value(value: Any) -> bool:
    """Check if a value is effectively empty and should be defaulted."""
    if value is None:
        return True
    if isinstance(value, str) and value.strip() == "":
        return True
    return False


def _move_first_present(fixed: dict[str, Any], target: str, aliases: tuple[str, ...]) -> None:
    """Move the first non-empty alias value to the target field."""
    if target in fixed and not _is_empty_value(fixed.get(target)):
        return
    for alias in aliases:
        value = fixed.get(alias)
        if not _is_empty_value(value):
            fixed[target] = value
            break


def _validate_and_fix_tool_input(tool_name: str, input_dict: dict[str, Any]) -> dict[str, Any]:
    """
    Validate tool_use input against schema and fix common errors:
      - fuzzy-match tool name
      - coerce types to match schema
      - add defaults for missing required fields
      - keep unknown fields (Claude Code may accept extra fields)
    """
    schema = _lookup_tool_schema(tool_name)
    matched_name = tool_name
    if schema is None:
        matched = _fuzzy_match_tool_name(tool_name)
        if matched:
            matched_name = matched
            schema = _lookup_tool_schema(matched)

    if schema is None:
        # No schema known: just ensure it's a dict and return
        return input_dict if isinstance(input_dict, dict) else {}

    properties = schema.get("properties") or {}
    required = set(schema.get("required") or [])
    fixed: dict[str, Any] = {}

    for key, raw_value in input_dict.items():
        prop_schema = properties.get(key)
        if prop_schema is None:
            # Unknown field: keep it rather than dropping it.
            # Claude Code may accept fields not in our static registry.
            fixed[key] = raw_value
            continue
        expected_type = prop_schema.get("type") if isinstance(prop_schema, dict) else None
        fixed[key] = _coerce_value(raw_value, expected_type)

    if matched_name == "TaskUpdate":
        if "taskId" not in fixed or _is_empty_value(fixed.get("taskId")):
            for alias in ("task_id", "id"):
                value = fixed.get(alias)
                if isinstance(value, (str, int, float)) and not isinstance(value, bool) and str(value).strip():
                    fixed["taskId"] = str(value)
                    break
        if "status" in fixed:
            status = normalize_task_update_status(fixed.get("status"))
            if status:
                fixed["status"] = status
        for alias in ("task_id", "id"):
            fixed.pop(alias, None)

    if matched_name == "CronCreate":
        _move_first_present(
            fixed,
            "cron",
            ("schedule", "cronExpression", "cron_expression", "expression", "interval", "time"),
        )
        _move_first_present(
            fixed,
            "prompt",
            ("message", "task", "instruction", "instructions", "command", "query"),
        )
        for key in ("cron", "prompt", "recurring", "durable"):
            prop_schema = properties.get(key)
            expected_type = prop_schema.get("type") if isinstance(prop_schema, dict) else None
            if key in fixed:
                fixed[key] = _coerce_value(fixed[key], expected_type)
        for alias in (
            "schedule",
            "cronExpression",
            "cron_expression",
            "expression",
            "interval",
            "time",
            "message",
            "task",
            "instruction",
            "instructions",
            "command",
            "query",
        ):
            fixed.pop(alias, None)

    if matched_name == "CronDelete":
        _move_first_present(fixed, "id", ("taskId", "task_id", "jobId", "job_id", "cronId", "cron_id"))
        if "id" in fixed:
            fixed["id"] = _coerce_value(fixed["id"], "string")
        for alias in ("taskId", "task_id", "jobId", "job_id", "cronId", "cron_id"):
            fixed.pop(alias, None)

    if matched_name == "CronList":
        fixed = {}

    # Fill in missing or empty required fields with defaults
    injected: list[str] = []
    for req in required:
        if req not in fixed or _is_empty_value(fixed.get(req)):
            default = _default_for_missing_required(matched_name, req)
            if default is not None:
                fixed[req] = default
            elif matched_name == "TaskUpdate" and req == "taskId":
                continue
            elif req not in fixed:
                # No known default: inject empty value matching expected type
                prop_schema = properties.get(req)
                expected_type = prop_schema.get("type") if isinstance(prop_schema, dict) else None
                if expected_type == "string":
                    fixed[req] = ""
                elif expected_type == "integer":
                    fixed[req] = 0
                elif expected_type == "number":
                    fixed[req] = 0.0
                elif expected_type == "boolean":
                    fixed[req] = False
                elif expected_type == "array":
                    fixed[req] = []
                elif expected_type == "object":
                    fixed[req] = {}
                else:
                    fixed[req] = ""
            injected.append(req)

    if injected:
        router_log("WARN", f"tool_guard: {matched_name}: injected missing required fields: {', '.join(injected)}")

    return fixed


MCP_NOTIFICATION_WAIT_TOOL_NAMES = {
    "wait_for_notification",
    "wait_for_notifications",
    "wait_for_message",
    "wait_for_messages",
    "wait_for_event",
    "wait_for_events",
    "wait_for_response",
    "wait_for_responses",
}


def _mcp_tool_leaf_name(tool_name: str) -> str:
    text = str(tool_name or "").strip()
    if "__" in text:
        return text.rsplit("__", 1)[-1].strip().lower()
    return text.lower()


def _is_mcp_notification_wait_tool(tool_name: str) -> bool:
    text = str(tool_name or "").strip().lower()
    if not text.startswith("mcp__"):
        return False
    return _mcp_tool_leaf_name(text) in MCP_NOTIFICATION_WAIT_TOOL_NAMES


def _mcp_notification_wait_timeout_cap_ms() -> int:
    raw = os.environ.get("CLAUDE_ANY_MCP_NOTIFICATION_WAIT_TIMEOUT_MS")
    if raw is None:
        return 1000
    try:
        value = int(float(str(raw).strip()))
    except Exception:
        return 1000
    if value <= 0:
        return 0
    return max(100, min(10_000, value))


def _mcp_notification_wait_duplicate_cap_ms() -> int:
    raw = os.environ.get("CLAUDE_ANY_MCP_NOTIFICATION_WAIT_DUPLICATE_TIMEOUT_MS")
    if raw is None:
        return 100
    try:
        value = int(float(str(raw).strip()))
    except Exception:
        return 100
    if value <= 0:
        return 0
    return max(50, min(5000, value))


def _mcp_notification_wait_duplicate_window_seconds() -> float:
    raw = os.environ.get("CLAUDE_ANY_MCP_NOTIFICATION_WAIT_DUPLICATE_WINDOW_SECONDS")
    if raw is None:
        return 90.0
    try:
        value = float(str(raw).strip())
    except Exception:
        return 90.0
    return max(0.0, min(600.0, value))


def _mcp_notification_wait_effective_cap_ms(tool_name: str) -> tuple[int, bool]:
    cap_ms = _mcp_notification_wait_timeout_cap_ms()
    if cap_ms <= 0:
        return 0, False
    duplicate_cap_ms = _mcp_notification_wait_duplicate_cap_ms()
    window = _mcp_notification_wait_duplicate_window_seconds()
    if duplicate_cap_ms <= 0 or window <= 0:
        return cap_ms, False
    key = str(tool_name or "").strip().lower()
    now = time.time()
    duplicate = False
    with _MCP_NOTIFICATION_WAIT_RECENT_LOCK:
        stale = [item_key for item_key, seen_at in _MCP_NOTIFICATION_WAIT_RECENT.items() if now - seen_at > window]
        for item_key in stale:
            _MCP_NOTIFICATION_WAIT_RECENT.pop(item_key, None)
        previous = _MCP_NOTIFICATION_WAIT_RECENT.get(key)
        duplicate = previous is not None and now - previous <= window
        _MCP_NOTIFICATION_WAIT_RECENT[key] = now
    if duplicate:
        return min(cap_ms, duplicate_cap_ms), True
    return cap_ms, False


def cap_mcp_notification_wait_tool_input(tool_name: str, tool_input: dict[str, Any]) -> dict[str, Any]:
    if not _is_mcp_notification_wait_tool(tool_name):
        return tool_input
    cap_ms, duplicate = _mcp_notification_wait_effective_cap_ms(tool_name)
    if cap_ms <= 0:
        return tool_input
    fixed = dict(tool_input) if isinstance(tool_input, dict) else {}
    schema = _lookup_tool_schema(tool_name) or {}
    properties = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
    changed: list[str] = []

    def set_if_lower(key: str, value: int | float) -> None:
        old = fixed.get(key)
        try:
            numeric = float(old)
        except Exception:
            fixed[key] = int(value) if float(value).is_integer() else value
            changed.append(f"{key}=missing->{value:g}")
            return
        if numeric > float(value):
            fixed[key] = int(value) if float(value).is_integer() else value
            changed.append(f"{key}={numeric:g}->{value:g}")

    for key in list(fixed):
        key_l = str(key).strip().lower()
        if key_l in {"timeout_ms", "timeoutms", "wait_ms", "waitms", "max_wait_ms", "maxwaitms"}:
            set_if_lower(key, cap_ms)
        elif key_l in {"timeout", "wait_seconds", "wait_s", "max_wait_seconds"}:
            set_if_lower(key, max(0.1, cap_ms / 1000.0))

    if not changed:
        if "timeout_ms" in properties or "timeout_ms" in fixed or not properties:
            set_if_lower("timeout_ms", cap_ms)
        elif "timeout" in properties:
            set_if_lower("timeout", max(0.1, cap_ms / 1000.0))

    if changed:
        duplicate_label = " duplicate=true" if duplicate else ""
        router_log("INFO", f"mcp_notification_wait_timeout_capped tool={tool_name}{duplicate_label} {' '.join(changed)}")
    return fixed


UI_TEXT = {
    "en": {
        "language": "Language",
        "provider": "Provider",
        "api_key": "API key",
        "base_url": "Base URL",
        "model": "Model",
        "advisor_model": "Advisor Model",
        "test": "Test compatibility",
        "options": "LLM options",
        "channel_delivery": "Channel delivery",
        "channels": "Channels",
        "log_level": "Log level",
        "presets": "LLM presets",
        "context_setup": "Context setup",
        "timeout_preset": "Timeout preset",
        "apply_preset": "Apply preset",
        "model_family": "Model family",
        "recommended_preset_is": "recommended preset is",
        "back": "Back",
        "launch": "Launch Claude Code",
        "quit": "Quit",
        "title": "claude-any pre-launch",
    },
    "ko": {
        "language": "언어",
        "provider": "프로바이더",
        "api_key": "API 키",
        "base_url": "Base URL",
        "model": "모델",
        "advisor_model": "Advisor Model",
        "test": "호환성 테스트",
        "options": "LLM 옵션",
        "channel_delivery": "채널 전달 방식",
        "channels": "채널",
        "log_level": "로그 레벨",
        "presets": "LLM 프리셋",
        "context_setup": "컨텍스트 설정",
        "timeout_preset": "타임아웃 프리셋",
        "apply_preset": "프리셋 적용",
        "model_family": "모델 계열",
        "recommended_preset_is": "추천 프리셋",
        "back": "뒤로",
        "launch": "Claude Code 실행",
        "quit": "종료",
        "title": "claude-any 실행 전 설정",
    },
    "ja": {
        "language": "言語",
        "provider": "プロバイダー",
        "api_key": "APIキー",
        "base_url": "Base URL",
        "model": "モデル",
        "advisor_model": "Advisor Model",
        "test": "互換性テスト",
        "options": "LLMオプション",
        "channel_delivery": "チャンネル配信方式",
        "channels": "チャンネル",
        "log_level": "ログレベル",
        "presets": "LLMプリセット",
        "context_setup": "コンテキスト設定",
        "timeout_preset": "timeout プリセット",
        "apply_preset": "プリセットを適用",
        "model_family": "モデル系統",
        "recommended_preset_is": "推奨プリセット",
        "back": "戻る",
        "launch": "Claude Codeを起動",
        "quit": "終了",
        "title": "claude-any 起動前設定",
    },
    "zh": {
        "language": "语言",
        "provider": "提供商",
        "api_key": "API 密钥",
        "base_url": "Base URL",
        "model": "模型",
        "advisor_model": "Advisor Model",
        "test": "兼容性测试",
        "options": "LLM 选项",
        "channel_delivery": "频道投递方式",
        "channels": "频道",
        "log_level": "日志级别",
        "presets": "LLM 预设",
        "context_setup": "上下文设置",
        "timeout_preset": "Timeout 预设",
        "apply_preset": "应用预设",
        "model_family": "模型类型",
        "recommended_preset_is": "推荐预设",
        "back": "返回",
        "launch": "启动 Claude Code",
        "quit": "退出",
        "title": "claude-any 启动前设置",
    },
}


def ui_text(key: str, lang: str | None = None) -> str:
    lang = lang or load_config().get("language", "en")
    return UI_TEXT.get(lang, UI_TEXT["en"]).get(key, UI_TEXT["en"].get(key, key))


PROVIDER_NOTES = {
    "en": {
        "anthropic": [
            "Anthropic: uses Claude Code's native Anthropic connection.",
            "Set an Anthropic API key here, or run `claude /login` separately to use your Claude account login.",
        ],
        "ollama": [
            "Ollama: uses your local Ollama daemon; API key is normally not required.",
            "To use :cloud models through local Ollama, sign in on the Ollama host with `ollama signin`.",
        ],
        "ollama-cloud": [
            "Ollama Cloud: calls https://ollama.com/api directly; an Ollama API key is required.",
            "Use this when you want cloud models without relying on the local Ollama daemon's sign-in state.",
        ],
        "vllm": [
            "vLLM: Anthropic Messages API is the default; OpenAI-only chat completions endpoints are auto-detected.",
            "If auto-detection finds only /v1/chat/completions, claude-any disables Native compatibility and routes through the local converter.",
        ],
        "lm-studio": [
            "LM Studio: uses the local Anthropic-compatible /v1/messages server by default.",
            "Start LM Studio's Local Server and use http://127.0.0.1:1234/v1 as the base URL; set native=false only for router fallback.",
        ],
        "self-hosted-nim": [
            "Self-hosted NIM: enter the NIM server root that exposes Anthropic-compatible /v1/messages.",
            "This native path does not use the NVIDIA hosted API Catalog proxy.",
        ],
        "nvidia-hosted": [
            "NVIDIA hosted: uses NVIDIA API Catalog at https://integrate.api.nvidia.com/v1.",
            "Hosted API Catalog currently uses the claude-any router path; self-hosted NIM uses native Messages.",
        ],
    },
    "ko": {
        "anthropic": [
            "Anthropic: Claude Code의 기본 Anthropic 연결을 사용합니다.",
            "여기에 Anthropic API key를 넣거나, 별도로 `claude /login`을 실행해 Claude 계정 로그인을 사용하세요.",
        ],
        "ollama": [
            "Ollama: 로컬 Ollama 데몬을 사용합니다. 일반 로컬 모델은 API key가 필요 없습니다.",
            "로컬 Ollama로 :cloud 모델을 쓰려면 Ollama가 실행되는 호스트에서 `ollama signin`이 필요합니다.",
        ],
        "ollama-cloud": [
            "Ollama Cloud: https://ollama.com/api를 직접 호출합니다. Ollama API key가 필요합니다.",
            "로컬 Ollama 데몬의 로그인 상태와 무관하게 클라우드 모델을 쓰고 싶을 때 사용합니다.",
        ],
        "vllm": [
            "vLLM: Anthropic Messages API를 기본값으로 사용합니다. OpenAI 전용 chat completions endpoint는 자동 감지합니다.",
            "자동 감지가 /v1/chat/completions만 찾으면 Native compatibility를 끄고 로컬 변환 라우터를 사용합니다.",
        ],
        "lm-studio": [
            "LM Studio: 기본적으로 로컬 Anthropic 호환 /v1/messages 서버를 직접 사용합니다.",
            "LM Studio의 Local Server를 켜고 base URL은 http://127.0.0.1:1234/v1 을 사용하세요. 라우터 fallback이 필요할 때만 native=false를 쓰세요.",
        ],
        "self-hosted-nim": [
            "Self-hosted NIM: Anthropic 호환 /v1/messages를 노출하는 NIM 서버 root를 넣으세요.",
            "이 native 경로는 NVIDIA hosted API Catalog 프록시를 사용하지 않습니다.",
        ],
        "nvidia-hosted": [
            "NVIDIA hosted: https://integrate.api.nvidia.com/v1 의 NVIDIA API Catalog를 사용합니다.",
            "Hosted API Catalog는 claude-any router 경로를 기본 사용합니다. self-hosted NIM은 native Messages를 사용합니다.",
        ],
    },
    "ja": {
        "anthropic": [
            "Anthropic: Claude CodeのネイティブAnthropic接続を使います。",
            "ここでAnthropic API keyを設定するか、別途`claude /login`を実行してClaudeアカウントログインを使ってください。",
        ],
        "ollama": [
            "Ollama: ローカルのOllama daemonを使います。通常のローカルモデルではAPI keyは不要です。",
            "ローカルOllama経由で:cloudモデルを使うには、Ollamaホストで`ollama signin`が必要です。",
        ],
        "ollama-cloud": [
            "Ollama Cloud: https://ollama.com/api を直接呼び出します。Ollama API keyが必要です。",
            "ローカルOllama daemonのサインイン状態に依存せずクラウドモデルを使う場合に選びます。",
        ],
        "vllm": [
            "vLLM: Anthropic Messages APIを既定にします。OpenAI専用chat completions endpointは自動検出します。",
            "自動検出で/v1/chat/completionsのみ見つかった場合、Native compatibilityを無効にしてローカル変換routerを使います。",
        ],
        "lm-studio": [
            "LM Studio: 既定ではローカルのAnthropic互換 /v1/messages サーバーを直接使います。",
            "LM StudioのLocal Serverを起動し、base URLは http://127.0.0.1:1234/v1 を使ってください。router fallbackが必要な時だけnative=falseにします。",
        ],
        "self-hosted-nim": [
            "Self-hosted NIM: Anthropic互換/v1/messagesを公開するNIMサーバーrootを入力してください。",
            "このnative経路はNVIDIA hosted API Catalog proxyを使いません。",
        ],
        "nvidia-hosted": [
            "NVIDIA hosted: https://integrate.api.nvidia.com/v1 のNVIDIA API Catalogを使います。",
            "Hosted API Catalogはclaude-any router経路を既定で使います。self-hosted NIMはnative Messagesを使います。",
        ],
    },
    "zh": {
        "anthropic": [
            "Anthropic: 使用Claude Code原生Anthropic连接。",
            "可在此设置Anthropic API key，或另行运行`claude /login`使用Claude账号登录。",
        ],
        "ollama": [
            "Ollama: 使用本地Ollama daemon；普通本地模型通常不需要API key。",
            "若通过本地Ollama使用:cloud模型，需要在运行Ollama的主机上执行`ollama signin`。",
        ],
        "ollama-cloud": [
            "Ollama Cloud: 直接调用 https://ollama.com/api；需要Ollama API key。",
            "当你想不依赖本地Ollama daemon登录状态使用云端模型时选择它。",
        ],
        "vllm": [
            "vLLM: 默认使用 Anthropic Messages API。仅 OpenAI chat completions 的端点会自动检测。",
            "如果自动检测只发现 /v1/chat/completions，claude-any 会关闭 Native compatibility 并使用本地转换路由。",
        ],
        "lm-studio": [
            "LM Studio: 默认直接使用本地 Anthropic-compatible /v1/messages 服务器。",
            "启动 LM Studio 的 Local Server，并使用 http://127.0.0.1:1234/v1 作为 base URL；仅在需要路由 fallback 时设置 native=false。",
        ],
        "self-hosted-nim": [
            "Self-hosted NIM: 请输入暴露 Anthropic-compatible /v1/messages 的 NIM 服务器 root。",
            "此 native 路径不使用 NVIDIA hosted API Catalog 代理。",
        ],
        "nvidia-hosted": [
            "NVIDIA hosted: 使用 https://integrate.api.nvidia.com/v1 的 NVIDIA API Catalog。",
            "Hosted API Catalog 默认使用 claude-any router 路径；self-hosted NIM 使用 native Messages。",
        ],
    },
}

DEFAULT_ADVISOR_MODELS: tuple[str, ...] = (
    "",
    "claude-fable-5",
    "claude-opus-4-8",
    "deepseek-v4-pro",
    "claude-opus-4-6",
    "claude-sonnet-4-6",
    "glm-5.1",
)

DEFAULT_CONFIG: dict[str, Any] = {
    "current_provider": "nvidia-hosted",
    "language": "en",
    "migrations": {},
    "router_debug_external_access": False,
    "router_debug_external_access_confirmed": False,
    "router_debug_message_preview_chars": 0,
    "claude_code": {
        "compat_prompt_for_non_anthropic": True,
        "channels": [],
        "development_channels": False,
        "channel_delivery": "llm",
    },
    "cleanup": {
        "managed_services_on_launch": True,
    },
    "web_search": {
        "auto_for_non_native": True,
        "provider": "duckduckgo",
        "package": "ddg-mcp-search",
        "fetch_enabled": True,
        "fetch_package": "mcp-server-fetch",
        "fetch_ignore_robots_txt": False,
        "fetch_user_agent": "",
    },
    "providers": {
        "anthropic": {
            "base_url": "https://api.anthropic.com",
            "api_key": "",
            "current_model": "claude-sonnet-4-6",
            "advisor_model": "",
            "custom_models": [],
            "route_through_router": False,
        },
        "ollama": {
            "base_url": "http://127.0.0.1:11434",
            "api_key": "ollama",
            "current_model": "qwen3-coder",
            "advisor_model": "",
            "custom_models": ["qwen3-coder"],
            "native_compat": True,
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "num_ctx": "auto",
            "num_ctx_min": 32768,
            "num_ctx_max": 131072,
            "keep_alive": "5m",
            "think": False,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "ollama_options": {
                "temperature": 0.7,
                "top_p": 0.8,
                "top_k": 40,
                "num_predict": 4096,
            },
        },
        "ollama-cloud": {
            "base_url": "https://ollama.com",
            "api_key": "",
            "current_model": "glm-5.1",
            "advisor_model": "",
            "custom_models": ["glm-5.1"],
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "num_ctx": "auto",
            "num_ctx_min": 32768,
            "num_ctx_max": 131072,
            "keep_alive": "5m",
            "think": False,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "ollama_options": {
                "temperature": 0.7,
                "top_p": 0.8,
                "top_k": 40,
                "num_predict": 4096,
            },
        },
        "deepseek": {
            "base_url": "https://api.deepseek.com/anthropic",
            "api_key": "",
            "current_model": "deepseek-v4-pro[1m]",
            "advisor_model": "",
            "custom_models": ["deepseek-v4-pro[1m]", "deepseek-v4-flash"],
            "native_compat": True,
            "context_window": 1048576,
            "max_output_tokens": 8192,
            "context_reserve_tokens": 8192,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "effort_level": "max",
            "haiku_model": "deepseek-v4-flash",
            "subagent_model": "deepseek-v4-flash",
        },
        "opencode": {
            "base_url": OPENCODE_ZEN_BASE_URL,
            "api_key": "",
            "current_model": "claude-sonnet-4-6",
            "advisor_model": "",
            "custom_models": ["claude-sonnet-4-6"],
            "native_compat": True,
            "context_window": 200000,
            "max_output_tokens": 8192,
            "context_reserve_tokens": 8192,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "ip_family": "ipv6-preferred",
            "haiku_model": "claude-haiku-4-5",
            "subagent_model": "claude-sonnet-4-6",
            "model_endpoints": {},
        },
        "opencode-go": {
            "base_url": OPENCODE_GO_BASE_URL,
            "api_key": "",
            "current_model": "qwen3.6-plus",
            "advisor_model": "",
            "custom_models": ["qwen3.6-plus"],
            "native_compat": True,
            "context_window": 1048576,
            "max_output_tokens": 8192,
            "context_reserve_tokens": 8192,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "ip_family": "ipv6-preferred",
            "haiku_model": "qwen3.5-plus",
            "subagent_model": "qwen3.6-plus",
            "model_endpoints": {},
        },
        "vllm": {
            "base_url": "http://127.0.0.1:8000",
            "api_key": "dummy",
            "current_model": "my-model",
            "advisor_model": "",
            "custom_models": ["my-model"],
            "native_compat": True,
            "context_window": 32768,
            "max_output_tokens": 4096,
            "temperature": 0.7,
            "top_p": 0.8,
            "context_reserve_tokens": 1024,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
        },
        "lm-studio": {
            "base_url": "http://127.0.0.1:1234/v1",
            "api_key": "",
            "current_model": "local-model",
            "advisor_model": "",
            "custom_models": ["local-model"],
            "native_compat": True,
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "context_window": 32768,
            "max_output_tokens": 4096,
            "temperature": 0.7,
            "top_p": 0.8,
            "context_reserve_tokens": 1024,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
        },
        "nvidia-hosted": {
            "base_url": "https://integrate.api.nvidia.com/v1",
            "api_key": "not-used",
            "current_model": "qwen/qwen3-coder-480b-a35b-instruct",
            "advisor_model": "",
            "custom_models": [],
            "native_compat": False,
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "context_window": 65536,
            "max_output_tokens": 4096,
            "temperature": 0.7,
            "top_p": 0.8,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
        },
        "self-hosted-nim": {
            "base_url": "http://127.0.0.1:8000",
            "api_key": "not-used",
            "current_model": "model",
            "advisor_model": "",
            "custom_models": ["model"],
            "native_compat": True,
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "context_window": 32768,
            "max_output_tokens": 4096,
            "temperature": 0.7,
            "top_p": 0.8,
            "context_reserve_tokens": 1024,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
        },
        "openrouter": {
            "base_url": "https://openrouter.ai/api/v1",
            "api_key": "",
            "current_model": "nvidia/nemotron-3-ultra-550b-a55b:free",
            "advisor_model": "",
            "custom_models": [],
            "native_compat": False,
            "rate_limit_rpm": 0,
            "rate_limit_status": False,
            "context_window": 262144,
            "max_output_tokens": 8192,
            "temperature": 0.7,
            "top_p": 0.8,
            "context_reserve_tokens": 1024,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
        },
        "fireworks": {
            "base_url": FIREWORKS_INFERENCE_BASE_URL,
            "api_key": "",
            "current_model": "accounts/fireworks/models/kimi-k2p5",
            "advisor_model": "",
            "custom_models": ["accounts/fireworks/models/kimi-k2p5"],
            "native_compat": True,
            "account_id": FIREWORKS_DEFAULT_ACCOUNT_ID,
            "model_api_base_url": FIREWORKS_API_BASE_URL,
            "context_window": 262144,
            "max_output_tokens": 8192,
            "context_reserve_tokens": 8192,
            "request_timeout_ms": DEFAULT_REQUEST_TIMEOUT_MS,
            "stream_enabled": True,
            "stream_word_chunking": False,
            "haiku_model": "accounts/fireworks/models/kimi-k2p5",
            "subagent_model": "accounts/fireworks/models/kimi-k2p5",
        },
    },
}


def deep_merge(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
    out = json.loads(json.dumps(a))
    for k, v in b.items():
        if isinstance(v, dict) and isinstance(out.get(k), dict):
            out[k] = deep_merge(out[k], v)
        else:
            out[k] = v
    return out


def apply_config_migrations(cfg: dict[str, Any]) -> None:
    migrations = cfg.setdefault("migrations", {})
    if not isinstance(migrations, dict):
        migrations = {}
        cfg["migrations"] = migrations

    marker = "nvidia_hosted_router_default_20260509"
    if not migrations.get(marker):
        pcfg = cfg.get("providers", {}).get("nvidia-hosted", {})
        if isinstance(pcfg, dict) and bool(pcfg.get("native_compat", False)):
            pcfg["native_compat"] = False
        migrations[marker] = True

    marker = "lm_studio_native_default_20260523"
    if not migrations.get(marker):
        pcfg = cfg.get("providers", {}).get("lm-studio", {})
        if isinstance(pcfg, dict):
            pcfg["native_compat"] = True
        migrations[marker] = True

    marker = "default_timeout_5m_20260513"
    if not migrations.get(marker):
        # Historical marker kept for configs that have already recorded it.
        # Do not rewrite 10/30 minute values here anymore: those can now be
        # deliberate long-running timeout profiles.
        migrations[marker] = True

    marker = "default_timeout_2m_20260514"
    if not migrations.get(marker):
        # Historical marker only. The 2-minute default was too short for
        # 50k+ token hosted requests, so newer migrations no longer apply it.
        migrations[marker] = True

    marker = "default_timeout_restore_5m_20260515"
    if not migrations.get(marker):
        for pcfg in (cfg.get("providers") or {}).values():
            if not isinstance(pcfg, dict):
                continue
            if positive_int(pcfg.get("request_timeout_ms")) == 120000 and not pcfg.get("llm_preset"):
                pcfg["request_timeout_ms"] = DEFAULT_REQUEST_TIMEOUT_MS
                if "stream_idle_timeout_ms" not in pcfg:
                    pcfg["stream_idle_timeout_ms"] = DEFAULT_REQUEST_TIMEOUT_MS
        migrations[marker] = True

    marker = "nvidia_context_window_32k_20260513"
    if not migrations.get(marker):
        pcfg = cfg.get("providers", {}).get("nvidia-hosted", {})
        if isinstance(pcfg, dict) and not positive_int(pcfg.get("context_window")):
            pcfg["context_window"] = nvidia_hosted_context_default(str(pcfg.get("current_model") or ""))
        migrations[marker] = True

    marker = "nvidia_context_window_unforce_32k_20260513"
    if not migrations.get(marker):
        pcfg = cfg.get("providers", {}).get("nvidia-hosted", {})
        if isinstance(pcfg, dict) and positive_int(pcfg.get("context_window")) == 32768:
            pcfg["context_window"] = nvidia_hosted_context_default(str(pcfg.get("current_model") or ""))
        migrations[marker] = True

    marker = "stream_enabled_default_true_20260513"
    if not migrations.get(marker):
        for pcfg in (cfg.get("providers") or {}).values():
            if isinstance(pcfg, dict) and "stream_enabled" not in pcfg:
                pcfg["stream_enabled"] = True
        migrations[marker] = True

    marker = "default_channel_delivery_native_20260520"
    if not migrations.get(marker):
        ccfg = cfg.setdefault("claude_code", {})
        if not isinstance(ccfg, dict):
            ccfg = {}
            cfg["claude_code"] = ccfg
        if normalize_channel_delivery(ccfg.get("channel_delivery")) == "stdin":
            ccfg["channel_delivery"] = "native"
        migrations[marker] = True

    marker = "default_channel_delivery_llm_20260523"
    if not migrations.get(marker):
        ccfg = cfg.setdefault("claude_code", {})
        if not isinstance(ccfg, dict):
            ccfg = {}
            cfg["claude_code"] = ccfg
        if normalize_channel_delivery(ccfg.get("channel_delivery")) == "native":
            ccfg["channel_delivery"] = "llm"
        migrations[marker] = True

    marker = "rate_limit_defaults_off_20260526"
    if not migrations.get(marker):
        providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
        for provider_name in ("ollama", "ollama-cloud", "nvidia-hosted", "self-hosted-nim"):
            pcfg = providers.get(provider_name)
            if not isinstance(pcfg, dict):
                continue
            old_default_rpm = str(pcfg.get("rate_limit_rpm", "")).strip() == "40"
            old_default_status = bool(pcfg.get("rate_limit_status", True))
            if old_default_rpm and old_default_status:
                pcfg["rate_limit_rpm"] = 0
                pcfg["rate_limit_status"] = False
        pcfg = providers.get("lm-studio")
        if (
            isinstance(pcfg, dict)
            and str(pcfg.get("rate_limit_rpm", "")).strip() == "0"
            and bool(pcfg.get("rate_limit_status", True))
        ):
            pcfg["rate_limit_status"] = False
        migrations[marker] = True

    marker = "opencode_go_qwen36_plus_context_1m_20260530"
    if not migrations.get(marker):
        providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
        pcfg = providers.get("opencode-go")
        if (
            isinstance(pcfg, dict)
            and is_qwen36_plus_model_id(str(pcfg.get("current_model") or ""))
            and positive_int(pcfg.get("context_window")) == 262144
        ):
            pcfg["context_window"] = 1048576
        migrations[marker] = True

    marker = "anthropic_drop_preset_output_tokens_20260610"
    if not migrations.get(marker):
        # Older anthropic presets force-wrote max_output_tokens (2048/4096/6144/8192),
        # which pinned CLAUDE_CODE_MAX_OUTPUT_TOKENS and overrode Claude Code's native
        # per-model default in routed mode. Those values are no longer written. Drop a
        # stale preset-origin value so existing routed configs recover the native cap.
        # Trade-off (matches the rate_limit precedent): a user who deliberately set one
        # of these exact round numbers via the CLI is cleared once; re-setting persists.
        providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
        pcfg = providers.get("anthropic")
        if isinstance(pcfg, dict) and positive_int(pcfg.get("max_output_tokens")) in (2048, 4096, 6144, 8192):
            pcfg.pop("max_output_tokens", None)
        migrations[marker] = True

    marker = "opencode_default_ipv6_preferred_20260611"
    if not migrations.get(marker):
        providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
        for provider_name in OPENCODE_PROVIDER_NAMES:
            pcfg = providers.get(provider_name)
            if isinstance(pcfg, dict) and not pcfg.get("ip_family"):
                pcfg["ip_family"] = "ipv6-preferred"
        migrations[marker] = True


_config_cache: dict[str, Any] | None = None
_config_cache_mtime: float = 0.0


def load_config() -> dict[str, Any]:
    global _config_cache, _config_cache_mtime
    try:
        mtime = CONFIG_PATH.stat().st_mtime
    except OSError:
        mtime = 0.0
    if _config_cache is not None and mtime == _config_cache_mtime:
        return json.loads(json.dumps(_config_cache))
    if CONFIG_PATH.exists():
        try:
            data = json.loads(CONFIG_PATH.read_text())
        except Exception:
            data = {}
    else:
        data = {}
    cfg = deep_merge(DEFAULT_CONFIG, data)
    apply_config_migrations(cfg)
    cloud = cfg["providers"].get("ollama-cloud", {})
    local_key = cfg["providers"].get("ollama", {}).get("api_key", "")
    if not cloud.get("api_key") and local_key and local_key not in ("ollama", "dummy", "not-used"):
        cloud["api_key"] = local_key
    for provider_name, pcfg in cfg.get("providers", {}).items():
        if isinstance(pcfg, dict):
            if pcfg.get("current_model"):
                pcfg["current_model"] = normalize_model_id(provider_name, str(pcfg["current_model"]))
            if isinstance(pcfg.get("custom_models"), list):
                pcfg["custom_models"] = [normalize_model_id(provider_name, str(mid)) for mid in pcfg["custom_models"] if str(mid).strip()]
    _config_cache = cfg
    _config_cache_mtime = mtime
    return cfg


def invalidate_config_cache() -> None:
    global _config_cache, _config_cache_mtime
    _config_cache = None
    _config_cache_mtime = 0.0


def save_config(cfg: dict[str, Any]) -> None:
    global _config_cache, _config_cache_mtime
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    tmp = CONFIG_PATH.with_name(f"{CONFIG_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
    tmp.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n")
    os.chmod(tmp, 0o600)
    tmp.replace(CONFIG_PATH)
    _config_cache = cfg
    try:
        _config_cache_mtime = CONFIG_PATH.stat().st_mtime
    except OSError:
        _config_cache_mtime = 0.0


def clear_model_cache() -> None:
    invalidate_config_cache()
    try:
        CLAUDE_GATEWAY_CACHE.unlink()
    except FileNotFoundError:
        pass
    try:
        MODEL_LIST_CACHE_PATH.unlink()
    except FileNotFoundError:
        pass
    try:
        MODEL_REGISTRY_PATH.unlink()
    except FileNotFoundError:
        pass


def normalize_provider(name: str) -> str:
    key = name.strip().lower().replace("_", "-").replace(" ", "-")
    if key not in PROVIDER_ALIASES:
        raise SystemExit(f"Unknown provider: {name}\nKnown: {', '.join(PROVIDER_LABELS)}")
    return PROVIDER_ALIASES[key]


def slug(s: str) -> str:
    return re.sub(r"-+", "-", re.sub(r"[^a-zA-Z0-9_.-]+", "-", s.lower())).strip("-") or "model"


def model_sort_key(model_id: str) -> tuple[str, str]:
    return (model_id.casefold(), model_id)


def sorted_model_ids(ids: list[str]) -> list[str]:
    return sorted(ids, key=model_sort_key)


def unique_model_ids(provider: str, ids: list[str]) -> list[str]:
    seen: set[str] = set()
    out: list[str] = []
    for raw in ids:
        mid = normalize_model_id(provider, str(raw))
        if not mid:
            continue
        key = mid.casefold()
        if key in seen:
            continue
        seen.add(key)
        out.append(mid)
    return out


def normalize_model_id(provider: str, model_id: str) -> str:
    model_id = str(model_id or "").strip() if provider == "deepseek" else strip_claude_context_suffix(model_id).strip()
    if provider == "ollama-cloud" and model_id.endswith(":cloud"):
        return model_id[:-6]
    return model_id


def strip_claude_context_suffix(model_id: str | None) -> str:
    text = str(model_id or "").strip()
    return re.sub(r"\[(?:1m)\]\s*$", "", text, flags=re.IGNORECASE)


def alias_for(provider: str, model_id: str) -> str:
    if provider == "nvidia-hosted" and model_id.startswith("claude-"):
        return model_id
    return f"claude-any-{provider}-{slug(model_id)}"


def unslug_provider_alias(provider: str, alias: str, model_map: dict[str, str]) -> str | None:
    alias = strip_claude_context_suffix(alias)
    if alias in model_map:
        return model_map[alias]
    prefix = f"claude-any-{provider}-"
    if alias.startswith(prefix):
        target_slug = alias[len(prefix):]
        for _, model_id in model_map.items():
            if slug(model_id) == target_slug:
                return model_id
    return None


def display_name(provider: str, model_id: str) -> str:
    label = PROVIDER_LABELS.get(provider, provider).replace("-", " ")
    cleaned = model_id
    if provider == "nvidia-hosted" and cleaned.startswith("claude-nvidia-"):
        cleaned = cleaned[len("claude-"):]
        return cleaned.replace("/", " ").replace("-", " ").replace("_", " ").title().replace("Nvidia", "Nvidia")
    cleaned = cleaned.replace("/", " ").replace("-", " ").replace("_", " ")
    return f"{label} {cleaned}".title().replace("Vllm", "vLLM").replace("Nvidia", "Nvidia")


def model_object(provider: str, model_id: str, pcfg: dict[str, Any] | None = None) -> dict[str, Any]:
    model_id = normalize_model_id(provider, model_id)
    alias = alias_for(provider, model_id)
    obj = {
        "id": alias,
        "type": "model",
        "display_name": display_name(provider, model_id),
        "created_at": 1700000000,
        "object": "model",
        "created": 1700000000,
        "owned_by": f"claude-any/{provider}",
        "claude_any": {"provider": provider, "upstream_model": model_id},
    }
    if provider in OPENCODE_PROVIDER_NAMES:
        endpoint = opencode_endpoint_kind(provider, model_id, pcfg)
        obj["claude_any"]["opencode_endpoint"] = endpoint
        obj["claude_any"]["router_supported"] = opencode_model_supported_by_router(provider, model_id, pcfg)
    return obj


def join_url(base: str, path: str) -> str:
    base = base.rstrip("/")
    if base.endswith("/v1") and path.startswith("/v1/"):
        return base + path[3:]
    return base + path


def inbound_query_has_beta_flag(request_path: str) -> bool:
    """True when Claude Code's inbound request carried ?beta=true.

    Claude Code signals beta-feature opt-in (e.g. the context-1m long-context
    beta) via the ``beta=true`` query parameter on /v1/messages. The router
    parses only the path and would otherwise drop it, so callers re-attach the
    flag when forwarding upstream. Only the known ``beta`` flag is inspected;
    other query parameters are intentionally not forwarded.
    """
    query = urllib.parse.urlparse(request_path).query
    for value in urllib.parse.parse_qs(query).get("beta", []):
        if value.strip().lower() in ("true", "1"):
            return True
    return False


def upstream_messages_query(pcfg: dict[str, Any], request_path: str) -> str:
    """Query string to append to the upstream /v1/messages URL.

    A configured ``force_query_string`` (testing override) wins, letting an
    operator inject an arbitrary raw query (e.g. "beta=true") from the options
    screen. Otherwise the inbound ``beta=true`` flag is propagated so beta
    features like the context-1m long-context request reach the upstream
    unchanged (do_POST parses only the path and drops the original query).
    """
    forced = str(pcfg.get("force_query_string") or "").strip().lstrip("?").strip()
    if forced:
        return forced
    if inbound_query_has_beta_flag(request_path):
        return "beta=true"
    return ""


def read_env_file(path: Path) -> dict[str, str]:
    env: dict[str, str] = {}
    if not path.exists():
        return env
    for line in path.read_text(errors="ignore").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        k, v = line.split("=", 1)
        env[k.strip()] = v.strip().strip("'\"")
    return env


def meaningful_key_value(value: Any) -> bool:
    if value is None:
        return False
    text = str(value).strip()
    return bool(text and text not in ("dummy", "not-used", "ollama"))


API_KEY_CLEAR_TOKENS = {"clear", "unset", "none", "null", "off", "delete", "remove"}


def api_key_clear_requested(value: Any) -> bool:
    return str(value or "").strip().lower() in API_KEY_CLEAR_TOKENS


def parse_api_key_list(value: Any) -> list[str]:
    if value is None:
        return []
    raw_items: list[Any]
    if isinstance(value, (list, tuple, set)):
        raw_items = []
        for item in value:
            raw_items.extend(parse_api_key_list(item))
    else:
        text = str(value)
        if re.search(r"[,;]", text):
            # Comma/semicolon-separated input often arrives through terminals or
            # web consoles with visual soft-wrap line breaks inside a long key.
            # Treat indented continuation lines inside a comma field as part of
            # the same key, while preserving non-indented newlines as separators.
            raw_items = []
            for field in re.split(r"[,;]+", text):
                current = ""
                for line in str(field).splitlines() or [str(field)]:
                    if not line.strip():
                        continue
                    if current and line[:1].isspace():
                        current += line.strip()
                    else:
                        if current:
                            raw_items.append(current)
                        current = line.strip()
                if current:
                    raw_items.append(current)
        else:
            raw_items = re.split(r"[\r\n]+", text)
    keys: list[str] = []
    seen: set[str] = set()
    for item in raw_items:
        key = str(item or "").strip()
        if not meaningful_key_value(key) or key in seen:
            continue
        keys.append(key)
        seen.add(key)
    return keys


def provider_config_api_keys(provider: str, pcfg: dict[str, Any]) -> list[str]:
    keys: list[str] = []
    if provider == "nvidia-hosted":
        keys.extend(parse_api_key_list(nvidia_api_key()))
    keys.extend(parse_api_key_list(pcfg.get("api_keys")))
    keys.extend(parse_api_key_list(pcfg.get("api_key")))
    deduped: list[str] = []
    seen: set[str] = set()
    for key in keys:
        if key in seen:
            continue
        deduped.append(key)
        seen.add(key)
    return deduped


def provider_has_api_key(provider: str, pcfg: dict[str, Any]) -> bool:
    return bool(provider_config_api_keys(provider, pcfg))


def provider_api_key_count(provider: str, pcfg: dict[str, Any]) -> int:
    return len(provider_config_api_keys(provider, pcfg))


def provider_primary_api_key(provider: str, pcfg: dict[str, Any]) -> str:
    keys = provider_config_api_keys(provider, pcfg)
    return keys[0] if keys else ""


def provider_api_key_rotation_name(provider: str, pcfg: dict[str, Any]) -> str:
    return f"{provider}:{str(pcfg.get('base_url') or default_base_url(provider)).rstrip('/')}"


def select_provider_api_key(provider: str, pcfg: dict[str, Any], *, rotate: bool = True) -> str:
    keys = provider_config_api_keys(provider, pcfg)
    if not keys:
        return ""
    if not rotate or len(keys) == 1:
        return keys[0]
    name = provider_api_key_rotation_name(provider, pcfg)
    # Skip keys that are resting after a 429; round-robin over the live ones so a
    # rate-limited key rests while the rest keep serving. If every key is cooling,
    # use the one that frees up soonest.
    now = time.time()
    live = [k for k in keys if api_key_cooldown_until(provider, pcfg, k) <= now]
    if not live:
        soonest = min(keys, key=lambda k: api_key_cooldown_until(provider, pcfg, k))
        router_log("DEBUG", f"api_key_round_robin provider={provider} all_cooling={len(keys)} using_soonest")
        return soonest
    with _API_KEY_ROTATION_LOCK:
        counter = _API_KEY_ROTATION_CURSOR.get(name, 0)
        _API_KEY_ROTATION_CURSOR[name] = counter + 1
    idx = counter % len(live)
    if len(live) < len(keys):
        router_log("DEBUG", f"api_key_round_robin provider={provider} key_index={idx + 1}/{len(live)} cooling={len(keys) - len(live)}")
    else:
        router_log("DEBUG", f"api_key_round_robin provider={provider} key_index={idx + 1}/{len(keys)}")
    return live[idx]


def env_bool(value: str | None, default: bool | None = None) -> bool | None:
    if value is None:
        return default
    text = value.strip().lower()
    if text in ("1", "true", "yes", "on", "y"):
        return True
    if text in ("0", "false", "no", "off", "n"):
        return False
    return default


def load_dotenv_into_environ(path: Path, *, override: bool = True) -> None:
    for key, value in read_env_file(path).items():
        if override or key not in os.environ:
            os.environ[key] = value


def executable_candidates(name: str) -> list[str]:
    if os.name == "nt" and not platform_path(name).suffix:
        return [f"{name}.exe", f"{name}.cmd", f"{name}.bat", name]
    return [name]


def executable_extra_dirs() -> list[Path]:
    dirs = [claude_any_user_bin_dir()]
    for env_name in ("UV_INSTALL_DIR", "CARGO_HOME"):
        root = os.environ.get(env_name)
        if root:
            path = Path(root)
            dirs.append(path if path.name == "bin" else path / "bin")
    if os.name == "nt":
        appdata = os.environ.get("APPDATA")
        if appdata:
            dirs.append(platform_path(appdata) / "npm")
        local_appdata = os.environ.get("LOCALAPPDATA")
        if local_appdata:
            dirs.append(platform_path(local_appdata) / "Programs" / "nodejs")
        pyver = f"Python{sys.version_info.major}{sys.version_info.minor}"
        for env_name in ("APPDATA", "LOCALAPPDATA"):
            root = os.environ.get(env_name)
            if root:
                dirs.append(platform_path(root) / "Python" / pyver / "Scripts")
        try:
            import site

            dirs.append(platform_path(site.getuserbase()) / "Scripts")
        except Exception:
            pass
        dirs.append(platform_path(sys.executable).parent / "Scripts")
    else:
        dirs.extend(
            [
                HOME / ".local" / "bin",
                HOME / ".cargo" / "bin",
                HOME / ".npm-global" / "bin",
                HOME / ".bun" / "bin",
                Path(sys.executable).resolve().parent,
                Path("/usr/local/bin"),
                Path("/usr/bin"),
                Path("/bin"),
                Path("/opt/homebrew/bin"),
            ]
        )
    out: list[Path] = []
    seen: set[str] = set()
    for directory in dirs:
        key = str(directory)
        if key and key not in seen:
            seen.add(key)
            out.append(directory)
    return out


def find_executable(name: str) -> str | None:
    for candidate in executable_candidates(name):
        found = shutil.which(candidate)
        if found:
            return found
    for directory in executable_extra_dirs():
        for candidate in executable_candidates(name):
            path = directory / candidate
            if path.exists():
                return str(path)
    return None


def resolve_executable_for_subprocess(command: str) -> str:
    command = str(command or "").strip()
    if not command:
        return command
    pathish = Path(command).is_absolute() or os.sep in command or bool(os.altsep and os.altsep in command)
    if pathish:
        return command
    return find_executable(command) or command


def resolve_mcp_server_process(command: str, args: list[str]) -> tuple[str, list[str]]:
    command = str(command or "").strip()
    resolved = resolve_executable_for_subprocess(command)
    name = Path(command).name.lower()
    if resolved == command and name in ("uvx", "uvx.exe", "uvx.cmd", "uvx.bat"):
        uv = find_executable("uv")
        if uv:
            return uv, ["tool", "run", *args]
        if importlib.util.find_spec("uv") is not None:
            return sys.executable, ["-m", "uv", "tool", "run", *args]
    return resolved, args


def shell_command_string(args: list[str]) -> str:
    if os.name == "nt":
        # Claude Code on Windows runs hook commands through sh/bash, which treats
        # backslashes in unquoted Windows paths as escape characters (so
        # "C:\Users\djlov" becomes "C:Usersdjlov"). Convert backslashes to
        # forward slashes for path-like args (Python and sh both accept them on
        # Windows) and use POSIX quoting.
        normalized: list[str] = []
        for arg in args:
            looks_like_path = "\\" in arg and (
                (len(arg) >= 2 and arg[1] == ":")
                or arg.startswith("\\\\")
                or arg.endswith((".py", ".exe", ".cmd", ".bat", ".ps1"))
            )
            if looks_like_path:
                arg = arg.replace("\\", "/")
            normalized.append(shlex.quote(arg))
        return " ".join(normalized)
    return " ".join(shlex.quote(arg) for arg in args)


def find_tool_guard_script() -> Path | None:
    candidates = [
        Path(__file__).resolve().with_name("claude-any-tool-guard.py"),
        claude_any_user_bin_dir() / "claude-any-tool-guard.py",
        claude_any_user_bin_dir() / "claude-any-tool-guard",
        HOME / ".local" / "bin" / "claude-any-tool-guard.py",
        HOME / ".local" / "bin" / "claude-any-tool-guard",
    ]
    found = find_executable("claude-any-tool-guard")
    if found:
        candidates.append(Path(found))
    found_py = find_executable("claude-any-tool-guard.py")
    if found_py:
        candidates.append(Path(found_py))
    for path in candidates:
        if path.exists():
            return path
    return None


def claude_any_tool_guard_command() -> str | None:
    script = find_tool_guard_script()
    if script is None:
        return None
    if script.suffix == ".py":
        return shell_command_string([sys.executable, str(script)])
    return shell_command_string([str(script)])


TOOL_GUARD_EVENTS_WITH_TOOL_MATCHER: tuple[str, ...] = (
    "PreToolUse",
    "PostToolUse",
    "PostToolUseFailure",
    "PermissionRequest",
    "PermissionDenied",
)

TOOL_GUARD_EVENTS_WITHOUT_MATCHER: tuple[str, ...] = (
    "PostToolBatch",
    "SessionStart",
    "SessionEnd",
    "Setup",
    "UserPromptSubmit",
    "UserPromptExpansion",
    "Stop",
    "StopFailure",
    "InstructionsLoaded",
    "ConfigChange",
    "CwdChanged",
    "Notification",
    "SubagentStart",
    "SubagentStop",
    "TeammateIdle",
    "TaskCreated",
    "TaskCompleted",
    "PreCompact",
    "PostCompact",
    "WorktreeCreate",
    "WorktreeRemove",
    "Elicitation",
    "ElicitationResult",
)


def install_tool_guard_hooks() -> None:
    command = claude_any_tool_guard_command()
    if not command:
        print("Claude Any warning: tool guard hook was not installed; claude-any-tool-guard was not found.", flush=True)
        return

    if CLAUDE_SETTINGS_PATH.exists():
        try:
            settings = json.loads(CLAUDE_SETTINGS_PATH.read_text(encoding="utf-8"))
            if not isinstance(settings, dict):
                settings = {}
        except Exception as exc:
            print(f"Claude Any warning: could not read {CLAUDE_SETTINGS_PATH} ({type(exc).__name__}); tool guard hook was not installed.", flush=True)
            return
    else:
        settings = {}

    hooks = settings.setdefault("hooks", {})
    if not isinstance(hooks, dict):
        print(f"Claude Any warning: {CLAUDE_SETTINGS_PATH} has non-object hooks; tool guard hook was not installed.", flush=True)
        return

    changed = False
    all_events: tuple[tuple[str, bool], ...] = tuple(
        (event, True) for event in TOOL_GUARD_EVENTS_WITH_TOOL_MATCHER
    ) + tuple(
        (event, False) for event in TOOL_GUARD_EVENTS_WITHOUT_MATCHER
    )
    for event, with_matcher in all_events:
        groups = hooks.setdefault(event, [])
        if not isinstance(groups, list):
            print(f"Claude Any warning: {CLAUDE_SETTINGS_PATH} hooks.{event} is not a list; tool guard hook was not installed.", flush=True)
            return
        existing = False
        for group in groups:
            if not isinstance(group, dict):
                continue
            handlers = group.get("hooks")
            if not isinstance(handlers, list):
                continue
            for handler in handlers:
                if isinstance(handler, dict) and "claude-any-tool-guard" in str(handler.get("command", "")):
                    existing = True
                    if handler.get("command") != command:
                        handler["command"] = command
                        changed = True
        if existing:
            continue
        group: dict[str, Any] = {"hooks": [{"type": "command", "command": command}]}
        if with_matcher:
            group["matcher"] = "*"
        groups.append(group)
        changed = True

    if changed:
        CLAUDE_SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
        tmp = CLAUDE_SETTINGS_PATH.with_name(f"{CLAUDE_SETTINGS_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(settings, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
        try:
            os.chmod(tmp, 0o600)
        except Exception:
            pass
        tmp.replace(CLAUDE_SETTINGS_PATH)


STATUSLINE_SCRIPT = r'''#!/usr/bin/env python3
import json
import os
import re
import sys
import time
from pathlib import Path

HOME = Path.home()


def default_config_dir():
    configured = os.environ.get("CLAUDE_ANY_CONFIG_DIR")
    if configured:
        return Path(configured)
    if os.name == "nt":
        root = os.environ.get("APPDATA") or os.environ.get("LOCALAPPDATA")
        if root:
            return Path(root) / "claude-any"
        return HOME / "AppData" / "Roaming" / "claude-any"
    return HOME / ".config" / "claude-any"


CONFIG_DIR = default_config_dir()
CONFIG_PATH = CONFIG_DIR / "config.json"
STATE_PATH = CONFIG_DIR / "rate-limit-state.json"
ACTIVITY_PATH = CONFIG_DIR / "router-activity.json"
CONTEXT_PATH = CONFIG_DIR / "context-usage.json"
CHAT_MESSAGES_PATH = CONFIG_DIR / "chat-messages.jsonl"
CHANNEL_LLM_CURSOR_PATH = CONFIG_DIR / "channel-llm-cursor.json"
PALETTE = (203, 209, 215, 221, 229, 187, 151, 116, 111, 147, 183, 219)


def load_json(path, default):
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
        return value if isinstance(value, type(default)) else default
    except Exception:
        return default


def color(text):
    if os.environ.get("CLAUDE_ANY_STATUSLINE_ANSI", "1").lower() in ("0", "false", "no"):
        return text
    phase = int(time.monotonic() * 8)
    out = []
    for i, ch in enumerate(text):
        if ch.isspace():
            out.append(ch)
        else:
            out.append(f"\033[1;38;5;{PALETTE[(phase + i) % len(PALETTE)]}m{ch}\033[0m")
    return "".join(out)


def gray(text):
    if os.environ.get("CLAUDE_ANY_STATUSLINE_ANSI", "1").lower() in ("0", "false", "no"):
        return text
    return f"\033[38;5;245m{text}\033[0m"


def token_text(value):
    try:
        return f"{int(value):,} tok"
    except Exception:
        return f"{value} tok"


def token_part(value, muted=False):
    text = token_text(value)
    return gray(text) if muted else color(text)


def context_status_text(context, provider, model):
    if not isinstance(context, dict):
        return ""
    if str(context.get("provider") or "") != str(provider or ""):
        return ""
    try:
        age = time.time() - float(context.get("updated_at") or 0)
    except Exception:
        age = 999999
    if age < 0 or age > 3600:
        return ""
    try:
        tokens = int(context.get("tokens") or 0)
    except Exception:
        tokens = 0
    if tokens <= 0:
        return ""
    try:
        limit = int(context.get("context_limit") or 0)
    except Exception:
        limit = 0
    if limit > 0:
        pct = (tokens / limit) * 100.0
        return f"ctx {tokens:,}/{limit:,} tok ({pct:.1f}%)"
    return f"ctx {tokens:,} tok"


def _as_int(value, default=0):
    try:
        return int(value)
    except Exception:
        return default


def session_context_status_text(session):
    if not isinstance(session, dict):
        return ""
    context_window = session.get("context_window")
    if not isinstance(context_window, dict):
        return ""
    usage = context_window.get("current_usage")
    if not isinstance(usage, dict):
        return ""
    tokens = (
        _as_int(usage.get("input_tokens"))
        + _as_int(usage.get("cache_creation_input_tokens"))
        + _as_int(usage.get("cache_read_input_tokens"))
    )
    if tokens <= 0:
        tokens = _as_int(context_window.get("total_input_tokens"))
    if tokens <= 0:
        return ""
    limit = _as_int(context_window.get("context_window_size"))
    pct = context_window.get("used_percentage")
    if limit > 0:
        try:
            pct_value = float(pct) if pct is not None else (tokens / limit) * 100.0
        except Exception:
            pct_value = (tokens / limit) * 100.0
        return f"ctx {tokens:,}/{limit:,} tok ({pct_value:.1f}%)"
    return f"ctx {tokens:,} tok"


def _status_as_string_list(value):
    if value is None:
        return []
    if isinstance(value, str):
        text = value.strip()
        if not text:
            return []
        try:
            parsed = json.loads(text)
            if isinstance(parsed, list):
                return _status_as_string_list(parsed)
        except Exception:
            pass
        return [text]
    if isinstance(value, (list, tuple, set)):
        out = []
        for item in value:
            out.extend(_status_as_string_list(item))
        return out
    return [str(value).strip()] if str(value).strip() else []


def _status_channel_message_skip_reason(message):
    visibility = str(message.get("visibility") or "user").strip().lower()
    if visibility in {"hidden", "internal", "transport", "control", "system"}:
        return f"visibility_{visibility}"
    recipients = {item.strip().lower() for item in _status_as_string_list(message.get("recipients"))}
    if "internal" in recipients:
        return "recipient_internal"
    delivery = _status_as_string_list(message.get("delivery"))
    if delivery:
        normalized_delivery = {item.strip().lower() for item in delivery}
        if not ({"all", "*", "llm"} & normalized_delivery):
            return "delivery_not_llm"
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    meta_kind = str(meta.get("kind") or meta.get("type") or meta.get("event_type") or meta.get("eventType") or meta.get("event") or meta.get("status") or "").strip().lower()
    if meta_kind in {"connection", "connected", "disconnect", "disconnected", "endpoint", "heartbeat", "initialized", "init", "keepalive", "ping", "pong", "ready", "status", "system"}:
        return meta_kind
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip().lower()
    kind = str(message.get("kind") or "").strip().lower()
    if not body:
        return "empty"
    if kind in {"connection", "connected", "heartbeat", "keepalive"}:
        return kind
    if re.fullmatch(r"[a-z0-9_.:-]{1,80}\.(ws|sse)\.connected", body):
        return "transport_connected"
    if meta.get("llm_direct_delivered"):
        return "llm_direct_delivered"
    return ""


def channel_pending_status_count():
    cursor = load_json(CHANNEL_LLM_CURSOR_PATH, {})
    last_id = _as_int(cursor.get("last_id") if isinstance(cursor, dict) else 0)
    count = 0
    try:
        if not CHAT_MESSAGES_PATH.exists():
            return 0
        with CHAT_MESSAGES_PATH.open("r", encoding="utf-8") as f:
            for line in f:
                try:
                    item = json.loads(line)
                    item_id = int(item.get("id") or 0)
                except Exception:
                    continue
                if item_id <= last_id:
                    continue
                if _status_channel_message_skip_reason(item):
                    continue
                count += 1
                if count >= 999:
                    return count
    except Exception:
        return 0
    return count


def is_claude_any_session(session):
    if os.environ.get("CLAUDE_ANY_PROVIDER") or os.environ.get("CLAUDE_ANY_MODEL_ALIAS"):
        return True
    if os.environ.get("CLAUDE_ANY_STATUSLINE_FORCE", "").lower() in ("1", "true", "yes", "on"):
        return True
    model_name = ((session.get("model") or {}).get("display_name") if isinstance(session.get("model"), dict) else None) or ""
    return str(model_name).startswith("claude-any-")


def display_capacity(rpm):
    if rpm <= 1:
        return rpm
    reserve = 1 if rpm <= 20 else max(1, int((rpm * 0.05) + 0.999))
    return max(1, rpm - reserve)


def main():
    try:
        session = json.load(sys.stdin)
        if not isinstance(session, dict):
            session = {}
    except Exception:
        session = {}
    if not is_claude_any_session(session):
        return
    cfg = load_json(CONFIG_PATH, {})
    providers = cfg.get("providers") if isinstance(cfg.get("providers"), dict) else {}
    provider = str(cfg.get("current_provider") or "")
    pcfg = providers.get(provider) if isinstance(providers.get(provider), dict) else {}
    router_debug_external = bool(cfg.get("router_debug_external_access", False))
    rpm_status = bool(pcfg.get("rate_limit_status", False))
    migrations = cfg.get("migrations") if isinstance(cfg.get("migrations"), dict) else {}
    if not migrations.get("rate_limit_defaults_off_20260526"):
        if (
            provider in ("ollama", "ollama-cloud", "nvidia-hosted", "self-hosted-nim")
            and str(pcfg.get("rate_limit_rpm", "")).strip() == "40"
        ):
            rpm_status = False
        elif provider == "lm-studio" and str(pcfg.get("rate_limit_rpm", "")).strip() == "0":
            rpm_status = False
    model = str(pcfg.get("current_model") or "")
    raw_rpm = pcfg.get("rate_limit_rpm", 0)
    try:
        rpm = int(raw_rpm)
    except Exception:
        rpm = 0
    state = load_json(STATE_PATH, {})
    activity = load_json(ACTIVITY_PATH, {})
    context = load_json(CONTEXT_PATH, {})
    now = time.time()
    key = f"{provider}:__global__" if provider else ""
    entry = state.get(key) if key else None
    if not isinstance(entry, dict):
        legacy_key = f"{provider}:{model}" if provider and model else ""
        entry = state.get(legacy_key) if legacy_key else None
    if not isinstance(entry, dict):
        prefix = f"{provider}:"
        candidates = [(k, v) for k, v in state.items() if isinstance(k, str) and k.startswith(prefix) and isinstance(v, dict)]
        if not candidates:
            candidates = [(k, v) for k, v in state.items() if isinstance(v, dict)]
        if candidates:
            key, entry = max(candidates, key=lambda item: float(item[1].get("updated_at") or 0))
    timestamps = entry.get("timestamps") if isinstance(entry, dict) else []
    if isinstance(entry, dict):
        try:
            rpm = int(entry.get("rpm") or rpm)
        except Exception:
            pass
    try:
        last_wait = float(entry.get("last_wait") or 0.0) if isinstance(entry, dict) else 0.0
    except Exception:
        last_wait = 0.0
    try:
        penalty_until = float(entry.get("penalty_until") or 0.0) if isinstance(entry, dict) else 0.0
    except Exception:
        penalty_until = 0.0
    try:
        updated_at = float(entry.get("updated_at") or 0.0) if isinstance(entry, dict) else 0.0
    except Exception:
        updated_at = 0.0
    server_remaining = entry.get("server_remaining") if isinstance(entry, dict) else None
    server_reset_seconds = entry.get("server_reset_seconds") if isinstance(entry, dict) else None
    server_rpm = entry.get("server_rpm") if isinstance(entry, dict) else None
    used = len([ts for ts in (timestamps or []) if isinstance(ts, (int, float)) and 0.0 <= now - float(ts) < 60.0])
    model_name = ((session.get("model") or {}).get("display_name") if isinstance(session.get("model"), dict) else None) or model or "model"
    current_dir = ((session.get("workspace") or {}).get("current_dir") if isinstance(session.get("workspace"), dict) else None) or session.get("cwd") or ""
    dir_name = Path(current_dir).name if current_dir else ""
    left = f"[{model_name}]"
    if dir_name:
        left += f" {dir_name}"
    status_parts = []
    ctx_text = session_context_status_text(session) or context_status_text(context, provider, model)
    if ctx_text:
        status_parts.append(gray(ctx_text))
    channel_pending = channel_pending_status_count()
    if channel_pending > 0:
        status_parts.append(color(f"channel queue {channel_pending}"))
    if router_debug_external:
        status_parts.append(color("debug external"))
    if rpm_status:
        if rpm > 0:
            shown_limit = display_capacity(rpm)
            shown_used = min(used, shown_limit)
            rpm_text = f"RPM used: {shown_used}/{shown_limit}"
        else:
            rpm_text = f"RPM used: {used}/min (unmanaged)"
        if server_rpm or server_remaining is not None or server_reset_seconds is not None:
            parts = []
            if server_remaining is not None:
                parts.append(f"remaining {server_remaining}")
            if server_rpm:
                parts.append(f"limit {server_rpm}")
            try:
                if server_reset_seconds is not None and float(server_reset_seconds) > 0:
                    parts.append(f"reset {float(server_reset_seconds):.0f}s")
            except Exception:
                pass
            if parts:
                rpm_text += " | server " + ", ".join(parts)
        if penalty_until > now:
            rpm_text += f" | wait {max(0.0, penalty_until - now):.0f}s"
        elif last_wait >= 0.5 and 0.0 <= now - updated_at < 60.0:
            rpm_text += f" | wait {last_wait:.1f}s"
        status_parts.append(color(rpm_text))
    activity_text = ""
    if isinstance(activity, dict):
        try:
            age = now - float(activity.get("updated_at") or 0)
        except Exception:
            age = 999999
        if 0 <= age < 180:
            event = str(activity.get("event") or "")
            if event == "retry":
                activity_text = color(f"retry {activity.get('attempt')}/{activity.get('total')}")
                wait = activity.get("wait")
                try:
                    if rpm_status and wait is not None and float(wait) > 0:
                        activity_text += " " + color(f"wait {float(wait):.0f}s")
                except Exception:
                    pass
                tokens = activity.get("tokens")
                if tokens:
                    activity_text += " " + color("last input") + " " + token_part(tokens, muted=rpm_status)
            elif event == "request":
                tokens = activity.get("tokens")
                activity_text = color(f"upstream {age:.0f}s")
                if tokens:
                    activity_text += " " + token_part(tokens, muted=rpm_status)
                output_tokens = activity.get("output_tokens")
                if output_tokens:
                    activity_text += " " + color("->") + " " + token_part(output_tokens, muted=rpm_status)
                chunks = activity.get("chunks")
                if chunks:
                    try:
                        activity_text += " " + color(f"({int(chunks):,} chunks)")
                    except Exception:
                        activity_text += " " + color(f"({chunks} chunks)")
            elif event in ("success", "error"):
                activity_text = color(f"{event} {age:.0f}s")
    if activity_text:
        status_parts.append(activity_text)
    status_text = " | ".join(status_parts)
    if status_text:
        print(f"{left} | {status_text}")
    else:
        print(left)


if __name__ == "__main__":
    main()
'''


def install_claude_any_statusline() -> None:
    try:
        CLAUDE_ANY_STATUSLINE_PATH.parent.mkdir(parents=True, exist_ok=True)
        if not CLAUDE_ANY_STATUSLINE_PATH.exists() or CLAUDE_ANY_STATUSLINE_PATH.read_text(encoding="utf-8") != STATUSLINE_SCRIPT:
            CLAUDE_ANY_STATUSLINE_PATH.write_text(STATUSLINE_SCRIPT, encoding="utf-8")
        try:
            os.chmod(CLAUDE_ANY_STATUSLINE_PATH, 0o700)
        except Exception:
            pass
        if CLAUDE_SETTINGS_PATH.exists():
            try:
                settings = json.loads(CLAUDE_SETTINGS_PATH.read_text(encoding="utf-8"))
                if not isinstance(settings, dict):
                    settings = {}
            except Exception as exc:
                print(f"Claude Any warning: could not read {CLAUDE_SETTINGS_PATH} ({type(exc).__name__}); status line was not installed.", flush=True)
                return
        else:
            settings = {}
        command = f"{shlex.quote(sys.executable)} {shlex.quote(str(CLAUDE_ANY_STATUSLINE_PATH))}"
        current = settings.get("statusLine")
        if isinstance(current, dict) and current.get("command") == command:
            return
        settings["statusLine"] = {
            "type": "command",
            "command": command,
            "padding": 0,
            "refreshInterval": 1000,
        }
        CLAUDE_SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
        tmp = CLAUDE_SETTINGS_PATH.with_name(f"{CLAUDE_SETTINGS_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(settings, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
        try:
            os.chmod(tmp, 0o600)
        except Exception:
            pass
        tmp.replace(CLAUDE_SETTINGS_PATH)
    except Exception as exc:
        print(f"Claude Any warning: could not install status line ({type(exc).__name__}: {exc}).", flush=True)


ADVISOR_SLASH_COMMAND = """---
description: Run the selected claude-any Advisor Model
argument-hint: [question or focus]
---

CLAUDE_ANY_ADVISOR_CALL

Focus: $ARGUMENTS

Use the Advisor Model selected in the claude-any launch menu. If the Advisor Model is off, explain how to enable it. Otherwise review the current conversation, tool history, and task state. Return concise guidance with the blocker, next concrete action, and validation step.
"""

ADVISOR_NATIVE_DISABLED_SLASH_COMMAND = """---
description: Claude Any Advisor unavailable in Claude Native mode
argument-hint: [ignored]
---

Claude Any Advisor is unavailable in direct Claude Native mode.

Do not run tools, shell commands, file searches, config scans, or environment checks for this command. Reply immediately with this exact status and the short enablement hint below.

This session bypasses the claude-any router, so /advisor cannot call the configured Advisor Model here. To use /advisor, launch a non-native provider or enable Anthropic routed mode in claude-any.
"""

ROUTER_DEBUG_SLASH_COMMAND = """---
description: Toggle claude-any router external debug access
argument-hint: [on|off|status]
---

CLAUDE_ANY_ROUTER_DEBUG_ACCESS

Value: $ARGUMENTS

Toggle claude-any router debug external access. With no argument, this toggles the current state. Use `on`, `off`, or `status` for explicit control.
"""

ROUTER_DEBUG_NATIVE_DISABLED_SLASH_COMMAND = """---
description: claude-any router debug unavailable in Claude Native mode
argument-hint: [ignored]
---

claude-any router debug controls are unavailable in direct Claude Native mode.

Do not run tools, shell commands, file searches, config scans, or environment checks for this command. Reply immediately with this exact status and the short enablement hint below.

This session bypasses the claude-any router. Launch a non-native provider or enable Anthropic routed mode to use /router-debug.
"""

CLAUDE_ANY_ADVISOR_COMMAND_MARKERS = (
    "CLAUDE_ANY_ADVISOR_CALL",
    "Run the selected claude-any Advisor Model",
    "Claude Any Advisor is unavailable in direct Claude Native mode",
)
CLAUDE_ANY_ROUTER_DEBUG_COMMAND_MARKERS = (
    "CLAUDE_ANY_ROUTER_DEBUG_ACCESS",
    "Toggle claude-any router external debug access",
    "claude-any router debug controls are unavailable in direct Claude Native mode",
)


def command_file_is_claude_any_owned(path: Path, markers: tuple[str, ...]) -> bool:
    try:
        text = path.read_text(encoding="utf-8", errors="replace")
    except Exception:
        return False
    return any(marker in text for marker in markers)


def remove_claude_any_advisor_command() -> None:
    """Remove the claude-any-owned /advisor command so Claude Code's built-in
    /advisor (the standard flow for the anthropic provider) surfaces."""
    try:
        path = CLAUDE_COMMANDS_DIR / "advisor.md"
        if path.exists() and command_file_is_claude_any_owned(path, CLAUDE_ANY_ADVISOR_COMMAND_MARKERS):
            path.unlink()
    except Exception as exc:
        print(f"Claude Any warning: could not remove claude-any /advisor command ({type(exc).__name__}: {exc}).", flush=True)


def install_claude_any_slash_commands(include_advisor: bool = True) -> None:
    try:
        CLAUDE_COMMANDS_DIR.mkdir(parents=True, exist_ok=True)
        commands = {
            "router-debug.md": ROUTER_DEBUG_SLASH_COMMAND,
        }
        if include_advisor:
            commands["advisor.md"] = ADVISOR_SLASH_COMMAND
        else:
            remove_claude_any_advisor_command()
        stale_channel = CLAUDE_COMMANDS_DIR / "channel.md"
        if stale_channel.exists():
            try:
                stale_text = stale_channel.read_text(encoding="utf-8", errors="replace")
                if "CLAUDE_ANY_CHANNEL_BRIDGE" in stale_text or "claude-any channel bridge" in stale_text:
                    stale_channel.unlink()
            except Exception:
                pass
        for name, content in commands.items():
            path = CLAUDE_COMMANDS_DIR / name
            if path.exists():
                existing = path.read_text(encoding="utf-8", errors="replace")
                markers = (
                    CLAUDE_ANY_ADVISOR_COMMAND_MARKERS
                    if name == "advisor.md"
                    else CLAUDE_ANY_ROUTER_DEBUG_COMMAND_MARKERS
                )
                if existing == content:
                    continue
                if not command_file_is_claude_any_owned(path, markers):
                    continue
            path.write_text(content, encoding="utf-8")
            try:
                os.chmod(path, 0o600)
            except Exception:
                pass
    except Exception as exc:
        print(f"Claude Any warning: could not install claude-any slash commands ({type(exc).__name__}: {exc}).", flush=True)


def disable_claude_any_slash_commands_for_native() -> None:
    try:
        if not CLAUDE_COMMANDS_DIR.exists():
            return
        commands = {
            "advisor.md": CLAUDE_ANY_ADVISOR_COMMAND_MARKERS,
            "router-debug.md": CLAUDE_ANY_ROUTER_DEBUG_COMMAND_MARKERS,
        }
        for name, markers in commands.items():
            path = CLAUDE_COMMANDS_DIR / name
            if not path.exists() or not command_file_is_claude_any_owned(path, markers):
                continue
            path.unlink()
    except Exception as exc:
        print(f"Claude Any warning: could not remove claude-any slash commands for native mode ({type(exc).__name__}: {exc}).", flush=True)


def http_json(
    url: str,
    headers: dict[str, str] | None = None,
    timeout: float = 8.0,
    provider: str | None = None,
    pcfg: dict[str, Any] | None = None,
) -> Any:
    req = urllib.request.Request(url, headers=with_upstream_user_agent(headers))
    with provider_urlopen(req, timeout=timeout, provider=provider, pcfg=pcfg) as r:
        return json.loads(r.read().decode("utf-8"))


def current_log_level() -> int:
    """Resolve effective log level. Priority: log-level file > env var > default.
    File mtime + 1s wall cache to keep overhead near zero on hot paths."""
    now = time.time()
    cache = _LOG_LEVEL_CACHE
    if cache["value"] is not None and (now - float(cache["checked_at"])) < 1.0:
        return int(cache["value"])
    level: int | None = None
    try:
        if LOG_LEVEL_PATH.exists():
            mtime = LOG_LEVEL_PATH.stat().st_mtime
            if cache["value"] is not None and mtime == cache["file_mtime"]:
                cache["checked_at"] = now
                return int(cache["value"])
            txt = LOG_LEVEL_PATH.read_text(encoding="utf-8").strip().upper()
            if txt in LOG_LEVELS:
                level = LOG_LEVELS[txt]
            elif txt.isdigit():
                level = max(0, min(5, int(txt)))
            cache["file_mtime"] = mtime
    except Exception:
        pass
    if level is None:
        env = os.environ.get("CLAUDE_ANY_LOG_LEVEL", "").strip().upper()
        if env in LOG_LEVELS:
            level = LOG_LEVELS[env]
        elif env.isdigit():
            level = max(0, min(5, int(env)))
    if level is None:
        level = LOG_LEVEL_DEFAULT
    cache["value"] = level
    cache["checked_at"] = now
    return level


def reset_log_level_cache() -> None:
    _LOG_LEVEL_CACHE.update({"value": None, "checked_at": 0.0, "file_mtime": 0.0})


def log_level_name(value: int | None = None) -> str:
    if value is None:
        value = current_log_level()
    return str(LOG_LEVEL_NAMES.get(int(value), value))


def log_level_source() -> str:
    if LOG_LEVEL_PATH.exists():
        return "file"
    if os.environ.get("CLAUDE_ANY_LOG_LEVEL", "").strip():
        return "env"
    return "default"


def log_level_status() -> str:
    return f"{log_level_name()} ({log_level_source()})"


def normalize_log_level(value: str) -> str | None:
    raw = str(value or "").strip()
    if not raw:
        raise ValueError("log level is empty")
    upper = raw.upper()
    aliases = {
        "OFF": "SILENT",
        "NONE": "SILENT",
        "QUIET": "SILENT",
        "WARNING": "WARN",
        "WARNINGS": "WARN",
    }
    upper = aliases.get(upper, upper)
    if upper in ("DEFAULT", "RESET", "UNSET", "AUTO"):
        return None
    if upper in LOG_LEVELS:
        return upper
    if upper.isdigit():
        numeric = max(0, min(5, int(upper)))
        return LOG_LEVEL_NAMES[numeric]
    raise ValueError(f"unknown log level: {value}")


def set_log_level_config(value: str) -> list[str]:
    try:
        level = normalize_log_level(value)
    except ValueError as exc:
        known = ", ".join(LOG_LEVELS)
        return [f"{exc}. Known levels: {known}, DEFAULT."]
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    if level is None:
        try:
            LOG_LEVEL_PATH.unlink()
        except FileNotFoundError:
            pass
        reset_log_level_cache()
        return [f"Log level reset to {log_level_status()}."]
    LOG_LEVEL_PATH.write_text(level + "\n", encoding="utf-8")
    try:
        os.chmod(LOG_LEVEL_PATH, 0o600)
    except Exception:
        pass
    reset_log_level_cache()
    return [f"Log level set to {level}."]


def router_log(level: str, message: str) -> None:
    """Append a line to router.log if the active level allows it.
    Rotates router.log when it exceeds ROUTER_LOG_MAX_BYTES."""
    threshold = LOG_LEVELS.get(level, 0)
    if threshold <= 0 or threshold > current_log_level():
        return
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        if LOG_PATH.exists() and LOG_PATH.stat().st_size > ROUTER_LOG_MAX_BYTES:
            LOG_PATH.replace(LOG_PATH.with_suffix(".log.1"))
        with LOG_PATH.open("a", encoding="utf-8") as f:
            f.write("%s [%s] %s\n" % (time.strftime("%Y-%m-%dT%H:%M:%S"), level, message))
    except Exception:
        pass


def _truncate_for_dump(value: Any, max_len: int = 4000) -> Any:
    try:
        text = json.dumps(value, ensure_ascii=False) if not isinstance(value, str) else value
    except Exception:
        text = str(value)
    if len(text) > max_len:
        return text[:max_len] + f"...<truncated {len(text) - max_len} chars>"
    return value


def resolve_blocked_tools(provider: str, pcfg: dict[str, Any]) -> set[str]:
    """Return the set of tool names to strip from upstream requests.
    `pcfg['blocked_tools']` overrides: None/missing => default list, False/[] => disable, list => explicit set."""
    if provider == "anthropic":
        return set()
    override = pcfg.get("blocked_tools", None)
    if override is False:
        return set()
    if isinstance(override, list):
        return {str(name).strip() for name in override if str(name).strip()}
    return set(DEFAULT_BLOCKED_TOOLS_NON_ANTHROPIC)


def forced_tool_choice_name(body: dict[str, Any]) -> str | None:
    tool_choice = body.get("tool_choice") if isinstance(body.get("tool_choice"), dict) else None
    if not tool_choice:
        return None
    if tool_choice.get("type") != "tool":
        return None
    name = tool_choice.get("name")
    return name if isinstance(name, str) and name else None


def tool_names_in_body(body: dict[str, Any]) -> set[str]:
    names: set[str] = set()
    tools = body.get("tools")
    if not isinstance(tools, list):
        return names
    for tool in tools:
        if isinstance(tool, dict) and isinstance(tool.get("name"), str):
            names.add(tool["name"])
    return names


def tool_schema_in_body(body: dict[str, Any], tool_name: str) -> dict[str, Any] | None:
    tools = body.get("tools")
    if not isinstance(tools, list):
        return None
    for tool in tools:
        if not isinstance(tool, dict):
            continue
        if str(tool.get("name") or "") != tool_name:
            continue
        schema = tool.get("input_schema")
        return schema if isinstance(schema, dict) else None
    return None


def _match_available_tool_name(name: str, available: set[str]) -> str | None:
    if not available:
        return None
    low = name.lower()
    for candidate in sorted(available):
        if candidate == name:
            return candidate
    for candidate in sorted(available):
        if candidate.lower() == low:
            return candidate
    mcp_key = _mcp_tool_name_server_normalized_key(name)
    if mcp_key is not None:
        matches = [
            candidate
            for candidate in sorted(available)
            if _mcp_tool_name_server_normalized_key(candidate) == mcp_key
        ]
        if len(matches) == 1:
            return matches[0]
    for candidate in sorted(available):
        candidate_low = candidate.lower()
        if low and (low in candidate_low or candidate_low in low):
            return candidate
    return None


def _mcp_tool_name_server_normalized_key(name: str) -> tuple[str, str] | None:
    """Return a safe comparison key for MCP tool names.

    Some non-native models rewrite the MCP server segment, e.g.
    ``mcp__ai-net-http__get_messages`` becomes
    ``mcp__ai-net_http__get_messages``. Only the server segment is normalized;
    the tool name segment must still match exactly case-insensitively.
    """
    if not isinstance(name, str) or not name.startswith("mcp__"):
        return None
    rest = name[5:]
    if "__" not in rest:
        return None
    server_name, tool_name = rest.split("__", 1)
    if not server_name or not tool_name:
        return None
    normalized_server = re.sub(r"[-_]+", "", server_name).lower()
    return normalized_server, tool_name.lower()


def resolve_emitted_tool_name(raw_name: str, source_body: dict[str, Any] | None) -> str:
    available = tool_names_in_body(source_body or {}) if isinstance(source_body, dict) else set()
    return _match_available_tool_name(raw_name, available) or _fuzzy_match_tool_name(raw_name) or raw_name


def synthetic_tool_use_response(model: str, tool_name: str, tool_input: dict[str, Any] | None = None) -> dict[str, Any]:
    now = int(time.time() * 1000)
    return {
        "id": f"msg_claude_any_tool_{now}",
        "type": "message",
        "role": "assistant",
        "model": model or "claude-any-router",
        "content": [
            {
                "type": "tool_use",
                "id": f"toolu_claude_any_{tool_name}_{now}",
                "name": tool_name,
                "input": tool_input or {},
            }
        ],
        "stop_reason": "tool_use",
        "stop_sequence": None,
        "usage": {"input_tokens": 0, "output_tokens": 0},
    }


def has_tool(body: dict[str, Any], name: str) -> bool:
    return name in tool_names_in_body(body)


ULTRACODE_ON_RE = re.compile(r"\bUltracode\s+is\s+(?:still\s+)?on\b", re.IGNORECASE)
ULTRACODE_OFF_RE = re.compile(r"\bUltracode\s+is\s+off\b", re.IGNORECASE)
ULTRACODE_STATE_RE = re.compile(r"\bUltracode\s+is\s+(?:still\s+)?(on|off)\b", re.IGNORECASE)


def body_ultracode_runtime_enabled(body: dict[str, Any]) -> bool:
    """Infer Claude Code's per-session ultracode runtime state from the prompt.

    `/effort ultracode` is session-scoped in Claude Code and is not persisted in
    claude-any provider config. Claude Code advertises that runtime state via
    system reminder text, so the router must infer it from the current request
    body rather than from config alone.
    """
    enabled = False
    system_text = anthropic_content_to_text(body.get("system"))
    for match in ULTRACODE_STATE_RE.finditer(system_text):
        enabled = match.group(1).lower() == "on"
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        text = anthropic_content_to_text(message.get("content"))
        for match in ULTRACODE_STATE_RE.finditer(text):
            enabled = match.group(1).lower() == "on"
    return enabled


def ultracode_workflow_preferred(body: dict[str, Any]) -> bool:
    return body_ultracode_runtime_enabled(body) and has_tool(body, "Workflow")


def _message_content_blocks(message: dict[str, Any]) -> list[Any]:
    content = message.get("content")
    if isinstance(content, list):
        return content
    if isinstance(content, str):
        return [{"type": "text", "text": content}]
    return []


def anthropic_thinking_requested(body: dict[str, Any]) -> bool:
    thinking = body.get("thinking")
    if isinstance(thinking, dict):
        thinking_type = str(thinking.get("type") or "").strip().lower()
        return thinking_type not in ("", "disabled", "none", "off", "false")
    return bool(thinking)


def anthropic_thinking_block_count(body: dict[str, Any]) -> int:
    count = 0
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        for block in _message_content_blocks(message):
            if isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES:
                count += 1
    return count


def anthropic_tool_continuation_block_count(body: dict[str, Any]) -> int:
    count = 0
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        role = message.get("role")
        for block in _message_content_blocks(message):
            if not isinstance(block, dict):
                continue
            block_type = block.get("type")
            if role == "assistant" and block_type == "tool_use":
                count += 1
            elif role == "user" and block_type == "tool_result":
                count += 1
    return count


def anthropic_assistant_history_count(body: dict[str, Any]) -> int:
    count = 0
    for message in body.get("messages") or []:
        if isinstance(message, dict) and message.get("role") == "assistant":
            count += 1
    return count


def strip_anthropic_thinking_blocks_from_messages(body: dict[str, Any]) -> dict[str, Any]:
    messages = body.get("messages")
    if not isinstance(messages, list):
        return body
    changed = False
    out_messages: list[Any] = []
    for message in messages:
        if not isinstance(message, dict):
            out_messages.append(message)
            continue
        content = message.get("content")
        if not isinstance(content, list):
            out_messages.append(message)
            continue
        filtered = [
            block
            for block in content
            if not (isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES)
        ]
        if len(filtered) != len(content):
            new_message = dict(message)
            new_message["content"] = filtered
            out_messages.append(new_message)
            changed = True
        else:
            out_messages.append(message)
    if not changed:
        return body
    out = dict(body)
    out["messages"] = out_messages
    return out


def has_claude_any_synthetic_tool_use(body: dict[str, Any]) -> bool:
    for message in body.get("messages") or []:
        if not isinstance(message, dict) or message.get("role") != "assistant":
            continue
        for block in _message_content_blocks(message):
            if not isinstance(block, dict) or block.get("type") != "tool_use":
                continue
            tool_id = str(block.get("id") or "")
            if tool_id.startswith("toolu_claude_any_"):
                return True
    return False


def should_defer_forced_tool_choice_for_thinking(provider: str, pcfg: dict[str, Any], body: dict[str, Any], name: str | None) -> bool:
    if name not in PLAN_MODE_SELF_TOOLS:
        return False
    return False


def preserves_anthropic_thinking_contract(provider: str, pcfg: dict[str, Any]) -> bool:
    configured = pcfg.get("preserve_anthropic_thinking")
    if configured is not None:
        return bool(configured)
    return provider == "anthropic"


def normalize_thinking_for_non_anthropic_provider(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    thinking_requested = anthropic_thinking_requested(body)
    thinking_blocks = anthropic_thinking_block_count(body)
    if not thinking_requested and thinking_blocks <= 0:
        return body
    if preserves_anthropic_thinking_contract(provider, pcfg):
        return body
    if openai_chat_reasoning_passback_enabled_for_body(provider, pcfg, body):
        if not thinking_requested:
            return body
        out = dict(body)
        out.pop("thinking", None)
        router_log(
            "INFO",
            "removed top-level Anthropic thinking request but preserved thinking blocks "
            f"for OpenAI-chat reasoning passback provider={provider} thinking_blocks={thinking_blocks}",
        )
        return out
    synthetic_tool = has_claude_any_synthetic_tool_use(body)
    continuation_blocks = anthropic_tool_continuation_block_count(body)
    assistant_history = anthropic_assistant_history_count(body)
    out = strip_anthropic_thinking_blocks_from_messages(body)
    out = dict(out)
    out.pop("thinking", None)
    router_log(
        "WARN",
        "removed Anthropic thinking request and thinking content blocks for non-Anthropic provider "
        f"provider={provider} synthetic_tool={synthetic_tool} continuation_blocks={continuation_blocks} "
        f"assistant_history={assistant_history} thinking_blocks={thinking_blocks}",
    )
    return out


def normalize_thinking_for_non_anthropic_native_provider(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    return normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)


def provider_supports_tool_choice(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> bool:
    configured = pcfg.get("supports_tool_choice")
    if configured is not None:
        return bool(configured)
    if provider != "deepseek":
        return True
    model_hint = strip_claude_context_suffix(str(body.get("model") or pcfg.get("current_model") or "")).lower()
    # DeepSeek's own V4 thinking-mode integration notes require
    # supportsToolChoice=false for agent tools. V4 thinking is enabled by
    # default, so treat forced tool_choice as unsupported for V4 models unless
    # the user explicitly overrides supports_tool_choice in provider options.
    return "deepseek-v4" not in model_hint


def normalize_tool_choice_for_provider(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    if body.get("tool_choice") is None:
        return body
    if provider_supports_tool_choice(provider, pcfg, body):
        return body
    out = dict(body)
    removed = out.pop("tool_choice", None)
    router_log(
        "WARN",
        f"removed unsupported tool_choice for {provider}: model={body.get('model')} tool_choice={removed}",
    )
    return out


def normalize_response_thinking_for_non_anthropic_provider(provider: str, pcfg: dict[str, Any], message: dict[str, Any], model: str | None = None) -> dict[str, Any]:
    if preserves_anthropic_thinking_contract(provider, pcfg):
        return message
    content = message.get("content")
    if not isinstance(content, list):
        return message
    filtered = [
        block
        for block in content
        if not (isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES)
    ]
    if len(filtered) == len(content):
        return message
    remember_suppressed_thinking_passback(provider, str(model or message.get("model") or ""), content)
    out = dict(message)
    out["content"] = filtered or [{"type": "text", "text": ""}]
    router_log(
        "WARN",
        f"removed Anthropic thinking response blocks for non-Anthropic provider provider={provider} "
        f"thinking_blocks={len(content) - len(filtered)}",
    )
    return out


def clear_suppressed_thinking_passback_cache() -> None:
    SUPPRESSED_THINKING_PASSBACK_CACHE.clear()


def _copy_thinking_blocks(blocks: Any) -> list[dict[str, Any]]:
    if not isinstance(blocks, list):
        return []
    copied: list[dict[str, Any]] = []
    for block in blocks:
        if isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES:
            copied.append(dict(block))
    return copied


def remember_suppressed_thinking_passback(provider: str, model: str, blocks: list[Any]) -> None:
    copied = _copy_thinking_blocks(blocks)
    if not copied:
        return
    SUPPRESSED_THINKING_PASSBACK_CACHE.append(
        {
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "provider": provider,
            "model": model,
            "blocks": copied,
        }
    )
    del SUPPRESSED_THINKING_PASSBACK_CACHE[:-SUPPRESSED_THINKING_PASSBACK_MAX]
    router_log(
        "WARN",
        f"stored suppressed Anthropic thinking passback blocks provider={provider} "
        f"model={model} blocks={len(copied)} cache={len(SUPPRESSED_THINKING_PASSBACK_CACHE)}",
    )


def rehydrate_suppressed_thinking_passback(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    if preserves_anthropic_thinking_contract(provider, pcfg):
        return body
    if latest_user_is_claude_code_suggestion_mode(body):
        return body
    if not SUPPRESSED_THINKING_PASSBACK_CACHE:
        return body
    messages = body.get("messages")
    if not isinstance(messages, list):
        return body
    assistant_indices: list[int] = []
    for index, message in enumerate(messages):
        if not isinstance(message, dict) or message.get("role") != "assistant":
            continue
        if anthropic_thinking_block_count({"messages": [message]}) > 0:
            continue
        content = message.get("content")
        if isinstance(content, list) and content:
            assistant_indices.append(index)
        elif isinstance(content, str) and content:
            assistant_indices.append(index)
    if not assistant_indices:
        return body
    matching_records = [
        record
        for record in SUPPRESSED_THINKING_PASSBACK_CACHE
        if isinstance(record, dict) and record.get("provider") == provider
    ]
    records = matching_records[-len(assistant_indices):]
    if not records:
        return body
    selected_indices = assistant_indices[-len(records):]
    out_messages = list(messages)
    inserted = 0
    for index, record in zip(selected_indices, records):
        message = out_messages[index]
        if not isinstance(message, dict):
            continue
        blocks = _copy_thinking_blocks(record.get("blocks") if isinstance(record, dict) else [])
        if not blocks:
            continue
        content = message.get("content")
        if isinstance(content, list):
            new_content = blocks + list(content)
        elif isinstance(content, str):
            new_content = blocks + [{"type": "text", "text": content}]
        else:
            continue
        patched = dict(message)
        patched["content"] = new_content
        out_messages[index] = patched
        inserted += len(blocks)
    if inserted <= 0:
        return body
    out = dict(body)
    out["messages"] = out_messages
    router_log(
        "WARN",
        f"rehydrated suppressed Anthropic thinking passback blocks for upstream provider={provider} "
        f"blocks={inserted} assistant_messages={len(selected_indices)} cache={len(SUPPRESSED_THINKING_PASSBACK_CACHE)}",
    )
    return out


def plan_mode_active(body: dict[str, Any]) -> bool:
    """Infer Claude Code Plan Mode from tool history and plan-mode attachments."""
    active = False
    tool_names_by_id: dict[str, str] = {}
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        attachment = message.get("attachment")
        if isinstance(attachment, dict):
            attachment_type = attachment.get("type")
            if attachment_type in {"plan_mode", "plan_mode_reentry"}:
                active = True
            elif attachment_type == "plan_mode_exit":
                active = False
        if message.get("role") == "assistant":
            for block in _message_content_blocks(message):
                if not isinstance(block, dict) or block.get("type") != "tool_use":
                    continue
                tool_id = str(block.get("id") or "")
                name = str(block.get("name") or "")
                if tool_id and name:
                    tool_names_by_id[tool_id] = name
        elif message.get("role") == "user":
            for block in _message_content_blocks(message):
                if not isinstance(block, dict):
                    continue
                if block.get("type") == "tool_result":
                    tool_use_id = str(block.get("tool_use_id") or "")
                    tool_name = tool_names_by_id.get(tool_use_id)
                    if tool_name == "EnterPlanMode":
                        active = True
                    elif tool_name == "ExitPlanMode":
                        active = False
                elif block.get("type") in {"plan_mode", "plan_mode_reentry"}:
                    active = True
                elif block.get("type") == "plan_mode_exit":
                    active = False
    return active


def has_plan_mode_exit(body: dict[str, Any]) -> bool:
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        attachment = message.get("attachment")
        if isinstance(attachment, dict) and attachment.get("type") == "plan_mode_exit":
            return True
        if message.get("role") != "assistant":
            continue
        for block in _message_content_blocks(message):
            if isinstance(block, dict) and block.get("type") == "tool_use" and block.get("name") == "ExitPlanMode":
                return True
    return False


def allowed_prompt_tools_for_exit_plan_mode(body: dict[str, Any]) -> list[str]:
    schema = tool_schema_in_body(body, "ExitPlanMode") or _lookup_tool_schema("ExitPlanMode")
    if not isinstance(schema, dict):
        return []
    properties = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
    allowed_schema = properties.get("allowedPrompts") if isinstance(properties.get("allowedPrompts"), dict) else None
    if not allowed_schema:
        return []
    items = allowed_schema.get("items") if isinstance(allowed_schema.get("items"), dict) else {}
    item_properties = items.get("properties") if isinstance(items.get("properties"), dict) else {}
    tool_schema = item_properties.get("tool") if isinstance(item_properties.get("tool"), dict) else {}
    enum_values = tool_schema.get("enum")
    if not isinstance(enum_values, list):
        return []
    return [str(item) for item in enum_values if isinstance(item, str) and item.strip()]


def exit_plan_mode_default_prompt_for_tool(tool_name: str) -> str:
    return f"use {tool_name} as needed to implement and verify the approved plan"


def backfill_exit_plan_mode_allowed_prompts(body: dict[str, Any], tool_input: dict[str, Any]) -> dict[str, Any]:
    existing = tool_input.get("allowedPrompts") if isinstance(tool_input, dict) else None
    if isinstance(existing, list) and any(isinstance(item, dict) for item in existing):
        return tool_input
    allowed_tools = allowed_prompt_tools_for_exit_plan_mode(body)
    if not allowed_tools:
        return tool_input
    out = dict(tool_input)
    out["allowedPrompts"] = [
        {"tool": tool_name, "prompt": exit_plan_mode_default_prompt_for_tool(tool_name)}
        for tool_name in allowed_tools
    ]
    router_log("INFO", f"backfilled ExitPlanMode allowedPrompts tools={','.join(allowed_tools)}")
    return out


def plan_mode_tool_name_for_emit(body: dict[str, Any], name: str, tool_input: dict[str, Any]) -> tuple[str | None, dict[str, Any]]:
    active = plan_mode_active(body)
    if name == "EnterPlanMode" and active:
        router_log("WARN", "dropped repeated EnterPlanMode while plan mode is active")
        return None, tool_input
    if name == "EnterPlanMode" and ultracode_workflow_preferred(body):
        router_log("WARN", "dropped EnterPlanMode because ultracode workflow is preferred")
        return None, tool_input
    if name == "ExitPlanMode" and not active:
        router_log("WARN", "dropped ExitPlanMode while plan mode is not active")
        return None, tool_input
    if name == "ExitPlanMode":
        tool_input = backfill_exit_plan_mode_allowed_prompts(body, tool_input)
    return name, tool_input


def is_guard_feedback_text(text: str) -> bool:
    stripped = (text or "").strip()
    return (
        stripped.startswith("Stop hook feedback:")
        or stripped.startswith("Claude Any plan guard:")
        or PLAN_GUARD_MARKER in stripped
    )


SYSTEM_REMINDER_BLOCK_RE = re.compile(r"<system-reminder>.*?</system-reminder>", re.DOTALL)
CLAUDE_CODE_SUGGESTION_MODE_PREFIX = "[SUGGESTION MODE:"


def strip_claude_code_system_reminders(text: str) -> str:
    return SYSTEM_REMINDER_BLOCK_RE.sub("", text or "").strip()


def is_claude_code_suggestion_mode_text(text: str) -> bool:
    return strip_claude_code_system_reminders(text).lstrip().startswith(CLAUDE_CODE_SUGGESTION_MODE_PREFIX)


def user_intent_text_from_message(message: dict[str, Any]) -> str:
    if not isinstance(message, dict) or message.get("role") != "user":
        return ""
    if message.get("isMeta") is True:
        return ""
    content = message.get("content")
    if isinstance(content, str):
        text = strip_claude_code_system_reminders(content)
        return "" if is_guard_feedback_text(text) else text
    if not isinstance(content, list):
        # Claude Code can inject user-role attachment records such as
        # plan_mode_exit. They are state metadata, not new user intent.
        return ""
    # Claude Code sends tool_result blocks as user-role messages. Those are not
    # user intent. System reminders can also arrive as text blocks adjacent to
    # the real prompt, so remove them before classifying resume prompts.
    parts: list[str] = []
    for block in content:
        if isinstance(block, str):
            text = strip_claude_code_system_reminders(block)
        elif isinstance(block, dict) and block.get("type") == "text":
            text = strip_claude_code_system_reminders(str(block.get("text", "")))
        else:
            continue
        if text and not is_guard_feedback_text(text):
            parts.append(text)
    return "\n".join(parts).strip()


def latest_user_text(body: dict[str, Any]) -> str:
    for message in reversed(body.get("messages") or []):
        text = user_intent_text_from_message(message) if isinstance(message, dict) else ""
        if not text:
            continue
        return text
    return ""


def latest_user_intent_message_index(body: dict[str, Any]) -> int | None:
    messages = body.get("messages") or []
    for index in range(len(messages) - 1, -1, -1):
        message = messages[index]
        if isinstance(message, dict) and user_intent_text_from_message(message):
            return index
    return None


def latest_user_is_claude_code_suggestion_mode(body: dict[str, Any]) -> bool:
    latest = latest_user_text(body)
    return bool(latest and is_claude_code_suggestion_mode_text(latest))


def router_debug_message_preview_chars(cfg: dict[str, Any] | None = None) -> int:
    cfg = cfg or load_config()
    env = os.environ.get("CLAUDE_ANY_ROUTER_MESSAGE_PREVIEW_CHARS", "").strip()
    if env:
        value = positive_int(env)
        return min(value or 0, 4000)
    value = positive_int(cfg.get("router_debug_message_preview_chars"))
    return min(value or 0, 4000)


def router_event_message_preview(body: dict[str, Any], cfg: dict[str, Any] | None = None) -> dict[str, Any]:
    limit = router_debug_message_preview_chars(cfg)
    if limit <= 0:
        return {}
    text = latest_user_text(body).strip()
    if not text:
        return {"message_preview_chars": limit, "message_preview": "", "message_preview_truncated": False}
    normalized = re.sub(r"\s+", " ", redact_sensitive_text(text))
    truncated = len(normalized) > limit
    return {
        "message_preview_chars": limit,
        "message_preview": normalized[:limit].rstrip(),
        "message_preview_truncated": truncated,
    }


def likely_implementation_planning_request(text: str) -> bool:
    normalized = re.sub(r"\s+", " ", text or "").strip()
    if len(normalized) >= 120:
        return True
    # Multi-line prompts usually carry enough task structure that a one-line
    # "I'll make a plan" style response is not a useful final answer.
    non_empty_lines = [line for line in (text or "").splitlines() if line.strip()]
    if len(non_empty_lines) >= 3 and len(normalized) >= 80:
        return True
    return False


def non_actionable_short_response(text: str) -> bool:
    normalized = re.sub(r"\s+", " ", text or "").strip()
    if not normalized:
        return True
    # Language-agnostic: for a long implementation request, a short single-line
    # text response with no tool call is not actionable. Do not inspect words.
    if len(normalized) <= 80 and "\n" not in (text or ""):
        return True
    if len(normalized) <= 160 and "\n" not in (text or "") and not re.search(r"[`{};/\\\\]|https?://", normalized):
        return True
    return False


def should_auto_enter_plan_mode(body: dict[str, Any], response_text: str, tool_calls: list[dict[str, Any]]) -> bool:
    if tool_calls:
        return False
    if ultracode_workflow_preferred(body):
        return False
    if not has_tool(body, "EnterPlanMode"):
        return False
    if plan_mode_active(body):
        return False
    if has_plan_mode_exit(body):
        return False
    if latest_tool_result_indicates_completed_work(body):
        return False
    if not non_actionable_short_response(response_text):
        return False
    return likely_implementation_planning_request(latest_user_text(body))


WORK_CONTINUATION_RESULT_TOOLS: frozenset[str] = frozenset(
    {
        "Bash",
        "Glob",
        "Grep",
        "LS",
        "Read",
        "Write",
        "Edit",
        "MultiEdit",
        "TaskCreate",
        "TaskList",
        "TaskUpdate",
        "TaskStop",
    }
)


WORK_COMPLETION_RESULT_TOOLS: frozenset[str] = frozenset(
    {
        "Write",
        "Edit",
        "MultiEdit",
        "TaskUpdate",
        "TaskStop",
    }
)


def bash_command_looks_mutating(command: str) -> bool:
    normalized = re.sub(r"\s+", " ", command or "").strip()
    if not normalized:
        return False
    return bool(
        re.search(
            r"(^|[;&|]\s*|\b)(rm|rmdir|mv|cp|mkdir|touch|chmod|chown|ln|install|git\s+(commit|push|pull|merge|rebase|checkout|switch|restore|reset|clean)|npm\s+(install|update|run|publish)|pnpm\s+(install|update|run|publish)|yarn\s+(install|add|run|publish)|python\d*\s+-m\s+pip\s+install|pip\d*\s+install|docker\s+(run|compose|build|up|down|rm|rmi)|kubectl\s+(apply|delete|create|replace|patch))\b",
            normalized,
        )
    )


def latest_user_tool_result_details(body: dict[str, Any]) -> list[dict[str, Any]]:
    tools_by_id: dict[str, tuple[str, dict[str, Any]]] = {}
    latest: list[dict[str, Any]] = []
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        content = message.get("content")
        if message.get("role") == "assistant" and isinstance(content, list):
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_use":
                    continue
                tool_id = str(block.get("id") or "")
                name = str(block.get("name") or "")
                tool_input = block.get("input") if isinstance(block.get("input"), dict) else {}
                if tool_id and name:
                    tools_by_id[tool_id] = (name, tool_input)
        elif message.get("role") == "user" and isinstance(content, list):
            current: list[dict[str, Any]] = []
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_result":
                    continue
                tool_use_id = str(block.get("tool_use_id") or "")
                name, tool_input = tools_by_id.get(tool_use_id, ("tool", {}))
                current.append(
                    {
                        "name": name,
                        "input": tool_input,
                        "text": anthropic_content_to_text(block.get("content", "")),
                        "is_error": bool(block.get("is_error")),
                    }
                )
            if current:
                latest = current
    return latest


def latest_tool_result_indicates_completed_work(body: dict[str, Any]) -> bool:
    details = latest_user_tool_result_details(body)
    if not details:
        return False
    for item in details:
        if item.get("is_error"):
            continue
        name = str(item.get("name") or "")
        tool_input = item.get("input") if isinstance(item.get("input"), dict) else {}
        if name in WORK_COMPLETION_RESULT_TOOLS:
            return True
        if name == "Bash" and bash_command_looks_mutating(str(tool_input.get("command") or "")):
            return True
    return False


def latest_user_tool_result_names(body: dict[str, Any]) -> list[str]:
    tool_names_by_id: dict[str, str] = {}
    latest: list[str] = []
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        content = message.get("content")
        if message.get("role") == "assistant" and isinstance(content, list):
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_use":
                    continue
                tool_id = str(block.get("id") or "")
                name = str(block.get("name") or "")
                if tool_id and name:
                    tool_names_by_id[tool_id] = name
        elif message.get("role") == "user" and isinstance(content, list):
            current: list[str] = []
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_result":
                    continue
                tool_use_id = str(block.get("tool_use_id") or "")
                if tool_use_id:
                    current.append(tool_names_by_id.get(tool_use_id, "tool"))
            if current:
                latest = current
    return latest


def latest_user_tool_result_text(body: dict[str, Any]) -> str:
    latest = ""
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        if message.get("role") != "user" or not isinstance(message.get("content"), list):
            continue
        parts: list[str] = []
        for block in message.get("content") or []:
            if not isinstance(block, dict) or block.get("type") != "tool_result":
                continue
            parts.append(anthropic_content_to_text(block.get("content", "")))
        if parts:
            latest = "\n".join(part for part in parts if part)
    return latest


def synthetic_tasklist_tool_use_id(tool_id: str, name: str) -> bool:
    if name != "TaskList":
        return False
    prefixes = (
        "toolu_ollama_keepalive_",
        "toolu_ollama_choice_",
        "toolu_openai_keepalive_",
        "toolu_openai_choice_",
        "toolu_anthropic_choice_",
        "toolu_claude_any_TaskList_",
    )
    return any(tool_id.startswith(prefix) for prefix in prefixes)


def recent_synthetic_tasklist_count(body: dict[str, Any], after_message_index: int | None = None) -> int:
    count = 0
    messages = body.get("messages") or []
    if after_message_index is not None:
        messages = messages[after_message_index + 1 :]
    for message in reversed(messages):
        if not isinstance(message, dict):
            continue
        if after_message_index is None and message.get("role") == "user" and user_intent_text_from_message(message):
            break
        content = message.get("content")
        if message.get("role") != "assistant" or not isinstance(content, list):
            continue
        found_keepalive = False
        for block in content:
            if not isinstance(block, dict) or block.get("type") != "tool_use":
                continue
            if synthetic_tasklist_tool_use_id(str(block.get("id") or ""), str(block.get("name") or "")):
                found_keepalive = True
        if found_keepalive:
            count += 1
    return count


def tasklist_result_has_active_work(text: str) -> bool:
    normalized = re.sub(r"\s+", " ", text or "").strip().lower()
    if not normalized:
        return False
    # This parses Claude Code's task-list tool output, not user-facing prose.
    if re.search(r"\[\s*(in progress|open|pending)\s*\]", normalized):
        return True
    for label in ("in progress", "open", "pending"):
        for match in re.finditer(rf"\b(\d+)\s+{re.escape(label)}\b", normalized):
            if int(match.group(1)) > 0:
                return True
    return False


def latest_tasklist_result_has_no_active_work(body: dict[str, Any]) -> bool:
    latest_names = latest_user_tool_result_names(body)
    if "TaskList" not in latest_names:
        return False
    return not tasklist_result_has_active_work(latest_user_tool_result_text(body))


def latest_assistant_text(body: dict[str, Any]) -> str:
    for message in reversed(body.get("messages") or []):
        if not isinstance(message, dict) or message.get("role") != "assistant":
            continue
        return anthropic_content_to_text(message.get("content"))
    return ""


def short_resume_prompt(text: str) -> bool:
    normalized = re.sub(r"\s+", " ", text or "").strip()
    if not normalized:
        return False
    if len(normalized) > 32:
        return False
    # Language-agnostic: a very short imperative with no question or code-like
    # syntax after an unfinished assistant turn is a request to proceed.
    return not re.search(r"[?？`{};/\\\\]|https?://", normalized)


def latest_user_looks_like_work_request(body: dict[str, Any]) -> bool:
    latest = latest_user_text(body)
    normalized = re.sub(r"\s+", " ", latest or "").strip()
    if not normalized:
        return False
    if likely_implementation_planning_request(latest):
        return True
    if short_resume_prompt(latest) and latest_assistant_text(body):
        return True
    if short_resume_prompt(latest) and latest_user_tool_result_names(body):
        return True
    return False


def response_asks_for_user_choice_or_permission(text: str) -> bool:
    normalized = re.sub(r"\s+", " ", text or "").strip()
    if not normalized:
        return False
    if "?" not in normalized and "？" not in normalized:
        return False
    # Structural guard: do not inspect language-specific words. A question-only
    # end_turn inside Plan Mode is a pause, not progress.
    return len(normalized) <= 1200


def should_auto_continue_choice_question_with_tasklist(body: dict[str, Any], response_text: str, tool_calls: list[dict[str, Any]]) -> bool:
    if tool_calls:
        return False
    if latest_user_is_claude_code_suggestion_mode(body):
        return False
    if not has_tool(body, "TaskList"):
        return False
    latest_names = latest_user_tool_result_names(body)
    if latest_tool_result_indicates_completed_work(body):
        return False
    if "TaskList" in latest_names and not tasklist_result_has_active_work(latest_user_tool_result_text(body)):
        return False
    intent_index = latest_user_intent_message_index(body)
    if recent_synthetic_tasklist_count(body, after_message_index=intent_index) >= 2:
        return False
    if not response_asks_for_user_choice_or_permission(response_text):
        return False
    if plan_mode_active(body):
        return True
    return bool(latest_user_tool_result_names(body) and latest_user_looks_like_work_request(body))


def should_keep_work_alive_with_tasklist(body: dict[str, Any], response_text: str, tool_calls: list[dict[str, Any]]) -> bool:
    if tool_calls:
        return False
    if latest_user_is_claude_code_suggestion_mode(body):
        return False
    if not has_tool(body, "TaskList"):
        return False
    latest_names = latest_user_tool_result_names(body)
    if not latest_names:
        return False
    latest_result_text = latest_user_tool_result_text(body)
    if latest_names == ["TaskList"] and "No tasks found" in latest_result_text:
        return False
    if "TaskList" in latest_names:
        if not tasklist_result_has_active_work(latest_result_text):
            return False
        max_keepalive = 6
        intent_index = latest_user_intent_message_index(body)
        if recent_synthetic_tasklist_count(body, after_message_index=intent_index) >= max_keepalive:
            return False
    if not any(name in WORK_CONTINUATION_RESULT_TOOLS for name in latest_names):
        return False
    if latest_tool_result_indicates_completed_work(body) and response_text.strip():
        return False
    if non_actionable_short_response(response_text):
        return True
    normalized = re.sub(r"\s+", " ", response_text or "").strip()
    # A resume/continue prompt after a tool result should not end the loop with
    # prose only. Keep this structural and bounded: do not inspect task names,
    # domains, languages, or provider-specific text.
    return latest_user_looks_like_work_request(body) and len(normalized) <= 1200


def should_recover_empty_end_turn_with_tasklist(body: dict[str, Any], response_text: str, tool_calls: list[dict[str, Any]]) -> bool:
    """Recover from non-Anthropic providers returning an empty end_turn.

    Claude Code treats an assistant end_turn with no text and no tool call as a
    completed turn. For implementation/resume prompts, ask Claude Code for the
    task list to give the model a concrete next tool result and keep the loop
    alive.
    """
    if tool_calls:
        return False
    if latest_user_is_claude_code_suggestion_mode(body):
        return False
    if response_text.strip():
        return False
    if not has_tool(body, "TaskList"):
        return False
    latest_tool_results = latest_user_tool_result_names(body)
    if latest_tool_results:
        if "TaskList" in latest_tool_results and not tasklist_result_has_active_work(latest_user_tool_result_text(body)):
            return False
        return True
    latest = latest_user_text(body)
    if not latest.strip():
        return False
    if short_resume_prompt(latest) and (latest_assistant_text(body) or plan_mode_active(body)):
        return True
    return likely_implementation_planning_request(latest)


def empty_end_turn_notice() -> str:
    return (
        "[claude-any] Upstream model returned an empty end_turn with no text or "
        "tool call. No work was performed; please retry or ask me to continue."
    )


def empty_end_turn_notice_for_body(body: dict[str, Any] | None) -> str:
    if isinstance(body, dict) and latest_tasklist_result_has_no_active_work(body):
        return (
            "[claude-any] TaskList returned no active tasks. No automatic continuation "
            "is available; provide the next instruction or ask for current status."
        )
    return empty_end_turn_notice()


def append_synthetic_tasklist_to_message(message: dict[str, Any], model: str, source_body: dict[str, Any], reason: str) -> dict[str, Any]:
    content = message.get("content")
    if not isinstance(content, list):
        content = [{"type": "text", "text": anthropic_content_to_text(content)}] if content else []
    tool_calls = [block for block in content if isinstance(block, dict) and block.get("type") == "tool_use"]
    text = anthropic_content_to_text(content)
    if not should_auto_continue_choice_question_with_tasklist(source_body, text, tool_calls):
        return message
    now = int(time.time() * 1000)
    out = dict(message)
    out_content = list(content)
    out_content.append(
        {
            "type": "tool_use",
            "id": f"toolu_claude_any_TaskList_{reason}_{now}",
            "name": "TaskList",
            "input": {},
        }
    )
    out["content"] = out_content
    out["stop_reason"] = "tool_use"
    out.setdefault("model", model or message.get("model") or "claude-any-router")
    router_log("WARN", f"auto-synthesized TaskList after clarification question ({reason})")
    return out


def maybe_handle_plan_mode_tool_choice(handler: BaseHTTPRequestHandler, provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> bool:
    """Support Claude Code's forced Plan-mode entry without relying on upstream model behavior."""
    if provider == "anthropic":
        return False
    name = forced_tool_choice_name(body)
    if name != "EnterPlanMode":
        return False
    if should_defer_forced_tool_choice_for_thinking(provider, pcfg, body, name):
        router_log(
            "INFO",
            f"deferred forced {name} tool_choice to native Anthropic-compatible upstream because thinking is enabled",
        )
        return False
    # Claude Code may force this tool when the user uses /plan or toggles Plan mode.
    # Returning a valid tool_use locally is more reliable than asking arbitrary
    # OpenAI/Ollama-compatible backends to select an internal Claude Code tool.
    available = tool_names_in_body(body)
    if available and name not in available:
        return False
    emit_name = name
    tool_input: dict[str, Any] = {}
    if plan_mode_active(body):
        router_log("WARN", f"ignored forced {name} tool_choice because plan mode is already active")
        return False
    else:
        router_log("INFO", f"synthesized {name} tool_use for {provider} forced tool_choice")
    write_json(handler, synthetic_tool_use_response(str(body.get("model") or ""), emit_name, tool_input))
    return True


def filter_blocked_tools(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    """Strip Claude-Code self-tools the upstream model shouldn't see (e.g. EnterPlanMode).
    Returns a (possibly new) body dict."""
    blocked = resolve_blocked_tools(provider, pcfg)
    dynamic_blocked: set[str] = set()
    if provider != "anthropic" and ultracode_workflow_preferred(body) and not plan_mode_active(body):
        dynamic_blocked.add("EnterPlanMode")
    blocked = set(blocked) | dynamic_blocked
    if not blocked:
        return body
    tools = body.get("tools")
    tool_choice = body.get("tool_choice") if isinstance(body.get("tool_choice"), dict) else None
    tool_choice_name = tool_choice.get("name") if tool_choice else None
    must_drop_tool_choice = isinstance(tool_choice_name, str) and tool_choice_name in blocked
    if not isinstance(tools, list) or not tools:
        if not must_drop_tool_choice:
            return body
        new_body = dict(body)
        new_body.pop("tool_choice", None)
        router_log("WARN", f"removed blocked tool_choice for {provider}: {tool_choice_name}")
        return new_body
    kept: list[Any] = []
    dropped: list[str] = []
    for tool in tools:
        name = tool.get("name") if isinstance(tool, dict) else None
        if isinstance(name, str) and name in blocked:
            dropped.append(name)
            continue
        kept.append(tool)
    if not dropped:
        if not must_drop_tool_choice:
            return body
        new_body = dict(body)
        new_body.pop("tool_choice", None)
        router_log("WARN", f"removed blocked tool_choice for {provider}: {tool_choice_name}")
        return new_body
    reason = " ultracode_workflow_preferred=true" if dynamic_blocked & set(dropped) else ""
    router_log("INFO", f"filtered upstream tools for {provider}: {', '.join(sorted(set(dropped)))}{reason}")
    new_body = dict(body)
    new_body["tools"] = kept
    if must_drop_tool_choice:
        new_body.pop("tool_choice", None)
        router_log("WARN", f"removed blocked tool_choice for {provider}: {tool_choice_name}")
    return new_body


def summarize_messages_for_trace(messages: Any, max_messages: int = 30) -> list[dict[str, Any]]:
    if not isinstance(messages, list):
        return []
    selected = messages[-max_messages:]
    offset = len(messages) - len(selected)
    summary: list[dict[str, Any]] = []
    for index, message in enumerate(selected, start=offset):
        if not isinstance(message, dict):
            continue
        entry: dict[str, Any] = {"index": index, "role": message.get("role")}
        blocks: list[dict[str, Any]] = []
        content = message.get("content")
        if isinstance(content, str):
            blocks.append({"type": "text", "text": _truncate_for_dump(content, 1000)})
        elif isinstance(content, list):
            for block in content:
                if isinstance(block, str):
                    blocks.append({"type": "text", "text": _truncate_for_dump(block, 1000)})
                    continue
                if not isinstance(block, dict):
                    continue
                block_type = str(block.get("type") or "")
                if block_type == "text":
                    blocks.append({"type": "text", "text": _truncate_for_dump(block.get("text", ""), 1000)})
                elif block_type == "tool_use":
                    blocks.append(
                        {
                            "type": "tool_use",
                            "id": block.get("id"),
                            "name": block.get("name"),
                            "input": _truncate_for_dump(block.get("input"), 1200),
                        }
                    )
                elif block_type == "tool_result":
                    blocks.append(
                        {
                            "type": "tool_result",
                            "tool_use_id": block.get("tool_use_id"),
                            "is_error": block.get("is_error"),
                            "content": _truncate_for_dump(anthropic_content_to_text(block.get("content", "")), 1200),
                        }
                    )
                elif block_type == "thinking":
                    blocks.append(
                        {
                            "type": "thinking",
                            "thinking_len": len(str(block.get("thinking") or "")),
                            "has_signature": bool(block.get("signature")),
                        }
                    )
                elif block_type == "redacted_thinking":
                    blocks.append(
                        {
                            "type": "redacted_thinking",
                            "data_len": len(str(block.get("data") or "")),
                        }
                    )
                else:
                    blocks.append({"type": block_type or "unknown"})
        else:
            blocks.append({"type": type(content).__name__, "text": _truncate_for_dump(content, 1000)})
        entry["content"] = blocks
        summary.append(entry)
    return summary


def dump_request_for_trace(provider: str, path: str, body: dict[str, Any]) -> None:
    """At TRACE level, append a redacted snapshot of an inbound /v1/messages body
    (tools list, system prompt summary, message/tool block summary) to requests.jsonl.
    Used to capture tool definitions Claude Code injects (e.g. EnterPlanMode)."""
    if current_log_level() < LOG_LEVELS["TRACE"]:
        return
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        if REQUEST_DUMP_PATH.exists() and REQUEST_DUMP_PATH.stat().st_size > REQUEST_DUMP_MAX_BYTES:
            REQUEST_DUMP_PATH.replace(REQUEST_DUMP_PATH.with_suffix(".jsonl.1"))
        record = {
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "provider": provider,
            "path": path,
            "model": body.get("model"),
            "stream": body.get("stream"),
            "thinking": body.get("thinking"),
            "thinking_blocks": anthropic_thinking_block_count(body),
            "tool_continuation_blocks": anthropic_tool_continuation_block_count(body),
            "messages_count": len(body.get("messages") or []),
            "messages": summarize_messages_for_trace(body.get("messages")),
            "system": _truncate_for_dump(body.get("system")),
            "tools": body.get("tools"),
        }
        with REQUEST_DUMP_PATH.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
    except Exception:
        pass


def dump_response_for_trace(provider: str, model: str, text_so_far: str, tool_calls: list[dict[str, Any]], stop_reason: str | None, input_tokens: int, output_tokens: int, last_chunk: dict[str, Any] | None = None) -> None:
    """At TRACE level, append a per-response summary to responses.jsonl.
    Used to confirm what GLM-5.1 (and other upstream models) actually sent
    when the Claude Code session appears to stall."""
    if current_log_level() < LOG_LEVELS["TRACE"]:
        return
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        if RESPONSE_DUMP_PATH.exists() and RESPONSE_DUMP_PATH.stat().st_size > RESPONSE_DUMP_MAX_BYTES:
            RESPONSE_DUMP_PATH.replace(RESPONSE_DUMP_PATH.with_suffix(".jsonl.1"))
        text_truncated = text_so_far
        text_full_len = len(text_so_far)
        if text_full_len > RESPONSE_DUMP_TEXT_LIMIT:
            text_truncated = text_so_far[:RESPONSE_DUMP_TEXT_LIMIT] + f"...<truncated {text_full_len - RESPONSE_DUMP_TEXT_LIMIT} chars>"
        tool_summary: list[dict[str, Any]] = []
        for call in tool_calls:
            fn = call.get("function") if isinstance(call.get("function"), dict) else {}
            tool_summary.append({
                "name": (fn or {}).get("name"),
                "arguments": (fn or {}).get("arguments"),
            })
        record = {
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "provider": provider,
            "model": model,
            "stop_reason": stop_reason,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "text_full_len": text_full_len,
            "tool_call_count": len(tool_calls),
            "text": text_truncated,
            "tool_calls": tool_summary,
            "done_reason": (last_chunk or {}).get("done_reason"),
        }
        with RESPONSE_DUMP_PATH.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
    except Exception:
        pass


def sse_trace_enabled() -> bool:
    value = os.environ.get("CLAUDE_ANY_SSE_TRACE", "").strip().lower()
    if value in {"1", "true", "yes", "on", "trace"}:
        return True
    return current_log_level() >= LOG_LEVELS["TRACE"]


def _summarize_sse_payload(payload: dict[str, Any]) -> dict[str, Any]:
    summary: dict[str, Any] = {
        "type": payload.get("type"),
    }
    if "index" in payload:
        summary["index"] = payload.get("index")
    if payload.get("type") == "message_start":
        message = payload.get("message") if isinstance(payload.get("message"), dict) else {}
        summary["message"] = {
            "id": message.get("id"),
            "model": message.get("model"),
            "role": message.get("role"),
            "content_len": len(message.get("content") or []),
            "usage": message.get("usage"),
        }
    elif payload.get("type") == "message_delta":
        summary["delta"] = payload.get("delta")
        summary["usage"] = payload.get("usage")
    elif payload.get("type") == "content_block_start":
        block = payload.get("content_block") if isinstance(payload.get("content_block"), dict) else {}
        block_summary = {
            "type": block.get("type"),
        }
        if block.get("type") == "tool_use":
            block_summary.update(
                {
                    "id": block.get("id"),
                    "name": block.get("name"),
                    "input": block.get("input"),
                }
            )
        elif block.get("type") == "text":
            block_summary["text_len"] = len(str(block.get("text") or ""))
        else:
            block_summary["keys"] = sorted(str(k) for k in block.keys())
        summary["content_block"] = block_summary
    elif payload.get("type") == "content_block_delta":
        delta = payload.get("delta") if isinstance(payload.get("delta"), dict) else {}
        delta_type = delta.get("type")
        delta_summary: dict[str, Any] = {"type": delta_type}
        if delta_type == "text_delta":
            text = str(delta.get("text") or "")
            delta_summary["text_len"] = len(text)
            delta_summary["text"] = _truncate_for_dump(text, 500)
        elif delta_type == "input_json_delta":
            partial_json = str(delta.get("partial_json") or "")
            delta_summary["partial_json_len"] = len(partial_json)
            delta_summary["partial_json"] = _truncate_for_dump(partial_json, 1000)
        else:
            delta_summary["keys"] = sorted(str(k) for k in delta.keys())
        summary["delta"] = delta_summary
    else:
        summary["keys"] = sorted(str(k) for k in payload.keys())
    return summary


def make_outgoing_sse_trace(provider: str, model: str, source: str, source_body: dict[str, Any] | None = None) -> dict[str, Any]:
    return {
        "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "provider": provider,
        "model": model,
        "source": source,
        "stream": True,
        "messages_count": len(source_body.get("messages") or []) if isinstance(source_body, dict) else None,
        "tools_count": len(source_body.get("tools") or []) if isinstance(source_body, dict) else None,
        "metadata_keys": sorted(str(k) for k in (source_body.get("metadata") or {}).keys()) if isinstance(source_body, dict) and isinstance(source_body.get("metadata"), dict) else [],
        "event_count": 0,
        "events_truncated": False,
        "events": [],
    }


def record_outgoing_sse_event(trace: dict[str, Any] | None, event_name: str, payload: dict[str, Any]) -> None:
    if not isinstance(trace, dict):
        return
    try:
        trace["event_count"] = int(trace.get("event_count") or 0) + 1
        events = trace.get("events")
        if not isinstance(events, list):
            events = []
            trace["events"] = events
        if len(events) >= SSE_TRACE_EVENT_LIMIT:
            trace["events_truncated"] = True
            return
        payload_summary = _summarize_sse_payload(payload)
        raw = ""
        if sse_trace_enabled():
            try:
                raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
                raw = _truncate_for_dump(raw, SSE_TRACE_PAYLOAD_LIMIT)
            except Exception:
                raw = ""
        event = {
            "n": trace["event_count"],
            "event": event_name,
            "payload": payload_summary,
        }
        if raw:
            event["raw"] = raw
        events.append(event)
    except Exception:
        pass


def finish_outgoing_sse_trace(
    trace: dict[str, Any] | None,
    *,
    outcome: str,
    text_len: int = 0,
    tool_call_count: int = 0,
    chunks: int = 0,
    stop_reason: str | None = None,
    error: str | None = None,
) -> None:
    if not isinstance(trace, dict):
        return
    try:
        trace["finished_at"] = time.strftime("%Y-%m-%dT%H:%M:%S")
        trace["outcome"] = outcome
        trace["text_len"] = text_len
        trace["tool_call_count"] = tool_call_count
        trace["chunks"] = chunks
        trace["stop_reason"] = stop_reason
        if error:
            trace["error"] = error
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        tmp = SSE_LAST_PATH.with_name(f"{SSE_LAST_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(trace, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
        tmp.replace(SSE_LAST_PATH)
        if sse_trace_enabled():
            if SSE_TRACE_PATH.exists() and SSE_TRACE_PATH.stat().st_size > SSE_TRACE_MAX_BYTES:
                SSE_TRACE_PATH.replace(SSE_TRACE_PATH.with_suffix(".jsonl.1"))
            with SSE_TRACE_PATH.open("a", encoding="utf-8") as f:
                f.write(json.dumps(trace, ensure_ascii=False, separators=(",", ":")) + "\n")
    except Exception:
        pass


def append_tool_call_log(event: str, payload: dict[str, Any]) -> None:
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        if TOOL_CALL_LOG_PATH.exists() and TOOL_CALL_LOG_PATH.stat().st_size > 2_000_000:
            TOOL_CALL_LOG_PATH.replace(TOOL_CALL_LOG_PATH.with_suffix(".jsonl.1"))
        record = {
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "event": event,
            **payload,
        }
        with TOOL_CALL_LOG_PATH.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
    except Exception:
        pass


def model_cache_key(provider: str, pcfg: dict[str, Any]) -> str:
    api_count = provider_api_key_count(provider, pcfg)
    api_state = "key" if api_count else "nokey"
    return json.dumps(
        {
            "provider": provider,
            "base_url": pcfg.get("base_url", ""),
            "model_api_base_url": pcfg.get("model_api_base_url", ""),
            "account_id": pcfg.get("account_id", ""),
            "api": api_state,
            "custom": pcfg.get("custom_models", []),
            "schema": 7,
        },
        sort_keys=True,
    )


def anthropic_model_family_from_id(model_id: str) -> str:
    model = (model_id or "").strip().lower()
    for family in ("fable", "mythos", "opus", "sonnet", "haiku"):
        if re.search(rf"(?:^|-)claude-(?:\d+(?:-\d+){{0,2}}-)?{family}(?:-|$)", model) or f"-{family}-" in model:
            return family
    return "claude"


def anthropic_model_limit_hints(model_id: str) -> dict[str, Any]:
    model = (model_id or "").strip().lower()
    family = anthropic_model_family_from_id(model)
    # Keep provider limits as metadata, not launch defaults. The CLI default
    # max_output_tokens below is intentionally lower for interactive safety.
    if family in ("fable", "mythos"):
        return {
            "context_window": 1048576,
            "max_output_tokens": 128000,
            "source": "anthropic-models-overview-current-table",
        }
    if family == "opus" and re.search(r"(?:^|-)opus-4-[678](?:-|$)", model):
        return {
            "context_window": 1048576,
            "max_output_tokens": 128000,
            "source": "anthropic-models-overview-current-table",
        }
    if family == "sonnet" and re.search(r"(?:^|-)sonnet-4-6(?:-|$)", model):
        return {
            "context_window": 1048576,
            "max_output_tokens": 64000,
            "source": "anthropic-models-overview-current-table",
        }
    if family == "haiku" and re.search(r"(?:^|-)haiku-4-5(?:-|$)", model):
        return {
            "context_window": 200000,
            "max_output_tokens": 64000,
            "source": "anthropic-models-overview-current-table",
        }
    return {
        "context_window": 200000,
        "source": "anthropic-default-compatibility",
    }


def anthropic_model_runtime_hints(model_id: str) -> dict[str, Any]:
    model = (model_id or "").strip().lower()
    family = anthropic_model_family_from_id(model)
    if family in ("fable", "mythos"):
        return {
            "claude_code_default_effort": "high",
            "claude_code_max_effort": "xhigh",
            "thinking_mode": "adaptive",
            "extended_thinking": False,
            "adaptive_thinking_always_on": True,
            "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
            "source": "anthropic-models-overview-current-table",
        }
    if re.search(r"(?:^|-)opus-4-8(?:-|$)", model):
        return {
            "claude_code_default_effort": "high",
            "claude_code_max_effort": "xhigh",
            "thinking_mode": "adaptive",
            "fast_mode": {
                "available": True,
                "preview": True,
            },
            "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
            "source": "anthropic-opus-4-8-launch-notes",
        }
    return {}


CLAUDE_CODE_SUPPORTED_CAPABILITY_VALUES: tuple[str, ...] = (
    "effort",
    "xhigh_effort",
    "max_effort",
    "thinking",
    "adaptive_thinking",
    "interleaved_thinking",
)


def normalize_claude_code_supported_capabilities(value: Any) -> list[str]:
    if value is None or value is False:
        return []
    if isinstance(value, str):
        raw_items = re.split(r"[,;\s]+", value.strip())
    elif isinstance(value, (list, tuple, set)):
        raw_items = [str(item) for item in value]
    else:
        raw_items = [str(value)]
    allowed = set(CLAUDE_CODE_SUPPORTED_CAPABILITY_VALUES)
    out: list[str] = []
    seen: set[str] = set()
    for raw in raw_items:
        item = str(raw or "").strip().lower().replace("-", "_")
        if not item:
            continue
        if item not in allowed:
            continue
        if item in seen:
            continue
        seen.add(item)
        out.append(item)
    return out


def infer_claude_code_supported_capabilities_from_model(model_id: str) -> list[str]:
    model = strip_claude_context_suffix(model_id).strip().lower()
    if re.search(r"(?:^|-)claude-(?:fable|mythos)(?:-|$)", model):
        return [
            "effort",
            "xhigh_effort",
            "max_effort",
            "thinking",
            "adaptive_thinking",
        ]
    if re.search(r"(?:^|-)opus-4-[78](?:-|$)", model):
        return [
            "effort",
            "xhigh_effort",
            "max_effort",
            "thinking",
            "adaptive_thinking",
            "interleaved_thinking",
        ]
    if re.search(r"(?:^|-)(?:opus-4-6|sonnet-4-6)(?:-|$)", model):
        return [
            "effort",
            "max_effort",
            "thinking",
            "adaptive_thinking",
            "interleaved_thinking",
        ]
    return []


def claude_code_supported_capabilities(provider: str, pcfg: dict[str, Any], model_id: str | None = None) -> list[str]:
    configured = pcfg.get("claude_code_supported_capabilities")
    caps = normalize_claude_code_supported_capabilities(configured)
    if caps:
        return caps
    model = model_id or current_upstream_model_id(provider, pcfg)
    return infer_claude_code_supported_capabilities_from_model(model)


def claude_code_capability_string(provider: str, pcfg: dict[str, Any], model_id: str | None = None) -> str:
    return ",".join(claude_code_supported_capabilities(provider, pcfg, model_id))


def claude_code_workflows_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    if parse_bool(pcfg.get("ultracode_enabled") if "ultracode_enabled" in pcfg else pcfg.get("ultracode"), False):
        return True
    if "workflows_enabled" in pcfg:
        return parse_bool(pcfg.get("workflows_enabled"), False)
    return parse_bool(pcfg.get("workflows"), False)


def claude_code_ultracode_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    if "ultracode_enabled" in pcfg:
        return parse_bool(pcfg.get("ultracode_enabled"), False)
    return parse_bool(pcfg.get("ultracode"), False)


def anthropic_recommended_preset_for_model(model_id: str) -> str:
    if anthropic_model_family_from_id(model_id) == "haiku":
        return "fast"
    return "balanced"


def model_registry_recommendations(provider: str, models: list[str]) -> dict[str, Any]:
    recommendations: dict[str, Any] = {}
    for model_id in unique_model_ids(provider, models):
        if provider != "anthropic":
            continue
        preset_id = anthropic_recommended_preset_for_model(model_id)
        timeout_ms = llm_preset_timeout_ms(preset_id)
        recommendations[model_id] = {
            "schema": 1,
            "model_family": anthropic_model_family_from_id(model_id),
            "recommended_preset": preset_id,
            "parameters": {
                "max_output_tokens": 2048 if preset_id == "fast" else 4096,
                "request_timeout_ms": timeout_ms,
                "stream_idle_timeout_ms": timeout_profile_idle_ms(timeout_ms),
            },
            "limits": anthropic_model_limit_hints(model_id),
            "runtime": anthropic_model_runtime_hints(model_id),
            "notes": [
                "Native Claude Code manages the real context window; claude-any stores context limits as model metadata only.",
                "Recommended max_output_tokens is intentionally lower than the provider hard limit for interactive CLI use.",
            ],
        }
    return recommendations


def read_model_registry(provider: str, pcfg: dict[str, Any], max_age_seconds: float = MODEL_CACHE_TTL_SECONDS) -> dict[str, Any] | None:
    try:
        data = json.loads(MODEL_REGISTRY_PATH.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(data, dict):
        return None
    providers = data.get("providers")
    if not isinstance(providers, dict):
        return None
    entry = providers.get(provider)
    if not isinstance(entry, dict):
        return None
    if entry.get("key") != model_cache_key(provider, pcfg):
        if provider != "anthropic" or entry.get("source") != "anthropic-docs":
            return None
        try:
            entry_key = json.loads(str(entry.get("key") or "{}"))
            current_key = json.loads(model_cache_key(provider, pcfg))
            entry_key.pop("api", None)
            current_key.pop("api", None)
            if entry_key != current_key:
                return None
        except Exception:
            return None
    try:
        if max_age_seconds > 0 and time.time() - float(entry.get("time", 0)) > max_age_seconds:
            return None
    except Exception:
        return None
    models = entry.get("models")
    if not isinstance(models, list):
        return None
    return entry


def read_model_registry_models(provider: str, pcfg: dict[str, Any], max_age_seconds: float = MODEL_CACHE_TTL_SECONDS) -> list[str] | None:
    entry = read_model_registry(provider, pcfg, max_age_seconds=max_age_seconds)
    models = entry.get("models") if entry else None
    return unique_model_ids(provider, [str(mid) for mid in models if str(mid).strip()]) if isinstance(models, list) else None


def read_model_registry_info(provider: str, pcfg: dict[str, Any], max_age_seconds: float = MODEL_CACHE_TTL_SECONDS) -> dict[str, dict[str, Any]]:
    entry = read_model_registry(provider, pcfg, max_age_seconds=max_age_seconds)
    metadata = entry.get("metadata") if entry else None
    model_info = metadata.get("model_info") if isinstance(metadata, dict) else None
    if not isinstance(model_info, dict):
        return {}
    out: dict[str, dict[str, Any]] = {}
    for raw_id, raw_info in model_info.items():
        model_id = normalize_model_id(provider, str(raw_id))
        if not model_id or not isinstance(raw_info, dict):
            continue
        info = dict(raw_info)
        max_context = positive_int(info.get("max_model_len"))
        if max_context:
            info["max_model_len"] = max_context
        out[model_id] = info
    return out


def write_model_registry(provider: str, pcfg: dict[str, Any], models: list[str], source: str = "provider", metadata: dict[str, Any] | None = None) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    try:
        data = json.loads(MODEL_REGISTRY_PATH.read_text(encoding="utf-8")) if MODEL_REGISTRY_PATH.exists() else {}
    except Exception:
        data = {}
    if not isinstance(data, dict):
        data = {}
    providers = data.get("providers")
    if not isinstance(providers, dict):
        providers = {}
    providers[provider] = {
        "time": time.time(),
        "key": model_cache_key(provider, pcfg),
        "source": source,
        "models": unique_model_ids(provider, models),
        "recommendations": model_registry_recommendations(provider, models),
        "metadata": metadata or {},
    }
    data["schema"] = 1
    data["providers"] = providers
    try:
        MODEL_REGISTRY_PATH.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
        os.chmod(MODEL_REGISTRY_PATH, 0o600)
    except Exception:
        pass


def read_model_list_cache(provider: str, pcfg: dict[str, Any]) -> list[str] | None:
    try:
        data = json.loads(MODEL_LIST_CACHE_PATH.read_text())
    except Exception:
        return read_model_registry_models(provider, pcfg, max_age_seconds=0)
    if not isinstance(data, dict):
        return read_model_registry_models(provider, pcfg, max_age_seconds=0)
    if data.get("key") != model_cache_key(provider, pcfg):
        return read_model_registry_models(provider, pcfg, max_age_seconds=0)
    if time.time() - float(data.get("time", 0)) > MODEL_CACHE_TTL_SECONDS:
        registry_models = read_model_registry_models(provider, pcfg, max_age_seconds=0)
        if registry_models:
            return registry_models
    models = data.get("models")
    if not isinstance(models, list):
        return None
    return unique_model_ids(provider, [str(mid) for mid in models if str(mid).strip()])


def read_model_info_cache(provider: str, pcfg: dict[str, Any]) -> dict[str, dict[str, Any]]:
    try:
        data = json.loads(MODEL_LIST_CACHE_PATH.read_text())
    except Exception:
        return read_model_registry_info(provider, pcfg, max_age_seconds=0)
    if not isinstance(data, dict) or data.get("key") != model_cache_key(provider, pcfg):
        return read_model_registry_info(provider, pcfg, max_age_seconds=0)
    metadata = data.get("metadata")
    model_info = metadata.get("model_info") if isinstance(metadata, dict) else None
    if not isinstance(model_info, dict):
        return read_model_registry_info(provider, pcfg, max_age_seconds=0)
    out: dict[str, dict[str, Any]] = {}
    for raw_id, raw_info in model_info.items():
        model_id = normalize_model_id(provider, str(raw_id))
        if not model_id or not isinstance(raw_info, dict):
            continue
        info = dict(raw_info)
        max_context = positive_int(info.get("max_model_len"))
        if max_context:
            info["max_model_len"] = max_context
        out[model_id] = info
    return out


def write_model_list_cache(provider: str, pcfg: dict[str, Any], models: list[str], metadata: dict[str, Any] | None = None) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    data = {"time": time.time(), "key": model_cache_key(provider, pcfg), "models": models, "metadata": metadata or {}}
    try:
        MODEL_LIST_CACHE_PATH.write_text(json.dumps(data, indent=2) + "\n")
        os.chmod(MODEL_LIST_CACHE_PATH, 0o600)
    except Exception:
        pass
    write_model_registry(provider, pcfg, models, "provider", metadata)


def cached_or_configured_model_ids(provider: str, pcfg: dict[str, Any]) -> list[str]:
    ids = read_model_list_cache(provider, pcfg) or []
    if provider == "ollama-cloud":
        ids.extend(ollama_catalog_model_ids(provider))
    for mid in pcfg.get("custom_models", []) or []:
        mid = normalize_model_id(provider, mid)
        if mid and mid not in ids:
            ids.append(mid)
    cur = normalize_model_id(provider, pcfg.get("current_model") or "")
    if cur and cur not in ids and not cur.startswith(f"claude-any-{provider}-"):
        ids.insert(0, cur)
    ids = unique_model_ids(provider, ids)
    if provider == "anthropic":
        return ids
    return sorted_model_ids(ids)


def model_ids_from_response(data: Any) -> list[str]:
    ids: list[str] = []
    candidates: Any
    if isinstance(data, dict):
        candidates = data.get("data")
        if candidates is None:
            candidates = data.get("models")
        if candidates is None:
            candidates = data.get("model")
    else:
        candidates = data
    if isinstance(candidates, str):
        candidates = [candidates]
    if not isinstance(candidates, list):
        return ids
    for item in candidates:
        if isinstance(item, str):
            mid = item
        elif isinstance(item, dict):
            mid = item.get("id") or item.get("key") or item.get("name") or item.get("model")
        else:
            mid = None
        if mid and str(mid).strip():
            ids.append(str(mid).strip())
    return ids


def model_info_from_response(provider: str, data: Any) -> dict[str, dict[str, Any]]:
    candidates: Any
    if isinstance(data, dict):
        candidates = data.get("data")
        if candidates is None:
            candidates = data.get("models")
        if candidates is None:
            candidates = data.get("model")
    else:
        candidates = data
    if isinstance(candidates, dict):
        candidates = [candidates]
    if isinstance(candidates, str):
        candidates = [candidates]
    if not isinstance(candidates, list):
        return {}
    out: dict[str, dict[str, Any]] = {}
    for item in candidates:
        if isinstance(item, str):
            mid = item
            raw: dict[str, Any] = {}
        elif isinstance(item, dict):
            mid = item.get("id") or item.get("key") or item.get("name") or item.get("model")
            raw = item
        else:
            continue
        model_id = normalize_model_id(provider, str(mid or "").strip())
        if not model_id:
            continue
        max_context = positive_int(raw.get("max_context_length")) or model_context_field(raw)
        info: dict[str, Any] = {}
        if max_context:
            info["max_model_len"] = max_context
        if provider == "fireworks" and isinstance(raw, dict):
            for source_key, target_key in (
                ("displayName", "display_name"),
                ("description", "description"),
                ("kind", "kind"),
                ("importedFrom", "imported_from"),
            ):
                value = raw.get(source_key)
                if value is not None:
                    info[target_key] = value
            for source_key, target_key in (
                ("supportsTools", "supports_tool_call"),
                ("supportsImageInput", "supports_vision"),
                ("public", "public"),
                ("supportsServerless", "supports_serverless"),
            ):
                value = raw.get(source_key)
                if isinstance(value, bool):
                    info[target_key] = value
            details = raw.get("baseModelDetails")
            if isinstance(details, dict):
                parameter_count = details.get("parameterCount")
                if parameter_count is not None:
                    info["parameter_count"] = str(parameter_count)
                for source_key, target_key in (
                    ("worldSize", "world_size"),
                    ("checkpointFormat", "checkpoint_format"),
                    ("modelType", "model_type"),
                    ("defaultPrecision", "default_precision"),
                ):
                    value = details.get(source_key)
                    if value is not None:
                        info[target_key] = value
        for key in ("owned_by", "root", "object"):
            value = raw.get(key)
            if value is not None:
                info[key] = value
        if info:
            out[model_id] = info
    return out


def fireworks_account_id(pcfg: dict[str, Any]) -> str:
    configured = str(pcfg.get("account_id") or "").strip()
    if configured:
        return configured
    for value in (pcfg.get("current_model"), *(pcfg.get("custom_models", []) or [])):
        text = str(value or "")
        match = re.match(r"^accounts/([^/]+)/models/[^/]+$", text)
        if match:
            return match.group(1)
    return FIREWORKS_DEFAULT_ACCOUNT_ID


def fireworks_management_base_url(pcfg: dict[str, Any]) -> str:
    configured = str(pcfg.get("model_api_base_url") or "").strip().rstrip("/")
    base = str(pcfg.get("base_url") or FIREWORKS_INFERENCE_BASE_URL).strip().rstrip("/")
    parsed = urllib.parse.urlparse(base)
    if configured and (
        configured != FIREWORKS_API_BASE_URL
        or not (parsed.scheme and parsed.netloc)
        or parsed.netloc.endswith("fireworks.ai")
    ):
        return configured
    if parsed.scheme and parsed.netloc:
        return f"{parsed.scheme}://{parsed.netloc}"
    return configured or FIREWORKS_API_BASE_URL


def fetch_fireworks_model_ids(
    pcfg: dict[str, Any],
    headers: dict[str, str],
    timeout: float = 8.0,
) -> tuple[list[str], dict[str, dict[str, Any]], str]:
    account_id = fireworks_account_id(pcfg)
    base = fireworks_management_base_url(pcfg)
    models: list[str] = []
    model_info: dict[str, dict[str, Any]] = {}
    page_token = ""
    source = f"fireworks:{account_id}"
    for _ in range(20):
        query = {"pageSize": "200"}
        if page_token:
            query["pageToken"] = page_token
        path = f"/v1/accounts/{urllib.parse.quote(account_id, safe='')}/models?{urllib.parse.urlencode(query)}"
        data = http_json(join_url(base, path), headers=headers, timeout=timeout, provider="fireworks", pcfg=pcfg)
        ids = [normalize_model_id("fireworks", mid) for mid in model_ids_from_response(data)]
        for mid in ids:
            if mid and mid not in models:
                models.append(mid)
        model_info.update(model_info_from_response("fireworks", data))
        if not isinstance(data, dict):
            break
        page_token = str(data.get("nextPageToken") or "").strip()
        if not page_token:
            break
    return models, model_info, source


ANTHROPIC_PUBLIC_MODEL_ID_RE = re.compile(
    r"(?<![A-Za-z0-9_.@:-])"
    r"(?:"
    r"claude-(?:fable|mythos)-\d+(?:-\d+)?(?:-\d{8})?|"
    r"claude-mythos-preview|"
    r"claude-(?:opus|sonnet|haiku)-\d+-\d+-\d{8}|"
    r"claude-(?:opus|sonnet|haiku)-\d+-\d{8}|"
    r"claude-(?:opus|sonnet|haiku)-\d+-\d+|"
    r"claude-(?:opus|sonnet|haiku)-\d+(?:-\d+)?-latest|"
    r"claude-\d+(?:-\d+){0,2}-(?:opus|sonnet|haiku)-(?:\d{8}|latest)"
    r")"
    r"(?![A-Za-z0-9_.@:-])"
)


def fetch_text_url(url: str, timeout: float = 8.0) -> str:
    req = urllib.request.Request(url, headers=with_upstream_user_agent())
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return resp.read(5_000_000).decode("utf-8", errors="replace")


def anthropic_model_ids_from_docs_text(text: str) -> list[str]:
    """Extract public Claude API model IDs from Anthropic's models overview page.

    Claude Native usually runs on Claude Code OAuth rather than an API key, so
    `/v1/models` is not available to claude-any. The public docs are the only
    unauthenticated source for the current model picker seed.
    """
    ids: list[str] = []
    seen: set[str] = set()
    for match in ANTHROPIC_PUBLIC_MODEL_ID_RE.finditer(html_lib.unescape(text or "")):
        mid = match.group(0)
        key = mid.casefold()
        if key in seen:
            continue
        seen.add(key)
        ids.append(mid)
    return ids


def filter_anthropic_default_model_ids(ids: list[str]) -> list[str]:
    """Keep only generally available current Claude models for the default picker.

    Anthropic's model overview page also mentions limited-access research models,
    cloud-provider IDs, and legacy/upgrade-path IDs. Those are useful reference
    text but bad defaults for Claude Code Native and routed launches because many
    users cannot select them. Custom model IDs still remain supported separately.
    """
    allowed = set(ANTHROPIC_PUBLIC_MODEL_DEFAULT_IDS)
    limited = set(ANTHROPIC_LIMITED_ACCESS_MODEL_IDS)
    out: list[str] = []
    seen: set[str] = set()
    for raw in ids:
        mid = normalize_model_id("anthropic", raw)
        key = mid.casefold()
        if not mid or key in seen or mid in limited or mid not in allowed:
            continue
        out.append(mid)
        seen.add(key)
    return out


def fetch_anthropic_public_model_ids(timeout: float = 8.0) -> list[str]:
    ids: list[str] = []
    errors: list[str] = []
    for url in ANTHROPIC_MODEL_DOCS_URLS:
        try:
            ids.extend(anthropic_model_ids_from_docs_text(fetch_text_url(url, timeout=timeout)))
        except Exception as exc:
            errors.append(f"{url}: {type(exc).__name__}: {exc}")
    out = filter_anthropic_default_model_ids(unique_model_ids("anthropic", ids))
    if out:
        return out
    if errors:
        router_log("WARN", "anthropic model docs fetch failed: " + " ; ".join(errors))
    return list(ANTHROPIC_PUBLIC_MODEL_FALLBACK_IDS)


def opencode_zen_endpoint_kind(model_id: str) -> str:
    """Return the documented OpenCode Zen endpoint family for a model id."""
    model = strip_claude_context_suffix(model_id).strip().lower()
    if model.startswith("claude-any-opencode-"):
        model = model[len("claude-any-opencode-"):]
    if model.startswith("claude-") or model.startswith("qwen3."):
        return "anthropic-messages"
    if model.startswith("gpt-"):
        return "openai-responses"
    if model.startswith("gemini-"):
        return "google-generative"
    if model.startswith(("minimax-", "glm-", "kimi-", "grok-", "big-pickle", "deepseek-", "mimo-", "nemotron-")):
        return "openai-chat"
    return "anthropic-messages"


def opencode_zen_model_supported_by_router(model_id: str) -> bool:
    return opencode_zen_endpoint_kind(model_id) in ("anthropic-messages", "openai-chat")


def normalize_opencode_endpoint_kind(value: Any) -> str | None:
    key = str(value or "").strip().lower().replace("_", "-")
    return OPENCODE_ENDPOINT_ALIASES.get(key)


def opencode_endpoint_override(provider: str, model_id: str, pcfg: dict[str, Any] | None = None) -> str | None:
    if not pcfg:
        return None
    overrides = pcfg.get("model_endpoints")
    if not isinstance(overrides, dict):
        return None
    normalized = normalize_model_id(provider, model_id)
    candidates = [
        model_id,
        normalized,
        strip_claude_context_suffix(model_id),
        alias_for(provider, normalized),
    ]
    for candidate in candidates:
        raw = overrides.get(candidate)
        endpoint = normalize_opencode_endpoint_kind(raw)
        if endpoint:
            return endpoint
    return None


def opencode_go_endpoint_kind(model_id: str) -> str:
    """Return the documented OpenCode Go endpoint family for a model id."""
    model = strip_claude_context_suffix(model_id).strip().lower()
    if model.startswith("claude-any-opencode-go-"):
        model = model[len("claude-any-opencode-go-"):]
    if model.startswith("qwen3.") or model.startswith("minimax-"):
        return "anthropic-messages"
    if model.startswith(("glm-", "kimi-", "deepseek-", "mimo-", "hy3-")):
        return "openai-chat"
    return "anthropic-messages"


def opencode_endpoint_kind(provider: str, model_id: str, pcfg: dict[str, Any] | None = None) -> str:
    override = opencode_endpoint_override(provider, model_id, pcfg)
    if override:
        return override
    if provider == "opencode-go":
        return opencode_go_endpoint_kind(model_id)
    return opencode_zen_endpoint_kind(model_id)


def opencode_model_supported_by_router(provider: str, model_id: str, pcfg: dict[str, Any] | None = None) -> bool:
    return opencode_endpoint_kind(provider, model_id, pcfg) in ("anthropic-messages", "openai-chat")


def opencode_endpoint_display(provider: str, model_id: str, pcfg: dict[str, Any] | None = None) -> str:
    endpoint = opencode_endpoint_kind(provider, model_id, pcfg)
    labels = {
        "anthropic-messages": "messages",
        "openai-chat": "chat",
        "openai-responses": "responses",
        "google-generative": "gemini",
    }
    text = labels.get(endpoint, endpoint)
    if not opencode_model_supported_by_router(provider, model_id, pcfg):
        text += " unsupported"
    if opencode_endpoint_override(provider, model_id, pcfg):
        text += " override"
    return text


def nvidia_hosted_list_headers() -> dict[str, str]:
    headers = {"content-type": "application/json"}
    key = read_env_file(NCP_ENV).get("NVIDIA_API_KEY") or os.environ.get("NVIDIA_API_KEY")
    if key:
        headers["authorization"] = f"Bearer {key}"
        headers["x-api-key"] = key
    return headers


def provider_model_list_headers(provider: str, pcfg: dict[str, Any]) -> dict[str, str]:
    headers = with_upstream_user_agent({"content-type": "application/json"})
    key = provider_primary_api_key(provider, pcfg)
    if provider == "anthropic" and key:
        headers["anthropic-version"] = "2023-06-01"
        headers["x-api-key"] = str(key)
    elif meaningful_key(str(key) if key is not None else None):
        headers["authorization"] = f"Bearer {key}"
        headers["x-api-key"] = str(key)
    return headers


def fetch_anthropic_api_model_ids(
    pcfg: dict[str, Any],
    headers: dict[str, str],
    timeout: float = 6.0,
) -> tuple[list[str], str]:
    base = provider_upstream_request_base("anthropic", pcfg)
    errors: list[str] = []
    for path in ("/v1/models", "/models"):
        try:
            data = http_json(join_url(base, path), headers=headers, timeout=timeout)
            ids = unique_model_ids("anthropic", model_ids_from_response(data))
            if ids:
                return ids, f"api:{path}"
        except Exception as exc:
            errors.append(f"{path}: {type(exc).__name__}: {exc}")
    if errors:
        router_log("DEBUG", "anthropic model API fetch failed: " + " ; ".join(errors))
    return [], ""


def post_json(
    url: str,
    body: Any,
    headers: dict[str, str] | None = None,
    timeout: float = 60.0,
    provider: str | None = None,
    pcfg: dict[str, Any] | None = None,
) -> Any:
    data = json.dumps(body).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers=with_upstream_user_agent(headers), method="POST")
    with provider_urlopen(req, timeout=timeout, provider=provider, pcfg=pcfg) as r:
        return json.loads(r.read().decode("utf-8"))


def router_rate_limit_configured_rpm(provider: str, pcfg: dict[str, Any]) -> int | None:
    raw = pcfg.get("rate_limit_rpm")
    if raw is None:
        return None
    if isinstance(raw, str) and raw.strip().lower() in ("0", "false", "off", "disable", "disabled", "none", "unset"):
        return 0
    try:
        if int(raw) == 0:
            return 0
    except Exception:
        pass
    rpm = positive_int(raw)
    return rpm if rpm and rpm > 0 else None


def router_rate_limit_rpm(provider: str, pcfg: dict[str, Any]) -> int | None:
    rpm = router_rate_limit_configured_rpm(provider, pcfg)
    return rpm if rpm and rpm > 0 else None


def router_rate_limit_key(provider: str, pcfg: dict[str, Any], model: str | None = None) -> str:
    # Provider/account limits such as NVIDIA NIM RPM apply across models.
    return f"{provider}:__global__"


def router_rate_limit_state_entry(provider: str, pcfg: dict[str, Any], model: str | None = None) -> dict[str, Any]:
    key = router_rate_limit_key(provider, pcfg, model)
    try:
        state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
        if not isinstance(state, dict):
            return {}
        entry = state.get(key)
        if isinstance(entry, dict):
            return entry
        legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
        entry = state.get(legacy_key)
        return entry if isinstance(entry, dict) else {}
    except Exception:
        return {}


def router_rate_limit_effective_rpm(provider: str, pcfg: dict[str, Any], model: str | None = None) -> int | None:
    configured = router_rate_limit_configured_rpm(provider, pcfg)
    if configured == 0:
        return 0
    entry = router_rate_limit_state_entry(provider, pcfg, model)
    try:
        server_rpm = int(entry.get("server_rpm") or 0)
        updated_at = float(entry.get("server_rpm_updated_at") or 0.0)
        if server_rpm > 0 and 0.0 <= time.time() - updated_at < 3600.0:
            return server_rpm
    except Exception:
        pass
    return configured


def router_rate_limit_capacity(rpm: int) -> int:
    if rpm <= 1:
        return 1
    reserve = 1 if rpm <= 20 else max(1, math.ceil(rpm * 0.05))
    return max(1, rpm - reserve)


def router_rate_limit_recent(timestamps: Any, now: float, window: float, *, include_future: bool) -> list[float]:
    recent: list[float] = []
    for ts in timestamps or []:
        if not isinstance(ts, (int, float)):
            continue
        value = float(ts)
        age = now - value
        if age < window and (include_future or age >= 0.0):
            recent.append(value)
    return sorted(recent)


def router_rate_limit_usage(provider: str, pcfg: dict[str, Any], model: str | None = None) -> tuple[int, int | None]:
    rpm = router_rate_limit_effective_rpm(provider, pcfg, model)
    if rpm is None:
        return 0, None
    if rpm == 0:
        return 0, 0
    key = router_rate_limit_key(provider, pcfg, model)
    now = time.time()
    try:
        state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
        entry = state.get(key) if isinstance(state, dict) else None
        if not isinstance(entry, dict):
            legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
            entry = state.get(legacy_key) if isinstance(state, dict) else None
        timestamps = entry.get("timestamps") if isinstance(entry, dict) else ([float(entry)] if isinstance(entry, (int, float)) else [])
    except Exception:
        timestamps = []
    used = len(router_rate_limit_recent(timestamps, now, 60.0, include_future=False))
    return used, rpm


def record_router_rate_usage(provider: str, pcfg: dict[str, Any], model: str | None, rpm: int | None) -> tuple[int, int | None]:
    if rpm is None:
        return 0, None
    key = router_rate_limit_key(provider, pcfg, model)
    window = 60.0
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
            if not isinstance(state, dict):
                state = {}
        except Exception:
            state = {}
        now = time.time()
        entry = state.get(key)
        if not isinstance(entry, dict):
            legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
            entry = state.get(legacy_key)
        timestamps = entry.get("timestamps") if isinstance(entry, dict) else ([float(entry)] if isinstance(entry, (int, float)) else [])
        recent = router_rate_limit_recent(timestamps, now, window, include_future=True)
        recent.append(now)
        keep = max(int(rpm or 0), 240)
        existing_penalty = float(entry.get("penalty_until") or 0.0) if isinstance(entry, dict) else 0.0
        new_entry: dict[str, Any] = {"timestamps": recent[-keep:], "rpm": int(rpm or 0), "updated_at": now, "last_wait": 0.0}
        if existing_penalty > now:
            new_entry["penalty_until"] = existing_penalty
        state[key] = new_entry
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
        return len(recent), rpm


def parse_retry_after_seconds(value: str | None) -> float | None:
    if not value:
        return None
    text = value.strip()
    try:
        seconds = float(text)
        return max(0.0, seconds)
    except Exception:
        pass
    try:
        dt = parsedate_to_datetime(text)
        return max(0.0, dt.timestamp() - time.time())
    except Exception:
        return None


def format_duration_seconds(seconds: float) -> str:
    total = max(0, int(round(seconds)))
    days, remainder = divmod(total, 86400)
    hours, remainder = divmod(remainder, 3600)
    minutes, secs = divmod(remainder, 60)
    parts: list[str] = []
    if days:
        parts.append(f"{days}d")
    if hours:
        parts.append(f"{hours}h")
    if minutes:
        parts.append(f"{minutes}m")
    if secs or not parts:
        parts.append(f"{secs}s")
    return " ".join(parts)


def first_header(headers: Any, names: list[str]) -> str | None:
    for name in names:
        try:
            value = headers.get(name)
        except Exception:
            value = None
        if value:
            return str(value)
    return None


def first_int_in_header(value: str | None) -> int | None:
    if not value:
        return None
    match = re.search(r"\d+", value)
    if not match:
        return None
    try:
        return int(match.group(0))
    except Exception:
        return None


def rate_limit_reset_seconds(value: str | None) -> float | None:
    if not value:
        return None
    text = value.strip()
    try:
        numeric = float(text)
        # Some providers (e.g. OpenRouter X-RateLimit-Reset) report the reset as a
        # millisecond Unix timestamp. A ms epoch is always an absolute timestamp, so
        # convert it and return the delta directly -- without this a value like
        # 1.78e12 is read as a seconds epoch and yields a ~55,000-year wait, and a
        # near-term reset (< 60s away) would otherwise be misread as a relative value.
        if numeric > 1e12:
            return max(0.0, numeric / 1000.0 - time.time())
        if numeric > time.time() + 60.0:
            return max(0.0, numeric - time.time())
        return max(0.0, numeric)
    except Exception:
        return parse_retry_after_seconds(text)


def learn_router_rate_limit_headers(provider: str, pcfg: dict[str, Any], model: str | None, headers: Any) -> None:
    limit = first_int_in_header(first_header(headers, [
        "x-ratelimit-limit-requests",
        "x-rate-limit-limit-requests",
        "ratelimit-limit",
        "rate-limit-limit",
        "x-ratelimit-limit",
        "x-rate-limit-limit",
    ]))
    remaining = first_int_in_header(first_header(headers, [
        "x-ratelimit-remaining-requests",
        "x-rate-limit-remaining-requests",
        "ratelimit-remaining",
        "rate-limit-remaining",
        "x-ratelimit-remaining",
        "x-rate-limit-remaining",
    ]))
    reset = rate_limit_reset_seconds(first_header(headers, [
        "x-ratelimit-reset-requests",
        "x-rate-limit-reset-requests",
        "ratelimit-reset",
        "rate-limit-reset",
        "x-ratelimit-reset",
        "x-rate-limit-reset",
    ]))
    if limit is None and remaining is None and reset is None:
        return
    configured = router_rate_limit_configured_rpm(provider, pcfg)
    rpm = limit if limit and limit > 0 else configured
    if rpm is None:
        rpm = 0
    multi_key = provider_api_key_count(provider, pcfg) > 1
    key = router_rate_limit_key(provider, pcfg, model)
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
            if not isinstance(state, dict):
                state = {}
        except Exception:
            state = {}
        now = time.time()
        entry = state.get(key)
        if not isinstance(entry, dict):
            legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
            entry = state.get(legacy_key)
        timestamps = entry.get("timestamps") if isinstance(entry, dict) else []
        recent = router_rate_limit_recent(timestamps, now, 60.0, include_future=True)
        penalty_until = 0.0 if multi_key else (float(entry.get("penalty_until") or 0.0) if isinstance(entry, dict) else 0.0)
        if remaining == 0 and reset and reset > 0 and not multi_key:
            penalty_until = max(penalty_until, now + reset)
        new_entry: dict[str, Any] = {
            "timestamps": recent[-max(int(rpm or 0), 240):],
            "rpm": int(rpm or 0),
            "updated_at": now,
            "last_wait": float(entry.get("last_wait") or 0.0) if isinstance(entry, dict) else 0.0,
            "server_remaining": remaining,
            "server_reset_seconds": reset,
        }
        if limit and limit > 0:
            new_entry["server_rpm"] = int(limit)
            new_entry["server_rpm_updated_at"] = now
        elif isinstance(entry, dict) and entry.get("server_rpm"):
            new_entry["server_rpm"] = entry.get("server_rpm")
            new_entry["server_rpm_updated_at"] = entry.get("server_rpm_updated_at")
        if penalty_until > now:
            new_entry["penalty_until"] = penalty_until
        state[key] = new_entry
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
    extra = " multi_key_no_global_penalty=1" if multi_key and remaining == 0 and reset and reset > 0 else ""
    router_log("INFO", f"rate_limit_headers provider={provider} model={model or ''} limit={limit} remaining={remaining} reset={reset}{extra}")


def register_router_rate_limit_backoff(provider: str, pcfg: dict[str, Any], model: str | None, retry_after: str | None = None) -> float:
    rpm = router_rate_limit_effective_rpm(provider, pcfg, model)
    fallback = 60.0 / float(rpm) if rpm and rpm > 0 else 15.0
    wait = parse_retry_after_seconds(retry_after)
    if wait is None:
        wait = max(10.0, min(60.0, fallback * 4.0))
    wait = max(1.0, min(300.0, wait))
    key = router_rate_limit_key(provider, pcfg, model)
    multi_key = provider_api_key_count(provider, pcfg) > 1
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
            if not isinstance(state, dict):
                state = {}
        except Exception:
            state = {}
        now = time.time()
        entry = state.get(key)
        if not isinstance(entry, dict):
            legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
            entry = state.get(legacy_key)
        timestamps = entry.get("timestamps") if isinstance(entry, dict) else []
        recent = router_rate_limit_recent(timestamps, now, 60.0, include_future=True)
        actual_recent = router_rate_limit_recent(timestamps, now, 60.0, include_future=False)
        configured_rpm = router_rate_limit_configured_rpm(provider, pcfg)
        inferred_rpm: int | None = None
        if (
            isinstance(entry, dict)
            and not entry.get("server_rpm")
            and configured_rpm
            and configured_rpm > 0
            and 0 < len(actual_recent) < configured_rpm
        ):
            inferred_rpm = max(1, len(actual_recent))
            rpm = inferred_rpm
        capacity = router_rate_limit_capacity(int(rpm or 0)) if rpm and rpm > 0 else int(rpm or 0)
        if capacity and capacity > 0 and len(actual_recent) >= capacity and actual_recent:
            wait = max(wait, max(0.0, actual_recent[0] + 60.0 - now))
        existing_penalty_until = 0.0 if multi_key else (float(entry.get("penalty_until") or 0.0) if isinstance(entry, dict) else 0.0)
        penalty_until = max(existing_penalty_until, now + wait) if not multi_key else 0.0
        state[key] = {
            "timestamps": recent[-max(int(rpm or 0), 240):],
            "rpm": int(rpm or 0),
            "updated_at": now,
            "last_wait": wait,
            "last_429_at": now,
        }
        if penalty_until > now:
            state[key]["penalty_until"] = penalty_until
        if isinstance(entry, dict):
            for preserve_key in ("server_rpm", "server_rpm_updated_at", "server_remaining", "server_reset_seconds"):
                if preserve_key in entry:
                    state[key][preserve_key] = entry[preserve_key]
        if inferred_rpm:
            state[key]["server_rpm"] = inferred_rpm
            state[key]["server_rpm_updated_at"] = now
            state[key]["server_rpm_reason"] = "inferred_from_429"
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
    extra = " multi_key_no_global_penalty=1" if multi_key else ""
    router_log("WARN", f"rate_limit_429_backoff provider={provider} model={model or ''} wait={wait:.2f}s{extra}")
    return wait


_RATE_LIMIT_RESET_HEADER_NAMES = (
    "x-ratelimit-reset-requests",
    "x-rate-limit-reset-requests",
    "ratelimit-reset",
    "rate-limit-reset",
    "x-ratelimit-reset",
    "x-rate-limit-reset",
)

# Ceiling covers a full daily-quota reset (e.g. OpenRouter :free RPD resets at
# 00:00 UTC, up to ~24h away) plus slack, so a key that hit its per-day limit
# rests until the quota actually refreshes instead of retrying hourly and burning
# more of the (failure-counted) daily allowance.
API_KEY_COOLDOWN_MAX_SECONDS = 90000.0
API_KEY_COOLDOWN_DEFAULT_SECONDS = 60.0


def _api_key_cooldown_state_key(provider: str, pcfg: dict[str, Any], key: str) -> str:
    # Namespaced by provider+base_url (so the same key rotates independently per
    # endpoint) and hashed -- the state file is plaintext, never store raw secrets.
    digest = hashlib.sha256(str(key).encode("utf-8")).hexdigest()[:12]
    return f"{provider_api_key_rotation_name(provider, pcfg)}:__key__:{digest}"


def api_key_cooldown_reset_seconds(headers: Any) -> float:
    """Seconds to rest a key after a 429, from the response headers.

    Priority: X-RateLimit-Reset (exact reset, possibly ms epoch) -> Retry-After
    (seconds) -> a conservative default. Clamped to a sane ceiling.
    """
    reset = rate_limit_reset_seconds(first_header(headers, list(_RATE_LIMIT_RESET_HEADER_NAMES)))
    if reset is None or reset <= 0:
        reset = parse_retry_after_seconds(first_header(headers, ["Retry-After", "retry-after"]))
    if reset is None or reset <= 0:
        reset = API_KEY_COOLDOWN_DEFAULT_SECONDS
    return max(1.0, min(float(reset), API_KEY_COOLDOWN_MAX_SECONDS))


def register_api_key_cooldown(provider: str, pcfg: dict[str, Any], key: str, headers: Any) -> float:
    """Rest a specific API key until its rate-limit reset. Returns the cooldown seconds."""
    if not meaningful_key(key):
        return 0.0
    reset = api_key_cooldown_reset_seconds(headers)
    state_key = _api_key_cooldown_state_key(provider, pcfg, key)
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
            if not isinstance(state, dict):
                state = {}
        except Exception:
            state = {}
        now = time.time()
        state[state_key] = {"cooldown_until": now + reset, "last_429_at": now}
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
    router_log("WARN", f"api_key_cooldown provider={provider} key_hash={state_key.rsplit(':', 1)[-1]} rest={reset:.0f}s")
    return reset


def api_key_cooldown_until(provider: str, pcfg: dict[str, Any], key: str) -> float:
    """Epoch until which this key is resting (0.0 if not cooling / expired)."""
    if not meaningful_key(key):
        return 0.0
    state_key = _api_key_cooldown_state_key(provider, pcfg, key)
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
        except Exception:
            return 0.0
        entry = state.get(state_key) if isinstance(state, dict) else None
    if not isinstance(entry, dict):
        return 0.0
    try:
        until = float(entry.get("cooldown_until") or 0.0)
    except Exception:
        return 0.0
    return until if until > time.time() else 0.0


def provider_live_api_key_count(provider: str, pcfg: dict[str, Any]) -> int:
    keys = provider_config_api_keys(provider, pcfg)
    if len(keys) <= 1:
        return len(keys)
    now = time.time()
    return sum(1 for key in keys if api_key_cooldown_until(provider, pcfg, key) <= now)


def provider_has_live_api_key(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider_live_api_key_count(provider, pcfg) > 0


def reset_api_key_cooldowns_for_router_start() -> int:
    """Clear per-API-key cooldowns when a fresh router process starts.

    Key cooldowns are runtime retry state. Keeping them across a new Claude Any
    router process can make a restarted session use only the one key that did
    not hit a prior 429. Provider/global RPM state is intentionally preserved.
    """
    with _RATE_LIMIT_LOCK:
        try:
            state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
            if not isinstance(state, dict):
                return 0
        except Exception:
            return 0
        kept = {key: value for key, value in state.items() if ":__key__:" not in str(key)}
        removed = len(state) - len(kept)
        if removed <= 0:
            return 0
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        RATE_LIMIT_STATE_PATH.write_text(json.dumps(kept, ensure_ascii=False) + "\n", encoding="utf-8")
    router_log("INFO", f"api_key_cooldown_reset_on_router_start removed={removed}")
    return removed


def retry_after_exceeds_request_timeout(headers: Any, timeout: float) -> tuple[bool, float | None]:
    retry_after = first_header(headers, ["Retry-After", "retry-after"])
    seconds = parse_retry_after_seconds(retry_after)
    if seconds is None:
        return False, None
    return seconds >= max(1.0, float(timeout) - 1.0), seconds


def apply_router_rate_limit(provider: str, pcfg: dict[str, Any], model: str | None = None) -> tuple[float, int, int | None]:
    rpm = router_rate_limit_effective_rpm(provider, pcfg, model)
    if rpm is None:
        return 0.0, 0, None
    if rpm <= 0:
        used, limit = record_router_rate_usage(provider, pcfg, model, rpm)
        return 0.0, used, limit
    window = 60.0
    base_interval = window / float(rpm)
    capacity = router_rate_limit_capacity(rpm)
    key = router_rate_limit_key(provider, pcfg, model)
    multi_key = provider_api_key_count(provider, pcfg) > 1
    waited = 0.0
    while True:
        with _RATE_LIMIT_LOCK:
            try:
                state = json.loads(RATE_LIMIT_STATE_PATH.read_text(encoding="utf-8")) if RATE_LIMIT_STATE_PATH.exists() else {}
                if not isinstance(state, dict):
                    state = {}
            except Exception:
                state = {}
            now = time.time()
            entry = state.get(key)
            if not isinstance(entry, dict):
                legacy_key = f"{provider}:{model or current_upstream_model_id(provider, pcfg)}"
                entry = state.get(legacy_key)
            if isinstance(entry, dict):
                timestamps = entry.get("timestamps")
                try:
                    penalty_until = 0.0 if multi_key else float(entry.get("penalty_until") or 0.0)
                except Exception:
                    penalty_until = 0.0
            elif isinstance(entry, (int, float)):
                timestamps = [float(entry)]
                penalty_until = 0.0
            else:
                timestamps = []
                penalty_until = 0.0
            recent = router_rate_limit_recent(timestamps, now, window, include_future=True)
            used = len(recent)
            usage_ratio = min(1.0, used / float(capacity))
            wait = 0.0
            if penalty_until > now:
                wait = max(wait, penalty_until - now)
            if used >= capacity and recent:
                wait = max(0.0, recent[0] + window - now)
            elif recent:
                elapsed_since_last = max(0.0, now - recent[-1])
                wait = max(0.0, base_interval - elapsed_since_last)
                if usage_ratio >= 0.70:
                    pressure = (usage_ratio - 0.70) / 0.30
                    target_interval = base_interval * (1.0 + max(0.0, min(1.0, pressure)) * 3.0)
                    wait = max(wait, target_interval - elapsed_since_last)
            CONFIG_DIR.mkdir(parents=True, exist_ok=True)
            if wait <= 0.001:
                recent.append(now)
                new_entry = {"timestamps": recent[-rpm:], "rpm": rpm, "updated_at": now, "last_wait": waited}
                if penalty_until > now:
                    new_entry["penalty_until"] = penalty_until
                state[key] = new_entry
                RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
                return waited, len(recent), rpm
            if used < capacity:
                scheduled = now + wait
                recent.append(scheduled)
                new_entry = {"timestamps": recent[-rpm:], "rpm": rpm, "updated_at": scheduled, "last_wait": wait}
                if penalty_until > now:
                    new_entry["penalty_until"] = penalty_until
                state[key] = new_entry
                RATE_LIMIT_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False) + "\n", encoding="utf-8")
                router_log("INFO", f"rate_limit_soft_wait provider={provider} model={model or ''} rpm={rpm} wait={wait:.2f}s")
                time.sleep(wait)
                return waited + wait, len(recent), rpm
        sleep_for = min(wait, 10.0)
        router_log("INFO", f"rate_limit_wait provider={provider} model={model or ''} rpm={rpm} wait={wait:.2f}s waited={waited:.2f}s")
        time.sleep(sleep_for)
        waited += sleep_for


RATE_LIMIT_NOTICE_PALETTE = (203, 209, 215, 221, 229, 187, 151, 116, 111, 147, 183, 219)


def colorize_status_text(text: str) -> str:
    if os.environ.get("CLAUDE_ANY_RATE_LIMIT_ANSI", "1").lower() in ("0", "false", "no"):
        return text
    parts: list[str] = []
    phase = int(time.monotonic() * 8)
    for i, ch in enumerate(text):
        if ch.isspace():
            parts.append(ch)
            continue
        color = RATE_LIMIT_NOTICE_PALETTE[(phase + i) % len(RATE_LIMIT_NOTICE_PALETTE)]
        parts.append(f"\033[1;38;5;{color}m{ch}\033[0m")
    return "".join(parts)


def rate_limit_notice(waited: float, used: int = 0, rpm: int | None = None, show_status: bool = False) -> str:
    return ""


def is_url_up(url: str) -> bool:
    try:
        http_json(url, timeout=1.5)
        return True
    except Exception:
        return False


def nvidia_upstream_base_url() -> str:
    return "https://integrate.api.nvidia.com/v1"


def nvidia_proxy_base_url() -> str:
    env = read_env_file(NCP_ENV)
    host = env.get("PROXY_HOST") or "127.0.0.1"
    port = env.get("PROXY_PORT") or "8788"
    return f"http://{host}:{port}"


def nvidia_api_key() -> str:
    return (
        read_env_file(NCP_ENV).get("NVIDIA_API_KEY")
        or os.environ.get("NVIDIA_API_KEY")
        or os.environ.get("NV_API_KEY")
        or ""
    ).strip()


def install_ncp_proxy() -> str | None:
    if os.environ.get("CLAUDE_ANY_AUTO_INSTALL_NCP", "1").lower() in ("0", "false", "no"):
        return None
    NCP_LOG.parent.mkdir(parents=True, exist_ok=True)
    with open(NCP_LOG, "ab", buffering=0) as log:
        log.write(b"\n[claude-any] installing nvd-claude-proxy with pip\n")
        proc = subprocess.run(
            [sys.executable, "-m", "pip", "install", "--user", "--upgrade", NCP_PYPI_PACKAGE],
            stdout=log,
            stderr=log,
            timeout=240,
        )
    if proc.returncode != 0:
        return None
    importlib.invalidate_caches()
    return find_executable("ncp")


def ncp_module_available() -> bool:
    return importlib.util.find_spec("nvd_claude_proxy") is not None


def ncp_proxy_executable() -> str | None:
    return find_executable("nvd-claude-proxy") or find_executable("ncp")


def ensure_ncp() -> None:
    cfg = load_config()
    provider = cfg["providers"]["nvidia-hosted"]
    upstream = provider.get("base_url") or nvidia_upstream_base_url()
    env = os.environ.copy()
    env.update(read_env_file(NCP_ENV))
    env["NVIDIA_BASE_URL"] = upstream.rstrip("/")
    env.setdefault("PROXY_HOST", "127.0.0.1")
    env.setdefault("PROXY_PORT", "8788")
    env.setdefault("STORAGE_ENGINE", "sqlite")
    timeout_ms = positive_int(provider.get("request_timeout_ms"))
    if timeout_ms:
        env["REQUEST_TIMEOUT_SECONDS"] = str(max(1, timeout_ms / 1000))
    base = f"http://{env['PROXY_HOST']}:{env['PROXY_PORT']}"
    if is_url_up(f"{base}/v1/models"):
        return
    NCP_LOG.parent.mkdir(parents=True, exist_ok=True)
    ncp_exe = ncp_proxy_executable()
    if not (ncp_exe or ncp_module_available()):
        install_ncp_proxy()
        ncp_exe = ncp_proxy_executable()
    if not (ncp_exe or ncp_module_available()):
        raise RuntimeError("nvd-claude-proxy was not found. Install it with: python -m pip install --user nvd-claude-proxy")
    creationflags = getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
    with open(NCP_LOG, "ab", buffering=0) as log:
        if ncp_exe:
            cmd = [ncp_exe]
            log.write(f"\n[claude-any] starting nvd-claude-proxy executable: {ncp_exe}\n".encode())
        else:
            cmd = [sys.executable, "-m", "nvd_claude_proxy.main"]
            log.write(b"\n[claude-any] starting nvd-claude-proxy module\n")
        subprocess.Popen(
            cmd,
            stdout=log,
            stderr=log,
            env=env,
            cwd=str(NCP_ENV.parent),
            creationflags=creationflags,
        )
    deadline = time.time() + 45
    while time.time() < deadline:
        if is_url_up(f"{base}/v1/models"):
            return
        time.sleep(0.5)
    raise RuntimeError("nvd-claude-proxy did not become ready")


def ncp_model_id_for_nvidia_hosted(model_id: str) -> str:
    if model_id.startswith("claude-") and not model_id.startswith("claude-any-"):
        return model_id
    try:
        data = http_json(join_url(nvidia_proxy_base_url(), "/v1/models"), timeout=3.0)
    except Exception:
        return model_id
    items = data.get("data") if isinstance(data, dict) else None
    if not isinstance(items, list):
        return model_id
    for item in items:
        if not isinstance(item, dict):
            continue
        ncp_id = str(item.get("id") or "").strip()
        nvidia_id = str(item.get("nvidia_id") or "").strip()
        if ncp_id == model_id:
            return ncp_id
        if nvidia_id and nvidia_id == model_id and ncp_id:
            return ncp_id
    return model_id


def key_from_request_headers(headers: Any) -> str:
    """Recover the API key baked into an outgoing request's headers (for cooldown)."""
    try:
        key = headers.get("x-api-key")
        if key:
            return str(key)
        auth = str(headers.get("authorization") or headers.get("Authorization") or "")
    except Exception:
        return ""
    if auth.lower().startswith("bearer "):
        return auth[7:].strip()
    return auth.strip()


def provider_headers(provider: str, pcfg: dict[str, Any], inbound_headers: Any | None = None) -> dict[str, str]:
    headers = with_upstream_user_agent({"content-type": "application/json", "anthropic-version": "2023-06-01"})
    key = select_provider_api_key(provider, pcfg) or str(pcfg.get("api_key") or "") or "not-used"
    if provider == "anthropic":
        if meaningful_key(key):
            headers["x-api-key"] = str(key)
        elif inbound_headers is not None:
            for name in (
                "authorization",
                "x-api-key",
                "anthropic-version",
                "anthropic-beta",
                "anthropic-dangerous-direct-browser-access",
            ):
                value = inbound_headers.get(name)
                if value:
                    headers[name] = value
            if not (headers.get("authorization") or headers.get("x-api-key")):
                raise RuntimeError("Anthropic routed mode did not receive Claude Code OAuth/API auth headers.")
        else:
            raise RuntimeError("Anthropic routed mode needs a configured API key or inbound Claude Code auth headers.")
    elif provider == "openrouter":
        if not meaningful_key(key):
            raise RuntimeError("OpenRouter requires a configured API key.")
        headers["x-api-key"] = key
        headers["Authorization"] = f"Bearer {key}"
    elif provider in ("ollama", "ollama-cloud", "vllm", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "fireworks"):
        headers["x-api-key"] = key
        headers["authorization"] = f"Bearer {key}"
    elif provider == "lm-studio":
        if meaningful_key(key):
            headers["x-api-key"] = str(key)
            headers["authorization"] = f"Bearer {key}"
    elif provider == "nvidia-hosted":
        if meaningful_key(key):
            headers["authorization"] = f"Bearer {key}"
            headers["x-api-key"] = key
    return headers


def get_current_provider(cfg: dict[str, Any]) -> tuple[str, dict[str, Any]]:
    provider = normalize_provider(cfg.get("current_provider", "nvidia-hosted"))
    return provider, cfg["providers"][provider]


def native_anthropic_enabled(provider: str) -> bool:
    return provider == "anthropic"


def anthropic_routed_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "anthropic" and parse_bool(pcfg.get("route_through_router"), default=False)


def direct_native_anthropic_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return native_anthropic_enabled(provider) and not anthropic_routed_enabled(provider, pcfg)


def upstream_model_ids(provider: str, pcfg: dict[str, Any], force_refresh: bool = False) -> list[str]:
    cached = None if force_refresh else read_model_list_cache(provider, pcfg)
    if cached is not None:
        return cached
    if provider == "deepseek":
        ids = unique_model_ids(provider, [
            "deepseek-v4-pro[1m]",
            "deepseek-v4-flash",
            *(pcfg.get("custom_models", []) or []),
            pcfg.get("current_model") or "",
        ])
        sorted_ids = sorted_model_ids(ids)
        write_model_list_cache(provider, pcfg, sorted_ids)
        return sorted_ids
    if provider == "anthropic":
        ids: list[str] = []
        source = ""
        if provider_has_api_key(provider, pcfg):
            ids, source = fetch_anthropic_api_model_ids(pcfg, provider_model_list_headers(provider, pcfg), timeout=6.0)
        if not ids:
            ids = fetch_anthropic_public_model_ids()
            source = "anthropic-docs"
        if not ids:
            return []
        for mid in pcfg.get("custom_models", []) or []:
            mid = normalize_model_id(provider, mid)
            if mid and mid not in ids:
                ids.append(mid)
        cur = normalize_model_id(provider, pcfg.get("current_model") or "")
        if cur and cur not in ids:
            ids.insert(0, cur)
        sorted_ids = unique_model_ids(provider, ids)
        write_model_list_cache(provider, pcfg, sorted_ids)
        write_model_registry(provider, pcfg, sorted_ids, source, {"urls": list(ANTHROPIC_MODEL_DOCS_URLS) if source == "anthropic-docs" else []})
        return sorted_ids
    if provider == "fireworks":
        headers = provider_model_list_headers(provider, pcfg)
        model_info: dict[str, dict[str, Any]] = {}
        source = ""
        try:
            ids, model_info, source = fetch_fireworks_model_ids(pcfg, headers, timeout=8.0)
            fetched = bool(ids)
        except Exception as exc:
            router_log("DEBUG", f"fireworks model API fetch failed: {type(exc).__name__}: {exc}")
            ids = []
            fetched = False
        if not fetched:
            base = provider_upstream_request_base(provider, pcfg)
            try:
                data = http_json(join_url(base, "/v1/models"), headers=headers, timeout=6.0, provider=provider, pcfg=pcfg)
                ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
                model_info.update(model_info_from_response(provider, data))
                fetched = bool(ids)
                source = "fireworks:/v1/models"
            except Exception as exc:
                router_log("DEBUG", f"fireworks inference model list fetch failed: {type(exc).__name__}: {exc}")
        if not fetched:
            ids = unique_model_ids(provider, [
                *(pcfg.get("custom_models", []) or []),
                pcfg.get("current_model") or "",
            ])
            sorted_ids = sorted_model_ids(ids)
            if sorted_ids:
                write_model_list_cache(provider, pcfg, sorted_ids)
            return sorted_ids
        for mid in pcfg.get("custom_models", []) or []:
            mid = normalize_model_id(provider, mid)
            if mid and mid not in ids:
                ids.append(mid)
        cur = normalize_model_id(provider, pcfg.get("current_model") or "")
        if cur and cur not in ids:
            ids.insert(0, cur)
        sorted_ids = sorted_model_ids(unique_model_ids(provider, ids))
        metadata = {
            "model_info": model_info,
            "source": source,
            "account_id": fireworks_account_id(pcfg),
            "model_api_base_url": fireworks_management_base_url(pcfg),
        }
        write_model_list_cache(provider, pcfg, sorted_ids, metadata)
        return sorted_ids
    if provider == "nvidia-hosted":
        base = (pcfg.get("base_url") or nvidia_upstream_base_url()).rstrip("/")
    else:
        base = provider_upstream_request_base(provider, pcfg)
    ids: list[str] = []
    model_info: dict[str, dict[str, Any]] = {}
    fetched = False
    try:
        if provider in ("ollama", "ollama-cloud"):
            try:
                data = http_json(join_url(base, "/api/tags"), headers=provider_model_list_headers(provider, pcfg), timeout=4.0, provider=provider, pcfg=pcfg)
                ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
                model_info.update(model_info_from_response(provider, data))
                fetched = True
            except Exception:
                data = http_json(join_url(base, "/v1/models"), headers=provider_model_list_headers(provider, pcfg), timeout=4.0, provider=provider, pcfg=pcfg)
                ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
                model_info.update(model_info_from_response(provider, data))
                fetched = True
        elif provider == "nvidia-hosted":
            data = http_json(join_url(base, "/v1/models"), headers=nvidia_hosted_list_headers(), timeout=8.0, provider=provider, pcfg=pcfg)
            ids = model_ids_from_response(data)
            model_info.update(model_info_from_response(provider, data))
            fetched = True
        elif provider == "lm-studio":
            headers = provider_model_list_headers(provider, pcfg)
            for path in ("/api/v0/models", "/api/v1/models", "/v1/models", "/models"):
                try:
                    data = http_json(join_url(lm_studio_api_base(pcfg) if path.startswith("/api/") else base, path), headers=headers, timeout=2.0, provider=provider, pcfg=pcfg)
                    ids = [normalize_model_id(provider, mid) for mid in model_ids_from_response(data)]
                    model_info.update(model_info_from_response(provider, data))
                    fetched = True
                    if ids:
                        break
                except Exception:
                    continue
        else:
            headers = provider_model_list_headers(provider, pcfg)
            for path in ("/v1/models", "/models"):
                try:
                    data = http_json(join_url(base, path), headers=headers, timeout=6.0, provider=provider, pcfg=pcfg)
                    ids = model_ids_from_response(data)
                    model_info.update(model_info_from_response(provider, data))
                    fetched = True
                    if ids:
                        break
                except Exception:
                    continue
    except Exception:
        ids = []
    if provider == "ollama-cloud" and not ids:
        ids = ollama_catalog_model_ids(provider)
        fetched = bool(ids)
    if not fetched and provider in OPENCODE_PROVIDER_NAMES:
        ids = unique_model_ids(provider, [
            *(pcfg.get("custom_models", []) or []),
            pcfg.get("current_model") or "",
        ])
        sorted_ids = sorted_model_ids(ids)
        if sorted_ids:
            write_model_list_cache(provider, pcfg, sorted_ids)
        return sorted_ids
    if not fetched:
        return []
    for mid in pcfg.get("custom_models", []) or []:
        mid = normalize_model_id(provider, mid)
        if mid and mid not in ids:
            ids.append(mid)
    cur = normalize_model_id(provider, pcfg.get("current_model") or "")
    if cur and provider != "nvidia-hosted" and cur.startswith(f"claude-any-{provider}-"):
        pass
    elif cur and cur not in ids and not (provider == "nvidia-hosted" and cur.startswith("claude-")):
        ids.insert(0, cur)
    if provider == "nvidia-hosted" and cur and cur not in ids:
        ids.insert(0, cur)
    sorted_ids = unique_model_ids(provider, ids)
    if provider != "anthropic":
        sorted_ids = sorted_model_ids(sorted_ids)
    metadata = {"model_info": model_info} if model_info else None
    write_model_list_cache(provider, pcfg, sorted_ids, metadata)
    return sorted_ids


def model_context_field(item: dict[str, Any]) -> int | None:
    for key in (
        "max_model_len",
        "max_context_length",
        "context_length",
        "contextLength",
        "max_context_tokens",
        "max_position_embeddings",
        "trainingContextLength",
    ):
        value = positive_int(item.get(key))
        if value:
            return value
    details = item.get("details")
    if isinstance(details, dict):
        for key in ("max_model_len", "max_context_length", "context_length", "contextLength", "max_context_tokens", "max_position_embeddings"):
            value = positive_int(details.get(key))
            if value:
                return value
    return None


def ollama_api_base(pcfg: dict[str, Any]) -> str:
    base = provider_upstream_request_base("ollama", pcfg)
    if base.endswith("/api"):
        return base[:-4].rstrip("/")
    return base.rstrip("/")


def ollama_model_id_matches(left: str, right: str) -> bool:
    lhs = (left or "").strip().lower()
    rhs = (right or "").strip().lower()
    if lhs == rhs:
        return True
    if ":" not in lhs:
        lhs = f"{lhs}:latest"
    if ":" not in rhs:
        rhs = f"{rhs}:latest"
    return lhs == rhs


def ollama_runtime_info(pcfg: dict[str, Any], timeout: float = 1.5) -> dict[str, Any] | None:
    base = ollama_api_base(pcfg)
    current = current_upstream_model_id("ollama", pcfg)
    if not base or not current:
        return None
    data = http_json(join_url(base, "/api/ps"), headers=provider_model_list_headers("ollama", pcfg), timeout=timeout)
    items = data.get("models") if isinstance(data, dict) else None
    if not isinstance(items, list):
        return None
    selected = None
    fallback = None
    for item in items:
        if not isinstance(item, dict):
            continue
        if fallback is None:
            fallback = item
        names = (
            str(item.get("name") or ""),
            str(item.get("model") or ""),
            str(item.get("id") or ""),
        )
        if any(ollama_model_id_matches(name, current) for name in names):
            selected = item
            break
    if selected is None and not current:
        selected = fallback
    if not isinstance(selected, dict):
        return None
    details = selected.get("details") if isinstance(selected.get("details"), dict) else {}
    return {
        "requested_model": current,
        "runtime_model": str(selected.get("name") or selected.get("model") or ""),
        "loaded_context_len": positive_int(selected.get("context_length")) or model_context_field(selected),
        "size_vram": positive_int(selected.get("size_vram")),
        "parameter_size": details.get("parameter_size"),
        "quantization_level": details.get("quantization_level"),
        "family": details.get("family"),
        "families": details.get("families"),
    }


def ollama_output_cap_for_context(context_length: int | None) -> int | None:
    context = positive_int(context_length)
    if not context:
        return None
    return max(2048, min(8192, context // 16))


def apply_ollama_runtime_output_guard(provider: str, pcfg: dict[str, Any]) -> list[str]:
    if provider != "ollama":
        return []
    try:
        info = ollama_runtime_info(pcfg)
    except Exception:
        return []
    loaded_context = positive_int((info or {}).get("loaded_context_len"))
    cap = ollama_output_cap_for_context(loaded_context)
    if not cap:
        return []
    opts = pcfg.setdefault("ollama_options", {})
    configured = positive_int(opts.get("num_predict")) or positive_int(pcfg.get("max_output_tokens"))
    if not configured or configured <= cap:
        return []
    opts["num_predict"] = cap
    pcfg["max_output_tokens"] = cap
    model = str((info or {}).get("runtime_model") or pcfg.get("current_model") or "")
    return [
        f"Ollama runtime context {format_context_tokens(loaded_context)} for {model or 'current model'}; output capped to {cap:,} tokens."
    ]


def lm_studio_api_base(pcfg: dict[str, Any]) -> str:
    base = provider_upstream_request_base("lm-studio", pcfg)
    if base.endswith("/v1"):
        return base[:-3].rstrip("/")
    return base


def lm_studio_model_id_matches(left: str, right: str) -> bool:
    return (left or "").strip().lower() == (right or "").strip().lower()


def lm_studio_runtime_info(pcfg: dict[str, Any], timeout: float = 3.0) -> dict[str, Any] | None:
    base = lm_studio_api_base(pcfg)
    if not base:
        return None
    current = current_upstream_model_id("lm-studio", pcfg)

    try:
        data = http_json(join_url(base, "/api/v0/models"), headers=provider_model_list_headers("lm-studio", pcfg), timeout=timeout)
        items = data.get("data") if isinstance(data, dict) else None
        if isinstance(items, list):
            selected: dict[str, Any] | None = None
            fallback: dict[str, Any] | None = None
            for item in items:
                if not isinstance(item, dict):
                    continue
                if fallback is None:
                    fallback = item
                if lm_studio_model_id_matches(str(item.get("id") or ""), current):
                    selected = item
                    break
            selected = selected or (fallback if not current else None)
            if selected:
                return {
                    "models_url": join_url(base, "/api/v0/models"),
                    "requested_model": current,
                    "runtime_model": str(selected.get("id") or ""),
                    "max_model_len": positive_int(selected.get("max_context_length")) or model_context_field(selected),
                    "loaded_context_len": positive_int(selected.get("loaded_context_length")),
                    "state": selected.get("state"),
                    "capabilities": selected.get("capabilities"),
                    "type": selected.get("type"),
                    "root": selected.get("arch"),
                }
    except Exception:
        pass

    try:
        data = http_json(join_url(base, "/api/v1/models"), headers=provider_model_list_headers("lm-studio", pcfg), timeout=timeout)
        items = data.get("models") if isinstance(data, dict) else None
        if isinstance(items, list):
            selected = None
            fallback = None
            for item in items:
                if not isinstance(item, dict):
                    continue
                if fallback is None:
                    fallback = item
                if lm_studio_model_id_matches(str(item.get("key") or ""), current):
                    selected = item
                    break
            selected = selected or (fallback if not current else None)
            if selected:
                loaded_context = None
                instance_ids: list[str] = []
                instances = selected.get("loaded_instances")
                if isinstance(instances, list) and instances:
                    for instance in instances:
                        if isinstance(instance, dict) and instance.get("id"):
                            instance_ids.append(str(instance["id"]))
                    config = instances[0].get("config") if isinstance(instances[0], dict) else None
                    if isinstance(config, dict):
                        loaded_context = positive_int(config.get("context_length"))
                return {
                    "models_url": join_url(base, "/api/v1/models"),
                    "requested_model": current,
                    "runtime_model": str(selected.get("key") or ""),
                    "max_model_len": positive_int(selected.get("max_context_length")) or model_context_field(selected),
                    "loaded_context_len": loaded_context,
                    "state": "loaded" if loaded_context else "not-loaded",
                    "instance_ids": instance_ids,
                    "capabilities": selected.get("capabilities"),
                    "type": selected.get("type"),
                    "root": selected.get("architecture"),
                }
    except Exception:
        pass
    return None


def lm_studio_v1_model_info(pcfg: dict[str, Any], timeout: float = 3.0) -> dict[str, Any] | None:
    base = lm_studio_api_base(pcfg)
    current = current_upstream_model_id("lm-studio", pcfg)
    if not base or not current:
        return None
    data = http_json(join_url(base, "/api/v1/models"), headers=provider_model_list_headers("lm-studio", pcfg), timeout=timeout)
    items = data.get("models") if isinstance(data, dict) else None
    if not isinstance(items, list):
        return None
    for item in items:
        if isinstance(item, dict) and lm_studio_model_id_matches(str(item.get("key") or ""), current):
            return item
    return None


def lm_studio_loaded_instance_ids(pcfg: dict[str, Any], timeout: float = 3.0) -> list[str]:
    try:
        item = lm_studio_v1_model_info(pcfg, timeout=timeout)
    except Exception:
        return []
    instances = item.get("loaded_instances") if isinstance(item, dict) else None
    if not isinstance(instances, list):
        return []
    ids: list[str] = []
    for instance in instances:
        if isinstance(instance, dict) and instance.get("id"):
            ids.append(str(instance["id"]))
    return ids


def lm_studio_target_context(pcfg: dict[str, Any], info: dict[str, Any] | None = None) -> int | None:
    target = positive_int(pcfg.get("context_window"))
    if not target:
        preset_id = recommended_preset_id("lm-studio", pcfg)
        target = required_context_for_preset(preset_id, "lm-studio")
    target = target or LM_STUDIO_DEFAULT_CLAUDE_CODE_CONTEXT
    max_len = positive_int((info or {}).get("max_model_len")) or model_context_hint_from_model_id(str(pcfg.get("current_model") or ""))
    if max_len:
        target = min(target, max_len)
    return target


def lm_studio_load_timeout_seconds(pcfg: dict[str, Any]) -> float:
    configured = positive_int(pcfg.get("request_timeout_ms"))
    if configured:
        return max(60.0, min(600.0, configured / 1000.0))
    return 300.0


def lm_studio_load_model(pcfg: dict[str, Any], context_length: int, timeout: float | None = None) -> dict[str, Any]:
    base = lm_studio_api_base(pcfg)
    model = current_upstream_model_id("lm-studio", pcfg)
    if not base or not model:
        raise RuntimeError("LM Studio model or base URL is not configured")
    body = {
        "model": model,
        "context_length": context_length,
        "flash_attention": True,
        "echo_load_config": True,
    }
    return post_json(
        join_url(base, "/api/v1/models/load"),
        body,
        headers=provider_model_list_headers("lm-studio", pcfg),
        timeout=timeout or lm_studio_load_timeout_seconds(pcfg),
    )


def lm_studio_unload_loaded_instances(pcfg: dict[str, Any], timeout: float = 20.0) -> list[str]:
    base = lm_studio_api_base(pcfg)
    if not base:
        return []
    unloaded: list[str] = []
    for instance_id in lm_studio_loaded_instance_ids(pcfg, timeout=3.0):
        try:
            post_json(
                join_url(base, "/api/v1/models/unload"),
                {"instance_id": instance_id},
                headers=provider_model_list_headers("lm-studio", pcfg),
                timeout=timeout,
            )
            unloaded.append(instance_id)
        except Exception:
            continue
    return unloaded


def lm_studio_load_response_context(response: dict[str, Any], fallback: int) -> int:
    config = response.get("load_config") if isinstance(response, dict) else None
    if isinstance(config, dict):
        value = positive_int(config.get("context_length"))
        if value:
            return value
    return fallback


def ensure_lm_studio_model_loaded_for_context(pcfg: dict[str, Any], timeout: float = 3.0) -> list[str]:
    info = lm_studio_runtime_info(pcfg, timeout=timeout)
    target = lm_studio_target_context(pcfg, info)
    if not target:
        return []
    messages: list[str] = []
    loaded = positive_int((info or {}).get("loaded_context_len"))
    state = str((info or {}).get("state") or "")
    max_len = positive_int((info or {}).get("max_model_len"))
    if max_len:
        messages.append(f"LM Studio model max context: {max_len:,} tokens.")
    if loaded:
        messages.append(f"LM Studio loaded context: {loaded:,} tokens.")
    if loaded and state == "loaded" and loaded >= target:
        pcfg["native_compat"] = True
        if not positive_int(pcfg.get("context_window")):
            pcfg["context_window"] = min(loaded, target)
        return messages
    if max_len and max_len < LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
        pcfg["native_compat"] = False
        pcfg["context_window"] = max_len
        messages.append(
            "LM Studio selected model cannot provide enough context for Claude Code "
            f"({max_len:,} < {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,})."
        )
        return messages

    action = "reloading" if loaded else "loading"
    messages.append(f"LM Studio auto-{action} selected model with {target:,} context tokens.")
    try:
        response = lm_studio_load_model(pcfg, target)
    except Exception:
        if loaded:
            lm_studio_unload_loaded_instances(pcfg)
            response = lm_studio_load_model(pcfg, target)
        else:
            raise
    actual = lm_studio_load_response_context(response, target)
    pcfg["context_window"] = actual
    if actual >= LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
        pcfg["native_compat"] = True
        messages.append(f"LM Studio loaded selected model with {actual:,} context tokens.")
    else:
        pcfg["native_compat"] = False
        current_output = positive_int(pcfg.get("max_output_tokens")) or 4096
        pcfg["max_output_tokens"] = min(current_output, max(512, actual // 4))
        messages.append(
            "LM Studio loaded the model, but the applied context is still too small for Claude Code "
            f"({actual:,} < {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,})."
        )
    return messages


def upstream_model_runtime_info(provider: str, pcfg: dict[str, Any], timeout: float = 3.0) -> dict[str, Any] | None:
    if provider not in ("vllm", "lm-studio", "self-hosted-nim"):
        return None
    if provider == "lm-studio":
        info = lm_studio_runtime_info(pcfg, timeout=timeout)
        if info:
            return info
    base = provider_upstream_request_base(provider, pcfg)
    if not base:
        return None
    current = current_upstream_model_id(provider, pcfg)
    try:
        data = http_json(join_url(base, "/v1/models"), headers=provider_model_list_headers(provider, pcfg), timeout=timeout)
    except Exception:
        return None
    items = data.get("data") if isinstance(data, dict) else None
    if not isinstance(items, list):
        return None
    fallback_item: dict[str, Any] | None = None
    for item in items:
        if not isinstance(item, dict):
            continue
        if fallback_item is None:
            fallback_item = item
        if str(item.get("id") or "") == current:
            selected = item
            break
    else:
        selected = fallback_item
    if not selected:
        return None
    return {
        "models_url": join_url(base, "/v1/models"),
        "requested_model": current,
        "runtime_model": str(selected.get("id") or ""),
        "max_model_len": model_context_field(selected),
        "owned_by": selected.get("owned_by"),
        "root": selected.get("root"),
    }


def upstream_model_context_limit(provider: str, pcfg: dict[str, Any], timeout: float = 3.0) -> int | None:
    info = upstream_model_runtime_info(provider, pcfg, timeout=timeout)
    if not info:
        return None
    return positive_int(info.get("max_model_len"))


def model_map_for(provider: str, pcfg: dict[str, Any], fetch: bool = True) -> dict[str, str]:
    ids = upstream_model_ids(provider, pcfg) if fetch else cached_or_configured_model_ids(provider, pcfg)
    return {alias_for(provider, mid): mid for mid in ids}


def current_alias(cfg: dict[str, Any]) -> str:
    provider, pcfg = get_current_provider(cfg)
    cur = normalize_model_id(provider, pcfg.get("current_model") or "model")
    if provider == "nvidia-hosted" and cur.startswith("claude-"):
        return cur
    if cur.startswith(f"claude-any-{provider}-"):
        return cur
    return alias_for(provider, cur)


def ollama_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "ollama" and bool(pcfg.get("native_compat", True))


def vllm_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "vllm" and bool(pcfg.get("native_compat", True))


def nim_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "self-hosted-nim" and bool(pcfg.get("native_compat", True))


def lm_studio_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "lm-studio" and bool(pcfg.get("native_compat", True))


def nvidia_hosted_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    # NVIDIA's self-hosted NIM server exposes Anthropic-compatible /v1/messages.
    # The hosted API Catalog endpoint at integrate.api.nvidia.com currently
    # exposes OpenAI-compatible /v1/chat/completions instead, so keep it on the
    # claude-any router conversion path even if an old config has native=true.
    return False


def deepseek_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "deepseek" and bool(pcfg.get("native_compat", True))


def opencode_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return (
        provider in OPENCODE_PROVIDER_NAMES
        and bool(pcfg.get("native_compat", True))
        and opencode_endpoint_kind(provider, str(pcfg.get("current_model") or ""), pcfg) == "anthropic-messages"
    )


def fireworks_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider == "fireworks" and bool(pcfg.get("native_compat", True))


def provider_native_compat_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return (
        vllm_native_compat_enabled(provider, pcfg)
        or lm_studio_native_compat_enabled(provider, pcfg)
        or nim_native_compat_enabled(provider, pcfg)
        or nvidia_hosted_native_compat_enabled(provider, pcfg)
        or deepseek_native_compat_enabled(provider, pcfg)
        or opencode_native_compat_enabled(provider, pcfg)
        or fireworks_native_compat_enabled(provider, pcfg)
    )


def current_upstream_model_id(provider: str, pcfg: dict[str, Any]) -> str:
    cur = normalize_model_id(provider, pcfg.get("current_model") or "model")
    if cur.startswith(f"claude-any-{provider}-"):
        try:
            return unslug_provider_alias(provider, cur, model_map_for(provider, pcfg)) or cur
        except Exception:
            return cur
    return cur


def launch_model_id(provider: str, pcfg: dict[str, Any]) -> str:
    cur = normalize_model_id(provider, pcfg.get("current_model") or "model")
    if provider != "ollama":
        return alias_for(provider, cur) if not (provider == "nvidia-hosted" and cur.startswith("claude-")) else cur
    if not cur.startswith("claude-any-ollama-"):
        return cur
    try:
        return unslug_provider_alias("ollama", cur, model_map_for("ollama", pcfg)) or cur
    except Exception:
        return cur


def resolve_requested_model(provider: str, pcfg: dict[str, Any], requested: str | None) -> str:
    requested = strip_claude_context_suffix(requested)
    fallback = normalize_model_id(provider, pcfg.get("current_model") or "model")
    if provider == "nvidia-hosted":
        if requested and requested.startswith("claude-nvidia-"):
            return requested
        if fallback:
            return fallback
    mmap = model_map_for(provider, pcfg)
    if requested:
        resolved = unslug_provider_alias(provider, requested, mmap)
        if resolved:
            return resolved
        # If Claude Code sends a bare model id from the gateway /v1/models list
        # during /model switching, route it to that exact upstream model. This is
        # especially important for third-party providers that expose Claude-named
        # models: unknown/stale Claude ids must still fall back, but models the
        # router advertised are valid for this provider.
        if requested in set(mmap.values()):
            return requested
        # Built-in Claude aliases and stale aliases from another provider route to current provider's model.
        if requested.startswith("claude-") or requested.startswith("claude-any-"):
            return fallback
        return normalize_model_id(provider, requested)
    return fallback


def resolve_tool_model_references(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    tools = body.get("tools")
    if not isinstance(tools, list) or not tools:
        return body
    changed = False
    resolved_count = 0
    next_tools: list[Any] = []
    for tool in tools:
        if not isinstance(tool, dict):
            next_tools.append(tool)
            continue
        model = tool.get("model")
        if not isinstance(model, str) or not model.strip():
            next_tools.append(tool)
            continue
        resolved = resolve_requested_model(provider, pcfg, model)
        if resolved and resolved != model:
            next_tool = dict(tool)
            next_tool["model"] = resolved
            next_tools.append(next_tool)
            changed = True
            resolved_count += 1
        else:
            next_tools.append(tool)
    if not changed:
        return body
    out = dict(body)
    out["tools"] = next_tools
    router_log("INFO", f"resolved upstream tool model references for {provider}: {resolved_count}")
    return out


def list_model_objects(provider: str, pcfg: dict[str, Any]) -> list[dict[str, Any]]:
    return [model_object(provider, mid, pcfg) for mid in upstream_model_ids(provider, pcfg)]


def list_model_objects_for_request(provider: str, pcfg: dict[str, Any], inbound_headers: Any | None = None) -> list[dict[str, Any]]:
    if anthropic_routed_enabled(provider, pcfg):
        try:
            headers = provider_headers(provider, pcfg, inbound_headers)
            ids, source = fetch_anthropic_api_model_ids(pcfg, headers, timeout=6.0)
            if ids:
                for mid in pcfg.get("custom_models", []) or []:
                    mid = normalize_model_id(provider, mid)
                    if mid and mid not in ids:
                        ids.append(mid)
                cur = normalize_model_id(provider, pcfg.get("current_model") or "")
                if cur and cur not in ids:
                    ids.append(cur)
                router_log("DEBUG", f"anthropic routed model discovery source={source} count={len(ids)}")
                return [model_object(provider, mid, pcfg) for mid in sorted_model_ids(unique_model_ids(provider, ids))]
        except Exception as exc:
            router_log("DEBUG", f"anthropic routed model discovery fallback error={type(exc).__name__}: {exc}")
    return list_model_objects(provider, pcfg)


def provider_upstream_request_base(provider: str, pcfg: dict[str, Any]) -> str:
    return pcfg.get("base_url", "").rstrip("/")


def native_anthropic_base_url(provider: str, pcfg: dict[str, Any]) -> str:
    base = pcfg.get("base_url", "http://127.0.0.1:8000").rstrip("/")
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "openrouter", "fireworks") and base.endswith("/v1"):
        return base[:-3].rstrip("/")
    return base


OPENAI_COMPATIBLE_ROUTER_PROVIDERS = ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "openrouter")
AUTO_DETECT_NATIVE_COMPAT_PROVIDERS = ("vllm", "lm-studio", "self-hosted-nim")


def provider_openai_router_enabled(provider: str, pcfg: dict[str, Any]) -> bool:
    return provider in OPENAI_COMPATIBLE_ROUTER_PROVIDERS and not provider_native_compat_enabled(provider, pcfg)


def endpoint_route_exists(url: str, headers: dict[str, str], timeout: float = 1.5) -> bool | None:
    req = urllib.request.Request(url, data=b"{}", headers=with_upstream_user_agent(headers), method="POST")
    try:
        with urllib.request.urlopen(req, timeout=timeout):
            return True
    except urllib.error.HTTPError as exc:
        try:
            exc.read()
        except Exception:
            pass
        if exc.code == 404:
            return False
        if exc.code in (400, 401, 403, 405, 422):
            return True
        return None
    except Exception:
        return None


def auto_detect_native_compat_for_base_url(provider: str, pcfg: dict[str, Any]) -> tuple[bool | None, str]:
    if provider not in AUTO_DETECT_NATIVE_COMPAT_PROVIDERS:
        return None, ""
    base = provider_upstream_request_base(provider, pcfg)
    if not base:
        return None, "missing base URL"
    headers = provider_model_list_headers(provider, pcfg)
    anthropic_route = endpoint_route_exists(join_url(native_anthropic_base_url(provider, pcfg), "/v1/messages"), headers)
    openai_route = endpoint_route_exists(join_url(base, "/v1/chat/completions"), headers)
    if anthropic_route is True:
        return True, "Anthropic Messages route detected"
    if openai_route is True and anthropic_route is False:
        return False, "OpenAI chat completions route detected"
    if openai_route is True:
        return None, "OpenAI route detected, Anthropic route inconclusive; keeping Anthropic default"
    return None, "endpoint family inconclusive; keeping Anthropic default"


def write_json(handler: BaseHTTPRequestHandler, obj: Any, status: int = 200) -> None:
    body = json.dumps(obj).encode("utf-8")
    handler.send_response(status)
    handler.send_header("content-type", "application/json")
    handler.send_header("content-length", str(len(body)))
    handler.end_headers()
    handler.wfile.write(body)


def _handler_response_status(handler: BaseHTTPRequestHandler) -> int | None:
    status = getattr(handler, "_claude_any_response_status", None)
    try:
        return int(status)
    except Exception:
        return None


def write_empty_response(handler: BaseHTTPRequestHandler, status: int = 202) -> None:
    handler.send_response(status)
    handler.send_header("content-length", "0")
    handler.end_headers()


def write_accepted_response(handler: BaseHTTPRequestHandler) -> None:
    body = b"Accepted"
    handler.send_response(202)
    handler.send_header("content-type", "text/plain")
    handler.send_header("content-length", str(len(body)))
    handler.end_headers()
    handler.wfile.write(body)


def reject_external_router_request(handler: BaseHTTPRequestHandler, cfg: dict[str, Any] | None = None) -> bool:
    if router_request_allowed(handler, cfg):
        return False
    write_json(
        handler,
        {
            "type": "error",
            "error": {
                "type": "forbidden",
                "message": "claude-any router external debug access is off.",
            },
        },
        403,
    )
    return True


def write_router_activity(event: str, provider: str, model: str | None = None, **fields: Any) -> None:
    try:
        level = "error" if event == "error" else ("warn" if event in {"retry", "wait"} else "info")
        EVENT_BUS.publish(
            level=level,
            category=f"router.{event}",
            message=f"{event} {provider} {model or ''}".strip(),
            provider=provider,
            model=model,
            data=fields,
        )
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        data = {
            "updated_at": time.time(),
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "event": event,
            "provider": provider,
            "model": model or "",
        }
        data.update(fields)
        tmp = ROUTER_ACTIVITY_PATH.with_name(f"{ROUTER_ACTIVITY_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(data, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
        tmp.replace(ROUTER_ACTIVITY_PATH)
    except Exception:
        pass


def context_limit_for_status(provider: str, pcfg: dict[str, Any]) -> int | None:
    if provider in ("ollama", "ollama-cloud"):
        return ollama_context_limit_for_budget(pcfg)
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim"):
        return openai_context_limit_for_budget(provider, pcfg)
    return positive_int(pcfg.get("context_window")) or positive_int(pcfg.get("max_model_len"))


def write_context_usage(provider: str, pcfg: dict[str, Any], body: dict[str, Any], source: str) -> None:
    try:
        tokens = estimate_tokens(body)
        limit = context_limit_for_status(provider, pcfg)
        percent = round((tokens / limit) * 100.0, 1) if limit else None
        EVENT_BUS.publish(
            level="debug",
            category="context.usage",
            message=f"context usage {tokens}/{limit or '?'} tokens",
            provider=provider,
            model=str(body.get("model") or pcfg.get("current_model") or ""),
            data={
                "source": source,
                "tokens": tokens,
                "context_limit": limit,
                "percent": percent,
                "messages": len(body.get("messages") or []),
                "tools": len(body.get("tools") or []),
            },
        )
        data = {
            "updated_at": time.time(),
            "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "provider": provider,
            "model": str(body.get("model") or pcfg.get("current_model") or ""),
            "source": source,
            "tokens": tokens,
            "context_limit": limit,
            "percent": percent,
            "messages": len(body.get("messages") or []),
            "tools": len(body.get("tools") or []),
        }
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        tmp = CONTEXT_USAGE_PATH.with_name(f"{CONTEXT_USAGE_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(data, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
        tmp.replace(CONTEXT_USAGE_PATH)
    except Exception:
        pass


def write_text_response(handler: BaseHTTPRequestHandler, text: str, status: int = 200, content_type: str = "text/plain; charset=utf-8") -> None:
    body = text.encode("utf-8")
    handler.send_response(status)
    handler.send_header("content-type", content_type)
    handler.send_header("content-length", str(len(body)))
    handler.end_headers()
    handler.wfile.write(body)


def read_json_file(path: Path) -> dict[str, Any]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return data if isinstance(data, dict) else {}


def llm_config_payload(messages: list[str] | None = None) -> dict[str, Any]:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    lang = cfg.get("language", "en")
    rows, values = llm_option_panel_rows(provider, pcfg, lang)
    option_rows = [
        {"label": row, "key": key, "value": llm_option_prompt_default(provider, pcfg, key)}
        for row, key in zip(rows, values)
        if key not in ("back", "__info__", "preset", "context_setup", "timeout_profile")
    ]
    preset_rows, preset_values = llm_preset_panel_rows(provider, pcfg, lang)
    context_rows, context_values = context_setup_panel_rows(provider, pcfg, lang)
    timeout_rows, timeout_values = timeout_profile_panel_rows(pcfg, lang)
    return {
        "ok": True,
        "messages": messages or [],
        "provider": provider,
        "provider_label": PROVIDER_LABELS.get(provider, provider),
        "model": str(pcfg.get("current_model") or ""),
        "alias": current_alias(cfg),
        "advisor_model": str(pcfg.get("advisor_model") or ""),
        "preset": applied_preset_id(provider, pcfg),
        "context": context_setting_status(provider, pcfg),
        "timeout": timeout_profile_status(pcfg, lang),
        "options": option_rows,
        "presets": [
            {"label": row, "value": value}
            for row, value in zip(preset_rows, preset_values)
            if value not in ("back", "__info__")
        ],
        "contexts": [
            {"label": row, "value": value}
            for row, value in zip(context_rows, context_values)
            if value not in ("back", "__info__")
        ],
        "timeouts": [
            {"label": row, "value": value}
            for row, value in zip(timeout_rows, timeout_values)
            if value not in ("back", "__info__")
        ],
    }


def apply_timeout_profile_config(provider: str, profile_id: str) -> list[str]:
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    lines = apply_timeout_profile_to_provider(pcfg, profile_id, cfg.get("language", "en"))
    save_config(cfg)
    clear_model_cache()
    return lines


def handle_llm_config_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
    if path != "/ca/config/llm":
        return False
    write_json(handler, llm_config_payload())
    return True


def handle_llm_config_post(handler: BaseHTTPRequestHandler, path: str, body: dict[str, Any]) -> bool:
    if path != "/ca/config/llm":
        return False
    cfg = load_config()
    provider, _pcfg = get_current_provider(cfg)
    action = str(body.get("action") or "option").strip()
    value = str(body.get("value") or "").strip()
    key = str(body.get("key") or "").strip()
    messages: list[str]
    try:
        if action == "model":
            messages = set_model_config(value)
        elif action == "advisor_model":
            messages = set_advisor_model_config(value)
        elif action == "preset":
            messages = apply_llm_preset_config(provider, value)
        elif action == "context_setup":
            messages = apply_context_setup_config(provider, value)
        elif action == "timeout_profile":
            messages = apply_timeout_profile_config(provider, value)
        elif action == "option":
            if not key:
                raise SystemExit("Missing option key")
            messages = set_llm_option_config(provider, key, value)
        else:
            raise SystemExit(f"Unknown LLM config action: {action}")
        EVENT_BUS.publish(level="info", category="config.llm", message=f"updated {action} {key or value}", provider=provider, data={"action": action, "key": key, "value": value})
        write_json(handler, llm_config_payload(messages))
    except SystemExit as exc:
        write_json(handler, {"ok": False, "error": str(exc), "messages": [str(exc)]}, 400)
    except Exception as exc:
        router_log("ERROR", f"llm config update failed: {type(exc).__name__}: {exc}")
        write_json(handler, {"ok": False, "error": f"{type(exc).__name__}: {exc}", "messages": [f"{type(exc).__name__}: {exc}"]}, 500)
    return True


def render_router_home_html(cfg: dict[str, Any], provider: str, pcfg: dict[str, Any]) -> str:
    model = current_alias(cfg)
    upstream = read_json_file(ROUTER_ACTIVITY_PATH)
    context = read_json_file(CONTEXT_USAGE_PATH)
    used, rpm_limit = router_rate_limit_usage(provider, pcfg)
    rpm_text = "off"
    if bool(pcfg.get("rate_limit_status", False)):
        rpm_text = f"{used}/min unmanaged" if rpm_limit == 0 else (f"{used}/{rpm_limit}" if rpm_limit else "unknown")
    timeout_ms = positive_int(pcfg.get("request_timeout_ms")) or DEFAULT_REQUEST_TIMEOUT_MS
    idle_ms = positive_int(pcfg.get("stream_idle_timeout_ms")) or timeout_profile_idle_ms(timeout_ms)
    context_limit = context_limit_for_status(provider, pcfg)
    context_tokens = positive_int(context.get("tokens"))
    context_pct = context.get("percent")
    if isinstance(context_pct, (int, float)):
        context_text = f"{context_tokens or 0:,}/{context_limit or 0:,} tok ({context_pct}%)"
    else:
        context_text = f"{context_tokens or 0:,}/{context_limit or 0:,} tok" if context_limit else "unknown"
    upstream_bits = [
        str(upstream.get("event") or "idle"),
        str(upstream.get("provider") or provider),
        str(upstream.get("model") or pcfg.get("current_model") or ""),
    ]
    upstream_text = " · ".join(bit for bit in upstream_bits if bit)
    links = [
        ("Events UI", "/ca/events", "Live router event stream with filters"),
        ("Session web chat", "/ca/web/chat", "Bridge messages into the active Claude Code session"),
        ("Recent events JSON", "/ca/events/recent", "Latest structured event records"),
        ("Events SSE", "/ca/events/stream", "Server-sent events stream"),
        ("Chat health", "/ca/chat/health", "Agent chat component status"),
        ("Chat messages", "/ca/chat/messages", "Stored agent chat messages"),
        ("Channel bridge", "/ca/channel/health", "External channel bridge API"),
        ("Channel messages", "/ca/channel/messages", "Messages posted through channel bridge"),
        ("Plan artifacts", "/ca/plan/artifacts", "Plan mode artifacts served by router"),
        ("Models", "/v1/models", "Claude-compatible model list"),
        ("Health", "/health", "Machine-readable health JSON"),
    ]
    link_html = "\n".join(
        f'<a class="link" href="{html_lib.escape(href)}"><strong>{html_lib.escape(label)}</strong><span>{html_lib.escape(desc)}</span></a>'
        for label, href, desc in links
    )
    return f"""<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Claude Any Router</title>
  <style>
    :root {{ color-scheme: dark; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; }}
    body {{ margin: 0; background: #090b0f; color: #e8edf4; }}
    header {{ padding: 22px 24px 16px; border-bottom: 1px solid #253044; background: #101722; }}
    h1 {{ margin: 0 0 6px; font-size: 24px; letter-spacing: 0; }}
    .sub {{ color: #a8b3c5; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }}
    .topnav {{ position: sticky; top: 0; z-index: 10; display: flex; gap: 6px; padding: 10px 24px; background: #0b111a; border-bottom: 1px solid #253044; overflow-x: auto; }}
    .tab {{ min-width: 96px; min-height: 34px; border-radius: 6px; border: 1px solid #334155; background: #101722; color: #cbd5e1; cursor: pointer; }}
    .tab:hover {{ border-color: #60a5fa; color: #eff6ff; }}
    .tab.active {{ background: #1d4ed8; border-color: #60a5fa; color: white; }}
    main {{ max-width: 1180px; margin: 0 auto; padding: 18px; }}
    .view {{ display: none; }}
    .view.active {{ display: block; }}
    .view h2 {{ margin: 0 0 12px; font-size: 20px; }}
    .grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }}
    .card, .link, .events {{ background: #0d131d; border: 1px solid #253044; border-radius: 8px; padding: 12px; }}
    .label {{ color: #93a4ba; font-size: 12px; text-transform: uppercase; }}
    .value {{ margin-top: 5px; font-size: 15px; word-break: break-word; }}
    .links {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; margin-top: 18px; }}
    a.link {{ display: block; color: #dbeafe; text-decoration: none; }}
    a.link:hover {{ border-color: #60a5fa; }}
    a.link span {{ display: block; margin-top: 4px; color: #93a4ba; font-size: 13px; }}
    .events {{ margin-top: 18px; }}
    .settings {{ background: #0d131d; border: 1px solid #253044; border-radius: 8px; padding: 12px; }}
    .settings h2, .events h2 {{ margin: 0 0 10px; font-size: 18px; }}
    .settings-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; }}
    .control {{ display: grid; gap: 6px; }}
    .control label {{ color: #93a4ba; font-size: 12px; text-transform: uppercase; }}
    input, select, button {{ min-height: 34px; border-radius: 6px; border: 1px solid #334155; background: #080d14; color: #e8edf4; padding: 6px 8px; }}
    button {{ cursor: pointer; background: #12304f; border-color: #2563eb; }}
    button:hover {{ background: #17406a; }}
    .option-row {{ display: grid; grid-template-columns: minmax(240px, 1fr) minmax(160px, 260px) auto; gap: 8px; align-items: center; padding: 8px 0; border-top: 1px solid #1f2937; }}
    .option-row .name {{ color: #dbeafe; word-break: break-word; }}
    .messages {{ margin-top: 10px; color: #c4b5fd; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre-wrap; }}
    .event {{ padding: 8px 0; border-top: 1px solid #1f2937; }}
    .event:first-child {{ border-top: 0; }}
    .meta {{ color: #93a4ba; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; }}
    .preview {{ margin-top: 4px; color: #cbd5e1; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; white-space: pre-wrap; word-break: break-word; }}
    code {{ color: #bfdbfe; }}
  </style>
</head>
<body>
  <header>
    <h1>Claude Any Router</h1>
    <div class="sub">v{html_lib.escape(VERSION)} · {html_lib.escape(provider)} · {html_lib.escape(model)}</div>
  </header>
  <nav class="topnav" aria-label="Router sections">
    <button class="tab active" data-view="overview">Overview</button>
    <button class="tab" data-view="settings">LLM Settings</button>
    <button class="tab" data-view="events">Events</button>
    <button class="tab" data-view="endpoints">Endpoints</button>
  </nav>
  <main>
    <section id="view-overview" class="view active">
      <h2>Overview</h2>
      <div class="grid">
      <div class="card"><div class="label">Provider</div><div class="value">{html_lib.escape(provider)}</div></div>
      <div class="card"><div class="label">Model</div><div class="value">{html_lib.escape(model)}</div></div>
      <div class="card"><div class="label">Context</div><div class="value">{html_lib.escape(context_text)}</div></div>
      <div class="card"><div class="label">Timeout</div><div class="value">{timeout_ms:,} ms · idle {idle_ms:,} ms</div></div>
      <div class="card"><div class="label">RPM</div><div class="value">{html_lib.escape(rpm_text)}</div></div>
      <div class="card"><div class="label">Upstream</div><div class="value">{html_lib.escape(upstream_text)}</div></div>
      </div>
    </section>
    <section id="view-settings" class="view">
      <h2>LLM Settings</h2>
      <div class="settings">
      <div class="settings-grid">
        <div class="control"><label>Model</label><input id="modelInput"><button id="modelApply">Apply model</button></div>
        <div class="control"><label>Advisor Model</label><input id="advisorInput" placeholder="off or model id"><button id="advisorApply">Apply advisor</button></div>
        <div class="control"><label>Preset</label><select id="presetSelect"></select><button id="presetApply">Apply preset</button></div>
        <div class="control"><label>Context Setup</label><select id="contextSelect"></select><button id="contextApply">Apply context</button></div>
        <div class="control"><label>Timeout Profile</label><select id="timeoutSelect"></select><button id="timeoutApply">Apply timeout</button></div>
      </div>
      <div id="optionRows"></div>
      <div id="settingsMessages" class="messages"></div>
      </div>
    </section>
    <section id="view-events" class="view events">
      <h2>Recent Events</h2>
      <div id="events"><div class="meta">Loading /ca/events/recent...</div></div>
    </section>
    <section id="view-endpoints" class="view">
      <h2>Endpoints</h2>
      <div class="links">{link_html}</div>
    </section>
  </main>
  <script>
    const tabs = Array.from(document.querySelectorAll('.tab'));
    const views = Array.from(document.querySelectorAll('.view'));
    function showView(name) {{
      tabs.forEach(tab => tab.classList.toggle('active', tab.dataset.view === name));
      views.forEach(view => view.classList.toggle('active', view.id === 'view-' + name));
      if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
    }}
    tabs.forEach(tab => tab.addEventListener('click', () => showView(tab.dataset.view)));
    const initialView = (location.hash || '#overview').slice(1);
    if (tabs.some(tab => tab.dataset.view === initialView)) showView(initialView);
    const el = document.getElementById('events');
    const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}}[c]));
    const modelInput = document.getElementById('modelInput');
    const advisorInput = document.getElementById('advisorInput');
    const presetSelect = document.getElementById('presetSelect');
    const contextSelect = document.getElementById('contextSelect');
    const timeoutSelect = document.getElementById('timeoutSelect');
    const optionRows = document.getElementById('optionRows');
    const settingsMessages = document.getElementById('settingsMessages');
    function fillSelect(select, rows, current) {{
      select.innerHTML = (rows || []).map(item => `<option value="${{esc(item.value)}}">${{esc(item.label)}}</option>`).join('');
      if (current) select.value = current;
    }}
    async function loadSettings(messages = []) {{
      const res = await fetch('/ca/config/llm');
      const data = await res.json();
      modelInput.value = data.model || '';
      advisorInput.value = data.advisor_model || '';
      fillSelect(presetSelect, data.presets, data.preset);
      fillSelect(contextSelect, data.contexts);
      fillSelect(timeoutSelect, data.timeouts);
      optionRows.innerHTML = (data.options || []).map(item => `<div class="option-row"><div class="name">${{esc(item.label)}}</div><input data-key="${{esc(item.key)}}" value="${{esc(item.value)}}"><button data-key="${{esc(item.key)}}">Apply</button></div>`).join('');
      settingsMessages.textContent = (messages.length ? messages : data.messages || []).join('\\n');
    }}
    async function postSettings(payload) {{
      settingsMessages.textContent = 'Saving...';
      const res = await fetch('/ca/config/llm', {{method:'POST', headers:{{'content-type':'application/json'}}, body: JSON.stringify(payload)}});
      const data = await res.json();
      if (!res.ok || !data.ok) {{
        settingsMessages.textContent = (data.messages || [data.error || 'Update failed']).join('\\n');
        return;
      }}
      await loadSettings(data.messages || []);
    }}
    document.getElementById('modelApply').onclick = () => postSettings({{action:'model', value:modelInput.value}});
    document.getElementById('advisorApply').onclick = () => postSettings({{action:'advisor_model', value:advisorInput.value}});
    document.getElementById('presetApply').onclick = () => postSettings({{action:'preset', value:presetSelect.value}});
    document.getElementById('contextApply').onclick = () => postSettings({{action:'context_setup', value:contextSelect.value}});
    document.getElementById('timeoutApply').onclick = () => postSettings({{action:'timeout_profile', value:timeoutSelect.value}});
    optionRows.addEventListener('click', ev => {{
      const button = ev.target.closest('button[data-key]');
      if (!button) return;
      const input = optionRows.querySelector(`input[data-key="${{CSS.escape(button.dataset.key)}}"]`);
      postSettings({{action:'option', key:button.dataset.key, value:input ? input.value : ''}});
    }});
    loadSettings();
    fetch('/ca/events/recent?limit=20').then(r => r.json()).then(j => {{
      const events = j.events || [];
      el.innerHTML = events.length ? events.reverse().map(e => {{
        const preview = e.data && e.data.message_preview ? `<div class="preview">${{esc(e.data.message_preview)}}${{e.data.message_preview_truncated ? '…' : ''}}</div>` : '';
        return `<div class="event"><div class="meta">#${{e.id}} ${{esc(e.time)}} · ${{esc(e.level)}} · ${{esc(e.category)}} · ${{esc(e.provider)}} ${{esc(e.model)}}</div><div>${{esc(e.message)}}</div>${{preview}}</div>`;
      }}).join('') : '<div class="meta">No events yet.</div>';
    }}).catch(err => {{ el.innerHTML = '<div class="meta">Could not load events: ' + esc(err) + '</div>'; }});
  </script>
</body>
</html>"""


def render_web_chat_html(cfg: dict[str, Any], provider: str, pcfg: dict[str, Any]) -> str:
    model = current_alias(cfg)
    timeout_ms = positive_int(pcfg.get("request_timeout_ms")) or DEFAULT_REQUEST_TIMEOUT_MS
    api_status = api_key_status_line(provider, pcfg)
    mode = provider_mode_label(provider, pcfg)
    escaped_model = html_lib.escape(model)
    escaped_provider = html_lib.escape(provider)
    escaped_mode = html_lib.escape(mode)
    escaped_api_status = html_lib.escape(api_status)
    return f"""<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Claude Any Web Chat</title>
  <style>
    :root {{
      color-scheme: dark;
      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
      --bg: #0a0d12;
      --panel: #111827;
      --panel-2: #162033;
      --line: #283548;
      --text: #eef2f8;
      --muted: #a9b4c6;
      --user: #174c6b;
      --assistant: #243447;
      --accent: #2f9e8f;
      --danger: #fca5a5;
      --ok: #86efac;
    }}
    * {{ box-sizing: border-box; }}
    body {{ margin: 0; min-height: 100vh; background: var(--bg); color: var(--text); }}
    .shell {{ display: grid; grid-template-columns: 280px minmax(0, 1fr); min-height: 100vh; }}
    aside {{ border-right: 1px solid var(--line); background: #0e1521; padding: 18px; }}
    .brand {{ font-size: 19px; font-weight: 700; letter-spacing: 0; margin: 0 0 12px; }}
    .status-card {{ border: 1px solid var(--line); border-radius: 8px; background: var(--panel); padding: 12px; display: grid; gap: 10px; }}
    .meta-label {{ color: var(--muted); font-size: 11px; text-transform: uppercase; }}
    .meta-value {{ margin-top: 3px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; word-break: break-word; }}
    .nav {{ margin-top: 14px; display: grid; gap: 8px; }}
    .nav a, .ghost {{
      display: flex; align-items: center; justify-content: center;
      min-height: 36px; border-radius: 6px; border: 1px solid var(--line);
      background: #0b111b; color: var(--text); text-decoration: none; cursor: pointer;
    }}
    .nav a:hover, .ghost:hover {{ border-color: var(--accent); }}
    main {{ display: grid; grid-template-rows: auto minmax(0, 1fr) auto; min-width: 0; }}
    header {{ min-height: 66px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--line); background: #0d1420; }}
    h1 {{ margin: 0; font-size: 18px; letter-spacing: 0; }}
    .sub {{ color: var(--muted); font-size: 12px; margin-top: 4px; }}
    .pill {{ border: 1px solid var(--line); border-radius: 999px; padding: 5px 9px; color: var(--muted); font-size: 12px; white-space: nowrap; }}
    #transcript {{ overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 12px; }}
    .row {{ display: flex; width: 100%; }}
    .row.user {{ justify-content: flex-end; }}
    .bubble {{
      max-width: min(760px, 86%);
      border: 1px solid var(--line);
      border-radius: 8px;
      padding: 10px 12px;
      line-height: 1.45;
      white-space: normal;
      word-break: break-word;
      box-shadow: 0 1px 0 rgba(255,255,255,.03) inset;
    }}
    .row.user .bubble {{ background: var(--user); border-color: #276a8d; }}
    .row.assistant .bubble {{ background: var(--assistant); }}
    .row.system .bubble {{ background: #191f2b; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; white-space: pre-wrap; }}
    .markdown > :first-child {{ margin-top: 0; }}
    .markdown > :last-child {{ margin-bottom: 0; }}
    .markdown p {{ margin: 0 0 10px; }}
    .markdown h1, .markdown h2, .markdown h3, .markdown h4 {{ margin: 12px 0 8px; line-height: 1.2; }}
    .markdown h1 {{ font-size: 1.35rem; }}
    .markdown h2 {{ font-size: 1.2rem; }}
    .markdown h3 {{ font-size: 1.08rem; }}
    .markdown ul, .markdown ol {{ margin: 0 0 10px 20px; padding: 0; }}
    .markdown li {{ margin: 3px 0; }}
    .markdown blockquote {{ margin: 0 0 10px; padding-left: 12px; border-left: 3px solid #4b6585; color: var(--muted); }}
    .markdown pre {{ margin: 0 0 10px; padding: 10px; overflow-x: auto; border: 1px solid #33445b; border-radius: 6px; background: #0b111b; white-space: pre; }}
    .markdown code {{ font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: .92em; }}
    .markdown :not(pre) > code {{ padding: 1px 4px; border-radius: 4px; background: rgba(191, 219, 254, .12); }}
    .markdown a {{ color: #8bd7ff; text-decoration: underline; text-underline-offset: 2px; }}
    .markdown table {{ width: 100%; border-collapse: collapse; margin: 0 0 10px; display: block; overflow-x: auto; }}
    .markdown th, .markdown td {{ border: 1px solid #3a4b63; padding: 6px 8px; text-align: left; vertical-align: top; }}
    .markdown th {{ background: rgba(255,255,255,.06); font-weight: 700; }}
    .markdown hr {{ border: 0; border-top: 1px solid var(--line); margin: 12px 0; }}
    .composer {{ border-top: 1px solid var(--line); padding: 12px 18px; background: #0d1420; }}
    .composer-inner {{ display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; align-items: end; }}
    textarea {{
      width: 100%; min-height: 54px; max-height: 180px; resize: vertical;
      border: 1px solid var(--line); border-radius: 8px; background: #080d14; color: var(--text);
      padding: 10px 12px; line-height: 1.4; font: inherit;
    }}
    button.primary {{
      width: 86px; min-height: 54px; border: 1px solid #37b7a4; border-radius: 8px;
      background: #127668; color: white; font-weight: 700; cursor: pointer;
    }}
    button.primary:disabled {{ opacity: .55; cursor: not-allowed; }}
    .composer-actions {{ display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }}
    .attach-button {{
      min-height: 34px; border: 1px solid var(--line); border-radius: 6px;
      background: #0b111b; color: var(--text); padding: 0 12px; cursor: pointer;
    }}
    .attach-button:hover {{ border-color: var(--accent); }}
    .attach-button:disabled {{ opacity: .55; cursor: not-allowed; }}
    #fileInput {{ display: none; }}
    .attachment-tray {{ display: flex; gap: 7px; flex-wrap: wrap; min-height: 0; }}
    .attachment-chip {{
      display: inline-flex; align-items: center; gap: 7px; max-width: min(360px, 100%);
      border: 1px solid #33445b; border-radius: 999px; background: #121b2a;
      padding: 5px 8px; color: var(--muted); font-size: 12px;
    }}
    .attachment-chip span {{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
    .attachment-chip button {{
      width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
      border: 0; border-radius: 999px; background: #243447; color: var(--text); cursor: pointer;
    }}
    .drop-active textarea {{ border-color: var(--accent); box-shadow: 0 0 0 2px rgba(47, 158, 143, .18); }}
    .hint {{ margin-top: 7px; color: var(--muted); font-size: 12px; }}
    .error {{ color: var(--danger); }}
    .ok {{ color: var(--ok); }}
    code {{ color: #bfdbfe; }}
    @media (max-width: 820px) {{
      .shell {{ grid-template-columns: 1fr; }}
      aside {{ display: none; }}
      .bubble {{ max-width: 94%; }}
      header {{ align-items: flex-start; flex-direction: column; }}
      .pill {{ white-space: normal; }}
    }}
  </style>
</head>
<body>
  <div class="shell">
    <aside>
      <div class="brand">Claude Any</div>
      <div class="status-card">
        <div><div class="meta-label">Provider</div><div class="meta-value">{escaped_provider}</div></div>
        <div><div class="meta-label">Mode</div><div class="meta-value">{escaped_mode}</div></div>
        <div><div class="meta-label">Model</div><div class="meta-value">{escaped_model}</div></div>
        <div><div class="meta-label">API</div><div class="meta-value">{escaped_api_status}</div></div>
        <div><div class="meta-label">Timeout</div><div class="meta-value">{timeout_ms:,} ms</div></div>
        <div><div class="meta-label">Bridge</div><div class="meta-value">active session channel</div></div>
      </div>
      <div class="nav">
        <a href="/">Router Home</a>
        <a href="/ca/events">Events</a>
        <a href="/health">Health JSON</a>
        <button class="ghost" id="shareButton" type="button">Copy Chat Link</button>
        <button class="ghost" id="clearButton" type="button">Clear Chat</button>
      </div>
    </aside>
    <main>
      <header>
        <div>
          <h1>Session Web Chat</h1>
          <div class="sub">Send messages into the active Claude Code session through the Claude Any channel bridge and stream replies from the same channel.</div>
        </div>
        <div class="pill" id="statePill">ready</div>
      </header>
      <section id="transcript" aria-live="polite"></section>
      <form class="composer" id="composer">
        <div class="composer-inner">
          <textarea id="prompt" placeholder="Type a message..." autocomplete="off"></textarea>
          <button class="primary" id="sendButton" type="submit">Send</button>
        </div>
        <div class="composer-actions">
          <button class="attach-button" id="attachButton" type="button">Attach files</button>
          <input id="fileInput" type="file" multiple>
          <div class="attachment-tray" id="attachmentTray" aria-live="polite"></div>
        </div>
        <div class="hint">Enter sends. Shift+Enter inserts a new line. The active Claude Code session handles the message, so its configured tools and MCP servers remain available. If replies stay queued, restart Claude Any so the session wake bridge wraps the terminal.</div>
      </form>
    </main>
  </div>
  <script>
    const MODEL = {json.dumps(model)};
    const transcript = document.getElementById('transcript');
    const composer = document.getElementById('composer');
    const prompt = document.getElementById('prompt');
    const sendButton = document.getElementById('sendButton');
    const attachButton = document.getElementById('attachButton');
    const fileInput = document.getElementById('fileInput');
    const attachmentTray = document.getElementById('attachmentTray');
    const shareButton = document.getElementById('shareButton');
    const clearButton = document.getElementById('clearButton');
    const statePill = document.getElementById('statePill');
    const SESSION_KEY = 'claude-any-web-chat-session';
    const LAST_ID_KEY = 'claude-any-web-chat-last-id';
    const HISTORY_PAGE_SIZE = 80;
    const renderedIds = new Set();
    let oldestId = 0;
    let historyLoading = false;
    let historyExhausted = false;
    function cleanSessionId(value) {{
      return String(value || '').replace(/[^a-zA-Z0-9_.:-]/g, '').slice(0, 128);
    }}
    const urlParams = new URLSearchParams(location.search);
    const urlSessionId = cleanSessionId(urlParams.get('session') || urlParams.get('s') || '');
    const storedSessionId = cleanSessionId(localStorage.getItem(SESSION_KEY) || '');
    const sessionId = urlSessionId || storedSessionId || (crypto.randomUUID ? crypto.randomUUID() : String(Date.now()) + '-' + Math.random().toString(16).slice(2));
    localStorage.setItem(SESSION_KEY, sessionId);
    if (!urlSessionId) {{
      urlParams.set('session', sessionId);
      const nextUrl = location.pathname + '?' + urlParams.toString() + location.hash;
      history.replaceState(null, '', nextUrl);
    }}
    const channel = 'web-chat-' + sessionId;
    const scopedLastIdKey = LAST_ID_KEY + ':' + sessionId;
    let lastId = Number(localStorage.getItem(scopedLastIdKey) || '0') || 0;
    let eventSource = null;
    let selectedFiles = [];
    function setState(text, cls = '') {{
      statePill.textContent = text;
      statePill.className = 'pill ' + cls;
    }}
    function escapeHtml(value) {{
      return String(value ?? '').replace(/[&<>"']/g, ch => ({{
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;'
      }}[ch]));
    }}
    function safeHref(value) {{
      const href = String(value || '').trim();
      if (/^(https?:|mailto:)/i.test(href)) return escapeHtml(href);
      return '#';
    }}
    function renderInlineMarkdown(value) {{
      const codeBlocks = [];
      const linkBlocks = [];
      let raw = String(value ?? '').replace(/`([^`\\n]+)`/g, (_match, code) => {{
        const token = '\\u0000CODE' + codeBlocks.length + '\\u0000';
        codeBlocks.push('<code>' + escapeHtml(code) + '</code>');
        return token;
      }});
      raw = raw.replace(/\\[([^\\]]+)\\]\\(([^)\\s]+)\\)/g, (_match, label, href) => {{
        const token = '\\u0000LINK' + linkBlocks.length + '\\u0000';
        linkBlocks.push('<a href="' + safeHref(href) + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(label) + '</a>');
        return token;
      }});
      let html = escapeHtml(raw);
      html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
      html = html.replace(/(^|[\\s(])\\*([^*\\n]+)\\*/g, '$1<em>$2</em>');
      html = html.replace(/~~([^~]+)~~/g, '<del>$1</del>');
      linkBlocks.forEach((link, index) => {{
        html = html.replace('\\u0000LINK' + index + '\\u0000', link);
      }});
      codeBlocks.forEach((code, index) => {{
        html = html.replace('\\u0000CODE' + index + '\\u0000', code);
      }});
      return html;
    }}
    function splitMarkdownTableRow(line) {{
      let row = String(line || '').trim();
      if (row.startsWith('|')) row = row.slice(1);
      if (row.endsWith('|')) row = row.slice(0, -1);
      return row.split('|').map(cell => cell.trim());
    }}
    function isMarkdownDelimiterCell(cell) {{
      const compact = String(cell || '').replace(/\\s+/g, '');
      const core = compact.replace(/^:/, '').replace(/:$/, '');
      return core.length >= 3 && /^-+$/.test(core);
    }}
    function isMarkdownTableDelimiter(line) {{
      const cells = splitMarkdownTableRow(line);
      return cells.length > 1 && cells.every(isMarkdownDelimiterCell);
    }}
    function isMarkdownTableStart(lines, index) {{
      return index + 1 < lines.length
        && String(lines[index] || '').includes('|')
        && splitMarkdownTableRow(lines[index]).length > 1
        && isMarkdownTableDelimiter(lines[index + 1]);
    }}
    function isMarkdownBlockStart(lines, index) {{
      const line = String(lines[index] || '');
      const trimmed = line.trim();
      if (!trimmed) return true;
      return trimmed.startsWith('```')
        || isMarkdownTableStart(lines, index)
        || /^(####|###|##|#)\\s+/.test(trimmed)
        || /^([-*+]\\s+|\\d+[.)]\\s+|>\\s?)/.test(trimmed)
        || /^(---+|\\*\\*\\*+|___+)$/.test(trimmed);
    }}
    function renderMarkdownTable(lines, startIndex) {{
      const headers = splitMarkdownTableRow(lines[startIndex]);
      const rows = [];
      let index = startIndex + 2;
      while (index < lines.length && String(lines[index] || '').trim() && String(lines[index] || '').includes('|')) {{
        rows.push(splitMarkdownTableRow(lines[index]));
        index += 1;
      }}
      const head = '<thead><tr>' + headers.map(cell => '<th>' + renderInlineMarkdown(cell) + '</th>').join('') + '</tr></thead>';
      const body = '<tbody>' + rows.map(row => {{
        const cells = headers.map((_header, cellIndex) => '<td>' + renderInlineMarkdown(row[cellIndex] || '') + '</td>').join('');
        return '<tr>' + cells + '</tr>';
      }}).join('') + '</tbody>';
      return {{ html: '<table>' + head + body + '</table>', nextIndex: index }};
    }}
    function renderMarkdown(text) {{
      const lines = String(text ?? '').replace(/\\r\\n?/g, '\\n').split('\\n');
      const blocks = [];
      let index = 0;
      while (index < lines.length) {{
        const line = lines[index];
        const trimmed = String(line || '').trim();
        if (!trimmed) {{
          index += 1;
          continue;
        }}
        if (trimmed.startsWith('```')) {{
          const code = [];
          index += 1;
          while (index < lines.length && !String(lines[index] || '').trim().startsWith('```')) {{
            code.push(lines[index]);
            index += 1;
          }}
          if (index < lines.length) index += 1;
          blocks.push('<pre><code>' + escapeHtml(code.join('\\n')) + '</code></pre>');
          continue;
        }}
        if (isMarkdownTableStart(lines, index)) {{
          const table = renderMarkdownTable(lines, index);
          blocks.push(table.html);
          index = table.nextIndex;
          continue;
        }}
        const heading = trimmed.match(/^(####|###|##|#)\\s+(.+)$/);
        if (heading) {{
          const level = Math.min(4, heading[1].length);
          blocks.push('<h' + level + '>' + renderInlineMarkdown(heading[2]) + '</h' + level + '>');
          index += 1;
          continue;
        }}
        if (/^(---+|\\*\\*\\*+|___+)$/.test(trimmed)) {{
          blocks.push('<hr>');
          index += 1;
          continue;
        }}
        if (/^[-*+]\\s+/.test(trimmed)) {{
          const items = [];
          while (index < lines.length && /^[-*+]\\s+/.test(String(lines[index] || '').trim())) {{
            items.push(String(lines[index] || '').trim().replace(/^[-*+]\\s+/, ''));
            index += 1;
          }}
          blocks.push('<ul>' + items.map(item => '<li>' + renderInlineMarkdown(item) + '</li>').join('') + '</ul>');
          continue;
        }}
        if (/^\\d+[.)]\\s+/.test(trimmed)) {{
          const items = [];
          while (index < lines.length && /^\\d+[.)]\\s+/.test(String(lines[index] || '').trim())) {{
            items.push(String(lines[index] || '').trim().replace(/^\\d+[.)]\\s+/, ''));
            index += 1;
          }}
          blocks.push('<ol>' + items.map(item => '<li>' + renderInlineMarkdown(item) + '</li>').join('') + '</ol>');
          continue;
        }}
        if (/^>\\s?/.test(trimmed)) {{
          const quotes = [];
          while (index < lines.length && /^>\\s?/.test(String(lines[index] || '').trim())) {{
            quotes.push(String(lines[index] || '').trim().replace(/^>\\s?/, ''));
            index += 1;
          }}
          blocks.push('<blockquote>' + renderInlineMarkdown(quotes.join('\\n')) + '</blockquote>');
          continue;
        }}
        const paragraph = [trimmed];
        index += 1;
        while (index < lines.length && !isMarkdownBlockStart(lines, index)) {{
          paragraph.push(String(lines[index] || '').trim());
          index += 1;
        }}
        blocks.push('<p>' + renderInlineMarkdown(paragraph.join(' ')) + '</p>');
      }}
      return blocks.join('');
    }}
    function addBubble(role, text, mode = 'append', id = null) {{
      if (id !== null && id !== undefined) {{
        const key = String(id);
        if (renderedIds.has(key)) return null;
        renderedIds.add(key);
      }}
      const row = document.createElement('div');
      row.className = 'row ' + role;
      const bubble = document.createElement('div');
      bubble.className = 'bubble';
      if (role === 'system') {{
        bubble.textContent = text;
      }} else {{
        bubble.classList.add('markdown');
        bubble.innerHTML = renderMarkdown(text);
      }}
      row.appendChild(bubble);
      if (mode === 'prepend') {{
        transcript.insertBefore(row, transcript.firstChild);
      }} else {{
        transcript.appendChild(row);
        transcript.scrollTop = transcript.scrollHeight;
      }}
      return bubble;
    }}
    function rememberLastId(id) {{
      const numeric = Number(id || 0) || 0;
      if (numeric > lastId) {{
        lastId = numeric;
        localStorage.setItem(scopedLastIdKey, String(lastId));
      }}
    }}
    function roleForMessage(message) {{
      return message.sender_id === 'web-user' ? 'user' : 'assistant';
    }}
    function renderIncomingMessage(message, mode = 'append') {{
      if (mode !== 'prepend') rememberLastId(message.id);
      const text = message.message || '';
      if (!text.trim()) return;
      addBubble(roleForMessage(message), text, mode, message.id);
      if (mode !== 'prepend' && message.sender_id !== 'web-user') setState('reply received', 'ok');
    }}
    function formatBytes(bytes) {{
      const value = Number(bytes || 0);
      if (value < 1024) return value + ' B';
      if (value < 1024 * 1024) return (value / 1024).toFixed(1).replace(/\\.0$/, '') + ' KB';
      return (value / (1024 * 1024)).toFixed(1).replace(/\\.0$/, '') + ' MB';
    }}
    function renderAttachmentTray() {{
      attachmentTray.innerHTML = '';
      selectedFiles.forEach((file, index) => {{
        const chip = document.createElement('div');
        chip.className = 'attachment-chip';
        const label = document.createElement('span');
        label.textContent = file.name + ' (' + formatBytes(file.size) + ')';
        const remove = document.createElement('button');
        remove.type = 'button';
        remove.setAttribute('aria-label', 'Remove ' + file.name);
        remove.textContent = 'x';
        remove.addEventListener('click', () => {{
          selectedFiles.splice(index, 1);
          renderAttachmentTray();
        }});
        chip.appendChild(label);
        chip.appendChild(remove);
        attachmentTray.appendChild(chip);
      }});
    }}
    function addSelectedFiles(fileList) {{
      const incoming = Array.from(fileList || []);
      if (!incoming.length) return;
      selectedFiles = selectedFiles.concat(incoming);
      renderAttachmentTray();
      setState(selectedFiles.length + ' file(s) ready', 'ok');
    }}
    function fileToBase64(file) {{
      return new Promise((resolve, reject) => {{
        const reader = new FileReader();
        reader.onload = () => {{
          const dataUrl = String(reader.result || '');
          const comma = dataUrl.indexOf(',');
          resolve(comma >= 0 ? dataUrl.slice(comma + 1) : dataUrl);
        }};
        reader.onerror = () => reject(reader.error || new Error('Could not read file'));
        reader.readAsDataURL(file);
      }});
    }}
    async function uploadAttachment(file) {{
      const content = await fileToBase64(file);
      const response = await fetch('/ca/channel/files', {{
        method: 'POST',
        headers: {{'content-type': 'application/json', 'accept': 'application/json'}},
        body: JSON.stringify({{
          channel,
          sender_id: 'web-user',
          recipients: ['all'],
          thread_id: sessionId,
          announce: false,
          name: file.name,
          content_type: file.type || 'application/octet-stream',
          encoding: 'base64',
          content
        }})
      }});
      const text = await response.text();
      let json = {{}};
      try {{ json = text ? JSON.parse(text) : {{}}; }} catch {{}}
      if (!response.ok || !json.ok) {{
        throw new Error(json.error || text || `Upload failed with HTTP ${{response.status}}`);
      }}
      return {{
        name: json.name,
        original_name: json.original_name || file.name,
        url: json.url,
        path: json.path,
        bytes: json.bytes,
        content_type: json.content_type || file.type || 'application/octet-stream'
      }};
    }}
    async function uploadAttachments(files) {{
      const uploads = [];
      for (const file of files) {{
        setState('uploading ' + file.name);
        uploads.push(await uploadAttachment(file));
      }}
      return uploads;
    }}
    function attachmentSummary(uploads) {{
      if (!uploads.length) return '';
      const lines = uploads.map(file => {{
        const label = file.original_name || file.name || 'file';
        const size = formatBytes(file.bytes);
        const type = file.content_type || 'application/octet-stream';
        const url = file.url || file.path || '';
        return '- [' + label + '](' + url + ') (' + size + ', ' + type + ') - router URL: ' + url;
      }});
      return 'Attached files:\\n' + lines.join('\\n');
    }}
    function buildOutboundText(text, uploads) {{
      const trimmed = String(text || '').trim();
      const summary = attachmentSummary(uploads);
      if (trimmed && summary) return trimmed + '\\n\\n' + summary;
      return trimmed || summary;
    }}
    function updateHistoryBounds(messages) {{
      if (!Array.isArray(messages) || messages.length === 0) return;
      const ids = messages.map(message => Number(message.id || 0)).filter(id => id > 0);
      if (!ids.length) return;
      const minId = Math.min(...ids);
      const maxId = Math.max(...ids);
      oldestId = oldestId ? Math.min(oldestId, minId) : minId;
      rememberLastId(maxId);
    }}
    async function fetchMessagePage(params) {{
      const query = new URLSearchParams({{
        channel,
        recipient: 'web',
        limit: String(HISTORY_PAGE_SIZE),
        ...params
      }});
      const response = await fetch('/ca/channel/messages?' + query.toString(), {{headers: {{'accept': 'application/json'}}}});
      if (!response.ok) throw new Error(await response.text() || `HTTP ${{response.status}}`);
      return await response.json();
    }}
    async function loadInitialHistory() {{
      try {{
        const json = await fetchMessagePage({{latest: '1'}});
        const messages = Array.isArray(json.messages) ? json.messages : [];
        messages.forEach(message => renderIncomingMessage(message, 'append'));
        updateHistoryBounds(messages);
        historyExhausted = messages.length < HISTORY_PAGE_SIZE;
      }} catch (err) {{
        addBubble('system', 'Could not load chat history: ' + String(err && err.message ? err.message : err));
      }}
    }}
    async function loadOlderHistory() {{
      if (historyLoading || historyExhausted || !oldestId) return;
      historyLoading = true;
      const previousHeight = transcript.scrollHeight;
      try {{
        const json = await fetchMessagePage({{before: String(oldestId)}});
        const messages = Array.isArray(json.messages) ? json.messages : [];
        if (!messages.length) {{
          historyExhausted = true;
          return;
        }}
        for (let i = messages.length - 1; i >= 0; i -= 1) {{
          renderIncomingMessage(messages[i], 'prepend');
        }}
        updateHistoryBounds(messages);
        historyExhausted = messages.length < HISTORY_PAGE_SIZE;
        transcript.scrollTop = transcript.scrollHeight - previousHeight;
      }} catch (err) {{
        addBubble('system', 'Could not load older history: ' + String(err && err.message ? err.message : err));
      }} finally {{
        historyLoading = false;
      }}
    }}
    function startChannelStream() {{
      if (eventSource) eventSource.close();
      const url = `/ca/channel/stream?channel=${{encodeURIComponent(channel)}}&recipient=web&after=${{lastId}}&timeout=3600`;
      eventSource = new EventSource(url);
      eventSource.onopen = () => setState('listening', 'ok');
      eventSource.onmessage = ev => {{
        try {{
          const message = JSON.parse(ev.data);
          renderIncomingMessage(message);
        }} catch {{}}
      }};
      eventSource.onerror = () => {{
        if (eventSource) eventSource.close();
        setState('reconnecting');
        setTimeout(startChannelStream, 1200);
      }};
    }}
    async function sendMessage(text, files = []) {{
      setState('queued');
      sendButton.disabled = true;
      attachButton.disabled = true;
      try {{
        const uploads = await uploadAttachments(files);
        const outboundText = buildOutboundText(text, uploads);
        addBubble('user', outboundText);
        const response = await fetch('/ca/channel/messages', {{
          method: 'POST',
          headers: {{'content-type': 'application/json', 'accept': 'application/json'}},
          body: JSON.stringify({{
            channel,
            sender_id: 'web-user',
            recipients: ['all'],
            delivery: ['llm', 'native'],
            thread_id: sessionId,
            kind: 'web_chat',
            message: outboundText,
            meta: {{
              source: 'claude-any-web-chat',
              web_chat_session: sessionId,
              reply_channel: channel,
              reply_recipient: 'web',
              reply_instruction: 'Use the claude-any-router send_message tool to answer this browser chat on the same channel/thread_id with recipients web and delivery web. Use send_file when returning a file attachment to this browser chat.',
              attachments: uploads
            }}
          }})
        }});
        if (!response.ok) {{
          const fallback = await response.text();
          throw new Error(fallback || `HTTP ${{response.status}}`);
        }}
        const json = await response.json();
        if (json.message) rememberLastId(json.message.id);
        addBubble('system', 'Message queued for the active Claude Code session. Waiting for a channel reply. If this never changes, restart Claude Any so the session wake bridge is active.');
        setState('waiting for session');
      }} catch (err) {{
        const bubble = addBubble('assistant', String(err && err.message ? err.message : err));
        bubble.classList.add('error');
        setState('error', 'error');
      }} finally {{
        sendButton.disabled = false;
        attachButton.disabled = false;
        prompt.focus();
      }}
    }}
    composer.addEventListener('submit', ev => {{
      ev.preventDefault();
      const text = prompt.value.trim();
      const files = selectedFiles.slice();
      if (!text && !files.length) return;
      prompt.value = '';
      selectedFiles = [];
      renderAttachmentTray();
      sendMessage(text, files);
    }});
    prompt.addEventListener('keydown', ev => {{
      if (ev.key === 'Enter' && !ev.shiftKey) {{
        ev.preventDefault();
        composer.requestSubmit();
      }}
    }});
    attachButton.addEventListener('click', () => fileInput.click());
    fileInput.addEventListener('change', () => {{
      addSelectedFiles(fileInput.files);
      fileInput.value = '';
    }});
    composer.addEventListener('dragover', ev => {{
      if (!ev.dataTransfer || !ev.dataTransfer.files || !ev.dataTransfer.files.length) return;
      ev.preventDefault();
      composer.classList.add('drop-active');
    }});
    composer.addEventListener('dragleave', () => composer.classList.remove('drop-active'));
    composer.addEventListener('drop', ev => {{
      if (!ev.dataTransfer || !ev.dataTransfer.files || !ev.dataTransfer.files.length) return;
      ev.preventDefault();
      composer.classList.remove('drop-active');
      addSelectedFiles(ev.dataTransfer.files);
    }});
    clearButton.addEventListener('click', () => {{
      transcript.innerHTML = '';
      renderedIds.clear();
      oldestId = 0;
      historyExhausted = false;
      selectedFiles = [];
      renderAttachmentTray();
      addBubble('system', `Chat cleared. This browser sends to active Claude Code session channel ${{channel}}.`);
      startChannelStream();
    }});
    shareButton.addEventListener('click', async () => {{
      const url = new URL(location.href);
      url.searchParams.set('session', sessionId);
      try {{
        await navigator.clipboard.writeText(url.toString());
        setState('link copied', 'ok');
      }} catch {{
        prompt.value = url.toString();
        prompt.focus();
        prompt.select();
        setState('copy manually');
      }}
    }});
    transcript.addEventListener('scroll', () => {{
      if (transcript.scrollTop < 48) loadOlderHistory();
    }});
    addBubble('system', `Connected to active session bridge for ${{MODEL}}. Messages are queued on channel ${{channel}} and replies stream back from /ca/channel/stream.`);
    loadInitialHistory().finally(startChannelStream);
    prompt.focus();
  </script>
</body>
</html>"""


def handle_web_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
    if path not in ("/ca/web/chat", "/ca/web/chat/"):
        return False
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    write_text_response(handler, render_web_chat_html(cfg, provider, pcfg), content_type="text/html; charset=utf-8")
    return True


def parse_json_body(raw: bytes) -> dict[str, Any]:
    try:
        value = json.loads(raw.decode("utf-8") if raw else "{}")
    except Exception:
        return {}
    return value if isinstance(value, dict) else {}


def query_int(params: dict[str, list[str]], name: str, default: int) -> int:
    values = params.get(name) or []
    try:
        return int(values[0])
    except Exception:
        return default


def handle_events_get(handler: BaseHTTPRequestHandler, path: str, query: dict[str, list[str]]) -> bool:
    if path == "/ca/events":
        write_text_response(handler, render_events_html(), content_type="text/html; charset=utf-8")
        return True
    if path == "/ca/events/recent":
        write_json(
            handler,
            {
                "ok": True,
                "events": EVENT_BUS.recent(
                    limit=query_int(query, "limit", 200),
                    min_id=query_int(query, "after", 0),
                    level=(query.get("level") or [None])[0],
                    category=(query.get("category") or [None])[0],
                ),
            },
        )
        return True
    if path == "/ca/events/stream":
        last_id = query_int(query, "after", 0)
        handler.send_response(200)
        handler.send_header("content-type", "text/event-stream")
        handler.send_header("cache-control", "no-cache")
        handler.send_header("connection", "close")
        handler.end_headers()
        try:
            for event in EVENT_BUS.recent(limit=200, min_id=last_id):
                last_id = max(last_id, int(event.get("id") or 0))
                handler.wfile.write(f"event: event\ndata: {json.dumps(event, ensure_ascii=False)}\n\n".encode())
            handler.wfile.flush()
            while True:
                events = EVENT_BUS.wait_after(last_id, timeout=15.0)
                if not events:
                    handler.wfile.write(b": keepalive\n\n")
                    handler.wfile.flush()
                    continue
                for event in events:
                    last_id = max(last_id, int(event.get("id") or 0))
                    handler.wfile.write(f"event: event\ndata: {json.dumps(event, ensure_ascii=False)}\n\n".encode())
                handler.wfile.flush()
        except (BrokenPipeError, ConnectionResetError):
            return True
        except Exception as exc:
            try:
                router_log("DEBUG", f"events stream closed: {type(exc).__name__}: {exc}")
            except Exception:
                pass
        return True
    return False


def _safe_segment(value: str, fallback: str = "item") -> str:
    text = re.sub(r"[^A-Za-z0-9._-]+", "-", (value or "").strip()).strip(".-")
    return text[:120] or fallback


def chat_file_max_bytes() -> int:
    raw = str(os.environ.get("CLAUDE_ANY_CHAT_FILE_MAX_BYTES") or "").strip()
    try:
        value = int(raw)
        if value > 0:
            return value
    except Exception:
        pass
    return 25 * 1024 * 1024


def store_chat_file_upload(body: dict[str, Any]) -> dict[str, Any]:
    CHAT_FILES_DIR.mkdir(parents=True, exist_ok=True)
    raw_name = str(body.get("name") or f"file-{int(time.time())}.txt").strip() or "file"
    content = body.get("content", "")
    encoding = str(body.get("encoding") or "utf-8").strip().lower()
    if encoding == "base64":
        try:
            data = base64.b64decode(str(content).encode("ascii"), validate=True)
        except Exception as exc:
            raise ValueError("invalid base64 file content") from exc
    elif encoding in {"", "text", "utf-8", "utf8"}:
        data = str(content).encode("utf-8")
    else:
        raise ValueError(f"unsupported file encoding: {encoding}")
    max_bytes = chat_file_max_bytes()
    if len(data) > max_bytes:
        raise OverflowError(f"file too large: {len(data)} bytes exceeds {max_bytes} bytes")
    name = f"{time.time_ns()}-{_safe_segment(raw_name, 'file')}"
    target = CHAT_FILES_DIR / name
    target.write_bytes(data)
    path = f"/ca/chat/files/{urllib.parse.quote(name)}"
    content_type = str(body.get("content_type") or body.get("mime_type") or "application/octet-stream").strip()
    return {
        "name": name,
        "original_name": raw_name,
        "url": f"{ROUTER_BASE}{path}",
        "path": path,
        "bytes": len(data),
        "content_type": content_type[:200] or "application/octet-stream",
    }


def store_chat_file_from_path(path_value: Any, name: str | None = None, content_type: str | None = None) -> dict[str, Any]:
    raw_path = str(path_value or "").strip()
    if not raw_path:
        raise ValueError("file path is required")
    source = Path(raw_path).expanduser()
    if not source.exists() or not source.is_file():
        raise FileNotFoundError(f"file not found: {raw_path}")
    data = source.read_bytes()
    max_bytes = chat_file_max_bytes()
    if len(data) > max_bytes:
        raise OverflowError(f"file too large: {len(data)} bytes exceeds {max_bytes} bytes")
    guessed_type = content_type or mimetypes.guess_type(source.name)[0] or "application/octet-stream"
    return store_chat_file_upload(
        {
            "name": name or source.name,
            "encoding": "base64",
            "content": base64.b64encode(data).decode("ascii"),
            "content_type": guessed_type,
        }
    )


def chat_file_markdown_lines(uploads: list[dict[str, Any]]) -> list[str]:
    lines: list[str] = []
    for upload in uploads:
        label = str(upload.get("original_name") or upload.get("name") or "file")
        url = str(upload.get("url") or upload.get("path") or "")
        byte_count = upload.get("bytes")
        ctype = str(upload.get("content_type") or "application/octet-stream")
        detail_parts = []
        if isinstance(byte_count, int):
            detail_parts.append(f"{byte_count} bytes")
        if ctype:
            detail_parts.append(ctype)
        detail = f" ({', '.join(detail_parts)})" if detail_parts else ""
        lines.append(f"- [{label}]({url}){detail}")
    return lines


def chat_file_message_text(message: str, uploads: list[dict[str, Any]]) -> str:
    body = str(message or "").strip()
    lines = chat_file_markdown_lines(uploads)
    if not lines:
        return body
    attachment_text = "Attached files:\n" + "\n".join(lines)
    return f"{body}\n\n{attachment_text}" if body else attachment_text


def _as_string_list(value: Any) -> list[str]:
    if value is None:
        return []
    if isinstance(value, str):
        text = value.strip()
        if not text:
            return []
        if text.startswith("[") and text.endswith("]"):
            try:
                parsed = json.loads(text)
                if isinstance(parsed, list):
                    return _as_string_list(parsed)
            except Exception:
                pass
        if text.lower() in ("all", "*"):
            return ["all"]
        return [text]
    if isinstance(value, (list, tuple, set)):
        out: list[str] = []
        for item in value:
            out.extend(_as_string_list(item))
        return out
    return [str(value).strip()] if str(value).strip() else []


def _chat_init_next_id() -> int:
    global _CHAT_NEXT_ID
    if _CHAT_NEXT_ID is not None:
        return _CHAT_NEXT_ID
    _CHAT_NEXT_ID = _chat_scan_max_id() + 1
    return _CHAT_NEXT_ID


def _chat_scan_max_id() -> int:
    max_id = 0
    try:
        if CHAT_MESSAGES_PATH.exists():
            with CHAT_MESSAGES_PATH.open("r", encoding="utf-8") as f:
                for line in f:
                    try:
                        item = json.loads(line)
                        max_id = max(max_id, int(item.get("id") or 0))
                    except Exception:
                        continue
    except Exception:
        pass
    return max_id


@contextlib.contextmanager
def _chat_messages_file_lock() -> Iterable[None]:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    lock_path = CHAT_MESSAGES_PATH.with_name(CHAT_MESSAGES_PATH.name + ".lock")
    with lock_path.open("a+b") as lock_file:
        if os.name == "nt":
            import msvcrt

            lock_file.seek(0, os.SEEK_END)
            if lock_file.tell() == 0:
                lock_file.write(b"\0")
                lock_file.flush()
            lock_file.seek(0)
            msvcrt.locking(lock_file.fileno(), msvcrt.LK_LOCK, 1)
            try:
                yield
            finally:
                lock_file.seek(0)
                msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
        else:
            import fcntl

            fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
            try:
                yield
            finally:
                fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)


def _message_visible_to(message: dict[str, Any], recipient: str | None) -> bool:
    if not recipient:
        return True
    recipients = _as_string_list(message.get("recipients"))
    if not recipients or "all" in [r.lower() for r in recipients] or "*" in recipients:
        return True
    return recipient in recipients or recipient == str(message.get("sender_id") or "")


def _chat_message_matches(message: dict[str, Any], channel: str | None = None, recipient: str | None = None) -> bool:
    if channel:
        meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
        aliases = {
            str(message.get("channel") or ""),
            str(meta.get("room_id") or ""),
            str(meta.get("room") or ""),
            str(meta.get("channel") or ""),
        }
        if channel not in aliases:
            return False
    return _message_visible_to(message, recipient)


def read_chat_messages(after_id: int = 0, channel: str | None = None, recipient: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
    messages: list[dict[str, Any]] = []
    try:
        if not CHAT_MESSAGES_PATH.exists():
            return []
        with CHAT_MESSAGES_PATH.open("r", encoding="utf-8") as f:
            for line in f:
                try:
                    item = json.loads(line)
                except Exception:
                    continue
                try:
                    if int(item.get("id") or 0) <= after_id:
                        continue
                except Exception:
                    continue
                if not _chat_message_matches(item, channel, recipient):
                    continue
                messages.append(item)
                if len(messages) >= limit:
                    break
    except Exception as exc:
        router_log("WARN", f"chat read failed: {exc}")
    return messages


def read_chat_messages_before(before_id: int = 0, channel: str | None = None, recipient: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
    messages: list[dict[str, Any]] = []
    try:
        if not CHAT_MESSAGES_PATH.exists():
            return []
        with CHAT_MESSAGES_PATH.open("r", encoding="utf-8") as f:
            for line in f:
                try:
                    item = json.loads(line)
                    item_id = int(item.get("id") or 0)
                except Exception:
                    continue
                if before_id > 0 and item_id >= before_id:
                    continue
                if not _chat_message_matches(item, channel, recipient):
                    continue
                messages.append(item)
                if len(messages) > limit:
                    messages = messages[-limit:]
    except Exception as exc:
        router_log("WARN", f"chat read before failed: {exc}")
    return messages


def _chat_message_payload_hash(value: Any) -> str:
    text = re.sub(r"\s+", " ", str(value or "")).strip()
    return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()


def _chat_message_time_seconds(value: Any) -> float:
    text = str(value or "").strip()
    if not text:
        return 0.0
    try:
        return time.mktime(time.strptime(text[:19], "%Y-%m-%dT%H:%M:%S"))
    except Exception:
        return 0.0


def _chat_message_recent_rows_locked(limit: int = CHAT_MESSAGE_DEDUPE_SCAN_LIMIT) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    if limit <= 0 or not CHAT_MESSAGES_PATH.exists():
        return rows
    try:
        with CHAT_MESSAGES_PATH.open("r", encoding="utf-8") as f:
            for line in f:
                try:
                    item = json.loads(line)
                except Exception:
                    continue
                if isinstance(item, dict):
                    rows.append(item)
                    if len(rows) > limit:
                        rows = rows[-limit:]
    except Exception as exc:
        router_log("WARN", f"chat duplicate scan failed: {exc}")
    return rows


def _chat_message_stable_dedupe_key(message: dict[str, Any]) -> tuple[str, ...] | None:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    source = str(meta.get("mcp_server") or meta.get("sse_source") or meta.get("source") or message.get("sender_id") or "").strip()
    method = str(meta.get("mcp_method") or "").strip()
    channel = str(message.get("channel") or meta.get("room_id") or meta.get("room") or meta.get("channel") or "").strip()
    kind = str(message.get("kind") or meta.get("kind") or meta.get("type") or meta.get("event_type") or meta.get("eventType") or "").strip()
    body_hash = _chat_message_payload_hash(message.get("message"))
    for key in ("cursor", "stream_id", "sse_id", "event_id", "message_id", "source_message_id", "sequence", "seq", "rpc_id"):
        value = meta.get(key)
        if value is None:
            continue
        stable_value = str(value).strip()
        if stable_value:
            return ("stable", source, method, channel, kind, key, stable_value, body_hash)
    return None


def _chat_message_fallback_dedupe_key(message: dict[str, Any]) -> tuple[str, ...] | None:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    method = str(meta.get("mcp_method") or "").strip()
    if not method.startswith("notifications/"):
        return None
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
    if not body:
        return None
    source = str(meta.get("mcp_server") or meta.get("sse_source") or meta.get("source") or message.get("sender_id") or "").strip()
    channel = str(message.get("channel") or meta.get("room_id") or meta.get("room") or meta.get("channel") or "").strip()
    sender = str(message.get("sender_id") or meta.get("sender_id") or meta.get("agent_id") or "").strip()
    kind = str(message.get("kind") or meta.get("kind") or meta.get("type") or meta.get("event_type") or meta.get("eventType") or "").strip()
    return ("fallback", source, method, channel, sender, kind, _chat_message_payload_hash(body))


def _chat_message_duplicate_locked(message: dict[str, Any]) -> dict[str, Any] | None:
    stable_key = _chat_message_stable_dedupe_key(message)
    fallback_key = _chat_message_fallback_dedupe_key(message)
    if not stable_key and not fallback_key:
        return None
    now = time.time()
    for row in reversed(_chat_message_recent_rows_locked()):
        if stable_key and _chat_message_stable_dedupe_key(row) == stable_key:
            return row
        if not fallback_key or _chat_message_fallback_dedupe_key(row) != fallback_key:
            continue
        row_time = _chat_message_time_seconds(row.get("time"))
        if row_time > 0 and now - row_time <= CHAT_MESSAGE_FALLBACK_DEDUPE_TTL_SECONDS:
            return row
    return None


def append_chat_message(payload: dict[str, Any]) -> dict[str, Any]:
    global _CHAT_NEXT_ID
    with _CHAT_CONDITION:
        with _chat_messages_file_lock():
            CONFIG_DIR.mkdir(parents=True, exist_ok=True)
            if CHAT_MESSAGES_PATH.exists() and CHAT_MESSAGES_PATH.stat().st_size > CHAT_MESSAGES_MAX_BYTES:
                CHAT_MESSAGES_PATH.replace(CHAT_MESSAGES_PATH.with_suffix(".jsonl.1"))
                _CHAT_NEXT_ID = 1
            next_id = _chat_scan_max_id() + 1
            _CHAT_NEXT_ID = next_id + 1
            message = {
                "id": next_id,
                "time": time.strftime("%Y-%m-%dT%H:%M:%S"),
                "channel": str(payload.get("channel") or "default"),
                "sender_id": str(payload.get("sender_id") or payload.get("sender") or "anonymous"),
                "recipients": _as_string_list(payload.get("recipients", payload.get("recipient_id"))),
                "thread_id": str(payload.get("thread_id") or payload.get("parent_id") or next_id),
                "parent_id": payload.get("parent_id"),
                "message": str(payload.get("message") or payload.get("text") or ""),
                "kind": str(payload.get("kind") or "message"),
                "meta": payload.get("meta") if isinstance(payload.get("meta"), dict) else {},
            }
            if payload.get("visibility") is not None:
                message["visibility"] = str(payload.get("visibility") or "user")
            if payload.get("delivery") is not None:
                message["delivery"] = _as_string_list(payload.get("delivery"))
            duplicate = _chat_message_duplicate_locked(message)
            if duplicate:
                existing_id = duplicate.get("id")
                returned = dict(duplicate)
                returned["_claude_any_duplicate"] = True
                router_log(
                    "INFO",
                    f"chat_message_skipped_duplicate existing_id={existing_id} channel={message.get('channel')} kind={message.get('kind')}",
                )
                return returned
            with CHAT_MESSAGES_PATH.open("a", encoding="utf-8") as f:
                f.write(json.dumps(message, ensure_ascii=False, separators=(",", ":")) + "\n")
        _CHAT_CONDITION.notify_all()
        return message


def _channel_sse_status_public(name: str, state: dict[str, Any]) -> dict[str, Any]:
    return {
        "name": name,
        "url": state.get("url"),
        "channel": state.get("channel"),
        "sender_id": state.get("sender_id"),
        "recipient": state.get("recipient"),
        "running": bool(state.get("running")),
        "started_at": state.get("started_at"),
        "last_event_at": state.get("last_event_at"),
        "messages_received": int(state.get("messages_received") or 0),
        "event_filter": state.get("event_filter") or [],
        "read_timeout_seconds": state.get("read_timeout_seconds"),
        "last_sse_event_id": state.get("last_sse_event_id"),
        "sse_reconnects": int(state.get("sse_reconnects") or 0),
        "transport": state.get("transport") or "sse",
        "mcp_endpoint": state.get("mcp_endpoint"),
        "mcp_initialized": bool(state.get("mcp_initialized")),
        "mcp_session_id": state.get("mcp_session_id"),
        "mcp_protocol_version": state.get("mcp_protocol_version"),
        "mcp_last_error": state.get("mcp_last_error"),
        "last_error": state.get("last_error"),
    }


def channel_sse_status() -> dict[str, Any]:
    with _CHANNEL_SSE_LOCK:
        return {name: _channel_sse_status_public(name, state) for name, state in _CHANNEL_SSE_CONNECTIONS.items()}


def _first_present_dict_value(*sources: Any, keys: tuple[str, ...]) -> Any:
    for source in sources:
        if not isinstance(source, dict):
            continue
        for key in keys:
            value = source.get(key)
            if value is not None:
                return value
    return None


def _event_payload_text(value: Any, depth: int = 0) -> str | None:
    if value is None or depth > 5:
        return None
    if isinstance(value, str):
        return value.strip() or None
    if not isinstance(value, dict):
        return str(value)
    direct = _first_present_dict_value(value, keys=("content", "message", "text", "body", "summary"))
    if isinstance(direct, str) and direct.strip():
        return direct.strip()
    if isinstance(direct, dict):
        nested = _event_payload_text(direct, depth + 1)
        if nested:
            return nested
    for key in ("data", "event", "payload", "message", "notification", "item"):
        nested = _event_payload_text(value.get(key), depth + 1)
        if nested:
            return nested
    event_type = value.get("type") or value.get("event_type") or value.get("kind")
    if event_type:
        payload = value.get("payload") if isinstance(value.get("payload"), dict) else value.get("data")
        if payload is not None:
            return f"{event_type}: {json.dumps(payload, ensure_ascii=False, separators=(',', ':'))}"
        return str(event_type)
    return None


def _event_meta_from_sources(*sources: Any) -> dict[str, Any]:
    meta: dict[str, Any] = {}
    for source in sources:
        if not isinstance(source, dict):
            continue
        source_meta = source.get("meta")
        if isinstance(source_meta, dict):
            meta.update(source_meta)
        for key in (
            "room_id",
            "room",
            "channel",
            "thread_id",
            "parent_id",
            "message_id",
            "source_message_id",
            "event_id",
            "stream_id",
            "sse_id",
            "cursor",
            "assignment_id",
            "poll_id",
            "task_id",
            "sequence",
            "seq",
            "round_id",
            "conversation_id",
            "session_id",
            "agent_id",
            "agent_name",
            "sender_id",
            "sender",
            "recipient_id",
            "recipient",
            "recipients",
            "target_id",
            "target",
            "type",
            "event_type",
            "eventType",
            "kind",
            "timestamp",
            "created_at",
            "updated_at",
            "status",
            "priority",
            "title",
            "name",
            "model",
            "runtime",
        ):
            value = source.get(key)
            if value is not None and key not in meta:
                meta[key] = value
    return meta


def _metadata_key_is_sensitive(key: str) -> bool:
    return bool(re.search(r"(authorization|api[_-]?key|token|secret|password|credential|cookie)", key, re.I))


def _json_safe_metadata(value: Any, depth: int = 0) -> Any:
    if depth > 8:
        return "<max-depth>"
    if value is None or isinstance(value, (bool, int, float)):
        return value
    if isinstance(value, str):
        return value if len(value) <= 8000 else value[:8000] + "...<truncated>"
    if isinstance(value, list):
        out = [_json_safe_metadata(item, depth + 1) for item in value[:200]]
        if len(value) > 200:
            out.append(f"...<{len(value) - 200} more>")
        return out
    if isinstance(value, dict):
        out: dict[str, Any] = {}
        items = list(value.items())
        for key, item in items[:200]:
            skey = str(key)
            out[skey] = "[redacted]" if _metadata_key_is_sensitive(skey) else _json_safe_metadata(item, depth + 1)
        if len(items) > 200:
            out["..."] = f"<{len(items) - 200} more>"
        return out
    return str(value)


def _compact_json_for_prompt(value: Any, max_chars: int = 2400) -> str:
    try:
        text = json.dumps(value, ensure_ascii=False, separators=(",", ":"), default=str)
    except Exception:
        text = str(value)
    if len(text) <= max_chars:
        return text
    return text[: max_chars - 16] + "...<truncated>"


_CHANNEL_CONTROL_KINDS = {
    "connection",
    "connected",
    "disconnect",
    "disconnected",
    "endpoint",
    "heartbeat",
    "initialized",
    "init",
    "keepalive",
    "ping",
    "pong",
    "ready",
    "status",
    "system",
}


def _channel_event_is_user_visible(kind: str, method: str, event_name: str, content: str, meta: dict[str, Any]) -> bool:
    normalized_kind = str(kind or "").strip().lower().replace("_", ".").replace("/", ".")
    normalized_method = str(method or "").strip().lower()
    normalized_event = str(event_name or "").strip().lower()
    meta_type = str(meta.get("type") or meta.get("event_type") or meta.get("eventType") or meta.get("kind") or meta.get("status") or "").strip().lower()
    if normalized_kind in _CHANNEL_CONTROL_KINDS or normalized_event in _CHANNEL_CONTROL_KINDS or meta_type in _CHANNEL_CONTROL_KINDS:
        return False
    if meta.get("jsonrpc") is not None:
        if normalized_method.startswith("notifications/claude/"):
            return True
        if normalized_method in {"notifications/message", "notifications/chat", "notifications/event"}:
            return True
        return False
    return bool(content.strip())


def _sse_payload_to_chat_payload(data_text: str, event_name: str, defaults: dict[str, Any], event_id: str | None = None) -> dict[str, Any] | None:
    text = (data_text or "").strip()
    if not text or text == "[DONE]":
        return None
    if (event_name or "").strip().lower() == "endpoint":
        return None
    parsed: Any = None
    try:
        parsed = json.loads(text)
    except Exception:
        parsed = None

    meta: dict[str, Any] = {
        "sse_event": event_name or "message",
        "sse_source": defaults.get("name") or "",
    }
    if event_id:
        meta["sse_id"] = event_id
    content = text
    kind = "sse"
    method = str(event_name or "message")
    event_filter = defaults.get("event_filter")
    allowed_events = {str(item).strip() for item in event_filter if str(item).strip()} if isinstance(event_filter, list) else set()
    if isinstance(parsed, dict):
        method = str(parsed.get("method") or event_name or "message")
        meta["sse_json"] = _json_safe_metadata(parsed)
        if parsed.get("jsonrpc") is not None:
            meta["jsonrpc"] = parsed.get("jsonrpc")
        if parsed.get("id") is not None:
            meta["rpc_id"] = parsed.get("id")
        if parsed.get("method") is not None:
            meta["mcp_method"] = parsed.get("method")
        params = parsed.get("params") if isinstance(parsed.get("params"), dict) else {}
        payload = parsed.get("payload") if isinstance(parsed.get("payload"), dict) else {}
        data = params.get("data") if isinstance(params.get("data"), dict) else {}
        event = params.get("event") if isinstance(params.get("event"), dict) else {}
        nested_payload = (
            data.get("payload") if isinstance(data.get("payload"), dict) else
            event.get("payload") if isinstance(event.get("payload"), dict) else {}
        )
        meta.update(_event_meta_from_sources(parsed, params, payload, data, event, nested_payload))
        nested_content = _event_payload_text(nested_payload) or _event_payload_text(payload)
        for source in (data, event):
            direct = _first_present_dict_value(source, keys=("content", "message", "text", "body", "summary"))
            nested_content = nested_content or _event_payload_text(direct)
        content = nested_content or _event_payload_text(params) or _event_payload_text(parsed) or content
        kind = method.replace("notifications/claude/", "").replace("/", ".") if method else "sse"
        if not parsed.get("method") and meta.get("kind"):
            kind = str(meta.get("kind"))
    if allowed_events and method not in allowed_events and (event_name or "message") not in allowed_events:
        return None
    if not _channel_event_is_user_visible(kind, method, event_name, content, meta):
        return None
    channel = meta.get("channel") or meta.get("room_id") or meta.get("room") or defaults.get("channel") or "default"
    return {
        "channel": channel,
        "sender_id": meta.get("sender_id") or meta.get("sender") or meta.get("agent_id") or defaults.get("sender_id") or "sse",
        "recipients": meta.get("recipients") or meta.get("recipient_id") or meta.get("recipient") or defaults.get("recipient") or defaults.get("recipients") or "all",
        "thread_id": meta.get("thread_id"),
        "parent_id": meta.get("parent_id"),
        "kind": kind,
        "message": content,
        "meta": meta,
        "visibility": "user",
        "delivery": ["llm"],
    }


def _channel_sse_set_state(name: str, **updates: Any) -> None:
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if state:
            state.update(updates)


def _channel_sse_absolute_endpoint(stream_url: str, endpoint: str) -> str:
    endpoint = (endpoint or "").strip()
    if endpoint.startswith(("http://", "https://")):
        return endpoint
    return urllib.parse.urljoin(stream_url, endpoint)


MCP_STREAMABLE_HTTP_PROTOCOL_VERSION = "2025-03-26"
MCP_LEGACY_SSE_PROTOCOL_VERSION = "2024-11-05"


def _read_mcp_sse_json_response(response: Any, request_id: Any | None = None) -> Any:
    data_lines: list[str] = []
    while True:
        raw = response.readline()
        if raw == b"":
            break
        line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
        if not line:
            if data_lines:
                data_text = "\n".join(data_lines).strip()
                try:
                    msg = json.loads(data_text)
                except Exception:
                    msg = None
                if isinstance(msg, dict):
                    if request_id is None or msg.get("id") == request_id or "id" not in msg:
                        return msg
            data_lines = []
            continue
        if line.startswith(":"):
            continue
        field, _, value = line.partition(":")
        if value.startswith(" "):
            value = value[1:]
        if field == "data":
            data_lines.append(value)
    if data_lines:
        data_text = "\n".join(data_lines).strip()
        try:
            msg = json.loads(data_text)
        except Exception:
            msg = None
        if isinstance(msg, dict) and (request_id is None or msg.get("id") == request_id or "id" not in msg):
            return msg
    return None


def _mcp_post_json_with_response_headers(
    endpoint: str,
    headers: dict[str, str],
    payload: dict[str, Any],
    timeout: float,
) -> tuple[Any, Any]:
    request_headers = {**headers, "Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
    req = urllib.request.Request(
        endpoint,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers=request_headers,
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=timeout) as response:
        content_type = str(response.headers.get("Content-Type") or "").lower()
        if "text/event-stream" in content_type:
            return _read_mcp_sse_json_response(response, payload.get("id")), response.headers
        data = response.read()
        if not data:
            return None, response.headers
        try:
            return json.loads(data.decode("utf-8")), response.headers
        except Exception:
            return data.decode("utf-8", errors="replace"), response.headers


def _mcp_sse_post_json(endpoint: str, headers: dict[str, str], payload: dict[str, Any], timeout: float) -> Any:
    result, _headers = _mcp_post_json_with_response_headers(endpoint, headers, payload, timeout)
    return result


def _mcp_streamable_headers(
    headers: dict[str, str],
    protocol_version: str,
    session_id: str | None = None,
    *,
    accept: str = "application/json, text/event-stream",
) -> dict[str, str]:
    out = {**headers, "Accept": accept, "MCP-Protocol-Version": protocol_version}
    if session_id:
        out["Mcp-Session-Id"] = session_id
    return out


def _mcp_streamable_post_json(
    endpoint: str,
    headers: dict[str, str],
    payload: dict[str, Any],
    timeout: float,
    protocol_version: str,
    session_id: str | None = None,
) -> tuple[Any, str | None]:
    result, response_headers = _mcp_post_json_with_response_headers(
        endpoint,
        _mcp_streamable_headers(headers, protocol_version, session_id),
        payload,
        timeout,
    )
    returned_session = None
    if response_headers is not None:
        returned_session = response_headers.get("Mcp-Session-Id") or response_headers.get("MCP-Session-Id")
    return result, str(returned_session).strip() if returned_session else None


def _http_error_body_text(exc: urllib.error.HTTPError) -> str:
    try:
        data = exc.read()
    except Exception:
        data = b""
    return data.decode("utf-8", errors="replace") if data else ""


def _streamable_http_session_not_found(exc: urllib.error.HTTPError, body_text: str = "") -> bool:
    text = f"{getattr(exc, 'reason', '')} {body_text}".strip().lower()
    return bool(
        exc.code == 404
        or "session-not-found" in text
        or "session not found" in text
        or ("session" in text and "not found" in text)
    )


def _channel_streamable_http_mark_session_lost(name: str, reason: str) -> None:
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if not state:
            return
        state["mcp_initialized"] = False
        state["mcp_session_id"] = None
        state["mcp_last_error"] = reason
        state["last_error"] = reason
        state["sse_reconnects"] = int(state.get("sse_reconnects") or 0) + 1


def _channel_sse_store_rpc_response(name: str, data_text: str) -> bool:
    try:
        payload = json.loads((data_text or "").strip())
    except Exception:
        return False
    if not isinstance(payload, dict) or payload.get("id") is None:
        return False
    if "result" not in payload and "error" not in payload:
        return False
    rpc_id = str(payload.get("id"))
    with _CHANNEL_SSE_RPC_CONDITION:
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if not state:
                return True
            results = state.get("mcp_rpc_results")
            if not isinstance(results, dict):
                results = {}
                state["mcp_rpc_results"] = results
            results[rpc_id] = payload
            if len(results) > 200:
                for old_id in list(results)[: len(results) - 200]:
                    results.pop(old_id, None)
        _CHANNEL_SSE_RPC_CONDITION.notify_all()
    router_log("INFO", f"channel_sse_mcp_rpc_response name={name} id={rpc_id}")
    return True


def _channel_sse_take_rpc_response(name: str, rpc_id: Any, timeout: float) -> dict[str, Any] | None:
    key = str(rpc_id)
    deadline = time.time() + max(0.1, timeout)
    with _CHANNEL_SSE_RPC_CONDITION:
        while True:
            with _CHANNEL_SSE_LOCK:
                state = _CHANNEL_SSE_CONNECTIONS.get(name)
                results = state.get("mcp_rpc_results") if isinstance(state, dict) else None
                if isinstance(results, dict) and key in results:
                    found = results.pop(key)
                    return found if isinstance(found, dict) else None
            remaining = deadline - time.time()
            if remaining <= 0:
                return None
            _CHANNEL_SSE_RPC_CONDITION.wait(min(remaining, 1.0))


def _channel_sse_public_mcp_name(name: str) -> str:
    text = str(name or "").strip()
    return text[4:] if text.startswith("mcp-") else text


def _channel_sse_state_name_for_mcp_server(server_name: str) -> str | None:
    candidates = []
    text = str(server_name or "").strip()
    if text:
        candidates.append(text)
        if text.startswith("mcp-"):
            candidates.append(text[4:])
        else:
            candidates.append(f"mcp-{text}")
    with _CHANNEL_SSE_LOCK:
        for candidate in candidates:
            if candidate in _CHANNEL_SSE_CONNECTIONS:
                return candidate
        for name in _CHANNEL_SSE_CONNECTIONS:
            if _channel_sse_public_mcp_name(name) == text:
                return name
    return None


def _channel_sse_rpc_request(name: str, method: str, params: dict[str, Any] | None = None, timeout: float | None = None) -> dict[str, Any]:
    request_id = int(time.time_ns() % 9_000_000_000_000_000)
    payload = {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params or {}}
    for attempt in range(2):
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if not state:
                raise RuntimeError(f"SSE channel {name} is not connected")
            transport = str(state.get("transport") or "sse").strip().lower()
            if transport in {"http", "streamable-http"} and not state.get("mcp_initialized"):
                needs_http_initialize = True
            else:
                needs_http_initialize = False
        if needs_http_initialize:
            _channel_streamable_http_initialize_mcp(name)
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if not state:
                raise RuntimeError(f"SSE channel {name} is not connected")
            if not state.get("mcp_initialized"):
                raise RuntimeError(f"SSE channel {name} is not MCP initialized")
            endpoint = str(state.get("mcp_endpoint") or "")
            headers = dict(state.get("headers") or {})
            transport = str(state.get("transport") or "sse").strip().lower()
            protocol_version = str(state.get("mcp_protocol_version") or MCP_LEGACY_SSE_PROTOCOL_VERSION)
            session_id = str(state.get("mcp_session_id") or "").strip() or None
            requires_session = parse_bool(state.get("streamable_requires_session"), True)
            effective_timeout = float(timeout if timeout is not None else state.get("mcp_timeout_seconds") or 20.0)
        if not endpoint:
            raise RuntimeError(f"SSE channel {name} has no MCP endpoint")
        if transport in {"http", "streamable-http"}:
            if requires_session and not session_id:
                raise RuntimeError(f"Streamable HTTP MCP channel {name} has no Mcp-Session-Id")
            try:
                posted, returned_session = _mcp_streamable_post_json(
                    endpoint,
                    headers,
                    payload,
                    max(1.0, min(120.0, effective_timeout)),
                    protocol_version,
                    session_id,
                )
            except urllib.error.HTTPError as exc:
                body_text = _http_error_body_text(exc)
                if attempt == 0 and _streamable_http_session_not_found(exc, body_text):
                    reason = f"streamable_http_session_not_found:HTTPError:{exc.code}"
                    _channel_streamable_http_mark_session_lost(name, reason)
                    router_log("WARN", f"channel_http_mcp_session_lost name={name} method={method} error=HTTPError:{exc.code}:{exc.reason}")
                    continue
                raise
            if returned_session:
                _channel_sse_set_state(name, mcp_session_id=returned_session)
        else:
            posted = _mcp_sse_post_json(endpoint, headers, payload, max(1.0, min(120.0, effective_timeout)))
        break
    else:
        raise RuntimeError(f"SSE channel {name} could not send MCP request")
    if isinstance(posted, dict) and posted.get("id") == request_id and ("result" in posted or "error" in posted):
        return posted
    response = _channel_sse_take_rpc_response(name, request_id, max(1.0, min(120.0, effective_timeout)))
    if response is None:
        raise TimeoutError(f"timed out waiting for MCP SSE response id={request_id} method={method} channel={name}")
    return response


def _channel_sse_maybe_initialize_mcp(name: str, endpoint_text: str) -> None:
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if not state:
            return
        if not bool(state.get("mcp_enabled", True)):
            return
        stream_url = str(state.get("url") or "")
        endpoint = _channel_sse_absolute_endpoint(stream_url, endpoint_text)
        current_endpoint = str(state.get("mcp_endpoint") or "")
        was_initialized = bool(state.get("mcp_initialized"))
        if was_initialized and current_endpoint == endpoint:
            return
        headers = dict(state.get("headers") or {})
        timeout = max(5.0, min(120.0, float(state.get("mcp_timeout_seconds") or 20.0)))
        protocol_version = str(state.get("mcp_protocol_version") or "2024-11-05")
    try:
        if was_initialized and current_endpoint:
            router_log("INFO", f"channel_sse_mcp_reinitializing name={name} old_endpoint={current_endpoint} new_endpoint={endpoint}")
        initialize = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": protocol_version,
                "capabilities": {},
                "clientInfo": {"name": "claude-any-channel-bridge", "version": VERSION},
            },
        }
        _mcp_sse_post_json(endpoint, headers, initialize, timeout)
        initialized = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}
        _mcp_sse_post_json(endpoint, headers, initialized, timeout)
        _channel_sse_set_state(name, mcp_endpoint=endpoint, mcp_initialized=True, mcp_last_error=None, mcp_rpc_results={})
        router_log("INFO", f"channel_sse_mcp_initialized name={name} endpoint={endpoint}")
    except Exception as exc:
        _channel_sse_set_state(name, mcp_endpoint=endpoint, mcp_initialized=False, mcp_last_error=f"{type(exc).__name__}: {exc}")
        router_log("WARN", f"channel_sse_mcp_initialize_failed name={name} endpoint={endpoint} error={type(exc).__name__}: {exc}")


def _channel_streamable_http_initialize_mcp(name: str) -> None:
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if not state:
            return
        if not bool(state.get("mcp_enabled", True)):
            return
        endpoint = str(state.get("url") or "")
        if bool(state.get("mcp_initialized")) and str(state.get("mcp_endpoint") or "") == endpoint:
            return
        headers = dict(state.get("headers") or {})
        timeout = max(5.0, min(120.0, float(state.get("mcp_timeout_seconds") or 20.0)))
        protocol_version = str(state.get("mcp_protocol_version") or MCP_STREAMABLE_HTTP_PROTOCOL_VERSION)
        requires_session = parse_bool(state.get("streamable_requires_session"), True)
    try:
        initialize = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": protocol_version,
                "capabilities": {},
                "clientInfo": {"name": "claude-any-channel-bridge", "version": VERSION},
            },
        }
        _result, session_id = _mcp_streamable_post_json(endpoint, headers, initialize, timeout, protocol_version)
        if requires_session and not session_id:
            _channel_sse_set_state(
                name,
                mcp_endpoint=endpoint,
                mcp_initialized=False,
                mcp_session_id=None,
                mcp_last_error="streamable_http_missing_session_id",
            )
            router_log("WARN", f"channel_http_mcp_initialize_failed name={name} endpoint={endpoint} error=missing_mcp_session_id")
            return
        initialized = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}
        _mcp_streamable_post_json(endpoint, headers, initialized, timeout, protocol_version, session_id)
        _channel_sse_set_state(
            name,
            mcp_endpoint=endpoint,
            mcp_initialized=True,
            mcp_session_id=session_id,
            mcp_last_error=None,
            mcp_rpc_results={},
        )
        visible_session = session_id or "-"
        router_log("INFO", f"channel_http_mcp_initialized name={name} endpoint={endpoint} session={visible_session}")
    except urllib.error.HTTPError as exc:
        if exc.code == 405:
            _channel_sse_set_state(
                name,
                transport="sse",
                mcp_endpoint="",
                mcp_initialized=False,
                mcp_session_id=None,
                mcp_protocol_version=MCP_LEGACY_SSE_PROTOCOL_VERSION,
                mcp_last_error="streamable_http_405_fallback_sse",
            )
            router_log("WARN", f"channel_http_fallback_sse name={name} endpoint={endpoint} reason=HTTPError:405")
            return
        _channel_sse_set_state(name, mcp_endpoint=endpoint, mcp_initialized=False, mcp_last_error=f"HTTPError: {exc.code} {exc.reason}")
        router_log("WARN", f"channel_http_mcp_initialize_failed name={name} endpoint={endpoint} error=HTTPError:{exc.code}: {exc.reason}")
    except Exception as exc:
        _channel_sse_set_state(name, mcp_endpoint=endpoint, mcp_initialized=False, mcp_last_error=f"{type(exc).__name__}: {exc}")
        router_log("WARN", f"channel_http_mcp_initialize_failed name={name} endpoint={endpoint} error={type(exc).__name__}: {exc}")


def _channel_sse_dispatch(name: str, event_name: str, data_lines: list[str], event_id: str | None = None) -> None:
    data_text = "\n".join(data_lines)
    if event_id is not None:
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if state:
                state["last_sse_event_id"] = str(event_id)
    if (event_name or "").strip().lower() == "endpoint":
        _channel_sse_maybe_initialize_mcp(name, data_text)
        return
    if _channel_sse_store_rpc_response(name, data_text):
        return
    if str(name or "").strip().lower() in _NATIVE_ROUTER_CHANNEL_NAMES:
        router_log(
            "INFO",
            f"channel_sse_message_ignored name={name} event={event_name or 'message'} reason=native_router_self_echo",
        )
        return
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if not state:
            return
        defaults = dict(state)
    payload = _sse_payload_to_chat_payload(data_text, event_name, defaults, event_id=event_id)
    if not payload:
        return
    payload = _mark_channel_payload_direct_llm_pending(payload)
    saved = append_chat_message(payload)
    if saved.get("_claude_any_duplicate"):
        router_log(
            "INFO",
            f"channel_sse_message_skipped_duplicate name={name} event={event_name or 'message'} existing_id={saved.get('id')} channel={saved.get('channel')}",
        )
        return
    now = time.strftime("%Y-%m-%dT%H:%M:%S")
    with _CHANNEL_SSE_LOCK:
        state = _CHANNEL_SSE_CONNECTIONS.get(name)
        if state:
            state["last_event_at"] = now
            state["messages_received"] = int(state.get("messages_received") or 0) + 1
            state["last_error"] = None
    router_log(
        "INFO",
        f"channel_sse_message_received name={name} event={event_name or 'message'} message_id={saved.get('id')} channel={saved.get('channel')}",
    )
    schedule_channel_direct_llm_delivery(saved)


def _channel_sse_worker(name: str) -> None:
    while True:
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if not state or not state.get("running"):
                return
            url = str(state.get("url") or "")
            headers = dict(state.get("headers") or {})
            last_event_id = state.get("last_sse_event_id")
            read_timeout = max(5.0, min(3600.0, float(state.get("read_timeout_seconds") or 300.0)))
            retry_seconds = max(1.0, min(60.0, float(state.get("retry_seconds") or 5.0)))
        event_name = "message"
        event_id: str | None = None
        data_lines: list[str] = []
        try:
            request_headers = {**headers, "Accept": "text/event-stream"}
            if last_event_id is not None and str(last_event_id) != "":
                request_headers["Last-Event-ID"] = str(last_event_id)
            req = urllib.request.Request(url, headers=request_headers)
            with urllib.request.urlopen(req, timeout=read_timeout) as response:
                _channel_sse_set_state(name, last_error=None)
                resumed = str(last_event_id) if last_event_id is not None and str(last_event_id) != "" else "-"
                router_log("INFO", f"channel_sse_connected name={name} url={url} last_event_id={resumed}")
                while True:
                    with _CHANNEL_SSE_LOCK:
                        current = _CHANNEL_SSE_CONNECTIONS.get(name)
                        if not current or not current.get("running"):
                            return
                    raw = response.readline()
                    if raw == b"":
                        raise ConnectionError("SSE stream ended")
                    line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
                    if not line:
                        if data_lines:
                            _channel_sse_dispatch(name, event_name, data_lines, event_id=event_id)
                        event_name = "message"
                        event_id = None
                        data_lines = []
                        continue
                    if line.startswith(":"):
                        continue
                    field, _, value = line.partition(":")
                    if value.startswith(" "):
                        value = value[1:]
                    if field == "event":
                        event_name = value or "message"
                    elif field == "data":
                        data_lines.append(value)
                    elif field == "id":
                        event_id = value
                    elif field == "retry":
                        try:
                            retry_seconds = max(1.0, min(60.0, int(value) / 1000.0))
                        except Exception:
                            pass
        except Exception as exc:
            with _CHANNEL_SSE_LOCK:
                state = _CHANNEL_SSE_CONNECTIONS.get(name)
                if not state or not state.get("running"):
                    return
                state["last_error"] = f"{type(exc).__name__}: {exc}"
                state["sse_reconnects"] = int(state.get("sse_reconnects") or 0) + 1
                last_event_id = state.get("last_sse_event_id")
            resumed = str(last_event_id) if last_event_id is not None and str(last_event_id) != "" else "-"
            router_log("WARN", f"channel_sse_reconnect name={name} last_event_id={resumed} error={type(exc).__name__}: {exc}")
            time.sleep(retry_seconds)


def _channel_streamable_http_worker(name: str) -> None:
    while True:
        with _CHANNEL_SSE_LOCK:
            state = _CHANNEL_SSE_CONNECTIONS.get(name)
            if not state or not state.get("running"):
                return
            url = str(state.get("url") or "")
            headers = dict(state.get("headers") or {})
            protocol_version = str(state.get("mcp_protocol_version") or MCP_STREAMABLE_HTTP_PROTOCOL_VERSION)
            session_id = str(state.get("mcp_session_id") or "").strip() or None
            requires_session = parse_bool(state.get("streamable_requires_session"), True)
            needs_initialize = bool(not state.get("mcp_initialized") or (requires_session and not session_id))
            last_event_id = state.get("last_sse_event_id")
            read_timeout = max(5.0, min(3600.0, float(state.get("read_timeout_seconds") or 300.0)))
            retry_seconds = max(1.0, min(60.0, float(state.get("retry_seconds") or 5.0)))
        if needs_initialize:
            _channel_streamable_http_initialize_mcp(name)
            sleep_after_initialize = False
            with _CHANNEL_SSE_LOCK:
                state = _CHANNEL_SSE_CONNECTIONS.get(name)
                if not state or not state.get("running"):
                    return
                if str(state.get("transport") or "").strip().lower() == "sse":
                    router_log("INFO", f"channel_http_worker_switching_to_sse name={name}")
                    break
                if not state.get("mcp_initialized"):
                    sleep_after_initialize = True
                else:
                    session_id = str(state.get("mcp_session_id") or "").strip() or None
                    requires_session = parse_bool(state.get("streamable_requires_session"), True)
                    if requires_session and not session_id:
                        state["mcp_initialized"] = False
                        state["mcp_last_error"] = "streamable_http_missing_session_id"
                        sleep_after_initialize = True
            if sleep_after_initialize:
                time.sleep(retry_seconds)
                continue
        event_name = "message"
        event_id: str | None = None
        data_lines: list[str] = []
        try:
            request_headers = _mcp_streamable_headers(
                headers,
                protocol_version,
                session_id,
                accept="text/event-stream",
            )
            if last_event_id is not None and str(last_event_id) != "":
                request_headers["Last-Event-ID"] = str(last_event_id)
            req = urllib.request.Request(url, headers=request_headers, method="GET")
            with urllib.request.urlopen(req, timeout=read_timeout) as response:
                _channel_sse_set_state(name, last_error=None)
                resumed = str(last_event_id) if last_event_id is not None and str(last_event_id) != "" else "-"
                visible_session = session_id or "-"
                router_log("INFO", f"channel_http_connected name={name} url={url} session={visible_session} last_event_id={resumed}")
                while True:
                    with _CHANNEL_SSE_LOCK:
                        current = _CHANNEL_SSE_CONNECTIONS.get(name)
                        if not current or not current.get("running"):
                            return
                    raw = response.readline()
                    if raw == b"":
                        raise ConnectionError("Streamable HTTP SSE stream ended")
                    line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
                    if not line:
                        if data_lines:
                            _channel_sse_dispatch(name, event_name, data_lines, event_id=event_id)
                        event_name = "message"
                        event_id = None
                        data_lines = []
                        continue
                    if line.startswith(":"):
                        continue
                    field, _, value = line.partition(":")
                    if value.startswith(" "):
                        value = value[1:]
                    if field == "event":
                        event_name = value or "message"
                    elif field == "data":
                        data_lines.append(value)
                    elif field == "id":
                        event_id = value
                    elif field == "retry":
                        try:
                            retry_seconds = max(1.0, min(60.0, int(value) / 1000.0))
                        except Exception:
                            pass
        except urllib.error.HTTPError as exc:
            body_text = _http_error_body_text(exc)
            with _CHANNEL_SSE_LOCK:
                state = _CHANNEL_SSE_CONNECTIONS.get(name)
                if not state or not state.get("running"):
                    return
                if exc.code == 405:
                    state["transport"] = "sse"
                    state["mcp_protocol_version"] = MCP_LEGACY_SSE_PROTOCOL_VERSION
                    state["mcp_initialized"] = False
                    state["mcp_session_id"] = None
                    state["last_error"] = "streamable_http_405_fallback_sse"
                    router_log("WARN", f"channel_http_fallback_sse name={name} url={url} reason=HTTPError:405")
                    break
                if _streamable_http_session_not_found(exc, body_text):
                    state["mcp_initialized"] = False
                    state["mcp_session_id"] = None
                    state["mcp_last_error"] = f"streamable_http_session_not_found:HTTPError:{exc.code}"
                state["last_error"] = f"HTTPError: {exc.code} {exc.reason}"
                state["sse_reconnects"] = int(state.get("sse_reconnects") or 0) + 1
                last_event_id = state.get("last_sse_event_id")
            resumed = str(last_event_id) if last_event_id is not None and str(last_event_id) != "" else "-"
            if _streamable_http_session_not_found(exc, body_text):
                router_log("WARN", f"channel_http_session_lost_reinitialize name={name} last_event_id={resumed} error=HTTPError:{exc.code}:{exc.reason}")
            else:
                router_log("WARN", f"channel_http_reconnect name={name} last_event_id={resumed} error=HTTPError:{exc.code}:{exc.reason}")
            time.sleep(retry_seconds)
        except Exception as exc:
            with _CHANNEL_SSE_LOCK:
                state = _CHANNEL_SSE_CONNECTIONS.get(name)
                if not state or not state.get("running"):
                    return
                state["last_error"] = f"{type(exc).__name__}: {exc}"
                state["sse_reconnects"] = int(state.get("sse_reconnects") or 0) + 1
                last_event_id = state.get("last_sse_event_id")
            resumed = str(last_event_id) if last_event_id is not None and str(last_event_id) != "" else "-"
            router_log("WARN", f"channel_http_reconnect name={name} last_event_id={resumed} error={type(exc).__name__}: {exc}")
            time.sleep(retry_seconds)
    _channel_sse_worker(name)


def start_channel_sse_connection(config: dict[str, Any]) -> dict[str, Any]:
    url = str(config.get("url") or "").strip()
    if not url.startswith(("http://", "https://")):
        raise ValueError("SSE url must start with http:// or https://")
    name = _safe_segment(str(config.get("name") or urllib.parse.urlparse(url).netloc or "sse"), "sse")
    declared_transport = str(config.get("transport") or config.get("type") or "").strip().lower()
    transport = "streamable-http" if declared_transport in {"http", "streamable-http"} else "sse"
    headers = config.get("headers") if isinstance(config.get("headers"), dict) else {}
    headers = {str(k): str(v) for k, v in headers.items() if str(k).strip()}
    token = str(config.get("bearer_token") or config.get("token") or "").strip()
    if token and "Authorization" not in headers:
        headers["Authorization"] = f"Bearer {token}"
    event_filter = config.get("event_filter")
    if isinstance(event_filter, str):
        event_filter = [item.strip() for item in event_filter.split(",") if item.strip()]
    elif isinstance(event_filter, list):
        event_filter = [str(item).strip() for item in event_filter if str(item).strip()]
    else:
        event_filter = []
    with _CHANNEL_SSE_LOCK:
        prior = _CHANNEL_SSE_CONNECTIONS.get(name)
        if prior:
            prior["running"] = False
        state = {
            "name": name,
            "url": url,
            "headers": headers,
            "channel": str(config.get("channel") or "default"),
            "sender_id": str(config.get("sender_id") or config.get("sender") or name),
            "recipient": str(config.get("recipient") or config.get("recipient_id") or config.get("to") or "all"),
            "running": True,
            "started_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "last_event_at": None,
            "messages_received": 0,
            "last_error": None,
            "event_filter": event_filter,
            "last_sse_event_id": str(
                config.get("last_sse_event_id")
                or config.get("last_event_id")
                or config.get("lastEventId")
                or ""
            ).strip(),
            "sse_reconnects": 0,
            "read_timeout_seconds": float(config.get("read_timeout_seconds") or config.get("timeout") or 300.0),
            "retry_seconds": float(config.get("retry_seconds") or 5.0),
            "mcp_enabled": bool(config.get("mcp", config.get("mcp_enabled", True))),
            "mcp_endpoint": None,
            "mcp_initialized": False,
            "mcp_session_id": None,
            "mcp_last_error": None,
            "mcp_rpc_results": {},
            "transport": transport,
            "streamable_requires_session": parse_bool(
                config.get("streamable_requires_session", config.get("require_session", config.get("mcp_session_required", True))),
                True,
            ),
            "mcp_protocol_version": str(
                config.get("mcp_protocol_version")
                or (MCP_STREAMABLE_HTTP_PROTOCOL_VERSION if transport == "streamable-http" else MCP_LEGACY_SSE_PROTOCOL_VERSION)
            ),
            "mcp_timeout_seconds": float(config.get("mcp_timeout_seconds") or 20.0),
        }
        _CHANNEL_SSE_CONNECTIONS[name] = state
    worker = _channel_streamable_http_worker if transport == "streamable-http" else _channel_sse_worker
    thread = threading.Thread(target=worker, args=(name,), daemon=True, name=f"claude-any-channel-{transport}-{name}")
    thread.start()
    return _channel_sse_status_public(name, state)


def stop_channel_sse_connection(name: str | None = None) -> dict[str, Any]:
    stopped: list[str] = []
    with _CHANNEL_SSE_LOCK:
        targets = [name] if name else list(_CHANNEL_SSE_CONNECTIONS)
        for target in targets:
            if not target:
                continue
            state = _CHANNEL_SSE_CONNECTIONS.get(target)
            if state:
                state["running"] = False
                stopped.append(target)
    return {"stopped": stopped, "connections": channel_sse_status()}


def _channel_mcp_session_id() -> str:
    return f"s{os.getpid()}-{time.time_ns()}"


def _native_channel_meta_value(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, str):
        return value
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    return json.dumps(_json_safe_metadata(value), ensure_ascii=False, separators=(",", ":"), default=str)


def _native_channel_meta(message: dict[str, Any]) -> dict[str, str]:
    raw_meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    meta: dict[str, str] = {}
    for key, value in raw_meta.items():
        name = str(key or "").strip()
        if not name:
            continue
        meta[name] = _native_channel_meta_value(value)
    message_recipients = _as_string_list(message.get("recipients")) if message.get("recipients") is not None else None
    base = {
        "claude_any_message_id": message.get("id"),
        "channel": message.get("channel") or "default",
        "sender_id": message.get("sender_id") or "channel",
        "thread_id": message.get("thread_id"),
        "parent_id": message.get("parent_id"),
        "kind": message.get("kind"),
        "recipients": message_recipients,
    }
    for key, value in base.items():
        if value is not None:
            meta[key] = _native_channel_meta_value(value)
    if raw_meta:
        meta["claude_any_meta_json"] = json.dumps(_json_safe_metadata(raw_meta), ensure_ascii=False, separators=(",", ":"), default=str)
    return meta


def _native_channel_param_value(value: Any) -> Any:
    if value is None:
        return None
    if isinstance(value, (str, bool, int, float)):
        return value
    return _json_safe_metadata(value)


def _channel_mcp_notification(message: dict[str, Any]) -> dict[str, Any]:
    text = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
    channel = str(message.get("channel") or "default")
    sender = str(message.get("sender_id") or "channel")
    prefix = f"[{channel}] {sender}"
    content = f"{prefix}: {text}" if text else prefix
    raw_meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    params: dict[str, Any] = {
        "content": content,
        "message": text,
        "text": text,
        "channel": channel,
        "source": channel,
        "sender_id": sender,
        "meta": _native_channel_meta(message),
    }
    for key in ("id", "thread_id", "parent_id", "kind", "time", "recipients"):
        if message.get(key) is not None:
            value = _as_string_list(message.get(key)) if key == "recipients" else message.get(key)
            params[key] = _native_channel_param_value(value)
    for key in ("room_id", "room", "recipient_id", "recipient", "conversation_id", "dm_id"):
        value = raw_meta.get(key)
        if value is not None and key not in params:
            params[key] = _native_channel_param_value(value)
    if "room_id" not in params and channel:
        params["room_id"] = channel
    return {
        "jsonrpc": "2.0",
        "method": _NATIVE_CHANNEL_NOTIFICATION_METHOD,
        "params": params,
    }


def _channel_mcp_capabilities() -> dict[str, Any]:
    return {
        "tools": {"listChanged": False},
        "experimental": {
            "claude/channel": {},
        },
    }


def _write_sse_event(handler: BaseHTTPRequestHandler, event: str, data: Any, event_id: int | None = None) -> None:
    if event_id is not None:
        handler.wfile.write(f"id: {event_id}\n".encode("utf-8"))
    handler.wfile.write(f"event: {event}\n".encode("utf-8"))
    payload = data if isinstance(data, str) else json.dumps(data, ensure_ascii=False, separators=(",", ":"))
    for line in payload.splitlines() or [""]:
        handler.wfile.write(f"data: {line}\n".encode("utf-8"))
    handler.wfile.write(b"\n")
    handler.wfile.flush()


def _send_channel_mcp_sse_headers(handler: BaseHTTPRequestHandler) -> None:
    handler.send_response(200)
    handler.send_header("content-type", "text/event-stream")
    handler.send_header("cache-control", "no-cache, no-transform")
    handler.send_header("connection", "keep-alive")
    handler.send_header("x-accel-buffering", "no")
    handler.end_headers()


def _channel_mcp_enqueue(session: str, payload: dict[str, Any]) -> bool:
    if not session:
        return False
    with _CHANNEL_MCP_LOCK:
        state = _CHANNEL_MCP_SESSIONS.get(session)
        if not state:
            return False
        outbox = state.setdefault("outbox", [])
        if isinstance(outbox, list):
            outbox.append(payload)
        else:
            state["outbox"] = [payload]
    with _CHAT_CONDITION:
        _CHAT_CONDITION.notify_all()
    return True


def _channel_mcp_take_outbox(session: str) -> list[dict[str, Any]]:
    with _CHANNEL_MCP_LOCK:
        state = _CHANNEL_MCP_SESSIONS.get(session)
        if not state:
            return []
        outbox = state.get("outbox")
        if not isinstance(outbox, list) or not outbox:
            return []
        state["outbox"] = []
        return [item for item in outbox if isinstance(item, dict)]


def _channel_mcp_initialize_response(request_id: Any, protocol: str) -> dict[str, Any]:
    # This endpoint implements the legacy HTTP+SSE transport, whose stable
    # protocol version is 2024-11-05 even when newer clients initiate the
    # handshake with a newer preferred protocol.
    protocol = "2024-11-05"
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "protocolVersion": protocol,
            "capabilities": _channel_mcp_capabilities(),
            "serverInfo": {"name": "claude-any-router", "version": VERSION},
        },
    }


def _channel_mcp_tool_schemas() -> list[dict[str, Any]]:
    return [
        {
            "name": "send_message",
            "description": (
                "Send a reply or status message to a Claude Any channel. "
                "Use this to answer messages delivered through the Claude Any channel inbox, "
                "including /ca/web/chat browser sessions."
            ),
            "inputSchema": {
                "type": "object",
                "properties": {
                    "channel": {
                        "type": "string",
                        "description": "Destination channel id from the incoming message.",
                    },
                    "message": {
                        "type": "string",
                        "description": "Message body to send.",
                    },
                    "recipients": {
                        "description": "Recipient id, 'all', or an array of recipients. Use 'web' for /ca/web/chat replies.",
                    },
                    "thread_id": {
                        "type": "string",
                        "description": "Thread/conversation id to continue.",
                    },
                    "parent_id": {
                        "description": "Optional parent message id.",
                    },
                    "delivery": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Delivery targets. Use ['web'] for browser-only replies.",
                    },
                    "kind": {
                        "type": "string",
                        "description": "Optional message kind, for example 'reply' or 'status'.",
                    },
                },
                "required": ["channel", "message"],
            },
        },
        {
            "name": "send_file",
            "description": (
                "Send a file attachment to a Claude Any channel. "
                "Use this to return files to /ca/web/chat browser sessions. "
                "Provide either path for an existing local file, or content with encoding='text' or encoding='base64'."
            ),
            "inputSchema": {
                "type": "object",
                "properties": {
                    "channel": {
                        "type": "string",
                        "description": "Destination channel id from the incoming message.",
                    },
                    "path": {
                        "type": "string",
                        "description": "Optional local file path to attach.",
                    },
                    "content": {
                        "type": "string",
                        "description": "Optional inline file content when path is not used.",
                    },
                    "encoding": {
                        "type": "string",
                        "description": "Inline content encoding: text or base64.",
                    },
                    "name": {
                        "type": "string",
                        "description": "Display filename. Defaults to the source path basename or file.txt.",
                    },
                    "content_type": {
                        "type": "string",
                        "description": "Optional MIME type.",
                    },
                    "message": {
                        "type": "string",
                        "description": "Optional message body to show with the file link.",
                    },
                    "recipients": {
                        "description": "Recipient id, 'all', or an array of recipients. Use 'web' for /ca/web/chat replies.",
                    },
                    "thread_id": {
                        "type": "string",
                        "description": "Thread/conversation id to continue.",
                    },
                    "parent_id": {
                        "description": "Optional parent message id.",
                    },
                    "delivery": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Delivery targets. Use ['web'] for browser-only replies.",
                    },
                },
                "required": ["channel"],
            },
        },
        {
            "name": "get_messages",
            "description": "Read recent Claude Any channel messages for a channel/thread.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "after": {"type": "integer", "description": "Only return messages after this id."},
                    "channel": {"type": "string", "description": "Optional channel filter."},
                    "recipient": {"type": "string", "description": "Optional recipient visibility filter."},
                    "limit": {"type": "integer", "description": "Maximum number of messages to return."},
                },
            },
        },
    ]


def _channel_mcp_tool_response(request_id: Any, text: str, is_error: bool = False) -> dict[str, Any]:
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "content": [{"type": "text", "text": text}],
            "isError": bool(is_error),
        },
    }


def _channel_mcp_tool_call_response(request_id: Any, params: dict[str, Any]) -> dict[str, Any]:
    name = str(params.get("name") or "")
    args = params.get("arguments") if isinstance(params.get("arguments"), dict) else {}
    if name == "send_message":
        channel = str(args.get("channel") or "").strip()
        message = str(args.get("message") or args.get("text") or "").strip()
        if not channel or not message:
            return _channel_mcp_tool_response(request_id, "send_message requires channel and message.", True)
        meta = args.get("meta") if isinstance(args.get("meta"), dict) else {}
        saved = append_chat_message(
            {
                "channel": channel,
                "sender_id": args.get("sender_id") or "claude-code",
                "recipients": args.get("recipients", args.get("recipient_id", "web")),
                "thread_id": args.get("thread_id"),
                "parent_id": args.get("parent_id"),
                "kind": args.get("kind") or "reply",
                "message": message,
                "delivery": args.get("delivery", ["web"]),
                "meta": {"source": "claude-any-router-tool", **meta},
            }
        )
        return _channel_mcp_tool_response(
            request_id,
            json.dumps({"ok": True, "message": saved}, ensure_ascii=False, separators=(",", ":")),
        )
    if name == "send_file":
        channel = str(args.get("channel") or "").strip()
        if not channel:
            return _channel_mcp_tool_response(request_id, "send_file requires channel.", True)
        try:
            if args.get("path"):
                upload = store_chat_file_from_path(
                    args.get("path"),
                    str(args.get("name") or "").strip() or None,
                    str(args.get("content_type") or args.get("mime_type") or "").strip() or None,
                )
            else:
                inline_body = {
                    "name": str(args.get("name") or "file.txt"),
                    "encoding": str(args.get("encoding") or "text"),
                    "content": args.get("content", ""),
                    "content_type": str(args.get("content_type") or args.get("mime_type") or "text/plain"),
                }
                upload = store_chat_file_upload(inline_body)
        except FileNotFoundError as exc:
            return _channel_mcp_tool_response(request_id, str(exc), True)
        except OverflowError as exc:
            return _channel_mcp_tool_response(request_id, str(exc), True)
        except ValueError as exc:
            return _channel_mcp_tool_response(request_id, str(exc), True)
        meta = args.get("meta") if isinstance(args.get("meta"), dict) else {}
        uploads = [upload]
        saved = append_chat_message(
            {
                "channel": channel,
                "sender_id": args.get("sender_id") or "claude-code",
                "recipients": args.get("recipients", args.get("recipient_id", "web")),
                "thread_id": args.get("thread_id"),
                "parent_id": args.get("parent_id"),
                "kind": args.get("kind") or "file",
                "message": chat_file_message_text(str(args.get("message") or ""), uploads),
                "delivery": args.get("delivery", ["web"]),
                "meta": {"source": "claude-any-router-tool", "attachments": uploads, **meta},
            }
        )
        return _channel_mcp_tool_response(
            request_id,
            json.dumps({"ok": True, "file": upload, "message": saved}, ensure_ascii=False, separators=(",", ":")),
        )
    if name == "get_messages":
        try:
            after = int(args.get("after") or 0)
        except Exception:
            after = 0
        try:
            limit = max(1, min(100, int(args.get("limit") or 20)))
        except Exception:
            limit = 20
        messages = read_chat_messages(
            after,
            str(args.get("channel") or "") or None,
            str(args.get("recipient") or args.get("recipient_id") or "") or None,
            limit,
        )
        return _channel_mcp_tool_response(
            request_id,
            json.dumps({"ok": True, "messages": messages}, ensure_ascii=False, separators=(",", ":")),
        )
    return _channel_mcp_tool_response(request_id, f"Unknown claude-any-router tool: {name}", True)


def _channel_mcp_write_cursor_locked(last_id: int) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    tmp_path = CHANNEL_MCP_CURSOR_PATH.with_suffix(".json.tmp")
    tmp_path.write_text(json.dumps({"last_id": max(0, int(last_id))}, separators=(",", ":")) + "\n", encoding="utf-8")
    tmp_path.replace(CHANNEL_MCP_CURSOR_PATH)


def _channel_mcp_read_cursor_locked() -> int:
    global _CHANNEL_MCP_CURSOR_LAST_ID
    if _CHANNEL_MCP_CURSOR_LAST_ID is not None:
        return _CHANNEL_MCP_CURSOR_LAST_ID
    if CHANNEL_MCP_CURSOR_PATH.exists():
        try:
            data = json.loads(CHANNEL_MCP_CURSOR_PATH.read_text(encoding="utf-8"))
            _CHANNEL_MCP_CURSOR_LAST_ID = max(0, int(data.get("last_id") or 0))
            return _CHANNEL_MCP_CURSOR_LAST_ID
        except Exception as exc:
            router_log("WARN", f"channel_mcp_cursor_read_failed error={type(exc).__name__}: {exc}")
    _CHANNEL_MCP_CURSOR_LAST_ID = max(0, _chat_scan_max_id())
    try:
        _channel_mcp_write_cursor_locked(_CHANNEL_MCP_CURSOR_LAST_ID)
    except Exception as exc:
        router_log("WARN", f"channel_mcp_cursor_write_failed error={type(exc).__name__}: {exc}")
    return _CHANNEL_MCP_CURSOR_LAST_ID


def _channel_mcp_ensure_cursor_initialized() -> int:
    with _CHANNEL_MCP_CURSOR_LOCK:
        return _channel_mcp_read_cursor_locked()


def _channel_mcp_update_cursor(last_id: int) -> None:
    global _CHANNEL_MCP_CURSOR_LAST_ID
    if last_id < 0:
        return
    with _CHANNEL_MCP_CURSOR_LOCK:
        current = _channel_mcp_read_cursor_locked()
        if last_id <= current:
            return
        _CHANNEL_MCP_CURSOR_LAST_ID = int(last_id)
        try:
            _channel_mcp_write_cursor_locked(_CHANNEL_MCP_CURSOR_LAST_ID)
        except Exception as exc:
            router_log("WARN", f"channel_mcp_cursor_write_failed error={type(exc).__name__}: {exc}")


def _channel_mcp_parse_event_id(value: Any) -> int | None:
    text = str(value or "").strip()
    if not text:
        return None
    try:
        return max(0, int(text))
    except Exception:
        return None


def _channel_mcp_client_last_event_id(handler: BaseHTTPRequestHandler) -> int | None:
    try:
        event_id = _channel_mcp_parse_event_id(handler.headers.get("Last-Event-ID"))
        if event_id is not None:
            return event_id
    except Exception:
        pass
    try:
        params = _query_params(handler)
        for key in ("lastEventId", "last_event_id", "last_id"):
            event_id = _channel_mcp_parse_event_id(_first_param(params, key))
            if event_id is not None:
                return event_id
    except Exception:
        pass
    return None


def _channel_mcp_session_start_last_id(handler: BaseHTTPRequestHandler) -> int:
    cursor_last_id = _channel_mcp_ensure_cursor_initialized()
    client_last_id = _channel_mcp_client_last_event_id(handler)
    if client_last_id is None:
        return cursor_last_id
    if client_last_id > cursor_last_id:
        _channel_mcp_update_cursor(client_last_id)
    router_log("INFO", f"channel_mcp_resume client_last_id={client_last_id} cursor_last_id={cursor_last_id}")
    return client_last_id


def _channel_mcp_message_skip_reason(message: dict[str, Any]) -> str | None:
    visibility = str(message.get("visibility") or "user").strip().lower()
    if visibility in {"hidden", "internal", "transport", "control", "system"}:
        return f"visibility_{visibility}"
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    meta_kind = str(meta.get("kind") or meta.get("type") or meta.get("event_type") or meta.get("eventType") or meta.get("event") or meta.get("status") or "").strip().lower()
    if meta_kind in _CHANNEL_CONTROL_KINDS:
        return meta_kind
    if meta.get("llm_direct_pending"):
        return "llm_direct_pending"
    recipients = {item.strip().lower() for item in _as_string_list(message.get("recipients"))}
    if "internal" in recipients:
        return "recipient_internal"
    delivery = _as_string_list(message.get("delivery"))
    if delivery:
        normalized_delivery = {item.strip().lower() for item in delivery}
        if not ({"all", "*", "native", "mcp"} & normalized_delivery):
            return "delivery_not_native"
    return _channel_wake_message_noise_reason(message)


def _channel_mcp_notifications_for_messages(
    messages: list[dict[str, Any]],
    session: str = "",
) -> tuple[int, list[tuple[int, dict[str, Any]]]]:
    last_id = 0
    events: list[tuple[int, dict[str, Any]]] = []
    for message in messages:
        message_id = int(message.get("id") or 0)
        last_id = max(last_id, message_id)
        skip_reason = _channel_mcp_message_skip_reason(message)
        if skip_reason:
            router_log(
                "INFO",
                f"channel_mcp_skipped_noise session={session or '-'} message_id={message.get('id')} channel={message.get('channel')} reason={skip_reason}",
            )
            continue
        notification = _channel_mcp_notification(message)
        events.append((last_id, notification))
        params = notification.get("params") if isinstance(notification, dict) else {}
        room_id = params.get("room_id") if isinstance(params, dict) else None
        recipients = params.get("recipients") if isinstance(params, dict) else None
        router_log(
            "INFO",
            f"channel_mcp_notification_prepared session={session or '-'} message_id={message.get('id')} channel={message.get('channel')} room_id={room_id or '-'} recipients={_native_channel_meta_value(recipients)[:120] if recipients is not None else '-'}",
        )
    return last_id, events


def handle_channel_mcp_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
    if path == "/ca/mcp/health":
        write_json(handler, {"ok": True, "name": "claude-any-router", "sse": "/ca/mcp/sse"})
        return True
    if path != "/ca/mcp/sse":
        return False
    session = _channel_mcp_session_id()
    last_id = _channel_mcp_session_start_last_id(handler)
    with _CHANNEL_MCP_LOCK:
        _CHANNEL_MCP_SESSIONS[session] = {"created_at": time.time(), "last_id": last_id, "initialized": False, "outbox": []}
    router_log("INFO", f"channel_mcp_session_started session={session} last_id={last_id}")
    started_at = time.time()
    close_reason = "finished"
    _send_channel_mcp_sse_headers(handler)
    _write_sse_event(handler, "endpoint", f"/ca/mcp/messages?sessionId={urllib.parse.quote(session)}")
    try:
        while True:
            with _CHANNEL_MCP_LOCK:
                state = _CHANNEL_MCP_SESSIONS.get(session)
                if not state:
                    close_reason = "session_missing"
                    return True
                last_id = int(state.get("last_id") or 0)
                initialized = bool(state.get("initialized"))
            outbox = _channel_mcp_take_outbox(session)
            if outbox:
                for payload in outbox:
                    _write_sse_event(handler, "message", payload)
                router_log("INFO", f"channel_mcp_rpc_flushed session={session} count={len(outbox)}")
                continue
            if not initialized:
                handler.wfile.write(b": waiting-for-initialize\n\n")
                handler.wfile.flush()
                with _CHAT_CONDITION:
                    _CHAT_CONDITION.wait(timeout=1.0)
                continue
            messages = read_chat_messages(last_id, None, None, 100)
            if messages:
                delivered_last_id, events = _channel_mcp_notifications_for_messages(messages, session)
                last_id = max(last_id, delivered_last_id)
                for event_id, notification in events:
                    _write_sse_event(handler, "message", notification, event_id)
                    router_log("INFO", f"channel_mcp_notification_written session={session} message_id={event_id}")
                _channel_mcp_update_cursor(last_id)
                with _CHANNEL_MCP_LOCK:
                    state = _CHANNEL_MCP_SESSIONS.get(session)
                    if state:
                        state["last_id"] = last_id
                continue
            handler.wfile.write(b": keepalive\n\n")
            handler.wfile.flush()
            with _CHAT_CONDITION:
                _CHAT_CONDITION.wait(timeout=5.0)
    except (BrokenPipeError, ConnectionError, ConnectionResetError) as exc:
        close_reason = type(exc).__name__
        return True
    except Exception as exc:
        close_reason = type(exc).__name__
        router_log("ERROR", f"channel_mcp_session_failed session={session} error={type(exc).__name__}: {exc}")
        return True
    finally:
        router_log("INFO", f"channel_mcp_session_closed session={session} reason={close_reason} age={time.time() - started_at:.1f}s")
        with _CHANNEL_MCP_LOCK:
            _CHANNEL_MCP_SESSIONS.pop(session, None)


def handle_channel_mcp_post(handler: BaseHTTPRequestHandler, path: str, body: dict[str, Any]) -> bool:
    if path != "/ca/mcp/messages":
        return False
    params = _query_params(handler)
    session = _first_param(params, "sessionId") or _first_param(params, "session")
    with _CHANNEL_MCP_LOCK:
        if session and session in _CHANNEL_MCP_SESSIONS:
            _CHANNEL_MCP_SESSIONS[session]["last_seen_at"] = time.time()
    method = str(body.get("method") or "")
    request_id = body.get("id")
    response: dict[str, Any] | None = None
    if method == "initialize":
        protocol = "2024-11-05"
        req_params = body.get("params") if isinstance(body.get("params"), dict) else {}
        if req_params.get("protocolVersion"):
            protocol = str(req_params["protocolVersion"])
        with _CHANNEL_MCP_LOCK:
            if session and session in _CHANNEL_MCP_SESSIONS:
                _CHANNEL_MCP_SESSIONS[session]["initialized"] = True
        response = _channel_mcp_initialize_response(request_id, protocol)
        router_log("INFO", f"channel_mcp_initialized session={session or '-'} protocol={protocol}")
    elif method == "tools/list":
        response = {"jsonrpc": "2.0", "id": request_id, "result": {"tools": _channel_mcp_tool_schemas()}}
    elif method == "tools/call":
        params = body.get("params") if isinstance(body.get("params"), dict) else {}
        response = _channel_mcp_tool_call_response(request_id, params)
    elif method == "ping":
        response = {"jsonrpc": "2.0", "id": request_id, "result": {}}
    elif request_id is not None:
        response = {"jsonrpc": "2.0", "id": request_id, "result": {}}
    if response is not None:
        if not _channel_mcp_enqueue(session, response):
            router_log("WARN", f"channel_mcp_rpc_enqueue_failed session={session or '-'} method={method}")
            write_json(
                handler,
                {
                    "jsonrpc": "2.0",
                    "id": request_id,
                    "error": {"code": -32000, "message": "MCP SSE session is not connected"},
                },
                404,
            )
            return True
        router_log("INFO", f"channel_mcp_rpc_queued session={session or '-'} method={method} request_id={request_id}")
    write_accepted_response(handler)
    return True


def _query_params(handler: BaseHTTPRequestHandler) -> dict[str, list[str]]:
    return urllib.parse.parse_qs(urllib.parse.urlparse(handler.path).query, keep_blank_values=True)


def _first_param(params: dict[str, list[str]], name: str, default: str = "") -> str:
    values = params.get(name)
    return values[0] if values else default


def handle_chat_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
    channel_alias = path.startswith("/ca/channel/")
    if channel_alias:
        path = "/ca/chat/" + path[len("/ca/channel/"):]
    if path == "/ca/chat/health":
        write_json(
            handler,
            {
                "ok": True,
                "base": ROUTER_BASE,
                "messages": "/ca/channel/messages" if channel_alias else "/ca/chat/messages",
                "wait": "/ca/channel/wait" if channel_alias else "/ca/chat/wait",
                "stream": "/ca/channel/stream" if channel_alias else "/ca/chat/stream",
                "notify": "/ca/channel/notify",
                "sse_status": "/ca/channel/sse/status",
                "sse_connect": "POST /ca/channel/sse/connect",
                "sse_disconnect": "POST /ca/channel/sse/disconnect",
                "native_note": "This is the Claude Any bridge API, not Claude Code's gated native --channels path.",
            },
        )
        return True
    if path == "/ca/chat/sse/status":
        write_json(handler, {"ok": True, "connections": channel_sse_status()})
        return True
    if path in ("/ca/chat/messages", "/ca/chat/wait"):
        params = _query_params(handler)
        after = int(_first_param(params, "after", "0") or 0)
        before = int(_first_param(params, "before", "0") or 0)
        limit = max(1, min(500, int(_first_param(params, "limit", "100") or 100)))
        channel = _first_param(params, "channel", "") or None
        recipient = _first_param(params, "recipient", "") or _first_param(params, "recipient_id", "") or None
        latest = _first_param(params, "latest", "") or _first_param(params, "history", "")
        timeout = 0.0 if path.endswith("/messages") else max(0.0, min(300.0, float(_first_param(params, "timeout", "60") or 60)))
        deadline = time.time() + timeout
        history_mode = path.endswith("/messages") and (before > 0 or latest.lower() in {"1", "true", "yes", "on"})
        messages = read_chat_messages_before(before, channel, recipient, limit) if history_mode else read_chat_messages(after, channel, recipient, limit)
        while not messages and timeout > 0 and time.time() < deadline:
            with _CHAT_CONDITION:
                _CHAT_CONDITION.wait(timeout=min(5.0, max(0.0, deadline - time.time())))
            messages = read_chat_messages(after, channel, recipient, limit)
        write_json(
            handler,
            {
                "ok": True,
                "messages": messages,
                "last_id": messages[-1]["id"] if messages else after,
                "oldest_id": messages[0]["id"] if messages else None,
                "has_more": bool(messages and (before > 0 or len(messages) >= limit)),
            },
        )
        return True
    if path == "/ca/chat/stream":
        params = _query_params(handler)
        after = int(_first_param(params, "after", "0") or 0)
        channel = _first_param(params, "channel", "") or None
        recipient = _first_param(params, "recipient", "") or _first_param(params, "recipient_id", "") or None
        timeout = max(1.0, min(3600.0, float(_first_param(params, "timeout", "300") or 300)))
        handler.send_response(200)
        handler.send_header("content-type", "text/event-stream")
        handler.send_header("cache-control", "no-cache")
        handler.send_header("connection", "close")
        handler.end_headers()
        deadline = time.time() + timeout
        last_id = after
        try:
            while time.time() < deadline:
                messages = read_chat_messages(last_id, channel, recipient, 100)
                for message in messages:
                    last_id = int(message["id"])
                    handler.wfile.write(f"id: {last_id}\n".encode("utf-8"))
                    handler.wfile.write(b"event: message\n")
                    handler.wfile.write(("data: " + json.dumps(message, ensure_ascii=False, separators=(",", ":")) + "\n\n").encode("utf-8"))
                    handler.wfile.flush()
                if messages:
                    continue
                handler.wfile.write(b": wait\n\n")
                handler.wfile.flush()
                with _CHAT_CONDITION:
                    _CHAT_CONDITION.wait(timeout=min(15.0, max(0.0, deadline - time.time())))
        except (BrokenPipeError, ConnectionError):
            pass
        return True
    if path.startswith("/ca/chat/files/"):
        name = _safe_segment(urllib.parse.unquote(path[len("/ca/chat/files/"):]), "file")
        target = CHAT_FILES_DIR / name
        if not target.exists() or not target.is_file():
            write_json(handler, {"ok": False, "error": "not_found"}, 404)
            return True
        data = target.read_bytes()
        handler.send_response(200)
        handler.send_header("content-type", "application/octet-stream")
        handler.send_header("content-disposition", f"attachment; filename={json.dumps(name)}")
        handler.send_header("content-length", str(len(data)))
        handler.end_headers()
        handler.wfile.write(data)
        return True
    return False


def handle_chat_post(handler: BaseHTTPRequestHandler, path: str, body: dict[str, Any]) -> bool:
    channel_alias = path.startswith("/ca/channel/")
    if channel_alias:
        path = "/ca/chat/" + path[len("/ca/channel/"):]
    if path == "/ca/chat/sse/connect":
        try:
            status = start_channel_sse_connection(body)
            write_json(handler, {"ok": True, "connection": status})
        except Exception as exc:
            write_json(handler, {"ok": False, "error": str(exc)}, 400)
        return True
    if path == "/ca/chat/sse/disconnect":
        name = body.get("name")
        result = stop_channel_sse_connection(str(name) if name else None)
        write_json(handler, {"ok": True, **result})
        return True
    if path == "/ca/chat/notify":
        params = body.get("params") if isinstance(body.get("params"), dict) else {}
        meta = params.get("meta") if isinstance(params.get("meta"), dict) else body.get("meta")
        if not isinstance(meta, dict):
            meta = {}
        content = str(params.get("content") or body.get("content") or body.get("message") or body.get("text") or "")
        message = append_chat_message({
            "channel": body.get("channel") or meta.get("channel") or "default",
            "sender_id": body.get("sender_id") or body.get("sender") or body.get("server") or meta.get("source") or "channel",
            "recipients": body.get("recipients", body.get("recipient_id", meta.get("recipients", "all"))),
            "thread_id": body.get("thread_id") or meta.get("thread_id"),
            "parent_id": body.get("parent_id") or meta.get("parent_id"),
            "kind": body.get("kind") or "channel",
            "message": content,
            "meta": meta,
        })
        write_json(handler, {"ok": True, "message": message})
        return True
    if path == "/ca/chat/messages":
        message = append_chat_message(body)
        write_json(handler, {"ok": True, "message": message})
        return True
    if path == "/ca/chat/files":
        try:
            upload = store_chat_file_upload(body)
        except OverflowError as exc:
            write_json(handler, {"ok": False, "error": str(exc)}, 413)
            return True
        except ValueError as exc:
            write_json(handler, {"ok": False, "error": str(exc)}, 400)
            return True
        if body.get("announce", True):
            attachments = [upload]
            append_chat_message({
                "channel": body.get("channel", "default"),
                "sender_id": body.get("sender_id", "system"),
                "recipients": body.get("recipients", "all"),
                "thread_id": body.get("thread_id"),
                "parent_id": body.get("parent_id"),
                "kind": "file",
                "message": str(body.get("message") or upload["url"]),
                "meta": {"attachments": attachments, "name": upload["original_name"], "url": upload["url"]},
            })
        write_json(handler, {"ok": True, **upload})
        return True
    return False


def handle_plan_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
    if path == "/ca/plan/artifacts":
        PLAN_ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
        items = []
        for item in sorted(PLAN_ARTIFACTS_DIR.glob("*")):
            if item.is_file():
                items.append({"name": item.name, "bytes": item.stat().st_size, "url": f"{ROUTER_BASE}/ca/plan/artifacts/{urllib.parse.quote(item.name)}"})
        write_json(handler, {"ok": True, "artifacts": items})
        return True
    if path.startswith("/ca/plan/artifacts/"):
        name = _safe_segment(urllib.parse.unquote(path[len("/ca/plan/artifacts/"):]), "plan.md")
        target = PLAN_ARTIFACTS_DIR / name
        if not target.exists() or not target.is_file():
            write_json(handler, {"ok": False, "error": "not_found"}, 404)
            return True
        content_type = "text/markdown; charset=utf-8" if target.suffix.lower() in (".md", ".markdown") else "text/plain; charset=utf-8"
        write_text_response(handler, target.read_text(encoding="utf-8", errors="replace"), content_type=content_type)
        return True
    return False


def handle_plan_post(handler: BaseHTTPRequestHandler, path: str, body: dict[str, Any]) -> bool:
    if path != "/ca/plan/artifacts":
        return False
    PLAN_ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)
    title = str(body.get("title") or "plan")
    content = str(body.get("content") or body.get("message") or "")
    name = _safe_segment(str(body.get("name") or f"{int(time.time())}-{title}.md"), "plan.md")
    if "." not in name:
        name += ".md"
    target = PLAN_ARTIFACTS_DIR / name
    target.write_text(content, encoding="utf-8")
    latest = PLAN_ARTIFACTS_DIR / "latest.md"
    if target.name != latest.name:
        latest.write_text(content, encoding="utf-8")
    url = f"{ROUTER_BASE}/ca/plan/artifacts/{urllib.parse.quote(name)}"
    if body.get("announce", True):
        append_chat_message({
            "channel": body.get("channel", "plan"),
            "sender_id": body.get("sender_id", "plan"),
            "recipients": body.get("recipients", "all"),
            "kind": "plan",
            "message": url,
            "meta": {"title": title, "url": url, "name": name},
        })
    write_json(handler, {"ok": True, "name": name, "url": url, "latest_url": f"{ROUTER_BASE}/ca/plan/artifacts/latest.md"})
    return True


def estimate_tokens(body: Any, _cache: dict[int, int] | None = None) -> int:
    if _cache is not None:
        body_id = id(body)
        if body_id in _cache:
            return _cache[body_id]
    text = json.dumps(body, ensure_ascii=False)
    result = max(1, len(text) // 4)
    if _cache is not None:
        _cache[id(body)] = result
    return result


def anthropic_content_to_text(content: Any) -> str:
    if isinstance(content, str):
        return content
    if not isinstance(content, list):
        return str(content) if content is not None else ""
    parts: list[str] = []
    for block in content:
        if isinstance(block, str):
            parts.append(block)
            continue
        if not isinstance(block, dict):
            continue
        btype = block.get("type")
        if btype == "text":
            parts.append(str(block.get("text", "")))
        elif btype == "tool_result":
            tool_text = anthropic_content_to_text(block.get("content", ""))
            parts.append(f"Tool result for {block.get('tool_use_id', 'tool')}:\n{tool_text}")
    return "\n".join(part for part in parts if part)


PROMPT_TOOL_INPUT_FIELD_LIMIT = 1200
PROMPT_TOOL_RESULT_LIMIT = 12000
PROMPT_MESSAGE_TEXT_LIMIT = 20000
CLAUDE_CODE_PERSISTED_OUTPUT_MARKER = "<persisted-output>"


def truncate_for_prompt(text: str, limit: int) -> str:
    if len(text) <= limit:
        return text
    omitted = len(text) - limit
    return text[:limit] + f"\n...[truncated {omitted} chars]..."


def is_claude_code_persisted_output_text(text: str) -> bool:
    return CLAUDE_CODE_PERSISTED_OUTPUT_MARKER in str(text or "")


def compact_tool_value_for_prompt(value: Any, limit: int = PROMPT_TOOL_INPUT_FIELD_LIMIT) -> Any:
    if isinstance(value, str):
        return truncate_for_prompt(value, limit)
    if isinstance(value, list):
        return [compact_tool_value_for_prompt(item, limit) for item in value[:20]]
    if isinstance(value, dict):
        compact: dict[str, Any] = {}
        for key, item in value.items():
            if key in {"content", "old_string", "new_string", "command"} and isinstance(item, str):
                compact[key] = truncate_for_prompt(item, limit)
            else:
                compact[key] = compact_tool_value_for_prompt(item, limit)
        return compact
    return value


def tool_input_for_prompt(tool_input: Any) -> str:
    if not tool_input:
        return "{}"
    compact = compact_tool_value_for_prompt(tool_input)
    return json.dumps(compact, ensure_ascii=False, sort_keys=True)


def compact_message_text_for_prompt(text: str) -> str:
    return truncate_for_prompt(text, PROMPT_MESSAGE_TEXT_LIMIT)


def is_advisor_request(body: dict[str, Any]) -> bool:
    return "CLAUDE_ANY_ADVISOR_CALL" in latest_user_text(body)


def is_router_debug_request(body: dict[str, Any]) -> bool:
    return "CLAUDE_ANY_ROUTER_DEBUG_ACCESS" in latest_user_text(body)


def parse_channel_bridge_args(raw: str) -> tuple[str, dict[str, str]]:
    text = (raw or "").strip()
    if not text:
        return "status", {}
    try:
        parts = shlex.split(text)
    except Exception:
        parts = text.split()
    if not parts:
        return "status", {}
    command = parts[0].strip().lower()
    if command not in {"status", "poll", "wait", "send", "post", "sse"}:
        parts = ["status", *parts]
        command = "status"
    options: dict[str, str] = {}
    loose: list[str] = []
    for part in parts[1:]:
        if "=" in part:
            key, value = part.split("=", 1)
            options[key.strip().lower().replace("-", "_")] = value.strip()
        elif part:
            loose.append(part)
    if loose and "message" not in options and command in {"send", "post"}:
        options["message"] = " ".join(loose)
    return command, options


def format_channel_messages(messages: list[dict[str, Any]], after: int) -> str:
    if not messages:
        return f"No channel messages after id {after}."
    lines = [f"Channel bridge messages ({len(messages)}):"]
    for item in messages:
        recipients = item.get("recipients") or []
        if isinstance(recipients, list):
            recipient_text = ",".join(str(r) for r in recipients) or "all"
        else:
            recipient_text = str(recipients or "all")
        text = re.sub(r"\s+", " ", str(item.get("message") or "")).strip()
        if len(text) > 500:
            text = text[:500].rstrip() + "..."
        lines.append(
            f"- #{item.get('id')} [{item.get('channel')}] {item.get('sender_id')} -> {recipient_text}: {text}"
        )
    lines.append(f"Last id: {messages[-1].get('id')}")
    return "\n".join(lines)


def router_debug_value_from_body(body: dict[str, Any]) -> str:
    text = latest_user_text(body)
    marker = "CLAUDE_ANY_ROUTER_DEBUG_ACCESS"
    if marker not in text:
        return "status"
    tail = text.split(marker, 1)[1]
    for line in tail.splitlines():
        stripped = line.strip()
        if not stripped:
            continue
        if stripped.lower().startswith("value:"):
            value = stripped.split(":", 1)[1].strip()
            return value or "toggle"
    return tail.strip() or "toggle"


def advisor_focus_from_body(body: dict[str, Any]) -> str:
    text = latest_user_text(body)
    marker = "CLAUDE_ANY_ADVISOR_CALL"
    if marker not in text:
        return ""
    return text.split(marker, 1)[1].strip()


def advisor_model_enabled(pcfg: dict[str, Any]) -> str:
    return str(pcfg.get("advisor_model") or "").strip()


def advisor_provider_kind(provider: str) -> str:
    if provider == "anthropic":
        return "anthropic"
    if provider in ("ollama", "ollama-cloud"):
        return "ollama"
    if provider in ("lm-studio", "nvidia-hosted"):
        return "openai-compatible"
    return ""


def advisor_provider_supported(provider: str) -> bool:
    return bool(advisor_provider_kind(provider))


def anthropic_system_with_advisor(system: Any, extra_system_texts: list[str] | None = None) -> list[dict[str, Any]]:
    """Build the advisor request system blocks, keeping the session identity first.

    Anthropic rejects OAuth-authenticated requests whose first system block is
    not the original Claude Code identity block (HTTP 429 ``rate_limit_error``
    with message "Error"), so the inbound session's first system block stays
    first and verbatim; the advisor instruction rides behind it.
    """
    blocks: list[dict[str, Any]] = []
    original_text = ""
    if isinstance(system, str):
        clean = system.strip()
        if clean:
            blocks.append({"type": "text", "text": clean})
    elif isinstance(system, list) and system:
        first = system[0]
        if isinstance(first, dict) and str(first.get("type") or "") == "text" and str(first.get("text") or "").strip():
            blocks.append(dict(first))
            original_text = anthropic_content_to_text(system[1:]).strip()
        else:
            original_text = anthropic_content_to_text(system).strip()
    elif system:
        original_text = anthropic_content_to_text(system).strip()
    blocks.append({"type": "text", "text": ADVISOR_REVIEW_PROMPT})
    if original_text:
        blocks.append({"type": "text", "text": "Original session system context:\n" + original_text})
    for text in extra_system_texts or []:
        clean = str(text or "").strip()
        if clean:
            blocks.append({"type": "text", "text": "Additional system context from message history:\n" + clean})
    return blocks


def append_anthropic_system_texts(system: Any, extra_system_texts: list[str] | None = None) -> Any:
    extras = [str(text or "").strip() for text in (extra_system_texts or []) if str(text or "").strip()]
    if not extras:
        return system
    blocks: list[dict[str, Any]] = []
    if isinstance(system, list):
        blocks = [block for block in system if isinstance(block, dict)]
    elif isinstance(system, str):
        clean = system.strip()
        if clean:
            blocks.append({"type": "text", "text": clean})
    elif system:
        clean = anthropic_content_to_text(system).strip()
        if clean:
            blocks.append({"type": "text", "text": clean})
    for text in extras:
        blocks.append({"type": "text", "text": text})
    return blocks


def normalize_anthropic_system_role_messages(body: dict[str, Any]) -> dict[str, Any]:
    """Move non-standard ``messages[].role == "system"`` entries to top-level system.

    Claude Code can include runtime state as a system-role item in message
    history. Anthropic-compatible /v1/messages servers such as vLLM accept
    only user/assistant roles in ``messages`` and expect system context at the
    top level.
    """
    messages = body.get("messages")
    if not isinstance(messages, list):
        return body
    next_messages: list[Any] = []
    system_texts: list[str] = []
    changed = False
    for message in messages:
        if not isinstance(message, dict):
            next_messages.append(message)
            continue
        role = str(message.get("role") or "").strip()
        if role != "system":
            next_messages.append(message)
            continue
        changed = True
        text = anthropic_content_to_text(message.get("content")).strip()
        if text:
            system_texts.append(text)
    if not changed:
        return body
    out = dict(body)
    out["messages"] = next_messages
    out["system"] = append_anthropic_system_texts(body.get("system"), system_texts)
    return out


def anthropic_advisor_messages_and_system(body: dict[str, Any]) -> tuple[list[dict[str, Any]], list[str]]:
    messages: list[dict[str, Any]] = []
    system_texts: list[str] = []
    for message in body.get("messages", []) or []:
        if not isinstance(message, dict):
            continue
        role = str(message.get("role") or "").strip()
        if role == "system":
            text = anthropic_content_to_text(message.get("content")).strip()
            if text:
                system_texts.append(text)
            continue
        if role in ("user", "assistant"):
            messages.append(message)
            continue
        text = anthropic_content_to_text(message.get("content")).strip()
        if text:
            messages.append({
                "role": "user",
                "content": [{"type": "text", "text": f"[{role or 'unknown'} message]\n{text}"}],
            })
    return messages, system_texts


def advisor_tool_schema() -> dict[str, Any]:
    return {
        "name": "advisor",
        "description": (
            "Consult a second, stronger advisor model for an independent review before you make an important "
            "plan, architecture, debugging, or final-completion decision. Use this when additional scrutiny could "
            "catch gaps, risks, or better next steps."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "question": {
                    "type": "string",
                    "description": "The specific decision, plan, code path, or risk you want the advisor to review.",
                }
            },
            "required": ["question"],
        },
    }


def body_with_advisor_tool(body: dict[str, Any], pcfg: dict[str, Any]) -> dict[str, Any]:
    if not advisor_model_enabled(pcfg):
        return body
    tools = body.get("tools")
    if not isinstance(tools, list):
        tools = []
    if any(isinstance(tool, dict) and str(tool.get("name") or "") == "advisor" for tool in tools):
        return body
    out = dict(body)
    out["tools"] = [*tools, advisor_tool_schema()]
    return out


def is_claude_code_advisor_server_tool(tool: Any) -> bool:
    if not isinstance(tool, dict):
        return False
    name = str(tool.get("name") or "")
    tool_type = str(tool.get("type") or "")
    return name == "advisor" and tool_type.startswith("advisor_")


def strip_autonomous_advisor_server_tools(provider: str, body: dict[str, Any]) -> dict[str, Any]:
    """Remove Claude Code's advisor server tool for non-Anthropic backends.

    The ``advisor_...`` server tool is executed by the Anthropic API; other
    backends cannot run it, so leaving it exposed on routed turns produces
    ``advisor_tool_result`` / ``too_many_requests`` errors. The anthropic
    provider passes through untouched: Claude Code's built-in /advisor flow
    (model picker + server tool) is the standard process in Claude native and
    Anthropic routed modes and must keep working.
    """
    if provider == "anthropic":
        return body
    if is_advisor_request(body) or body_has_advisor_feedback(body):
        return body
    tools = body.get("tools")
    if not isinstance(tools, list) or not tools:
        return body
    kept = [tool for tool in tools if not is_claude_code_advisor_server_tool(tool)]
    removed = len(tools) - len(kept)
    if not removed:
        return body
    out = dict(body)
    out["tools"] = kept
    router_log("INFO", f"stripped autonomous advisor server tool count={removed}")
    return out


def advisor_tool_focus_from_message(message: dict[str, Any]) -> str | None:
    content = message.get("content")
    if not isinstance(content, list):
        return None
    for block in content:
        if not isinstance(block, dict) or block.get("type") != "tool_use":
            continue
        if str(block.get("name") or "") != "advisor":
            continue
        tool_input = block.get("input")
        if isinstance(tool_input, dict):
            for key in ("question", "prompt", "focus", "task"):
                value = tool_input.get(key)
                if isinstance(value, str) and value.strip():
                    return value.strip()
        return "advisor tool call"
    return None


def tool_review_context_from_message(message: dict[str, Any], trigger: str) -> str:
    content = message.get("content")
    if not isinstance(content, list):
        return ""
    sections: list[str] = []
    for block in content:
        if not isinstance(block, dict) or block.get("type") != "tool_use":
            continue
        name = str(block.get("name") or "").strip()
        if not name:
            continue
        if name == "advisor":
            continue
        tool_input = block.get("input")
        input_text = tool_input_for_prompt(tool_input)
        if name == "ExitPlanMode":
            sections.append(
                "Review target: ExitPlanMode plan before user approval.\n"
                "The executor is about to submit this plan. Review the actual plan/tool input below.\n"
                f"Tool input:\n{input_text}"
            )
        elif trigger:
            sections.append(f"Review target tool: {name} ({trigger}).\nTool input:\n{input_text}")
    return "\n\n".join(sections)


def advisor_focus_for_message(message: dict[str, Any], trigger: str | None) -> tuple[str | None, str | None]:
    explicit_focus = advisor_tool_focus_from_message(message)
    if explicit_focus:
        return "advisor tool call", explicit_focus
    if not trigger:
        return None, None
    review_context = tool_review_context_from_message(message, trigger)
    if review_context:
        return trigger, review_context
    return trigger, trigger


def assistant_tool_call_summary_for_prompt(message: dict[str, Any]) -> str:
    content = message.get("content")
    if not isinstance(content, list):
        return ""
    sections: list[str] = []
    for block in content:
        if not isinstance(block, dict) or block.get("type") != "tool_use":
            continue
        name = str(block.get("name") or "tool").strip() or "tool"
        if name == "advisor":
            continue
        sections.append(f"Pending Claude Code tool call: {name}\nTool input:\n{tool_input_for_prompt(block.get('input'))}")
    return "\n\n".join(sections)


def body_has_advisor_feedback(body: dict[str, Any]) -> bool:
    for message in reversed(body.get("messages") or []):
        if not isinstance(message, dict):
            continue
        text = anthropic_content_to_text(message.get("content"))
        if ADVISOR_FEEDBACK_MARKER in text:
            return True
    return False


def anthropic_message_tool_names(message: dict[str, Any]) -> list[str]:
    names: list[str] = []
    content = message.get("content")
    if not isinstance(content, list):
        return names
    for block in content:
        if isinstance(block, dict) and block.get("type") == "tool_use":
            name = block.get("name")
            if isinstance(name, str) and name:
                names.append(name)
    return names


def advisor_trigger_for_message(body: dict[str, Any], message: dict[str, Any]) -> str | None:
    names = set(anthropic_message_tool_names(message))
    if "ExitPlanMode" in names:
        return "before ExitPlanMode plan approval"
    if names:
        return None
    text = anthropic_content_to_text(message.get("content")).strip()
    if (
        text
        and message.get("stop_reason") == "end_turn"
        and latest_tool_result_indicates_completed_work(body)
        and not non_actionable_short_response(text)
    ):
        return "before completion/final response"
    return None


def advisor_gate_possible_for_body(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> bool:
    return bool(advisor_gate_reason_for_body(provider, pcfg, body))


def advisor_gate_reason_for_body(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> str:
    if not advisor_model_enabled(pcfg) or not advisor_provider_supported(provider):
        return ""
    if is_advisor_request(body) or body_has_advisor_feedback(body):
        return ""
    if plan_mode_active(body):
        return "plan_mode"
    if latest_tool_result_indicates_completed_work(body):
        return "completed_work"
    return ""


def anthropic_system_to_ollama_messages(system: Any) -> list[dict[str, Any]]:
    if not system:
        return []
    if isinstance(system, str):
        text = system
    else:
        text = anthropic_content_to_text(system)
    return [{"role": "system", "content": text}] if text else []


def ollama_claude_code_reminder() -> dict[str, str]:
    return {
        "role": "system",
        "content": (
            "Claude Code execution reminder: when the user asks to create, edit, or run code, "
            "use the available tools such as Write, Edit, Read, and Bash. Do not stop after saying "
            "you will run something. Do not present a code block as a substitute for creating the file "
            "unless the user explicitly asks for code only. Exception: while Claude Code is in Plan Mode, "
            "do not implement normal project files; explore as needed, write or update the plan file, "
            "then call ExitPlanMode when the plan is ready for approval."
        ),
    }


READ_UNCHANGED_RESULT_RE = re.compile(
    r"(wasted call|unchanged since (?:your )?last read|file unchanged since (?:your )?last read)",
    re.IGNORECASE,
)

def message_attachment(message: dict[str, Any]) -> dict[str, Any] | None:
    attachment = message.get("attachment")
    return attachment if isinstance(attachment, dict) else None


def is_attachment_only_message(message: dict[str, Any]) -> bool:
    if not message_attachment(message):
        return False
    content = message.get("content")
    if content is None:
        return True
    blocks = _message_content_blocks(message)
    if any(isinstance(block, dict) and block.get("type") in {"tool_use", "tool_result"} for block in blocks):
        return False
    return not anthropic_content_to_text(content).strip()


def latest_plan_attachment(body: dict[str, Any]) -> dict[str, Any] | None:
    latest: dict[str, Any] | None = None
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        attachment = message_attachment(message)
        if not attachment:
            continue
        if attachment.get("type") in {"plan_mode", "plan_mode_reentry", "plan_mode_exit"}:
            latest = attachment
    return latest


def plan_file_written_in_body(body: dict[str, Any], plan_file_path: str) -> bool:
    if not plan_file_path:
        return False
    tool_names_by_id: dict[str, str] = {}
    tool_inputs_by_id: dict[str, Any] = {}
    for message in body.get("messages") or []:
        if not isinstance(message, dict):
            continue
        content = _message_content_blocks(message)
        if message.get("role") == "assistant":
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_use":
                    continue
                tool_id = str(block.get("id") or "")
                if not tool_id:
                    continue
                tool_names_by_id[tool_id] = str(block.get("name") or "")
                tool_inputs_by_id[tool_id] = block.get("input") if isinstance(block.get("input"), dict) else {}
        elif message.get("role") == "user":
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_result":
                    continue
                tool_use_id = str(block.get("tool_use_id") or "")
                if tool_names_by_id.get(tool_use_id) not in {"Write", "Edit", "MultiEdit"}:
                    continue
                tool_input = tool_inputs_by_id.get(tool_use_id)
                if not isinstance(tool_input, dict):
                    continue
                if str(tool_input.get("file_path") or "") == plan_file_path and not block.get("is_error"):
                    return True
    return False


def claude_code_state_messages(body: dict[str, Any]) -> list[dict[str, str]]:
    messages: list[dict[str, str]] = []
    plan_attachment = latest_plan_attachment(body)
    if plan_mode_active(body):
        plan_file_path = ""
        plan_exists = None
        if plan_attachment:
            plan_file_path = str(plan_attachment.get("planFilePath") or "")
            plan_exists = plan_attachment.get("planExists")
        plan_written = plan_file_written_in_body(body, plan_file_path)
        parts = [
            "Claude Code state: Plan Mode is active.",
            "In Plan Mode, exploration tools are allowed, but implementation/editing of normal project files must wait until ExitPlanMode approval.",
        ]
        if plan_file_path:
            parts.append(f"Plan file path: {plan_file_path}.")
            parts.append(f"Plan file written or updated in this conversation: {'yes' if plan_written else 'no'}.")
        if plan_exists is not None:
            parts.append(f"Claude Code attachment planExists: {bool(plan_exists)}.")
        parts.append(
            "When the plan file is complete and no new information is needed, call ExitPlanMode with the plan instead of repeating unchanged Read calls."
        )
        messages.append({"role": "system", "content": "\n".join(parts)})
    return messages


def canonical_tool_signature(tool_name: str, tool_input: Any) -> str:
    normalized = tool_input if isinstance(tool_input, dict) else {}
    return f"{tool_name}:{json.dumps(normalized, ensure_ascii=False, sort_keys=True, separators=(',', ':'))}"


def is_read_unchanged_result(tool_name: str, result_text: str) -> bool:
    return tool_name == "Read" and bool(READ_UNCHANGED_RESULT_RE.search(result_text or ""))


def upstream_relevant_message(message: dict[str, Any]) -> bool:
    if is_attachment_only_message(message) or should_skip_upstream_message(message):
        return False
    role = message.get("role")
    if role not in {"assistant", "user", "system", "tool"}:
        return False
    blocks = _message_content_blocks(message)
    if any(isinstance(block, dict) and block.get("type") in {"tool_use", "tool_result"} for block in blocks):
        return True
    return bool(anthropic_content_to_text(message.get("content", "")).strip())


def collect_tool_result_context(body: dict[str, Any]) -> tuple[dict[str, str], set[str]]:
    tool_names_by_id: dict[str, str] = {}
    tool_inputs_by_id: dict[str, Any] = {}
    successful_results: dict[str, str] = {}
    tool_result_records: list[tuple[int, str, str, Any, str, bool]] = []
    latest_relevant_message_index = -1
    for message_index, message in enumerate(body.get("messages") or []):
        if not isinstance(message, dict):
            continue
        if upstream_relevant_message(message):
            latest_relevant_message_index = message_index
        content = _message_content_blocks(message)
        if message.get("role") == "assistant":
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_use":
                    continue
                tool_id = str(block.get("id") or "")
                if not tool_id:
                    continue
                tool_names_by_id[tool_id] = str(block.get("name") or "")
                tool_inputs_by_id[tool_id] = block.get("input") if isinstance(block.get("input"), dict) else {}
        elif message.get("role") == "user":
            for block in content:
                if not isinstance(block, dict) or block.get("type") != "tool_result":
                    continue
                tool_use_id = str(block.get("tool_use_id") or "")
                tool_name = tool_names_by_id.get(tool_use_id, "tool")
                tool_input = tool_inputs_by_id.get(tool_use_id) or {}
                result_text = anthropic_content_to_text(block.get("content", ""))
                signature = canonical_tool_signature(tool_name, tool_input)
                is_error = bool(block.get("is_error"))
                tool_result_records.append((message_index, tool_use_id, tool_name, tool_input, result_text, is_error))
                if (
                    not is_claude_code_persisted_output_text(result_text)
                    and not is_read_unchanged_result(tool_name, result_text)
                    and not is_error
                    and result_text.strip()
                ):
                    successful_results[signature] = result_text
    current_read_noop_ids = {
        tool_use_id
        for message_index, tool_use_id, tool_name, _tool_input, result_text, _is_error in tool_result_records
        if message_index == latest_relevant_message_index and is_read_unchanged_result(tool_name, result_text)
    }
    return successful_results, current_read_noop_ids


def format_tool_result_for_upstream(
    tool_name: str,
    tool_input_text: str,
    result_text: str,
    is_error: bool,
    prior_success_text: str = "",
    include_prior_success: bool = False,
    in_plan_mode: bool = False,
) -> tuple[str, str]:
    if is_error:
        tool_text = (
            f"Tool `{tool_name}` failed.\n"
            f"Input:\n{tool_input_text}\n\n"
            f"Error:\n{result_text}"
        )
        tool_summary = (
            f"The `{tool_name}` tool call above failed. Its input was {tool_input_text}. "
            f"Use the error output to choose a different next step; do not blindly repeat it."
        )
        return tool_text, tool_summary

    if is_read_unchanged_result(tool_name, result_text):
        if not include_prior_success:
            tool_text = (
                "Tool `Read` returned an unchanged/no-op cache result for content that was already "
                "available earlier in this conversation. No new file content was produced by this "
                "historical duplicate observation."
            )
            return tool_text, ""
        previous = ""
        if prior_success_text:
            previous = (
                "\n\nPrevious successful Read result for this exact input remains authoritative:\n"
                f"{truncate_for_prompt(prior_success_text, PROMPT_TOOL_RESULT_LIMIT)}"
            )
        plan_hint = (
            " If you are in Plan Mode and the plan file is already complete, call ExitPlanMode."
            if in_plan_mode
            else ""
        )
        if not prior_success_text:
            tool_text = (
                "Tool `Read` returned a no-op unchanged result.\n"
                f"Input:\n{tool_input_text}\n\n"
                f"Result:\n{result_text}\n\n"
                "No previous successful Read result for this exact input is available in the converted context. "
                "Do not repeat the same Read. If the content is still needed, read a different or broader range once; "
                "otherwise proceed with the next distinct step."
            )
            tool_summary = (
                "The latest `Read` result says the file/range is unchanged, but no prior exact Read content is available "
                "in this converted context. Do not loop on the same Read; either read a different range once or continue "
                f"with the next distinct step.{plan_hint}"
            )
            return tool_text, tool_summary
        tool_text = (
            "Tool `Read` returned a no-op unchanged result.\n"
            f"Input:\n{tool_input_text}\n\n"
            f"Result:\n{result_text}"
            f"{previous}\n\n"
            "This is not new file content. It means the previous successful Read result for the same input is still current."
        )
        tool_summary = (
            "The latest `Read` result is a Claude Code no-op/cache result: no file content changed and no new observation was produced. "
            "Use the previous successful Read result for this exact input as the current observation, then choose the next distinct step."
            f"{plan_hint}"
        )
        return tool_text, tool_summary

    tool_text = (
        f"Tool `{tool_name}` completed successfully.\n"
        f"Input:\n{tool_input_text}\n\n"
        f"Result:\n{result_text}\n\n"
        f"If this result satisfies the user's request, provide the final answer now. "
        f"Do not call `{tool_name}` again with the same arguments."
    )
    tool_summary = (
        f"The `{tool_name}` tool call above already completed successfully. "
        f"Treat its tool output as authoritative. Do not repeat the same or equivalent "
        f"`{tool_name}` call; continue with the next required concrete tool call or final answer."
    )
    return tool_text, tool_summary


def should_skip_upstream_message(message: dict[str, Any]) -> bool:
    if is_claude_code_transcript_event(message):
        return True
    role = message.get("role")
    content = message.get("content", "")
    if role == "user" and message.get("isMeta") is True:
        return True
    text = anthropic_content_to_text(content).strip()
    if is_guard_feedback_text(text):
        return True
    # Router diagnostics must never be fed back to the upstream model. In Claude
    # Code they can also appear in the prompt input after a malformed/empty turn.
    router_diagnostics = (
        "Upstream returned an empty stream.",
        "Upstream returned no stream data.",
    )
    if role in ("assistant", "user") and (
        text in router_diagnostics or text.startswith("Upstream stream error:")
    ):
        return True
    if role == "assistant" and text == "No response requested.":
        return True
    return False


def anthropic_messages_to_ollama(body: dict[str, Any]) -> list[dict[str, Any]]:
    messages = anthropic_system_to_ollama_messages(body.get("system"))
    messages.append(ollama_claude_code_reminder())
    messages.extend(claude_code_state_messages(body))
    prior_tool_results, latest_noop_result_ids = collect_tool_result_context(body)
    in_plan_mode = plan_mode_active(body)
    tool_names_by_id: dict[str, str] = {}
    tool_inputs_by_id: dict[str, Any] = {}
    for message in body.get("messages", []) or []:
        if not isinstance(message, dict):
            continue
        if is_attachment_only_message(message):
            continue
        if should_skip_upstream_message(message):
            continue
        role = message.get("role", "user")
        content = message.get("content", "")

        if role == "user" and isinstance(content, list):
            text_blocks: list[Any] = []
            tool_blocks: list[dict[str, Any]] = []
            for block in content:
                if isinstance(block, dict) and block.get("type") == "tool_result":
                    tool_blocks.append(block)
                else:
                    text_blocks.append(block)
            text = anthropic_content_to_text(text_blocks)
            if text:
                messages.append({"role": "user", "content": compact_message_text_for_prompt(text)})
            for block in tool_blocks:
                tool_use_id = str(block.get("tool_use_id") or "")
                tool_name = tool_names_by_id.get(tool_use_id, "tool")
                tool_input = tool_inputs_by_id.get(tool_use_id)
                tool_input_text = tool_input_for_prompt(tool_input)
                raw_result_text = anthropic_content_to_text(block.get("content", ""))
                if is_claude_code_persisted_output_text(raw_result_text):
                    result_text = raw_result_text
                    tool_text = raw_result_text
                    tool_summary = ""
                else:
                    result_text = truncate_for_prompt(raw_result_text, PROMPT_TOOL_RESULT_LIMIT)
                    signature = canonical_tool_signature(tool_name, tool_input)
                    tool_text, tool_summary = format_tool_result_for_upstream(
                        tool_name=tool_name,
                        tool_input_text=tool_input_text,
                        result_text=result_text,
                        is_error=bool(block.get("is_error")),
                        prior_success_text=prior_tool_results.get(signature, ""),
                        include_prior_success=tool_use_id in latest_noop_result_ids,
                        in_plan_mode=in_plan_mode,
                    )
                    if not block.get("is_error"):
                        if tool_name == "TaskList":
                            tool_summary = (
                                f"The task list is current:\n{result_text}\n\n"
                                "If any task is in_progress and the user's request is not finished, your next response "
                                "must call a concrete work tool such as Write, Edit, Read, or Bash. Do not respond with "
                                "another progress announcement like 'I will write the files now'. If everything is "
                                "actually complete, provide the final answer."
                            )
                messages.append({"role": "tool", "tool_name": tool_name, "content": tool_text})
                if tool_summary:
                    messages.append({"role": "user", "content": tool_summary})
            continue

        text = anthropic_content_to_text(content)
        out: dict[str, Any] = {"role": role, "content": compact_message_text_for_prompt(text)}
        if role == "assistant" and isinstance(content, list):
            calls = []
            for block in content:
                if isinstance(block, dict) and block.get("type") == "tool_use":
                    name = str(block.get("name") or "tool")
                    tool_id = str(block.get("id") or "")
                    if tool_id:
                        tool_names_by_id[tool_id] = name
                        tool_inputs_by_id[tool_id] = block.get("input") or {}
                    calls.append({"function": {"name": name, "arguments": block.get("input") or {}}})
            if calls:
                out["tool_calls"] = calls
        messages.append(out)
    return messages


def anthropic_tools_to_ollama(tools: Any) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    if not isinstance(tools, list):
        return out
    for tool in tools:
        if not isinstance(tool, dict) or not tool.get("name"):
            continue
        out.append(
            {
                "type": "function",
                "function": {
                    "name": tool["name"],
                    "description": tool.get("description", ""),
                    "parameters": tool.get("input_schema") or {"type": "object", "properties": {}},
                },
            }
        )
    return out


def anthropic_messages_to_openai(body: dict[str, Any], reasoning_passback: bool = False) -> list[dict[str, Any]]:
    messages = anthropic_system_to_ollama_messages(body.get("system"))
    messages.append(ollama_claude_code_reminder())
    messages.extend(claude_code_state_messages(body))
    prior_tool_results, latest_noop_result_ids = collect_tool_result_context(body)
    in_plan_mode = plan_mode_active(body)
    tool_names_by_id: dict[str, str] = {}
    tool_inputs_by_id: dict[str, Any] = {}
    for message in body.get("messages", []) or []:
        if not isinstance(message, dict):
            continue
        if is_attachment_only_message(message):
            continue
        if should_skip_upstream_message(message):
            continue
        role = message.get("role", "user")
        content = message.get("content", "")
        if role == "assistant" and isinstance(content, list):
            text_blocks: list[Any] = []
            tool_calls: list[dict[str, Any]] = []
            reasoning_seen = False
            reasoning_parts: list[str] = []
            for block in content:
                if isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES:
                    reasoning_seen = True
                    if block.get("type") == "thinking":
                        reasoning_parts.append(str(block.get("thinking") or ""))
                    continue
                if isinstance(block, dict) and block.get("type") == "tool_use":
                    tool_id = str(block.get("id") or f"call_{len(tool_calls) + 1}")
                    name = str(block.get("name") or "tool")
                    tool_input = block.get("input") if isinstance(block.get("input"), dict) else {}
                    if tool_id:
                        tool_names_by_id[tool_id] = name
                        tool_inputs_by_id[tool_id] = tool_input
                    tool_calls.append({
                        "id": tool_id,
                        "type": "function",
                        "function": {
                            "name": name,
                            "arguments": json.dumps(tool_input, ensure_ascii=False),
                        },
                    })
                else:
                    text_blocks.append(block)
            out: dict[str, Any] = {"role": "assistant", "content": compact_message_text_for_prompt(anthropic_content_to_text(text_blocks))}
            if reasoning_seen or reasoning_passback:
                out["reasoning_content"] = "\n".join(reasoning_parts)
            if tool_calls:
                out["tool_calls"] = tool_calls
            messages.append(out)
            continue
        if role == "user" and isinstance(content, list):
            text_blocks = []
            for block in content:
                if isinstance(block, dict) and block.get("type") == "tool_result":
                    tool_id = str(block.get("tool_use_id") or "call_tool")
                    if tool_id not in tool_names_by_id:
                        raw_result_text = anthropic_content_to_text(block.get("content", ""))
                        result_text = (
                            raw_result_text
                            if is_claude_code_persisted_output_text(raw_result_text)
                            else truncate_for_prompt(raw_result_text, PROMPT_TOOL_RESULT_LIMIT)
                        )
                        if is_claude_code_persisted_output_text(result_text):
                            text_blocks.append({"type": "text", "text": result_text})
                            continue
                        text_blocks.append({
                            "type": "text",
                            "text": (
                                f"Historical tool result without a retained assistant tool call ({tool_id}):\n"
                                f"{result_text}"
                            ),
                        })
                        continue
                    tool_name = tool_names_by_id.get(tool_id, "tool")
                    tool_input = tool_inputs_by_id.get(tool_id)
                    tool_input_text = tool_input_for_prompt(tool_input)
                    raw_result_text = anthropic_content_to_text(block.get("content", ""))
                    if is_claude_code_persisted_output_text(raw_result_text):
                        tool_text = raw_result_text
                    else:
                        result_text = truncate_for_prompt(raw_result_text, PROMPT_TOOL_RESULT_LIMIT)
                        signature = canonical_tool_signature(tool_name, tool_input)
                        tool_text, _ = format_tool_result_for_upstream(
                            tool_name=tool_name,
                            tool_input_text=tool_input_text,
                            result_text=result_text,
                            is_error=bool(block.get("is_error")),
                            prior_success_text=prior_tool_results.get(signature, ""),
                            include_prior_success=tool_id in latest_noop_result_ids,
                            in_plan_mode=in_plan_mode,
                        )
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_id,
                        "id": tool_id,
                        "content": tool_text,
                    })
                else:
                    text_blocks.append(block)
            text = anthropic_content_to_text(text_blocks)
            if text:
                messages.append({"role": "user", "content": compact_message_text_for_prompt(text)})
            continue
        out = {"role": role, "content": compact_message_text_for_prompt(anthropic_content_to_text(content))}
        if role == "assistant" and reasoning_passback:
            out["reasoning_content"] = ""
        messages.append(out)
    return messages


def missing_openai_tool_result_message(tool_call: dict[str, Any]) -> dict[str, Any]:
    tool_id = str(tool_call.get("id") or "call_tool")
    fn = tool_call.get("function") if isinstance(tool_call.get("function"), dict) else {}
    name = str(fn.get("name") or "tool")
    return {
        "role": "tool",
        "tool_call_id": tool_id,
        "id": tool_id,
        "content": (
            f"Tool result for historical tool call `{name}` was not present in the retained "
            "Claude Code transcript. Treat this as missing historical context, not as a "
            "successful tool execution."
        ),
    }


def orphan_openai_tool_message_to_user(message: dict[str, Any]) -> dict[str, str]:
    tool_id = str(message.get("tool_call_id") or message.get("id") or "unknown")
    content = str(message.get("content") or "")
    return {
        "role": "user",
        "content": (
            f"Historical tool message without a retained assistant tool call ({tool_id}):\n"
            f"{content}"
        ),
    }


def repair_openai_tool_call_adjacency(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Make Anthropic/Claude historical tool turns valid for OpenAI chat APIs.

    OpenAI-compatible providers reject any assistant message with tool_calls unless
    the immediately following messages contain a tool response for every
    tool_call_id. Claude Code can send compacted long histories where an old
    assistant tool_use survives but its tool_result was dropped from the retained
    transcript. Anthropic tolerates that historical shape better than OpenAI chat
    does, so we repair only the wire-format sequence here.
    """
    repaired: list[dict[str, Any]] = []
    missing_count = 0
    orphan_count = 0
    i = 0
    while i < len(messages):
        message = messages[i]
        tool_calls = message.get("tool_calls") if isinstance(message, dict) else None
        if not (message.get("role") == "assistant" and isinstance(tool_calls, list) and tool_calls):
            if message.get("role") == "tool":
                repaired.append(orphan_openai_tool_message_to_user(message))
                orphan_count += 1
            else:
                repaired.append(message)
            i += 1
            continue

        repaired.append(message)
        i += 1

        immediate_tools: list[dict[str, Any]] = []
        while i < len(messages) and isinstance(messages[i], dict) and messages[i].get("role") == "tool":
            immediate_tools.append(messages[i])
            i += 1

        by_id: dict[str, list[dict[str, Any]]] = {}
        for tool_message in immediate_tools:
            tool_id = str(tool_message.get("tool_call_id") or tool_message.get("id") or "")
            by_id.setdefault(tool_id, []).append(tool_message)

        required_ids: list[str] = []
        for call in tool_calls:
            if not isinstance(call, dict):
                continue
            tool_id = str(call.get("id") or "")
            if not tool_id:
                continue
            required_ids.append(tool_id)
            matches = by_id.get(tool_id) or []
            if matches:
                repaired.append(matches.pop(0))
            else:
                repaired.append(missing_openai_tool_result_message(call))
                missing_count += 1

        required = set(required_ids)
        for tool_message in immediate_tools:
            tool_id = str(tool_message.get("tool_call_id") or tool_message.get("id") or "")
            if tool_id in required:
                remaining = by_id.get(tool_id) or []
                if tool_message in remaining:
                    remaining.remove(tool_message)
                    repaired.append(orphan_openai_tool_message_to_user(tool_message))
                    orphan_count += 1
            else:
                repaired.append(orphan_openai_tool_message_to_user(tool_message))
                orphan_count += 1

    if missing_count or orphan_count:
        router_log(
            "WARN",
            f"openai_tool_call_adjacency_repaired missing_tool_results={missing_count} orphan_tool_messages={orphan_count}",
        )
    return repaired


def anthropic_tool_choice_to_openai(tool_choice: Any) -> Any:
    if not isinstance(tool_choice, dict):
        return tool_choice
    choice_type = tool_choice.get("type")
    if choice_type == "tool" and tool_choice.get("name"):
        return {"type": "function", "function": {"name": str(tool_choice["name"])}}
    if choice_type == "any":
        return "required"
    if choice_type == "auto":
        return "auto"
    return tool_choice


def opencode_model_id_hint(provider: str, pcfg: dict[str, Any], model: str | None) -> str:
    requested = strip_claude_context_suffix(model).strip()
    fallback = normalize_model_id(provider, pcfg.get("current_model") or "")
    prefix = f"claude-any-{provider}-"
    if requested.startswith(prefix):
        return requested[len(prefix):]
    if requested.startswith("claude-any-"):
        return fallback
    return normalize_model_id(provider, requested or fallback)


def openai_chat_reasoning_passback_enabled(provider: str, model: str | None, pcfg: dict[str, Any]) -> bool:
    if provider not in OPENCODE_PROVIDER_NAMES:
        return False
    model_id = opencode_model_id_hint(provider, pcfg, model).strip().lower()
    if not model_id.startswith("deepseek-"):
        return False
    return opencode_endpoint_kind(provider, model_id, pcfg) == "openai-chat"


def openai_chat_reasoning_passback_enabled_for_body(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> bool:
    return openai_chat_reasoning_passback_enabled(provider, str(body.get("model") or ""), pcfg)


def openai_reasoning_to_anthropic_thinking_block(reasoning_content: Any) -> dict[str, Any] | None:
    reasoning = str(reasoning_content or "")
    if not reasoning:
        return None
    digest = hashlib.sha256(reasoning.encode("utf-8", errors="replace")).hexdigest()[:24]
    return {
        "type": "thinking",
        "thinking": reasoning,
        "signature": f"claude-any-openai-reasoning-{digest}",
    }


def should_omit_openai_chat_tool_choice(provider: str, model: str, body: dict[str, Any], pcfg: dict[str, Any]) -> bool:
    """Return true when an OpenAI-chat backend should receive tools without a forced tool_choice."""
    if body.get("tool_choice") is None:
        return False
    return openai_chat_reasoning_passback_enabled(provider, model, pcfg)


def positive_int(value: Any) -> int | None:
    try:
        out = int(value)
    except Exception:
        return None
    return out if out > 0 else None


def finite_float(value: Any) -> float | None:
    try:
        out = float(value)
    except Exception:
        return None
    return out if math.isfinite(out) else None


def parse_config_value(value: str) -> Any:
    text = value.strip()
    low = text.lower()
    if low in ("true", "yes", "on"):
        return True
    if low in ("false", "no", "off"):
        return False
    if low in ("none", "null"):
        return None
    try:
        return json.loads(text)
    except Exception:
        pass
    try:
        return int(text)
    except Exception:
        pass
    try:
        return float(text)
    except Exception:
        return text


def parse_bool(value: Any, default: bool = False) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return default
    if isinstance(value, (int, float)):
        return bool(value)
    text = str(value).strip().lower()
    if text in ("true", "yes", "on", "1", "enable", "enabled"):
        return True
    if text in ("false", "no", "off", "0", "disable", "disabled"):
        return False
    return default


def router_debug_external_access_enabled(cfg: dict[str, Any] | None = None) -> bool:
    env = env_bool(os.environ.get("CLAUDE_ANY_ROUTER_DEBUG_EXTERNAL"), None)
    if env is not None:
        return bool(env)
    if cfg is None:
        cfg = load_config()
    return (
        parse_bool(cfg.get("router_debug_external_access"), False)
        and parse_bool(cfg.get("router_debug_external_access_confirmed"), False)
    )


def router_bind_host(cfg: dict[str, Any] | None = None) -> str:
    env_host = (os.environ.get("CLAUDE_ANY_ROUTER_BIND_HOST") or os.environ.get("CLAUDE_ANY_ROUTER_HOST") or "").strip()
    if env_host:
        return env_host
    return "0.0.0.0" if router_debug_external_access_enabled(cfg) else "127.0.0.1"


def is_loopback_address(host: str | None) -> bool:
    host = (host or "").strip().lower()
    return host in ("127.0.0.1", "::1", "localhost") or host.startswith("127.")


def router_request_allowed(handler: BaseHTTPRequestHandler, cfg: dict[str, Any] | None = None) -> bool:
    if router_debug_external_access_enabled(cfg):
        return True
    try:
        return is_loopback_address(str(handler.client_address[0]))
    except Exception:
        return False


def set_router_debug_external_access_config(value: Any) -> list[str]:
    cfg = load_config()
    enabled = parse_bool(value, False)
    cfg["router_debug_external_access"] = enabled
    cfg["router_debug_external_access_confirmed"] = enabled
    save_config(cfg)
    clear_model_cache()
    bind = router_bind_host(cfg)
    if enabled:
        return [
            "Router debug external access: on.",
            f"Router bind host for next launch: {bind}.",
            "External clients are allowed while the router is bound to an external interface.",
        ]
    return [
        "Router debug external access: off.",
        "External clients are denied immediately; next launch binds to 127.0.0.1 unless overridden by environment.",
    ]


def schedule_router_process_restart(delay: float = 0.8) -> None:
    def restart() -> None:
        try:
            router_log("INFO", "router_debug_restart execing router process")
            os.execv(sys.executable, [sys.executable, str(Path(__file__).resolve()), "serve"])
        except Exception as exc:
            try:
                router_log("ERROR", f"router_debug_restart_failed {type(exc).__name__}: {exc}")
            except Exception:
                pass

    timer = threading.Timer(delay, restart)
    timer.daemon = True
    timer.start()


def ctx_bucket(target: int, minimum: int, maximum: int) -> int:
    target = max(minimum, min(maximum, target))
    buckets = [4096, 8192, 16384, 32768, 65536, 131072, 262144]
    for bucket in buckets:
        if bucket >= target:
            return min(bucket, maximum)
    return maximum


def ollama_num_ctx_for_payload(pcfg: dict[str, Any], payload: Any, _token_cache: dict[int, int] | None = None) -> int | None:
    override = os.environ.get("CLAUDE_ANY_OLLAMA_NUM_CTX")
    if override:
        return positive_int(override)
    raw = pcfg.get("num_ctx", "auto")
    if isinstance(raw, str) and raw.strip().lower() in ("", "auto", "dynamic"):
        minimum = positive_int(pcfg.get("num_ctx_min")) or 8192
        maximum = positive_int(pcfg.get("num_ctx_max")) or 65536
        if maximum < minimum:
            maximum = minimum
        estimated = estimate_tokens(payload, _token_cache)
        # Leave headroom for tool results, follow-up commands, and model-side formatting.
        target = int(estimated * 1.45) + 2048
        return ctx_bucket(target, minimum, maximum)
    return positive_int(raw)


def ollama_num_ctx_status(pcfg: dict[str, Any]) -> str:
    raw = pcfg.get("num_ctx", "auto")
    if isinstance(raw, str) and raw.strip().lower() in ("", "auto", "dynamic"):
        minimum = positive_int(pcfg.get("num_ctx_min")) or 8192
        maximum = positive_int(pcfg.get("num_ctx_max")) or 65536
        return f"auto ({minimum}-{maximum})"
    return str(positive_int(raw) or raw)


def ollama_extra_options(pcfg: dict[str, Any]) -> dict[str, Any]:
    raw = pcfg.get("ollama_options") or {}
    if not isinstance(raw, dict):
        return {}
    return {str(k): v for k, v in raw.items() if v is not None}


def ollama_options_status(pcfg: dict[str, Any]) -> str:
    opts = ollama_extra_options(pcfg)
    if not opts:
        return "{}"
    return ", ".join(f"{k}={json.dumps(v, ensure_ascii=False)}" for k, v in sorted(opts.items()))


def ollama_request_timeout_seconds(pcfg: dict[str, Any]) -> float:
    raw = pcfg.get("request_timeout_ms", pcfg.get("request_timeout", pcfg.get("timeout_ms", DEFAULT_REQUEST_TIMEOUT_MS)))
    try:
        value = float(raw)
    except (TypeError, ValueError):
        return 120.0
    if value <= 0:
        return 120.0
    # Values above 10k are treated as milliseconds, matching common UI/API timeout notation.
    if value > 10000:
        return max(1.0, value / 1000.0)
    return value


def configured_output_tokens(pcfg: dict[str, Any], body: dict[str, Any], option_key: str | None = None) -> int | None:
    configured = positive_int(pcfg.get("max_output_tokens"))
    if option_key:
        opts = ollama_extra_options(pcfg)
        configured = positive_int(opts.get(option_key)) or configured
    requested = positive_int(body.get("max_tokens"))
    if configured and requested:
        return min(configured, requested)
    return configured or requested


def cap_output_tokens_for_context(
    pcfg: dict[str, Any],
    body: dict[str, Any],
    payload: Any,
    context_limit: int | None,
    configured: int | None,
    _token_cache: dict[int, int] | None = None,
) -> int | None:
    if not configured:
        return None
    if not context_limit:
        return configured
    reserve = positive_int(pcfg.get("context_reserve_tokens")) or 1024
    estimated_input = estimate_tokens(payload, _token_cache)
    available = context_limit - estimated_input - reserve
    if available <= 0:
        return min(configured, 256)
    return max(1, min(configured, available))


def ollama_context_limit_for_budget(pcfg: dict[str, Any]) -> int:
    raw = pcfg.get("num_ctx", "auto")
    if isinstance(raw, str) and raw.strip().lower() == "auto":
        return positive_int(pcfg.get("num_ctx_max")) or 65536
    return positive_int(raw) or positive_int(pcfg.get("num_ctx_max")) or 65536


def openai_context_limit_for_budget(provider: str, pcfg: dict[str, Any]) -> int:
    configured = positive_int(pcfg.get("context_window")) or positive_int(pcfg.get("max_model_len"))
    if configured:
        return configured
    if provider == "nvidia-hosted":
        return nvidia_hosted_context_default(str(pcfg.get("current_model") or ""))
    return 65536


def compact_ollama_messages_for_budget(
    messages: list[dict[str, Any]],
    tools: list[dict[str, Any]],
    budget_tokens: int,
) -> list[dict[str, Any]]:
    if not messages:
        return messages
    budget_tokens = max(8192, budget_tokens)
    payload = {"messages": messages, "tools": tools}
    initial_tokens = estimate_tokens(payload)
    if initial_tokens <= budget_tokens:
        return messages

    system_messages = [m for m in messages if m.get("role") == "system"]
    non_system = [m for m in messages if m.get("role") != "system"]
    first_user: dict[str, Any] | None = next((m for m in non_system if m.get("role") == "user"), None)

    preserved_tail: list[dict[str, Any]] = []
    omitted = 0
    omitted_tokens = 0
    summary: dict[str, Any] = {
        "role": "user",
        "content": (
            "[claude-any context guard: older conversation messages were omitted because the provider context "
            "budget would be exceeded. Large file contents and prior Write/Edit inputs were truncated. "
            "Use Read on specific files if exact old content is needed.]"
        ),
    }

    fixed_prefix = list(system_messages)
    if first_user is not None:
        fixed_prefix.append(first_user)
    fixed_prefix.append(summary)

    for msg in reversed(non_system):
        if first_user is not None and msg is first_user:
            continue
        candidate = fixed_prefix + list(reversed(preserved_tail + [msg]))
        if estimate_tokens({"messages": candidate, "tools": tools}) <= budget_tokens:
            preserved_tail.append(msg)
        else:
            omitted += 1
            omitted_tokens += estimate_tokens(msg)

    if first_user is None:
        fixed_prefix = list(system_messages)
        fixed_prefix.append(summary)

    summary["content"] = (
        f"[claude-any context guard: omitted {omitted} older messages, approx {omitted_tokens} tokens, "
        f"because the provider context budget is {budget_tokens} tokens. Large file contents and prior "
        "Write/Edit inputs were truncated. Continue from the current task list and recent tool results; "
        "use Read on specific files if exact old content is needed.]"
    )
    compacted = fixed_prefix + list(reversed(preserved_tail))
    router_log(
        "WARN",
        f"compacted ollama payload messages {len(messages)}->{len(compacted)} tokens {initial_tokens}->{estimate_tokens({'messages': compacted, 'tools': tools})} budget={budget_tokens}",
    )
    return compacted


def provider_request_timeout_seconds(pcfg: dict[str, Any]) -> float:
    return ollama_request_timeout_seconds(pcfg)


def provider_stream_idle_timeout_seconds(pcfg: dict[str, Any]) -> float:
    raw = positive_int(pcfg.get("stream_idle_timeout_ms"))
    if raw:
        return max(5.0, raw / 1000.0)
    return max(30.0, min(provider_request_timeout_seconds(pcfg), 300.0))


def set_upstream_stream_read_timeout(resp: Any, timeout: float) -> None:
    """Keep one stalled upstream read from holding Claude Code's prompt queue forever."""
    try:
        if hasattr(resp, "fp") and getattr(resp, "fp") is not None:
            raw = getattr(resp.fp, "raw", None)
            sock = getattr(raw, "_sock", None)
            if sock is not None and hasattr(sock, "settimeout"):
                sock.settimeout(timeout)
                return
        sock = getattr(resp, "sock", None)
        if sock is not None and hasattr(sock, "settimeout"):
            sock.settimeout(timeout)
    except Exception:
        pass


class UpstreamClientDisconnected(Exception):
    """Raised when the downstream Claude Code client closes while upstream is still streaming."""


def router_client_connection_closed(handler: BaseHTTPRequestHandler) -> bool:
    conn = getattr(handler, "connection", None)
    if conn is None:
        return False
    try:
        readable, _, _ = select.select([conn], [], [], 0)
    except (OSError, ValueError):
        return True
    except Exception:
        return False
    if not readable:
        return False
    try:
        flags = getattr(socket, "MSG_PEEK", 0)
        data = conn.recv(1, flags)
        return data == b""
    except BlockingIOError:
        return False
    except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError, OSError):
        return True


def upstream_stream_poll_timeout_seconds(idle_timeout: float) -> float:
    try:
        idle = float(idle_timeout)
    except Exception:
        idle = 30.0
    return max(0.2, min(1.0, idle))


def upstream_read_timed_out(exc: BaseException) -> bool:
    if isinstance(exc, TimeoutError):
        return True
    return "timed out" in f"{type(exc).__name__}: {exc}".lower()


def iter_upstream_lines_until_client_disconnect(
    handler: BaseHTTPRequestHandler,
    resp: Any,
    idle_timeout: float,
) -> Iterable[bytes]:
    try:
        idle = max(1.0, float(idle_timeout))
    except Exception:
        idle = 30.0
    poll_timeout = upstream_stream_poll_timeout_seconds(idle)
    set_upstream_stream_read_timeout(resp, poll_timeout)
    idle_deadline = time.monotonic() + idle
    while True:
        if router_client_connection_closed(handler):
            raise UpstreamClientDisconnected("downstream client disconnected")
        try:
            raw = resp.readline()
        except (TimeoutError, OSError) as exc:
            if router_client_connection_closed(handler):
                raise UpstreamClientDisconnected("downstream client disconnected during upstream read") from exc
            if upstream_read_timed_out(exc) and time.monotonic() < idle_deadline:
                continue
            raise
        if raw in (b"", ""):
            return
        idle_deadline = time.monotonic() + idle
        yield raw


def sleep_until_or_client_disconnect(handler: BaseHTTPRequestHandler, seconds: float) -> bool:
    deadline = time.monotonic() + max(0.0, seconds)
    while True:
        if router_client_connection_closed(handler):
            return False
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            return True
        time.sleep(min(0.25, remaining))


def cap_anthropic_body_for_provider(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    capped = dict(body)
    if provider not in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim"):
        return capped
    if provider == "vllm":
        context_limit = positive_int(pcfg.get("context_window")) or positive_int(pcfg.get("max_model_len")) or 32768
    else:
        context_limit = positive_int(pcfg.get("context_window")) or positive_int(pcfg.get("max_model_len"))
    configured = configured_output_tokens(pcfg, capped)
    output_tokens = cap_output_tokens_for_context(pcfg, capped, {k: v for k, v in capped.items() if k != "max_tokens"}, context_limit, configured)
    if output_tokens:
        capped["max_tokens"] = output_tokens
    return capped


def apply_provider_request_options(provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> dict[str, Any]:
    if provider not in PROVIDER_SAMPLING_OPTION_PROVIDERS:
        return body
    out = dict(body)
    for key in PROVIDER_SAMPLING_OPTIONS:
        value = pcfg.get(key)
        if value is not None:
            out[key] = value
    return out


def normalize_anthropic_model_request_options(provider: str, pcfg: dict[str, Any], body: dict[str, Any], model_id: str) -> dict[str, Any]:
    if provider != "anthropic":
        return body
    unsupported = anthropic_model_runtime_hints(model_id).get("unsupported_sampling_parameters")
    if not isinstance(unsupported, list) or not unsupported:
        return body
    out = dict(body)
    removed: list[str] = []
    for key in unsupported:
        if isinstance(key, str) and key in out:
            out.pop(key, None)
            removed.append(key)
    if removed:
        router_log("INFO", f"anthropic_request_options_removed model={model_id} keys={','.join(removed)}")
    return out


def ollama_chat_request(model: str, body: dict[str, Any], pcfg: dict[str, Any], stream: bool = True) -> dict[str, Any]:
    messages = anthropic_messages_to_ollama(body)
    tools = anthropic_tools_to_ollama(body.get("tools"))
    context_limit = ollama_context_limit_for_budget(pcfg)
    configured = configured_output_tokens(pcfg, body, "num_predict")
    reserve = positive_int(pcfg.get("context_reserve_tokens")) or 1024
    output_reserve = configured or positive_int(body.get("max_tokens")) or 4096
    input_budget = max(8192, context_limit - output_reserve - reserve)
    messages = compact_ollama_messages_for_budget(messages, tools, input_budget)
    req: dict[str, Any] = {
        "model": model,
        "messages": messages,
        "stream": stream,
        "think": bool(pcfg.get("think", False)),
    }
    if pcfg.get("keep_alive"):
        req["keep_alive"] = str(pcfg["keep_alive"])
    if tools:
        req["tools"] = tools
    options: dict[str, Any] = ollama_extra_options(pcfg)
    payload_for_est = {"messages": messages, "tools": tools}
    token_cache: dict[int, int] = {}
    num_ctx = ollama_num_ctx_for_payload(pcfg, payload_for_est, _token_cache=token_cache)
    num_predict = cap_output_tokens_for_context(
        pcfg,
        body,
        payload_for_est,
        num_ctx,
        configured,
        _token_cache=token_cache,
    )
    if num_predict:
        options["num_predict"] = num_predict
    if num_ctx:
        options.setdefault("num_ctx", num_ctx)
    if options:
        req["options"] = options
    return req


def openai_compatible_chat_request(provider: str, model: str, body: dict[str, Any], pcfg: dict[str, Any], stream: bool = False) -> dict[str, Any]:
    reasoning_passback = openai_chat_reasoning_passback_enabled(provider, model, pcfg)
    messages = anthropic_messages_to_openai(body, reasoning_passback=reasoning_passback)
    tools = anthropic_tools_to_ollama(body.get("tools"))
    context_limit = openai_context_limit_for_budget(provider, pcfg)
    configured = configured_output_tokens(pcfg, body)
    reserve = positive_int(pcfg.get("context_reserve_tokens")) or 1024
    output_reserve = configured or positive_int(body.get("max_tokens")) or 4096
    messages = compact_ollama_messages_for_budget(messages, tools, max(8192, context_limit - output_reserve - reserve))
    messages = repair_openai_tool_call_adjacency(messages)
    req: dict[str, Any] = {
        "model": model,
        "messages": messages,
        "stream": stream,
    }
    if tools:
        req["tools"] = tools
    if body.get("tool_choice") is not None and not should_omit_openai_chat_tool_choice(provider, model, body, pcfg):
        req["tool_choice"] = anthropic_tool_choice_to_openai(body.get("tool_choice"))
    max_tokens = configured_output_tokens(pcfg, body)
    if max_tokens:
        req["max_tokens"] = max_tokens
    for key in ("temperature", "top_p"):
        if pcfg.get(key) is not None:
            req[key] = pcfg[key]
    return req


ADVISOR_REVIEW_PROMPT = (
    "You are claude-any Advisor, a stronger reviewer model. Review the current task state and provide "
    "concise, actionable guidance for the executor model. Review now; do not say that you will review later. "
    "Do not write code unless a small exact patch is the clearest advice. Use this exact structure: "
    "Verdict: approve, revise, or continue. Key findings: concrete gaps or risks. Required next action: "
    "the next action or Claude Code tool call. Validation: the check that proves the work. "
    "If the executor is stuck after progress announcements, tell it the exact next Claude Code tool to call."
)


def advisor_messages_for_provider(provider: str, body: dict[str, Any], focus_override: str = "") -> list[dict[str, Any]]:
    kind = advisor_provider_kind(provider)
    if kind == "anthropic":
        messages, _ = anthropic_advisor_messages_and_system(body)
    elif kind == "openai-compatible":
        messages = anthropic_messages_to_openai(body)
    else:
        messages = anthropic_messages_to_ollama(body)
    focus = focus_override or advisor_focus_from_body(body)
    if kind != "anthropic":
        messages.insert(0, {"role": "system", "content": ADVISOR_REVIEW_PROMPT})
    if focus:
        focus_text = f"Advisor focus:\n{compact_message_text_for_prompt(focus)}"
        if kind == "anthropic":
            messages.append({"role": "user", "content": [{"type": "text", "text": focus_text}]})
        else:
            messages.append({"role": "user", "content": focus_text})
    return messages


def advisor_input_budget(provider: str, pcfg: dict[str, Any]) -> int:
    kind = advisor_provider_kind(provider)
    if kind == "ollama":
        context_limit = ollama_context_limit_for_budget(pcfg)
    elif kind == "anthropic":
        context_limit = context_limit_for_status(provider, pcfg) or 200000
    else:
        context_limit = openai_context_limit_for_budget(provider, pcfg)
    return max(8192, context_limit - 4096 - (positive_int(pcfg.get("context_reserve_tokens")) or 1024))


def advisor_upstream_model(provider: str, model: str) -> str:
    return ncp_model_id_for_nvidia_hosted(model) if provider == "nvidia-hosted" else model


def advisor_request(provider: str, model: str, body: dict[str, Any], pcfg: dict[str, Any], focus_override: str = "") -> dict[str, Any]:
    messages = advisor_messages_for_provider(provider, body, focus_override=focus_override)
    if advisor_provider_kind(provider) != "anthropic":
        messages = compact_ollama_messages_for_budget(messages, [], advisor_input_budget(provider, pcfg))
    upstream_model = advisor_upstream_model(provider, model)
    if advisor_provider_kind(provider) == "anthropic":
        _, extra_system_texts = anthropic_advisor_messages_and_system(body)
        req = {
            "model": upstream_model,
            "system": anthropic_system_with_advisor(body.get("system"), extra_system_texts),
            "messages": messages,
            "stream": False,
            "max_tokens": min(4096, configured_output_tokens(pcfg, body) or 4096),
        }
        for key in ("temperature", "top_p"):
            if pcfg.get(key) is not None:
                req[key] = pcfg[key]
        return req
    if advisor_provider_kind(provider) == "openai-compatible":
        req: dict[str, Any] = {
            "model": upstream_model,
            "messages": messages,
            "stream": False,
            "max_tokens": min(4096, configured_output_tokens(pcfg, body) or 4096),
        }
        for key in ("temperature", "top_p"):
            if pcfg.get(key) is not None:
                req[key] = pcfg[key]
        return req
    req: dict[str, Any] = {
        "model": upstream_model,
        "messages": messages,
        "stream": False,
        "think": bool(pcfg.get("think", False)),
    }
    options = ollama_extra_options(pcfg)
    options.setdefault("num_predict", min(4096, positive_int(options.get("num_predict")) or 4096))
    num_ctx = ollama_num_ctx_for_payload(pcfg, {"messages": messages, "tools": []})
    if num_ctx:
        options.setdefault("num_ctx", num_ctx)
    if options:
        req["options"] = options
    return req


def advisor_response_text(provider: str, data: Any) -> str:
    if not isinstance(data, dict):
        return ""
    if advisor_provider_kind(provider) == "anthropic":
        return anthropic_content_to_text(data.get("content")).strip()
    if advisor_provider_kind(provider) == "openai-compatible":
        choices = data.get("choices")
        if isinstance(choices, list) and choices:
            message = choices[0].get("message") if isinstance(choices[0], dict) else {}
            if isinstance(message, dict):
                return str(message.get("content") or "").strip()
        return ""
    message = data.get("message") if isinstance(data, dict) else {}
    return str((message or {}).get("content") or "").strip()


def advisor_endpoint(provider: str, pcfg: dict[str, Any]) -> str:
    base = pcfg.get("base_url", "").rstrip("/")
    if advisor_provider_kind(provider) == "anthropic":
        url = join_url(base, "/v1/messages")
        # Honor force_query_string like the main forwarding path; the advisor
        # call has no inbound request path, so only the explicit override applies.
        query = upstream_messages_query(pcfg, "")
        return f"{url}?{query}" if query else url
    if advisor_provider_kind(provider) == "openai-compatible":
        return join_url(provider_upstream_request_base(provider, pcfg), "/v1/chat/completions")
    return join_url(base, "/api/chat")


def call_advisor_text(
    provider: str,
    pcfg: dict[str, Any],
    body: dict[str, Any],
    focus: str = "",
    inbound_headers: Any | None = None,
    *,
    allow_rate_limit_wait: bool = True,
    retry_rate_limits: bool = True,
    raise_errors: bool = False,
) -> str:
    advisor_model = advisor_model_enabled(pcfg)
    if not advisor_model or not advisor_provider_supported(provider):
        return ""
    upstream_model = advisor_upstream_model(provider, advisor_model)
    req_body = advisor_request(provider, advisor_model, body, pcfg, focus_override=focus)
    try:
        if allow_rate_limit_wait:
            apply_router_rate_limit(provider, pcfg, upstream_model)
        write_router_activity("advisor", provider, upstream_model, tokens=estimate_tokens(req_body))
        if advisor_provider_kind(provider) == "openai-compatible":
            data = post_json_with_rate_retry(
                advisor_endpoint(provider, pcfg),
                req_body,
                provider_headers(provider, pcfg),
                provider_request_timeout_seconds(pcfg),
                provider,
                pcfg,
                upstream_model,
                None,
                retry_rate_limits=retry_rate_limits,
            )
        elif advisor_provider_kind(provider) == "anthropic":
            data = post_json_with_rate_retry(
                advisor_endpoint(provider, pcfg),
                req_body,
                provider_headers(provider, pcfg, inbound_headers),
                provider_request_timeout_seconds(pcfg),
                provider,
                pcfg,
                upstream_model,
                None,
                retry_rate_limits=retry_rate_limits,
            )
        else:
            data = post_json_with_rate_retry(
                advisor_endpoint(provider, pcfg),
                req_body,
                provider_headers(provider, pcfg),
                ollama_request_timeout_seconds(pcfg),
                provider,
                pcfg,
                upstream_model,
                None,
                retry_rate_limits=retry_rate_limits,
            )
        text = advisor_response_text(provider, data)
        if text:
            router_log("INFO", f"advisor_feedback provider={provider} advisor_model={upstream_model} chars={len(text)}")
        return text
    except Exception as exc:
        router_log("WARN", f"advisor_request_failed provider={provider} advisor_model={upstream_model} error={type(exc).__name__}: {exc}")
        write_router_activity("advisor_error", provider, upstream_model, error=type(exc).__name__)
        if raise_errors:
            raise
        return ""


def body_with_internal_advisor_feedback(body: dict[str, Any], assistant_message: dict[str, Any], advisor_text: str, trigger: str) -> dict[str, Any]:
    messages = [m for m in body.get("messages", []) if isinstance(m, dict)]
    assistant_text = anthropic_content_to_text(assistant_message.get("content")).strip()
    tool_names = anthropic_message_tool_names(assistant_message)
    tool_summary = assistant_tool_call_summary_for_prompt(assistant_message)
    if assistant_text:
        messages.append({"role": "assistant", "content": [{"type": "text", "text": compact_message_text_for_prompt(assistant_text)}]})
    elif tool_summary:
        messages.append({"role": "assistant", "content": [{"type": "text", "text": compact_message_text_for_prompt(tool_summary)}]})
    elif tool_names:
        messages.append({
            "role": "assistant",
            "content": [{"type": "text", "text": f"I was about to call Claude Code tool(s): {', '.join(tool_names)}."}],
        })
    feedback = (
        f"{ADVISOR_FEEDBACK_MARKER}\n"
        f"Advisor review trigger: {trigger}.\n\n"
        f"{advisor_text}\n\n"
        "Apply this advisor feedback now. If the plan is still ready, call ExitPlanMode with the improved plan. "
        "If the task is complete, provide the final answer. If the advisor found a gap, continue with concrete "
        "Claude Code tool calls instead of stopping after an announcement."
    )
    messages.append({"role": "user", "content": [{"type": "text", "text": feedback}]})
    follow_body = dict(body)
    follow_body["messages"] = messages
    follow_body.pop("tool_choice", None)
    return follow_body


def advisor_visible_summary(advisor_text: str, trigger: str, limit: int = 700) -> str:
    text = re.sub(r"\s+", " ", str(advisor_text or "")).strip()
    if not text:
        return ""
    if len(text) > limit:
        text = text[: max(0, limit - 1)].rstrip() + "…"
    return f"Advisor review ({trigger}): {text}\n\n"


def call_provider_chat_once(provider: str, pcfg: dict[str, Any], body: dict[str, Any], model: str) -> dict[str, Any]:
    body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
    body = normalize_tool_choice_for_provider(provider, pcfg, body)
    if provider in ("ollama", "ollama-cloud"):
        base = pcfg.get("base_url", "").rstrip("/")
        req_body = ollama_chat_request(model, body, pcfg, stream=False)
        apply_router_rate_limit(provider, pcfg, model)
        data = post_json_with_rate_retry(
            join_url(base, "/api/chat"),
            req_body,
            provider_headers(provider, pcfg),
            ollama_request_timeout_seconds(pcfg),
            provider,
            pcfg,
            model,
            None,
        )
        return ollama_chat_to_anthropic(data, model, source_body=body)
    if provider in ("lm-studio", "nvidia-hosted"):
        upstream_model = ncp_model_id_for_nvidia_hosted(model) if provider == "nvidia-hosted" else model
        req_body = openai_compatible_chat_request(provider, upstream_model, body, pcfg, stream=False)
        apply_router_rate_limit(provider, pcfg, upstream_model)
        data = post_json_with_rate_retry(
            join_url(provider_upstream_request_base(provider, pcfg), "/v1/chat/completions"),
            req_body,
            provider_headers(provider, pcfg),
            provider_request_timeout_seconds(pcfg),
            provider,
            pcfg,
            upstream_model,
            None,
        )
        return openai_chat_to_anthropic(data, upstream_model, source_body=body)
    raise RuntimeError(f"Advisor refinement is not supported for provider {provider}")


def refine_message_with_advisor(
    provider: str,
    pcfg: dict[str, Any],
    original_body: dict[str, Any],
    message: dict[str, Any],
    main_model: str,
) -> dict[str, Any]:
    if not advisor_model_enabled(pcfg):
        return message
    if not advisor_provider_supported(provider):
        return message
    if is_advisor_request(original_body) or body_has_advisor_feedback(original_body):
        return message
    trigger = advisor_trigger_for_message(original_body, message)
    trigger, advisor_focus = advisor_focus_for_message(message, trigger)
    if not trigger:
        return message
    advisor_text = call_advisor_text(provider, pcfg, original_body, focus=advisor_focus or trigger)
    if not advisor_text:
        return message
    follow_body = body_with_internal_advisor_feedback(original_body, message, advisor_text, trigger)
    visible_summary = advisor_visible_summary(advisor_text, trigger)
    try:
        router_log("INFO", f"advisor_refinement_call trigger={trigger} main_model={main_model}")
        refined = call_provider_chat_once(provider, pcfg, follow_body, main_model)
        router_log("INFO", f"advisor_refinement_done trigger={trigger} stop_reason={refined.get('stop_reason')}")
        return prepend_anthropic_text(refined, visible_summary)
    except Exception as exc:
        router_log("WARN", f"advisor_refinement_failed trigger={trigger} model={main_model} error={type(exc).__name__}: {exc}")
        write_router_activity("advisor_refinement_error", provider, main_model, error=type(exc).__name__)
        return prepend_anthropic_text(message, visible_summary)


def anthropic_text_response(model: str, text: str, stop_reason: str = "end_turn") -> dict[str, Any]:
    return {
        "id": f"msg_ollama_advisor_{int(time.time() * 1000)}",
        "type": "message",
        "role": "assistant",
        "model": model,
        "content": [{"type": "text", "text": text}],
        "stop_reason": stop_reason,
        "stop_sequence": None,
        "usage": {"input_tokens": 0, "output_tokens": max(1, len(text) // 4)},
    }


def write_anthropic_text_response(handler: BaseHTTPRequestHandler, model: str, text: str, stream: bool) -> None:
    if not stream:
        write_json(handler, anthropic_text_response(model, text))
        return
    handler.send_response(200)
    handler.send_header("content-type", "text/event-stream")
    handler.send_header("cache-control", "no-cache")
    handler.send_header("connection", "close")
    handler.end_headers()
    msg_id = f"msg_ollama_advisor_{int(time.time() * 1000)}"
    events = [
        ("message_start", {
            "type": "message_start",
            "message": {
                "id": msg_id,
                "type": "message",
                "role": "assistant",
                "content": [],
                "model": model,
                "stop_reason": None,
                "stop_sequence": None,
                "usage": {"input_tokens": 0, "output_tokens": 0},
            },
        }),
        ("content_block_start", {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}),
        ("content_block_delta", {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": text}}),
        ("content_block_stop", {"type": "content_block_stop", "index": 0}),
        ("message_delta", {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": None}, "usage": {"output_tokens": max(1, len(text) // 4)}}),
        ("message_stop", {"type": "message_stop"}),
    ]
    for event_name, payload in events:
        handler.wfile.write(f"event: {event_name}\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n".encode())
    handler.wfile.flush()


def write_anthropic_message_response(handler: BaseHTTPRequestHandler, message: dict[str, Any], stream: bool) -> None:
    if not stream:
        write_json(handler, message)
        return
    handler.send_response(200)
    handler.send_header("content-type", "text/event-stream")
    handler.send_header("cache-control", "no-cache")
    handler.send_header("connection", "close")
    handler.end_headers()
    handler.wfile.write(f"event: message_start\ndata: {json.dumps({'type': 'message_start', 'message': {**message, 'content': [], 'stop_reason': None}}, ensure_ascii=False)}\n\n".encode())
    for index, block in enumerate(message.get("content") or []):
        btype = block.get("type")
        if btype == "text":
            handler.wfile.write(f"event: content_block_start\ndata: {json.dumps({'type': 'content_block_start', 'index': index, 'content_block': {'type': 'text', 'text': ''}}, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps({'type': 'content_block_delta', 'index': index, 'delta': {'type': 'text_delta', 'text': block.get('text', '')}}, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
        elif btype == "thinking":
            start = {"type": "content_block_start", "index": index, "content_block": {"type": "thinking", "thinking": ""}}
            handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
            thinking_text = str(block.get("thinking") or "")
            if thinking_text:
                delta = {"type": "content_block_delta", "index": index, "delta": {"type": "thinking_delta", "thinking": thinking_text}}
                handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
            signature = str(block.get("signature") or "")
            if signature:
                delta = {"type": "content_block_delta", "index": index, "delta": {"type": "signature_delta", "signature": signature}}
                handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
        elif btype == "redacted_thinking":
            start = {"type": "content_block_start", "index": index, "content_block": block}
            handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
        elif btype == "tool_use":
            tool_input = block.get("input") or {}
            start = {"type": "content_block_start", "index": index, "content_block": {**block, "input": {}}}
            delta = {"type": "content_block_delta", "index": index, "delta": {"type": "input_json_delta", "partial_json": json.dumps(tool_input, ensure_ascii=False)}}
            handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
            handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
    handler.wfile.write(f"event: message_delta\ndata: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': message.get('stop_reason') or 'end_turn', 'stop_sequence': None}, 'usage': message.get('usage') or {'output_tokens': 1}}, ensure_ascii=False)}\n\n".encode())
    handler.wfile.write(b"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n")
    handler.wfile.flush()


def _write_anthropic_stream_block(handler: BaseHTTPRequestHandler, index: int, block: dict[str, Any]) -> None:
    btype = block.get("type")
    if btype == "text":
        handler.wfile.write(f"event: content_block_start\ndata: {json.dumps({'type': 'content_block_start', 'index': index, 'content_block': {'type': 'text', 'text': ''}}, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps({'type': 'content_block_delta', 'index': index, 'delta': {'type': 'text_delta', 'text': block.get('text', '')}}, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
    elif btype == "thinking":
        start = {"type": "content_block_start", "index": index, "content_block": {"type": "thinking", "thinking": ""}}
        handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
        thinking_text = str(block.get("thinking") or "")
        if thinking_text:
            delta = {"type": "content_block_delta", "index": index, "delta": {"type": "thinking_delta", "thinking": thinking_text}}
            handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
        signature = str(block.get("signature") or "")
        if signature:
            delta = {"type": "content_block_delta", "index": index, "delta": {"type": "signature_delta", "signature": signature}}
            handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
    elif btype == "redacted_thinking":
        start = {"type": "content_block_start", "index": index, "content_block": block}
        handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())
    elif btype == "tool_use":
        tool_input = block.get("input") or {}
        start = {"type": "content_block_start", "index": index, "content_block": {**block, "input": {}}}
        delta = {"type": "content_block_delta", "index": index, "delta": {"type": "input_json_delta", "partial_json": json.dumps(tool_input, ensure_ascii=False)}}
        handler.wfile.write(f"event: content_block_start\ndata: {json.dumps(start, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_delta\ndata: {json.dumps(delta, ensure_ascii=False)}\n\n".encode())
        handler.wfile.write(f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': index}, ensure_ascii=False)}\n\n".encode())


def write_anthropic_open_stream_start(handler: BaseHTTPRequestHandler, model: str, input_tokens: int = 0) -> None:
    handler.send_response(200)
    handler.send_header("content-type", "text/event-stream")
    handler.send_header("cache-control", "no-cache")
    handler.send_header("connection", "close")
    handler.end_headers()
    msg_id = f"msg_claude_any_{int(time.time() * 1000)}"
    payload = {
        "type": "message_start",
        "message": {
            "id": msg_id,
            "type": "message",
            "role": "assistant",
            "content": [],
            "model": model,
            "stop_reason": None,
            "stop_sequence": None,
            "usage": {"input_tokens": max(0, int(input_tokens or 0)), "output_tokens": 0},
        },
    }
    handler.wfile.write(f"event: message_start\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n".encode())
    handler.wfile.flush()


def write_anthropic_stream_blocks(handler: BaseHTTPRequestHandler, blocks: list[dict[str, Any]], start_index: int = 0) -> int:
    index = start_index
    for block in blocks:
        _write_anthropic_stream_block(handler, index, block)
        index += 1
    handler.wfile.flush()
    return index


def write_anthropic_open_stream_stop(handler: BaseHTTPRequestHandler, message: dict[str, Any] | None = None) -> None:
    message = message or {}
    stop_reason = message.get("stop_reason") or "end_turn"
    usage = message.get("usage") or {"output_tokens": 1}
    handler.wfile.write(f"event: message_delta\ndata: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': usage}, ensure_ascii=False)}\n\n".encode())
    handler.wfile.write(b"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n")
    handler.wfile.flush()


def prepend_anthropic_text(message: dict[str, Any], text: str) -> dict[str, Any]:
    if not text:
        return message
    out = dict(message)
    content = out.get("content")
    blocks = list(content) if isinstance(content, list) else []
    blocks.insert(0, {"type": "text", "text": text})
    out["content"] = blocks
    return out


def maybe_handle_advisor_request(handler: BaseHTTPRequestHandler, provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> bool:
    if provider == "anthropic":
        # Claude native and Anthropic routed sessions follow Claude Code's
        # built-in /advisor flow (model picker + advisor server tool);
        # claude-any neither installs its own /advisor command nor intercepts
        # advisor traffic for this provider.
        return False
    if not is_advisor_request(body):
        return False
    advisor_model = str(pcfg.get("advisor_model") or "").strip()
    stream = bool(body.get("stream", True))
    if not advisor_model:
        write_anthropic_text_response(
            handler,
            str(body.get("model") or current_alias(load_config())),
            "Advisor is off. Choose an Advisor Model in the claude-any launch menu (item 5), or run `claude-any advisor-model <model-id>`, then use `/advisor` again.",
            stream,
        )
        return True
    if not advisor_provider_supported(provider):
        write_anthropic_text_response(
            handler,
            advisor_model,
            f"Advisor Model is configured as `{advisor_model}`, but claude-any advisor calling is not implemented for provider `{provider}`.",
            stream,
        )
        return True
    try:
        text = call_advisor_text(
            provider,
            pcfg,
            body,
            inbound_headers=handler.headers,
            allow_rate_limit_wait=False,
            retry_rate_limits=False,
            raise_errors=True,
        )
        if not text:
            text = "Advisor returned no text."
    except Exception as exc:
        text = f"Advisor request failed: {type(exc).__name__}: {exc}"
    write_anthropic_text_response(handler, advisor_model, "Advisor guidance:\n\n" + text, stream)
    return True


def maybe_handle_router_debug_request(handler: BaseHTTPRequestHandler, body: dict[str, Any]) -> bool:
    if not is_router_debug_request(body):
        return False
    stream = bool(body.get("stream", True))
    value = router_debug_value_from_body(body).strip()
    cfg = load_config()
    current = router_debug_external_access_enabled(cfg)
    low = value.lower()
    should_restart = False
    if low in ("", "status", "state", "show", "?"):
        lines = [
            f"Router debug external access: {'on' if current else 'off'}.",
            f"Current router bind host: {router_bind_host(cfg)}.",
        ]
    elif low in ("toggle", "tog", "switch"):
        lines = set_router_debug_external_access_config(not current)
        should_restart = True
    elif low in ("on", "true", "1", "enable", "enabled"):
        lines = set_router_debug_external_access_config(True)
        should_restart = True
    elif low in ("off", "false", "0", "disable", "disabled"):
        lines = set_router_debug_external_access_config(False)
        should_restart = True
    else:
        lines = ["Usage: `/router-debug`, `/router-debug on`, `/router-debug off`, or `/router-debug status`."]
    if should_restart:
        lines.append("Router restart scheduled so the bind address changes immediately.")
    write_anthropic_text_response(handler, str(body.get("model") or current_alias(load_config())), "\n".join(lines), stream)
    if should_restart:
        schedule_router_process_restart()
    return True


def normalize_tool_arguments(tool_name: str, args: Any) -> dict[str, Any]:
    if isinstance(args, dict):
        return args
    if isinstance(args, str):
        text = args.strip()
        if not text:
            return {}
        try:
            parsed = json.loads(text)
            if isinstance(parsed, dict):
                return parsed
        except Exception:
            pass
        if tool_name == "Bash":
            return {"command": text}
    return {}


PSEUDO_TOOL_START = "<|tool_calls_section_begin|>"
PSEUDO_TOOL_END = "<|tool_calls_section_end|>"
PSEUDO_CALL_BEGIN = "<|tool_call_begin|>"
PSEUDO_ARG_BEGIN = "<|tool_call_argument_begin|>"
PSEUDO_CALL_END = "<|tool_call_end|>"


def infer_tool_name_from_args(args: dict[str, Any]) -> str:
    keys = set(args)
    if "command" in keys:
        return "Bash"
    if {"file_path", "content"}.issubset(keys):
        return "Write"
    if {"file_path", "old_string", "new_string"}.issubset(keys):
        return "Edit"
    if "file_path" in keys:
        return "Read"
    task_update_keys = {"taskId", "task_id", "addBlocks", "addBlockedBy"}
    if keys & task_update_keys:
        return "TaskUpdate"
    return "TaskList" if not args else "Write"


def parse_pseudo_tool_calls(text: str) -> tuple[str, list[dict[str, Any]]]:
    if PSEUDO_TOOL_START not in text:
        return text, []
    visible_parts: list[str] = []
    calls: list[dict[str, Any]] = []
    pos = 0
    while True:
        start = text.find(PSEUDO_TOOL_START, pos)
        if start < 0:
            visible_parts.append(text[pos:])
            break
        visible_parts.append(text[pos:start])
        end = text.find(PSEUDO_TOOL_END, start)
        if end < 0:
            section = text[start + len(PSEUDO_TOOL_START):]
            pos = len(text)
        else:
            section = text[start + len(PSEUDO_TOOL_START):end]
            pos = end + len(PSEUDO_TOOL_END)
        for match in re.finditer(
            re.escape(PSEUDO_CALL_BEGIN) + r"(.*?)" + re.escape(PSEUDO_ARG_BEGIN) + r"(.*?)" + re.escape(PSEUDO_CALL_END),
            section,
            flags=re.DOTALL,
        ):
            raw_header = match.group(1).strip()
            raw_args = match.group(2).strip()
            try:
                args = json.loads(raw_args)
            except Exception:
                continue
            if not isinstance(args, dict):
                continue
            name = ""
            for part in re.split(r"[\s:|,]+", raw_header):
                candidate = _fuzzy_match_tool_name(part)
                if candidate:
                    name = candidate
                    break
            if not name:
                name = infer_tool_name_from_args(args)
            calls.append({"function": {"name": name, "arguments": args}, "id": raw_header})
        if end < 0:
            break
    return "".join(visible_parts), calls


def ollama_chat_to_anthropic(data: dict[str, Any], model: str, source_body: dict[str, Any] | None = None) -> dict[str, Any]:
    message = data.get("message") if isinstance(data.get("message"), dict) else {}
    content: list[dict[str, Any]] = []
    text = message.get("content") or ""
    text, pseudo_tool_calls = parse_pseudo_tool_calls(text)
    if text:
        content.append({"type": "text", "text": text})
    tool_id_prefix = f"toolu_ollama_{int(time.time() * 1000)}_{os.getpid()}"
    for i, call in enumerate(list(message.get("tool_calls") or []) + pseudo_tool_calls):
        fn = call.get("function") if isinstance(call, dict) else {}
        if not isinstance(fn, dict) or not fn.get("name"):
            continue
        name = str(fn["name"])
        matched_name = resolve_emitted_tool_name(name, source_body)
        raw_args = fn.get("arguments")
        normalized_args = normalize_tool_arguments(matched_name, raw_args)
        fixed_input = _validate_and_fix_tool_input(matched_name, normalized_args)
        if source_body is not None:
            matched_name, fixed_input = plan_mode_tool_name_for_emit(source_body, matched_name, fixed_input)
            if matched_name is None:
                continue
        fixed_input = cap_mcp_notification_wait_tool_input(matched_name, fixed_input)
        append_tool_call_log(
            "ollama_nonstream_tool_call",
            {
                "model": model,
                "raw_name": name,
                "matched_name": matched_name,
                "raw_arguments": raw_args,
                "normalized_arguments": normalized_args,
                "emitted_input": fixed_input,
            },
        )
        content.append(
            {
                "type": "tool_use",
                "id": f"{tool_id_prefix}_{i}",
                "name": matched_name,
                "input": fixed_input,
            }
        )
    emitted_tool_calls = [block for block in content if isinstance(block, dict) and block.get("type") == "tool_use"]
    if source_body is not None and should_auto_enter_plan_mode(source_body, text, emitted_tool_calls):
        router_log("WARN", "auto-synthesized EnterPlanMode from short/empty upstream response")
        return synthetic_tool_use_response(model, "EnterPlanMode")
    if source_body is not None and should_recover_empty_end_turn_with_tasklist(source_body, text, emitted_tool_calls):
        router_log("WARN", "auto-synthesized TaskList from empty upstream end_turn")
        return synthetic_tool_use_response(model, "TaskList")
    if source_body is not None and should_keep_work_alive_with_tasklist(source_body, text, emitted_tool_calls):
        router_log("WARN", "auto-synthesized TaskList to keep work moving after tool result")
        content.append(
            {
                "type": "tool_use",
                "id": f"{tool_id_prefix}_keepalive",
                "name": "TaskList",
                "input": {},
            }
        )
        emitted_tool_calls.append(content[-1])
    if source_body is not None and should_auto_continue_choice_question_with_tasklist(source_body, text, emitted_tool_calls):
        router_log("WARN", "auto-synthesized TaskList after clarification question")
        content.append(
            {
                "type": "tool_use",
                "id": f"toolu_ollama_choice_{int(time.time() * 1000)}",
                "name": "TaskList",
                "input": {},
            }
        )
        emitted_tool_calls.append(content[-1])
    if source_body is not None and not text.strip() and not emitted_tool_calls:
        text = empty_end_turn_notice_for_body(source_body)
        router_log("WARN", f"ollama_empty_end_turn_notice model={model} latest_tool_results={','.join(latest_user_tool_result_names(source_body)) or '-'}")
        content.append({"type": "text", "text": text})
    done_reason = data.get("done_reason")
    stop_reason = "tool_use" if any(block.get("type") == "tool_use" for block in content) else "end_turn"
    if done_reason == "length":
        stop_reason = "max_tokens"
    input_tokens = int(data.get("prompt_eval_count") or 0)
    if input_tokens <= 0 and isinstance(source_body, dict):
        input_tokens = estimate_tokens(source_body)
    output_tokens = int(data.get("eval_count") or 0)
    if output_tokens <= 0:
        output_tokens = max(1, len(text) // 4)
    return {
        "id": f"msg_ollama_{int(time.time() * 1000)}",
        "type": "message",
        "role": "assistant",
        "model": model,
        "content": content or [{"type": "text", "text": ""}],
        "stop_reason": stop_reason,
        "stop_sequence": None,
        "usage": {
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
        },
    }


STREAM_WORD_CHUNK_MAX_BUFFER = 64


def _split_word_buffer(buf: str, force: bool = False, max_buffer: int = STREAM_WORD_CHUNK_MAX_BUFFER) -> tuple[str, str]:
    """
    Split text into (to_flush, remainder) for word-boundary streaming.

    Without force: flush up to and including the last whitespace, unless the
    buffer length is at least max_buffer (then flush the entire buffer to avoid
    unbounded buffering on input without whitespace, e.g. very long words or
    CJK text).
    With force=True: flush the entire buffer (used at content_block_stop).
    """
    if not buf:
        return "", ""
    if force:
        return buf, ""
    last_ws = -1
    for i in range(len(buf) - 1, -1, -1):
        if buf[i].isspace():
            last_ws = i
            break
    if last_ws >= 0:
        return buf[:last_ws + 1], buf[last_ws + 1:]
    if len(buf) >= max_buffer:
        return buf, ""
    return "", buf


def _rebatch_anthropic_sse_text(
    handler: BaseHTTPRequestHandler,
    resp: Any,
    model: str = "claude-any-upstream",
    word_chunking: bool = True,
    source_body: dict[str, Any] | None = None,
    preserve_thinking: bool = True,
    normalize_tool_use: bool = False,
    provider: str = "",
) -> None:
    """
    Parse upstream Anthropic SSE and re-emit it with text_delta events buffered
    to word boundaries. Non-text events are forwarded in the same SSE framing.
    When the selected provider cannot preserve Anthropic's thinking passback
    contract, thinking blocks are suppressed and later content block indices are
    compacted.
    """
    text_buffers: dict[int, str] = {}
    pending_event_type: str | None = None
    pending_event_lines: list[str] = []
    saw_message_start = False
    saw_message_stop = False
    text_so_far = ""
    saw_tool_use = False
    emitted_tool_use = False
    next_content_index = 0
    open_content_blocks: set[int] = set()
    content_index_map: dict[int, int] = {}
    suppressed_content_indices: set[int] = set()
    suppressed_thinking_blocks: dict[int, dict[str, Any]] = {}
    suppressed_thinking_passback_blocks: list[dict[str, Any]] = []
    buffered_tool_uses: dict[int, dict[str, Any]] = {}
    pending_message_delta: tuple[str | None, str] | None = None
    pending_message_stop: tuple[str | None, str] | None = None
    last_suppressed_keepalive_at = 0.0

    class ClientStreamDisconnected(Exception):
        pass

    def emit_raw(event_type: str | None, data_str: str) -> None:
        try:
            if event_type:
                handler.wfile.write(f"event: {event_type}\ndata: {data_str}\n\n".encode())
            else:
                handler.wfile.write(f"data: {data_str}\n\n".encode())
            handler.wfile.flush()
        except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError) as exc:
            raise ClientStreamDisconnected(f"{type(exc).__name__}: {exc}") from exc

    def emit_suppressed_keepalive(force: bool = False) -> None:
        nonlocal last_suppressed_keepalive_at
        now = time.time()
        if not force and now - last_suppressed_keepalive_at < 1.0:
            return
        try:
            handler.wfile.write(b": suppressed-thinking\n\n")
            handler.wfile.flush()
        except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError) as exc:
            raise ClientStreamDisconnected(f"{type(exc).__name__}: {exc}") from exc
        last_suppressed_keepalive_at = now

    def emit_text_delta(index: int, text: str) -> None:
        if not text:
            return
        payload = {
            "type": "content_block_delta",
            "index": index,
            "delta": {"type": "text_delta", "text": text},
        }
        emit_raw("content_block_delta", json.dumps(payload, ensure_ascii=False))

    def emit_text_block(index: int, text: str) -> None:
        emit_raw(
            "content_block_start",
            json.dumps(
                {
                    "type": "content_block_start",
                    "index": index,
                    "content_block": {"type": "text", "text": ""},
                },
                ensure_ascii=False,
            ),
        )
        emit_text_delta(index, text)
        emit_raw("content_block_stop", json.dumps({"type": "content_block_stop", "index": index}, ensure_ascii=False))

    def flush_buffer(index: int, force: bool = False) -> None:
        buf = text_buffers.get(index, "")
        if not buf:
            return
        to_flush, remainder = _split_word_buffer(buf, force=force)
        text_buffers[index] = remainder
        emit_text_delta(index, to_flush)

    def emit_tasklist_tool(index: int) -> None:
        nonlocal emitted_tool_use
        tool_id = f"toolu_anthropic_choice_{int(time.time() * 1000)}"
        emit_raw(
            "content_block_start",
            json.dumps(
                {
                    "type": "content_block_start",
                    "index": index,
                    "content_block": {"type": "tool_use", "id": tool_id, "name": "TaskList", "input": {}},
                },
                ensure_ascii=False,
            ),
        )
        emit_raw(
            "content_block_delta",
            json.dumps(
                {
                    "type": "content_block_delta",
                    "index": index,
                    "delta": {"type": "input_json_delta", "partial_json": "{}"},
                },
                ensure_ascii=False,
            ),
        )
        emit_raw("content_block_stop", json.dumps({"type": "content_block_stop", "index": index}, ensure_ascii=False))
        emitted_tool_use = True

    def mapped_content_index(index: Any) -> int | None:
        if not isinstance(index, int):
            return None
        if index in suppressed_content_indices:
            return None
        return content_index_map.get(index, index)

    def append_suppressed_thinking_delta(index: Any, delta: dict[str, Any]) -> None:
        if not isinstance(index, int):
            return
        block = suppressed_thinking_blocks.get(index)
        if not isinstance(block, dict):
            return
        delta_type = delta.get("type")
        if delta_type == "thinking_delta":
            block["thinking"] = str(block.get("thinking") or "") + str(delta.get("thinking") or "")
        elif delta_type == "signature_delta":
            block["signature"] = str(delta.get("signature") or "")

    def finish_suppressed_thinking_block(index: Any) -> None:
        if not isinstance(index, int):
            return
        block = suppressed_thinking_blocks.pop(index, None)
        if isinstance(block, dict) and block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES:
            suppressed_thinking_passback_blocks.append(block)

    def flush_suppressed_thinking_passback() -> None:
        if preserve_thinking or not suppressed_thinking_passback_blocks:
            return
        if source_body is not None and latest_user_is_claude_code_suggestion_mode(source_body):
            router_log(
                "DEBUG",
                f"discarded suppressed Anthropic thinking passback blocks for suggestion-mode request "
                f"provider={provider} model={model} blocks={len(suppressed_thinking_passback_blocks)}",
            )
            suppressed_thinking_passback_blocks.clear()
            return
        remember_suppressed_thinking_passback(provider, model, suppressed_thinking_passback_blocks)
        suppressed_thinking_passback_blocks.clear()

    def patched_message_delta(stop_reason: str) -> str:
        event: dict[str, Any] = {}
        if pending_message_delta is not None:
            try:
                parsed = json.loads(pending_message_delta[1])
                if isinstance(parsed, dict):
                    event = dict(parsed)
            except Exception:
                event = {}
        if not event:
            event = {
                "type": "message_delta",
                "delta": {"stop_reason": None, "stop_sequence": None},
                "usage": {"output_tokens": max(1, len(text_so_far) // 4)},
            }
        delta = event.get("delta") if isinstance(event.get("delta"), dict) else {}
        patched_delta = dict(delta)
        patched_delta["stop_reason"] = stop_reason
        patched_delta.setdefault("stop_sequence", None)
        event["delta"] = patched_delta
        event.setdefault("type", "message_delta")
        event.setdefault("usage", {"output_tokens": max(1, len(text_so_far) // 4)})
        return json.dumps(event, ensure_ascii=False)

    def emit_pending_message_end(default_stop_reason: str = "end_turn") -> None:
        stop_reason = default_stop_reason
        if pending_message_delta is not None:
            try:
                parsed = json.loads(pending_message_delta[1])
                if isinstance(parsed, dict):
                    delta = parsed.get("delta") if isinstance(parsed.get("delta"), dict) else {}
                    stop_reason = str(delta.get("stop_reason") or stop_reason)
            except Exception:
                pass
        emit_raw(
            pending_message_delta[0] if pending_message_delta is not None else "message_delta",
            patched_message_delta(stop_reason),
        )
        emit_raw(
            pending_message_stop[0] if pending_message_stop is not None else "message_stop",
            pending_message_stop[1] if pending_message_stop is not None else "{\"type\":\"message_stop\"}",
        )

    def recover_hidden_only_response_if_needed() -> None:
        nonlocal next_content_index, saw_tool_use, emitted_tool_use, text_so_far, pending_message_delta
        recovery_reason = ""
        latest_names: list[str] = []
        synthetic_count = 0
        has_tasklist_tool = False
        if source_body is not None:
            try:
                latest_names = latest_user_tool_result_names(source_body)
                intent_index = latest_user_intent_message_index(source_body)
                synthetic_count = recent_synthetic_tasklist_count(source_body, after_message_index=intent_index)
                has_tasklist_tool = has_tool(source_body, "TaskList")
                if emitted_tool_use:
                    recovery_reason = ""
                elif should_recover_empty_end_turn_with_tasklist(source_body, text_so_far, []):
                    recovery_reason = "hidden-only" if suppressed_thinking_passback_blocks else "empty"
                elif should_keep_work_alive_with_tasklist(source_body, text_so_far, []):
                    recovery_reason = "keepalive"
            except Exception as exc:
                router_log(
                    "WARN",
                    "anthropic_hidden_recovery_state_error "
                    f"provider={provider} model={model} error={type(exc).__name__}: {exc}",
                )
        if recovery_reason:
            router_log(
                "WARN",
                f"auto-synthesized TaskList from {recovery_reason} Anthropic-compatible stream "
                f"latest_tool_results={','.join(latest_names) or '-'} synthetic_tasklists={synthetic_count}",
            )
            emit_tasklist_tool(next_content_index)
            next_content_index += 1
            saw_tool_use = True
            pending_message_delta = (
                pending_message_delta[0] if pending_message_delta is not None else "message_delta",
                patched_message_delta("tool_use"),
            )
            return
        if text_so_far.strip() or emitted_tool_use:
            if suppressed_thinking_passback_blocks:
                router_log(
                    "DEBUG",
                    "anthropic_hidden_recovery_skipped "
                    f"provider={provider} model={model} reason=visible_or_tool "
                    f"text_len={len(text_so_far.strip())} emitted_tool_use={emitted_tool_use} "
                    f"latest_tool_results={','.join(latest_names) or '-'} "
                    f"synthetic_tasklists={synthetic_count} suppressed_blocks={len(suppressed_thinking_passback_blocks)}",
                )
            return
        if not suppressed_thinking_passback_blocks:
            return
        router_log(
            "WARN",
            "anthropic_hidden_recovery_not_applicable "
            f"provider={provider} model={model} has_tasklist={has_tasklist_tool} "
            f"latest_tool_results={','.join(latest_names) or '-'} synthetic_tasklists={synthetic_count} "
            f"suppressed_blocks={len(suppressed_thinking_passback_blocks)}",
        )
        notice = empty_end_turn_notice_for_body(source_body) if source_body is not None else ""
        router_log("WARN", f"anthropic_hidden_only_stream provider={provider} model={model}")
        emit_text_block(next_content_index, notice)
        next_content_index += 1
        if notice:
            text_so_far = notice
        pending_message_delta = (
            pending_message_delta[0] if pending_message_delta is not None else "message_delta",
            patched_message_delta("end_turn"),
        )

    def append_tool_partial(tool_state: dict[str, Any], partial: Any) -> None:
        if partial is None:
            return
        if isinstance(partial, str):
            tool_state["partial_json"] = str(tool_state.get("partial_json") or "") + partial
        else:
            tool_state["partial_json"] = str(tool_state.get("partial_json") or "") + json.dumps(partial, ensure_ascii=False)

    def emit_normalized_tool_use(index: int, tool_state: dict[str, Any]) -> None:
        nonlocal emitted_tool_use
        raw_name = str(tool_state.get("name") or "")
        raw_args = str(tool_state.get("partial_json") or "")
        parsed_args = normalize_tool_arguments(raw_name, raw_args)
        if not raw_name:
            raw_name = infer_tool_name_from_args(parsed_args)
        matched_name = resolve_emitted_tool_name(raw_name, source_body)
        if not matched_name:
            matched_name = infer_tool_name_from_args(parsed_args)
        fixed_input = _validate_and_fix_tool_input(matched_name, parsed_args)
        if isinstance(source_body, dict):
            mapped_name, mapped_input = plan_mode_tool_name_for_emit(source_body, matched_name, fixed_input)
            if mapped_name is None:
                router_log(
                    "WARN",
                    f"dropped upstream tool_use before emit raw_name={raw_name!r} matched_name={matched_name!r}",
                )
                return
            matched_name, fixed_input = mapped_name, mapped_input
        fixed_input = cap_mcp_notification_wait_tool_input(matched_name, fixed_input)
        tool_id = str(tool_state.get("id") or f"toolu_anthropic_{int(time.time() * 1000)}_{index}")
        _remember_channel_injected_tool_use(source_body, tool_id, matched_name, fixed_input)
        append_tool_call_log(
            "anthropic_stream_tool_call",
            {
                "model": model,
                "raw_name": raw_name,
                "matched_name": matched_name,
                "raw_arguments": raw_args,
                "emitted_input": fixed_input,
                "sse_index": index,
            },
        )
        emit_raw(
            "content_block_start",
            json.dumps(
                {
                    "type": "content_block_start",
                    "index": index,
                    "content_block": {"type": "tool_use", "id": tool_id, "name": matched_name, "input": {}},
                },
                ensure_ascii=False,
            ),
        )
        emit_raw(
            "content_block_delta",
            json.dumps(
                {
                    "type": "content_block_delta",
                    "index": index,
                    "delta": {"type": "input_json_delta", "partial_json": json.dumps(fixed_input, ensure_ascii=False)},
                },
                ensure_ascii=False,
            ),
        )
        emit_raw("content_block_stop", json.dumps({"type": "content_block_stop", "index": index}, ensure_ascii=False))
        emitted_tool_use = True

    def process_event(event_type: str | None, data_str: str) -> None:
        nonlocal saw_message_start, saw_message_stop, text_so_far, saw_tool_use, emitted_tool_use, next_content_index, pending_message_delta, pending_message_stop
        try:
            event = json.loads(data_str)
        except Exception:
            emit_raw(event_type, data_str)
            return
        if not isinstance(event, dict):
            emit_raw(event_type, data_str)
            return
        evt_type = event.get("type") or event_type
        if evt_type == "message_start":
            saw_message_start = True
        elif evt_type == "message_stop":
            saw_message_stop = True
            pending_message_stop = (event_type, data_str)
            return
        elif evt_type == "content_block_start":
            index = event.get("index")
            content_block = event.get("content_block") if isinstance(event.get("content_block"), dict) else {}
            mapped_index: int | None = None
            if isinstance(index, int):
                if not preserve_thinking and content_block.get("type") in ANTHROPIC_THINKING_BLOCK_TYPES:
                    suppressed_content_indices.add(index)
                    suppressed_thinking_blocks[index] = dict(content_block)
                    router_log("WARN", f"suppressed Anthropic thinking response block for non-Anthropic provider model={model}")
                    emit_suppressed_keepalive(force=True)
                    return
                if index in content_index_map:
                    mapped_index = content_index_map[index]
                else:
                    mapped_index = next_content_index
                    content_index_map[index] = mapped_index
                    next_content_index += 1
                open_content_blocks.add(mapped_index)
                patched = dict(event)
                patched["index"] = mapped_index
                event = patched
                data_str = json.dumps(event, ensure_ascii=False)
            if content_block.get("type") == "tool_use":
                saw_tool_use = True
                tool_name = str(content_block.get("name") or "")
                should_buffer_tool_use = bool(
                    mapped_index is not None
                    and (normalize_tool_use or _is_mcp_notification_wait_tool(tool_name))
                )
                if should_buffer_tool_use and mapped_index is not None:
                    buffered_tool_uses[mapped_index] = {
                        "id": str(content_block.get("id") or ""),
                        "name": tool_name,
                        "partial_json": "",
                    }
                    initial_input = content_block.get("input")
                    if isinstance(initial_input, dict) and initial_input:
                        append_tool_partial(buffered_tool_uses[mapped_index], initial_input)
                    return
                emitted_tool_use = True
        elif evt_type == "content_block_stop":
            index = event.get("index")
            mapped_index = mapped_content_index(index)
            if isinstance(index, int) and mapped_index is None:
                finish_suppressed_thinking_block(index)
                return
            if mapped_index is not None:
                open_content_blocks.discard(mapped_index)
                if mapped_index in buffered_tool_uses:
                    emit_normalized_tool_use(mapped_index, buffered_tool_uses.pop(mapped_index))
                    return
                patched = dict(event)
                patched["index"] = mapped_index
                data_str = json.dumps(patched, ensure_ascii=False)
                if isinstance(mapped_index, int) and word_chunking:
                    flush_buffer(mapped_index, force=True)
                emit_raw(event_type, data_str)
                return
        elif evt_type == "message_delta":
            delta = event.get("delta") if isinstance(event.get("delta"), dict) else {}
            stop_reason = str(delta.get("stop_reason") or "")
            tool_calls = [{"type": "tool_use"}] if emitted_tool_use else []
            if (
                stop_reason == "end_turn"
                and source_body is not None
                and should_auto_continue_choice_question_with_tasklist(source_body, text_so_far, tool_calls)
            ):
                for index in list(text_buffers.keys()):
                    flush_buffer(index, force=True)
                router_log("WARN", "auto-synthesized TaskList after clarification question Anthropic-compatible stream")
                emit_tasklist_tool(next_content_index)
                next_content_index += 1
                saw_tool_use = True
                patched = dict(event)
                patched_delta = dict(delta)
                patched_delta["stop_reason"] = "tool_use"
                patched["delta"] = patched_delta
                pending_message_delta = (event_type, json.dumps(patched, ensure_ascii=False))
                return
            should_recover = source_body is not None and should_recover_empty_end_turn_with_tasklist(source_body, text_so_far, tool_calls)
            should_keep_alive = (
                source_body is not None
                and not should_recover
                and should_keep_work_alive_with_tasklist(source_body, text_so_far, tool_calls)
            )
            if should_recover or should_keep_alive:
                for index in list(text_buffers.keys()):
                    flush_buffer(index, force=True)
                reason = "empty" if should_recover else "keepalive"
                router_log(
                    "WARN",
                    f"auto-synthesized TaskList from {reason} Anthropic-compatible message_delta "
                    f"stop_reason={stop_reason or '-'}",
                )
                emit_tasklist_tool(next_content_index)
                next_content_index += 1
                saw_tool_use = True
                patched = dict(event)
                patched_delta = dict(delta)
                patched_delta["stop_reason"] = "tool_use"
                patched["delta"] = patched_delta
                pending_message_delta = (event_type, json.dumps(patched, ensure_ascii=False))
                return
            pending_message_delta = (event_type, data_str)
            return
        if evt_type == "content_block_delta":
            delta = event.get("delta") if isinstance(event.get("delta"), dict) else {}
            index = event.get("index")
            mapped_index = mapped_content_index(index)
            if isinstance(index, int) and mapped_index is None:
                append_suppressed_thinking_delta(index, delta)
                emit_suppressed_keepalive()
                return
            if not preserve_thinking and delta.get("type") in {"thinking_delta", "signature_delta"}:
                emit_suppressed_keepalive()
                return
            if isinstance(mapped_index, int) and mapped_index in buffered_tool_uses:
                if delta.get("type") == "input_json_delta":
                    append_tool_partial(buffered_tool_uses[mapped_index], delta.get("partial_json"))
                return
            if mapped_index is not None:
                patched = dict(event)
                patched["index"] = mapped_index
                event = patched
                data_str = json.dumps(event, ensure_ascii=False)
            if isinstance(mapped_index, int) and delta.get("type") == "text_delta":
                text = delta.get("text") or ""
                if not text:
                    return
                text_so_far += text
                if not word_chunking:
                    emit_raw(event_type, data_str)
                    return
                text_buffers[mapped_index] = text_buffers.get(mapped_index, "") + text
                flush_buffer(mapped_index, force=False)
                return
            emit_raw(event_type, data_str)
            return
        if evt_type == "content_block_stop":
            index = event.get("index")
            mapped_index = mapped_content_index(index)
            if isinstance(index, int) and mapped_index is None:
                finish_suppressed_thinking_block(index)
                return
            if mapped_index is not None:
                if mapped_index in buffered_tool_uses:
                    emit_normalized_tool_use(mapped_index, buffered_tool_uses.pop(mapped_index))
                    return
                patched = dict(event)
                patched["index"] = mapped_index
                event = patched
                data_str = json.dumps(event, ensure_ascii=False)
            if isinstance(mapped_index, int) and word_chunking:
                flush_buffer(mapped_index, force=True)
            emit_raw(event_type, data_str)
            return
        if evt_type == "message_stop":
            flush_suppressed_thinking_passback()
        emit_raw(event_type, data_str)

    try:
        for raw in resp:
            line = raw.decode("utf-8", errors="ignore")
            stripped = line.rstrip("\r\n")
            if stripped == "":
                if pending_event_lines:
                    data_str = "\n".join(pending_event_lines)
                    process_event(pending_event_type, data_str)
                pending_event_type = None
                pending_event_lines = []
                continue
            if stripped.startswith("event:"):
                pending_event_type = stripped[len("event:"):].strip() or None
                continue
            if stripped.startswith("data:"):
                pending_event_lines.append(stripped[len("data:"):].lstrip())
                continue
        if pending_event_lines:
            data_str = "\n".join(pending_event_lines)
            process_event(pending_event_type, data_str)
        for index in list(text_buffers.keys()):
            flush_buffer(index, force=True)
        for index in list(suppressed_thinking_blocks.keys()):
            finish_suppressed_thinking_block(index)
        recover_hidden_only_response_if_needed()
        flush_suppressed_thinking_passback()
        if pending_message_delta is not None or pending_message_stop is not None:
            emit_pending_message_end()
    except ClientStreamDisconnected as exc:
        router_log(
            "WARN",
            f"anthropic_sse_client_disconnected model={model} "
            f"text_len={len(text_so_far)} emitted_tool_use={emitted_tool_use} "
            f"suppressed_blocks={len(suppressed_thinking_passback_blocks) + len(suppressed_thinking_blocks)} "
            f"error={exc}",
        )
    except Exception as exc:
        router_log("ERROR", f"anthropic_sse_forward_error model={model} error={type(exc).__name__}: {exc}")
        try:
            if pending_event_lines:
                data_str = "\n".join(pending_event_lines)
                process_event(pending_event_type, data_str)
                pending_event_lines = []
                pending_event_type = None
            for index in list(text_buffers.keys()):
                flush_buffer(index, force=True)
            for index in list(suppressed_thinking_blocks.keys()):
                finish_suppressed_thinking_block(index)
            recover_hidden_only_response_if_needed()
            flush_suppressed_thinking_passback()
            if pending_message_delta is not None or pending_message_stop is not None:
                emit_pending_message_end()
            for index in sorted(open_content_blocks):
                emit_raw("content_block_stop", json.dumps({"type": "content_block_stop", "index": index}, ensure_ascii=False))
            open_content_blocks.clear()
            if not saw_message_stop:
                if not saw_message_start:
                    payload = {
                        "type": "message_start",
                        "message": {
                            "id": f"msg_claude_any_forward_{int(time.time() * 1000)}",
                            "type": "message",
                            "role": "assistant",
                            "content": [],
                            "model": model,
                            "stop_reason": None,
                            "stop_sequence": None,
                            "usage": {"input_tokens": 0, "output_tokens": 0},
                        },
                    }
                    emit_raw("message_start", json.dumps(payload, ensure_ascii=False))
                    emit_raw(
                        "content_block_start",
                        json.dumps({"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}, ensure_ascii=False),
                    )
                    emit_text_delta(0, f"Upstream stream error: {type(exc).__name__}: {exc}")
                    emit_raw("content_block_stop", json.dumps({"type": "content_block_stop", "index": 0}, ensure_ascii=False))
                emit_raw(
                    "message_delta",
                    json.dumps({"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": None}, "usage": {"output_tokens": 1}}, ensure_ascii=False),
                )
                emit_raw("message_stop", "{\"type\":\"message_stop\"}")
        except Exception:
            pass
    finally:
        try:
            resp.close()
        except Exception:
            pass


def _ollama_stream_to_anthropic_sse(
    handler: BaseHTTPRequestHandler,
    resp: Any,
    model: str,
    word_chunking: bool = False,
    provider: str = "ollama",
    source_body: dict[str, Any] | None = None,
    idle_timeout: float = 30.0,
) -> None:
    """Stream Ollama NDJSON /api/chat response as Anthropic SSE /v1/messages format."""
    handler.send_response(200)
    handler.send_header("content-type", "text/event-stream")
    handler.send_header("cache-control", "no-cache")
    handler.send_header("connection", "close")
    handler.end_headers()
    msg_id = f"msg_ollama_{int(time.time() * 1000)}"
    started = False
    text_started = False
    text_suppressed_for_plan = False
    next_content_index = 0
    text_index: int | None = None
    text_so_far = ""
    text_buffer = ""
    tool_calls: list[dict[str, Any]] = []
    tool_indices: list[int] = []
    stopped_tool_indices: set[int] = set()
    input_tokens = estimate_tokens(source_body) if isinstance(source_body, dict) else 0
    output_tokens = 0
    chunk: dict[str, Any] = {}
    chunks_seen = 0
    text_stopped = False
    last_activity_update = 0.0
    sse_trace = make_outgoing_sse_trace(provider, model, "ollama_stream", source_body)
    sse_trace_outcome = "started"
    sse_trace_error: str | None = None

    def emit(event_name: str, payload: dict[str, Any]) -> None:
        try:
            record_outgoing_sse_event(sse_trace, event_name, payload)
            handler.wfile.write(f"event: {event_name}\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n".encode())
            handler.wfile.flush()
        except (BrokenPipeError, ConnectionAbortedError, ConnectionResetError, OSError) as exc:
            raise UpstreamClientDisconnected(f"downstream write failed: {type(exc).__name__}: {exc}") from exc

    def ensure_message_started() -> None:
        nonlocal started
        if started:
            return
        started = True
        event = {
            "type": "message_start",
            "message": {
                "id": msg_id,
                "type": "message",
                "role": "assistant",
                "content": [],
                "model": model,
                "stop_reason": None,
                "stop_sequence": None,
                "usage": {"input_tokens": input_tokens, "output_tokens": 0},
            },
        }
        emit("message_start", event)

    def emit_text_block(index: int, text: str) -> None:
        emit("content_block_start", {"type": "content_block_start", "index": index, "content_block": {"type": "text", "text": ""}})
        if text:
            emit("content_block_delta", {"type": "content_block_delta", "index": index, "delta": {"type": "text_delta", "text": text}})
        emit("content_block_stop", {"type": "content_block_stop", "index": index})

    def update_stream_activity(force: bool = False) -> None:
        nonlocal last_activity_update
        now = time.time()
        if not force and now - last_activity_update < 0.5:
            return
        last_activity_update = now
        estimated_output = output_tokens or max(0, len(text_so_far) // 4)
        write_router_activity(
            "request",
            provider,
            model,
            tokens=input_tokens,
            output_tokens=estimated_output,
            chunks=chunks_seen,
            stream=True,
        )

    try:
        for line in iter_upstream_lines_until_client_disconnect(handler, resp, idle_timeout):
            chunks_seen += 1
            line = line.decode("utf-8", errors="ignore").strip()
            if not line:
                continue
            try:
                chunk = json.loads(line)
            except Exception:
                continue
            if not isinstance(chunk, dict):
                continue
            message = chunk.get("message") if isinstance(chunk.get("message"), dict) else {}
            input_tokens = max(input_tokens, int(chunk.get("prompt_eval_count") or 0))
            output_tokens = max(output_tokens, int(chunk.get("eval_count") or 0))
            if not started:
                ensure_message_started()
            # Handle text content
            text_chunk = message.get("content") or ""
            if text_chunk:
                if source_body is not None and not text_started and not tool_calls and should_auto_enter_plan_mode(source_body, text_so_far + text_chunk, []):
                    text_so_far += text_chunk
                    text_suppressed_for_plan = True
                    continue
                if text_suppressed_for_plan and not text_started and text_so_far:
                    pending_text = text_so_far + text_chunk
                    text_so_far = pending_text
                    text_suppressed_for_plan = False
                    text_started = True
                    text_index = next_content_index
                    next_content_index += 1
                    event = {
                        "type": "content_block_start",
                        "index": text_index,
                        "content_block": {"type": "text", "text": ""},
                    }
                    emit("content_block_start", event)
                    if word_chunking:
                        text_buffer += pending_text
                        to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
                        if to_flush:
                            event = {
                                "type": "content_block_delta",
                                "index": text_index,
                                "delta": {"type": "text_delta", "text": to_flush},
                            }
                            emit("content_block_delta", event)
                    else:
                        event = {
                            "type": "content_block_delta",
                            "index": text_index,
                            "delta": {"type": "text_delta", "text": pending_text},
                        }
                        emit("content_block_delta", event)
                    update_stream_activity()
                    continue
                if not text_started:
                    text_started = True
                    text_index = next_content_index
                    next_content_index += 1
                    event = {
                        "type": "content_block_start",
                        "index": text_index,
                        "content_block": {"type": "text", "text": ""},
                    }
                    emit("content_block_start", event)
                text_so_far += text_chunk
                if word_chunking:
                    text_buffer += text_chunk
                    to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
                    if to_flush:
                        event = {
                            "type": "content_block_delta",
                            "index": text_index,
                            "delta": {"type": "text_delta", "text": to_flush},
                        }
                        emit("content_block_delta", event)
                else:
                    event = {
                        "type": "content_block_delta",
                        "index": text_index,
                        "delta": {"type": "text_delta", "text": text_chunk},
                    }
                    emit("content_block_delta", event)
                update_stream_activity()
            # Handle tool calls
            for call in message.get("tool_calls") or []:
                fn = call.get("function") if isinstance(call.get("function"), dict) else {}
                if not isinstance(fn, dict) or not fn.get("name"):
                    continue
                raw_name = str(fn["name"])
                matched_name = resolve_emitted_tool_name(raw_name, source_body)
                raw_args = fn.get("arguments")
                normalized_args = normalize_tool_arguments(matched_name, raw_args)
                fixed_input = _validate_and_fix_tool_input(matched_name, normalized_args)
                if source_body is not None:
                    matched_name, fixed_input = plan_mode_tool_name_for_emit(source_body, matched_name, fixed_input)
                    if matched_name is None:
                        continue
                fixed_input = cap_mcp_notification_wait_tool_input(matched_name, fixed_input)
                tool_calls.append({"function": {"name": matched_name, "arguments": fixed_input}})
                tool_id = f"toolu_ollama_{int(time.time() * 1000)}_{len(tool_calls) - 1}"
                tool_index = next_content_index
                next_content_index += 1
                tool_indices.append(tool_index)
                _remember_channel_injected_tool_use(source_body, tool_id, matched_name, fixed_input)
                append_tool_call_log(
                    "ollama_stream_tool_call",
                    {
                        "model": model,
                        "raw_name": raw_name,
                        "matched_name": matched_name,
                        "raw_arguments": raw_args,
                        "normalized_arguments": normalized_args,
                        "emitted_input": fixed_input,
                        "sse_index": tool_index,
                    },
                )
                tool_event = {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {
                        "type": "tool_use",
                        "id": tool_id,
                        "name": matched_name,
                        "input": {},
                    },
                }
                emit("content_block_start", tool_event)
                delta_event = {
                    "type": "content_block_delta",
                    "index": tool_index,
                    "delta": {
                        "type": "input_json_delta",
                        "partial_json": json.dumps(fixed_input, ensure_ascii=False),
                    },
                }
                emit("content_block_delta", delta_event)
                update_stream_activity()
            update_stream_activity()
        update_stream_activity(force=True)
        # Flush any remaining buffered text when word-chunking is active
        if source_body is not None and should_auto_enter_plan_mode(source_body, text_so_far, tool_calls):
            ensure_message_started()
            router_log("WARN", "auto-synthesized EnterPlanMode from short/empty upstream stream")
            tool_calls.append({"function": {"name": "EnterPlanMode", "arguments": {}}})
            tool_id = f"toolu_ollama_plan_{int(time.time() * 1000)}"
            tool_index = next_content_index
            next_content_index += 1
            tool_indices.append(tool_index)
            tool_event = {
                "type": "content_block_start",
                "index": tool_index,
                "content_block": {
                    "type": "tool_use",
                    "id": tool_id,
                    "name": "EnterPlanMode",
                    "input": {},
                },
            }
            emit("content_block_start", tool_event)
            delta_event = {
                "type": "content_block_delta",
                "index": tool_index,
                "delta": {"type": "input_json_delta", "partial_json": "{}"},
            }
            emit("content_block_delta", delta_event)
        elif source_body is not None and should_recover_empty_end_turn_with_tasklist(source_body, text_so_far, tool_calls):
            ensure_message_started()
            router_log("WARN", "auto-synthesized TaskList from empty upstream end_turn stream")
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            tool_id = f"toolu_ollama_empty_{int(time.time() * 1000)}"
            tool_index = next_content_index
            next_content_index += 1
            tool_indices.append(tool_index)
            tool_event = {
                "type": "content_block_start",
                "index": tool_index,
                "content_block": {
                    "type": "tool_use",
                    "id": tool_id,
                    "name": "TaskList",
                    "input": {},
                },
            }
            emit("content_block_start", tool_event)
            delta_event = {
                "type": "content_block_delta",
                "index": tool_index,
                "delta": {"type": "input_json_delta", "partial_json": "{}"},
            }
            emit("content_block_delta", delta_event)
        elif text_suppressed_for_plan and not text_started and text_so_far:
            text_started = True
            text_index = next_content_index
            next_content_index += 1
            event = {
                "type": "content_block_start",
                "index": text_index,
                "content_block": {"type": "text", "text": ""},
            }
            emit("content_block_start", event)
            event = {
                "type": "content_block_delta",
                "index": text_index,
                "delta": {"type": "text_delta", "text": text_so_far},
            }
            emit("content_block_delta", event)
        if word_chunking and text_started and text_buffer:
            to_flush, text_buffer = _split_word_buffer(text_buffer, force=True)
            if to_flush:
                event = {
                    "type": "content_block_delta",
                    "index": text_index,
                    "delta": {"type": "text_delta", "text": to_flush},
                }
                emit("content_block_delta", event)
        if source_body is not None and should_keep_work_alive_with_tasklist(source_body, text_so_far, tool_calls):
            ensure_message_started()
            router_log("WARN", "auto-synthesized TaskList to keep work moving after tool result stream")
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            tool_id = f"toolu_ollama_keepalive_{int(time.time() * 1000)}"
            tool_index = next_content_index
            next_content_index += 1
            tool_indices.append(tool_index)
            tool_event = {
                "type": "content_block_start",
                "index": tool_index,
                "content_block": {
                    "type": "tool_use",
                    "id": tool_id,
                    "name": "TaskList",
                    "input": {},
                },
            }
            emit("content_block_start", tool_event)
            delta_event = {
                "type": "content_block_delta",
                "index": tool_index,
                "delta": {"type": "input_json_delta", "partial_json": "{}"},
            }
            emit("content_block_delta", delta_event)
        if source_body is not None and should_auto_continue_choice_question_with_tasklist(source_body, text_so_far, tool_calls):
            ensure_message_started()
            router_log("WARN", "auto-synthesized TaskList after clarification question stream")
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            tool_id = f"toolu_ollama_choice_{int(time.time() * 1000)}"
            tool_index = next_content_index
            next_content_index += 1
            tool_indices.append(tool_index)
            tool_event = {
                "type": "content_block_start",
                "index": tool_index,
                "content_block": {
                    "type": "tool_use",
                    "id": tool_id,
                    "name": "TaskList",
                    "input": {},
                },
            }
            emit("content_block_start", tool_event)
            delta_event = {
                "type": "content_block_delta",
                "index": tool_index,
                "delta": {"type": "input_json_delta", "partial_json": "{}"},
            }
            emit("content_block_delta", delta_event)
        # Send content_block_stop for text if any
        if text_started:
            event = {"type": "content_block_stop", "index": text_index}
            emit("content_block_stop", event)
            text_stopped = True
        # Send content_block_stop for each tool call
        for tool_index in tool_indices:
            event = {"type": "content_block_stop", "index": tool_index}
            emit("content_block_stop", event)
            stopped_tool_indices.add(tool_index)
        if not started:
            ensure_message_started()
        if not text_started and not tool_indices:
            router_log("WARN", f"ollama_empty_stream provider={provider} model={model} chunks={chunks_seen}")
            write_router_activity("error", provider, model, error="empty_stream", stream=True)
            empty_index = next_content_index
            next_content_index += 1
            notice = empty_end_turn_notice_for_body(source_body) if source_body is not None else ""
            if notice:
                text_so_far = notice
            emit_text_block(empty_index, notice)
        # Determine stop reason
        stop_reason = "tool_use" if tool_calls else "end_turn"
        if chunk.get("done_reason") == "length":
            stop_reason = "max_tokens"
        # Send message_delta with final stop_reason
        event = {
            "type": "message_delta",
            "delta": {"stop_reason": stop_reason, "stop_sequence": None},
            "usage": {"output_tokens": output_tokens},
        }
        emit("message_delta", event)
        # Send message_stop
        emit("message_stop", {"type": "message_stop"})
        sse_trace_outcome = "success"
        if text_started or tool_indices:
            write_router_activity(
                "success",
                provider,
                model,
                tokens=input_tokens,
                output_tokens=output_tokens or max(1, len(text_so_far) // 4),
                chunks=chunks_seen,
                stream=True,
            )
    except UpstreamClientDisconnected as exc:
        sse_trace_outcome = "client_disconnected"
        sse_trace_error = f"{type(exc).__name__}: {exc}"
        router_log(
            "WARN",
            f"ollama_stream_client_disconnected provider={provider} model={model} "
            f"chunks={chunks_seen} text_len={len(text_so_far)} error={exc}",
        )
        write_router_activity(
            "cancel",
            provider,
            model,
            error=type(exc).__name__,
            tokens=input_tokens,
            output_tokens=output_tokens or max(0, len(text_so_far) // 4),
            chunks=chunks_seen,
            stream=True,
        )
    except Exception as exc:
        sse_trace_outcome = "error"
        sse_trace_error = f"{type(exc).__name__}: {exc}"
        router_log("ERROR", f"ollama_stream_error provider={provider} model={model} error={type(exc).__name__}: {exc}")
        write_router_activity("error", provider, model, error=type(exc).__name__, stream=True)
        try:
            ensure_message_started()
            if word_chunking and text_started and text_buffer:
                to_flush, text_buffer = _split_word_buffer(text_buffer, force=True)
                if to_flush and text_index is not None:
                    emit("content_block_delta", {"type": "content_block_delta", "index": text_index, "delta": {"type": "text_delta", "text": to_flush}})
            if text_started and text_index is not None and not text_stopped:
                emit("content_block_stop", {"type": "content_block_stop", "index": text_index})
            for tool_index in tool_indices:
                if tool_index not in stopped_tool_indices:
                    emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})
            if not text_started and not tool_indices:
                error_index = next_content_index
                next_content_index += 1
                emit_text_block(error_index, "")
            emit("message_delta", {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": None}, "usage": {"output_tokens": max(1, output_tokens or len(text_so_far) // 4)}})
            emit("message_stop", {"type": "message_stop"})
        except Exception:
            pass
    finally:
        try:
            resp.close()
        except Exception:
            pass
        try:
            final_stop_reason = locals().get("stop_reason")
            finish_outgoing_sse_trace(
                sse_trace,
                outcome=sse_trace_outcome,
                text_len=len(text_so_far),
                tool_call_count=len(tool_calls),
                chunks=chunks_seen,
                stop_reason=final_stop_reason if isinstance(final_stop_reason, str) else None,
                error=sse_trace_error,
            )
            dump_response_for_trace(
                provider=provider,
                model=model,
                text_so_far=text_so_far,
                tool_calls=tool_calls,
                stop_reason=final_stop_reason if isinstance(final_stop_reason, str) else None,
                input_tokens=input_tokens,
                output_tokens=output_tokens,
                last_chunk=chunk if isinstance(chunk, dict) else None,
            )
        except Exception:
            pass


def forward_ollama_api_chat(handler: BaseHTTPRequestHandler, provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> None:
    _update_tool_schema_registry(body.get("tools"))
    body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
    model = resolve_requested_model(provider, pcfg, body.get("model"))
    base = pcfg.get("base_url", "").rstrip("/")
    original_body = body
    upstream_body = body_with_advisor_tool(body, pcfg) if advisor_provider_supported(provider) else body
    stream_requested = body.get("stream", True)
    if not bool(pcfg.get("stream_enabled", True)):
        stream_requested = False
    if stream_requested and advisor_model_enabled(pcfg) and advisor_provider_supported(provider):
        stream_requested = False
        router_log("INFO", "advisor tool enabled; collecting this turn so advisor tool calls can be resolved internally")
    if stream_requested and advisor_gate_possible_for_body(provider, pcfg, body):
        gate_reason = advisor_gate_reason_for_body(provider, pcfg, body)
        stream_requested = False
        router_log("INFO", f"advisor gate enabled reason={gate_reason}; collecting this turn before returning it to Claude Code")
    word_chunking = bool(pcfg.get("stream_word_chunking", False))
    req_body = ollama_chat_request(model, upstream_body, pcfg, stream=stream_requested)
    headers = provider_headers(provider, pcfg)
    url = join_url(base, "/api/chat")
    waited, rpm_used, rpm_limit = apply_router_rate_limit(provider, pcfg, model)
    rpm_status = bool(pcfg.get("rate_limit_status", False))
    if stream_requested:
        # Stream Ollama response through as Anthropic SSE
        data_bytes = json.dumps(req_body).encode("utf-8")
        req_tokens = estimate_tokens(req_body)
        req_bytes = len(data_bytes)
        gateway_retries = configured_gateway_retries(pcfg)
        max_attempts = max(1, gateway_retries + 1)
        resp = None
        stream_idle_timeout = provider_stream_idle_timeout_seconds(pcfg)
        for attempt in range(max_attempts):
            req = urllib.request.Request(url, data=data_bytes, headers=headers, method="POST")
            try:
                write_router_activity(
                    "request",
                    provider,
                    model,
                    attempt=attempt + 1,
                    total=max_attempts,
                    tokens=req_tokens,
                    bytes=req_bytes,
                    timeout=ollama_request_timeout_seconds(pcfg),
                    stream=True,
                )
                router_log("INFO", f"ollama_stream_request provider={provider} model={model} attempt={attempt + 1}/{max_attempts} tokens={req_tokens} bytes={req_bytes}")
                resp = urllib.request.urlopen(req, timeout=ollama_request_timeout_seconds(pcfg))
                set_upstream_stream_read_timeout(resp, upstream_stream_poll_timeout_seconds(stream_idle_timeout))
                learn_router_rate_limit_headers(provider, pcfg, model, resp.headers)
                break
            except urllib.error.HTTPError as exc:
                raw = exc.read().decode("utf-8", errors="ignore")
                learn_router_rate_limit_headers(provider, pcfg, model, exc.headers)
                if exc.code == 429 and attempt + 1 < max_attempts:
                    retry_no = attempt + 1
                    wait = register_router_rate_limit_backoff(provider, pcfg, model, exc.headers.get("Retry-After"))
                    write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, wait=wait, tokens=req_tokens, bytes=req_bytes, stream=True)
                    router_log("WARN", f"ollama_stream_rate_limit_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} wait={wait:.2f}s tokens={req_tokens} bytes={req_bytes}")
                    if not sleep_until_or_client_disconnect(handler, wait):
                        write_router_activity("cancel", provider, model, stage="rate_limit_retry_wait", tokens=req_tokens, bytes=req_bytes, stream=True)
                        router_log("WARN", f"ollama_stream_cancelled_before_rate_limit_retry provider={provider} model={model} tokens={req_tokens} bytes={req_bytes}")
                        return
                    continue
                if exc.code in UPSTREAM_RETRY_HTTP_CODES and attempt + 1 < max_attempts:
                    retry_no = attempt + 1
                    write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, tokens=req_tokens, bytes=req_bytes, stream=True)
                    router_log("WARN", f"ollama_stream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} code={exc.code} tokens={req_tokens} bytes={req_bytes}")
                    if not sleep_until_or_client_disconnect(handler, upstream_retry_wait_seconds(retry_no)):
                        write_router_activity("cancel", provider, model, stage="http_retry_wait", tokens=req_tokens, bytes=req_bytes, stream=True)
                        router_log("WARN", f"ollama_stream_cancelled_before_http_retry provider={provider} model={model} tokens={req_tokens} bytes={req_bytes}")
                        return
                    continue
                if router_client_connection_closed(handler):
                    write_router_activity("cancel", provider, model, stage="http_error", code=exc.code, tokens=req_tokens, bytes=req_bytes, stream=True)
                    router_log("WARN", f"ollama_stream_client_gone_before_error_response provider={provider} model={model} code={exc.code}")
                    return
                write_router_activity("error", provider, model, code=exc.code, tokens=req_tokens, bytes=req_bytes, stream=True)
                write_json(
                    handler,
                    {"type": "error", "error": {"type": "upstream_error", "message": upstream_http_error_message(exc, raw)}},
                    exc.code,
                )
                return
            except (TimeoutError, urllib.error.URLError, OSError) as exc:
                if retryable_upstream_exception(exc) and attempt + 1 < max_attempts:
                    retry_no = attempt + 1
                    write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes, stream=True)
                    router_log("WARN", f"ollama_stream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} error={type(exc).__name__} tokens={req_tokens} bytes={req_bytes}")
                    if not sleep_until_or_client_disconnect(handler, upstream_retry_wait_seconds(retry_no)):
                        write_router_activity("cancel", provider, model, stage="exception_retry_wait", error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes, stream=True)
                        router_log("WARN", f"ollama_stream_cancelled_before_exception_retry provider={provider} model={model} error={type(exc).__name__}")
                        return
                    continue
                if router_client_connection_closed(handler):
                    write_router_activity("cancel", provider, model, stage="exception_error", error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes, stream=True)
                    router_log("WARN", f"ollama_stream_client_gone_before_exception_response provider={provider} model={model} error={type(exc).__name__}")
                    return
                write_router_activity("error", provider, model, error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes, stream=True)
                write_json(
                    handler,
                    {"type": "error", "error": {"type": "upstream_error", "message": f"{type(exc).__name__}: {exc}"}},
                    504 if retryable_upstream_exception(exc) else 502,
                )
                return
        if resp is None:
            write_router_activity("error", provider, model, tokens=req_tokens, bytes=req_bytes, stream=True)
            write_json(
                handler,
                {"type": "error", "error": {"type": "upstream_error", "message": "upstream stream request failed"}},
                504,
            )
            return
        # Check if Claude Code requested SSE streaming
        accept = handler.headers.get("accept", "")
        if "text/event-stream" in accept or stream_requested:
            _ollama_stream_to_anthropic_sse(handler, resp, model, word_chunking=word_chunking, provider=provider, source_body=original_body, idle_timeout=stream_idle_timeout)
        else:
            # Non-SSE client but streaming from Ollama: collect full response
            chunks = []
            try:
                for line in iter_upstream_lines_until_client_disconnect(handler, resp, stream_idle_timeout):
                    chunks.append(line)
            except UpstreamClientDisconnected as exc:
                write_router_activity("cancel", provider, model, stage="collect_stream", error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes, stream=True)
                router_log("WARN", f"ollama_stream_collect_client_disconnected provider={provider} model={model} error={exc}")
                try:
                    resp.close()
                except Exception:
                    pass
                return
            resp.close()
            full = b"".join(chunks).decode("utf-8", errors="ignore")
            data = None
            for line in full.splitlines():
                line = line.strip()
                if not line:
                    continue
                try:
                    chunk = json.loads(line)
                    if isinstance(chunk, dict) and chunk.get("done"):
                        data = chunk
                except Exception:
                    continue
            if data is None:
                data = {"message": {"content": ""}, "done": True, "done_reason": "end_turn"}
            message = ollama_chat_to_anthropic(data, model, source_body=original_body)
            message = refine_message_with_advisor(provider, pcfg, original_body, message, model)
            remember_channel_injected_tool_uses(original_body, message)
            message = prepend_anthropic_text(message, rate_limit_notice(waited, rpm_used, rpm_limit, rpm_status))
            write_json(handler, message)
        return
    # Non-streaming fallback
    data_bytes = json.dumps(req_body).encode("utf-8")
    req_tokens = estimate_tokens(req_body)
    req_bytes = len(data_bytes)
    gateway_retries = configured_gateway_retries(pcfg)
    max_attempts = max(1, gateway_retries + 1)
    data = None
    for attempt in range(max_attempts):
        req = urllib.request.Request(url, data=data_bytes, headers=headers, method="POST")
        try:
            write_router_activity(
                "request",
                provider,
                model,
                attempt=attempt + 1,
                total=max_attempts,
                tokens=req_tokens,
                bytes=req_bytes,
                timeout=ollama_request_timeout_seconds(pcfg),
            )
            router_log("INFO", f"ollama_request provider={provider} model={model} attempt={attempt + 1}/{max_attempts} tokens={req_tokens} bytes={req_bytes}")
            with urllib.request.urlopen(req, timeout=ollama_request_timeout_seconds(pcfg)) as resp:
                learn_router_rate_limit_headers(provider, pcfg, model, resp.headers)
                data = json.loads(resp.read().decode("utf-8"))
                break
        except urllib.error.HTTPError as exc:
            raw = exc.read().decode("utf-8", errors="ignore")
            learn_router_rate_limit_headers(provider, pcfg, model, exc.headers)
            if exc.code == 429 and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                wait = register_router_rate_limit_backoff(provider, pcfg, model, exc.headers.get("Retry-After"))
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, wait=wait, tokens=req_tokens, bytes=req_bytes)
                router_log("WARN", f"ollama_rate_limit_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} wait={wait:.2f}s tokens={req_tokens} bytes={req_bytes}")
                if not sleep_until_or_client_disconnect(handler, wait):
                    write_router_activity("cancel", provider, model, stage="rate_limit_retry_wait", tokens=req_tokens, bytes=req_bytes)
                    router_log("WARN", f"ollama_cancelled_before_rate_limit_retry provider={provider} model={model} tokens={req_tokens} bytes={req_bytes}")
                    return
                continue
            if exc.code in UPSTREAM_RETRY_HTTP_CODES and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, tokens=req_tokens, bytes=req_bytes)
                router_log("WARN", f"ollama_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} code={exc.code} tokens={req_tokens} bytes={req_bytes}")
                if not sleep_until_or_client_disconnect(handler, upstream_retry_wait_seconds(retry_no)):
                    write_router_activity("cancel", provider, model, stage="http_retry_wait", tokens=req_tokens, bytes=req_bytes)
                    router_log("WARN", f"ollama_cancelled_before_http_retry provider={provider} model={model} tokens={req_tokens} bytes={req_bytes}")
                    return
                continue
            if router_client_connection_closed(handler):
                write_router_activity("cancel", provider, model, stage="http_error", code=exc.code, tokens=req_tokens, bytes=req_bytes)
                router_log("WARN", f"ollama_client_gone_before_error_response provider={provider} model={model} code={exc.code}")
                return
            write_router_activity("error", provider, model, code=exc.code, tokens=req_tokens, bytes=req_bytes)
            write_json(
                handler,
                {"type": "error", "error": {"type": "upstream_error", "message": upstream_http_error_message(exc, raw)}},
                exc.code,
            )
            return
        except (TimeoutError, urllib.error.URLError, OSError) as exc:
            if retryable_upstream_exception(exc) and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes)
                router_log("WARN", f"ollama_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} error={type(exc).__name__} tokens={req_tokens} bytes={req_bytes}")
                if not sleep_until_or_client_disconnect(handler, upstream_retry_wait_seconds(retry_no)):
                    write_router_activity("cancel", provider, model, stage="exception_retry_wait", error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes)
                    router_log("WARN", f"ollama_cancelled_before_exception_retry provider={provider} model={model} error={type(exc).__name__}")
                    return
                continue
            if router_client_connection_closed(handler):
                write_router_activity("cancel", provider, model, stage="exception_error", error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes)
                router_log("WARN", f"ollama_client_gone_before_exception_response provider={provider} model={model} error={type(exc).__name__}")
                return
            write_router_activity("error", provider, model, error=type(exc).__name__, tokens=req_tokens, bytes=req_bytes)
            write_json(
                handler,
                {"type": "error", "error": {"type": "upstream_error", "message": f"{type(exc).__name__}: {exc}"}},
                504 if retryable_upstream_exception(exc) else 502,
            )
            return
    if data is None:
        write_router_activity("error", provider, model, tokens=req_tokens, bytes=req_bytes)
        write_json(
            handler,
            {"type": "error", "error": {"type": "upstream_error", "message": "upstream request failed"}},
            504,
        )
        return
    message = ollama_chat_to_anthropic(data, model, source_body=original_body)
    message = refine_message_with_advisor(provider, pcfg, original_body, message, model)
    remember_channel_injected_tool_uses(original_body, message)
    message = prepend_anthropic_text(message, rate_limit_notice(waited, rpm_used, rpm_limit, rpm_status))
    write_json(handler, message)


def openai_chat_to_anthropic(data: dict[str, Any], model: str, source_body: dict[str, Any] | None = None) -> dict[str, Any]:
    choice = {}
    choices = data.get("choices")
    if isinstance(choices, list) and choices:
        choice = choices[0] if isinstance(choices[0], dict) else {}
    message = choice.get("message") if isinstance(choice.get("message"), dict) else {}
    wrapped = {
        "message": {
            "content": message.get("content") or "",
            "tool_calls": message.get("tool_calls") or [],
        },
        "done_reason": "length" if choice.get("finish_reason") == "length" else "stop",
    }
    usage = data.get("usage") if isinstance(data.get("usage"), dict) else {}
    wrapped["prompt_eval_count"] = positive_int(usage.get("prompt_tokens")) or (estimate_tokens(source_body) if isinstance(source_body, dict) else 0)
    wrapped["eval_count"] = positive_int(usage.get("completion_tokens")) or 0
    out = ollama_chat_to_anthropic(wrapped, model, source_body=source_body)
    thinking_block = openai_reasoning_to_anthropic_thinking_block(message.get("reasoning_content"))
    if thinking_block is None:
        return out
    content = out.get("content")
    if not isinstance(content, list):
        content = [{"type": "text", "text": anthropic_content_to_text(content)}]
    out = dict(out)
    out["content"] = [thinking_block] + content
    return out


def stream_openai_chat_to_anthropic_sse(
    handler: BaseHTTPRequestHandler,
    resp: Any,
    model: str,
    provider: str,
    source_body: dict[str, Any] | None = None,
    start_index: int = 0,
    word_chunking: bool = False,
    input_tokens: int | None = None,
    input_bytes: int | None = None,
) -> bool:
    next_content_index = start_index
    text_started = False
    text_suppressed_for_plan = False
    text_index: int | None = None
    text_so_far = ""
    pseudo_text = ""
    pseudo_mode = False
    text_buffer = ""
    text_stopped = False
    reasoning_started = False
    reasoning_stopped = False
    reasoning_index: int | None = None
    reasoning_so_far = ""
    tool_fragments: dict[int, dict[str, Any]] = {}
    output_tokens = 0
    finish_reason = "stop"
    chunks_seen = 0
    last_activity_update = 0.0

    def emit(event_name: str, payload: dict[str, Any]) -> None:
        handler.wfile.write(f"event: {event_name}\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n".encode())
        handler.wfile.flush()

    def ensure_text_started() -> int:
        nonlocal text_started, text_index, next_content_index, text_stopped
        if text_started and text_index is not None:
            return text_index
        text_started = True
        text_stopped = False
        text_index = next_content_index
        next_content_index += 1
        emit(
            "content_block_start",
            {"type": "content_block_start", "index": text_index, "content_block": {"type": "text", "text": ""}},
        )
        return text_index

    def ensure_reasoning_started() -> int:
        nonlocal reasoning_started, reasoning_index, next_content_index, reasoning_stopped
        if reasoning_started and reasoning_index is not None:
            return reasoning_index
        reasoning_started = True
        reasoning_stopped = False
        reasoning_index = next_content_index
        next_content_index += 1
        emit(
            "content_block_start",
            {
                "type": "content_block_start",
                "index": reasoning_index,
                "content_block": {"type": "thinking", "thinking": ""},
            },
        )
        return reasoning_index

    def emit_reasoning_delta(text: str) -> None:
        if not text:
            return
        idx = ensure_reasoning_started()
        emit(
            "content_block_delta",
            {"type": "content_block_delta", "index": idx, "delta": {"type": "thinking_delta", "thinking": text}},
        )

    def close_reasoning_block() -> None:
        nonlocal reasoning_stopped
        if not reasoning_started or reasoning_index is None or reasoning_stopped:
            return
        digest = hashlib.sha256(reasoning_so_far.encode("utf-8", errors="replace")).hexdigest()[:24]
        emit(
            "content_block_delta",
            {
                "type": "content_block_delta",
                "index": reasoning_index,
                "delta": {
                    "type": "signature_delta",
                    "signature": f"claude-any-openai-reasoning-{digest}",
                },
            },
        )
        emit("content_block_stop", {"type": "content_block_stop", "index": reasoning_index})
        reasoning_stopped = True

    def emit_text_delta(text: str) -> None:
        if not text:
            return
        idx = ensure_text_started()
        emit(
            "content_block_delta",
            {"type": "content_block_delta", "index": idx, "delta": {"type": "text_delta", "text": text}},
        )

    def update_stream_activity(force: bool = False) -> None:
        nonlocal last_activity_update
        now = time.time()
        if not force and now - last_activity_update < 0.5:
            return
        last_activity_update = now
        estimated_output = output_tokens or max(0, len(text_so_far) // 4)
        write_router_activity(
            "request",
            provider,
            model,
            tokens=input_tokens,
            bytes=input_bytes,
            output_tokens=estimated_output,
            chunks=chunks_seen,
            stream=True,
        )

    try:
        for raw_line in resp:
            chunks_seen += 1
            line = raw_line.decode("utf-8", errors="ignore").strip()
            if not line or line.startswith(":"):
                continue
            if line.startswith("data:"):
                line = line[5:].strip()
            if not line or line == "[DONE]":
                break
            try:
                event = json.loads(line)
            except Exception:
                continue
            if not isinstance(event, dict):
                continue
            usage = event.get("usage")
            if isinstance(usage, dict):
                output_tokens = max(output_tokens, positive_int(usage.get("completion_tokens")) or 0)
            choices = event.get("choices")
            if not isinstance(choices, list) or not choices:
                continue
            choice = choices[0] if isinstance(choices[0], dict) else {}
            if choice.get("finish_reason"):
                finish_reason = str(choice.get("finish_reason"))
            delta = choice.get("delta") if isinstance(choice.get("delta"), dict) else {}
            reasoning_chunk = delta.get("reasoning_content") or ""
            if reasoning_chunk:
                reasoning_so_far += str(reasoning_chunk)
                emit_reasoning_delta(str(reasoning_chunk))
                update_stream_activity()
            text_chunk = delta.get("content") or ""
            if text_chunk:
                close_reasoning_block()
                if pseudo_mode or PSEUDO_TOOL_START in text_chunk:
                    before, sep, after = text_chunk.partition(PSEUDO_TOOL_START)
                    if before and not pseudo_mode:
                        text_so_far += before
                        if word_chunking:
                            text_buffer += before
                            to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
                            emit_text_delta(to_flush)
                        else:
                            emit_text_delta(before)
                    pseudo_mode = True
                    pseudo_text += (sep + after) if sep else text_chunk
                    if PSEUDO_TOOL_END in pseudo_text:
                        pseudo_mode = False
                    continue
                if source_body is not None and not text_started and not tool_fragments and should_auto_enter_plan_mode(source_body, text_so_far + text_chunk, []):
                    text_so_far += text_chunk
                    text_suppressed_for_plan = True
                    continue
                if text_suppressed_for_plan and not text_started and text_so_far:
                    pending_text = text_so_far + text_chunk
                    text_so_far = pending_text
                    text_suppressed_for_plan = False
                    if word_chunking:
                        text_buffer += pending_text
                        to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
                        emit_text_delta(to_flush)
                    else:
                        emit_text_delta(pending_text)
                    update_stream_activity()
                    continue
                text_so_far += text_chunk
                if word_chunking:
                    text_buffer += text_chunk
                    to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
                    emit_text_delta(to_flush)
                else:
                    emit_text_delta(text_chunk)
                update_stream_activity()
            for call in delta.get("tool_calls") or []:
                if not isinstance(call, dict):
                    continue
                try:
                    call_index = int(call.get("index"))
                except Exception:
                    call_index = len(tool_fragments)
                slot = tool_fragments.setdefault(call_index, {"id": "", "name": "", "arguments": ""})
                if call.get("id"):
                    slot["id"] = str(call.get("id"))
                fn = call.get("function") if isinstance(call.get("function"), dict) else {}
                if fn.get("name"):
                    slot["name"] += str(fn.get("name"))
                if fn.get("arguments"):
                    slot["arguments"] += str(fn.get("arguments"))
                update_stream_activity()
        update_stream_activity(force=True)
        if word_chunking and text_buffer:
            to_flush, text_buffer = _split_word_buffer(text_buffer, force=True)
            emit_text_delta(to_flush)
        close_reasoning_block()

        tool_calls: list[dict[str, Any]] = []
        _, pseudo_tool_calls = parse_pseudo_tool_calls(pseudo_text)
        for i, pseudo in enumerate(pseudo_tool_calls):
            fn = pseudo.get("function") if isinstance(pseudo, dict) else {}
            if isinstance(fn, dict):
                tool_fragments.setdefault(100000 + i, {
                    "id": str(pseudo.get("id") or ""),
                    "name": str(fn.get("name") or ""),
                    "arguments": json.dumps(fn.get("arguments") or {}, ensure_ascii=False),
                })
        for _, fragment in sorted(tool_fragments.items()):
            raw_name = str(fragment.get("name") or "")
            if not raw_name:
                continue
            matched_name = resolve_emitted_tool_name(raw_name, source_body)
            normalized_args = normalize_tool_arguments(matched_name, fragment.get("arguments") or {})
            fixed_input = _validate_and_fix_tool_input(matched_name, normalized_args)
            if source_body is not None:
                matched_name, fixed_input = plan_mode_tool_name_for_emit(source_body, matched_name, fixed_input)
                if matched_name is None:
                    continue
            fixed_input = cap_mcp_notification_wait_tool_input(matched_name, fixed_input)
            tool_calls.append({"function": {"name": matched_name, "arguments": fixed_input}})
            tool_index = next_content_index
            next_content_index += 1
            tool_id = str(fragment.get("id") or f"toolu_openai_{int(time.time() * 1000)}_{tool_index}")
            _remember_channel_injected_tool_use(source_body, tool_id, matched_name, fixed_input)
            append_tool_call_log(
                "openai_stream_tool_call",
                {
                    "model": model,
                    "raw_name": raw_name,
                    "matched_name": matched_name,
                    "raw_arguments": fragment.get("arguments"),
                    "emitted_input": fixed_input,
                    "sse_index": tool_index,
                },
            )
            emit(
                "content_block_start",
                {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {"type": "tool_use", "id": tool_id, "name": matched_name, "input": {}},
                },
            )
            emit(
                "content_block_delta",
                {
                    "type": "content_block_delta",
                    "index": tool_index,
                    "delta": {"type": "input_json_delta", "partial_json": json.dumps(fixed_input, ensure_ascii=False)},
                },
            )
            emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})

        if source_body is not None and should_auto_enter_plan_mode(source_body, text_so_far, tool_calls):
            router_log("WARN", "auto-synthesized EnterPlanMode from short/empty upstream OpenAI stream")
            tool_index = next_content_index
            next_content_index += 1
            tool_calls.append({"function": {"name": "EnterPlanMode", "arguments": {}}})
            emit(
                "content_block_start",
                {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {"type": "tool_use", "id": f"toolu_openai_plan_{int(time.time() * 1000)}", "name": "EnterPlanMode", "input": {}},
                },
            )
            emit("content_block_delta", {"type": "content_block_delta", "index": tool_index, "delta": {"type": "input_json_delta", "partial_json": "{}"}})
            emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})
        elif source_body is not None and should_recover_empty_end_turn_with_tasklist(source_body, text_so_far, tool_calls):
            router_log("WARN", "auto-synthesized TaskList from empty upstream end_turn OpenAI stream")
            tool_index = next_content_index
            next_content_index += 1
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            emit(
                "content_block_start",
                {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {"type": "tool_use", "id": f"toolu_openai_empty_{int(time.time() * 1000)}", "name": "TaskList", "input": {}},
                },
            )
            emit("content_block_delta", {"type": "content_block_delta", "index": tool_index, "delta": {"type": "input_json_delta", "partial_json": "{}"}})
            emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})
        elif text_suppressed_for_plan and not text_started and text_so_far:
            emit_text_delta(text_so_far)

        if source_body is not None and should_keep_work_alive_with_tasklist(source_body, text_so_far, tool_calls):
            router_log("WARN", "auto-synthesized TaskList to keep work moving after OpenAI stream")
            tool_index = next_content_index
            next_content_index += 1
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            emit(
                "content_block_start",
                {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {"type": "tool_use", "id": f"toolu_openai_keepalive_{int(time.time() * 1000)}", "name": "TaskList", "input": {}},
                },
            )
            emit("content_block_delta", {"type": "content_block_delta", "index": tool_index, "delta": {"type": "input_json_delta", "partial_json": "{}"}})
            emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})

        if source_body is not None and should_auto_continue_choice_question_with_tasklist(source_body, text_so_far, tool_calls):
            router_log("WARN", "auto-synthesized TaskList after clarification question OpenAI stream")
            tool_index = next_content_index
            next_content_index += 1
            tool_calls.append({"function": {"name": "TaskList", "arguments": {}}})
            emit(
                "content_block_start",
                {
                    "type": "content_block_start",
                    "index": tool_index,
                    "content_block": {"type": "tool_use", "id": f"toolu_openai_choice_{int(time.time() * 1000)}", "name": "TaskList", "input": {}},
                },
            )
            emit("content_block_delta", {"type": "content_block_delta", "index": tool_index, "delta": {"type": "input_json_delta", "partial_json": "{}"}})
            emit("content_block_stop", {"type": "content_block_stop", "index": tool_index})

        if text_started and text_index is not None:
            emit("content_block_stop", {"type": "content_block_stop", "index": text_index})
            text_stopped = True
        if not text_started and not tool_calls:
            text_so_far = empty_end_turn_notice_for_body(source_body) if source_body is not None else ""
            if source_body is not None:
                router_log(
                    "WARN",
                    f"openai_empty_end_turn_notice provider={provider} model={model} "
                    f"latest_tool_results={','.join(latest_user_tool_result_names(source_body)) or '-'}",
                )
            emit_text_delta(text_so_far)
            if text_index is not None:
                emit("content_block_stop", {"type": "content_block_stop", "index": text_index})
                text_stopped = True
        stop_reason = "tool_use" if tool_calls else ("max_tokens" if finish_reason == "length" else "end_turn")
        write_anthropic_open_stream_stop(handler, {"stop_reason": stop_reason, "usage": {"output_tokens": output_tokens or max(1, len(text_so_far) // 4)}})
        return True
    except Exception as exc:
        router_log("ERROR", f"openai_stream_error provider={provider} model={model} error={type(exc).__name__}: {exc}")
        write_router_activity("error", provider, model, error=type(exc).__name__, stream=True)
        try:
            if word_chunking and text_buffer:
                to_flush, text_buffer = _split_word_buffer(text_buffer, force=True)
                emit_text_delta(to_flush)
            if not text_started:
                emit_text_delta(f"Upstream stream error: {type(exc).__name__}: {exc}")
            if text_started and text_index is not None and not text_stopped:
                emit("content_block_stop", {"type": "content_block_stop", "index": text_index})
                text_stopped = True
            write_anthropic_open_stream_stop(
                handler,
                {"stop_reason": "end_turn", "usage": {"output_tokens": output_tokens or max(1, len(text_so_far) // 4)}},
            )
        except Exception:
            pass
        return False
    finally:
        try:
            resp.close()
        except Exception:
            pass


def upstream_http_error_message(exc: urllib.error.HTTPError, raw: str | None = None) -> str:
    if raw is None:
        raw = exc.read().decode("utf-8", errors="ignore")
    msg = raw.strip() or str(exc)
    error_type = ""
    try:
        err = json.loads(raw)
        if isinstance(err, dict):
            if isinstance(err.get("error"), dict):
                error_obj = err["error"]
                error_type = str(error_obj.get("type") or "").strip()
                msg = str(error_obj.get("message") or error_obj)
            elif err.get("error"):
                msg = str(err["error"])
            elif err.get("message"):
                msg = str(err["message"])
                error_type = str(err.get("type") or "").strip()
    except Exception:
        pass
    if error_type and error_type not in msg:
        msg = f"{error_type}: {msg}"
    retry_after = first_header(exc.headers, ["Retry-After", "retry-after"])
    if retry_after:
        retry_after_text = retry_after.strip()
        retry_after_seconds = parse_retry_after_seconds(retry_after_text)
        if retry_after_seconds is not None:
            retry_after_display = format_duration_seconds(retry_after_seconds)
            if retry_after_text and re.fullmatch(r"\d+(?:\.\d+)?", retry_after_text):
                msg = f"{msg} Retry-After: {retry_after_display} ({retry_after_text}s)"
            else:
                msg = f"{msg} Retry-After: {retry_after_display}"
        else:
            msg = f"{msg} Retry-After: {retry_after_text}"
    return msg


UPSTREAM_RETRY_HTTP_CODES: frozenset[int] = frozenset({502, 503, 504})


def upstream_retry_message(attempt: int, total: int) -> str:
    lang = str(load_config().get("language") or "en")
    if lang == "ko":
        return f"서버가 응답하지 않아 재시도합니다 ({attempt}/{total})."
    if lang == "ja":
        return f"サーバーが応答しないため再試行します ({attempt}/{total})。"
    if lang == "zh":
        return f"服务器未响应，正在重试 ({attempt}/{total})。"
    return f"Upstream server did not respond; retrying ({attempt}/{total})."


def upstream_rate_limit_retry_message(attempt: int, total: int) -> str:
    lang = str(load_config().get("language") or "en")
    if lang == "ko":
        return f"Upstream rate limit에 도달해 대기 후 재시도합니다 ({attempt}/{total})."
    if lang == "ja":
        return f"Upstream rate limit に達したため、待機して再試行します ({attempt}/{total})。"
    if lang == "zh":
        return f"已达到 upstream rate limit，等待后重试 ({attempt}/{total})。"
    return f"Upstream rate limit reached; waiting before retry ({attempt}/{total})."


def upstream_retry_wait_seconds(attempt: int) -> float:
    return min(20.0, 2.0 * max(1, attempt))


def retryable_upstream_exception(exc: BaseException) -> bool:
    text = f"{type(exc).__name__}: {exc}".lower()
    markers = (
        "timed out",
        "timeout",
        "connection aborted",
        "connection was aborted",
        "connection reset",
        "connection refused",
        "remote end closed connection",
        "remote disconnected",
        "eof occurred in violation of protocol",
        "temporarily unavailable",
        "broken pipe",
    )
    return any(marker in text for marker in markers)


def configured_gateway_retries(pcfg: dict[str, Any]) -> int:
    value = pcfg.get("gateway_retries")
    if value is None:
        return 2
    try:
        retries = int(value)
    except Exception:
        return 2
    return max(0, retries)


def post_json_with_rate_retry(
    url: str,
    req_body: Any,
    headers: dict[str, str],
    timeout: float,
    provider: str,
    pcfg: dict[str, Any],
    model: str,
    retry_notice: Callable[[str], None] | None = None,
    *,
    retry_rate_limits: bool = True,
) -> Any:
    gateway_retries = configured_gateway_retries(pcfg)
    max_attempts = max(1, gateway_retries + 1)
    rate_limit_max_attempts = max(max_attempts, provider_api_key_count(provider, pcfg))
    token_estimate = estimate_tokens(req_body)
    byte_estimate = len(json.dumps(req_body, ensure_ascii=False).encode("utf-8"))
    for attempt in range(rate_limit_max_attempts):
        try:
            write_router_activity(
                "request",
                provider,
                model,
                attempt=attempt + 1,
                total=max_attempts,
                tokens=token_estimate,
                bytes=byte_estimate,
                timeout=timeout,
            )
            router_log("INFO", f"upstream_request provider={provider} model={model} attempt={attempt + 1}/{max_attempts} tokens={token_estimate} bytes={byte_estimate} timeout={timeout}")
            data_bytes = json.dumps(req_body).encode("utf-8")
            req = urllib.request.Request(url, data=data_bytes, headers=headers, method="POST")
            with provider_urlopen(req, timeout=timeout, provider=provider, pcfg=pcfg) as resp:
                learn_router_rate_limit_headers(provider, pcfg, model, resp.headers)
                data = json.loads(resp.read().decode("utf-8"))
                write_router_activity("success", provider, model, attempt=attempt + 1, tokens=token_estimate, bytes=byte_estimate)
                return data
        except urllib.error.HTTPError as exc:
            raw = exc.read().decode("utf-8", errors="ignore")
            learn_router_rate_limit_headers(provider, pcfg, model, exc.headers)
            if exc.code == 429:
                register_api_key_cooldown(provider, pcfg, key_from_request_headers(headers), exc.headers)
            if (
                exc.code == 429
                and retry_rate_limits
                and provider_api_key_count(provider, pcfg) > 1
                and provider_has_live_api_key(provider, pcfg)
                and attempt + 1 < rate_limit_max_attempts
            ):
                retry_no = attempt + 1
                headers = provider_headers(provider, pcfg)
                next_hash = hashlib.sha256(key_from_request_headers(headers).encode("utf-8")).hexdigest()[:12]
                write_router_activity("retry", provider, model, attempt=retry_no, total=rate_limit_max_attempts - 1, code=exc.code, wait=0, tokens=token_estimate, bytes=byte_estimate)
                router_log("WARN", f"upstream_rate_limit_key_retry provider={provider} model={model} attempt={retry_no}/{rate_limit_max_attempts - 1} next_key_hash={next_hash} tokens={token_estimate} bytes={byte_estimate}")
                continue
            if exc.code == 429 and retry_rate_limits and attempt + 1 < max_attempts:
                skip_retry, retry_after_seconds = retry_after_exceeds_request_timeout(exc.headers, timeout)
                if skip_retry:
                    write_router_activity("error", provider, model, code=exc.code, retry_after=retry_after_seconds, tokens=token_estimate, bytes=byte_estimate)
                    router_log(
                        "WARN",
                        f"upstream_rate_limit_no_retry provider={provider} model={model} retry_after={retry_after_seconds:.2f}s timeout={timeout:.2f}s tokens={token_estimate} bytes={byte_estimate}",
                    )
                    raise RuntimeError(upstream_http_error_message(exc, raw)) from exc
                retry_no = attempt + 1
                wait = register_router_rate_limit_backoff(provider, pcfg, model, exc.headers.get("Retry-After"))
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, wait=wait, tokens=token_estimate, bytes=byte_estimate)
                router_log("WARN", f"upstream_rate_limit_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} wait={wait:.2f}s tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_rate_limit_retry_message(retry_no, gateway_retries))
                time.sleep(wait)
                # The just-failed key is now resting; re-pick so the retry uses a live key.
                headers = provider_headers(provider, pcfg)
                continue
            if exc.code in UPSTREAM_RETRY_HTTP_CODES and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, tokens=token_estimate, bytes=byte_estimate)
                router_log("WARN", f"upstream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} code={exc.code} tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_retry_message(retry_no, gateway_retries))
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            write_router_activity("error", provider, model, code=exc.code, tokens=token_estimate, bytes=byte_estimate)
            raise RuntimeError(upstream_http_error_message(exc, raw)) from exc
        except (TimeoutError, urllib.error.URLError) as exc:
            if retryable_upstream_exception(exc) and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, error=type(exc).__name__, tokens=token_estimate, bytes=byte_estimate)
                router_log("WARN", f"upstream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} error={type(exc).__name__} tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_retry_message(retry_no, gateway_retries))
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            write_router_activity("error", provider, model, error=type(exc).__name__, tokens=token_estimate, bytes=byte_estimate)
            raise RuntimeError(f"{type(exc).__name__}: {exc}") from exc
    raise RuntimeError("upstream request failed")


def open_provider_request_with_key_retry(
    url: str,
    req_body: Any,
    headers: dict[str, str],
    timeout: float,
    provider: str,
    pcfg: dict[str, Any],
    model: str,
    *,
    stream: bool = False,
    retry_rate_limits: bool = True,
) -> Any:
    gateway_retries = configured_gateway_retries(pcfg)
    max_attempts = max(1, gateway_retries + 1)
    rate_limit_max_attempts = max(max_attempts, provider_api_key_count(provider, pcfg))
    token_estimate = estimate_tokens(req_body)
    byte_estimate = len(json.dumps(req_body, ensure_ascii=False).encode("utf-8"))
    data_bytes = json.dumps(req_body).encode("utf-8")
    for attempt in range(rate_limit_max_attempts):
        try:
            write_router_activity(
                "request",
                provider,
                model,
                attempt=attempt + 1,
                total=max_attempts,
                tokens=token_estimate,
                bytes=byte_estimate,
                timeout=timeout,
                stream=stream,
            )
            router_log("INFO", f"upstream_direct_request provider={provider} model={model} attempt={attempt + 1}/{max_attempts} tokens={token_estimate} bytes={byte_estimate} timeout={timeout}")
            req = urllib.request.Request(url, data=data_bytes, headers=headers, method="POST")
            resp = provider_urlopen(req, timeout=timeout, provider=provider, pcfg=pcfg)
            learn_router_rate_limit_headers(provider, pcfg, model, resp.headers)
            return resp
        except urllib.error.HTTPError as exc:
            learn_router_rate_limit_headers(provider, pcfg, model, exc.headers)
            if exc.code == 429:
                register_api_key_cooldown(provider, pcfg, key_from_request_headers(headers), exc.headers)
            if (
                exc.code == 429
                and retry_rate_limits
                and provider_api_key_count(provider, pcfg) > 1
                and provider_has_live_api_key(provider, pcfg)
                and attempt + 1 < rate_limit_max_attempts
            ):
                retry_no = attempt + 1
                headers = provider_headers(provider, pcfg)
                next_hash = hashlib.sha256(key_from_request_headers(headers).encode("utf-8")).hexdigest()[:12]
                write_router_activity("retry", provider, model, attempt=retry_no, total=rate_limit_max_attempts - 1, code=exc.code, wait=0, tokens=token_estimate, bytes=byte_estimate, stream=stream)
                router_log("WARN", f"upstream_direct_rate_limit_key_retry provider={provider} model={model} attempt={retry_no}/{rate_limit_max_attempts - 1} next_key_hash={next_hash} tokens={token_estimate} bytes={byte_estimate}")
                continue
            if exc.code in UPSTREAM_RETRY_HTTP_CODES and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, tokens=token_estimate, bytes=byte_estimate, stream=stream)
                router_log("WARN", f"upstream_direct_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} code={exc.code} tokens={token_estimate} bytes={byte_estimate}")
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            raise
        except (TimeoutError, urllib.error.URLError) as exc:
            if retryable_upstream_exception(exc) and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, error=type(exc).__name__, tokens=token_estimate, bytes=byte_estimate, stream=stream)
                router_log("WARN", f"upstream_direct_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} error={type(exc).__name__} tokens={token_estimate} bytes={byte_estimate}")
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            raise RuntimeError(f"{type(exc).__name__}: {exc}") from exc
    raise RuntimeError("upstream direct request failed")


def open_openai_stream_with_rate_retry(
    url: str,
    req_body: Any,
    headers: dict[str, str],
    timeout: float,
    provider: str,
    pcfg: dict[str, Any],
    model: str,
    retry_notice: Callable[[str], None] | None = None,
    *,
    retry_rate_limits: bool = True,
) -> Any:
    gateway_retries = configured_gateway_retries(pcfg)
    max_attempts = max(1, gateway_retries + 1)
    rate_limit_max_attempts = max(max_attempts, provider_api_key_count(provider, pcfg))
    token_estimate = estimate_tokens(req_body)
    byte_estimate = len(json.dumps(req_body, ensure_ascii=False).encode("utf-8"))
    data_bytes = json.dumps(req_body).encode("utf-8")
    for attempt in range(rate_limit_max_attempts):
        try:
            write_router_activity(
                "request",
                provider,
                model,
                attempt=attempt + 1,
                total=max_attempts,
                tokens=token_estimate,
                bytes=byte_estimate,
                timeout=timeout,
                stream=True,
            )
            router_log("INFO", f"upstream_stream_request provider={provider} model={model} attempt={attempt + 1}/{max_attempts} tokens={token_estimate} bytes={byte_estimate} timeout={timeout}")
            req = urllib.request.Request(url, data=data_bytes, headers=headers, method="POST")
            resp = provider_urlopen(req, timeout=timeout, provider=provider, pcfg=pcfg)
            set_upstream_stream_read_timeout(resp, provider_stream_idle_timeout_seconds(pcfg))
            learn_router_rate_limit_headers(provider, pcfg, model, resp.headers)
            return resp
        except urllib.error.HTTPError as exc:
            raw = exc.read().decode("utf-8", errors="ignore")
            learn_router_rate_limit_headers(provider, pcfg, model, exc.headers)
            if exc.code == 429:
                register_api_key_cooldown(provider, pcfg, key_from_request_headers(headers), exc.headers)
            if (
                exc.code == 429
                and retry_rate_limits
                and provider_api_key_count(provider, pcfg) > 1
                and provider_has_live_api_key(provider, pcfg)
                and attempt + 1 < rate_limit_max_attempts
            ):
                retry_no = attempt + 1
                headers = provider_headers(provider, pcfg)
                next_hash = hashlib.sha256(key_from_request_headers(headers).encode("utf-8")).hexdigest()[:12]
                write_router_activity("retry", provider, model, attempt=retry_no, total=rate_limit_max_attempts - 1, code=exc.code, wait=0, tokens=token_estimate, bytes=byte_estimate, stream=True)
                router_log("WARN", f"upstream_stream_rate_limit_key_retry provider={provider} model={model} attempt={retry_no}/{rate_limit_max_attempts - 1} next_key_hash={next_hash} tokens={token_estimate} bytes={byte_estimate}")
                continue
            if exc.code == 429 and retry_rate_limits and attempt + 1 < max_attempts:
                skip_retry, retry_after_seconds = retry_after_exceeds_request_timeout(exc.headers, timeout)
                if skip_retry:
                    write_router_activity("error", provider, model, code=exc.code, retry_after=retry_after_seconds, tokens=token_estimate, bytes=byte_estimate, stream=True)
                    router_log(
                        "WARN",
                        f"upstream_stream_rate_limit_no_retry provider={provider} model={model} retry_after={retry_after_seconds:.2f}s timeout={timeout:.2f}s tokens={token_estimate} bytes={byte_estimate}",
                    )
                    raise RuntimeError(upstream_http_error_message(exc, raw)) from exc
                retry_no = attempt + 1
                wait = register_router_rate_limit_backoff(provider, pcfg, model, exc.headers.get("Retry-After"))
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, wait=wait, tokens=token_estimate, bytes=byte_estimate, stream=True)
                router_log("WARN", f"upstream_stream_rate_limit_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} wait={wait:.2f}s tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_rate_limit_retry_message(retry_no, gateway_retries))
                time.sleep(wait)
                # The just-failed key is now resting; re-pick so the retry uses a live key.
                headers = provider_headers(provider, pcfg)
                continue
            if exc.code in UPSTREAM_RETRY_HTTP_CODES and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, code=exc.code, tokens=token_estimate, bytes=byte_estimate, stream=True)
                router_log("WARN", f"upstream_stream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} code={exc.code} tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_retry_message(retry_no, gateway_retries))
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            write_router_activity("error", provider, model, code=exc.code, tokens=token_estimate, bytes=byte_estimate, stream=True)
            raise RuntimeError(upstream_http_error_message(exc, raw)) from exc
        except (TimeoutError, urllib.error.URLError) as exc:
            if retryable_upstream_exception(exc) and attempt + 1 < max_attempts:
                retry_no = attempt + 1
                write_router_activity("retry", provider, model, attempt=retry_no, total=gateway_retries, error=type(exc).__name__, tokens=token_estimate, bytes=byte_estimate, stream=True)
                router_log("WARN", f"upstream_stream_retry provider={provider} model={model} attempt={retry_no}/{gateway_retries} error={type(exc).__name__} tokens={token_estimate} bytes={byte_estimate}")
                if retry_notice:
                    retry_notice(upstream_retry_message(retry_no, gateway_retries))
                time.sleep(upstream_retry_wait_seconds(retry_no))
                continue
            write_router_activity("error", provider, model, error=type(exc).__name__, tokens=token_estimate, bytes=byte_estimate, stream=True)
            raise RuntimeError(f"{type(exc).__name__}: {exc}") from exc
    raise RuntimeError("upstream stream request failed")


def forward_openai_compatible_chat(handler: BaseHTTPRequestHandler, provider: str, pcfg: dict[str, Any], body: dict[str, Any]) -> None:
    _update_tool_schema_registry(body.get("tools"))
    body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
    model = resolve_requested_model(provider, pcfg, body.get("model"))
    if provider == "nvidia-hosted":
        model = ncp_model_id_for_nvidia_hosted(model)
    original_body = body
    upstream_body = body_with_advisor_tool(body, pcfg) if advisor_provider_supported(provider) else body
    url = join_url(provider_upstream_request_base(provider, pcfg), "/v1/chat/completions")
    waited, rpm_used, rpm_limit = apply_router_rate_limit(provider, pcfg, model)
    compatibility_test = str(handler.headers.get(COMPATIBILITY_TEST_HEADER) or "").strip().lower() in ("1", "true", "yes", "on")
    stream_enabled = bool(pcfg.get("stream_enabled", True))
    stream = True if provider == "nvidia-hosted" else bool(body.get("stream", stream_enabled)) and stream_enabled
    if stream and advisor_model_enabled(pcfg) and advisor_provider_supported(provider):
        stream = False
        router_log("INFO", f"advisor tool enabled for {provider}; collecting this turn so advisor tool calls can be resolved internally")
    if stream and advisor_gate_possible_for_body(provider, pcfg, body):
        gate_reason = advisor_gate_reason_for_body(provider, pcfg, body)
        stream = False
        router_log("INFO", f"advisor gate enabled for {provider} reason={gate_reason}; collecting this turn before returning it to Claude Code")
    notice = rate_limit_notice(waited, rpm_used, rpm_limit, bool(pcfg.get("rate_limit_status", False)))
    if stream:
        req_body = openai_compatible_chat_request(provider, model, upstream_body, pcfg, stream=True)
        req_tokens = estimate_tokens(req_body)
        req_bytes = len(json.dumps(req_body, ensure_ascii=False).encode("utf-8"))
        write_anthropic_open_stream_start(handler, model, input_tokens=req_tokens)
        index = 0
        if notice:
            index = write_anthropic_stream_blocks(handler, [{"type": "text", "text": notice}], index)
        try:
            def emit_retry_notice(text: str) -> None:
                nonlocal index
                index = write_anthropic_stream_blocks(handler, [{"type": "text", "text": text + "\n"}], index)

            resp = open_openai_stream_with_rate_retry(
                url,
                req_body,
                provider_headers(provider, pcfg),
                provider_request_timeout_seconds(pcfg),
                provider,
                pcfg,
                model,
                emit_retry_notice,
                retry_rate_limits=not compatibility_test,
            )
            stream_ok = stream_openai_chat_to_anthropic_sse(
                handler,
                resp,
                model,
                provider,
                source_body=original_body,
                start_index=index,
                word_chunking=bool(pcfg.get("stream_word_chunking", False)),
                input_tokens=req_tokens,
                input_bytes=req_bytes,
            )
            if stream_ok:
                write_router_activity("success", provider, model, tokens=req_tokens, bytes=req_bytes, stream=True)
        except RuntimeError as exc:
            msg = str(exc)
            write_anthropic_stream_blocks(handler, [{"type": "text", "text": f"Upstream error: {msg}"}], index)
            write_anthropic_open_stream_stop(handler)
            return
        except Exception as exc:
            msg = f"{type(exc).__name__}: {exc}"
            write_router_activity("error", provider, model, error=type(exc).__name__, stream=True)
            write_anthropic_stream_blocks(handler, [{"type": "text", "text": f"Upstream error: {msg}"}], index)
            write_anthropic_open_stream_stop(handler)
            return
        return
    req_body = openai_compatible_chat_request(provider, model, upstream_body, pcfg, stream=False)
    try:
        data = post_json_with_rate_retry(
            url,
            req_body,
            provider_headers(provider, pcfg),
            provider_request_timeout_seconds(pcfg),
            provider,
            pcfg,
            model,
            None,
            retry_rate_limits=not compatibility_test,
        )
    except RuntimeError as exc:
        write_json(handler, {"type": "error", "error": {"type": "upstream_error", "message": str(exc)}}, 500)
        return
    message = openai_chat_to_anthropic(data, model, source_body=original_body)
    message = refine_message_with_advisor(provider, pcfg, original_body, message, model)
    remember_channel_injected_tool_uses(original_body, message)
    message = prepend_anthropic_text(message, notice)
    write_anthropic_message_response(handler, message, stream)


class RouterHandler(BaseHTTPRequestHandler):
    server_version = "claude-any/0.1"

    def send_response(self, code: int, message: str | None = None) -> None:
        try:
            self._claude_any_response_status = int(code)
        except Exception:
            self._claude_any_response_status = None
        super().send_response(code, message)

    def log_message(self, fmt: str, *args: Any) -> None:
        try:
            router_log("INFO", "access " + (fmt % args))
        except Exception:
            pass

    def log_error(self, fmt: str, *args: Any) -> None:
        try:
            router_log("ERROR", "http " + (fmt % args))
        except Exception:
            pass

    def do_HEAD(self) -> None:
        cfg = load_config()
        if reject_external_router_request(self, cfg):
            return
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path
        if path in ("/", "/health", "/healthz"):
            self.send_response(200)
            self.send_header("content-type", "text/plain; charset=utf-8")
            self.end_headers()
            return
        self.send_response(404)
        self.send_header("content-type", "application/json")
        self.end_headers()

    def do_GET(self) -> None:
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path
        query = urllib.parse.parse_qs(parsed.query)
        cfg = load_config()
        if reject_external_router_request(self, cfg):
            return
        if handle_events_get(self, path, query):
            return
        if handle_llm_config_get(self, path):
            return
        if handle_channel_mcp_get(self, path):
            return
        if handle_web_get(self, path):
            return
        if handle_chat_get(self, path) or handle_plan_get(self, path):
            return
        provider, pcfg = get_current_provider(cfg)
        if path == "/":
            write_text_response(self, render_router_home_html(cfg, provider, pcfg), content_type="text/html; charset=utf-8")
            return
        if path in ("/health", "/healthz"):
            write_json(
                self,
                {
                    "ok": True,
                    "version": VERSION,
                    "source_fingerprint": SOURCE_FINGERPRINT,
                    "pid": os.getpid(),
                    "user": getpass.getuser(),
                    "home": str(HOME),
                    "config_dir": str(CONFIG_DIR),
                    "router_port": ROUTER_PORT,
                    "provider": provider,
                    "model": current_alias(cfg),
                    "web_chat": "/ca/web/chat",
                    "chat": "/ca/chat/health",
                    "plan": "/ca/plan/artifacts",
                    "events": "/ca/events",
                },
            )
            return
        if path == "/v1/models":
            data = list_model_objects_for_request(provider, pcfg, self.headers)
            write_json(self, {"object": "list", "data": data, "has_more": False})
            return
        if path.startswith("/v1/models/"):
            mid = urllib.parse.unquote(path[len("/v1/models/"):])
            resolved = resolve_requested_model(provider, pcfg, mid)
            write_json(self, model_object(provider, resolved))
            return
        write_json(self, {"type": "error", "error": {"type": "not_found_error", "message": path}}, 404)

    def do_POST(self) -> None:
        path = urllib.parse.urlparse(self.path).path
        cfg = load_config()
        if reject_external_router_request(self, cfg):
            return
        length = int(self.headers.get("content-length", "0") or 0)
        raw = self.rfile.read(length) if length else b"{}"
        body = parse_json_body(raw)
        if handle_llm_config_post(self, path, body):
            return
        if handle_channel_mcp_post(self, path, body):
            return
        if handle_chat_post(self, path, body) or handle_plan_post(self, path, body):
            return
        provider, pcfg = get_current_provider(cfg)
        if path == "/v1/messages/count_tokens":
            tokens = estimate_tokens(body)
            write_context_usage(provider, pcfg, body, "count_tokens")
            write_json(self, {"input_tokens": tokens})
            return
        if path != "/v1/messages":
            write_json(self, {"type": "error", "error": {"type": "not_found_error", "message": path}}, 404)
            return
        _update_tool_schema_registry(body.get("tools"))
        body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
        request_id = f"{os.getpid()}-{time.time_ns()}"
        EVENT_BUS.publish(
            level="info",
            category="router.request",
            message="Anthropic messages request received",
            request_id=request_id,
            provider=provider,
            model=str(body.get("model") or ""),
            data={
                "path": path,
                "messages": len(body.get("messages") or []),
                "tools": len(body.get("tools") or []),
                **router_event_message_preview(body, cfg),
            },
        )
        dump_request_for_trace(provider, path, body)
        if maybe_handle_plan_mode_tool_choice(self, provider, pcfg, body):
            EVENT_BUS.publish(level="info", category="plan_mode.short_circuit", message="plan mode tool choice handled locally", request_id=request_id, provider=provider, model=str(body.get("model") or ""))
            return
        body = filter_blocked_tools(provider, pcfg, body)
        body = normalize_tool_choice_for_provider(provider, pcfg, body)
        write_context_usage(provider, pcfg, body, "messages")
        if maybe_handle_router_debug_request(self, body):
            EVENT_BUS.publish(level="info", category="router_debug.short_circuit", message="router debug request handled locally", request_id=request_id, provider=provider, model=str(body.get("model") or ""))
            return
        if maybe_handle_advisor_request(self, provider, pcfg, body):
            EVENT_BUS.publish(level="info", category="advisor.short_circuit", message="advisor request handled locally", request_id=request_id, provider=provider, model=str(body.get("model") or ""))
            return
        body = strip_autonomous_advisor_server_tools(provider, body)
        body = body_with_pending_channel_messages(body)
        body = body_with_pending_channel_summaries(body)
        body = body_with_channel_tool_result_context(body)
        body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
        body = normalize_tool_choice_for_provider(provider, pcfg, body)
        router_log("DEBUG", f"POST {path} provider={provider} model={body.get('model')} tools={len(body.get('tools') or [])} msgs={len(body.get('messages') or [])}")
        try:
            if provider in ("ollama", "ollama-cloud"):
                EVENT_BUS.publish(level="info", category="upstream.request", message="forwarding to Ollama-compatible provider", request_id=request_id, provider=provider, model=str(body.get("model") or ""))
                forward_ollama_api_chat(self, provider, pcfg, body)
                commit_pending_channel_delivery_cursors(body, self)
                return
            if provider in OPENCODE_PROVIDER_NAMES:
                upstream_model = resolve_requested_model(provider, pcfg, body.get("model"))
                endpoint_kind = opencode_endpoint_kind(provider, upstream_model, pcfg)
                provider_label = PROVIDER_LABELS.get(provider, provider)
                if endpoint_kind == "openai-chat":
                    EVENT_BUS.publish(
                        level="info",
                        category="upstream.request",
                        message=f"forwarding to {provider_label} chat-compatible provider",
                        request_id=request_id,
                        provider=provider,
                        model=upstream_model,
                    )
                    forward_openai_compatible_chat(self, provider, pcfg, body)
                    commit_pending_channel_delivery_cursors(body, self)
                    return
                if endpoint_kind not in ("anthropic-messages",):
                    write_json(
                        self,
                        {
                            "type": "error",
                            "error": {
                                "type": "unsupported_model_endpoint",
                                "message": (
                                    f"{provider_label} model {upstream_model!r} uses the {endpoint_kind} endpoint family. "
                                    f"claude-any currently routes {provider_label} /v1/messages and /v1/chat/completions models."
                                ),
                            },
                        },
                        400,
                    )
                    return
            if provider_openai_router_enabled(provider, pcfg):
                EVENT_BUS.publish(level="info", category="upstream.request", message="forwarding to OpenAI-compatible provider", request_id=request_id, provider=provider, model=str(body.get("model") or ""))
                forward_openai_compatible_chat(self, provider, pcfg, body)
                commit_pending_channel_delivery_cursors(body, self)
                return
            body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
            body = normalize_anthropic_system_role_messages(body)
            body = cap_anthropic_body_for_provider(provider, pcfg, body)
            body = apply_provider_request_options(provider, pcfg, body)
            body = rehydrate_suppressed_thinking_passback(provider, pcfg, body)
            upstream_model = resolve_requested_model(provider, pcfg, body.get("model"))
            if provider == "nvidia-hosted":
                upstream_model = ncp_model_id_for_nvidia_hosted(upstream_model)
            body["model"] = upstream_model
            body = resolve_tool_model_references(provider, pcfg, body)
            body = normalize_anthropic_model_request_options(provider, pcfg, body, upstream_model)
            stream_enabled = bool(pcfg.get("stream_enabled", True))
            word_chunking = bool(pcfg.get("stream_word_chunking", False))
            if not stream_enabled:
                body["stream"] = False
            upstream_body = body_without_claude_any_internal_metadata(body)
            base = native_anthropic_base_url(provider, pcfg) if provider_native_compat_enabled(provider, pcfg) else provider_upstream_request_base(provider, pcfg)
            url = join_url(base, "/v1/messages")
            upstream_query = upstream_messages_query(pcfg, self.path)
            if upstream_query:
                url = f"{url}?{upstream_query}"
            headers = provider_headers(provider, pcfg, self.headers)
            for h in ("anthropic-beta", "anthropic-dangerous-direct-browser-access"):
                if self.headers.get(h):
                    headers[h] = self.headers[h]
            waited, rpm_used, rpm_limit = apply_router_rate_limit(provider, pcfg, upstream_model)
            try:
                EVENT_BUS.publish(level="info", category="upstream.request", message="forwarding to Anthropic-compatible provider", request_id=request_id, provider=provider, model=upstream_model, data={"url": url, "stream": bool(body.get("stream", stream_enabled))})
                resp = open_provider_request_with_key_retry(
                    url,
                    upstream_body,
                    headers,
                    provider_request_timeout_seconds(pcfg),
                    provider,
                    pcfg,
                    upstream_model,
                    stream=bool(body.get("stream", stream_enabled)),
                )
                if bool(body.get("stream", stream_enabled)):
                    set_upstream_stream_read_timeout(resp, provider_stream_idle_timeout_seconds(pcfg))
                status = getattr(resp, "status", 200)
                ctype = resp.headers.get("content-type", "application/json")
                if stream_enabled and "text/event-stream" in ctype:
                    self.send_response(status)
                    self.send_header("content-type", ctype)
                    self.send_header("cache-control", "no-cache")
                    self.send_header("connection", "close")
                    self.end_headers()
                    _rebatch_anthropic_sse_text(
                        self,
                        resp,
                        upstream_model,
                        word_chunking=word_chunking,
                        source_body=body,
                        preserve_thinking=preserves_anthropic_thinking_contract(provider, pcfg),
                        normalize_tool_use=not preserves_anthropic_thinking_contract(provider, pcfg),
                        provider=provider,
                    )
                else:
                    self.send_response(status)
                    self.send_header("content-type", ctype)
                    self.end_headers()
                    raw_resp = resp.read()
                    notice = rate_limit_notice(waited, rpm_used, rpm_limit, bool(pcfg.get("rate_limit_status", False)))
                    if "application/json" in ctype:
                        try:
                            payload = json.loads(raw_resp.decode("utf-8", errors="replace"))
                            if isinstance(payload, dict):
                                payload = normalize_response_thinking_for_non_anthropic_provider(provider, pcfg, payload, upstream_model)
                                payload = append_synthetic_tasklist_to_message(payload, upstream_model, body, "native_json")
                                if notice:
                                    payload = prepend_anthropic_text(payload, notice)
                                raw_resp = json.dumps(payload, ensure_ascii=False).encode("utf-8")
                        except Exception:
                            pass
                    self.wfile.write(raw_resp)
                    self.wfile.flush()
                commit_pending_channel_delivery_cursors(body, self)
            except urllib.error.HTTPError as e:
                err = e.read()
                if e.code == 429:
                    register_api_key_cooldown(provider, pcfg, key_from_request_headers(headers), e.headers)
                EVENT_BUS.publish(level="error", category="upstream.error", message=f"upstream HTTP {e.code}", request_id=request_id, provider=provider, model=upstream_model, data={"status": e.code})
                self.send_response(e.code)
                self.send_header("content-type", e.headers.get("content-type", "application/json"))
                self.end_headers()
                self.wfile.write(err)
        except Exception as exc:
            EVENT_BUS.publish(level="error", category="router.error", message=str(exc), request_id=request_id, provider=provider, model=str(body.get("model") or ""), data={"error_type": type(exc).__name__})
            write_json(self, {"type": "error", "error": {"type": "api_error", "message": str(exc)}}, 500)


def serve(_: argparse.Namespace) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    cfg = load_config()
    reset_api_key_cooldowns_for_router_start()
    bind_host = router_bind_host(cfg)
    PID_PATH.write_text(str(os.getpid()))
    os.chmod(PID_PATH, 0o600)
    lvl = current_log_level()
    src = "file" if LOG_LEVEL_PATH.exists() else ("env" if os.environ.get("CLAUDE_ANY_LOG_LEVEL") else "default")
    sys.stderr.write(
        f"claude-any router starting on {bind_host}:{ROUTER_PORT} "
        f"(client base {ROUTER_BASE}, log level {LOG_LEVEL_NAMES.get(lvl, lvl)}, source={src})\n"
    )
    sys.stderr.flush()
    server = ThreadingHTTPServer((bind_host, ROUTER_PORT), RouterHandler)
    start_managed_router_lifetime_watchdog(server)
    channel_start_thread = threading.Thread(
        target=lambda: start_router_managed_channel_sse(cfg),
        daemon=True,
        name="ca-router-channel-sse-start",
    )
    channel_start_thread.start()
    try:
        server.serve_forever()
    finally:
        stop_channel_sse_connection(None)
        try:
            PID_PATH.unlink()
        except FileNotFoundError:
            pass


def router_health() -> dict[str, Any] | None:
    try:
        data = http_json(f"{ROUTER_BASE}/health", timeout=1.0)
        return data if isinstance(data, dict) else None
    except Exception:
        return None


def router_up() -> bool:
    return router_health() is not None


def router_health_matches_current(health: dict[str, Any] | None) -> bool:
    if health is None:
        return False
    if str(health.get("version") or "") != VERSION:
        return False
    if str(health.get("source_fingerprint") or "") != SOURCE_FINGERPRINT:
        return False
    if str(health.get("user") or "") != getpass.getuser():
        return False
    if not router_health_config_matches_current(health):
        return False
    return True


def _path_identity_text(value: Any) -> str:
    text = str(value or "").strip()
    if not text:
        return ""
    try:
        return str(Path(text).expanduser().resolve(strict=False))
    except Exception:
        return text


def router_health_config_matches_current(health: dict[str, Any] | None) -> bool:
    if not isinstance(health, dict):
        return False
    return _path_identity_text(health.get("config_dir")) == _path_identity_text(CONFIG_DIR)


def router_health_has_foreign_config(health: dict[str, Any] | None) -> bool:
    if not isinstance(health, dict):
        return False
    config_dir = _path_identity_text(health.get("config_dir"))
    return bool(config_dir) and config_dir != _path_identity_text(CONFIG_DIR)


def invalid_nvidia_hosted_base_url(value: str | None) -> bool:
    text = (value or "").strip()
    if not text or text.startswith("nv" + "api-") or not text.startswith(("http://", "https://")):
        return True
    parsed = urllib.parse.urlparse(text)
    return (parsed.hostname or "") in ("127.0.0.1", "localhost")


def ensure_nvidia_hosted_base_url(pcfg: dict[str, Any]) -> bool:
    if invalid_nvidia_hosted_base_url(pcfg.get("base_url")):
        pcfg["base_url"] = nvidia_upstream_base_url()
        return True
    return False


def store_nvidia_api_key(key: str) -> None:
    env = read_env_file(NCP_ENV)
    env["NVIDIA_API_KEY"] = key
    env.setdefault("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1")
    env.setdefault("PROXY_HOST", "127.0.0.1")
    env.setdefault("PROXY_PORT", "8788")
    env.setdefault("STORAGE_ENGINE", "sqlite")
    NCP_ENV.parent.mkdir(parents=True, exist_ok=True)
    NCP_ENV.write_text("".join(f"{k}={v}\n" for k, v in env.items()))
    os.chmod(NCP_ENV, 0o600)


def clear_nvidia_api_key() -> None:
    env = read_env_file(NCP_ENV)
    if "NVIDIA_API_KEY" not in env:
        return
    env.pop("NVIDIA_API_KEY", None)
    NCP_ENV.parent.mkdir(parents=True, exist_ok=True)
    NCP_ENV.write_text("".join(f"{k}={v}\n" for k, v in env.items()))
    os.chmod(NCP_ENV, 0o600)


def set_provider_config(provider: str) -> list[str]:
    cfg = load_config()
    cfg["current_provider"] = provider
    pcfg = cfg["providers"][provider]
    if provider == "anthropic":
        pcfg["route_through_router"] = False
    fixed_base = ensure_nvidia_hosted_base_url(pcfg) if provider == "nvidia-hosted" else False
    save_config(cfg)
    clear_model_cache()
    lines = [f"Provider set to {provider} ({PROVIDER_LABELS[provider]})."]
    if provider == "anthropic":
        lines.append("mode: anthropic-native")
    if fixed_base:
        lines.append(f"Base URL set to {pcfg['base_url']} for NVIDIA hosted.")
    return lines


def set_provider_choice_config(choice: str) -> list[str]:
    cfg = load_config()
    if choice in (ANTHROPIC_NATIVE_PROVIDER_CHOICE, ANTHROPIC_ROUTED_PROVIDER_CHOICE):
        cfg["current_provider"] = "anthropic"
        pcfg = cfg["providers"]["anthropic"]
        routed = choice == ANTHROPIC_ROUTED_PROVIDER_CHOICE
        pcfg["route_through_router"] = routed
        save_config(cfg)
        clear_model_cache()
        if routed:
            lines = [
                "Provider set to anthropic (Anthropic routed).",
                "mode: anthropic-routed",
            ]
            if not provider_has_api_key("anthropic", pcfg):
                lines.append("Anthropic routed mode will use Claude Code OAuth/API auth headers when available.")
            return lines
        return [
            "Provider set to anthropic (Claude Native).",
            "mode: anthropic-native",
            "Claude Code OAuth/Max can be used directly, but claude-any router features such as /advisor are unavailable.",
        ]
    return set_provider_config(choice)


def set_base_url_config(provider: str, url: str) -> list[str]:
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    if provider == "nvidia-hosted" and invalid_nvidia_hosted_base_url(url):
        url = nvidia_upstream_base_url()
    old_url = str(pcfg.get("base_url") or "").rstrip("/")
    new_url = url.rstrip("/")
    pcfg["base_url"] = new_url
    reset_model = old_url != new_url
    if reset_model:
        pcfg["current_model"] = ""
        pcfg["custom_models"] = []
    lines = [f"Base URL for {provider} set to {pcfg['base_url']}."]
    if reset_model:
        lines.append("Model selection was reset because the provider endpoint changed.")
        detected_native, detect_reason = auto_detect_native_compat_for_base_url(provider, pcfg)
        if detected_native is None:
            if provider in AUTO_DETECT_NATIVE_COMPAT_PROVIDERS:
                pcfg["native_compat"] = True
                lines.append(f"Endpoint auto-detect inconclusive ({detect_reason}); Native compatibility kept on as the Anthropic default.")
        else:
            pcfg["native_compat"] = bool(detected_native)
            mode = "enabled" if detected_native else "disabled"
            lines.append(f"Endpoint auto-detected ({detect_reason}); Native compatibility {mode}.")
    save_config(cfg)
    clear_model_cache()
    return lines


def set_model_config(value: str) -> list[str]:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    mmap = model_map_for(provider, pcfg, fetch=False)
    model_id = normalize_model_id(provider, unslug_provider_alias(provider, value, mmap) or value)
    pcfg["current_model"] = model_id
    selected_info = read_model_info_cache(provider, pcfg).get(model_id) or {}
    selected_context = positive_int(selected_info.get("max_model_len"))
    if selected_context:
        pcfg["max_model_len"] = selected_context
    preset = model_preset(model_id)
    if preset.get("num_ctx_min"):
        pcfg["num_ctx_min"] = preset["num_ctx_min"]
    if preset.get("num_ctx_max"):
        pcfg["num_ctx_max"] = preset["num_ctx_max"]
    context_msgs = sync_ollama_library_context_limit(provider, pcfg, model_id)
    context_msgs.extend(cap_context_settings_to_model_capacity(provider, pcfg))
    preset_msgs = auto_apply_recommended_llm_preset_for_model(provider, pcfg, cfg.get("language", "en"))
    timeout_msgs = apply_recommended_timeout_for_model_context(provider, pcfg, use_context_fallback=False)
    known = read_model_list_cache(provider, pcfg) or []
    custom = pcfg.setdefault("custom_models", [])
    if model_id not in custom and model_id not in known:
        custom.append(model_id)
    save_config(cfg)
    clear_model_cache()
    msgs = [f"Model for {provider} set to {model_id}.", f"Claude Code alias: {alias_for(provider, model_id)}"]
    if selected_context:
        msgs.append(f"Model context size: {format_context_tokens(selected_context)} ({selected_context:,} tokens).")
    msgs.extend(context_msgs)
    msgs.extend(preset_msgs)
    msgs.extend(timeout_msgs)
    if preset.get("thinking"):
        msgs.append("Note: this is a thinking model; compatibility test uses extended token budget.")
    return msgs


def set_advisor_model_config(value: str) -> list[str]:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    if provider == "anthropic":
        return [
            "Anthropic modes use Claude Code's built-in /advisor; "
            "run /advisor in the session to pick its model."
        ]
    model_id = normalize_model_id(provider, value.strip()) if value.strip() else ""
    pcfg["advisor_model"] = model_id
    if model_id:
        known = read_model_list_cache(provider, pcfg) or []
        custom = pcfg.setdefault("custom_models", [])
        if model_id not in custom and model_id not in known:
            custom.append(model_id)
    save_config(cfg)
    clear_model_cache()
    if not model_id:
        return [f"Advisor Model for {provider} disabled."]
    return [f"Advisor Model for {provider} set to {model_id}."]


def store_api_key_config(provider: str, key: str) -> list[str]:
    if api_key_clear_requested(key):
        return clear_api_key_config(provider)
    if provider == "nvidia-hosted":
        store_nvidia_api_key(key)
        cfg = load_config()
        cfg["providers"][provider].pop("api_keys", None)
        if ensure_nvidia_hosted_base_url(cfg["providers"][provider]):
            save_config(cfg)
        location = str(NCP_ENV)
    else:
        cfg = load_config()
        cfg["providers"][provider]["api_key"] = key
        cfg["providers"][provider].pop("api_keys", None)
        save_config(cfg)
        location = str(CONFIG_PATH)
    clear_model_cache()
    return [
        f"Stored API key for {provider}.",
        f"Saved: {mask_secret(key)}; fp {secret_fingerprint(key)} in {location}",
    ]


def clear_api_key_config(provider: str) -> list[str]:
    cfg = load_config()
    providers = cfg["providers"]
    missing = object()
    other_key_fields: dict[str, tuple[Any, Any]] = {}
    for name, other_pcfg in providers.items():
        if name == provider or not isinstance(other_pcfg, dict):
            continue
        api_key_value = other_pcfg.get("api_key", missing)
        api_keys_value = json.loads(json.dumps(other_pcfg.get("api_keys"))) if "api_keys" in other_pcfg else missing
        other_key_fields[name] = (api_key_value, api_keys_value)
    pcfg = cfg["providers"][provider]
    had_config_key = bool(parse_api_key_list(pcfg.get("api_key")) or parse_api_key_list(pcfg.get("api_keys")))
    pcfg.pop("api_key", None)
    pcfg.pop("api_keys", None)
    if provider == "nvidia-hosted":
        had_config_key = had_config_key or bool(parse_api_key_list(read_env_file(NCP_ENV).get("NVIDIA_API_KEY")))
        clear_nvidia_api_key()
        ensure_nvidia_hosted_base_url(pcfg)
    for name, (api_key_value, api_keys_value) in other_key_fields.items():
        other_pcfg = providers.get(name)
        if not isinstance(other_pcfg, dict):
            continue
        if api_key_value is missing:
            other_pcfg.pop("api_key", None)
        else:
            other_pcfg["api_key"] = api_key_value
        if api_keys_value is missing:
            other_pcfg.pop("api_keys", None)
        else:
            other_pcfg["api_keys"] = api_keys_value
    save_config(cfg)
    clear_model_cache()
    with _API_KEY_ROTATION_LOCK:
        _API_KEY_ROTATION_CURSOR.pop(provider_api_key_rotation_name(provider, pcfg), None)
    if had_config_key:
        return [f"Cleared stored API key(s) for {provider}. Other providers unchanged."]
    return [f"No stored API key(s) for {provider}; other providers unchanged."]


def store_api_keys_config(provider: str, keys: list[str]) -> list[str]:
    parsed = parse_api_key_list(keys)
    if len(parsed) == 1 and api_key_clear_requested(parsed[0]):
        return clear_api_key_config(provider)
    if not parsed:
        raise SystemExit("No API keys provided; unchanged.")
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    pcfg["api_key"] = parsed[0]
    if len(parsed) > 1:
        pcfg["api_keys"] = parsed
    else:
        pcfg.pop("api_keys", None)
    if provider == "nvidia-hosted":
        store_nvidia_api_key(parsed[0])
        ensure_nvidia_hosted_base_url(pcfg)
    save_config(cfg)
    clear_model_cache()
    with _API_KEY_ROTATION_LOCK:
        _API_KEY_ROTATION_CURSOR.pop(provider_api_key_rotation_name(provider, pcfg), None)
    return [
        f"Stored {len(parsed)} API key{'s' if len(parsed) != 1 else ''} for {provider}.",
        f"Round-robin: {'enabled' if len(parsed) > 1 else 'disabled'}",
        f"Primary: {mask_secret(parsed[0])}; fp {secret_fingerprint(parsed[0])}",
    ]


def mask_secret(value: str | None) -> str:
    text = value or ""
    if not text:
        return "not set"
    if len(text) <= 8:
        return "*" * len(text)
    return f"{text[:4]}...{text[-4:]}"


def secret_fingerprint(value: str | None, length: int = 12) -> str:
    text = value or ""
    if not text:
        return "-"
    digest = hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()
    return digest[: max(4, length)]


SECRET_TEXT_PATTERNS = (
    re.compile(r"ak_key_[A-Za-z0-9_-]+_secret_[A-Za-z0-9_-]+"),
    re.compile(r"(AINET_API_KEY\s*=\s*)(\S+)", re.IGNORECASE),
    re.compile(r"(Authorization\s*:\s*Bearer\s+)(\S+)", re.IGNORECASE),
    re.compile(r"(token=)(ak_key_[A-Za-z0-9_-]+_secret_[A-Za-z0-9_-]+)", re.IGNORECASE),
)


def redact_sensitive_text(text: str) -> str:
    redacted = text
    redacted = SECRET_TEXT_PATTERNS[0].sub(lambda m: mask_secret(m.group(0)), redacted)
    for pattern in SECRET_TEXT_PATTERNS[1:]:
        redacted = pattern.sub(lambda m: f"{m.group(1)}{mask_secret(m.group(2))}", redacted)
    return redacted


def redact_sensitive_obj(value: Any) -> Any:
    if isinstance(value, str):
        return redact_sensitive_text(value)
    if isinstance(value, list):
        return [redact_sensitive_obj(item) for item in value]
    if isinstance(value, dict):
        redacted: dict[str, Any] = {}
        for key, item in value.items():
            if str(key).lower() in {"api_key", "api_keys", "apikey", "token", "authorization", "bearer_token"}:
                redacted[key] = mask_secret(str(item))
            else:
                redacted[key] = redact_sensitive_obj(item)
        return redacted
    return value


def stored_api_key_mask(provider: str, pcfg: dict[str, Any]) -> str:
    keys = provider_config_api_keys(provider, pcfg)
    if not keys:
        return "not set"
    primary = f"{mask_secret(keys[0])}; fp {secret_fingerprint(keys[0])}"
    if len(keys) == 1:
        return primary
    return f"{len(keys)} keys (round-robin; primary {primary})"


def store_api_key_input_config(provider: str, raw_value: str) -> list[str]:
    if api_key_clear_requested(raw_value):
        return clear_api_key_config(provider)
    keys = parse_api_key_list(raw_value)
    if len(keys) > 1:
        return store_api_keys_config(provider, keys)
    if len(keys) == 1:
        return store_api_key_config(provider, keys[0])
    raise SystemExit("No API key provided; unchanged.")


def read_clipboard_text() -> str:
    commands: list[list[str]] = []
    if os.name == "nt":
        commands.append(["powershell", "-NoProfile", "-Command", "Get-Clipboard -Raw"])
    elif sys.platform == "darwin":
        commands.append(["pbpaste"])
    else:
        commands.extend([["xclip", "-selection", "clipboard", "-o"], ["xsel", "--clipboard", "--output"]])
    for cmd in commands:
        try:
            proc = subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=5)
            if proc.returncode == 0 and proc.stdout.strip():
                return proc.stdout.strip()
        except Exception:
            pass
    return ""


def cmd_provider(args: argparse.Namespace) -> None:
    cfg = load_config()
    if not args.name:
        cur = cfg["current_provider"]
        print("Available providers (current: %s)" % cur)
        for i, p in enumerate(PROVIDER_LABELS, 1):
            mark = "*" if p == cur else " "
            base = cfg["providers"][p].get("base_url", "")
            print(f" {mark} {i}. {p:<15} {PROVIDER_LABELS[p]:<16} {base}")
        print("\nUse: /provider <name>")
        print("Example: /provider ollama")
        print("Then run /model to choose a model for the selected provider.")
        return
    provider = normalize_provider(args.name)
    for line in set_provider_config(provider):
        print(line)
    print("Gateway model cache cleared. Run /model to refresh the model picker.")


def cmd_set_api_key(args: argparse.Namespace) -> None:
    provider = normalize_provider(args.provider)
    key = args.key.strip()
    if not key:
        raise SystemExit("No key provided; unchanged.")
    for line in store_api_key_input_config(provider, key):
        print(line)


def cmd_set_api_keys(args: argparse.Namespace) -> None:
    provider = normalize_provider(args.provider)
    raw = "\n".join(str(item) for item in getattr(args, "keys", []) if str(item).strip())
    keys = parse_api_key_list(raw)
    if not keys:
        raise SystemExit("No API keys provided; unchanged.")
    for line in store_api_keys_config(provider, keys):
        print(line)


def cmd_api_key(args: argparse.Namespace) -> None:
    cfg = load_config()
    if not args.provider:
        print("API key status:")
        for p, pcfg in cfg["providers"].items():
            needs = p in ("anthropic", "ollama-cloud", "deepseek", "opencode", "opencode-go", "nvidia-hosted", "openrouter", "fireworks")
            count = provider_api_key_count(p, pcfg)
            label = f"{count} keys (round-robin)" if count > 1 else ("set" if count == 1 else ("missing" if needs else "not required"))
            primary = provider_primary_api_key(p, pcfg)
            suffix = f" (primary {mask_secret(primary)}; fp {secret_fingerprint(primary)})" if count else ""
            print(f" {p:<15} {label}{suffix}")
        print("\nSet securely from terminal: claude-anyctl api-key anthropic")
        print("Set multiple keys: claude-anyctl set-api-keys deepseek KEY1,KEY2")
        print("For NVIDIA hosted, use: claude-anyctl api-key nvidia-hosted")
        return
    provider = normalize_provider(args.provider)
    action = str(getattr(args, "action", "") or "").strip()
    if api_key_clear_requested(action):
        for line in clear_api_key_config(provider):
            print(line)
        return
    if not sys.stdin.isatty():
        print("For security, do not paste API keys into Claude Code chat.")
        print(f"Run this in the SSH terminal instead: claude-anyctl api-key {provider}")
        return
    key = getpass.getpass(f"API key for {provider}: ").strip()
    if not key:
        raise SystemExit("No key entered; unchanged.")
    for line in store_api_key_input_config(provider, key):
        print(line)


def cmd_base_url(args: argparse.Namespace) -> None:
    provider = normalize_provider(args.provider)
    for line in set_base_url_config(provider, args.url):
        print(line)


def cmd_model(args: argparse.Namespace) -> None:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    if not args.value:
        print(f"Model menu for {provider} (current: {pcfg.get('current_model')})")
        models = cached_or_configured_model_ids(provider, pcfg)
        for i, mid in enumerate(models[:100], 1):
            mark = "*" if mid == pcfg.get("current_model") else " "
            print(f" {mark} {i:>3}. {alias_for(provider, mid)}    [{mid}]")
        if len(models) > 100:
            print(f" ... {len(models) - 100} more")
        if read_model_list_cache(provider, pcfg) is None:
            print("\nProvider model list is not cached yet. Use the menu refresh row or run: claude-anyctl models")
        print("\nSet direct/custom model with: /set-model MODEL_ID")
        print("Or from terminal: claude-anyctl model MODEL_ID")
        return
    value = " ".join(args.value).strip()
    if value.startswith("add "):
        value = value[4:].strip()
    if not value:
        raise SystemExit("Missing model id")
    for line in set_model_config(value):
        print(line)
    print("Gateway model cache cleared. Run /model to refresh if needed.")


def cmd_advisor_model(args: argparse.Namespace) -> None:
    if not args.value:
        cfg = load_config()
        provider, pcfg = get_current_provider(cfg)
        if provider == "anthropic":
            print("Anthropic modes use Claude Code's built-in /advisor; run /advisor in the session to pick its model.")
            return
        current = pcfg.get("advisor_model") or "off"
        print(f"Advisor Model for {provider}: {current}")
        print("Set with: claude-anyctl advisor-model deepseek-v4-pro")
        print("Disable with: claude-anyctl advisor-model off")
        return
    value = " ".join(args.value).strip()
    if value.lower() in ("off", "unset", "disable", "disabled", "none", "null"):
        value = ""
    for line in set_advisor_model_config(value):
        print(line)


def cmd_models(args: argparse.Namespace) -> None:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    if args.provider:
        provider = normalize_provider(args.provider)
        pcfg = cfg["providers"][provider]
    models = upstream_model_ids(provider, pcfg)
    print(f"{provider}: {len(models)} models")
    for mid in models:
        print(f"{alias_for(provider, mid)}\t{mid}")


def cmd_ollama_catalog(args: argparse.Namespace) -> None:
    include_contexts = not bool(getattr(args, "no_contexts", False))
    catalog = refresh_ollama_model_catalog(include_contexts=include_contexts, timeout=float(getattr(args, "timeout", 10.0)))
    models = catalog.get("models") if isinstance(catalog.get("models"), dict) else {}
    context_count = 0
    for entry in models.values():
        if isinstance(entry, dict) and isinstance(entry.get("context_windows"), dict) and entry["context_windows"]:
            context_count += 1
    print(f"Ollama catalog saved: {OLLAMA_MODEL_CATALOG_PATH}")
    print(f"API models: {catalog.get('model_count', 0)}")
    print(f"Base models: {len(models)}")
    print(f"Context windows: {context_count}/{len(models)}")


def provider_mode_label(provider: str, pcfg: dict[str, Any]) -> str:
    if direct_native_anthropic_enabled(provider, pcfg):
        return "anthropic-native"
    if anthropic_routed_enabled(provider, pcfg):
        return "anthropic-routed"
    return "claude-any-router"


def status_lines() -> list[str]:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    mode = provider_mode_label(provider, pcfg)
    direct_native = direct_native_anthropic_enabled(provider, pcfg)
    return [
        f"provider: {provider}",
        f"language: {cfg.get('language', 'en')}",
        f"mode: {mode}",
        f"base_url: {pcfg.get('base_url')}",
        f"model: {pcfg.get('current_model')}",
        *([f"num_ctx: {ollama_num_ctx_status(pcfg)}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"ollama_options: {ollama_options_status(pcfg)}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"keep_alive: {pcfg.get('keep_alive', 'default')}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"think: {bool(pcfg.get('think', False))}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"request_timeout_ms: {pcfg.get('request_timeout_ms', 'default')}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"stream_idle_timeout_ms: {pcfg.get('stream_idle_timeout_ms', 'auto')}"] if provider in ("ollama", "ollama-cloud") else []),
        *([f"context_window: {pcfg.get('context_window', 'default')}"] if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks") else []),
        *([f"context_reserve_tokens: {pcfg.get('context_reserve_tokens', 'default')}"] if provider in ("vllm", "lm-studio", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks") else []),
        *([f"max_output_tokens: {pcfg.get('max_output_tokens', 'default')}"] if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks") else []),
        *([f"request_timeout_ms: {pcfg.get('request_timeout_ms', 'default')}"] if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks") else []),
        *([f"stream_idle_timeout_ms: {pcfg.get('stream_idle_timeout_ms', 'auto')}"] if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks") else []),
        f"claude_model: {current_upstream_model_id(provider, pcfg) if direct_native else current_alias(cfg)}",
        f"log_level: {log_level_status()}",
        f"channels: {channel_status_text(cfg)}",
        f"channel_delivery: {channel_delivery_mode(cfg)}",
        f"router: {'bypassed for native provider compatibility' if direct_native else (('up' if router_up() else 'down') + ' ' + ROUTER_BASE)}",
        f"config: {CONFIG_PATH}",
    ]


def cmd_status(_: argparse.Namespace) -> None:
    print("\n".join(status_lines()))


def cmd_log_level(args: argparse.Namespace) -> None:
    value = getattr(args, "value", None)
    if not value:
        print(f"log_level: {log_level_status()}")
        for numeric in sorted(LOG_LEVEL_NAMES):
            name = LOG_LEVEL_NAMES[numeric]
            mark = "*" if name == log_level_name() else " "
            print(f" {mark} {name:<6} {numeric}")
        print("   DEFAULT reset to environment/default")
        return
    for line in set_log_level_config(str(value)):
        print(line)


def cmd_language(args: argparse.Namespace) -> None:
    cfg = load_config()
    if not args.value:
        current = cfg.get("language", "en")
        print(f"language: {current} ({LANGUAGES.get(current, current)})")
        for code, label in LANGUAGES.items():
            mark = "*" if code == current else " "
            print(f" {mark} {code:<2} {label}")
        return
    value = args.value.strip().lower()
    aliases = {
        "english": "en",
        "korean": "ko",
        "한국어": "ko",
        "japanese": "ja",
        "日本語": "ja",
        "chinese": "zh",
        "中文": "zh",
        "zh-cn": "zh",
        "cn": "zh",
    }
    value = aliases.get(value, value)
    if value not in LANGUAGES:
        raise SystemExit(f"Unknown language: {args.value}\nKnown: {', '.join(LANGUAGES)}")
    cfg["language"] = value
    save_config(cfg)
    print(f"Language set to {value} ({LANGUAGES[value]}).")


def set_web_search_enabled(enabled: bool) -> None:
    cfg = load_config()
    cfg.setdefault("web_search", {})["auto_for_non_native"] = enabled
    save_config(cfg)


def cmd_web_search(args: argparse.Namespace) -> None:
    cfg = load_config()
    web = cfg.setdefault("web_search", {})
    if args.value:
        value = args.value.lower()
        if value in ("on", "enable", "enabled", "true", "1"):
            web["auto_for_non_native"] = True
            save_config(cfg)
        elif value in ("off", "disable", "disabled", "false", "0"):
            web["auto_for_non_native"] = False
            save_config(cfg)
        else:
            raise SystemExit("Use: claude-any web-search on|off|status")
    state = "on" if web.get("auto_for_non_native", True) else "off"
    package = web.get("package", "ddg-mcp-search")
    print(f"web_search: {state}")
    print(f"search_provider: {web.get('provider', 'duckduckgo')}")
    print(f"search_package: {package}")
    print(f"web_fetch: {'on' if web.get('fetch_enabled', True) else 'off'}")
    print(f"fetch_package: {web.get('fetch_package', 'mcp-server-fetch')}")
    print(f"mcp_config: {WEB_TOOLS_MCP_CONFIG}")


def cmd_web_fetch(args: argparse.Namespace) -> None:
    cfg = load_config()
    web = cfg.setdefault("web_search", {})
    if args.value:
        value = args.value.lower()
        if value in ("on", "enable", "enabled", "true", "1"):
            web["fetch_enabled"] = True
            save_config(cfg)
        elif value in ("off", "disable", "disabled", "false", "0"):
            web["fetch_enabled"] = False
            save_config(cfg)
        elif value == "ignore-robots-on":
            web["fetch_ignore_robots_txt"] = True
            save_config(cfg)
        elif value == "ignore-robots-off":
            web["fetch_ignore_robots_txt"] = False
            save_config(cfg)
        else:
            raise SystemExit("Use: claude-any web-fetch on|off|ignore-robots-on|ignore-robots-off")
    print(f"web_fetch: {'on' if web.get('fetch_enabled', True) else 'off'}")
    print(f"fetch_package: {web.get('fetch_package', 'mcp-server-fetch')}")
    print(f"ignore_robots_txt: {bool(web.get('fetch_ignore_robots_txt', False))}")
    print(f"user_agent: {web.get('fetch_user_agent') or 'default'}")
    print(f"mcp_config: {WEB_TOOLS_MCP_CONFIG}")


def channel_specs(cfg: dict[str, Any] | None = None) -> list[str]:
    cfg = cfg or load_config()
    raw = cfg.setdefault("claude_code", {}).get("channels", [])
    if isinstance(raw, str):
        items = [raw]
    elif isinstance(raw, list):
        items = raw
    else:
        items = []
    channels: list[str] = [BUILTIN_CHANNEL_SPEC]
    seen: set[str] = {BUILTIN_CHANNEL_SPEC}
    for item in items:
        spec = str(item).strip()
        if not spec or spec in seen:
            continue
        seen.add(spec)
        channels.append(spec)
    return channels


def _dedupe_strings(values: Iterable[str]) -> list[str]:
    out: list[str] = []
    seen: set[str] = set()
    for value in values:
        text = str(value or "").strip()
        if not text or text in seen:
            continue
        seen.add(text)
        out.append(text)
    return out


def _path_for_compare(path: Path | str) -> str:
    try:
        return str(Path(path).expanduser().resolve()).replace("\\", "/").rstrip("/").casefold()
    except Exception:
        return str(path).replace("\\", "/").rstrip("/").casefold()


def _project_key_matches_cwd(project_key: str, cwd: Path) -> bool:
    key = str(project_key or "").strip()
    if not key:
        return False
    try:
        project_path = Path(key).expanduser()
    except Exception:
        return False
    if not project_path.is_absolute():
        return False
    project = _path_for_compare(project_path)
    current = _path_for_compare(cwd)
    return current == project or current.startswith(project + "/")


def _mcp_server_names_from_mapping(mapping: Any) -> list[str]:
    if not isinstance(mapping, dict):
        return []
    names: list[str] = []
    for key in ("mcpServers", "servers"):
        servers = mapping.get(key)
        if isinstance(servers, dict):
            names.extend(str(name).strip() for name in servers if str(name).strip())
    return _dedupe_strings(names)


def _mcp_servers_from_mapping(mapping: Any) -> list[tuple[str, dict[str, Any]]]:
    if not isinstance(mapping, dict):
        return []
    found: list[tuple[str, dict[str, Any]]] = []
    seen: set[str] = set()
    for key in ("mcpServers", "servers"):
        servers = mapping.get(key)
        if not isinstance(servers, dict):
            continue
        for raw_name, raw_server in servers.items():
            name = str(raw_name or "").strip()
            if not name or name in seen or not isinstance(raw_server, dict):
                continue
            seen.add(name)
            found.append((name, dict(raw_server)))
    return found


def _read_mcp_server_names_from_json(path: Path, cwd: Path) -> list[str]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return []
    names = _mcp_server_names_from_mapping(data)
    if path.name == ".claude.json" and isinstance(data, dict):
        projects = data.get("projects")
        if isinstance(projects, dict):
            for project_key, project_data in projects.items():
                if _project_key_matches_cwd(str(project_key), cwd):
                    names.extend(_mcp_server_names_from_mapping(project_data))
    return _dedupe_strings(names)


def _read_mcp_servers_from_json(path: Path, cwd: Path) -> list[tuple[str, dict[str, Any]]]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return []
    servers = _mcp_servers_from_mapping(data)
    if path.name == ".claude.json" and isinstance(data, dict):
        projects = data.get("projects")
        if isinstance(projects, dict):
            for project_key, project_data in projects.items():
                if _project_key_matches_cwd(str(project_key), cwd):
                    servers.extend(_mcp_servers_from_mapping(project_data))
    out: list[tuple[str, dict[str, Any]]] = []
    seen: set[str] = set()
    for name, server in servers:
        if name in seen:
            continue
        seen.add(name)
        out.append((name, server))
    return out


def _mcp_server_is_stdio(server: dict[str, Any]) -> bool:
    if not isinstance(server, dict):
        return False
    server_type = str(server.get("type") or "").strip().lower()
    if server_type and server_type not in ("stdio", "command"):
        return False
    command = resolve_executable_for_subprocess(str(server.get("command") or "").strip())
    if not command:
        return False
    args = [str(item) for item in server.get("args", []) if item is not None] if isinstance(server.get("args", []), list) else []
    return "mcp-proxy" not in args


def _mcp_server_is_streamable_http(server: dict[str, Any]) -> bool:
    if not isinstance(server, dict):
        return False
    server_type = str(server.get("type") or server.get("transport") or "").strip().lower()
    if server_type not in {"http", "streamable-http"}:
        return False
    url = str(server.get("url") or server.get("endpoint") or "").strip()
    return url.startswith(("http://", "https://"))


def _mcp_server_force_proxy(server: dict[str, Any]) -> bool:
    if not isinstance(server, dict):
        return False
    return parse_bool(
        server.get(
            "claude_any_mcp_proxy",
            server.get("claude_any_force_mcp_proxy", server.get("force_mcp_proxy", False)),
        ),
        False,
    )


def _mcp_server_disable_proxy_notification_stream(server: dict[str, Any]) -> bool:
    if not isinstance(server, dict):
        return False
    return parse_bool(
        server.get(
            "claude_any_disable_notification_stream",
            server.get("claude_any_disable_mcp_notifications", False),
        ),
        False,
    )


def _channel_probe_strategy_for(server: dict[str, Any]) -> str:
    """How to frame the initialize request we send to this stdio server.

    Default is ``"jsonl"`` (newline-delimited JSON), which is the MCP
    stdio wire format per modelcontextprotocol.io:
        "Messages are delimited by newlines, and MUST NOT contain embedded
         newlines."

    Servers that need LSP-style ``Content-Length: N\\r\\n\\r\\n`` framing
    (not spec for MCP, but seen in some legacy implementations) can opt in
    with ``"claude_any_stdio": "framed"`` in their MCP config entry. This
    is distinct from ``_mcp_proxy_stdio_mode``, whose history pre-dates
    the framing audit and whose default we do not change here.
    """
    if not isinstance(server, dict):
        return "jsonl"
    mode = str(server.get("claude_any_stdio") or server.get("stdio_mode") or "").strip().lower()
    if mode in ("framed", "framed-only", "content-length", "lsp"):
        return "framed"
    return "jsonl"


def _channel_probe_initialize_payload() -> bytes:
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {"name": "claude-any-channel-probe", "version": VERSION},
        },
    }
    return json.dumps(payload, ensure_ascii=False).encode("utf-8")


def _channel_probe_parse_framed_responses(buffer: bytes) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    idx = 0
    while idx < len(buffer):
        header_end = buffer.find(b"\r\n\r\n", idx)
        if header_end < 0:
            return out
        header = buffer[idx:header_end].decode("ascii", errors="replace")
        length: int | None = None
        for line in header.split("\r\n"):
            if line.lower().startswith("content-length:"):
                try:
                    length = int(line.split(":", 1)[1].strip())
                except Exception:
                    return out
                break
        if length is None:
            return out
        body_start = header_end + 4
        body_end = body_start + length
        if len(buffer) < body_end:
            return out
        try:
            msg = json.loads(buffer[body_start:body_end].decode("utf-8", errors="replace"))
        except Exception:
            idx = body_end
            continue
        if isinstance(msg, dict):
            out.append(msg)
        idx = body_end
    return out


def _channel_probe_parse_jsonl_responses(buffer: bytes) -> list[dict[str, Any]]:
    out: list[dict[str, Any]] = []
    for raw_line in buffer.split(b"\n"):
        line = raw_line.strip()
        if not line:
            continue
        try:
            msg = json.loads(line.decode("utf-8", errors="replace"))
        except Exception:
            continue
        if isinstance(msg, dict):
            out.append(msg)
    return out


def _channel_probe_find_initialize_response(buffer: bytes, framed: bool) -> dict[str, Any] | None:
    msgs = _channel_probe_parse_framed_responses(buffer) if framed else _channel_probe_parse_jsonl_responses(buffer)
    for msg in msgs:
        if msg.get("id") == 1 and "result" in msg:
            return msg
    return None


def _channel_probe_capability_present(initialize_response: dict[str, Any]) -> bool:
    result = initialize_response.get("result")
    if not isinstance(result, dict):
        return False
    capabilities = result.get("capabilities")
    if not isinstance(capabilities, dict):
        return False
    experimental = capabilities.get("experimental")
    if not isinstance(experimental, dict):
        return False
    value = experimental.get("claude/channel")
    return value is not None and value is not False


CHANNEL_PROBE_DEFAULT_TIMEOUT_SECONDS = 15.0


def channel_probe_default_timeout() -> float:
    """Default per-server probe timeout. Configurable via
    CLAUDE_ANY_CHANNEL_PROBE_TIMEOUT_SECONDS so users with slow MCP servers
    (npx cold start, remote API init) can extend it without code changes."""
    raw = os.environ.get("CLAUDE_ANY_CHANNEL_PROBE_TIMEOUT_SECONDS")
    if raw is None:
        return CHANNEL_PROBE_DEFAULT_TIMEOUT_SECONDS
    try:
        value = float(str(raw).strip())
    except (TypeError, ValueError):
        return CHANNEL_PROBE_DEFAULT_TIMEOUT_SECONDS
    if value <= 0:
        return CHANNEL_PROBE_DEFAULT_TIMEOUT_SECONDS
    return value


CHANNEL_PROBE_STDERR_CAP_BYTES = 4096
CHANNEL_PROBE_STDOUT_PREVIEW_BYTES = 200
CHANNEL_PROBE_STDERR_PREVIEW_CHARS = 500
CHANNEL_PROBE_SSE_OPEN_TIMEOUT_SECONDS = 5.0
CHANNEL_PROBE_SSE_INIT_POST_TIMEOUT_SECONDS = 5.0


def _decode_sse_events(buf: bytearray) -> tuple[list[tuple[str, str]], bytearray]:
    """Drain complete SSE events from buf. Each event is (event_name, data).
    Multi-line `data:` fields are joined with '\\n'. Returns the parsed
    events and the leftover (incomplete) buffer."""
    events: list[tuple[str, str]] = []
    text = bytes(buf).decode("utf-8", errors="replace")
    text = text.replace("\r\n", "\n").replace("\r", "\n")
    while True:
        sep = text.find("\n\n")
        if sep < 0:
            break
        event_text = text[:sep]
        text = text[sep + 2:]
        event_name = "message"
        data_lines: list[str] = []
        for line in event_text.split("\n"):
            if not line or line.startswith(":"):
                continue
            if line.startswith("event:"):
                event_name = line[len("event:"):].lstrip()
            elif line.startswith("data:"):
                payload = line[len("data:"):]
                if payload.startswith(" "):
                    payload = payload[1:]
                data_lines.append(payload)
        events.append((event_name, "\n".join(data_lines)))
    return events, bytearray(text.encode("utf-8"))


def probe_sse_mcp_for_channel_capability_detailed(
    server_name: str,
    server: dict[str, Any],
    timeout: float | None = None,
) -> dict[str, Any]:
    """Probe an SSE-typed MCP server with an MCP `initialize` round-trip.

    MCP SSE transport: GET the server URL with `Accept: text/event-stream`.
    The server emits an `event: endpoint` whose `data:` is the URL to POST
    JSON-RPC requests to. POST initialize to that endpoint, then continue
    reading the SSE stream until the matching response arrives.
    """
    started = time.time()
    url = str(server.get("url") or "").strip()
    if not url:
        return {
            "capable": False,
            "reason": "no_url",
            "response_bytes": 0,
            "response_received": False,
            "elapsed_ms": 0,
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": "",
            "stdout_preview": "",
        }
    effective_timeout = timeout if timeout is not None else channel_probe_default_timeout()
    open_timeout = min(CHANNEL_PROBE_SSE_OPEN_TIMEOUT_SECONDS, effective_timeout)

    request_headers: dict[str, str] = {"Accept": "text/event-stream", "Cache-Control": "no-cache"}
    custom_headers = server.get("headers")
    if isinstance(custom_headers, dict):
        for key, value in custom_headers.items():
            if key and value is not None:
                request_headers[str(key)] = str(value)

    try:
        get_req = urllib.request.Request(url, headers=request_headers, method="GET")
        sse_resp = urllib.request.urlopen(get_req, timeout=open_timeout)
    except Exception as exc:
        return {
            "capable": False,
            "reason": f"sse_open_failed:{type(exc).__name__}",
            "response_bytes": 0,
            "response_received": False,
            "elapsed_ms": int((time.time() - started) * 1000),
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": str(exc)[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS],
            "stdout_preview": "",
        }

    chunks_q: queue.Queue[bytes | None] = queue.Queue()

    def _sse_reader() -> None:
        try:
            while True:
                line = sse_resp.readline()
                if not line:
                    break
                chunks_q.put(line)
        except Exception:
            pass
        finally:
            chunks_q.put(None)

    threading.Thread(target=_sse_reader, daemon=True, name=f"channel-probe-sse-{server_name}").start()

    deadline = time.time() + effective_timeout
    sse_buf = bytearray()
    bytes_seen = 0
    endpoint_url: str | None = None
    init_posted = False
    init_post_error: str | None = None
    capable = False
    response_received = False
    response_data_preview = ""

    post_headers = {"Content-Type": "application/json"}
    for key, value in request_headers.items():
        if key.lower() not in ("accept", "cache-control"):
            post_headers[key] = value

    try:
        while time.time() < deadline:
            wait = min(0.2, max(0.001, deadline - time.time()))
            try:
                chunk = chunks_q.get(timeout=wait)
            except queue.Empty:
                continue
            if chunk is None:
                break
            sse_buf.extend(chunk)
            bytes_seen += len(chunk)
            events, sse_buf = _decode_sse_events(sse_buf)
            for event_name, data_text in events:
                if not init_posted and event_name == "endpoint":
                    target = data_text.strip()
                    if not target:
                        continue
                    endpoint_url = urllib.parse.urljoin(url, target)
                    init_body = _channel_probe_initialize_payload()
                    try:
                        post_req = urllib.request.Request(
                            endpoint_url,
                            data=init_body,
                            headers=post_headers,
                            method="POST",
                        )
                        with urllib.request.urlopen(
                            post_req,
                            timeout=min(CHANNEL_PROBE_SSE_INIT_POST_TIMEOUT_SECONDS, max(1.0, deadline - time.time())),
                        ) as post_resp:
                            post_resp.read()
                        init_posted = True
                    except Exception as exc:
                        init_post_error = f"{type(exc).__name__}: {exc}"
                        break
                    continue
                if not init_posted or not data_text:
                    continue
                try:
                    msg = json.loads(data_text)
                except Exception:
                    continue
                if not isinstance(msg, dict):
                    continue
                if msg.get("id") == 1 and "result" in msg:
                    response_received = True
                    capable = _channel_probe_capability_present(msg)
                    response_data_preview = _decode_preview(
                        data_text.encode("utf-8"), CHANNEL_PROBE_STDOUT_PREVIEW_BYTES
                    )
                    break
            if response_received or init_post_error:
                break
    finally:
        try:
            sse_resp.close()
        except Exception:
            pass

    elapsed_ms = int((time.time() - started) * 1000)
    if capable:
        reason = "capable"
    elif response_received:
        reason = "no_experimental_claude_channel"
    elif init_post_error:
        reason = f"sse_init_post_failed:{init_post_error.split(':', 1)[0]}"
    elif init_posted:
        reason = "timeout_waiting_for_initialize_response"
    elif endpoint_url:
        reason = "timeout_after_endpoint_event"
    else:
        reason = "timeout_no_endpoint_event"

    stderr_preview = init_post_error[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS] if init_post_error else ""
    stdout_preview = response_data_preview if (response_data_preview and not capable) else ""

    router_log(
        "INFO",
        "channel_probe_result server=%s channel_capable=%s reason=%s transport=sse url=%s bytes=%d elapsed_ms=%d timeout_s=%.1f"
        % (server_name, capable, reason, url, bytes_seen, elapsed_ms, effective_timeout),
    )
    if stderr_preview:
        router_log("INFO", f"channel_probe_sse_error server={server_name} preview={stderr_preview!r}")

    return {
        "capable": capable,
        "reason": reason,
        "response_bytes": bytes_seen,
        "response_received": response_received,
        "elapsed_ms": elapsed_ms,
        "exit_code": None,
        "stderr_bytes": len(stderr_preview),
        "stderr_preview": stderr_preview,
        "stdout_preview": stdout_preview,
    }


def _channel_probe_initialize_payload_dict(protocol_version: str) -> dict[str, Any]:
    return {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
            "protocolVersion": protocol_version,
            "capabilities": {},
            "clientInfo": {"name": "claude-any-channel-probe", "version": VERSION},
        },
    }


def probe_streamable_http_mcp_for_channel_capability_detailed(
    server_name: str,
    server: dict[str, Any],
    timeout: float | None = None,
) -> dict[str, Any]:
    """Probe a Streamable HTTP MCP server with initialize/initialized.

    MCP Streamable HTTP (2025-03-26) uses one endpoint for POST JSON-RPC
    and GET server-sent events. Unlike legacy SSE transport, it does not
    emit an `endpoint` event; the client must initialize by POSTing to the
    configured URL and then attach the returned Mcp-Session-Id to follow-up
    requests.
    """
    started = time.time()
    url = str(server.get("url") or server.get("endpoint") or "").strip()
    if not url:
        return {
            "capable": False,
            "reason": "no_url",
            "response_bytes": 0,
            "response_received": False,
            "elapsed_ms": 0,
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": "",
            "stdout_preview": "",
        }
    effective_timeout = timeout if timeout is not None else channel_probe_default_timeout()
    protocol_version = str(server.get("protocolVersion") or server.get("protocol_version") or MCP_STREAMABLE_HTTP_PROTOCOL_VERSION)
    headers: dict[str, str] = {}
    custom_headers = server.get("headers")
    if isinstance(custom_headers, dict):
        for key, value in custom_headers.items():
            if key and value is not None:
                headers[str(key)] = str(value)

    bytes_seen = 0
    response_received = False
    capable = False
    reason = ""
    stderr_preview = ""
    stdout_preview = ""
    initialized_failed = False
    try:
        initialize = _channel_probe_initialize_payload_dict(protocol_version)
        response, session_id = _mcp_streamable_post_json(
            url,
            headers,
            initialize,
            max(1.0, min(120.0, effective_timeout)),
            protocol_version,
        )
        preview_source = json.dumps(response, ensure_ascii=False) if isinstance(response, (dict, list)) else str(response or "")
        bytes_seen = len(preview_source.encode("utf-8", errors="replace"))
        if isinstance(response, dict) and response.get("id") == 1 and "result" in response:
            response_received = True
            capable = _channel_probe_capability_present(response)
            if not capable:
                stdout_preview = _decode_preview(preview_source.encode("utf-8"), CHANNEL_PROBE_STDOUT_PREVIEW_BYTES)
        else:
            stdout_preview = _decode_preview(preview_source.encode("utf-8"), CHANNEL_PROBE_STDOUT_PREVIEW_BYTES)
        if response_received:
            initialized = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}
            try:
                _mcp_streamable_post_json(
                    url,
                    headers,
                    initialized,
                    max(1.0, min(120.0, effective_timeout)),
                    protocol_version,
                    session_id,
                )
            except Exception as exc:
                initialized_failed = True
                capable = False
                stderr_preview = f"{type(exc).__name__}: {exc}"[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS]
        if initialized_failed:
            reason = "streamable_http_initialized_post_failed"
        else:
            reason = "capable" if capable else ("no_experimental_claude_channel" if response_received else "no_initialize_response")
    except urllib.error.HTTPError as exc:
        reason = "streamable_http_405_fallback_sse" if exc.code == 405 else f"streamable_http_open_failed:HTTPError:{exc.code}"
        try:
            data = exc.read()
        except Exception:
            data = b""
        bytes_seen = len(data)
        stderr_preview = (data.decode("utf-8", errors="replace") or str(exc))[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS]
    except Exception as exc:
        reason = f"streamable_http_open_failed:{type(exc).__name__}"
        stderr_preview = str(exc)[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS]

    elapsed_ms = int((time.time() - started) * 1000)
    router_log(
        "INFO",
        "channel_probe_result server=%s channel_capable=%s reason=%s transport=streamable-http url=%s bytes=%d elapsed_ms=%d timeout_s=%.1f"
        % (server_name, capable, reason, url, bytes_seen, elapsed_ms, effective_timeout),
    )
    if stderr_preview:
        router_log("INFO", f"channel_probe_streamable_http_error server={server_name} preview={stderr_preview!r}")
    return {
        "capable": capable,
        "reason": reason,
        "response_bytes": bytes_seen,
        "response_received": response_received,
        "elapsed_ms": elapsed_ms,
        "exit_code": None,
        "stderr_bytes": len(stderr_preview),
        "stderr_preview": stderr_preview,
        "stdout_preview": stdout_preview,
    }


def _decode_preview(buf: bytes | bytearray, limit_chars: int) -> str:
    text = bytes(buf).decode("utf-8", errors="replace")
    text = text.replace("\x00", " ")
    text = " ".join(text.split())
    if len(text) > limit_chars:
        text = text[:limit_chars] + "..."
    return text


def probe_stdio_mcp_for_channel_capability_detailed(
    server_name: str,
    server: dict[str, Any],
    timeout: float | None = None,
) -> dict[str, Any]:
    """Probe a stdio MCP server with an MCP `initialize` request and report
    detail. The returned dict contains:

      - capable (bool): True only when the server's initialize response
        carries `capabilities.experimental['claude/channel']`.
      - reason (str): one of 'capable', 'timeout',
        'exited_without_response', 'no_experimental_claude_channel',
        'spawn_failed:<exc>', 'no_command', 'not_stdio'.
      - response_bytes (int): bytes of stdout observed.
      - response_received (bool): whether an initialize response was parsed.
      - exit_code (int|None): child process exit code if known.
      - stderr_bytes (int): bytes of stderr captured (capped).
      - stderr_preview (str): truncated stderr text for non-capable cases.
      - stdout_preview (str): truncated stdout text when no parseable
        response was found.
      - elapsed_ms (int): wall time of the probe.
    """
    started = time.time()
    if not _mcp_server_is_stdio(server):
        return {
            "capable": False,
            "reason": "not_stdio",
            "response_bytes": 0,
            "response_received": False,
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": "",
            "stdout_preview": "",
            "elapsed_ms": 0,
        }
    command = str(server.get("command") or "").strip()
    args_raw = server.get("args", [])
    args = [str(item) for item in args_raw] if isinstance(args_raw, list) else []
    if not command:
        return {
            "capable": False,
            "reason": "no_command",
            "response_bytes": 0,
            "response_received": False,
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": "",
            "stdout_preview": "",
            "elapsed_ms": 0,
        }
    command, args = resolve_mcp_server_process(command, args)
    env = os.environ.copy()
    raw_env = server.get("env")
    if isinstance(raw_env, dict):
        env.update({str(k): str(v) for k, v in raw_env.items() if str(k)})
    cwd_value = server.get("cwd") or server.get("workingDirectory")
    cwd = str(cwd_value) if cwd_value else None
    strategy = _channel_probe_strategy_for(server)
    framed = strategy == "framed"
    effective_timeout = timeout if timeout is not None else channel_probe_default_timeout()

    proc: subprocess.Popen[bytes] | None = None
    try:
        proc = subprocess.Popen(
            [command, *args],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=cwd,
            env=env,
            bufsize=0,
            close_fds=True,
        )
    except Exception as exc:
        router_log("DEBUG", f"channel_probe_spawn_failed server={server_name} error={type(exc).__name__}: {exc}")
        return {
            "capable": False,
            "reason": f"spawn_failed:{type(exc).__name__}",
            "response_bytes": 0,
            "response_received": False,
            "exit_code": None,
            "stderr_bytes": 0,
            "stderr_preview": str(exc)[:CHANNEL_PROBE_STDERR_PREVIEW_CHARS],
            "stdout_preview": "",
            "elapsed_ms": int((time.time() - started) * 1000),
        }

    stdout_chunks: queue.Queue[bytes | None] = queue.Queue()
    stderr_buf = bytearray()
    stderr_lock = threading.Lock()

    def _stdout_reader() -> None:
        try:
            assert proc is not None
            stdout = proc.stdout
            if stdout is None:
                return
            while True:
                chunk = stdout.read(4096)
                if not chunk:
                    break
                stdout_chunks.put(chunk)
        except Exception:
            pass
        finally:
            stdout_chunks.put(None)

    def _stderr_reader() -> None:
        try:
            assert proc is not None
            stderr = proc.stderr
            if stderr is None:
                return
            while True:
                chunk = stderr.read(1024)
                if not chunk:
                    break
                with stderr_lock:
                    remaining = CHANNEL_PROBE_STDERR_CAP_BYTES - len(stderr_buf)
                    if remaining > 0:
                        stderr_buf.extend(chunk[:remaining])
        except Exception:
            pass

    threading.Thread(target=_stdout_reader, daemon=True, name=f"channel-probe-stdout-{server_name}").start()
    threading.Thread(target=_stderr_reader, daemon=True, name=f"channel-probe-stderr-{server_name}").start()

    body = _channel_probe_initialize_payload()
    if framed:
        frame = b"Content-Length: " + str(len(body)).encode("ascii") + b"\r\n\r\n" + body
    else:
        frame = body + b"\n"
    try:
        if proc.stdin:
            proc.stdin.write(frame)
            proc.stdin.flush()
    except Exception:
        pass

    deadline = time.time() + effective_timeout
    stdout_buf = bytearray()
    capable = False
    response_received = False
    eof_seen = False
    try:
        while time.time() < deadline:
            wait = min(0.2, max(0.001, deadline - time.time()))
            try:
                chunk = stdout_chunks.get(timeout=wait)
            except queue.Empty:
                continue
            if chunk is None:
                eof_seen = True
                break
            stdout_buf.extend(chunk)
            response = _channel_probe_find_initialize_response(bytes(stdout_buf), framed)
            if response is not None:
                response_received = True
                capable = _channel_probe_capability_present(response)
                break
    finally:
        try:
            if proc.stdin:
                proc.stdin.close()
        except Exception:
            pass
        try:
            proc.terminate()
            proc.wait(timeout=1.0)
        except Exception:
            try:
                proc.kill()
            except Exception:
                pass

    exit_code = proc.returncode if proc else None
    if capable:
        reason = "capable"
    elif response_received:
        reason = "no_experimental_claude_channel"
    elif eof_seen:
        reason = "exited_without_response"
    else:
        reason = "timeout"

    with stderr_lock:
        stderr_bytes = len(stderr_buf)
        stderr_preview = _decode_preview(stderr_buf, CHANNEL_PROBE_STDERR_PREVIEW_CHARS) if not capable else ""
    stdout_preview = ""
    if not capable and not response_received and stdout_buf:
        stdout_preview = _decode_preview(bytes(stdout_buf)[:CHANNEL_PROBE_STDOUT_PREVIEW_BYTES], CHANNEL_PROBE_STDOUT_PREVIEW_BYTES)
    elapsed_ms = int((time.time() - started) * 1000)

    router_log(
        "INFO",
        "channel_probe_result server=%s channel_capable=%s reason=%s framed=%s bytes=%d stderr_bytes=%d exit_code=%s elapsed_ms=%d timeout_s=%.1f"
        % (
            server_name,
            capable,
            reason,
            framed,
            len(stdout_buf),
            stderr_bytes,
            "None" if exit_code is None else str(exit_code),
            elapsed_ms,
            effective_timeout,
        ),
    )
    if stderr_preview:
        router_log("INFO", f"channel_probe_stderr server={server_name} preview={stderr_preview!r}")
    if stdout_preview:
        router_log("INFO", f"channel_probe_stdout server={server_name} preview={stdout_preview!r}")

    return {
        "capable": capable,
        "reason": reason,
        "response_bytes": len(stdout_buf),
        "response_received": response_received,
        "exit_code": exit_code,
        "stderr_bytes": stderr_bytes,
        "stderr_preview": stderr_preview,
        "stdout_preview": stdout_preview,
        "elapsed_ms": elapsed_ms,
    }


def probe_stdio_mcp_for_channel_capability(server_name: str, server: dict[str, Any], timeout: float | None = None) -> bool:
    """Thin bool wrapper around the detailed probe. Preserves the older API
    for `detect_channel_capable_mcp_servers` and any external callers."""
    return probe_stdio_mcp_for_channel_capability_detailed(server_name, server, timeout=timeout)["capable"]


def detect_channel_capable_mcp_servers(
    mcp_config_paths: Iterable[str],
    cwd: Path,
    *,
    include_router_self: bool = True,
    timeout_per_server: float = 3.0,
) -> list[str]:
    """Probe MCP servers declared in given config files; return names that declare experimental['claude/channel']."""
    records = _probe_mcp_servers_to_records(
        mcp_config_paths,
        cwd,
        include_router_self=include_router_self,
        timeout_per_server=timeout_per_server,
    )
    return [str(record.get("name")) for record in records if record.get("capable") and record.get("name")]


def _mcp_config_passthrough_values(passthrough: list[str]) -> list[str]:
    values: list[str] = []
    i = 0
    while i < len(passthrough):
        arg = passthrough[i]
        if arg == "--mcp-config":
            i += 1
            while i < len(passthrough) and not passthrough[i].startswith("-"):
                values.append(passthrough[i])
                i += 1
            continue
        if arg.startswith("--mcp-config="):
            value = arg.split("=", 1)[1].strip()
            if value:
                values.append(value)
        i += 1
    return values


def strip_mcp_config_passthrough(passthrough: list[str]) -> list[str]:
    stripped: list[str] = []
    i = 0
    while i < len(passthrough):
        arg = passthrough[i]
        if arg == "--mcp-config":
            i += 1
            while i < len(passthrough) and not passthrough[i].startswith("-"):
                i += 1
            continue
        if arg.startswith("--mcp-config="):
            i += 1
            continue
        stripped.append(arg)
        i += 1
    return stripped


def _safe_mcp_proxy_name(name: str) -> str:
    safe = re.sub(r"[^A-Za-z0-9_.-]+", "_", name.strip())
    return safe[:80] or "server"


def _mcp_config_paths_from_passthrough(passthrough: list[str]) -> list[Path]:
    return [Path(value).expanduser() for value in _mcp_config_passthrough_values(passthrough)]


def claude_mcp_config_paths(passthrough: list[str] | None = None, cwd: Path | None = None, home: Path | None = None) -> list[Path]:
    cwd = cwd or Path.cwd()
    home = home or HOME
    paths: list[Path] = []
    paths.extend(_mcp_config_paths_from_passthrough(passthrough or []))
    current = cwd
    visited: set[str] = set()
    while True:
        key = _path_for_compare(current)
        if key in visited:
            break
        visited.add(key)
        paths.append(current / ".mcp.json")
        if current == current.parent:
            break
        current = current.parent
    paths.extend([
        home / ".mcp.json",
        home / ".claude" / "settings.json",
        home / ".claude.json",
    ])
    out: list[Path] = []
    seen: set[str] = set()
    for path in paths:
        key = _path_for_compare(path)
        if key in seen:
            continue
        seen.add(key)
        out.append(path)
    return out


def existing_claude_mcp_config_paths(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
) -> list[Path]:
    """Return existing Claude MCP config files that should be passed to Claude.

    This is intentionally transport-agnostic. Channel-capable MCP servers are
    discovered separately by the channel probe cache; this helper is only for
    preserving Claude Code's normal MCP tool surface when claude-any launches it.
    """
    return [
        path
        for path in claude_mcp_config_paths(passthrough, cwd, home)
        if path.exists() and path.is_file()
    ]


def discovered_claude_mcp_servers(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
) -> dict[str, dict[str, Any]]:
    cwd = cwd or Path.cwd()
    servers: dict[str, dict[str, Any]] = {}
    for path in existing_claude_mcp_config_paths(passthrough, cwd, home):
        for name, server in _read_mcp_servers_from_json(path, cwd):
            servers.setdefault(name, server)
    return servers


def write_native_mcp_config_from_discovery(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
) -> Path | None:
    """Write a Claude Code --mcp-config compatible file for native launches.

    Discovery may read files that are not directly valid --mcp-config inputs
    (notably ~/.claude/settings.json and project-scoped ~/.claude.json).  Claude
    Code expects a top-level mcpServers record, so native launches receive this
    normalized generated file instead of the source files.
    """
    servers = discovered_claude_mcp_servers(passthrough, cwd, home)
    if not servers:
        return None
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    NATIVE_MCP_CONFIG.write_text(json.dumps({"mcpServers": servers}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    try:
        os.chmod(NATIVE_MCP_CONFIG, 0o600)
    except Exception:
        pass
    router_log("INFO", f"native_mcp_config_written servers={','.join(sorted(servers))}")
    return NATIVE_MCP_CONFIG


def auto_discovered_mcp_channel_specs(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
) -> list[str]:
    cwd = cwd or Path.cwd()
    specs: list[str] = []
    for path in claude_mcp_config_paths(passthrough, cwd, home):
        if not path.exists() or not path.is_file():
            continue
        for name in _read_mcp_server_names_from_json(path, cwd):
            if re.search(r"\s", name):
                continue
            specs.append(f"server:{name}" if not is_channel_spec_tagged(name) else name)
    return _dedupe_strings(specs)


CHANNEL_PROBE_CACHE_VERSION = 1


def _builtin_router_probe_record() -> dict[str, Any]:
    return {
        "name": "claude-any-router",
        "capable": True,
        "transport": "sse",
        "source_path": "<built-in>",
        "url": f"{ROUTER_BASE}/ca/mcp/sse",
        "response_bytes": 0,
        "reason": "built-in",
    }


def _server_transport_label(server: dict[str, Any]) -> str:
    if not isinstance(server, dict):
        return "unknown"
    declared = str(server.get("type") or "").strip().lower()
    if declared in {"http", "streamable-http"}:
        return "streamable-http"
    if declared:
        return declared
    if server.get("url"):
        return "sse"
    if server.get("command"):
        return "stdio"
    return "unknown"


def _probe_mcp_servers_to_records(
    paths: Iterable[str],
    cwd: Path,
    *,
    include_router_self: bool = True,
    timeout_per_server: float | None = None,
) -> list[dict[str, Any]]:
    """Probe every MCP server referenced from the given config paths and
    return one record per server (capable and non-capable alike) for cache
    consumers and menu rendering."""
    records: list[dict[str, Any]] = []
    seen: set[str] = set()
    if include_router_self:
        records.append(_builtin_router_probe_record())
        seen.add("claude-any-router")
    for path_str in paths:
        if not path_str:
            continue
        path = Path(path_str)
        if not path.exists() or not path.is_file():
            continue
        for name, server in _read_mcp_servers_from_json(path, cwd):
            if name in seen:
                continue
            seen.add(name)
            if name == "claude-any-router":
                continue
            transport = _server_transport_label(server)
            record: dict[str, Any] = {
                "name": name,
                "capable": False,
                "transport": transport,
                "source_path": str(path),
                "response_bytes": 0,
                "response_received": False,
                "elapsed_ms": 0,
                "exit_code": None,
                "stderr_bytes": 0,
                "stderr_preview": "",
                "stdout_preview": "",
                "reason": "",
            }
            if isinstance(server.get("url"), str):
                record["url"] = str(server.get("url"))
            probe_fn: Callable[..., dict[str, Any]] | None = None
            if _mcp_server_is_stdio(server):
                probe_fn = probe_stdio_mcp_for_channel_capability_detailed
            elif transport == "sse" and isinstance(server.get("url"), str):
                probe_fn = probe_sse_mcp_for_channel_capability_detailed
            elif transport == "streamable-http" and isinstance(server.get("url"), str):
                probe_fn = probe_streamable_http_mcp_for_channel_capability_detailed
            else:
                record["reason"] = "transport_not_probed"
                records.append(record)
                continue
            try:
                detail = probe_fn(name, server, timeout=timeout_per_server)
                record["capable"] = bool(detail.get("capable"))
                record["reason"] = str(detail.get("reason") or "")
                record["response_bytes"] = int(detail.get("response_bytes") or 0)
                record["response_received"] = bool(detail.get("response_received"))
                record["elapsed_ms"] = int(detail.get("elapsed_ms") or 0)
                record["exit_code"] = detail.get("exit_code")
                record["stderr_bytes"] = int(detail.get("stderr_bytes") or 0)
                record["stderr_preview"] = str(detail.get("stderr_preview") or "")
                record["stdout_preview"] = str(detail.get("stdout_preview") or "")
            except Exception as exc:
                record["reason"] = f"probe_exception:{type(exc).__name__}"
                router_log("WARN", f"channel_probe_exception server={name} error={type(exc).__name__}: {exc}")
            records.append(record)
    return records


def read_channel_probe_cache() -> dict[str, Any]:
    if not CHANNEL_PROBE_CACHE_PATH.exists():
        return {"version": CHANNEL_PROBE_CACHE_VERSION, "probed_at": 0.0, "servers": []}
    try:
        data = json.loads(CHANNEL_PROBE_CACHE_PATH.read_text(encoding="utf-8"))
    except Exception as exc:
        router_log("WARN", f"channel_probe_cache_read_failed error={type(exc).__name__}: {exc}")
        return {"version": CHANNEL_PROBE_CACHE_VERSION, "probed_at": 0.0, "servers": []}
    if not isinstance(data, dict):
        return {"version": CHANNEL_PROBE_CACHE_VERSION, "probed_at": 0.0, "servers": []}
    data.setdefault("version", CHANNEL_PROBE_CACHE_VERSION)
    data.setdefault("probed_at", 0.0)
    servers = data.get("servers")
    data["servers"] = [item for item in servers if isinstance(item, dict)] if isinstance(servers, list) else []
    return data


def _write_channel_probe_cache(cache: dict[str, Any]) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    tmp = CHANNEL_PROBE_CACHE_PATH.with_suffix(".json.tmp")
    tmp.write_text(json.dumps(cache, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    tmp.replace(CHANNEL_PROBE_CACHE_PATH)
    try:
        os.chmod(CHANNEL_PROBE_CACHE_PATH, 0o600)
    except Exception:
        pass


def refresh_channel_probe_cache(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
    timeout_per_server: float | None = None,
) -> dict[str, Any]:
    """Re-scan known MCP config files, probe each stdio entry for the
    channel capability, write the result to disk and return it."""
    cwd = cwd or Path.cwd()
    paths = [str(p) for p in claude_mcp_config_paths(passthrough or [], cwd, home)]
    records = _probe_mcp_servers_to_records(paths, cwd, timeout_per_server=timeout_per_server)
    cache = {
        "version": CHANNEL_PROBE_CACHE_VERSION,
        "probed_at": time.time(),
        "servers": records,
    }
    _write_channel_probe_cache(cache)
    capable = [r["name"] for r in records if r.get("capable")]
    router_log(
        "INFO",
        f"channel_probe_cache_refreshed total={len(records)} capable={len(capable)} servers={','.join(capable) or '-'}",
    )
    return cache


def cached_channel_probe_servers() -> list[dict[str, Any]]:
    cache = read_channel_probe_cache()
    servers = cache.get("servers")
    if isinstance(servers, list):
        return [item for item in servers if isinstance(item, dict)]
    return []


def channel_probe_record_bucket(record: dict[str, Any]) -> str:
    """Classify a probe record for display.

    `non-capable` is reserved for a server that answered initialize and did
    not advertise claude/channel. Timeouts and transport failures are
    inconclusive: the server may still be channel-capable, but the probe did
    not complete.
    """
    if record.get("capable"):
        return "capable"
    reason = str(record.get("reason") or "").strip()
    if reason == "no_experimental_claude_channel" and record.get("response_received"):
        return "non_capable"
    if reason in {"transport_not_probed", "no_url"}:
        return "skipped"
    return "inconclusive"


def cached_channel_capable_server_names() -> list[str]:
    """Capable server names from cache, with claude-any-router always
    present (because the router's own MCP bridge is built into claude-any)."""
    names = [str(r.get("name")) for r in cached_channel_probe_servers() if r.get("capable") and r.get("name")]
    if "claude-any-router" not in names:
        names.insert(0, "claude-any-router")
    return _dedupe_strings(names)


def cached_external_channel_capable_server_names() -> list[str]:
    """Channel-capable MCP servers from cache, excluding claude-any's router.

    Claude Native launches can directly subscribe to external channel-capable
    MCP servers with --dangerously-load-development-channels. The built-in
    claude-any-router is only valid when claude-any intentionally starts and
    manages its router bridge, so it must not be auto-injected into plain
    native launches.
    """
    names: list[str] = []
    for record in cached_channel_probe_servers():
        if not record.get("capable"):
            continue
        name = str(record.get("name") or "").strip()
        if not name or name.strip().lower() in _NATIVE_ROUTER_CHANNEL_NAMES:
            continue
        names.append(name)
    return _dedupe_strings(names)


def native_auto_channel_capable_server_names(passthrough: list[str] | None = None) -> list[str]:
    """External channel-capable servers that are also in current MCP discovery."""
    discovered = set(discovered_claude_mcp_servers(passthrough or []).keys())
    if not discovered:
        return []
    return [name for name in cached_external_channel_capable_server_names() if name in discovered]


def cached_channel_source_paths_for_specs(specs: Iterable[str]) -> list[Path]:
    """Return MCP config files that supplied the selected channel servers.

    The channel picker is driven by the probe cache, so launch should also
    honor the cache's source_path. This keeps a selected server available even
    when it came from an explicit/probed config path rather than the default
    discovery set for the current working directory.
    """
    wanted = {
        str(spec).split(":", 1)[1]
        for spec in specs
        if str(spec).startswith("server:") and str(spec).split(":", 1)[1]
    }
    if not wanted:
        return []
    out: list[Path] = []
    seen: set[str] = set()
    for record in cached_channel_probe_servers():
        name = str(record.get("name") or "")
        if name not in wanted:
            continue
        source = str(record.get("source_path") or "")
        if not source or source == "<built-in>":
            continue
        path = Path(source).expanduser()
        key = _path_for_compare(path)
        if key in seen:
            continue
        seen.add(key)
        out.append(path)
    return out


def _server_names_from_channel_specs(specs: Iterable[str]) -> list[str]:
    names: list[str] = []
    for spec in specs:
        text = str(spec or "").strip()
        if not text.startswith("server:"):
            continue
        name = text.split(":", 1)[1].strip()
        if name:
            names.append(name)
    return _dedupe_strings(names)


def channel_probe_cache_needs_launch_refresh(cfg: dict[str, Any], passthrough: list[str]) -> bool:
    cache = read_channel_probe_cache()
    records = cached_channel_probe_servers()
    if not cache.get("probed_at") or not records:
        return True
    configured_names = [
        name for name in _server_names_from_channel_specs(channel_specs_for_launch(cfg, passthrough))
        if name != "claude-any-router"
    ]
    if not configured_names:
        return False
    if not cache.get("probed_at"):
        return True
    by_name = {str(r.get("name") or ""): r for r in records if r.get("name")}
    for name in configured_names:
        record = by_name.get(name)
        if not record or not record.get("capable"):
            return True
        source = str(record.get("source_path") or "")
        if not source or source == "<built-in>":
            return True
    return False


def ensure_channel_probe_cache_for_launch(cfg: dict[str, Any], passthrough: list[str]) -> bool:
    if not channel_probe_cache_needs_launch_refresh(cfg, passthrough):
        return False
    try:
        router_log("INFO", "channel_probe_launch_refresh reason=missing_cache_or_selected_server")
        refresh_channel_probe_cache(passthrough)
        return True
    except Exception as exc:
        router_log("WARN", f"channel_probe_launch_refresh_failed error={type(exc).__name__}: {exc}")
        return False


def channel_probe_summary_message(prefix: str, cache: dict[str, Any]) -> str:
    records = [r for r in cache.get("servers") or [] if isinstance(r, dict)]
    capable = [r for r in records if channel_probe_record_bucket(r) == "capable"]
    inconclusive = [r for r in records if channel_probe_record_bucket(r) == "inconclusive"]
    non_capable = [r for r in records if channel_probe_record_bucket(r) == "non_capable"]
    return (
        f"{prefix}: {len(capable)} channel-capable, "
        f"{len(inconclusive)} inconclusive, {len(non_capable)} non-capable server(s)."
    )


def channel_panel_rows_for_menu(cfg: dict[str, Any], passthrough: list[str]) -> tuple[list[str], list[str], list[str]]:
    messages: list[str] = []
    if channel_probe_cache_needs_launch_refresh(cfg, passthrough):
        try:
            router_log("INFO", "channel_probe_menu_refresh reason=missing_cache_or_selected_server")
            result = refresh_channel_probe_cache(passthrough)
            messages = [channel_probe_summary_message("Probe complete", result)]
        except Exception as exc:
            router_log("WARN", f"channel_probe_menu_refresh_failed error={type(exc).__name__}: {exc}")
            messages = [f"Channel probe failed: {type(exc).__name__}: {exc}"]
    rows, values = channel_panel_rows(cfg)
    return rows, values, messages


def parse_passthrough_channel_specs(passthrough: list[str]) -> list[str]:
    """Extract channel specs from passthrough args of either
    --channels or --dangerously-load-development-channels (and the
    =VALUE inline form)."""
    specs: list[str] = []
    i = 0
    while i < len(passthrough):
        arg = passthrough[i]
        if arg in ("--channels", "--dangerously-load-development-channels"):
            i += 1
            while i < len(passthrough) and is_channel_spec_tagged(passthrough[i]):
                specs.append(passthrough[i])
                i += 1
            continue
        if arg.startswith("--channels=") or arg.startswith("--dangerously-load-development-channels="):
            value = arg.split("=", 1)[1].strip()
            if value and is_channel_spec_tagged(value):
                specs.append(value)
            i += 1
            continue
        i += 1
    return _dedupe_strings(specs)


def auto_import_passthrough_channels(passthrough: list[str]) -> list[str]:
    """Add channel specs that arrived as CLI passthrough to the persisted
    cfg.channels list, so they show up alongside auto-detected entries in
    the menu and survive subsequent launches. Returns the newly added specs."""
    specs = parse_passthrough_channel_specs(passthrough)
    if not specs:
        return []
    cfg = load_config()
    existing = set(channel_specs(cfg))
    if all(spec in existing for spec in specs):
        return []
    cc = cfg.setdefault("claude_code", {})
    merged = [spec for spec in channel_specs(cfg) if spec != BUILTIN_CHANNEL_SPEC]
    added: list[str] = []
    for spec in specs:
        if spec in existing or spec == BUILTIN_CHANNEL_SPEC:
            continue
        merged.append(spec)
        existing.add(spec)
        added.append(spec)
    if not added:
        return []
    cc["channels"] = merged
    save_config(cfg)
    invalidate_config_cache()
    router_log(
        "INFO",
        f"channels_auto_imported_from_passthrough count={len(added)} specs={','.join(added)}",
    )
    return added


def _mcp_sse_servers_from_mapping(mapping: Any) -> list[dict[str, Any]]:
    if not isinstance(mapping, dict):
        return []
    found: list[dict[str, Any]] = []
    for key in ("mcpServers", "servers"):
        servers = mapping.get(key)
        if not isinstance(servers, dict):
            continue
        for raw_name, raw_server in servers.items():
            name = str(raw_name or "").strip()
            if not name or not isinstance(raw_server, dict):
                continue
            url = str(raw_server.get("url") or raw_server.get("endpoint") or "").strip()
            if not url.startswith(("http://", "https://")):
                continue
            server_type = str(raw_server.get("type") or "").strip().lower()
            if server_type and server_type not in ("sse", "http", "streamable-http"):
                continue
            transport = "streamable-http" if server_type in ("http", "streamable-http") else "sse"
            headers = raw_server.get("headers") if isinstance(raw_server.get("headers"), dict) else {}
            streamable_requires_session = raw_server.get(
                "streamable_requires_session",
                raw_server.get("require_session", raw_server.get("mcp_session_required", True)),
            )
            found.append(
                {
                    "name": f"mcp-{name}",
                    "url": url,
                    "type": server_type or transport,
                    "transport": transport,
                    "headers": {str(k): str(v) for k, v in headers.items() if str(k).strip()},
                    "channel": name,
                    "sender_id": name,
                    "recipient": "all",
                    "mcp": True,
                    "streamable_requires_session": streamable_requires_session,
                }
            )
    return found


def _read_mcp_sse_servers_from_json(path: Path, cwd: Path) -> list[dict[str, Any]]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return []
    servers = _mcp_sse_servers_from_mapping(data)
    if path.name == ".claude.json" and isinstance(data, dict):
        projects = data.get("projects")
        if isinstance(projects, dict):
            for project_key, project_data in projects.items():
                if _project_key_matches_cwd(str(project_key), cwd):
                    servers.extend(_mcp_sse_servers_from_mapping(project_data))
    out: list[dict[str, Any]] = []
    seen: set[str] = set()
    for server in servers:
        key = f"{server.get('name')}|{server.get('url')}"
        if key in seen:
            continue
        seen.add(key)
        out.append(server)
    return out


def external_mcp_channel_server_names_from_configs(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
    extra_config_paths: list[Path | str] | None = None,
) -> list[str]:
    cwd = cwd or Path.cwd()
    paths = [Path(path).expanduser() for path in extra_config_paths or []]
    paths.extend(claude_mcp_config_paths(passthrough, cwd, home))
    names: list[str] = []
    seen_paths: set[str] = set()
    for path in paths:
        key = _path_for_compare(path)
        if key in seen_paths:
            continue
        seen_paths.add(key)
        if not path.exists() or not path.is_file():
            continue
        for server in _read_mcp_sse_servers_from_json(path, cwd):
            name = str(server.get("channel") or "").strip()
            if not name or name.strip().lower() in _NATIVE_ROUTER_CHANNEL_NAMES:
                continue
            names.append(name)
    return _dedupe_strings(names)


def auto_start_sse_channels_from_mcp_configs(
    passthrough: list[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
    extra_config_paths: list[Path | str] | None = None,
    allowed_server_names: Iterable[str] | None = None,
) -> list[dict[str, Any]]:
    cwd = cwd or Path.cwd()
    started: list[dict[str, Any]] = []
    allowed: set[str] | None = None
    if allowed_server_names is not None:
        allowed = {
            _channel_sse_public_mcp_name(str(name or "").strip())
            for name in allowed_server_names
            if str(name or "").strip()
        }
    paths = [Path(path).expanduser() for path in extra_config_paths or []]
    paths.extend(claude_mcp_config_paths(passthrough, cwd, home))
    seen: set[str] = set()
    for path in paths:
        key = _path_for_compare(path)
        if key in seen:
            continue
        seen.add(key)
        if not path.exists() or not path.is_file():
            continue
        for server in _read_mcp_sse_servers_from_json(path, cwd):
            public_name = _channel_sse_public_mcp_name(str(server.get("name") or ""))
            if allowed is not None and public_name not in allowed:
                continue
            try:
                status = start_channel_sse_connection(server)
                started.append(status)
                router_log("INFO", f"channel_sse_auto_started name={status.get('name')} url={status.get('url')}")
            except Exception as exc:
                router_log("WARN", f"channel_sse_auto_start_failed path={path} error={type(exc).__name__}: {exc}")
    return started


def proxy_owned_channel_server_names() -> set[str]:
    """Servers the launch process already routes through claude-any's mcp-proxy.

    launch_claude force-proxies channel-capable streamable-HTTP servers so the
    proxy is the single owner of their notification stream (and idle wake). The
    router runs in a separate process and would otherwise ALSO open a channel
    SSE worker for the same server if it appears in config channels, producing a
    second owner and duplicate notifications. The proxied servers are recorded
    in the generated mcp-proxy.json (each as a `mcp-proxy --server-name X`
    command wrapper), so the router reads that file to learn which servers to
    leave to the proxy.

    A wrapped server whose per-server config disables the proxy notification
    stream is NOT proxy-owned for notifications (the proxy suppresses its GET
    stream), so it is excluded here -- the router worker must still own it, or
    there would be zero notification owners.
    """
    owned: set[str] = set()
    try:
        if not MCP_PROXY_CONFIG.exists():
            return owned
        data = json.loads(MCP_PROXY_CONFIG.read_text(encoding="utf-8"))
    except Exception:
        return owned
    servers = data.get("mcpServers") if isinstance(data, dict) else None
    if not isinstance(servers, dict):
        return owned
    for name, entry in servers.items():
        if not isinstance(entry, dict):
            continue
        args = entry.get("args")
        if not isinstance(args, list):
            continue
        args_s = [str(a) for a in args]
        if "mcp-proxy" not in args_s or "--server-name" not in args_s:
            continue
        try:
            wrapped_name = args_s[args_s.index("--server-name") + 1].strip()
        except IndexError:
            wrapped_name = str(name).strip()
        # Skip servers whose proxy notification stream is disabled -- the proxy
        # does not own notifications for them, so the router must keep its worker.
        if _proxy_server_config_disables_notifications(args_s):
            continue
        if wrapped_name:
            owned.add(wrapped_name)
    return owned


def _proxy_server_config_disables_notifications(args_s: list[str]) -> bool:
    try:
        cfg_path = args_s[args_s.index("--server-config") + 1]
    except (ValueError, IndexError):
        return False
    try:
        server = json.loads(Path(cfg_path).read_text(encoding="utf-8"))
    except Exception:
        return False
    return _mcp_server_disable_proxy_notification_stream(server) if isinstance(server, dict) else False


def router_managed_channel_server_names(cfg: dict[str, Any]) -> list[str]:
    names = _server_names_from_channel_specs(channel_specs_for_launch(cfg, []))
    names = [name for name in names if name.strip().lower() not in _NATIVE_ROUTER_CHANNEL_NAMES]
    owned = proxy_owned_channel_server_names()
    if not owned:
        return names
    owned_public = {_channel_sse_public_mcp_name(n) for n in owned}
    kept = [n for n in names if _channel_sse_public_mcp_name(n) not in owned_public]
    if len(kept) != len(names):
        dropped = sorted({n for n in names} - {n for n in kept})
        router_log(
            "INFO",
            "router_channel_sse_skipped_proxy_owned servers=%s" % (",".join(dropped) or "-"),
        )
    return kept


def start_router_managed_channel_sse(cfg: dict[str, Any]) -> list[dict[str, Any]]:
    if not should_use_channel_llm_delivery(True, [], cfg):
        return []
    names = router_managed_channel_server_names(cfg)
    source_paths: list[Path] = []
    # Pass the (possibly empty) name list straight through. An empty list MUST
    # remain an empty allow-list ("open nothing"), not collapse to None which
    # auto_start_sse_channels_from_mcp_configs treats as "open every MCP server"
    # -- that allow-all flip made the router open a second held notification
    # stream to backends like ai-net-http even when no channels were configured,
    # so the same digest arrived twice (router worker + Claude Code's own MCP).
    allowed_names: list[str] = list(names)
    if names:
        try:
            ensure_channel_probe_cache_for_launch(cfg, [])
            source_paths = cached_channel_source_paths_for_specs([f"server:{name}" for name in names])
        except Exception as exc:
            router_log("WARN", f"router_channel_probe_cache_failed error={type(exc).__name__}: {exc}")
    else:
        router_log("INFO", "router_channel_sse_skipped reason=no_external_channel_specs")
        return []
    started = auto_start_sse_channels_from_mcp_configs(
        [],
        extra_config_paths=source_paths,
        allowed_server_names=allowed_names,
    )
    router_log(
        "INFO",
        "router_channel_sse_started count=%d servers=%s"
        % (len(started), ",".join(str(item.get("name") or "") for item in started) or "-"),
    )
    return started


def channel_specs_for_launch(cfg: dict[str, Any], passthrough: list[str], extra_specs: list[str] | None = None) -> list[str]:
    configured = [spec for spec in channel_specs(cfg) if is_channel_spec_tagged(spec)]
    specs = configured
    if extra_specs:
        specs = [*specs, *extra_specs]
    return _dedupe_strings(spec for spec in specs if is_channel_spec_tagged(spec))


def is_channel_spec_tagged(spec: str) -> bool:
    return spec.startswith("plugin:") or spec.startswith("server:")


def channel_status_text(cfg: dict[str, Any] | None = None) -> str:
    cfg = cfg or load_config()
    channels = channel_specs(cfg)
    if not channels:
        return "off"
    return f"{len(channels)} channel{'s' if len(channels) != 1 else ''}"


def set_channel_development_enabled(enabled: bool) -> list[str]:
    return ["Channel wake delivery is always enabled by Claude Any."]


def normalize_channel_delivery(value: Any) -> str:
    text = str(value or "").strip().lower().replace("_", "-")
    if text in {"native", "native-channel", "native-channel-bridge", "claude-channel", "claude/native"}:
        return "native"
    if text in {"llm", "model", "context", "router", "advisor", "inband", "in-band"}:
        return "llm"
    if text in {"stdin", "pty", "terminal", "wake", "wake-proxy", "legacy"}:
        return "stdin"
    if text in {"auto", ""}:
        return "llm"
    return "llm"


def channel_delivery_mode(cfg: dict[str, Any] | None = None) -> str:
    env_value = os.environ.get("CLAUDE_ANY_CHANNEL_DELIVERY")
    if env_value is not None:
        return normalize_channel_delivery(env_value)
    cfg = cfg or load_config()
    return normalize_channel_delivery(cfg.setdefault("claude_code", {}).get("channel_delivery", "llm"))


def set_channel_delivery_config(value: Any) -> list[str]:
    mode = normalize_channel_delivery(value)
    cfg = load_config()
    cfg.setdefault("claude_code", {})["channel_delivery"] = mode
    save_config(cfg)
    if mode == "native":
        return ["Channel delivery set to native claude/channel bridge."]
    if mode == "llm":
        return ["Channel delivery set to LLM context injection."]
    return ["Channel delivery set to stdin wake proxy."]


def add_channel_spec(spec: str, *, development: bool = False) -> list[str]:
    spec = spec.strip()
    if not spec:
        return ["Channel spec was empty."]
    if not is_channel_spec_tagged(spec):
        return ["Channel spec must start with plugin: or server:."]
    if spec == BUILTIN_CHANNEL_SPEC:
        return ["Claude Any router channel is always enabled."]
    cfg = load_config()
    cc = cfg.setdefault("claude_code", {})
    channels = [item for item in channel_specs(cfg) if item != BUILTIN_CHANNEL_SPEC]
    if spec not in channels:
        channels.append(spec)
    cc["channels"] = channels
    save_config(cfg)
    return [f"Channel added: {spec}."]


def remove_channel_spec(spec: str) -> list[str]:
    if spec == BUILTIN_CHANNEL_SPEC:
        return ["Claude Any router channel is always enabled and cannot be removed."]
    cfg = load_config()
    cc = cfg.setdefault("claude_code", {})
    before = [item for item in channel_specs(cfg) if item != BUILTIN_CHANNEL_SPEC]
    after = [item for item in before if item != spec]
    cc["channels"] = after
    save_config(cfg)
    return [f"Channel removed: {spec}." if len(after) != len(before) else f"Channel was not configured: {spec}."]


def clear_channel_specs() -> list[str]:
    cfg = load_config()
    cfg.setdefault("claude_code", {})["channels"] = []
    save_config(cfg)
    return ["External Claude Code channels cleared. Claude Any router remains enabled."]


def cmd_channels(args: argparse.Namespace) -> None:
    cfg = load_config()
    values = list(getattr(args, "values", []) or [])
    if not values:
        print(f"channels: {channel_status_text(cfg)}")
        print(f"delivery: {channel_delivery_mode(cfg)}")
        for name, spec in OFFICIAL_CHANNEL_PLUGINS.items():
            mark = "*" if spec in channel_specs(cfg) else " "
            print(f" {mark} {name:<10} {spec}")
        for spec in channel_specs(cfg):
            if spec not in OFFICIAL_CHANNEL_PLUGINS.values():
                print(f" * custom    {spec}")
        return
    head = values[0].strip().lower()
    if head in ("on", "enable", "add"):
        if len(values) < 2:
            raise SystemExit("Usage: claude-any channels add CHANNEL_SPEC")
        for line in add_channel_spec(values[1]):
            print(line)
        return
    if head in ("dev", "development"):
        if len(values) >= 2 and values[1].lower() in ("on", "off", "true", "false", "1", "0"):
            for line in set_channel_development_enabled(True):
                print(line)
            return
        if len(values) < 2:
            raise SystemExit("Usage: claude-any channels add CHANNEL_SPEC")
        for line in add_channel_spec(values[1]):
            print(line)
        return
    if head in ("off", "disable", "remove", "rm"):
        if len(values) < 2:
            raise SystemExit("Usage: claude-any channels remove CHANNEL_SPEC")
        for line in remove_channel_spec(values[1]):
            print(line)
        return
    if head in ("clear", "reset"):
        for line in clear_channel_specs():
            print(line)
        return
    if head in ("detect", "probe", "refresh"):
        try:
            result = refresh_channel_probe_cache()
        except Exception as exc:
            raise SystemExit(f"Channel probe failed: {type(exc).__name__}: {exc}")
        servers = result.get("servers") or []
        capable = [r for r in servers if channel_probe_record_bucket(r) == "capable"]
        non_capable = [r for r in servers if channel_probe_record_bucket(r) == "non_capable"]
        inconclusive = [r for r in servers if channel_probe_record_bucket(r) == "inconclusive"]
        skipped = [r for r in servers if channel_probe_record_bucket(r) == "skipped"]
        probed_at = result.get("probed_at") or 0
        ts_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(probed_at)) if probed_at else "-"
        timeout_s = channel_probe_default_timeout()
        print(f"channel probe complete (probed at {ts_str}, timeout {timeout_s:.1f}s per server)")
        print(f"  capable     : {len(capable)}")
        for r in capable:
            transport = r.get("transport") or "?"
            source = r.get("source_path") or ""
            suffix = " built-in" if source == "<built-in>" else f" {source}"
            print(f"    * {r.get('name')} ({transport}){suffix}")
        print(f"  non-capable : {len(non_capable)}")
        for r in non_capable:
            transport = r.get("transport") or "?"
            reason = r.get("reason") or "-"
            print(f"      {r.get('name')} ({transport}) reason={reason}")
        print(f"  inconclusive: {len(inconclusive)}")
        timeout_seen = False
        exited_seen = False
        for r in inconclusive:
            transport = r.get("transport") or "?"
            reason = r.get("reason") or "-"
            elapsed = r.get("elapsed_ms")
            bytes_seen = r.get("response_bytes")
            stderr_bytes = r.get("stderr_bytes")
            exit_code = r.get("exit_code")
            extra = []
            if isinstance(elapsed, int) and elapsed:
                extra.append(f"elapsed={elapsed}ms")
            if isinstance(bytes_seen, int) and bytes_seen:
                extra.append(f"stdout={bytes_seen}B")
            if isinstance(stderr_bytes, int) and stderr_bytes:
                extra.append(f"stderr={stderr_bytes}B")
            if exit_code is not None:
                extra.append(f"exit={exit_code}")
            extra_str = (" " + " ".join(extra)) if extra else ""
            print(f"      {r.get('name')} ({transport}) reason={reason}{extra_str}")
            stderr_preview = r.get("stderr_preview")
            if stderr_preview:
                print(f"        stderr: {stderr_preview}")
            stdout_preview = r.get("stdout_preview")
            if stdout_preview:
                print(f"        stdout: {stdout_preview}")
            if str(reason).startswith("timeout"):
                timeout_seen = True
            if reason == "exited_without_response":
                exited_seen = True
        if skipped:
            print(f"  skipped     : {len(skipped)}")
            for r in skipped:
                transport = r.get("transport") or "?"
                reason = r.get("reason") or "-"
                print(f"      {r.get('name')} ({transport}) reason={reason}")
        if timeout_seen:
            print("  hint: inconclusive timeout means the probe could not finish; the server may still be capable.")
            print("        increase CLAUDE_ANY_CHANNEL_PROBE_TIMEOUT_SECONDS if cold start is the cause,")
            print("        or re-run with the latest claude-any if this was an SSE endpoint event race.")
        if exited_seen:
            print("  hint: exited_without_response means the child died before responding. Check stderr above.")
        return
    if head in ("delivery", "mode"):
        if len(values) < 2:
            print(f"channel_delivery: {channel_delivery_mode(cfg)}")
            return
        for line in set_channel_delivery_config(values[1]):
            print(line)
        return
    if head in OFFICIAL_CHANNEL_PLUGINS:
        spec = OFFICIAL_CHANNEL_PLUGINS[head]
        if spec in channel_specs(cfg):
            for line in remove_channel_spec(spec):
                print(line)
        else:
            for line in add_channel_spec(spec):
                print(line)
        return
    for line in add_channel_spec(values[0]):
        print(line)


def cmd_channel_delivery(args: argparse.Namespace) -> None:
    value = getattr(args, "value", None)
    if value:
        for line in set_channel_delivery_config(value):
            print(line)
    else:
        print(f"channel_delivery: {channel_delivery_mode()}")


def cmd_ollama_native(args: argparse.Namespace) -> None:
    cfg = load_config()
    pcfg = cfg["providers"]["ollama"]
    if args.value:
        value = args.value.lower()
        if value in ("on", "enable", "enabled", "true", "1"):
            pcfg["native_compat"] = True
            save_config(cfg)
        elif value in ("off", "disable", "disabled", "false", "0"):
            pcfg["native_compat"] = False
            save_config(cfg)
        else:
            raise SystemExit("Use: claude-any ollama-native on|off|status")
    state = "on" if pcfg.get("native_compat", True) else "off"
    print(f"ollama_native_compat: {state}")
    print(f"base_url: {pcfg.get('base_url')}")
    print(f"model: {pcfg.get('current_model')}")
    print("launch_env: ANTHROPIC_BASE_URL=<ollama>, ANTHROPIC_AUTH_TOKEN=ollama, ANTHROPIC_API_KEY=\"\"")


def apply_ollama_option(pcfg: dict[str, Any], token: str) -> None:
    if token.startswith("unset:"):
        key = token.split(":", 1)[1].strip()
        if key in ("num_ctx", "ctx"):
            pcfg["num_ctx"] = "auto"
        elif key in ("context_window", "context", "max_model_len"):
            pcfg.pop("context_window", None)
            pcfg["num_ctx"] = "auto"
            pcfg.pop("num_ctx_max", None)
        elif key in ("num_ctx_min", "ctx_min", "min"):
            pcfg.pop("num_ctx_min", None)
        elif key in ("num_ctx_max", "ctx_max", "max"):
            pcfg.pop("num_ctx_max", None)
        elif key in ("max_output_tokens", "max_tokens", "maxtoken", "max_token", "num_predict"):
            pcfg.pop("max_output_tokens", None)
            pcfg.setdefault("ollama_options", {}).pop("num_predict", None)
        elif key in ("keep_alive", "keepalive"):
            pcfg.pop("keep_alive", None)
        elif key == "think":
            pcfg["think"] = False
        elif key in ("stream", "stream_enabled"):
            pcfg["stream_enabled"] = True
        elif key in ("stream_word_chunking", "word_chunking", "stream_chunk", "stream_words"):
            pcfg["stream_word_chunking"] = False
        elif key in ("stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms"):
            pcfg.pop("stream_idle_timeout_ms", None)
        elif key in ("rate_limit", "rate_limit_rpm", "rpm"):
            pcfg["rate_limit_rpm"] = 0
            pcfg["rate_limit_status"] = False
        elif key in ("rate_limit_status", "rpm_status"):
            pcfg["rate_limit_status"] = False
        else:
            pcfg.setdefault("ollama_options", {}).pop(key, None)
        return
    if "=" not in token:
        raise SystemExit(f"Expected key=value or unset:key, got: {token}")
    key, raw_value = token.split("=", 1)
    key = key.strip()
    value = parse_config_value(raw_value)
    if key in ("num_ctx", "ctx"):
        if isinstance(value, str) and value.lower() in ("auto", "dynamic"):
            pcfg["num_ctx"] = "auto"
        else:
            fixed = positive_int(value)
            if not fixed:
                raise SystemExit("num_ctx must be auto or a positive integer")
            pcfg["num_ctx"] = fixed
        return
    if key in ("context_window", "context", "max_model_len"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("context_window must be a positive integer")
        pcfg["context_window"] = fixed
        pcfg["num_ctx"] = "auto"
        pcfg["num_ctx_max"] = fixed
        pcfg["num_ctx_min"] = min(fixed, 32768 if fixed <= 65536 else 65536)
        return
    if key in ("num_ctx_min", "ctx_min", "min"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("num_ctx_min must be a positive integer")
        pcfg["num_ctx_min"] = fixed
        return
    if key in ("num_ctx_max", "ctx_max", "max"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("num_ctx_max must be a positive integer")
        pcfg["num_ctx_max"] = fixed
        return
    if key in ("keep_alive", "keepalive"):
        if value is None:
            pcfg.pop("keep_alive", None)
        else:
            pcfg["keep_alive"] = str(value)
        return
    if key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("timeout must be a positive integer; values above 10000 are treated as milliseconds")
        pcfg["request_timeout_ms"] = fixed if key.endswith("_ms") or fixed > 10000 else fixed * 1000
        return
    if key in ("stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("stream_idle_timeout_ms must be a positive integer; values above 10000 are treated as milliseconds")
        pcfg["stream_idle_timeout_ms"] = fixed if key.endswith("_ms") or fixed > 10000 else fixed * 1000
        return
    if key in ("max_output_tokens", "max_tokens", "maxtoken", "max_token", "num_predict"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("max_tokens/num_predict must be a positive integer")
        pcfg["max_output_tokens"] = fixed
        pcfg.setdefault("ollama_options", {})["num_predict"] = fixed
        return
    if key == "think":
        pcfg["think"] = bool(value)
        return
    if key in ("stream", "stream_enabled"):
        pcfg["stream_enabled"] = parse_bool(value, default=True)
        return
    if key in ("stream_word_chunking", "word_chunking", "stream_chunk", "stream_words"):
        pcfg["stream_word_chunking"] = parse_bool(value, default=False)
        return
    if key in ("rate_limit", "rate_limit_rpm", "rpm"):
        fixed = positive_int(value)
        if not fixed:
            if str(value).lower() in ("0", "false", "off", "disable", "disabled", "none", "unset"):
                pcfg["rate_limit_rpm"] = 0
                pcfg["rate_limit_status"] = False
                return
            raise SystemExit("rate_limit_rpm must be a positive integer, or 0 to disable")
        pcfg["rate_limit_rpm"] = fixed
        return
    if key in ("rate_limit_status", "rpm_status"):
        pcfg["rate_limit_status"] = parse_bool(value, default=False)
        return
    opts = pcfg.setdefault("ollama_options", {})
    if value is None:
        opts.pop(key, None)
    else:
        opts[key] = value


def cmd_ollama_options(args: argparse.Namespace) -> None:
    cfg = load_config()
    values = list(getattr(args, "values", []) or [])
    provider = cfg.get("current_provider", "ollama")
    if provider not in ("ollama", "ollama-cloud"):
        provider = "ollama"
    if values:
        try:
            maybe_provider = normalize_provider(values[0])
            if maybe_provider in ("ollama", "ollama-cloud"):
                provider = maybe_provider
                values = values[1:]
        except SystemExit:
            pass
    pcfg = cfg["providers"][provider]
    if values:
        context_changed = any(
            token.split("=", 1)[0].replace("unset:", "").strip() in ("num_ctx", "ctx", "num_ctx_min", "ctx_min", "min", "num_ctx_max", "ctx_max", "max")
            for token in values
        )
        explicit_timeout = any(
            token.split("=", 1)[0].replace("unset:", "").strip() in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms", "stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms")
            for token in values
        )
        for token in values:
            apply_ollama_option(pcfg, token)
        timeout_lines = apply_recommended_timeout_for_model_context(provider, pcfg) if context_changed and not explicit_timeout else []
        save_config(cfg)
        clear_model_cache()
        print(f"Ollama options updated for {provider}.")
        for line in timeout_lines:
            print(line)
    print(f"provider: {provider}")
    print(f"num_ctx: {ollama_num_ctx_status(pcfg)}")
    print(f"keep_alive: {pcfg.get('keep_alive', 'default')}")
    print(f"think: {bool(pcfg.get('think', False))}")
    print(f"request_timeout_ms: {pcfg.get('request_timeout_ms', 'default')}")
    used, limit = router_rate_limit_usage(provider, pcfg)
    if limit is not None:
        print(f"rate_limit_rpm: {limit}")
        if bool(pcfg.get("rate_limit_status", False)):
            suffix = f"{used}/{limit}" if limit > 0 else f"{used}/min (unmanaged)"
            print(f"rpm_used: {suffix}")
    print(f"ollama_options: {ollama_options_status(pcfg)}")
    print("Examples:")
    print("  claude-anyctl ollama-options num_ctx=auto min=32768 max=131072")
    print("  claude-anyctl ollama-options num_ctx=65536 temperature=0.7 top_p=0.8 max_tokens=32768 timeout=300000")
    print("  claude-any --ca-ollama-option temperature=0.7 --ca-ollama-num-ctx 65536")


PROVIDER_OPTION_PROVIDERS = ("anthropic", "vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "ollama", "ollama-cloud", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks")
PROVIDER_SAMPLING_OPTION_PROVIDERS = ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "openrouter")
PROVIDER_SAMPLING_OPTIONS = ("temperature", "top_p", "top_k")


def sampling_option_key(key: str) -> str | None:
    normalized = key.strip().lower().replace("-", "_")
    aliases = {
        "temp": "temperature",
        "temperature": "temperature",
        "top": "top_p",
        "top_p": "top_p",
        "topp": "top_p",
        "topk": "top_k",
        "top_k": "top_k",
    }
    return aliases.get(normalized)


def validate_sampling_option(key: str, value: Any) -> float | int:
    if key == "temperature":
        fixed = finite_float(value)
        if fixed is None or fixed < 0 or fixed > 2:
            raise SystemExit("temperature must be a number from 0 to 2")
        return fixed
    if key == "top_p":
        fixed = finite_float(value)
        if fixed is None or fixed <= 0 or fixed > 1:
            raise SystemExit("top_p must be a number greater than 0 and up to 1")
        return fixed
    if key == "top_k":
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("top_k must be a positive integer")
        return fixed
    raise SystemExit(f"Unknown provider option: {key}")


def provider_sampling_status(pcfg: dict[str, Any]) -> list[str]:
    return [f"{key}={pcfg.get(key, 'default')}" for key in PROVIDER_SAMPLING_OPTIONS]


def provider_options_status(provider: str, pcfg: dict[str, Any]) -> str:
    timeout = pcfg.get("request_timeout_ms", "default")
    timeout_text = f"{timeout}ms" if timeout != "default" else "default"
    parts = [
        f"max_output_tokens={pcfg.get('max_output_tokens', 'default')}",
        f"timeout={timeout_text}",
    ]
    if pcfg.get("stream_idle_timeout_ms") is not None:
        parts.append(f"stream_idle_timeout={pcfg.get('stream_idle_timeout_ms')}ms")
    if provider in ("lm-studio", "nvidia-hosted", "self-hosted-nim", "ollama", "ollama-cloud"):
        parts.append(f"rate_limit_rpm={pcfg.get('rate_limit_rpm', 0)}")
        if bool(pcfg.get("rate_limit_status", False)):
            used, limit = router_rate_limit_usage(provider, pcfg)
            if limit is not None:
                suffix = f"{used}/{limit}" if limit > 0 else f"{used}/min(unmanaged)"
                parts.append(f"rpm_used={suffix}")
    if provider in ("ollama", "ollama-cloud"):
        parts.insert(0, f"num_ctx={ollama_num_ctx_status(pcfg)}")
        parts.append(f"ollama_options={ollama_options_status(pcfg)}")
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        parts.insert(0, f"context_window={pcfg.get('context_window', 'default')}")
        parts.insert(1, f"reserve={pcfg.get('context_reserve_tokens', 'default')}")
    if provider in ("vllm", "lm-studio", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        native_default = True
        parts.append(f"native={bool(pcfg.get('native_compat', native_default))}")
    if provider in OPENCODE_PROVIDER_NAMES:
        overrides = pcfg.get("model_endpoints")
        count = len(overrides) if isinstance(overrides, dict) else 0
        parts.append(f"ip_family={provider_ip_family(provider, pcfg)}")
        parts.append(f"endpoint_overrides={count}")
    if provider == "anthropic":
        parts.append(f"routed={'on' if anthropic_routed_enabled(provider, pcfg) else 'off'}")
    forced_query = str(pcfg.get("force_query_string") or "").strip()
    if forced_query:
        parts.append(f"force_query={forced_query}")
    if provider in PROVIDER_SAMPLING_OPTION_PROVIDERS:
        parts.extend(provider_sampling_status(pcfg))
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "ollama", "ollama-cloud", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        parts.append(f"stream={'on' if bool(pcfg.get('stream_enabled', True)) else 'off'}")
        if bool(pcfg.get("stream_word_chunking", False)):
            parts.append("word_chunk=on")
    return ", ".join(parts)


def llm_options_status(provider: str, pcfg: dict[str, Any]) -> str:
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        pieces = [
            f"ctx {ollama_num_ctx_status(pcfg)}",
            f"keep {pcfg.get('keep_alive', 'default')}",
            f"think {bool(pcfg.get('think', False))}",
            f"timeout {pcfg.get('request_timeout_ms', 'default')}ms",
        ]
        if pcfg.get("stream_idle_timeout_ms") is not None:
            pieces.append(f"stream_idle_timeout={pcfg.get('stream_idle_timeout_ms')}ms")
        for key in ("num_predict", "temperature", "top_p", "top_k"):
            if key in opts:
                pieces.append(f"{key}={opts[key]}")
        return "; ".join(pieces)
    if provider == "anthropic":
        return (
            f"max_output_tokens={pcfg.get('max_output_tokens', 'Claude Code default')}, "
            f"timeout={pcfg.get('request_timeout_ms', 'Claude Code default')}ms, "
            f"routed={'on' if anthropic_routed_enabled(provider, pcfg) else 'off'}"
        )
    if provider in PROVIDER_OPTION_PROVIDERS:
        return provider_options_status(provider, pcfg)
    return "provider defaults"


def model_option_family(provider: str, pcfg: dict[str, Any]) -> str:
    model = str(pcfg.get("current_model") or "").lower()
    if any(marker in model for marker in ("coder", "codegemma", "starcoder", "devstral")):
        return "coding"
    if any(marker in model for marker in ("reason", "thinking", "r1", "qwq")):
        return "reasoning"
    if any(marker in model for marker in ("1m", "v4-pro", "million")):
        return "million-context"
    if any(marker in model for marker in ("70b", "120b", "253b", "405b", "480b", "large", "ultra", "pro")):
        return "large"
    if provider in ("vllm", "lm-studio", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        ctx = provider_model_context_capacity(provider, pcfg) or positive_int(pcfg.get("context_window")) or 0
        if ctx >= 524288:
            return "million-context"
        if ctx >= 65536:
            return "long-context"
    if provider in ("ollama", "ollama-cloud"):
        ctx = positive_int(pcfg.get("num_ctx_max")) or positive_int(pcfg.get("num_ctx")) or 0
        if ctx >= 524288:
            return "million-context"
        if ctx >= 65536:
            return "long-context"
    return "general"


def recommended_preset_id(provider: str, pcfg: dict[str, Any]) -> str:
    family = model_option_family(provider, pcfg)
    if family == "reasoning":
        return "reasoning"
    if family == "coding":
        return "coding"
    if family == "million-context":
        return "million-context-1m"
    if family == "long-context":
        if (provider_model_context_capacity(provider, pcfg) or 0) >= 131072:
            return "long-context-128k"
        return "long-context-65k"
    if family == "large":
        return "balanced"
    return "balanced"


LLM_PRESETS: dict[str, tuple[str, str]] = {
    "balanced": ("Balanced Claude Code", "4K output, stable coding/chat defaults"),
    "coding": ("Coding deterministic", "lower randomness for edits, scripts, reviews"),
    "fast": ("Fast short tasks", "shorter output and timeout for quick jobs"),
    "long-context-65k": ("Long context 65K", "65K context target, 4K output reserve"),
    "long-context-128k": ("Long context 128K", "64K-128K context target, 4K-8K output reserve"),
    "million-context-1m": ("Ultra context 1M", "1M context target for high-capacity models"),
    "large-output": ("Large output/report", "larger 8K output for summaries/reports"),
    "reasoning": ("Reasoning model", "reasoning-friendly sampling"),
    "novelist": ("Novelist", "creative prose, voice, scenes, and narrative continuity"),
    "humanities-researcher": ("Humanities researcher", "interpretive research, close reading, and long evidence chains"),
    "mathematician": ("Mathematician", "careful derivations, proofs, and low-randomness reasoning"),
    "product-architect": ("Product architect", "requirements, system structure, tradeoffs, and implementation plans"),
    "teacher": ("Teacher / tutor", "clear explanations, examples, and step-by-step learning"),
}


LLM_PRESET_I18N: dict[str, dict[str, tuple[str, str]]] = {
    "ko": {
        "balanced": ("균형형 Claude Code", "4K 출력, 안정적인 코딩/채팅 기본값"),
        "coding": ("코딩 결정형", "편집, 스크립트, 코드 리뷰용 낮은 무작위성"),
        "fast": ("빠른 짧은 작업", "짧은 출력과 짧은 타임아웃"),
        "long-context-65k": ("긴 컨텍스트 65K", "65K 컨텍스트 목표, 4K 출력 여유"),
        "long-context-128k": ("긴 컨텍스트 128K", "64K-128K 컨텍스트 목표, 4K-8K 출력 여유"),
        "million-context-1m": ("초장문 컨텍스트 1M", "고용량 모델용 1M 컨텍스트 목표"),
        "large-output": ("긴 출력/리포트", "요약과 리포트용 8K 출력"),
        "reasoning": ("추론 모델", "추론 친화 샘플링"),
        "novelist": ("소설가", "문체, 서사, 장면 전개를 위한 창작 설정"),
        "humanities-researcher": ("인문연구자", "해석, 근거 정리, 긴 문헌 맥락"),
        "mathematician": ("수학자", "낮은 무작위성과 단계적 증명/유도"),
        "product-architect": ("제품 설계자", "요구사항, 구조, 트레이드오프, 구현 계획"),
        "teacher": ("교사/튜터", "쉬운 설명, 예제, 단계별 학습"),
    },
    "ja": {
        "balanced": ("バランス型 Claude Code", "4K 出力、安定したコーディング/チャット既定値"),
        "coding": ("コーディング決定型", "編集、スクリプト、コードレビュー向けの低いランダム性"),
        "fast": ("高速な短い作業", "短い出力と短いタイムアウト"),
        "long-context-65k": ("長いコンテキスト 65K", "65K コンテキスト目標、4K 出力予約"),
        "long-context-128k": ("長いコンテキスト 128K", "64K-128K コンテキスト目標、4K-8K 出力予約"),
        "million-context-1m": ("超長文コンテキスト 1M", "大容量モデル向けの 1M コンテキスト目標"),
        "large-output": ("長い出力/レポート", "要約とレポート向けの 8K 出力"),
        "reasoning": ("推論モデル", "推論向けサンプリング"),
        "novelist": ("小説家", "文体、物語、場面展開向けの創作設定"),
        "humanities-researcher": ("人文学研究者", "解釈、根拠整理、長い文献文脈"),
        "mathematician": ("数学者", "低いランダム性と段階的な証明/導出"),
        "product-architect": ("プロダクト設計者", "要件、構造、トレードオフ、実装計画"),
        "teacher": ("教師/チューター", "分かりやすい説明、例、段階的学習"),
    },
    "zh": {
        "balanced": ("均衡型 Claude Code", "4K 输出，稳定的编码/聊天默认值"),
        "coding": ("编码确定型", "用于编辑、脚本和代码审查的低随机性"),
        "fast": ("快速短任务", "较短输出和较短超时"),
        "long-context-65k": ("长上下文 65K", "65K 上下文目标，4K 输出预留"),
        "long-context-128k": ("长上下文 128K", "64K-128K 上下文目标，4K-8K 输出预留"),
        "million-context-1m": ("超长上下文 1M", "面向高容量模型的 1M 上下文目标"),
        "large-output": ("长输出/报告", "用于摘要和报告的 8K 输出"),
        "reasoning": ("推理模型", "适合推理的采样"),
        "novelist": ("小说家", "面向文风、叙事、场景推进的创作设置"),
        "humanities-researcher": ("人文学研究者", "解释性研究、证据整理和长文献上下文"),
        "mathematician": ("数学家", "低随机性、逐步证明和推导"),
        "product-architect": ("产品架构师", "需求、系统结构、取舍和实现计划"),
        "teacher": ("教师/导师", "清晰解释、示例和分步学习"),
    },
}


TIMEOUT_PRESETS: dict[str, tuple[int, str, str]] = {
    "timeout-fast": (120000, "Fast retry 2m", "short wait, quick retry on stalled providers"),
    "timeout-standard": (300000, "Standard 5m", "balanced wait for normal cloud coding"),
    "timeout-long": (600000, "Long stream 10m", "large edits or slow streamed responses"),
    "timeout-deep": (1200000, "Deep work 20m", "long reasoning, reports, and big context"),
    "timeout-marathon": (3600000, "Marathon 60m", "very long hosted/model-server jobs"),
}


TIMEOUT_PRESET_I18N: dict[str, dict[str, tuple[str, str]]] = {
    "ko": {
        "timeout-fast": ("빠른 재시도 2분", "멈춘 provider를 빨리 감지"),
        "timeout-standard": ("표준 5분", "일반 클라우드 코딩용 균형값"),
        "timeout-long": ("긴 스트림 10분", "큰 편집 또는 느린 스트리밍 응답"),
        "timeout-deep": ("깊은 작업 20분", "긴 추론, 리포트, 큰 컨텍스트"),
        "timeout-marathon": ("장시간 60분", "매우 긴 hosted/model-server 작업"),
    },
    "ja": {
        "timeout-fast": ("高速 retry 2分", "停止した provider を早く検出"),
        "timeout-standard": ("標準 5分", "通常のクラウド coding 向け"),
        "timeout-long": ("長い stream 10分", "大きな編集や遅い streamed 応答"),
        "timeout-deep": ("深い作業 20分", "長い推論、report、大きな context"),
        "timeout-marathon": ("長時間 60分", "非常に長い hosted/model-server 作業"),
    },
    "zh": {
        "timeout-fast": ("快速重试 2分钟", "快速发现卡住的 provider"),
        "timeout-standard": ("标准 5分钟", "普通云端编码的均衡值"),
        "timeout-long": ("长流式 10分钟", "大型编辑或较慢流式响应"),
        "timeout-deep": ("深度工作 20分钟", "长推理、报告和大上下文"),
        "timeout-marathon": ("超长 60分钟", "很长的 hosted/model-server 任务"),
    },
}


LLM_PRESET_TIMEOUT_MS: dict[str, int] = {
    "balanced": DEFAULT_REQUEST_TIMEOUT_MS,
    "coding": DEFAULT_REQUEST_TIMEOUT_MS,
    "fast": 120000,
    "long-context-65k": DEFAULT_REQUEST_TIMEOUT_MS,
    "long-context-128k": 600000,
    "million-context-1m": 600000,
    "large-output": 600000,
    "reasoning": 600000,
    "novelist": 600000,
    "humanities-researcher": 600000,
    "mathematician": 600000,
    "product-architect": 600000,
    "teacher": 300000,
}


def llm_preset_timeout_ms(preset_id: str) -> int:
    return LLM_PRESET_TIMEOUT_MS.get(preset_id, DEFAULT_REQUEST_TIMEOUT_MS)


def active_llm_preset_timeout_ms(pcfg: dict[str, Any]) -> int | None:
    preset_id = str(pcfg.get("llm_preset") or "").strip()
    if not preset_id:
        return None
    return positive_int(LLM_PRESET_TIMEOUT_MS.get(preset_id))


def timeout_profile_id_for_ms(ms: int | None) -> str | None:
    if not ms:
        return None
    for preset_id, (preset_ms, _, _) in TIMEOUT_PRESETS.items():
        if ms == preset_ms:
            return preset_id
    return None


def timeout_profile_text(profile_id: str, lang: str | None = None) -> tuple[str, str]:
    lang = lang or load_config().get("language", "en")
    if profile_id == "__custom__":
        return {
            "ko": ("사용자 지정", "직접 입력한 timeout 값"),
            "ja": ("カスタム", "直接入力した timeout 値"),
            "zh": ("自定义", "手动输入的 timeout 值"),
        }.get(lang, ("Custom", "manually entered timeout value"))
    fallback = TIMEOUT_PRESETS[profile_id]
    return TIMEOUT_PRESET_I18N.get(lang, {}).get(profile_id, (fallback[1], fallback[2]))


def timeout_profile_status(pcfg: dict[str, Any], lang: str | None = None) -> str:
    ms = positive_int(pcfg.get("request_timeout_ms")) or DEFAULT_REQUEST_TIMEOUT_MS
    profile_id = timeout_profile_id_for_ms(ms)
    if profile_id:
        label = timeout_profile_text(profile_id, lang)[0]
    else:
        label = timeout_profile_text("__custom__", lang)[0]
    idle = positive_int(pcfg.get("stream_idle_timeout_ms"))
    idle_text = f"; idle {idle}ms" if idle and idle != ms else ""
    return f"{label}; {ms}ms{idle_text}"


def timeout_profile_idle_ms(request_timeout_ms: int) -> int:
    return min(request_timeout_ms, 300000)


def timeout_profile_panel_rows(pcfg: dict[str, Any], lang: str | None = None) -> tuple[list[str], list[str]]:
    lang = lang or load_config().get("language", "en")
    current_ms = positive_int(pcfg.get("request_timeout_ms")) or DEFAULT_REQUEST_TIMEOUT_MS
    rows = [f"Current timeout: {current_ms} ms = {format_timeout_minutes(current_ms, lang)}"]
    values = ["__info__"]
    current_profile = timeout_profile_id_for_ms(current_ms)
    for profile_id, (ms, _, _) in TIMEOUT_PRESETS.items():
        label, description = timeout_profile_text(profile_id, lang)
        mark = "*" if profile_id == current_profile else " "
        rows.append(f"{mark} {pad_cells(label, 22)} {ms:>7} ms  {description}")
        values.append(profile_id)
    rows.append(ui_text("back", lang))
    values.append("back")
    return rows, values


def apply_timeout_profile_to_provider(pcfg: dict[str, Any], profile_id: str, lang: str | None = None) -> list[str]:
    if profile_id not in TIMEOUT_PRESETS:
        raise SystemExit(f"Unknown timeout preset: {profile_id}")
    ms, _, _ = TIMEOUT_PRESETS[profile_id]
    idle_ms = timeout_profile_idle_ms(ms)
    pcfg["request_timeout_ms"] = ms
    pcfg["stream_idle_timeout_ms"] = idle_ms
    label = timeout_profile_text(profile_id, lang)[0]
    return [f"Timeout preset: {label}", f"request_timeout_ms: {ms}", f"stream_idle_timeout_ms: {idle_ms}"]


def with_preset_timeout_tokens(tokens: list[str], preset_id: str) -> list[str]:
    filtered = [
        token
        for token in tokens
        if not token.startswith(("timeout=", "timeout_ms=", "request_timeout=", "request_timeout_ms=", "stream_idle_timeout=", "stream_idle_timeout_ms="))
    ]
    timeout_ms = llm_preset_timeout_ms(preset_id)
    idle_ms = timeout_profile_idle_ms(timeout_ms)
    filtered.append(f"timeout={timeout_ms}")
    filtered.append(f"stream_idle_timeout_ms={idle_ms}")
    return filtered


CONTEXT_HEAVY_PRESETS = {
    "long-context-65k",
    "long-context-128k",
    "million-context-1m",
    "large-output",
    "reasoning",
    "novelist",
    "humanities-researcher",
    "mathematician",
    "product-architect",
    "teacher",
}


def is_qwen36_plus_model_id(model_id: str) -> bool:
    compact = re.sub(r"[^a-z0-9]+", "", (model_id or "").lower())
    return "qwen36plus" in compact


def model_context_hint_from_model_id(model_id: str) -> int | None:
    model = (model_id or "").lower()
    if not model:
        return None
    if is_qwen36_plus_model_id(model_id):
        return 1048576
    catalog_limit, _, _ = ollama_catalog_context_for_model(model_id)
    if catalog_limit:
        return catalog_limit
    if any(marker in model for marker in ("deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4", "v4-pro", "v4-flash", "1m", "million")):
        return 1048576
    if any(marker in model for marker in ("kimi-k2.6", "kimi_k2.6", "kimi2.6", "kimi-k2")):
        return 262144
    if "qwen3.6" in model:
        return 262144
    if "glm-4.7" in model or "glm-5.1" in model:
        return 131072
    if "deepseek-r1" in model or "llama3.3" in model:
        return 131072
    preset = model_preset(model_id)
    return positive_int(preset.get("num_ctx_max"))


def provider_model_context_capacity(provider: str, pcfg: dict[str, Any]) -> int | None:
    model = str(pcfg.get("current_model") or "")
    if provider == "anthropic":
        return None
    if provider == "nvidia-hosted":
        return nvidia_hosted_context_default(model)
    if provider in ("vllm", "self-hosted-nim"):
        return (
            upstream_model_context_limit(provider, pcfg, timeout=1.0)
            or positive_int(pcfg.get("max_model_len"))
            or model_context_hint_from_model_id(model)
            or positive_int(pcfg.get("context_window"))
        )
    if provider in ("deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        return (
            positive_int(pcfg.get("max_model_len"))
            or model_context_hint_from_model_id(model)
            or positive_int(pcfg.get("context_window"))
        )
    hint = model_context_hint_from_model_id(model)
    if hint:
        return hint
    if provider in ("ollama", "ollama-cloud"):
        if ollama_context_model_matches(model, str(pcfg.get("model_context_model") or "")):
            cached = positive_int(pcfg.get("model_context_max"))
            if cached:
                return cached
        return positive_int(pcfg.get("num_ctx_max")) or positive_int(pcfg.get("num_ctx"))
    return positive_int(pcfg.get("context_window")) or positive_int(pcfg.get("max_model_len"))


def cap_context_settings_to_model_capacity(provider: str, pcfg: dict[str, Any]) -> list[str]:
    capacity = provider_model_context_capacity(provider, pcfg)
    if not capacity:
        return []
    messages: list[str] = []
    if provider in ("ollama", "ollama-cloud"):
        maximum = positive_int(pcfg.get("num_ctx_max"))
        if maximum and maximum > capacity:
            pcfg["num_ctx_max"] = capacity
            messages.append(f"Context max capped to selected model limit: {capacity:,} tokens.")
        minimum = positive_int(pcfg.get("num_ctx_min"))
        if minimum and minimum > capacity:
            pcfg["num_ctx_min"] = capacity
        fixed_ctx = positive_int(pcfg.get("num_ctx"))
        if fixed_ctx and fixed_ctx > capacity:
            pcfg["num_ctx"] = capacity
        return messages
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        context_window = positive_int(pcfg.get("context_window"))
        if context_window and context_window > capacity:
            pcfg["context_window"] = capacity
            messages.append(f"Context window capped to selected model limit: {capacity:,} tokens.")
    return messages


def cached_current_model_info(provider: str, pcfg: dict[str, Any]) -> dict[str, Any]:
    info = read_model_info_cache(provider, pcfg)
    if not info:
        return {}
    candidates = [
        normalize_model_id(provider, current_upstream_model_id(provider, pcfg)),
        normalize_model_id(provider, str(pcfg.get("current_model") or "")),
        strip_claude_context_suffix(normalize_model_id(provider, str(pcfg.get("current_model") or ""))),
    ]
    for model_id in candidates:
        if model_id and model_id in info:
            return info[model_id]
    current = normalize_model_id(provider, current_upstream_model_id(provider, pcfg)).casefold()
    for model_id, model_info in info.items():
        if normalize_model_id(provider, model_id).casefold() == current:
            return model_info
    return {}


def apply_current_model_specs_to_provider(provider: str, pcfg: dict[str, Any]) -> list[str]:
    info = cached_current_model_info(provider, pcfg)
    max_context = positive_int(info.get("max_model_len")) if info else None
    if not max_context:
        return []
    model = normalize_model_id(provider, current_upstream_model_id(provider, pcfg))
    messages: list[str] = []
    if provider in ("ollama", "ollama-cloud"):
        if not ollama_context_model_matches(model, str(pcfg.get("model_context_model") or "")) or positive_int(pcfg.get("model_context_max")) != max_context:
            pcfg["model_context_max"] = max_context
            pcfg["model_context_model"] = model
            messages.append(f"Model context size from provider specs: {format_context_tokens(max_context)} ({max_context:,} tokens).")
        return messages
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        if positive_int(pcfg.get("max_model_len")) != max_context:
            pcfg["max_model_len"] = max_context
            messages.append(f"Model context size from provider specs: {format_context_tokens(max_context)} ({max_context:,} tokens).")
    return messages


def refresh_current_model_specs_for_auto_llm(provider: str, pcfg: dict[str, Any]) -> list[str]:
    messages: list[str] = []
    try:
        models = upstream_model_ids(provider, pcfg, force_refresh=True)
        if models:
            messages.append(f"Model specs refreshed from provider: {len(models)} model(s).")
    except Exception as exc:
        messages.append(f"Model specs refresh failed: {type(exc).__name__}: {exc}")
    messages.extend(apply_current_model_specs_to_provider(provider, pcfg))
    return messages


def apply_lm_studio_loaded_context_guard(pcfg: dict[str, Any], load: bool = False) -> list[str]:
    if load:
        try:
            return ensure_lm_studio_model_loaded_for_context(pcfg, timeout=1.5)
        except Exception as exc:
            pcfg["native_compat"] = False
            return [
                "LM Studio could not automatically load the selected model with the recommended context.",
                f"LM Studio load error: {type(exc).__name__}: {exc}",
            ]

    info = None
    target = lm_studio_target_context(pcfg, info)
    loaded = positive_int((info or {}).get("loaded_context_len"))
    state = str((info or {}).get("state") or "")
    max_len = positive_int((info or {}).get("max_model_len"))
    messages: list[str] = []
    if max_len:
        messages.append(f"LM Studio model max context: {max_len:,} tokens.")
    if target:
        messages.append(f"LM Studio target context: {target:,} tokens.")
    if max_len and max_len < LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
        pcfg["native_compat"] = False
        pcfg["context_window"] = max_len
        messages.append(
            "LM Studio selected model cannot provide enough context for Claude Code "
            f"({max_len:,} < {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,})."
        )
        return messages
    pcfg["native_compat"] = True
    if loaded:
        messages.append(f"LM Studio currently loaded context: {loaded:,} tokens.")
        if target and loaded < target:
            messages.append("LM Studio will reload this model with the target context when you launch or test.")
    elif state and state != "loaded":
        messages.append("LM Studio will load this model with the target context when you launch or test.")
    elif target:
        messages.append("LM Studio will prepare this model with the target context when you launch or test.")
    return messages


def required_context_for_preset(preset_id: str, provider: str | None = None) -> int | None:
    provider = provider or ""
    if preset_id == "million-context-1m":
        return 1048576
    if preset_id == "humanities-researcher":
        return 524288 if provider in ("ollama", "ollama-cloud") else 262144
    if preset_id in ("novelist", "mathematician", "product-architect"):
        return 262144
    if preset_id == "teacher":
        return 131072
    if preset_id == "reasoning":
        return 262144 if provider == "nvidia-hosted" else 131072
    if preset_id == "large-output":
        if provider == "nvidia-hosted":
            return 262144
        if provider in ("ollama", "ollama-cloud"):
            return 131072
        return 65536
    if preset_id == "long-context-128k":
        return 131072
    if preset_id == "long-context-65k":
        return 131072 if provider == "nvidia-hosted" else 65536
    return None


def preset_available_for_model(provider: str, pcfg: dict[str, Any], preset_id: str) -> bool:
    required = required_context_for_preset(preset_id, provider)
    if not required:
        return True
    capacity = provider_model_context_capacity(provider, pcfg)
    if not capacity:
        return True
    return required <= capacity


def format_context_tokens(value: int | None) -> str:
    if not value:
        return "unknown"
    if value >= 1024 * 1024 and value % (1024 * 1024) == 0:
        return f"{value // (1024 * 1024)}M"
    if value >= 1024 and value % 1024 == 0:
        return f"{value // 1024}K"
    return f"{value:,}"


def format_parameter_count(value: Any) -> str:
    fixed = positive_int(value)
    if not fixed:
        return ""
    units = (("T", 1_000_000_000_000), ("B", 1_000_000_000), ("M", 1_000_000))
    for suffix, scale in units:
        if fixed >= scale:
            scaled = fixed / scale
            text = f"{scaled:.1f}".rstrip("0").rstrip(".")
            return f"{text}{suffix}"
    return str(fixed)


def context_setting_status(provider: str, pcfg: dict[str, Any]) -> str:
    capacity = provider_model_context_capacity(provider, pcfg)
    cap_text = format_context_tokens(capacity)
    if provider in ("ollama", "ollama-cloud"):
        return f"model max {cap_text}; {ollama_num_ctx_status(pcfg)}"
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        window = positive_int(pcfg.get("context_window"))
        reserve = positive_int(pcfg.get("context_reserve_tokens"))
        reserve_text = f"; reserve {format_context_tokens(reserve)}" if reserve else ""
        return f"model max {cap_text}; window {format_context_tokens(window)}{reserve_text}"
    if provider == "anthropic":
        return "managed by Claude Code"
    return f"model max {cap_text}"


def configured_context_window_for_timeout(provider: str, pcfg: dict[str, Any]) -> int | None:
    if provider in ("ollama", "ollama-cloud"):
        return (
            positive_int(pcfg.get("num_ctx_max"))
            or positive_int(pcfg.get("num_ctx"))
            or provider_model_context_capacity(provider, pcfg)
        )
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        return positive_int(pcfg.get("context_window")) or provider_model_context_capacity(provider, pcfg)
    if provider == "anthropic":
        return provider_model_context_capacity(provider, pcfg)
    return provider_model_context_capacity(provider, pcfg)


AUTO_TIMEOUT_MIN_MS = 120000
AUTO_TIMEOUT_MAX_MS = 600000
AUTO_TIMEOUT_ROUND_MS = 30000
HOSTED_TIMEOUT_PROVIDERS = {
    "anthropic",
    "ollama-cloud",
    "deepseek",
    "opencode",
    "opencode-go",
    "openrouter",
    "nvidia-hosted",
    "fireworks",
}
PROVIDER_TIMEOUT_WEIGHTS = {
    "ollama-cloud": 1.2,
}


def configured_output_tokens_for_timeout(provider: str, pcfg: dict[str, Any]) -> int | None:
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        return positive_int(opts.get("num_predict")) or positive_int(pcfg.get("max_output_tokens"))
    return positive_int(pcfg.get("max_output_tokens")) or positive_int(pcfg.get("num_predict"))


def clamp_auto_timeout_ms(ms: int | float | None) -> int:
    value = positive_int(ms) or DEFAULT_REQUEST_TIMEOUT_MS
    value = max(AUTO_TIMEOUT_MIN_MS, min(AUTO_TIMEOUT_MAX_MS, value))
    return int(math.ceil(value / AUTO_TIMEOUT_ROUND_MS) * AUTO_TIMEOUT_ROUND_MS)


def calculated_request_timeout_ms(
    provider: str,
    pcfg: dict[str, Any],
    timeout_candidates: list[int] | None = None,
) -> int:
    context_tokens = positive_int(configured_context_window_for_timeout(provider, pcfg))
    output_tokens = positive_int(configured_output_tokens_for_timeout(provider, pcfg))
    timeout_ms = AUTO_TIMEOUT_MIN_MS
    if context_tokens:
        # 64K -> +0, 128K -> +1m, 256K -> +2m, 512K -> +3m, 1M+ -> +4m.
        context_score = max(0.0, min(1.0, math.log2(max(context_tokens, 65536) / 65536) / 4.0))
        timeout_ms += int(240000 * context_score)
    if output_tokens:
        # 2K output is cheap; 8K+ output gets the full +2m allowance.
        output_score = max(0.0, min(1.0, (output_tokens - 2048) / 6144))
        timeout_ms += int(120000 * output_score)
    if provider in HOSTED_TIMEOUT_PROVIDERS:
        timeout_ms += 60000
    for candidate in timeout_candidates or []:
        fixed = positive_int(candidate)
        if fixed:
            timeout_ms = max(timeout_ms, fixed)
    timeout_ms *= PROVIDER_TIMEOUT_WEIGHTS.get(provider, 1.0)
    return clamp_auto_timeout_ms(timeout_ms)


def recommended_request_timeout_ms(provider: str, pcfg: dict[str, Any], use_context_fallback: bool = True) -> int:
    model = str(pcfg.get("current_model") or "")
    candidates: list[int] = []
    active_preset_timeout = active_llm_preset_timeout_ms(pcfg)
    if active_preset_timeout:
        candidates.append(active_preset_timeout)
    if provider in ("ollama", "ollama-cloud"):
        catalog_timeout = ollama_catalog_timeout_for_model(model)
        if catalog_timeout:
            candidates.append(catalog_timeout)
    model_timeout = positive_int(model_preset(model).get("recommended_timeout_ms"))
    if model_timeout:
        candidates.append(model_timeout)
    if candidates:
        return calculated_request_timeout_ms(provider, pcfg, candidates)
    if not use_context_fallback:
        return DEFAULT_REQUEST_TIMEOUT_MS
    context_timeout = recommended_timeout_ms_for_context(configured_context_window_for_timeout(provider, pcfg))
    return calculated_request_timeout_ms(provider, pcfg, [context_timeout])


def apply_recommended_timeout_for_model_context(
    provider: str,
    pcfg: dict[str, Any],
    use_context_fallback: bool = True,
) -> list[str]:
    timeout_ms = recommended_request_timeout_ms(provider, pcfg, use_context_fallback=use_context_fallback)
    idle_ms = timeout_profile_idle_ms(timeout_ms)
    changed = positive_int(pcfg.get("request_timeout_ms")) != timeout_ms or positive_int(pcfg.get("stream_idle_timeout_ms")) != idle_ms
    pcfg["request_timeout_ms"] = timeout_ms
    pcfg["stream_idle_timeout_ms"] = idle_ms
    context = configured_context_window_for_timeout(provider, pcfg)
    if not changed:
        return []
    return [
        f"Auto timeout: {timeout_ms}ms for context {format_context_tokens(context)}.",
        f"stream_idle_timeout_ms: {idle_ms}",
    ]


def context_mode_values_for_capacity(capacity: int | None) -> dict[str, tuple[int, int, int]]:
    cap = capacity or 131072

    def clamp(value: int) -> int:
        return max(8192, min(cap, value))

    compact = clamp(32768)
    balanced = clamp(65536 if cap <= 131072 else 131072)
    project = clamp(262144 if cap >= 262144 else cap)
    full = clamp(cap)
    return {
        "context-compact": (compact, min(2048, max(1024, compact // 16)), 4096),
        "context-balanced": (balanced, min(4096, max(2048, balanced // 16)), 4096),
        "context-project": (project, min(8192, max(4096, project // 16)), 8192),
        "context-full": (full, min(16384, max(4096, full // 16)), 8192),
    }


def context_setup_text(key: str, lang: str | None = None) -> tuple[str, str]:
    lang = lang or load_config().get("language", "en")
    entries = {
        "en": {
            "context-compact": ("Compact / fast", "small context, faster and cheaper"),
            "context-balanced": ("Balanced", "good default for normal coding sessions"),
            "context-project": ("Large project", "more files/history, slower but safer for big work"),
            "context-full": ("Full model window", "use the selected model's maximum context"),
        },
        "ko": {
            "context-compact": ("컴팩트/빠름", "작은 컨텍스트, 빠르고 가벼움"),
            "context-balanced": ("균형형", "일반 코딩 세션의 권장 기본값"),
            "context-project": ("대형 프로젝트", "파일/히스토리를 더 많이 사용, 큰 작업에 안정적"),
            "context-full": ("모델 최대 컨텍스트", "선택한 모델의 최대 컨텍스트 사용"),
        },
        "ja": {
            "context-compact": ("コンパクト/高速", "小さなコンテキストで高速かつ軽量"),
            "context-balanced": ("バランス", "通常のコーディングセッション向けの既定値"),
            "context-project": ("大規模プロジェクト", "より多くのファイル/履歴を使う大型作業向け"),
            "context-full": ("モデル最大コンテキスト", "選択モデルの最大コンテキストを使用"),
        },
        "zh": {
            "context-compact": ("紧凑/快速", "较小上下文，更快更轻"),
            "context-balanced": ("均衡", "普通编码会话的推荐默认值"),
            "context-project": ("大型项目", "使用更多文件/历史，适合大任务"),
            "context-full": ("模型最大上下文", "使用所选模型的最大上下文"),
        },
    }
    return entries.get(lang, entries["en"]).get(key, entries["en"][key])


def context_setup_panel_rows(provider: str, pcfg: dict[str, Any], lang: str | None = None) -> tuple[list[str], list[str]]:
    lang = lang or load_config().get("language", "en")
    capacity = provider_model_context_capacity(provider, pcfg)
    rows = [f"Model context capacity: {format_context_tokens(capacity)}"]
    values = ["__info__"]
    if provider == "anthropic":
        rows.append("Claude Code manages Anthropic context automatically.")
        values.append("__info__")
        rows.append(ui_text("back", lang))
        values.append("back")
        return rows, values
    current_window = positive_int(pcfg.get("num_ctx_max" if provider in ("ollama", "ollama-cloud") else "context_window"))
    choices = context_mode_values_for_capacity(capacity)
    ordered_keys = ["context-compact", "context-balanced", "context-project", "context-full"]
    visible_keys: list[str] = []
    seen_windows: set[int] = set()
    for key in reversed(ordered_keys):
        window = choices[key][0]
        if window in seen_windows:
            continue
        seen_windows.add(window)
        visible_keys.append(key)
    for key in reversed(visible_keys):
        window, reserve, output = choices[key]
        label, description = context_setup_text(key, lang)
        mark = "*" if current_window == window else " "
        rows.append(
            f"{mark} {pad_cells(label, 22)} "
            f"{format_context_tokens(window):>6}  out {format_context_tokens(output):>5}  {description}"
        )
        values.append(key)
    rows.append(ui_text("back", lang))
    values.append("back")
    return rows, values


def apply_context_setup_to_provider(provider: str, pcfg: dict[str, Any], mode: str, lang: str | None = None) -> list[str]:
    choices = context_mode_values_for_capacity(provider_model_context_capacity(provider, pcfg))
    if mode not in choices:
        raise SystemExit(f"Unknown context mode: {mode}")
    window, reserve, output = choices[mode]
    label = context_setup_text(mode, lang)[0]
    if provider in ("ollama", "ollama-cloud"):
        pcfg["num_ctx"] = "auto"
        pcfg["num_ctx_max"] = window
        pcfg["num_ctx_min"] = min(window, 32768 if window <= 65536 else 65536)
        pcfg.setdefault("ollama_options", {})["num_predict"] = output
    elif provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
        pcfg["context_window"] = window
        pcfg["context_reserve_tokens"] = reserve
        pcfg["max_output_tokens"] = output
    else:
        return ["Context setup is managed by Claude Code for this provider."]
    messages = cap_context_settings_to_model_capacity(provider, pcfg)
    messages.extend(apply_recommended_timeout_for_model_context(provider, pcfg))
    return [
        f"{ui_text('context_setup', lang)}: {label}",
        f"Applied context: {context_setting_status(provider, pcfg)}",
        *messages,
    ]


def apply_context_setup_config(provider: str, mode: str) -> list[str]:
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    lines = apply_context_setup_to_provider(provider, pcfg, mode, cfg.get("language", "en"))
    save_config(cfg)
    clear_model_cache()
    return lines


MODEL_FAMILY_I18N: dict[str, dict[str, str]] = {
    "ko": {
        "coding": "코딩",
        "reasoning": "추론",
        "million-context": "1M 컨텍스트",
        "large": "대형 모델",
        "long-context": "긴 컨텍스트",
        "general": "일반",
    },
    "ja": {
        "coding": "コーディング",
        "reasoning": "推論",
        "million-context": "1M コンテキスト",
        "large": "大型モデル",
        "long-context": "長いコンテキスト",
        "general": "汎用",
    },
    "zh": {
        "coding": "编码",
        "reasoning": "推理",
        "million-context": "1M 上下文",
        "large": "大型模型",
        "long-context": "长上下文",
        "general": "通用",
    },
}


def applied_preset_id(provider: str, pcfg: dict[str, Any]) -> str:
    preset_id = str(pcfg.get("llm_preset") or "").strip()
    if preset_id in LLM_PRESETS:
        return preset_id
    inferred = infer_preset_id_from_options(provider, pcfg)
    if inferred and preset_available_for_model(provider, pcfg, inferred):
        return inferred
    recommended = recommended_preset_id(provider, pcfg)
    if preset_available_for_model(provider, pcfg, recommended):
        return recommended
    return "balanced"


def infer_preset_id_from_options(provider: str, pcfg: dict[str, Any]) -> str | None:
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        num_predict = positive_int(opts.get("num_predict")) or 0
        num_ctx = positive_int(pcfg.get("num_ctx")) or 0
        num_ctx_min = positive_int(pcfg.get("num_ctx_min")) or 0
        num_ctx_max = positive_int(pcfg.get("num_ctx_max")) or 0
        if bool(pcfg.get("think", False)):
            return "reasoning"
        if num_ctx_max >= 524288:
            return "million-context-1m"
        if num_ctx_max >= 131072 and num_predict >= 8192:
            return "long-context-128k"
        if num_predict >= 8192:
            return "large-output"
        if num_ctx_min >= 65536 or num_ctx_max >= 131072:
            return "long-context-65k"
        if num_ctx and num_ctx <= 32768 and num_predict and num_predict <= 2048:
            return "fast"
        return None
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks", "anthropic"):
        max_output = positive_int(pcfg.get("max_output_tokens")) or 0
        context_window = positive_int(pcfg.get("context_window")) or 0
        if bool(pcfg.get("think", False)):
            return "reasoning"
        if context_window >= 524288:
            return "million-context-1m"
        if context_window >= 131072 and max_output >= 8192:
            return "long-context-128k"
        if max_output >= 8192:
            return "large-output"
        if context_window >= 65536:
            return "long-context-65k"
        if max_output and max_output <= 2048:
            return "fast"
    return None


def llm_preset_text(preset_id: str, lang: str | None = None) -> tuple[str, str]:
    lang = lang or load_config().get("language", "en")
    return LLM_PRESET_I18N.get(lang, {}).get(preset_id, LLM_PRESETS[preset_id])


def model_family_text(family: str, lang: str | None = None) -> str:
    lang = lang or load_config().get("language", "en")
    return MODEL_FAMILY_I18N.get(lang, {}).get(family, family)


def llm_preset_panel_rows(provider: str, pcfg: dict[str, Any], lang: str | None = None) -> tuple[list[str], list[str]]:
    lang = lang or load_config().get("language", "en")
    recommended = recommended_preset_id(provider, pcfg)
    applied = applied_preset_id(provider, pcfg)
    family = model_option_family(provider, pcfg)
    recommended_label, _ = llm_preset_text(recommended, lang)
    rows = [
        f"{ui_text('model_family', lang)}: {model_family_text(family, lang)}; "
        f"{ui_text('recommended_preset_is', lang)} {recommended_label}"
    ]
    values = ["__info__"]
    for preset_id in LLM_PRESETS:
        label, description = llm_preset_text(preset_id, lang)
        mark = "*" if preset_id == applied else " "
        suffix = ""
        required = required_context_for_preset(preset_id, provider)
        capacity = provider_model_context_capacity(provider, pcfg) if required else None
        if required and capacity and required > capacity:
            suffix = f" (requires {format_context_tokens(required)}; server {format_context_tokens(capacity)})"
        rows.append(f"{mark} {pad_cells(label, 24)} {description}{suffix}")
        values.append(preset_id)
    rows.append(ui_text("back", lang))
    values.append("back")
    return rows, values


def apply_llm_preset_to_provider(
    provider: str,
    pcfg: dict[str, Any],
    preset_id: str,
    lang: str | None = None,
    sync_ollama_context: bool = True,
    load_lm_studio: bool = False,
) -> list[str]:
    if preset_id not in LLM_PRESETS:
        raise SystemExit(f"Unknown preset: {preset_id}")
    lang = lang or load_config().get("language", "en")
    label = llm_preset_text(preset_id, lang)[0]
    pcfg["llm_preset"] = preset_id
    context_msgs: list[str] = []
    if provider in ("ollama", "ollama-cloud"):
        tokens_by_preset = {
            "balanced": [
                "num_ctx=auto",
                "num_ctx_min=32768",
                "num_ctx_max=65536",
                "num_predict=4096",
                "temperature=0.3",
                "top_p=0.9",
                "top_k=40",
                "think=false",
                "keep_alive=5m",
                "timeout=300000",
            ],
            "coding": [
                "num_ctx=auto",
                "num_ctx_min=32768",
                "num_ctx_max=65536",
                "num_predict=4096",
                "temperature=0.2",
                "top_p=0.8",
                "top_k=40",
                "think=false",
                "keep_alive=5m",
                "timeout=300000",
            ],
            "fast": [
                "num_ctx=32768",
                "num_predict=2048",
                "temperature=0.2",
                "top_p=0.8",
                "top_k=40",
                "think=false",
                "keep_alive=5m",
                "timeout=300000",
            ],
            "long-context-65k": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=131072",
                "num_predict=4096",
                "temperature=0.3",
                "top_p=0.9",
                "top_k=40",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "long-context-128k": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=131072",
                "num_predict=8192",
                "temperature=0.3",
                "top_p=0.9",
                "top_k=40",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "million-context-1m": [
                "num_ctx=auto",
                "num_ctx_min=262144",
                "num_ctx_max=1048576",
                "num_predict=8192",
                "temperature=0.3",
                "top_p=0.9",
                "top_k=40",
                "think=false",
                "keep_alive=15m",
                "timeout=300000",
            ],
            "large-output": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=131072",
                "num_predict=8192",
                "temperature=0.3",
                "top_p=0.9",
                "top_k=40",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "reasoning": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=131072",
                "num_predict=4096",
                "temperature=0.6",
                "top_p=0.95",
                "top_k=40",
                "think=true",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "novelist": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=262144",
                "num_predict=8192",
                "temperature=0.85",
                "top_p=0.95",
                "top_k=80",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "humanities-researcher": [
                "num_ctx=auto",
                "num_ctx_min=131072",
                "num_ctx_max=524288",
                "num_predict=8192",
                "temperature=0.45",
                "top_p=0.9",
                "top_k=50",
                "think=false",
                "keep_alive=15m",
                "timeout=300000",
            ],
            "mathematician": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=262144",
                "num_predict=8192",
                "temperature=0.15",
                "top_p=0.85",
                "top_k=40",
                "think=true",
                "keep_alive=15m",
                "timeout=300000",
            ],
            "product-architect": [
                "num_ctx=auto",
                "num_ctx_min=65536",
                "num_ctx_max=262144",
                "num_predict=8192",
                "temperature=0.25",
                "top_p=0.85",
                "top_k=40",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
            "teacher": [
                "num_ctx=auto",
                "num_ctx_min=32768",
                "num_ctx_max=131072",
                "num_predict=6144",
                "temperature=0.55",
                "top_p=0.9",
                "top_k=60",
                "think=false",
                "keep_alive=10m",
                "timeout=300000",
            ],
        }
        for token in with_preset_timeout_tokens(tokens_by_preset[preset_id], preset_id):
            apply_ollama_option(pcfg, token)
        model_id = str(pcfg.get("current_model") or "").strip()
        if model_id and sync_ollama_context:
            context_msgs = sync_ollama_library_context_limit(provider, pcfg, model_id)
        context_msgs.extend(apply_ollama_runtime_output_guard(provider, pcfg))
    elif provider == "anthropic":
        # Anthropic presets intentionally do NOT set max_output_tokens. Forcing it
        # would pin CLAUDE_CODE_MAX_OUTPUT_TOKENS and override Claude Code's native
        # per-model default (e.g. Fable 5 / Opus = 64000, Sonnet = 32000). Claude
        # Code chooses that per-model cap itself; claude-any must not degrade it.
        # Only an explicit user value set via the options screen should emit the
        # env var. Clear any preset-origin value so a stale forced cap cannot linger
        # (older builds wrote 2048/4096/6144/8192 here).
        pcfg.pop("max_output_tokens", None)
        tokens_by_preset = {
            "balanced": ["timeout=300000"],
            "coding": ["timeout=300000"],
            "fast": ["timeout=300000"],
            "long-context-65k": ["timeout=300000"],
            "long-context-128k": ["timeout=300000"],
            "million-context-1m": ["timeout=300000"],
            "large-output": ["timeout=300000"],
            "reasoning": ["timeout=300000"],
            "novelist": ["timeout=300000"],
            "humanities-researcher": ["timeout=300000"],
            "mathematician": ["timeout=300000"],
            "product-architect": ["timeout=300000"],
            "teacher": ["timeout=300000"],
        }
        for token in with_preset_timeout_tokens(tokens_by_preset[preset_id], preset_id):
            apply_provider_option(provider, pcfg, token)
    else:
        native_default = "false" if provider == "nvidia-hosted" else "true"
        if provider == "lm-studio":
            server_limit = provider_model_context_capacity(provider, pcfg)
        else:
            server_limit = upstream_model_context_limit(provider, pcfg) if provider in ("vllm", "self-hosted-nim") else None
        if provider == "nvidia-hosted":
            tokens_by_preset = {
                "balanced": [
                    "context_window=65536",
                    "reserve=4096",
                    "max_output_tokens=4096",
                    "timeout=300000",
                    "temperature=0.3",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "coding": [
                    "context_window=65536",
                    "reserve=4096",
                    "max_output_tokens=4096",
                    "timeout=300000",
                    "temperature=0.2",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "fast": [
                    "context_window=65536",
                    "reserve=2048",
                    "max_output_tokens=2048",
                    "timeout=300000",
                    "temperature=0.2",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "long-context-65k": [
                    "context_window=131072",
                    "reserve=8192",
                    "max_output_tokens=4096",
                    "timeout=300000",
                    "temperature=0.3",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "long-context-128k": [
                    "context_window=131072",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.3",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "million-context-1m": [
                    "context_window=1048576",
                    "reserve=16384",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.3",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "large-output": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.3",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "reasoning": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=4096",
                    "timeout=300000",
                    "temperature=0.6",
                    "unset:top_p",
                    "unset:top_k",
                ],
                "novelist": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.85",
                    "top_p=0.95",
                    "top_k=80",
                ],
                "humanities-researcher": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.45",
                    "top_p=0.9",
                    "top_k=50",
                ],
                "mathematician": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.15",
                    "top_p=0.85",
                    "top_k=40",
                ],
                "product-architect": [
                    "context_window=262144",
                    "reserve=8192",
                    "max_output_tokens=8192",
                    "timeout=300000",
                    "temperature=0.25",
                    "top_p=0.85",
                    "top_k=40",
                ],
                "teacher": [
                    "context_window=131072",
                    "reserve=4096",
                    "max_output_tokens=6144",
                    "timeout=300000",
                    "temperature=0.55",
                    "top_p=0.9",
                    "top_k=60",
                ],
            }
        else:
            tokens_by_preset = {
            "balanced": [
                "context_window=32768",
                "reserve=2048",
                "max_output_tokens=4096",
                "timeout=300000",
                "temperature=0.3",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "coding": [
                "context_window=32768",
                "reserve=2048",
                "max_output_tokens=4096",
                "timeout=300000",
                "temperature=0.2",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "fast": [
                "context_window=32768",
                "reserve=1024",
                "max_output_tokens=2048",
                "timeout=300000",
                "temperature=0.2",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "long-context-65k": [
                "context_window=65536",
                "reserve=4096",
                "max_output_tokens=4096",
                "timeout=300000",
                "temperature=0.3",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "long-context-128k": [
                "context_window=131072",
                "reserve=8192",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.3",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "million-context-1m": [
                "context_window=1048576",
                "reserve=16384",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.3",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "large-output": [
                "context_window=65536",
                "reserve=4096",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.3",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "reasoning": [
                "context_window=65536",
                "reserve=4096",
                "max_output_tokens=4096",
                "timeout=300000",
                "temperature=0.6",
                "unset:top_p",
                "unset:top_k",
                f"native={native_default}",
            ],
            "novelist": [
                "context_window=262144",
                "reserve=8192",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.85",
                "top_p=0.95",
                "top_k=80",
                f"native={native_default}",
            ],
            "humanities-researcher": [
                "context_window=262144",
                "reserve=8192",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.45",
                "top_p=0.9",
                "top_k=50",
                f"native={native_default}",
            ],
            "mathematician": [
                "context_window=262144",
                "reserve=8192",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.15",
                "top_p=0.85",
                "top_k=40",
                f"native={native_default}",
            ],
            "product-architect": [
                "context_window=262144",
                "reserve=8192",
                "max_output_tokens=8192",
                "timeout=300000",
                "temperature=0.25",
                "top_p=0.85",
                "top_k=40",
                f"native={native_default}",
            ],
            "teacher": [
                "context_window=131072",
                "reserve=4096",
                "max_output_tokens=6144",
                "timeout=300000",
                "temperature=0.55",
                "top_p=0.9",
                "top_k=60",
                f"native={native_default}",
            ],
            }
        for token in with_preset_timeout_tokens(tokens_by_preset[preset_id], preset_id):
            if provider == "nvidia-hosted" and token.startswith("native="):
                continue
            apply_provider_option(provider, pcfg, token)
        if server_limit:
            requested_context = positive_int(pcfg.get("context_window"))
            if requested_context and requested_context > server_limit:
                pcfg["context_window"] = server_limit
                if server_limit <= 32768:
                    pcfg["max_output_tokens"] = min(positive_int(pcfg.get("max_output_tokens")) or 2048, 2048)
                else:
                    pcfg["max_output_tokens"] = min(positive_int(pcfg.get("max_output_tokens")) or 4096, max(1024, server_limit // 8))
    context_msgs.extend(cap_context_settings_to_model_capacity(provider, pcfg))
    if provider == "lm-studio":
        context_msgs.extend(apply_lm_studio_loaded_context_guard(pcfg, load=load_lm_studio))
    context_msgs.extend(apply_recommended_timeout_for_model_context(provider, pcfg))
    family = model_option_family(provider, pcfg)
    lines = [
        f"{ui_text('apply_preset', lang)}: {label}",
        f"Provider: {provider}; {ui_text('model_family', lang)}: {model_family_text(family, lang)}",
    ]
    lines.extend(context_msgs)
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim"):
        server_limit = provider_model_context_capacity(provider, pcfg) if provider == "lm-studio" else upstream_model_context_limit(provider, pcfg)
        required_context = required_context_for_preset(preset_id, provider) or 65536
        if server_limit:
            label = "Model max context" if provider == "lm-studio" else "Server max_model_len"
            lines.append(f"{label}: {server_limit}")
            if preset_id in CONTEXT_HEAVY_PRESETS and server_limit < required_context:
                lines.append(f"This preset requires restarting the server with --max-model-len {required_context} or higher.")
                lines.append("Client settings were capped to the server-reported context length.")
        elif preset_id in CONTEXT_HEAVY_PRESETS and provider != "lm-studio":
            lines.append("Could not verify server max_model_len; vLLM/NIM must be started with a matching context limit.")
    if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim"):
        lines.append(
            "Applied options: "
            f"context_window={pcfg.get('context_window', 'default')}, "
            f"reserve={pcfg.get('context_reserve_tokens', 'default')}, "
            f"max_output_tokens={pcfg.get('max_output_tokens', 'default')}, "
            f"timeout={pcfg.get('request_timeout_ms', 'default')}ms"
        )
    elif provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        lines.append(
            "Applied options: "
            f"num_ctx={ollama_num_ctx_status(pcfg)}, "
            f"num_predict={opts.get('num_predict', 'default')}, "
            f"timeout={pcfg.get('request_timeout_ms', 'default')}ms"
        )
    elif provider == "anthropic":
        lines.append(
            "Applied options: "
            f"max_output_tokens={pcfg.get('max_output_tokens', 'default')}, "
            f"timeout={pcfg.get('request_timeout_ms', 'default')}ms"
        )
    return lines


def auto_apply_recommended_llm_preset_for_model(provider: str, pcfg: dict[str, Any], lang: str | None = None) -> list[str]:
    preset_id = recommended_preset_id(provider, pcfg)
    if not preset_available_for_model(provider, pcfg, preset_id):
        return []
    label = llm_preset_text(preset_id, lang)[0]
    lines = [f"Auto-applied recommended LLM preset for selected model: {label}."]
    lines.extend(apply_llm_preset_to_provider(provider, pcfg, preset_id, lang, sync_ollama_context=False))
    return lines


def apply_auto_llm_options_config(model_id: str | None = None) -> list[str]:
    lines: list[str] = []
    if model_id and model_id.strip():
        lines.extend(set_model_config(model_id.strip()))
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    model = str(pcfg.get("current_model") or "").strip()
    lines.extend(refresh_current_model_specs_for_auto_llm(provider, pcfg))
    if model:
        context_msgs = sync_ollama_library_context_limit(provider, pcfg, model)
        context_msgs.extend(cap_context_settings_to_model_capacity(provider, pcfg))
    else:
        context_msgs = []
    preset_id = recommended_preset_id(provider, pcfg)
    if not preset_available_for_model(provider, pcfg, preset_id):
        preset_id = applied_preset_id(provider, pcfg)
    label = llm_preset_text(preset_id, cfg.get("language", "en"))[0]
    lines.append(f"Auto LLM options applied for {provider}{f' model {model}' if model else ''}: {label}.")
    lines.extend(context_msgs)
    lines.extend(apply_llm_preset_to_provider(provider, pcfg, preset_id, cfg.get("language", "en")))
    save_config(cfg)
    invalidate_config_cache()
    return lines


def apply_llm_preset_config(provider: str, preset_id: str) -> list[str]:
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    lines = apply_llm_preset_to_provider(provider, pcfg, preset_id, cfg.get("language", "en"))
    save_config(cfg)
    clear_model_cache()
    return lines


LLM_OPTION_DESCRIPTIONS: dict[str, dict[str, str]] = {
    "preset": {
        "en": "Apply a bundled LLM preset (output tokens, sampling, timeout) tuned for this provider/model family.",
        "ko": "현재 provider/모델 계열에 맞춘 LLM 프리셋(출력 토큰, 샘플링, 타임아웃)을 한 번에 적용합니다.",
        "ja": "現在のprovider/モデル系列向けに調整されたLLMプリセット(出力トークン、サンプリング、タイムアウト)を一括適用します。",
        "zh": "应用为当前 provider/模型系列调优的 LLM 预设（输出 token、采样、超时）。",
    },
    "context_setup": {
        "en": "Begin here: choose how much of the selected model's context window claude-any may use. Larger windows read more files/history but cost more time and provider quota.",
        "ko": "처음엔 여기부터 설정하세요. 선택한 모델의 컨텍스트 창을 claude-any가 얼마나 사용할지 고릅니다. 클수록 파일/히스토리를 더 많이 읽지만 느리고 provider 한도를 더 씁니다.",
        "ja": "まずここから設定します。選択モデルのコンテキスト窓をclaude-anyがどれだけ使うかを選びます。大きいほど多くのファイル/履歴を読めますが、遅くなりprovider枠を使います。",
        "zh": "先从这里设置：选择 claude-any 可使用所选模型多少上下文窗口。越大可读更多文件/历史，但更慢并消耗 provider 配额。",
    },
    "num_ctx": {
        "en": "Ollama context window (num_ctx). Use 'auto' to size per-request between min/max, or a fixed integer like 65536.",
        "ko": "Ollama 컨텍스트 창(num_ctx). auto 면 요청 크기에 따라 min/max 사이에서 자동 선택, 또는 65536 같은 고정 정수.",
        "ja": "Ollamaのコンテキスト窓(num_ctx)。autoでmin/max間を要求毎に自動選択、または65536のような固定整数を指定。",
        "zh": "Ollama 上下文窗口（num_ctx）。auto 在 min/max 间按请求自动选择，或填固定整数如 65536。",
    },
    "num_ctx_min": {
        "en": "Lower bound when num_ctx is auto. Small requests will not go below this value.",
        "ko": "num_ctx=auto일 때 사용할 최소 컨텍스트. 작은 요청도 이 값보다 작게 내려가지 않습니다.",
        "ja": "num_ctx=auto時の最小コンテキスト。小さな要求でもこの値未満にはなりません。",
        "zh": "num_ctx=auto 时的下限。小请求也不会低于此值。",
    },
    "num_ctx_max": {
        "en": "Upper bound when num_ctx is auto. Keep at or below the real server context limit.",
        "ko": "num_ctx=auto일 때 사용할 최대 컨텍스트. 실제 서버 한계 이하로 두세요.",
        "ja": "num_ctx=auto時の最大コンテキスト。実サーバー上限以下にしてください。",
        "zh": "num_ctx=auto 时的上限。应不高于真实服务器上下文上限。",
    },
    "num_predict": {
        "en": "Ollama max output tokens (num_predict). Input + reserved output must fit inside num_ctx.",
        "ko": "Ollama 최대 출력 토큰(num_predict). 입력 + 예약 출력이 num_ctx 안에 들어가야 합니다.",
        "ja": "Ollamaの最大出力トークン(num_predict)。入力と予約出力はnum_ctxの中に収まる必要があります。",
        "zh": "Ollama 最大输出 token（num_predict）。输入加预留输出必须放进 num_ctx。",
    },
    "max_output_tokens": {
        "en": "Max output tokens passed to Claude Code (CLAUDE_CODE_MAX_OUTPUT_TOKENS) and used as the router cap.",
        "ko": "Claude Code에 전달되는 최대 출력 토큰(CLAUDE_CODE_MAX_OUTPUT_TOKENS)이자 라우터 출력 상한.",
        "ja": "Claude Codeへ渡す最大出力トークン(CLAUDE_CODE_MAX_OUTPUT_TOKENS)であり、ルーター出力上限としても使われます。",
        "zh": "传给 Claude Code 的最大输出 token（CLAUDE_CODE_MAX_OUTPUT_TOKENS），同时作为路由器输出上限。",
    },
    "context_window": {
        "en": "vLLM/NIM context window used by claude-any caps. Native mode cannot raise the real server limit.",
        "ko": "claude-any 라우터가 사용하는 vLLM/NIM 컨텍스트 값. native 모드에서는 실제 서버 한계를 늘릴 수 없습니다.",
        "ja": "claude-anyルーターが使うvLLM/NIMコンテキスト値。nativeモードでは実サーバー上限は超えられません。",
        "zh": "claude-any 路由器使用的 vLLM/NIM 上下文值。native 模式无法提高真实服务器上限。",
    },
    "context_reserve_tokens": {
        "en": "Tokens reserved for the input side when claude-any caps max_tokens. Ignored by direct native requests.",
        "ko": "claude-any가 max_tokens를 줄일 때 입력 쪽 여유로 남기는 토큰. direct native 요청에는 적용되지 않습니다.",
        "ja": "claude-anyがmax_tokensを制限する時に入力側へ残す余裕。direct native要求では無視されます。",
        "zh": "claude-any 限制 max_tokens 时为输入侧预留的 token。direct native 请求会忽略。",
    },
    "request_timeout_ms": {
        "en": "Upstream wait timeout in milliseconds.",
        "ko": "업스트림 응답 대기 시간(ms).",
        "ja": "上流応答待ちタイムアウト(ms)。",
        "zh": "上游响应等待超时（毫秒）。",
    },
    "timeout_profile": {
        "en": "Choose a timeout preset. It sets both upstream wait timeout and stream idle timeout.",
        "ko": "타임아웃 프리셋을 선택합니다. 업스트림 대기 시간과 스트림 idle timeout을 함께 설정합니다.",
        "ja": "timeout preset を選びます。上流待機 timeout と stream idle timeout を同時に設定します。",
        "zh": "选择 timeout 预设，同时设置上游等待超时和 stream idle timeout。",
    },
    "stream_idle_timeout_ms": {
        "en": "Maximum silence allowed while a stream is open. If no bytes arrive for this long, claude-any retries or reports a timeout.",
        "ko": "스트림 연결이 열린 뒤 아무 byte도 오지 않아도 기다리는 최대 시간입니다. 이 시간을 넘으면 재시도하거나 timeout으로 처리합니다.",
        "ja": "stream 接続中に byte が来ないまま待つ最大時間です。超えると retry または timeout として扱います。",
        "zh": "流式连接打开后允许无字节到达的最长时间；超过后重试或作为 timeout 处理。",
    },
    "rate_limit_rpm": {
        "en": "Router-side upstream request limit per minute. Default is off; set a positive RPM to enable waiting.",
        "ko": "라우터가 업스트림 요청 수를 분당 제한합니다. 기본값은 off입니다. 양수 RPM을 설정하면 대기 제한을 켭니다.",
        "ja": "ルーター側の上流リクエスト数/分の制限。既定は off。正の RPM を設定すると待機制限を有効化します。",
        "zh": "路由器侧上游每分钟请求限制。默认关闭；设置正数 RPM 后启用等待限制。",
    },
    "rate_limit_enabled": {
        "en": "Toggle claude-any's router-side RPM limiter. Off means rate_limit_rpm=0 and no claude-any wait is inserted.",
        "ko": "claude-any 라우터 내부 RPM 제한을 켜거나 끕니다. off면 rate_limit_rpm=0이며 claude-any가 대기 시간을 넣지 않습니다.",
        "ja": "claude-any ルーター側 RPM 制限を切り替えます。off は rate_limit_rpm=0 で、claude-any は待機を挿入しません。",
        "zh": "切换 claude-any 路由器侧 RPM 限制。off 表示 rate_limit_rpm=0，claude-any 不插入等待。",
    },
    "rate_limit_status": {
        "en": "Show optional colored RPM usage status in Claude responses. Default is off.",
        "ko": "Claude 응답에 RPM 사용량 상태를 색상 텍스트로 표시합니다. 기본값은 off입니다.",
        "ja": "Claude応答にRPM使用量状態を色付きテキストで表示します。既定は off。",
        "zh": "在 Claude 响应中显示彩色 RPM 使用量状态。默认关闭。",
    },
    "temperature": {
        "en": "Sampling temperature (0..2). Higher is more varied; lower is more deterministic.",
        "ko": "샘플링 temperature (0~2). 높을수록 다양, 낮을수록 결정적.",
        "ja": "サンプリングtemperature (0〜2)。高いほど多様、低いほど決定的。",
        "zh": "采样 temperature（0..2）。越高越多样，越低越确定。",
    },
    "top_p": {
        "en": "Nucleus sampling top_p (0..1). Lower restricts token choices; 0.8 is a moderate default.",
        "ko": "누적 확률 top_p (0~1). 낮을수록 후보 토큰을 좁힘. 0.8 정도가 적당한 기본값.",
        "ja": "nucleus samplingのtop_p (0〜1)。低いほど候補を絞ります。0.8は中程度の既定値。",
        "zh": "nucleus 采样 top_p（0..1）。越低候选越窄；0.8 是中等默认值。",
    },
    "top_k": {
        "en": "Top-K sampling cutoff. Smaller values pick from a tighter token shortlist.",
        "ko": "Top-K 샘플링. 값이 작을수록 후보 토큰 집합이 좁아집니다.",
        "ja": "Top-Kサンプリング。値が小さいほど候補集合は狭くなります。",
        "zh": "Top-K 采样。值越小候选集合越窄。",
    },
    "think": {
        "en": "Toggle Ollama 'think' output. Claude Code may not display provider-specific thinking cleanly.",
        "ko": "Ollama thinking 출력 여부. Claude Code가 provider별 thinking을 항상 깔끔히 표시하지는 않습니다.",
        "ja": "Ollama thinking出力を切り替えます。Claude Code側で常に綺麗に表示されるとは限りません。",
        "zh": "切换 Ollama thinking 输出。Claude Code 不一定能完整显示。",
    },
    "keep_alive": {
        "en": "How long Ollama keeps the model loaded after a request. Longer reduces reloads but holds memory.",
        "ko": "요청 후 Ollama가 모델을 메모리에 유지하는 시간. 길수록 재로딩은 줄지만 메모리를 더 잡습니다.",
        "ja": "要求後にOllamaがモデルを保持する時間。長いほど再読み込みは減りますがメモリを保持します。",
        "zh": "请求后 Ollama 保持模型加载的时间。越长减少重载，但占用内存更久。",
    },
    "native_compat": {
        "en": "Use direct Anthropic-compatible /v1/messages on this provider. Off routes through claude-any's translator.",
        "ko": "이 provider의 Anthropic-호환 /v1/messages에 직접 연결합니다. off 면 claude-any 라우터를 거칩니다.",
        "ja": "このproviderのAnthropic互換/v1/messagesに直接接続します。offだとclaude-anyルーターを経由します。",
        "zh": "对该 provider 直接走 Anthropic 兼容 /v1/messages；关闭则经由 claude-any 路由器转换。",
    },
    "stream_enabled": {
        "en": "Toggle streaming. Off forces stream:false upstream and returns the full response, useful when SSE fragmentation causes tool-call/JSON parse errors.",
        "ko": "스트리밍 on/off. off면 업스트림에 stream:false를 강제하고 응답 전체를 받습니다. SSE 단편화로 tool-call/JSON 파싱이 실패할 때 유용합니다.",
        "ja": "ストリーミングを切り替えます。offにすると上流にstream:falseを強制し、応答全体を返します。SSE断片化でtool-call/JSONが失敗する時に有効です。",
        "zh": "切换流式输出。off 时强制对上游 stream:false 并返回完整响应；用于 SSE 分片导致的 tool-call/JSON 解析失败。",
    },
    "stream_word_chunking": {
        "en": "Buffer text deltas at whitespace/word boundaries before flushing the SSE event. Tool deltas pass through unchanged.",
        "ko": "텍스트 delta를 공백/단어 경계까지 모아서 SSE 이벤트로 전송. tool delta는 그대로 통과합니다.",
        "ja": "テキストdeltaを空白/単語境界までバッファしてSSEイベントを送信します。tool deltaはそのまま透過します。",
        "zh": "在空白/单词边界处合并文本 delta 后发送 SSE 事件。工具 delta 原样透传。",
    },
    "workflows_enabled": {
        "en": "Allow Claude Code dynamic workflow features through the claude-any gateway. This removes the experimental-beta disable env for this launch.",
        "ko": "claude-any 게이트웨이 경유 상태에서 Claude Code dynamic workflow 기능을 허용합니다. 이 실행에서는 experimental beta 차단 env를 제거합니다.",
        "ja": "claude-any gateway 経由で Claude Code dynamic workflow 機能を許可します。この起動では experimental beta 無効化 env を外します。",
        "zh": "允许 Claude Code dynamic workflow 通过 claude-any gateway 工作。本次启动会移除 experimental beta 禁用环境变量。",
    },
    "ultracode_enabled": {
        "en": "Start Claude Code with the ultracode session setting. Requires verified xhigh_effort model capability.",
        "ko": "Claude Code를 ultracode 세션 설정으로 시작합니다. 검증된 xhigh_effort 모델 capability가 필요합니다.",
        "ja": "Claude Code を ultracode セッション設定で起動します。検証済みの xhigh_effort model capability が必要です。",
        "zh": "用 ultracode 会话设置启动 Claude Code。需要已验证的 xhigh_effort 模型 capability。",
    },
    "claude_code_supported_capabilities": {
        "en": "Comma-separated Claude Code capability override for the selected model: effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking.",
        "ko": "선택 모델의 Claude Code capability override입니다. 쉼표로 effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking 를 지정합니다.",
        "ja": "選択モデルの Claude Code capability override。カンマ区切りで effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking を指定します。",
        "zh": "所选模型的 Claude Code capability override，逗号分隔：effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking。",
    },
    "router_debug_external_access": {
        "en": "Expose the router UI/API to non-local clients for debugging. Off denies external clients and next launch binds locally unless environment overrides it.",
        "ko": "디버깅을 위해 라우터 UI/API를 외부 클라이언트에 노출합니다. off면 외부 요청을 차단하고 다음 실행부터 환경값이 없으면 로컬에만 바인딩합니다.",
        "ja": "デバッグ用にルーター UI/API を外部クライアントへ公開します。off では外部リクエストを拒否し、次回起動時は環境変数がなければローカルのみで bind します。",
        "zh": "为了调试向外部客户端暴露路由器 UI/API。关闭时会拒绝外部请求；下次启动在没有环境变量覆盖时仅绑定本地。",
    },
    "route_through_router": {
        "en": "Route Anthropic through the local claude-any router. This enables router features such as LLM channel injection. If no Anthropic API key is configured, the router forwards Claude Code OAuth/API auth headers.",
        "ko": "Anthropic을 로컬 claude-any 라우터로 경유합니다. LLM 채널 주입 같은 라우터 기능을 쓸 수 있습니다. Anthropic API 키가 없으면 Claude Code의 OAuth/API 인증 헤더를 라우터가 전달합니다.",
        "ja": "Anthropic をローカル claude-any ルーター経由にします。LLM channel injection などのルーター機能を使えます。Anthropic API key が未設定の場合は Claude Code の OAuth/API 認証ヘッダーをルーターが転送します。",
        "zh": "通过本地 claude-any 路由器转发 Anthropic。可使用 LLM channel injection 等路由器功能。未配置 Anthropic API key 时，路由器会转发 Claude Code 的 OAuth/API 认证头。",
    },
    "router_debug_message_preview_chars": {
        "en": "When greater than 0, include the first N characters of the latest user message in router event data for debugging. Keep 0 for privacy.",
        "ko": "0보다 크면 디버깅용 이벤트 data에 최근 사용자 메시지 앞 N자를 포함합니다. 개인정보 보호를 위해 기본값은 0입니다.",
        "ja": "0より大きい場合、デバッグ用イベントdataに直近ユーザーメッセージの先頭N文字を含めます。プライバシー保護のため既定値は0です。",
        "zh": "大于0时，在调试事件data中包含最近用户消息的前N个字符。为保护隐私默认值为0。",
    },
    "back": {
        "en": "Return to the main menu.",
        "ko": "메인 메뉴로 돌아갑니다.",
        "ja": "メインメニューに戻ります。",
        "zh": "返回主菜单。",
    },
}


def llm_option_description(provider: str, key: str, lang: str | None = None) -> str:
    lang = lang or load_config().get("language", "en")
    entry = LLM_OPTION_DESCRIPTIONS.get(key)
    if not entry:
        return ""
    return entry.get(lang) or entry.get("en", "")


def format_timeout_minutes(ms: int, lang: str = "en") -> str:
    seconds = ms / 1000
    minutes = seconds / 60
    labels = {
        "ko": "분",
        "ja": "分",
        "zh": "分钟",
    }
    label = labels.get(lang, "minutes")
    if abs(minutes - round(minutes)) < 0.01:
        return f"{int(round(minutes))}{label if lang in labels else ' ' + label}"
    return f"{minutes:.1f}{label if lang in labels else ' ' + label}"


def llm_option_description_for_value(provider: str, pcfg: dict[str, Any], key: str, lang: str | None = None) -> str:
    text = llm_option_description(provider, key, lang)
    if key not in ("request_timeout_ms", "stream_idle_timeout_ms"):
        return text
    value = positive_int(pcfg.get(key))
    if not value:
        return text
    lang = lang or load_config().get("language", "en")
    suffix = {
        "ko": f" 현재값: {value} ms = {format_timeout_minutes(value, lang)}.",
        "ja": f" 現在値: {value} ms = {format_timeout_minutes(value, lang)}.",
        "zh": f" 当前值：{value} ms = {format_timeout_minutes(value, lang)}。",
    }.get(lang, f" Current value: {value} ms = {format_timeout_minutes(value, lang)}.")
    return text + suffix


# Boolean keys whose Enter handler should flip on/off in place instead of
# prompting for a value. Covers both on/off labels (stream_*) and True/False
# labels (native_compat, think).
LLM_OPTION_TOGGLE_KEYS = {
    "stream_enabled",
    "stream_word_chunking",
    "native_compat",
    "think",
    "rate_limit_enabled",
    "rate_limit_status",
    "router_debug_external_access",
    "route_through_router",
    "workflows_enabled",
    "ultracode_enabled",
}


def llm_option_current_bool(provider: str, pcfg: dict[str, Any], key: str) -> bool:
    if key == "stream_enabled":
        return bool(pcfg.get("stream_enabled", True))
    if key == "stream_word_chunking":
        return bool(pcfg.get("stream_word_chunking", False))
    if key == "native_compat":
        default = False if provider == "nvidia-hosted" else True
        return bool(pcfg.get("native_compat", default))
    if key == "think":
        return bool(pcfg.get("think", False))
    if key == "rate_limit_enabled":
        return bool(router_rate_limit_configured_rpm(provider, pcfg))
    if key == "rate_limit_status":
        return bool(pcfg.get("rate_limit_status", False))
    if key == "router_debug_external_access":
        return router_debug_external_access_enabled()
    if key == "route_through_router":
        return anthropic_routed_enabled(provider, pcfg)
    if key == "workflows_enabled":
        return claude_code_workflows_enabled(provider, pcfg)
    if key == "ultracode_enabled":
        return claude_code_ultracode_enabled(provider, pcfg)
    return bool(pcfg.get(key, False))


def rate_limit_status_label(provider: str, pcfg: dict[str, Any]) -> str:
    rpm = router_rate_limit_configured_rpm(provider, pcfg)
    return f"on ({rpm} rpm)" if rpm else "off"


def rate_limit_rpm_label(provider: str, pcfg: dict[str, Any]) -> str:
    rpm = router_rate_limit_configured_rpm(provider, pcfg)
    return str(rpm) if rpm else "0 (off)"


def llm_option_panel_rows(provider: str, pcfg: dict[str, Any], lang: str | None = None) -> tuple[list[str], list[str]]:
    lang = lang or load_config().get("language", "en")
    rows: list[str] = []
    values: list[str] = []

    def add(label: str, key: str, value: Any) -> None:
        rows.append(f"{label:<24} [{compact_text(value, 56)}]")
        values.append(key)

    add(ui_text("context_setup", lang), "context_setup", context_setting_status(provider, pcfg))
    add(ui_text("apply_preset", lang), "preset", llm_preset_text(applied_preset_id(provider, pcfg), lang)[0])
    add(ui_text("timeout_preset", lang), "timeout_profile", timeout_profile_status(pcfg, lang))
    add("Router debug external", "router_debug_external_access", "on" if router_debug_external_access_enabled() else "off")
    add("Event message preview", "router_debug_message_preview_chars", router_debug_message_preview_chars())
    if not direct_native_anthropic_enabled(provider, pcfg):
        caps = claude_code_capability_string(provider, pcfg, current_upstream_model_id(provider, pcfg))
        add("Claude Code workflows", "workflows_enabled", "on" if claude_code_workflows_enabled(provider, pcfg) else "off")
        add("Claude Code ultracode", "ultracode_enabled", "on" if claude_code_ultracode_enabled(provider, pcfg) else "off")
        add("Claude Code capabilities", "claude_code_supported_capabilities", caps or "auto/none")
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        add("Context window", "num_ctx", ollama_num_ctx_status(pcfg))
        add("Context min", "num_ctx_min", pcfg.get("num_ctx_min", "default"))
        add("Context max", "num_ctx_max", pcfg.get("num_ctx_max", "default"))
        add("Max output tokens", "num_predict", opts.get("num_predict", "default"))
        add("Temperature", "temperature", opts.get("temperature", "default"))
        add("Top P", "top_p", opts.get("top_p", "default"))
        add("Top K", "top_k", opts.get("top_k", "default"))
        add("Think", "think", bool(pcfg.get("think", False)))
        add("Keep alive", "keep_alive", pcfg.get("keep_alive", "default"))
        add("Timeout ms", "request_timeout_ms", pcfg.get("request_timeout_ms", "default"))
        add("Stream", "stream_enabled", "on" if bool(pcfg.get("stream_enabled", True)) else "off")
        if bool(pcfg.get("stream_enabled", True)):
            add("Stream idle timeout ms", "stream_idle_timeout_ms", pcfg.get("stream_idle_timeout_ms", "auto"))
            add("Stream word chunking", "stream_word_chunking", "on" if bool(pcfg.get("stream_word_chunking", False)) else "off")
        add("RPM limiter", "rate_limit_enabled", rate_limit_status_label(provider, pcfg))
        add("Rate limit RPM", "rate_limit_rpm", rate_limit_rpm_label(provider, pcfg))
        add("Rate limit status", "rate_limit_status", "on" if bool(pcfg.get("rate_limit_status", False)) else "off")
        add("IP family", "ip_family", provider_ip_family(provider, pcfg))
    else:
        if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
            add("Context window", "context_window", pcfg.get("context_window", "default"))
            add("Context reserve", "context_reserve_tokens", pcfg.get("context_reserve_tokens", "default"))
        add("Max output tokens", "max_output_tokens", pcfg.get("max_output_tokens", "default"))
        if provider in ("vllm", "lm-studio", "nvidia-hosted", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
            add("Timeout ms", "request_timeout_ms", pcfg.get("request_timeout_ms", "default"))
            add("RPM limiter", "rate_limit_enabled", rate_limit_status_label(provider, pcfg))
            add("Rate limit RPM", "rate_limit_rpm", rate_limit_rpm_label(provider, pcfg))
            add("Rate limit status", "rate_limit_status", "on" if bool(pcfg.get("rate_limit_status", False)) else "off")
            add("Temperature", "temperature", pcfg.get("temperature", "default"))
            add("Top P", "top_p", pcfg.get("top_p", "default"))
            add("Top K", "top_k", pcfg.get("top_k", "default"))
            if provider in ("vllm", "lm-studio", "self-hosted-nim", "deepseek", "opencode", "opencode-go", "openrouter", "fireworks"):
                add("Native compatibility", "native_compat", bool(pcfg.get("native_compat", True)))
            add("Stream", "stream_enabled", "on" if bool(pcfg.get("stream_enabled", True)) else "off")
            if bool(pcfg.get("stream_enabled", True)):
                add("Stream idle timeout ms", "stream_idle_timeout_ms", pcfg.get("stream_idle_timeout_ms", "auto"))
                add("Stream word chunking", "stream_word_chunking", "on" if bool(pcfg.get("stream_word_chunking", False)) else "off")
            add("IP family", "ip_family", provider_ip_family(provider, pcfg))
        elif provider == "anthropic":
            add("Route through router", "route_through_router", "on" if anthropic_routed_enabled(provider, pcfg) else "off")
            add("Timeout ms", "request_timeout_ms", pcfg.get("request_timeout_ms", "Claude Code default"))
            add("Force query string (test)", "force_query_string", pcfg.get("force_query_string", "auto (beta=true)"))
            if anthropic_routed_enabled(provider, pcfg):
                add("IP family", "ip_family", provider_ip_family(provider, pcfg))

    rows.append(ui_text("back", lang))
    values.append("back")
    return rows, values


def llm_option_prompt_default(provider: str, pcfg: dict[str, Any], key: str) -> str:
    if key == "router_debug_external_access":
        return "true" if router_debug_external_access_enabled() else "false"
    if key == "route_through_router":
        return "true" if anthropic_routed_enabled(provider, pcfg) else "false"
    if key == "router_debug_message_preview_chars":
        return str(router_debug_message_preview_chars())
    if key == "stream_enabled":
        return "true" if bool(pcfg.get("stream_enabled", True)) else "false"
    if key == "stream_word_chunking":
        return "true" if bool(pcfg.get("stream_word_chunking", False)) else "false"
    if key == "rate_limit_status":
        return "true" if bool(pcfg.get("rate_limit_status", False)) else "false"
    if key == "rate_limit_enabled":
        return "true" if bool(router_rate_limit_configured_rpm(provider, pcfg)) else "false"
    if key == "workflows_enabled":
        return "true" if claude_code_workflows_enabled(provider, pcfg) else "false"
    if key == "ultracode_enabled":
        return "true" if claude_code_ultracode_enabled(provider, pcfg) else "false"
    if key == "claude_code_supported_capabilities":
        return claude_code_capability_string(provider, pcfg, current_upstream_model_id(provider, pcfg))
    if key == "force_query_string":
        return str(pcfg.get("force_query_string") or "")
    if key == "ip_family":
        return provider_ip_family(provider, pcfg)
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        if key == "num_ctx":
            return str(pcfg.get("num_ctx", "auto"))
        if key in ("num_ctx_min", "num_ctx_max", "keep_alive", "think", "request_timeout_ms", "stream_idle_timeout_ms"):
            return str(pcfg.get(key, ""))
        if key in opts:
            return str(opts[key])
        return ""
    value = pcfg.get(key)
    return "" if value is None else str(value)


def set_llm_option_config(provider: str, key: str, raw_value: str) -> list[str]:
    cfg = load_config()
    pcfg = cfg["providers"][provider]
    value = raw_value.strip()
    if not value:
        return ["Option unchanged."]
    if key == "router_debug_external_access":
        return set_router_debug_external_access_config(value)
    if key == "router_debug_message_preview_chars":
        fixed = positive_int(value)
        if str(value).lower() in ("0", "false", "off", "disable", "disabled", "none", "unset"):
            fixed = 0
        if fixed is None:
            return ["router_debug_message_preview_chars: enter digits only, or 0/off to disable."]
        cfg["router_debug_message_preview_chars"] = min(fixed, 4000)
        save_config(cfg)
        return ["Router event message preview updated.", f"preview chars: {cfg['router_debug_message_preview_chars']}"]
    if key == "route_through_router":
        pcfg["route_through_router"] = parse_bool(value, default=False)
        save_config(cfg)
        clear_model_cache()
        mode = "routed through claude-any router" if pcfg["route_through_router"] else "direct Claude Native"
        return ["Anthropic routing mode updated.", f"mode: {mode}"]
    if key == "rate_limit_enabled":
        enabled = parse_bool(value, default=False)
        if enabled:
            if not router_rate_limit_configured_rpm(provider, pcfg):
                pcfg["rate_limit_rpm"] = 60
            save_config(cfg)
            clear_model_cache()
            return ["RPM limiter enabled.", f"rate_limit_rpm: {router_rate_limit_configured_rpm(provider, pcfg)}"]
        pcfg["rate_limit_rpm"] = 0
        pcfg["rate_limit_status"] = False
        save_config(cfg)
        clear_model_cache()
        return ["RPM limiter disabled.", "rate_limit_rpm: 0", "rate_limit_status: off"]
    if key in ("workflows_enabled", "workflow", "workflows"):
        pcfg["workflows_enabled"] = parse_bool(value, default=False)
        save_config(cfg)
        clear_model_cache()
        return ["Claude Code workflow support updated.", f"workflows_enabled: {pcfg['workflows_enabled']}"]
    if key in ("ultracode_enabled", "ultracode"):
        pcfg["ultracode_enabled"] = parse_bool(value, default=False)
        if pcfg["ultracode_enabled"]:
            pcfg["workflows_enabled"] = True
        save_config(cfg)
        clear_model_cache()
        return ["Claude Code ultracode setting updated.", f"ultracode_enabled: {pcfg['ultracode_enabled']}"]
    if key in ("claude_code_supported_capabilities", "supported_capabilities", "capabilities"):
        caps = normalize_claude_code_supported_capabilities(value)
        pcfg["claude_code_supported_capabilities"] = caps
        save_config(cfg)
        clear_model_cache()
        return ["Claude Code model capabilities updated.", f"capabilities: {','.join(caps) or 'none'}"]
    numeric_keys = {
        "context_window",
        "context",
        "max_model_len",
        "context_reserve_tokens",
        "reserve",
        "max_output_tokens",
        "max_tokens",
        "maxtoken",
        "max_token",
        "num_ctx_min",
        "num_ctx_max",
        "num_predict",
        "timeout",
        "timeout_ms",
        "request_timeout",
        "request_timeout_ms",
        "stream_idle_timeout",
        "stream_idle_timeout_ms",
        "idle_timeout",
        "idle_timeout_ms",
        "rate_limit",
        "rate_limit_rpm",
        "rpm",
        "top_k",
    }
    disable_words = ("0", "false", "off", "disable", "disabled")
    rate_limit_keys = ("rate_limit", "rate_limit_rpm", "rpm")
    if (
        key in numeric_keys
        and value.lower() not in ("default", "unset", "none", "null", "0")
        and not (key in rate_limit_keys and value.lower() in disable_words)
    ):
        if not re.fullmatch(r"\d+", value):
            return [f"{key}: enter digits only, or use default/unset to clear."]
    clear_words = ("default", "unset", "none", "null")
    token = f"unset:{key}" if value.lower() in clear_words else f"{key}={value}"
    context_changed = key in ("context_window", "context", "max_model_len", "num_ctx", "ctx", "num_ctx_min", "ctx_min", "min", "num_ctx_max", "ctx_max", "max")
    explicit_timeout = key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms", "stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms")
    if provider in ("ollama", "ollama-cloud"):
        apply_ollama_option(pcfg, token)
    elif provider == "anthropic":
        if key in ("route", "routed", "route_through_router", "router"):
            pcfg["route_through_router"] = parse_bool(value, default=False)
        elif key in ("max_output_tokens", "max_tokens", "maxtoken", "max_token"):
            apply_provider_option(provider, pcfg, token)
        elif key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms"):
            apply_provider_option(provider, pcfg, token)
        elif key in ("force_query_string", "force_query", "upstream_query", "test_query_string"):
            apply_provider_option(provider, pcfg, token)
        else:
            raise SystemExit(f"Unknown Anthropic option: {key}")
    else:
        apply_provider_option(provider, pcfg, token)
    timeout_lines = apply_recommended_timeout_for_model_context(provider, pcfg) if context_changed and not explicit_timeout else []
    save_config(cfg)
    clear_model_cache()
    return [f"{PROVIDER_LABELS.get(provider, provider)} option updated.", f"{key}: {value}", *timeout_lines]


def apply_provider_option(provider: str, pcfg: dict[str, Any], token: str) -> None:
    if provider in OPENCODE_PROVIDER_NAMES and token.startswith("endpoint:") and "=" in token:
        key, raw_value = token.split("=", 1)
        model_id = key.split(":", 1)[1].strip()
        endpoint = normalize_opencode_endpoint_kind(raw_value)
        if not model_id:
            raise SystemExit("endpoint override requires endpoint:<model-id>=<messages|chat|responses|gemini>")
        if not endpoint:
            raise SystemExit("endpoint override must be one of: messages, chat, responses, gemini")
        endpoints = pcfg.setdefault("model_endpoints", {})
        if not isinstance(endpoints, dict):
            endpoints = {}
            pcfg["model_endpoints"] = endpoints
        endpoints[normalize_model_id(provider, model_id)] = endpoint
        return
    if provider in ("ollama", "ollama-cloud"):
        apply_ollama_option(pcfg, token)
        return
    if token.startswith("unset:"):
        key = token.split(":", 1)[1].strip()
        if key in ("context_window", "context", "max_model_len"):
            pcfg.pop("context_window", None)
        elif key in ("context_reserve_tokens", "reserve"):
            pcfg.pop("context_reserve_tokens", None)
        elif key in ("max_output_tokens", "max_tokens", "maxtoken", "max_token"):
            pcfg.pop("max_output_tokens", None)
        elif key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms"):
            pcfg.pop("request_timeout_ms", None)
        elif key in ("stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms"):
            pcfg.pop("stream_idle_timeout_ms", None)
        elif key in ("rate_limit", "rate_limit_rpm", "rpm"):
            pcfg["rate_limit_rpm"] = 0
            pcfg["rate_limit_status"] = False
        elif key in ("rate_limit_status", "rpm_status"):
            pcfg["rate_limit_status"] = False
        elif key in ("native", "native_compat"):
            if provider == "nvidia-hosted":
                raise SystemExit(
                    f"{provider} does not expose Anthropic /v1/messages; use router mode. "
                    "Use self-hosted-nim or vLLM for native /v1/messages."
                )
            pcfg["native_compat"] = True
        elif key in ("route", "routed", "route_through_router", "router"):
            pcfg["route_through_router"] = False
        elif key in ("stream", "stream_enabled"):
            pcfg["stream_enabled"] = True
        elif key in ("stream_word_chunking", "word_chunking", "stream_chunk", "stream_words"):
            pcfg["stream_word_chunking"] = False
        elif key in ("ip_family", "network_family", "address_family", "addr_family"):
            pcfg.pop("ip_family", None)
        elif key in ("force_query_string", "force_query", "upstream_query", "test_query_string"):
            pcfg.pop("force_query_string", None)
        elif provider == "fireworks" and key in ("account_id", "account"):
            pcfg.pop("account_id", None)
        elif provider == "fireworks" and key in ("model_api_base_url", "model_base_url", "models_base_url", "management_base_url"):
            pcfg.pop("model_api_base_url", None)
        elif key in ("workflows_enabled", "workflow", "workflows"):
            pcfg["workflows_enabled"] = False
        elif key in ("ultracode_enabled", "ultracode"):
            pcfg["ultracode_enabled"] = False
        elif key in ("claude_code_supported_capabilities", "supported_capabilities", "capabilities"):
            pcfg.pop("claude_code_supported_capabilities", None)
        elif provider in OPENCODE_PROVIDER_NAMES and key.startswith("endpoint:"):
            model_id = normalize_model_id(provider, key.split(":", 1)[1].strip())
            endpoints = pcfg.get("model_endpoints")
            if isinstance(endpoints, dict):
                endpoints.pop(model_id, None)
        elif sampling_option_key(key):
            pcfg.pop(sampling_option_key(key), None)
        else:
            raise SystemExit(f"Unknown provider option: {key}")
        return
    if "=" not in token:
        raise SystemExit(f"Expected key=value or unset:key, got: {token}")
    key, raw_value = token.split("=", 1)
    key = key.strip()
    value = parse_config_value(raw_value)
    if key in ("context_window", "context", "max_model_len"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("context_window must be a positive integer")
        pcfg["context_window"] = fixed
        return
    if key in ("context_reserve_tokens", "reserve"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("context_reserve_tokens must be a positive integer")
        pcfg["context_reserve_tokens"] = fixed
        return
    if key in ("max_output_tokens", "max_tokens", "maxtoken", "max_token"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("max_output_tokens must be a positive integer")
        pcfg["max_output_tokens"] = fixed
        return
    if key in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("timeout must be a positive integer; values above 10000 are treated as milliseconds")
        pcfg["request_timeout_ms"] = fixed if key.endswith("_ms") or fixed > 10000 else fixed * 1000
        return
    if key in ("stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms"):
        fixed = positive_int(value)
        if not fixed:
            raise SystemExit("stream_idle_timeout_ms must be a positive integer; values above 10000 are treated as milliseconds")
        pcfg["stream_idle_timeout_ms"] = fixed if key.endswith("_ms") or fixed > 10000 else fixed * 1000
        return
    if key in ("rate_limit", "rate_limit_rpm", "rpm"):
        fixed = positive_int(value)
        if value in (0, "0", False, None) or str(value).lower() in ("false", "off", "disable", "disabled", "none", "unset"):
            pcfg["rate_limit_rpm"] = 0
            pcfg["rate_limit_status"] = False
            return
        if not fixed:
            raise SystemExit("rate_limit_rpm must be a positive integer, or 0/unset to disable")
        pcfg["rate_limit_rpm"] = fixed
        return
    if key in ("native", "native_compat"):
        if provider == "nvidia-hosted":
            raise SystemExit(
                f"{provider} does not expose Anthropic /v1/messages; use router mode. "
                "Use self-hosted-nim or vLLM for native /v1/messages."
            )
        pcfg["native_compat"] = bool(value)
        return
    if key in ("route", "routed", "route_through_router", "router"):
        if provider != "anthropic":
            raise SystemExit("route_through_router is only available for the Anthropic provider")
        pcfg["route_through_router"] = parse_bool(value, default=False)
        return
    if key in ("stream", "stream_enabled"):
        pcfg["stream_enabled"] = parse_bool(value, default=True)
        return
    if key in ("stream_word_chunking", "word_chunking", "stream_chunk", "stream_words"):
        pcfg["stream_word_chunking"] = parse_bool(value, default=False)
        return
    if key in ("ip_family", "network_family", "address_family", "addr_family"):
        pcfg["ip_family"] = normalize_ip_family(value)
        return
    if key in ("force_query_string", "force_query", "upstream_query", "test_query_string"):
        # Testing aid: force a raw query string onto the upstream /v1/messages URL
        # (e.g. "beta=true" or "beta=true&foo=bar"). Lets the operator probe how the
        # upstream reacts to arbitrary query params without code changes. A leading
        # "?" is tolerated and stripped. Empty value clears the override.
        text = "" if value is None else str(value).strip().lstrip("?").strip()
        if not text:
            pcfg.pop("force_query_string", None)
        else:
            pcfg["force_query_string"] = text
        return
    if provider == "fireworks" and key in ("account_id", "account"):
        text = "" if value is None else str(value).strip()
        if not text:
            pcfg.pop("account_id", None)
        else:
            pcfg["account_id"] = text
        return
    if provider == "fireworks" and key in ("model_api_base_url", "model_base_url", "models_base_url", "management_base_url"):
        text = "" if value is None else str(value).strip().rstrip("/")
        if not text:
            pcfg.pop("model_api_base_url", None)
        else:
            pcfg["model_api_base_url"] = text
        return
    if key in ("rate_limit_status", "rpm_status"):
        pcfg["rate_limit_status"] = parse_bool(value, default=False)
        return
    if key in ("workflows_enabled", "workflow", "workflows"):
        pcfg["workflows_enabled"] = parse_bool(value, default=False)
        return
    if key in ("ultracode_enabled", "ultracode"):
        pcfg["ultracode_enabled"] = parse_bool(value, default=False)
        if pcfg["ultracode_enabled"]:
            pcfg["workflows_enabled"] = True
        return
    if key in ("claude_code_supported_capabilities", "supported_capabilities", "capabilities"):
        pcfg["claude_code_supported_capabilities"] = normalize_claude_code_supported_capabilities(value)
        return
    sample_key = sampling_option_key(key)
    if sample_key:
        if value is None:
            pcfg.pop(sample_key, None)
        else:
            pcfg[sample_key] = validate_sampling_option(sample_key, value)
        return
    raise SystemExit(f"Unknown provider option: {key}")


def cmd_provider_options(args: argparse.Namespace) -> None:
    cfg = load_config()
    values = list(getattr(args, "values", []) or [])
    provider = cfg.get("current_provider", "vllm")
    if values:
        try:
            maybe_provider = normalize_provider(values[0])
            if maybe_provider in PROVIDER_OPTION_PROVIDERS:
                provider = maybe_provider
                values = values[1:]
        except SystemExit:
            pass
    if provider not in PROVIDER_OPTION_PROVIDERS:
        raise SystemExit("Provider options are available for anthropic, ollama, ollama-cloud, deepseek, opencode, opencode-go, fireworks, vllm, lm-studio, nvidia-hosted, self-hosted-nim, and openrouter.")
    pcfg = cfg["providers"][provider]
    if values:
        context_changed = any(
            token.split("=", 1)[0].replace("unset:", "").strip() in ("context_window", "context", "max_model_len")
            for token in values
        )
        explicit_timeout = any(
            token.split("=", 1)[0].replace("unset:", "").strip() in ("timeout", "timeout_ms", "request_timeout", "request_timeout_ms", "stream_idle_timeout", "stream_idle_timeout_ms", "idle_timeout", "idle_timeout_ms")
            for token in values
        )
        for token in values:
            apply_provider_option(provider, pcfg, token)
        timeout_lines = apply_recommended_timeout_for_model_context(provider, pcfg) if context_changed and not explicit_timeout else []
        save_config(cfg)
        clear_model_cache()
        print(f"Provider options updated for {provider}.")
        for line in timeout_lines:
            print(line)
    print(f"provider: {provider}")
    print(f"provider_options: {provider_options_status(provider, pcfg)}")
    print("Notes:")
    print("  max_output_tokens is passed to Claude Code as CLAUDE_CODE_MAX_OUTPUT_TOKENS.")
    print("  context_window is a claude-any/router cap; native mode still cannot raise the real server limit.")
    print("  temperature/top_p/top_k are injected by claude-any router mode when the provider supports them.")
    if provider in OPENCODE_PROVIDER_NAMES:
        print("  OpenCode endpoint override: endpoint:<model-id>=messages|chat|responses|gemini")
        print("  OpenCode ip_family options: auto, ipv4, ipv6, ipv4-preferred, ipv6-preferred")
    if provider == "fireworks":
        print("  Fireworks model list options: account_id=fireworks, model_api_base_url=https://api.fireworks.ai")
    print("Examples:")
    print("  claude-anyctl provider-options deepseek max_output_tokens=8192 context_window=1048576")
    print("  claude-anyctl provider-options opencode-go endpoint:custom-model=chat")
    print("  claude-anyctl provider-options opencode ip_family=ipv6-preferred")
    print("  claude-anyctl provider-options fireworks account_id=fireworks model_api_base_url=https://api.fireworks.ai")
    print("  claude-anyctl provider-options nvidia-hosted max_output_tokens=4096 temperature=0.7 top_p=0.8 timeout=300000 rate_limit_rpm=40")
    print("  claude-anyctl provider-options vllm max_output_tokens=4096 context_window=65536 timeout=300000")
    print("  claude-anyctl provider-options self-hosted-nim native=true max_output_tokens=4096")


COMPAT_TOOL_NAME = "compat_echo"
COMPATIBILITY_TEST_HEADER = "x-claude-any-compatibility-test"


def compatibility_tool_schema() -> dict[str, Any]:
    return {
        "name": COMPAT_TOOL_NAME,
        "description": "A minimal compatibility test tool. It echoes one required text argument.",
        "input_schema": {
            "type": "object",
            "properties": {"text": {"type": "string"}},
            "required": ["text"],
            "additionalProperties": False,
        },
    }


def compatibility_text_request(model: str) -> dict[str, Any]:
    return {
        "model": model,
        "max_tokens": compat_max_tokens_for_model(model),
        "stream": False,
        "messages": [
            {
                "role": "user",
                "content": "Compatibility text test. Reply with exactly OK and do not call tools.",
            }
        ],
    }


def compatibility_tool_request(model: str) -> dict[str, Any]:
    return {
        "model": model,
        "max_tokens": 128,
        "stream": False,
        "messages": [
            {
                "role": "user",
                "content": "Compatibility tool test. Use the compat_echo tool exactly once with text set to ping.",
            }
        ],
        "tools": [compatibility_tool_schema()],
        "tool_choice": {"type": "tool", "name": COMPAT_TOOL_NAME},
    }


def compatibility_tool_result_request(model: str, tool_use: dict[str, Any]) -> dict[str, Any]:
    tool_id = str(tool_use.get("id") or "toolu_compat_echo_1")
    tool_input = tool_use.get("input") if isinstance(tool_use.get("input"), dict) else {"text": "ping"}
    return {
        "model": model,
        "max_tokens": 64,
        "stream": False,
        "messages": [
            {
                "role": "user",
                "content": "Compatibility tool test. Use the compat_echo tool exactly once with text set to ping.",
            },
            {
                "role": "assistant",
                "content": [
                    {
                        "type": "tool_use",
                        "id": tool_id,
                        "name": COMPAT_TOOL_NAME,
                        "input": tool_input,
                    }
                ],
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_id,
                        "content": "pong",
                    },
                    {
                        "type": "text",
                        "text": "Now reply with FINAL_OK and do not call tools.",
                    },
                ],
            },
        ],
        "tools": [compatibility_tool_schema()],
    }


def response_content_blocks(data: Any) -> list[dict[str, Any]]:
    if not isinstance(data, dict):
        return []
    content = data.get("content")
    if not isinstance(content, list):
        return []
    return [block for block in content if isinstance(block, dict)]


def response_content_types(data: Any) -> list[str]:
    return [str(block.get("type", "?")) for block in response_content_blocks(data)]


def response_text_preview(data: Any) -> str:
    parts: list[str] = []
    for block in response_content_blocks(data):
        if block.get("type") == "text" and isinstance(block.get("text"), str):
            parts.append(block["text"].strip())
    return " ".join(parts).strip()[:300]


def find_compat_tool_use(data: Any) -> tuple[dict[str, Any] | None, str]:
    for block in response_content_blocks(data):
        if block.get("type") != "tool_use":
            continue
        if block.get("name") != COMPAT_TOOL_NAME:
            return None, f"unexpected tool name {block.get('name')!r}"
        tool_input = block.get("input")
        if not isinstance(tool_input, dict):
            return None, "tool input was not a JSON object"
        if tool_input.get("text") != "ping":
            return None, f"tool input text was {tool_input.get('text')!r}, expected 'ping'"
        if not block.get("id"):
            return None, "tool_use block did not include an id"
        return block, ""
    types = ", ".join(response_content_types(data)) or "none"
    preview = response_text_preview(data)
    suffix = f"; text={preview!r}" if preview else ""
    return None, f"no compat_echo tool_use block returned; content blocks: {types}{suffix}"


def summarize_compat_response(data: Any, label: str) -> list[str]:
    lines = [f"{label}: OK"]
    if isinstance(data, dict):
        stop = data.get("stop_reason")
        if stop:
            lines.append(f"Stop reason: {stop}")
        types = response_content_types(data)
        if types:
            lines.append("Content blocks: " + ", ".join(types[:6]))
        usage = data.get("usage")
        if isinstance(usage, dict):
            tokens = []
            if "input_tokens" in usage:
                tokens.append(f"in={usage['input_tokens']}")
            if "output_tokens" in usage:
                tokens.append(f"out={usage['output_tokens']}")
            if tokens:
                lines.append("Tokens: " + ", ".join(tokens))
    return lines


def compatibility_failure_diagnosis(provider: str, code: int | None, msg: str) -> str | None:
    lower = msg.lower()
    if provider == "vllm" and ("tool" in lower or "parse" in lower or "parser" in lower):
        return (
            "Diagnosis: vLLM tool calling depends on the server's model-specific --tool-call-parser and chat template. "
            "For Qwen3-Coder models, current vLLM docs recommend --tool-call-parser qwen3_xml; Hermes is for Hermes-style models "
            "and some older Qwen tool templates."
        )
    if "does not support tools" in lower:
        return "Diagnosis: selected model does not support tool calling, so it is not suitable for normal Claude Code use."
    if provider == "nvidia-hosted" and code == 404:
        return (
            "Diagnosis: NVIDIA API Catalog does not expose this request path/model for the current account. "
            "Use the default router mode for nvidia-hosted, or pick another hosted model."
        )
    if provider == "nvidia-hosted" and code in (502, 503, 504):
        return (
            "Diagnosis: NVIDIA API Catalog or the hosted model backend returned a transient upstream error. "
            "Retry the compatibility test, or choose another NVIDIA hosted model if it repeats."
        )
    if provider == "nvidia-hosted" and (
        "remotedisconnected" in lower
        or "remote end closed connection" in lower
        or "connection reset" in lower
        or "gateway timeout" in lower
    ):
        return (
            "Diagnosis: the NVIDIA hosted upstream closed the request without a complete response. "
            "This is usually a transient API Catalog/backend issue rather than a local claude-any configuration error. "
            "Retry the test, or choose another hosted model if it repeats."
        )
    if provider == "nvidia-hosted" and "function" in lower and "not found" in lower:
        return (
            "Diagnosis: NVIDIA returned a missing function for this hosted model. The model is visible in /v1/models "
            "but is not callable with the current account."
        )
    return None


class CompatibilityApiKeyProbeError(Exception):
    def __init__(self, message: str, code: int | None = None, diagnosis: str = "") -> None:
        super().__init__(message)
        self.code = code
        self.diagnosis = diagnosis


def compatibility_http_error_message(exc: urllib.error.HTTPError) -> str:
    raw = exc.read().decode("utf-8", errors="ignore")
    msg = raw.strip()
    error_type = ""
    try:
        err = json.loads(raw)
        if isinstance(err, dict):
            if isinstance(err.get("error"), dict):
                error_obj = err["error"]
                error_type = str(error_obj.get("type") or "").strip()
                msg = str(error_obj.get("message") or json.dumps(error_obj, ensure_ascii=False))
            elif err.get("message"):
                msg = str(err["message"])
                error_type = str(err.get("type") or "").strip()
    except Exception:
        pass
    if error_type and error_type not in msg:
        msg = f"{error_type}: {msg}"
    retry_after = first_header(exc.headers, ["Retry-After", "retry-after"])
    if retry_after:
        retry_after_text = retry_after.strip()
        retry_after_seconds = parse_retry_after_seconds(retry_after_text)
        if retry_after_seconds is not None:
            retry_after_display = format_duration_seconds(retry_after_seconds)
            if retry_after_text:
                if re.fullmatch(r"\d+(?:\.\d+)?", retry_after_text):
                    suffix = f"{retry_after_display} ({retry_after_text}s)"
                else:
                    suffix = f"{retry_after_display} ({retry_after_text})"
                msg = f"{msg} Retry-After: {suffix}"
            else:
                msg = f"{msg} Retry-After: {retry_after_display}"
        else:
            msg = f"{msg} Retry-After: {retry_after_text}"
    return msg


def provider_config_for_single_api_key(pcfg: dict[str, Any], key: str) -> dict[str, Any]:
    keyed = dict(pcfg)
    keyed["api_key"] = key
    keyed["api_keys"] = []
    return keyed


def compatibility_api_key_probe_request(
    provider: str,
    pcfg: dict[str, Any],
    model: str,
    request_body: dict[str, Any],
) -> tuple[str, dict[str, Any], dict[str, str]]:
    body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, request_body)
    body = normalize_tool_choice_for_provider(provider, pcfg, body)
    upstream_model = resolve_requested_model(provider, pcfg, model)
    headers = provider_headers(provider, pcfg)
    if provider in ("ollama", "ollama-cloud"):
        req_body = ollama_chat_request(upstream_model, body, pcfg, stream=False)
        return join_url(provider_upstream_request_base(provider, pcfg), "/api/chat"), req_body, headers
    if provider in OPENCODE_PROVIDER_NAMES:
        endpoint_kind = opencode_endpoint_kind(provider, upstream_model, pcfg)
        if endpoint_kind == "openai-chat":
            req_body = openai_compatible_chat_request(provider, upstream_model, body, pcfg, stream=False)
            return join_url(provider_upstream_request_base(provider, pcfg), "/v1/chat/completions"), req_body, headers
        if endpoint_kind != "anthropic-messages":
            raise CompatibilityApiKeyProbeError(
                f"model {upstream_model!r} uses unsupported endpoint family {endpoint_kind!r} for API-key probing"
            )
    if provider_openai_router_enabled(provider, pcfg):
        upstream_model = ncp_model_id_for_nvidia_hosted(upstream_model) if provider == "nvidia-hosted" else upstream_model
        req_body = openai_compatible_chat_request(provider, upstream_model, body, pcfg, stream=False)
        return join_url(provider_upstream_request_base(provider, pcfg), "/v1/chat/completions"), req_body, headers
    body = cap_anthropic_body_for_provider(provider, pcfg, body)
    body = apply_provider_request_options(provider, pcfg, body)
    body = dict(body)
    body["model"] = upstream_model
    body = resolve_tool_model_references(provider, pcfg, body)
    base = native_anthropic_base_url(provider, pcfg) if provider_native_compat_enabled(provider, pcfg) else provider_upstream_request_base(provider, pcfg)
    return join_url(base, "/v1/messages"), body, headers


def run_compatibility_api_key_probes(
    provider: str,
    pcfg: dict[str, Any],
    model: str,
    request_body: dict[str, Any],
    timeout: float,
) -> list[str]:
    keys = provider_config_api_keys(provider, pcfg)
    if len(keys) <= 1:
        return []
    lines = [f"API key checks: running {len(keys)} configured keys"]
    for index, key in enumerate(keys, start=1):
        label = f"API key {index}/{len(keys)} ({mask_secret(key)})"
        keyed_pcfg = provider_config_for_single_api_key(pcfg, key)
        try:
            url, probe_body, probe_headers = compatibility_api_key_probe_request(provider, keyed_pcfg, model, request_body)
            post_json(url, probe_body, headers=probe_headers, timeout=timeout, provider=provider, pcfg=keyed_pcfg)
        except CompatibilityApiKeyProbeError:
            raise
        except urllib.error.HTTPError as exc:
            msg = compatibility_http_error_message(exc)
            diagnosis = compatibility_failure_diagnosis(provider, exc.code, msg) or ""
            raise CompatibilityApiKeyProbeError(f"{label}: {msg}", exc.code, diagnosis) from exc
        except TimeoutError as exc:
            raise CompatibilityApiKeyProbeError(f"{label}: timed out before the {timeout:g}s API-key probe timeout") from exc
        except Exception as exc:
            raise CompatibilityApiKeyProbeError(f"{label}: {type(exc).__name__}: {exc}") from exc
        lines.append(f"{label}: OK")
    return lines


def vllm_tool_parser_hint(model: str) -> str | None:
    normalized = model.lower()
    if "qwen3-coder" in normalized or "qwen3_coder" in normalized:
        return "vLLM hint: Qwen3-Coder models should be served with --enable-auto-tool-choice --tool-call-parser qwen3_xml."
    if "qwen2.5" in normalized or "qwen2_5" in normalized or "qwq" in normalized:
        return "vLLM hint: Qwen2.5/QwQ tool templates usually use --enable-auto-tool-choice --tool-call-parser hermes."
    if "glm-4.7" in normalized or "glm4.7" in normalized:
        return "vLLM hint: GLM-4.7 models should be served with --enable-auto-tool-choice --tool-call-parser glm47."
    if "glm-4.5" in normalized or "glm4.5" in normalized or "glm-4.6" in normalized or "glm4.6" in normalized:
        return "vLLM hint: GLM-4.5/4.6 models should be served with --enable-auto-tool-choice --tool-call-parser glm45."
    if "deepseek-v3.1" in normalized:
        return "vLLM hint: DeepSeek-V3.1 models should be served with --enable-auto-tool-choice --tool-call-parser deepseek_v31."
    if "deepseek-v3" in normalized or "deepseek-r1" in normalized:
        return "vLLM hint: DeepSeek-V3/R1 models require the matching DeepSeek tool parser and chat template from vLLM examples."
    if "llama-3" in normalized or "llama3" in normalized:
        return "vLLM hint: Llama 3.x models usually need --enable-auto-tool-choice --tool-call-parser llama3_json and the matching tool chat template."
    if "hermes" in normalized:
        return "vLLM hint: Hermes models should be served with --enable-auto-tool-choice --tool-call-parser hermes."
    if "qwen3" in normalized or "qwen-3" in normalized:
        return (
            "vLLM hint: this looks like a Qwen3-family model. Verify its model card/tool format; "
            "Qwen3-Coder uses qwen3_xml, while older Hermes-style Qwen templates use hermes."
        )
    return None


def compatibility_runtime_lines(provider: str, pcfg: dict[str, Any], native: bool) -> list[str]:
    if provider not in ("vllm", "lm-studio", "self-hosted-nim"):
        return []
    lines: list[str] = []
    info = upstream_model_runtime_info(provider, pcfg, timeout=4.0)
    configured_context = positive_int(pcfg.get("context_window"))
    configured_output = positive_int(pcfg.get("max_output_tokens"))
    if info:
        lines.append(f"Runtime models URL: {info.get('models_url')}")
        if info.get("runtime_model"):
            lines.append(f"Runtime model id: {info.get('runtime_model')}")
        runtime_limit = positive_int(info.get("max_model_len"))
        if runtime_limit:
            lines.append(f"Runtime max_model_len: {runtime_limit}")
        else:
            lines.append("Runtime max_model_len: not reported by /v1/models")
        loaded_context = positive_int(info.get("loaded_context_len"))
        if provider == "lm-studio" and loaded_context:
            lines.append(f"Runtime loaded_context_length: {loaded_context}")
        if provider == "lm-studio" and info.get("state"):
            lines.append(f"Runtime model state: {info.get('state')}")
    else:
        runtime_limit = None
        lines.append("Runtime max_model_len: unavailable (/v1/models did not return model metadata)")
    if configured_context:
        lines.append(f"Configured context_window: {configured_context}")
    if configured_output:
        lines.append(f"Configured max_output_tokens: {configured_output}")
    if runtime_limit and configured_context and configured_context != runtime_limit:
        lines.append(f"Context warning: configured context_window {configured_context} differs from runtime max_model_len {runtime_limit}.")
    if runtime_limit and configured_output and configured_output >= runtime_limit:
        lines.append("Context warning: max_output_tokens is greater than or equal to the full runtime context length.")
    if native:
        lines.append("Runtime mode note: native mode sends Claude Code requests directly; claude-any cannot shrink max_tokens per request.")
    else:
        lines.append("Runtime mode note: router mode can cap max_tokens based on configured context_window.")
    return lines


def set_compatibility_cache(
    cfg: dict[str, Any],
    provider: str,
    model: str,
    ok: bool,
    code: int | None = None,
    message: str = "",
    diagnosis: str = "",
) -> None:
    cache = cfg.setdefault("compatibility_cache", {})
    if not isinstance(cache, dict):
        cache = {}
        cfg["compatibility_cache"] = cache
    provider_cache = cache.setdefault(provider, {})
    if not isinstance(provider_cache, dict):
        provider_cache = {}
        cache[provider] = provider_cache
    provider_cache[model] = {
        "ok": ok,
        "code": code,
        "message": message[:500],
        "diagnosis": diagnosis[:500],
        "tested_at": int(time.time()),
    }
    save_config(cfg)


def _cmd_test(args: argparse.Namespace) -> None:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    test_mode = getattr(args, "mode", "auto") or "auto"
    if test_mode not in ("auto", "quick", "smoke", "full"):
        raise SystemExit("test mode must be auto, quick, smoke, or full")
    effective_mode = "quick" if test_mode == "auto" and provider == "nvidia-hosted" else ("full" if test_mode == "auto" else test_mode)
    lm_studio_preflight_lines: list[str] = []
    if provider == "lm-studio":
        try:
            lm_studio_preflight_lines = ensure_lm_studio_model_loaded_for_context(pcfg, timeout=1.5)
            save_config(cfg)
        except Exception as exc:
            print("Compatibility: FAIL")
            print("Reason: Claude Any could not automatically load the selected LM Studio model with the recommended context.")
            print(f"Diagnosis: LM Studio load failed ({type(exc).__name__}: {exc}).")
            sys.exit(1)
    ollama_native = ollama_native_compat_enabled(provider, pcfg)
    provider_native = provider_native_compat_enabled(provider, pcfg)
    native = ollama_native or provider_native
    model = current_upstream_model_id(provider, pcfg) if provider_native else (launch_model_id(provider, pcfg) if ollama_native else current_alias(cfg))
    base = native_anthropic_base_url(provider, pcfg) if native else ROUTER_BASE
    if not native:
        # Compatibility tests must exercise the currently installed router.
        # Older long-running routers can keep stale NVIDIA proxy code alive
        # across npm upgrades, producing false nvd-claude-proxy failures.
        stop_router_processes(quiet=True)
        start_router_if_needed()
    url = join_url(base, "/v1/messages")
    headers = provider_headers(provider, pcfg)
    headers[COMPATIBILITY_TEST_HEADER] = "1"
    if ollama_native:
        headers = {
            "content-type": "application/json",
            "anthropic-version": "2023-06-01",
            "authorization": "Bearer ollama",
            "x-api-key": "ollama",
            COMPATIBILITY_TEST_HEADER: "1",
        }
    text_body = normalize_tool_choice_for_provider(
        provider,
        pcfg,
        normalize_thinking_for_non_anthropic_provider(provider, pcfg, compatibility_text_request(model)),
    )
    tool_body = normalize_tool_choice_for_provider(
        provider,
        pcfg,
        normalize_thinking_for_non_anthropic_provider(provider, pcfg, compatibility_tool_request(model)),
    )
    print(f"Testing provider: {provider}")
    print(f"Test mode: {effective_mode}")
    if ollama_native:
        mode = "ollama-native"
    elif vllm_native_compat_enabled(provider, pcfg):
        mode = "vllm-native"
    elif lm_studio_native_compat_enabled(provider, pcfg):
        mode = "lm-studio-native"
    elif nim_native_compat_enabled(provider, pcfg):
        mode = "nim-native"
    elif nvidia_hosted_native_compat_enabled(provider, pcfg):
        mode = "nvidia-native"
    else:
        mode = "claude-any-router"
    print(f"Mode: {mode}")
    print(f"Claude API URL: {url}")
    if not native:
        print(f"Upstream base URL: {pcfg.get('base_url')}")
        for line in provider_ip_family_probe_lines(provider, pcfg):
            print(line)
        if provider in ("ollama", "ollama-cloud"):
            req_preview = ollama_chat_request(resolve_requested_model(provider, pcfg, model), tool_body, pcfg, stream=False)
            print(f"Ollama num_ctx: {req_preview.get('options', {}).get('num_ctx', 'default')}")
    elif provider in OPENCODE_PROVIDER_NAMES:
        for line in provider_ip_family_probe_lines(provider, pcfg):
            print(line)
    print(f"Model: {model}")
    for line in lm_studio_preflight_lines:
        print(line)
    for line in compatibility_runtime_lines(provider, pcfg, native):
        print(line)
    if provider == "lm-studio":
        info = upstream_model_runtime_info(provider, pcfg, timeout=1.5)
        loaded = positive_int(info.get("loaded_context_len")) if info else None
        state = str(info.get("state") or "") if info else ""
        if loaded and loaded < LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
            print("Compatibility: FAIL")
            print(
                "Reason: LM Studio loaded context is "
                f"{loaded:,} tokens; Claude Code needs at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,}."
            )
            print("Diagnosis: reload the model in LM Studio with a larger context length, then retry.")
            sys.exit(1)
        if state and state != "loaded":
            print("Compatibility: FAIL")
            print("Reason: the selected LM Studio model is not loaded, so the active context length cannot be verified.")
            print(f"Diagnosis: load the model in LM Studio with at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,} context tokens, then retry.")
            sys.exit(1)
    if provider == "vllm":
        hint = vllm_tool_parser_hint(model)
        if hint:
            print(hint)

    def fail(message: str, code: int | None = None, diagnosis: str = "") -> None:
        print("Compatibility: FAIL")
        if code is not None:
            print(f"HTTP: {code}")
        print(f"Reason: {message[:1000]}")
        if diagnosis:
            print(diagnosis)
        set_compatibility_cache(cfg, provider, model, False, code, message, diagnosis)
        raise SystemExit(1)

    def run_phase(label: str, request_body: dict[str, Any]) -> Any:
        print(f"{label}: running")
        try:
            return post_json(
                url,
                request_body,
                headers=headers,
                timeout=args.timeout,
                provider=provider if native else None,
                pcfg=pcfg if native else None,
            )
        except urllib.error.HTTPError as exc:
            msg = compatibility_http_error_message(exc)
            diagnosis = compatibility_failure_diagnosis(provider, exc.code, msg)
            fail(f"{label}: {msg}", exc.code, diagnosis or "")
        except TimeoutError:
            print("Compatibility: TIMEOUT")
            print(f"Reason: {label} did not respond before the {args.timeout:g}s compatibility-test timeout.")
            print("Diagnosis: this timeout was not saved as a model failure. Retry the test or choose another model if it repeats.")
            sys.stdout.flush()
            sys.exit(1)
        except Exception as exc:
            msg = f"{type(exc).__name__}: {exc}"
            if "timed out" in msg.lower() or "timeout" in msg.lower():
                print("Compatibility: TIMEOUT")
                print(f"Reason: {label}: {msg}")
                print("Diagnosis: this timeout was not saved as a model failure. Retry the test or choose another model if it repeats.")
                sys.stdout.flush()
                sys.exit(1)
            fail(f"{label}: {msg}")

    try:
        for line in run_compatibility_api_key_probes(provider, pcfg, model, text_body, args.timeout):
            print(line)
    except CompatibilityApiKeyProbeError as exc:
        fail(f"API key check: {exc}", exc.code, exc.diagnosis)

    text_data = run_phase("Text response", text_body)
    for line in summarize_compat_response(text_data, "Text response"):
        print(line)

    if effective_mode == "quick":
        set_compatibility_cache(cfg, provider, model, True, 200, "text quick OK", "")
        print("Compatibility: OK")
        print("Note: quick mode checked text only; run `claude-any test 120 smoke` for tool_use or `claude-any test 180 full` for tool_result.")
        return

    tool_data = run_phase("Tool use", tool_body)
    tool_use, tool_error = find_compat_tool_use(tool_data)
    if not tool_use:
        diagnosis = (
            "Diagnosis: the model/server did not return a valid Anthropic tool_use block. "
            "Claude Code can fail with 'tool call could not be parsed' on this provider/model."
        )
        if provider == "vllm":
            hint = vllm_tool_parser_hint(model)
            if hint:
                diagnosis = f"{diagnosis} {hint}"
        fail(f"Tool use: {tool_error}", diagnosis=diagnosis)
    for line in summarize_compat_response(tool_data, "Tool use"):
        print(line)

    if effective_mode == "smoke":
        set_compatibility_cache(cfg, provider, model, True, 200, "text/tool_use smoke OK", "")
        print("Compatibility: OK")
        print("Note: smoke mode checked text and tool_use only; run `claude-any test 180 full` for tool_result round trip.")
        return

    result_body = compatibility_tool_result_request(model, tool_use)
    result_data = run_phase("Tool result", result_body)
    result_preview = response_text_preview(result_data)
    if not result_preview:
        fail(
            "Tool result: no final text response after tool_result.",
            diagnosis="Diagnosis: the provider accepted tool_use but did not complete the tool_result round trip.",
        )
    for line in summarize_compat_response(result_data, "Tool result"):
        print(line)
    print(f"Tool result text: {result_preview[:120]}")

    set_compatibility_cache(cfg, provider, model, True, 200, "text/tool_use/tool_result OK", "")
    print("Compatibility: OK")


def cmd_test(args: argparse.Namespace) -> None:
    try:
        _cmd_test(args)
    except SystemExit:
        raise
    except Exception as exc:
        print("Compatibility: FAIL")
        print(f"Reason: {type(exc).__name__}: {exc}")
        raise SystemExit(1)


def claude_code_output_token_limit(provider: str, pcfg: dict[str, Any]) -> int | None:
    configured = positive_int(pcfg.get("max_output_tokens"))
    if configured:
        return configured
    if provider in ("ollama", "ollama-cloud"):
        opts = ollama_extra_options(pcfg)
        configured = positive_int(opts.get("num_predict"))
        if configured:
            return configured
    return None


def claude_code_auto_compact_window(provider: str, pcfg: dict[str, Any]) -> int | None:
    configured = positive_int(pcfg.get("auto_compact_window"))
    if configured:
        return configured
    limit = context_limit_for_status(provider, pcfg)
    if limit:
        return limit
    return None


def claude_code_context_model_alias(provider: str, pcfg: dict[str, Any], model: str) -> str:
    model = strip_claude_context_suffix(model)
    limit = context_limit_for_status(provider, pcfg)
    if limit and limit >= 1000000 and "[1m]" not in model.lower():
        return f"{model}[1m]"
    return model


def _model_id_matches_claude_family(model_id: str, family: str) -> bool:
    normalized = strip_claude_context_suffix(model_id).strip().lower()
    family = family.strip().lower()
    if not normalized or family not in ("opus", "sonnet", "haiku"):
        return False
    return bool(re.search(rf"(?:^|[-_./]){re.escape(family)}(?:[-_./]|$)", normalized))


def claude_code_default_model_aliases(provider: str, pcfg: dict[str, Any], current_model_alias: str) -> dict[str, str]:
    current_upstream = current_upstream_model_id(provider, pcfg)
    candidates = cached_or_configured_model_ids(provider, pcfg)
    if current_upstream and current_upstream not in candidates:
        candidates.insert(0, current_upstream)
    out: dict[str, str] = {}
    for family, key in (
        ("haiku", "ANTHROPIC_DEFAULT_HAIKU_MODEL"),
        ("opus", "ANTHROPIC_DEFAULT_OPUS_MODEL"),
        ("sonnet", "ANTHROPIC_DEFAULT_SONNET_MODEL"),
    ):
        selected = ""
        if _model_id_matches_claude_family(current_upstream, family):
            selected = current_upstream
        if not selected:
            for model_id in candidates:
                if _model_id_matches_claude_family(model_id, family):
                    selected = model_id
                    break
        alias = alias_for(provider, selected) if selected else current_model_alias
        out[key] = claude_code_context_model_alias(provider, pcfg, alias)
    return out


def apply_common_claude_env(provider: str, pcfg: dict[str, Any], env: dict[str, str]) -> dict[str, str]:
    # Claude Code's AI-generated terminal/session title can be persisted as
    # ai-title records and, in some resume/queued-command states, visually bleed
    # into the prompt area. Disable that side path for claude-any launches.
    env["CLAUDE_CODE_DISABLE_TERMINAL_TITLE"] = "1"
    output_tokens = claude_code_output_token_limit(provider, pcfg)
    if output_tokens:
        env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = str(output_tokens)
    compact_window = claude_code_auto_compact_window(provider, pcfg)
    if compact_window:
        env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] = str(compact_window)
    advisor_model = str(pcfg.get("advisor_model") or "").strip()
    if advisor_model:
        env["CLAUDE_ANY_ADVISOR_MODEL"] = advisor_model
    claude_model = str(env.get("ANTHROPIC_MODEL") or env.get("CLAUDE_ANY_MODEL_ALIAS") or "").strip()
    capability_string = claude_code_capability_string(provider, pcfg, current_upstream_model_id(provider, pcfg))
    if claude_model and capability_string:
        env["ANTHROPIC_CUSTOM_MODEL_OPTION"] = claude_model
        env["ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES"] = capability_string
    for key in (
        "ANTHROPIC_DEFAULT_HAIKU_MODEL",
        "ANTHROPIC_DEFAULT_OPUS_MODEL",
        "ANTHROPIC_DEFAULT_SONNET_MODEL",
    ):
        model_alias = str(env.get(key) or "").strip()
        if not model_alias:
            continue
        upstream_model = resolve_requested_model(provider, pcfg, model_alias)
        default_caps = claude_code_capability_string(provider, pcfg, upstream_model)
        if default_caps:
            env[f"{key}_SUPPORTS"] = default_caps
            env[f"{key}_SUPPORTED_CAPABILITIES"] = default_caps
    if claude_code_workflows_enabled(provider, pcfg):
        env.pop("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS", None)
    return env


def env_vars(cfg: dict[str, Any] | None = None) -> dict[str, str]:
    """Build the environment overrides claude-any adds when spawning `claude`.

    Claude Native mode contract: when the selected provider is native
    Anthropic, claude-any MUST NOT inject anything that would alter Claude
    Code's default model selection, backend URL, advisor flow, output-token
    cap, auto-compact window, or any other Claude-Code-visible behavior.
    The only override is an optional ``ANTHROPIC_API_KEY`` if the user has
    one stored in claude-any's config (Claude Code's OAuth credentials win
    otherwise). ``CLAUDE_ANY_PROVIDER=anthropic`` is set purely as a marker
    for claude-any's own helpers (statusline, hooks) so they can self-suppress.
    """
    cfg = cfg or load_config()
    provider, pcfg = get_current_provider(cfg)
    if direct_native_anthropic_enabled(provider, pcfg):
        env = {"CLAUDE_ANY_PROVIDER": provider}
        key = provider_primary_api_key(provider, pcfg)
        if meaningful_key(key):
            env["ANTHROPIC_API_KEY"] = str(key)
        return env
    alias = current_alias(cfg)
    claude_model = claude_code_context_model_alias(provider, pcfg, alias)
    auth_token = claude_code_router_auth_token(provider, pcfg)
    default_models = claude_code_default_model_aliases(provider, pcfg, claude_model)
    env = {
        "CLAUDE_ANY_PROVIDER": provider,
        "ANTHROPIC_BASE_URL": ROUTER_BASE,
        "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1",
        "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
        "CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
        "ANTHROPIC_MODEL": claude_model,
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": default_models["ANTHROPIC_DEFAULT_HAIKU_MODEL"],
        "ANTHROPIC_DEFAULT_OPUS_MODEL": default_models["ANTHROPIC_DEFAULT_OPUS_MODEL"],
        "ANTHROPIC_DEFAULT_SONNET_MODEL": default_models["ANTHROPIC_DEFAULT_SONNET_MODEL"],
        "CLAUDE_CODE_SUBAGENT_MODEL": claude_model,
        "CLAUDE_ANY_MODEL_ALIAS": claude_model,
        "CLAUDE_ANY_BYPASS_PERMISSIONS": "1",
    }
    if auth_token:
        env["ANTHROPIC_AUTH_TOKEN"] = auth_token
    return apply_common_claude_env(provider, pcfg, env)


def claude_code_router_auth_token(provider: str, pcfg: dict[str, Any]) -> str:
    if provider == "anthropic":
        return ""
    key = provider_primary_api_key(provider, pcfg)
    if meaningful_key(key):
        return key
    if provider == "ollama":
        return "ollama"
    return "not-used"


def claude_code_runtime_settings(provider: str, pcfg: dict[str, Any]) -> dict[str, Any]:
    settings: dict[str, Any] = {}
    if claude_code_ultracode_enabled(provider, pcfg):
        settings["ultracode"] = True
    return settings


def append_claude_code_runtime_settings_args(extra_args: list[str], passthrough: list[str], provider: str, pcfg: dict[str, Any]) -> None:
    settings = claude_code_runtime_settings(provider, pcfg)
    if not settings:
        return
    if has_passthrough_option(passthrough, "--settings"):
        router_log("WARN", "claude_code_runtime_settings_skipped reason=passthrough_settings_present")
        return
    extra_args.extend(["--settings", json.dumps(settings, separators=(",", ":"))])


def cmd_env(_: argparse.Namespace) -> None:
    env = env_vars()
    for optional in ("ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY"):
        if optional in env:
            print(f"export {optional}={json.dumps(env[optional])}")
        else:
            print(f"unset {optional}")
    if "ANTHROPIC_AUTH_TOKEN" in env:
        print(f"export ANTHROPIC_AUTH_TOKEN={json.dumps(env['ANTHROPIC_AUTH_TOKEN'])}")
    else:
        print('unset ANTHROPIC_AUTH_TOKEN')
    for key in (
        "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
        "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
        "CLAUDE_CODE_ATTRIBUTION_HEADER",
        "CLAUDE_CODE_MAX_OUTPUT_TOKENS",
        "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
        "CLAUDE_CODE_EFFORT_LEVEL",
        "ANTHROPIC_MODEL",
        "ANTHROPIC_CUSTOM_MODEL_OPTION",
        "ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTS",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES",
        "ANTHROPIC_DEFAULT_OPUS_MODEL",
        "ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTS",
        "ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES",
        "ANTHROPIC_DEFAULT_SONNET_MODEL",
        "ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTS",
        "ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES",
        "CLAUDE_CODE_SUBAGENT_MODEL",
        "CLAUDE_ANY_MODEL_ALIAS",
        "CLAUDE_ANY_PROVIDER",
    ):
        if key in env:
            print(f"export {key}={json.dumps(env[key])}")
        else:
            print(f"unset {key}")


def cmd_stop(_: argparse.Namespace) -> None:
    stopped = stop_router_processes(quiet=True)
    stopped = stop_ncp_proxy(quiet=True) or stopped
    print("claude-any managed services stopped" if stopped else "claude-any managed services were not running")


def pid_is_running(pid: int) -> bool:
    if pid <= 0:
        return False
    if os.name == "nt":
        try:
            proc = subprocess.run(
                ["tasklist", "/FI", f"PID eq {pid}", "/FO", "CSV", "/NH"],
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                timeout=3,
            )
            out = proc.stdout or ""
            return str(pid) in out and "No tasks" not in out and "INFO:" not in out
        except Exception:
            return False
    try:
        os.kill(pid, 0)
        return True
    except (OSError, SystemError):
        return False


def register_router_client(pid: int | None = None) -> Path:
    client_pid = int(pid or os.getpid())
    ROUTER_CLIENTS_DIR.mkdir(parents=True, exist_ok=True)
    path = ROUTER_CLIENTS_DIR / f"{client_pid}.json"
    payload = {
        "pid": client_pid,
        "started_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "router_port": ROUTER_PORT,
    }
    path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
    try:
        os.chmod(path, 0o600)
    except Exception:
        pass
    router_log("INFO", f"router_client_registered pid={client_pid} path={path}")
    return path


def release_router_client(path: Path | None) -> None:
    if path is None:
        return
    try:
        path.unlink()
        router_log("INFO", f"router_client_released path={path}")
    except FileNotFoundError:
        pass
    except Exception as exc:
        router_log("WARN", f"router_client_release_failed path={path} error={type(exc).__name__}: {exc}")


def router_managed_idle_exit_seconds() -> float:
    raw = os.environ.get("CLAUDE_ANY_ROUTER_IDLE_EXIT_SECONDS", "90")
    try:
        value = float(raw)
    except (TypeError, ValueError):
        value = 90.0
    return max(0.0, value)


def managed_router_stop_reason(started_at: float, owner_pid: int, idle_seconds: float) -> str | None:
    if os.environ.get("CLAUDE_ANY_MANAGED_ROUTER") != "1":
        return None
    active = active_router_client_pids()
    if active:
        return None
    if owner_pid > 0 and not pid_is_running(owner_pid):
        return "owner_dead_no_clients"
    if idle_seconds > 0 and time.time() - started_at >= idle_seconds:
        return "idle_no_clients"
    return None


def start_managed_router_lifetime_watchdog(server: ThreadingHTTPServer) -> None:
    if os.environ.get("CLAUDE_ANY_MANAGED_ROUTER") != "1":
        return
    try:
        owner_pid = int(os.environ.get("CLAUDE_ANY_ROUTER_OWNER_PID") or "0")
    except ValueError:
        owner_pid = 0
    idle_seconds = router_managed_idle_exit_seconds()
    started_at = time.time()

    def watch() -> None:
        interval = min(5.0, max(0.5, idle_seconds / 3.0 if idle_seconds else 5.0))
        while True:
            time.sleep(interval)
            reason = managed_router_stop_reason(started_at, owner_pid, idle_seconds)
            if not reason:
                continue
            router_log("INFO", f"router_managed_lifetime_shutdown reason={reason} owner_pid={owner_pid or '-'}")
            try:
                server.shutdown()
            except Exception as exc:
                router_log("ERROR", f"router_managed_lifetime_shutdown_failed error={type(exc).__name__}: {exc}")
            return

    thread = threading.Thread(target=watch, daemon=True, name="ca-router-lifetime-watchdog")
    thread.start()


def active_router_client_pids() -> list[int]:
    if not ROUTER_CLIENTS_DIR.exists():
        return []
    active: list[int] = []
    for path in ROUTER_CLIENTS_DIR.glob("*.json"):
        pid = 0
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
            pid = int(data.get("pid") or path.stem)
        except Exception:
            try:
                pid = int(path.stem)
            except Exception:
                pid = 0
        if pid_is_running(pid):
            active.append(pid)
            continue
        try:
            path.unlink()
            router_log("INFO", f"router_client_stale_removed pid={pid or '-'} path={path}")
        except Exception:
            pass
    return sorted(set(active))


def stop_router_if_no_active_clients(reason: str, quiet: bool = True) -> bool:
    active = active_router_client_pids()
    if active:
        router_log("INFO", f"router_lifetime_keep_alive reason={reason} active_clients={','.join(map(str, active))}")
        return False
    try:
        stopped = stop_router_with_guarantee(reason, quiet=quiet)
        router_log("INFO", f"router_lifetime_stopped reason={reason} stopped={stopped}")
        return stopped
    except Exception as exc:
        router_log("ERROR", f"router_lifetime_stop_failed reason={reason} error={type(exc).__name__}: {exc}")
        return False


def router_client_supervisor_interval_seconds() -> float:
    raw = os.environ.get("CLAUDE_ANY_ROUTER_SUPERVISOR_SECONDS", "2")
    try:
        value = float(raw)
    except (TypeError, ValueError):
        value = 2.0
    return max(0.5, min(30.0, value))


def ensure_managed_router_running_for_client() -> bool:
    if router_up():
        return True
    router_log("WARN", f"router_lifetime_restart reason=router_down_active_client base={ROUTER_BASE}")
    try:
        return bool(start_router_if_needed())
    except Exception as exc:
        router_log("ERROR", f"router_lifetime_restart_failed error={type(exc).__name__}: {exc}")
        return False


def start_router_client_supervisor(stop_event: threading.Event) -> threading.Thread:
    def watch() -> None:
        interval = router_client_supervisor_interval_seconds()
        while not stop_event.wait(interval):
            ensure_managed_router_running_for_client()

    thread = threading.Thread(target=watch, daemon=True, name="ca-router-client-supervisor")
    thread.start()
    return thread


def run_with_router_lifetime(runner: Callable[[], int], manage_router: bool) -> int:
    client_path: Path | None = None
    supervisor_stop: threading.Event | None = None
    if manage_router:
        try:
            client_path = register_router_client()
            supervisor_stop = threading.Event()
            start_router_client_supervisor(supervisor_stop)
        except Exception as exc:
            router_log("WARN", f"router_client_register_failed error={type(exc).__name__}: {exc}")
    try:
        return runner()
    finally:
        if supervisor_stop is not None:
            supervisor_stop.set()
        if manage_router:
            release_router_client(client_path)
            stop_router_if_no_active_clients("claude_exit", quiet=True)


def terminate_pid(pid: int, label: str, quiet: bool = False) -> bool:
    if not pid_is_running(pid):
        return False
    try:
        if os.name == "nt":
            subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=8)
        else:
            os.kill(pid, signal.SIGTERM)
            deadline = time.time() + 4
            while time.time() < deadline and pid_is_running(pid):
                time.sleep(0.1)
            if pid_is_running(pid):
                os.kill(pid, signal.SIGKILL)
        if not quiet:
            print(f"Stopped existing {label} session (pid {pid}).")
        return True
    except Exception as exc:
        if not quiet:
            print(f"Could not stop existing {label} session ({type(exc).__name__}).")
        return False


def terminate_pid_file(path: Path, label: str, quiet: bool = False) -> bool:
    if not path.exists():
        return False
    try:
        pid = int(path.read_text().strip())
    except Exception:
        try:
            path.unlink()
        except FileNotFoundError:
            pass
        return False
    stopped = terminate_pid(pid, label, quiet=quiet)
    if stopped or not pid_is_running(pid):
        try:
            path.unlink()
        except FileNotFoundError:
            pass
    return stopped


def windows_pids_on_port(port: int) -> list[int]:
    if os.name != "nt":
        return []
    try:
        proc = subprocess.run(
            ["netstat", "-ano", "-p", "tcp"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            timeout=5,
        )
    except Exception:
        return []
    pids: set[int] = set()
    marker = f":{port}"
    for line in proc.stdout.splitlines():
        if marker not in line or "LISTENING" not in line:
            continue
        parts = line.split()
        if len(parts) < 5:
            continue
        try:
            pids.add(int(parts[-1]))
        except ValueError:
            continue
    return sorted(pids)


def linux_procfs_pids_on_port(port: int) -> list[int]:
    if os.name == "nt":
        return []
    wanted_port = f"{int(port):04X}"
    inodes: set[str] = set()
    for table in (Path("/proc/net/tcp"), Path("/proc/net/tcp6")):
        try:
            lines = table.read_text(encoding="utf-8", errors="replace").splitlines()[1:]
        except Exception:
            continue
        for line in lines:
            parts = line.split()
            if len(parts) < 10:
                continue
            local_addr = parts[1]
            state = parts[3]
            inode = parts[9]
            if state != "0A" or ":" not in local_addr:
                continue
            if local_addr.rsplit(":", 1)[1].upper() == wanted_port and inode and inode != "0":
                inodes.add(inode)
    if not inodes:
        return []
    pids: set[int] = set()
    current = os.getpid()
    parent = os.getppid()
    try:
        proc_entries = list(Path("/proc").iterdir())
    except Exception:
        return []
    for entry in proc_entries:
        if not entry.name.isdigit():
            continue
        try:
            pid = int(entry.name)
        except ValueError:
            continue
        if pid in (current, parent):
            continue
        fd_dir = entry / "fd"
        try:
            fds = list(fd_dir.iterdir())
        except Exception:
            continue
        for fd in fds:
            try:
                target = os.readlink(fd)
            except Exception:
                continue
            match = re.match(r"socket:\[(\d+)\]$", target)
            if match and match.group(1) in inodes:
                pids.add(pid)
                break
    return sorted(pids)


def posix_pids_on_port(port: int) -> list[int]:
    if os.name == "nt":
        return []
    pids: set[int] = set(linux_procfs_pids_on_port(port))

    def add_ints(text: str, *, skip_port: bool = False) -> None:
        for value in re.findall(r"\b\d+\b", text or ""):
            try:
                pid = int(value)
            except ValueError:
                continue
            if skip_port and pid == port:
                continue
            pids.add(pid)

    commands: list[tuple[list[str], str]] = [
        (["lsof", "-nP", f"-iTCP:{port}", "-sTCP:LISTEN", "-t"], "plain"),
        (["fuser", "-n", "tcp", str(port)], "fuser"),
        (["ss", "-ltnp", f"sport = :{port}"], "ss"),
        (["netstat", "-ltnp"], "netstat"),
    ]
    for cmd, kind in commands:
        if not shutil.which(cmd[0]):
            continue
        try:
            proc = subprocess.run(
                cmd,
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                timeout=5,
            )
        except Exception:
            continue
        text = (proc.stdout or "") + "\n" + (proc.stderr or "")
        if kind == "plain":
            add_ints(text)
        elif kind == "fuser":
            add_ints(text.split(":", 1)[1] if ":" in text else text, skip_port=True)
        elif kind == "ss":
            for value in re.findall(r"pid=(\d+)", text):
                add_ints(value)
        else:
            for line in text.splitlines():
                if "LISTEN" not in line or f":{port}" not in line:
                    continue
                match = re.search(r"\s(\d+)/", line)
                if match:
                    add_ints(match.group(1))
    return sorted(pid for pid in pids if pid not in (os.getpid(), os.getppid()))


def terminate_posix_port(port: int, label: str, quiet: bool = False) -> bool:
    stopped = False
    pids = posix_pids_on_port(port)
    for pid in pids:
        stopped = terminate_pid(pid, label, quiet=True) or stopped
    if stopped and not quiet:
        print(f"Stopped existing {label} listener(s): {', '.join(map(str, pids))}.")
    return stopped


def router_port_listener_pids() -> list[int]:
    if os.name == "nt":
        return windows_pids_on_port(ROUTER_PORT)
    return posix_pids_on_port(ROUTER_PORT)


def terminate_router_health_pid(health: dict[str, Any] | None, quiet: bool = True) -> bool:
    if not isinstance(health, dict):
        return False
    if not router_health_config_matches_current(health):
        router_log(
            "INFO",
            "router_kill_skipped_foreign_config "
            f"running_config={health.get('config_dir') or '-'} current_config={CONFIG_DIR}",
        )
        return False
    try:
        pid = int(health.get("pid") or 0)
    except Exception:
        return False
    if pid in (os.getpid(), os.getppid()):
        return False
    return terminate_pid(pid, "claude-any router", quiet=quiet)


def ensure_router_port_available_for_spawn(
    reason: str,
    health: dict[str, Any] | None = None,
    max_wait_seconds: float = 5.0,
) -> None:
    """Clear the local router/MCP port before spawning a replacement router."""
    if router_health_has_foreign_config(health):
        raise RuntimeError(
            f"claude-any router port {ROUTER_PORT} is already used by another claude-any config "
            f"({health.get('config_dir')}). Set CLAUDE_ANY_ROUTER_PORT or CLAUDE_ANY_CONFIG_DIR "
            "for this instance instead of killing the other router."
        )
    stopped = terminate_router_health_pid(health, quiet=True)
    stopped = stop_router_processes(quiet=True) or stopped
    deadline = time.time() + max(0.1, max_wait_seconds)
    while time.time() < deadline:
        current_health = router_health()
        if router_health_has_foreign_config(current_health):
            raise RuntimeError(
                f"claude-any router port {ROUTER_PORT} is already used by another claude-any config "
                f"({current_health.get('config_dir')}). Set CLAUDE_ANY_ROUTER_PORT or "
                "CLAUDE_ANY_CONFIG_DIR for this instance instead of killing the other router."
            )
        if current_health is None and not router_port_listener_pids():
            router_log(
                "INFO",
                f"router_port_clear reason={reason} port={ROUTER_PORT} stopped={str(stopped).lower()}",
            )
            return
        if current_health is not None:
            terminate_router_health_pid(current_health, quiet=True)
        stop_router_processes(quiet=True)
        time.sleep(0.1)
    pids = router_port_listener_pids()
    current_health = router_health()
    health_desc = ""
    if isinstance(current_health, dict):
        health_desc = (
            f" version={current_health.get('version') or '-'}"
            f" source={current_health.get('source_fingerprint') or '-'}"
            f" pid={current_health.get('pid') or '-'}"
        )
    raise RuntimeError(
        f"stale claude-any router is still serving on {ROUTER_BASE}; "
        f"port {ROUTER_PORT} listener_pids={pids or '-'}{health_desc}; "
        "run `claude-any stop` and launch again."
    )


def terminate_windows_port(port: int, label: str, quiet: bool = False) -> bool:
    pids = windows_pids_on_port(port)
    stopped = False
    for pid in pids:
        if pid in (os.getpid(), os.getppid()):
            continue
        try:
            subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=8)
            stopped = True
        except Exception:
            pass
    if stopped and not quiet:
        print(f"Stopped existing {label} session(s): {', '.join(map(str, pids))}.")
    return stopped


def terminate_matching_processes(needles: list[str], label: str, quiet: bool = False) -> bool:
    if os.name == "nt":
        script = (
            "Get-CimInstance Win32_Process | "
            "Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"
        )
        try:
            p = subprocess.run(
                ["powershell", "-NoProfile", "-Command", script],
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                timeout=8,
            )
            rows = json.loads(p.stdout or "[]")
        except Exception:
            return False
        if isinstance(rows, dict):
            rows = [rows]
        current = os.getpid()
        matched: list[int] = []
        for row in rows if isinstance(rows, list) else []:
            try:
                pid = int(row.get("ProcessId"))
            except Exception:
                continue
            command = str(row.get("CommandLine") or "")
            if pid == current or pid == os.getppid() or not command:
                continue
            if all(needle in command for needle in needles):
                matched.append(pid)
        stopped = False
        for pid in matched:
            try:
                subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=8)
                stopped = True
            except Exception:
                pass
        if stopped and not quiet:
            print(f"Stopped existing {label} session(s): {', '.join(map(str, matched))}.")
        return stopped
    try:
        p = subprocess.run(
            ["ps", "-u", getpass.getuser(), "-o", "pid=,stat=,command="],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            timeout=5,
        )
    except Exception:
        return False
    current = os.getpid()
    matched: list[int] = []
    for line in p.stdout.splitlines():
        parts = line.strip().split(maxsplit=2)
        if len(parts) < 3:
            continue
        try:
            pid = int(parts[0])
        except ValueError:
            continue
        stat, command = parts[1], parts[2]
        if pid == current or pid == os.getppid() or stat.startswith("Z"):
            continue
        if all(needle in command for needle in needles):
            matched.append(pid)
    stopped = False
    for pid in matched:
        try:
            os.kill(pid, signal.SIGTERM)
            stopped = True
        except Exception:
            pass
    deadline = time.time() + 3
    while time.time() < deadline:
        alive = [pid for pid in matched if pid_is_running(pid)]
        if not alive:
            break
        time.sleep(0.1)
    for pid in matched:
        if pid_is_running(pid):
            try:
                os.kill(pid, signal.SIGKILL)
            except Exception:
                pass
    if stopped and not quiet:
        print(f"Stopped existing {label} session(s): {', '.join(map(str, matched))}.")
    return stopped


def stop_ncp_proxy(quiet: bool = False) -> bool:
    if os.name == "nt":
        port = positive_int(read_env_file(NCP_ENV).get("PROXY_PORT")) or 8788
        stopped = terminate_windows_port(port, "Nvidia NCP proxy", quiet=True)
        if stopped and not quiet:
            print("Stopped existing Nvidia NCP proxy session if one was running.")
        return stopped
    ncp = find_executable("ncp")
    stopped = False
    if not ncp:
        return terminate_matching_processes(["nvd_claude_proxy"], "Nvidia NCP proxy", quiet=quiet)
    try:
        subprocess.run([ncp, "kill"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10)
        stopped = True
    except Exception:
        pass
    stopped = terminate_matching_processes(["nvd-claude-proxy"], "Nvidia NCP proxy", quiet=True) or stopped
    stopped = terminate_matching_processes(["ncp", "proxy"], "Nvidia NCP proxy", quiet=True) or stopped
    stopped = terminate_matching_processes(["nvd_claude_proxy"], "Nvidia NCP proxy", quiet=True) or stopped
    if stopped and not quiet:
        print("Stopped existing Nvidia NCP proxy session if one was running.")
    return stopped


def stop_router_processes(quiet: bool = False) -> bool:
    stopped = terminate_pid_file(PID_PATH, "claude-any router", quiet=quiet)
    health = router_health()
    if router_health_has_foreign_config(health):
        router_log(
            "INFO",
            "router_stop_skipped_foreign_config "
            f"running_config={health.get('config_dir') or '-'} current_config={CONFIG_DIR}",
        )
        return stopped
    if router_health_config_matches_current(health):
        stopped = terminate_router_health_pid(health, quiet=True) or stopped
    return stopped


def stop_router_with_guarantee(reason: str, max_wait_seconds: float = 5.0, quiet: bool = True) -> bool:
    """Kill the claude-any router and verify it actually stopped.

    Unlike ``stop_router_processes`` (which signals termination and returns
    without checking), this polls ``router_up()`` until the deadline. Raises
    ``RuntimeError`` if the router is still serving after ``max_wait_seconds``.

    Used by Claude Native mode launches so the user has a hard guarantee that
    no claude-any router process can intercept the subsequent ``claude`` call.
    """
    initial_health = router_health()
    if initial_health is None:
        router_log("INFO", f"router_kill_guarantee reason={reason} state=already_down")
        return False
    if router_health_has_foreign_config(initial_health):
        router_log(
            "INFO",
            "router_kill_guarantee_skipped_foreign_config "
            f"reason={reason} running_config={initial_health.get('config_dir') or '-'} current_config={CONFIG_DIR}",
        )
        return False
    stop_router_processes(quiet=quiet)
    deadline = time.time() + max(0.1, max_wait_seconds)
    while time.time() < deadline:
        health = router_health()
        if router_health_has_foreign_config(health):
            router_log(
                "INFO",
                "router_kill_guarantee_skipped_foreign_config "
                f"reason={reason} running_config={health.get('config_dir') or '-'} current_config={CONFIG_DIR}",
            )
            return False
        if health is None:
            elapsed_ms = int((max_wait_seconds - (deadline - time.time())) * 1000)
            router_log("INFO", f"router_kill_guarantee reason={reason} state=killed elapsed_ms={elapsed_ms}")
            return True
        time.sleep(0.1)
    router_log("ERROR", f"router_kill_guarantee reason={reason} state=still_up_after_{max_wait_seconds:.1f}s")
    raise RuntimeError(
        f"claude-any router is still serving on {ROUTER_BASE} after a {max_wait_seconds:.1f}s "
        f"shutdown attempt for '{reason}'. Aborting to prevent the subsequent claude launch "
        f"from accidentally routing through it. Investigate the PID at {PID_PATH} or use "
        f"`claude-any stop` manually."
    )


def cleanup_managed_services_for_provider(provider: str, pcfg: dict[str, Any], cfg: dict[str, Any], quiet: bool = False) -> None:
    if direct_native_anthropic_enabled(provider, pcfg):
        # Claude Native mode strips claude-any routing env before spawning
        # `claude`. Clean up only this config's idle router; do not kill a
        # different folder/config or an active routed session.
        stop_router_if_no_active_clients("native_anthropic_launch", quiet=quiet)
        if provider != "nvidia-hosted" or provider_native_compat_enabled(provider, pcfg):
            stop_ncp_proxy(quiet=quiet)
        return
    if not cfg.get("cleanup", {}).get("managed_services_on_launch", True):
        return
    if provider != "nvidia-hosted" or provider_native_compat_enabled(provider, pcfg):
        stop_ncp_proxy(quiet=quiet)


def default_base_url(provider: str) -> str:
    return {
        "anthropic": "https://api.anthropic.com",
        "ollama": "http://your-ollama:11434",
        "ollama-cloud": "https://ollama.com",
        "deepseek": "https://api.deepseek.com/anthropic",
        "opencode": OPENCODE_ZEN_BASE_URL,
        "opencode-go": OPENCODE_GO_BASE_URL,
        "vllm": "http://your-vllm:8000",
        "lm-studio": "http://127.0.0.1:1234/v1",
        "nvidia-hosted": nvidia_upstream_base_url(),
        "self-hosted-nim": "http://your-nim:8000",
        "openrouter": "https://openrouter.ai/api/v1",
        "fireworks": FIREWORKS_INFERENCE_BASE_URL,
    }.get(provider, "http://localhost:8000")


def meaningful_key(value: str | None) -> bool:
    return meaningful_key_value(value)


def api_key_status_line(provider: str, pcfg: dict[str, Any]) -> str:
    key_count = provider_api_key_count(provider, pcfg)
    round_robin = f"{key_count} keys, round-robin" if key_count > 1 else ""
    primary = provider_primary_api_key(provider, pcfg)
    primary_detail = f"; primary {mask_secret(primary)}; fp {secret_fingerprint(primary)}" if key_count else ""
    if provider == "nvidia-hosted":
        if key_count > 1:
            return f"API keys: {round_robin} (NVIDIA{primary_detail})"
        return f"API key: set (NVIDIA{primary_detail})" if key_count else "API key: missing (NVIDIA required)"
    if provider == "anthropic":
        if anthropic_routed_enabled(provider, pcfg):
            if key_count > 1:
                return f"API keys: {round_robin} (Anthropic routed{primary_detail})"
            return f"API key: set (Anthropic routed{primary_detail})" if key_count else "API key: not set (uses Claude Code OAuth/API auth headers)"
        if key_count > 1:
            return f"API keys: {round_robin} (Anthropic{primary_detail})"
        return f"API key: set (Anthropic{primary_detail})" if key_count else "API key: not set (use API key or Claude login)"
    if provider == "ollama-cloud":
        if key_count > 1:
            return f"API keys: {round_robin} (Ollama Cloud{primary_detail})"
        return f"API key: set (Ollama Cloud{primary_detail})" if key_count else "API key: missing (Ollama Cloud required)"
    if provider == "deepseek":
        if key_count > 1:
            return f"API keys: {round_robin} (DeepSeek{primary_detail})"
        return f"API key: set (DeepSeek{primary_detail})" if key_count else "API key: missing (DeepSeek required)"
    if provider in OPENCODE_PROVIDER_NAMES:
        label = PROVIDER_LABELS.get(provider, provider)
        if key_count > 1:
            return f"API keys: {round_robin} ({label}{primary_detail})"
        return f"API key: set ({label}{primary_detail})" if key_count else f"API key: missing ({label} required)"
    if provider == "fireworks":
        if key_count > 1:
            return f"API keys: {round_robin} (Fireworks.ai{primary_detail})"
        return f"API key: set (Fireworks.ai{primary_detail})" if key_count else "API key: missing (Fireworks.ai required)"
    if key_count:
        if key_count > 1:
            return f"API keys: {round_robin} ({primary_detail.lstrip('; ')})"
        return f"API key: set ({primary_detail.lstrip('; ')})"
    if provider == "ollama":
        return "API key: not required for Ollama"
    return "API key: optional or not configured"


def base_url_status_line(provider: str, pcfg: dict[str, Any]) -> str:
    base = (pcfg.get("base_url") or "").rstrip("/")
    if not base:
        return "Base URL: missing"
    if "your-" in base:
        return f"Base URL: placeholder ({base})"
    if provider == "nvidia-hosted":
        if nvidia_hosted_native_compat_enabled(provider, pcfg):
            return f"Base URL: NVIDIA hosted native ({native_anthropic_base_url(provider, pcfg)}/v1/messages)"
        state = "ready" if router_up() else "starts on launch"
        return f"Base URL: NVIDIA hosted ({base}); local router {ROUTER_BASE} {state}"
    if provider == "deepseek":
        return f"Base URL: DeepSeek Anthropic API configured ({base})"
    if provider in OPENCODE_PROVIDER_NAMES:
        label = PROVIDER_LABELS.get(provider, provider)
        path = "/v1/models"
        headers = provider_model_list_headers(provider, pcfg)
        try:
            data = http_json(join_url(base, path), headers=headers, timeout=2.5)
            count = len(model_ids_from_response(data))
            return f"Base URL: {label} model list reachable ({path}, {count} models)"
        except urllib.error.HTTPError as exc:
            if exc.code in (401, 403):
                return f"Base URL: {label} reachable, auth rejected ({exc.code})"
            return f"Base URL: {label} HTTP {exc.code}"
        except Exception as exc:
            return f"Base URL: {label} unreachable ({type(exc).__name__})"
    if provider == "fireworks":
        label = PROVIDER_LABELS.get(provider, provider)
        account_id = fireworks_account_id(pcfg)
        path = f"/v1/accounts/{urllib.parse.quote(account_id, safe='')}/models?pageSize=1"
        headers = provider_model_list_headers(provider, pcfg)
        try:
            data = http_json(join_url(fireworks_management_base_url(pcfg), path), headers=headers, timeout=2.5, provider=provider, pcfg=pcfg)
            count = len(model_ids_from_response(data))
            return f"Base URL: {label} model API reachable ({path}, {count} sampled)"
        except urllib.error.HTTPError as exc:
            if exc.code in (401, 403):
                return f"Base URL: {label} reachable, auth rejected ({exc.code})"
            return f"Base URL: {label} HTTP {exc.code}"
        except Exception as exc:
            return f"Base URL: {label} model API unreachable ({type(exc).__name__})"
    path = "/api/tags" if provider in ("ollama", "ollama-cloud") else "/v1/models"
    headers: dict[str, str] = {}
    key = provider_primary_api_key(provider, pcfg)
    if meaningful_key(key):
        headers = {"x-api-key": key, "authorization": f"Bearer {key}"}
    headers = with_upstream_user_agent(headers)
    try:
        req = urllib.request.Request(join_url(base, path), headers=headers)
        with provider_urlopen(req, timeout=2.5, provider=provider, pcfg=pcfg) as resp:
            body = resp.read(131072).decode("utf-8", errors="ignore")
        count = ""
        try:
            data = json.loads(body)
            if provider in ("ollama", "ollama-cloud"):
                count = f", {len(data.get('models', []))} models"
            elif isinstance(data.get("data"), list):
                count = f", {len(data['data'])} models"
                limit = upstream_model_context_limit(provider, pcfg, timeout=1.0)
                if limit:
                    count += f", max_model_len {limit}"
        except Exception:
            pass
        return f"Base URL: model list reachable ({path}{count})"
    except urllib.error.HTTPError as exc:
        if exc.code in (401, 403):
            return f"Base URL: model list reachable, auth rejected ({exc.code})"
        return f"Base URL: HTTP {exc.code}"
    except Exception as exc:
        if provider == "nvidia-hosted" and "127.0.0.1" in base:
            return "Base URL: proxy down; starts on launch"
        return f"Base URL: unreachable ({type(exc).__name__})"


def preflight_lines() -> list[str]:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    lang = cfg.get("language", "en")
    notes = PROVIDER_NOTES.get(lang, PROVIDER_NOTES["en"]).get(provider, [])
    return [
        base_url_status_line(provider, pcfg),
        api_key_status_line(provider, pcfg),
        *notes,
    ]


def launch_readiness_errors(cfg: dict[str, Any] | None = None) -> list[str]:
    cfg = cfg or load_config()
    provider, pcfg = get_current_provider(cfg)
    if direct_native_anthropic_enabled(provider, pcfg):
        return []
    status = base_url_status_line(provider, pcfg)
    low = status.lower()
    errors: list[str] = []
    if any(marker in low for marker in ("unreachable", "placeholder", "missing")):
        errors.append(f"Launch blocked: {status}")
        if provider == "vllm":
            errors.append("vLLM must be reachable from this machine and expose Anthropic-compatible /v1/messages.")
        elif provider in ("ollama", "ollama-cloud"):
            errors.append("Start Ollama or set a reachable Base URL before launching Claude Code.")
        elif provider == "self-hosted-nim":
            errors.append("Start NIM or set a reachable Anthropic-compatible Base URL before launching Claude Code.")
        elif provider == "lm-studio":
            errors.append("Start LM Studio's Local Server or set a reachable Anthropic-compatible Base URL before launching Claude Code.")
        else:
            errors.append("Set a reachable Base URL before launching Claude Code.")
    if provider == "nvidia-hosted" and not provider_has_api_key(provider, pcfg):
        errors.append("Launch blocked: NVIDIA hosted requires an NVIDIA API key.")
    if provider == "ollama-cloud" and not provider_has_api_key(provider, pcfg):
        errors.append("Launch blocked: Ollama Cloud requires an API key.")
    if provider == "deepseek" and not provider_has_api_key(provider, pcfg):
        errors.append("Launch blocked: DeepSeek.com requires a DeepSeek API key.")
    if provider == "openrouter" and not provider_has_api_key(provider, pcfg):
        errors.append("Launch blocked: OpenRouter requires an OpenRouter API key.")
    if provider == "fireworks" and not provider_has_api_key(provider, pcfg):
        errors.append("Launch blocked: Fireworks.ai requires a Fireworks API key.")
    if provider in OPENCODE_PROVIDER_NAMES and not provider_has_api_key(provider, pcfg):
        label = PROVIDER_LABELS.get(provider, provider)
        errors.append(f"Launch blocked: {label} requires a {label} API key.")
    if claude_code_ultracode_enabled(provider, pcfg):
        caps = set(claude_code_supported_capabilities(provider, pcfg, current_upstream_model_id(provider, pcfg)))
        if "xhigh_effort" not in caps:
            errors.append(
                "Launch blocked: ultracode requires a Claude Code model capability set that includes xhigh_effort. "
                "Use a compatible Claude model or set claude_code_supported_capabilities after verifying the provider/model supports xhigh workflow thinking."
            )
    if provider == "lm-studio":
        try:
            ensure_lm_studio_model_loaded_for_context(pcfg, timeout=1.5)
            save_config(cfg)
        except Exception as exc:
            errors.append(
                "Launch blocked: Claude Any could not automatically load the selected LM Studio model "
                f"with the recommended context ({type(exc).__name__}: {exc})."
            )
            return errors
        info = upstream_model_runtime_info(provider, pcfg, timeout=1.5)
        loaded = positive_int(info.get("loaded_context_len")) if info else None
        state = str(info.get("state") or "") if info else ""
        if loaded and loaded < LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
            errors.append(
                "Launch blocked: LM Studio loaded context is "
                f"{loaded:,} tokens; Claude Code needs at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,}. "
                "Reload the model with a larger context length."
            )
        elif state and state != "loaded":
            errors.append(
                "Launch blocked: selected LM Studio model is not loaded, so the active context length cannot be verified. "
                f"Load it with at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,} context tokens."
            )
    return errors


def launch_blockers_require_api_key(blockers: list[str]) -> bool:
    return any("requires" in line.lower() and "api key" in line.lower() for line in blockers)


def settings_ready_except_api_key() -> bool:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    base = pcfg.get("base_url", "")
    model = pcfg.get("current_model", "")
    return bool(provider and base and model and "your-" not in base)


def self_cmd(args: list[str]) -> tuple[int, str]:
    p = subprocess.run(
        [sys.executable, str(Path(__file__).resolve()), *args],
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    )
    return p.returncode, p.stdout


def enable_ansi() -> None:
    if os.name == "nt":
        os.system("")


def ansi(text: str, code: str) -> str:
    return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text


ANIMATED_TEXT_PALETTE = (203, 209, 215, 221, 229, 187, 151, 116, 111, 147, 183, 219)


def animated_ansi_text(text: str, *, phase: int | None = None, bold: bool = True) -> str:
    if not sys.stdout.isatty():
        return text
    if phase is None:
        phase = int(time.monotonic() * 8)
    parts: list[str] = []
    for i, ch in enumerate(text):
        if ch.isspace():
            parts.append(ch)
            continue
        code = f"38;5;{ANIMATED_TEXT_PALETTE[(phase + i) % len(ANIMATED_TEXT_PALETTE)]}"
        if bold:
            code = "1;" + code
        parts.append(ansi(ch, code))
    return "".join(parts)


def cell_width(text: str) -> int:
    width = 0
    for ch in text:
        if unicodedata.combining(ch):
            continue
        width += 2 if unicodedata.east_asian_width(ch) in ("F", "W") else 1
    return width


def fit_cells(value: Any, width: int) -> str:
    text = str(value if value is not None else "")
    width = max(1, width)
    if cell_width(text) <= width:
        return text
    suffix = "..." if width >= 4 else ""
    limit = max(1, width - cell_width(suffix))
    out: list[str] = []
    used = 0
    for ch in text:
        ch_width = 0 if unicodedata.combining(ch) else (2 if unicodedata.east_asian_width(ch) in ("F", "W") else 1)
        if used + ch_width > limit:
            break
        out.append(ch)
        used += ch_width
    return "".join(out) + suffix


def pad_cells(value: Any, width: int) -> str:
    text = fit_cells(value, width)
    return text + (" " * max(0, width - cell_width(text)))


def color_line(text: str, code: str, width: int) -> str:
    fitted = fit_cells(text, width)
    return ansi(fitted, code)


def clean_render_lines(lines: list[str], width: int) -> list[str]:
    # All menu rows must stay single-line. Windows cmd corrupts redraws after
    # implicit line wrapping, even when ANSI clear-to-end is used.
    return [fit_cells(line, width) for line in lines]


def clear_screen() -> None:
    if sys.stdout.isatty():
        print("\033[2J\033[H", end="")


def intro_panel_lines(width: int) -> list[str]:
    width = max(48, min(width, 120))
    line = "-" * (width - 2)
    lines = [f"+{line}+"]
    title = f" {APP_NAME} "
    lines.append(f"|{title}{' ' * max(0, width - len(title) - 2)}|")
    if width >= 92:
        left_w = 39
        right_w = width - left_w - 4
        rows = [
            ("Welcome back!", "Tips for getting started"),
            ("", "Choose provider, model, base URL, and API key before launch."),
            ("   CLAUDE", "Routes Claude Code to Anthropic, Ollama, vLLM, Nvidia, or NIM."),
            ("      ANY", "Adds DuckDuckGo web search tooling for non-native providers."),
            (CREDITS, "Use --ca-* flags for headless runs; Claude flags pass through."),
        ]
        for left, right in rows:
            left_text = left[:left_w].ljust(left_w)
            right_text = right[:right_w].ljust(right_w)
            lines.append(f"| {left_text} | {right_text}|")
    else:
        rows = [
            f"{APP_NAME} routes Claude Code through selectable providers.",
            "Anthropic, Ollama, vLLM, Nvidia Hosted, and self-hosted NIM.",
            "DuckDuckGo web search is attached for non-native providers.",
            "Headless setup uses --ca-* flags; Claude flags pass through.",
            CREDITS,
        ]
        for row in rows:
            lines.append(f"| {row[: width - 4].ljust(width - 4)} |")
    lines.append(f"+{line}+")
    return lines


def print_intro_panel(width: int) -> None:
    print("\n".join(intro_panel_lines(width)))


def append_menu_key_debug_log(line: str) -> None:
    try:
        MENU_KEY_DEBUG_PATH.parent.mkdir(parents=True, exist_ok=True)
        with MENU_KEY_DEBUG_PATH.open("a", encoding="utf-8") as f:
            f.write(line)
    except OSError:
        pass


def read_menu_key(fd: int | None = None) -> str:
    if os.name == "nt":
        import msvcrt
        ch = msvcrt.getwch()
        if ch in ("\x00", "\xe0"):
            code = msvcrt.getwch()
            return {"H": "up", "P": "down", "K": "left", "M": "right"}.get(code, "")
        if ch in ("\r", "\n"):
            return "enter"
        if ch == "\x1b":
            return "esc"
        return ch.lower()

    import time
    if fd is None or fd < 0:
        fd = sys.stdin.fileno()
    ch = os.read(fd, 1)
    log = f"{time.time():.3f} first={ch!r}"
    if ch == b"\x1b":
        seq = ch.decode("latin-1")
        b = os.read(fd, 1)
        log += f" next={b!r}"
        if not b:
            append_menu_key_debug_log(log + " result='esc'\n")
            return "esc"
        seq += b.decode("latin-1")
        if b == b"[":
            while True:
                b = os.read(fd, 1)
                log += f" next={b!r}"
                if not b:
                    break
                seq += b.decode("latin-1")
                if 0x40 <= b[0] <= 0x7E:
                    break
        elif b == b"O":
            b = os.read(fd, 1)
            log += f" next={b!r}"
            if b:
                seq += b.decode("latin-1")
        result = {
            "\x1b[A": "up", "\x1b[B": "down", "\x1b[D": "left", "\x1b[C": "right",
            "\x1b[5~": "pageup", "\x1b[6~": "pagedown",
            "\x1b[H": "home", "\x1b[F": "end",
        }.get(seq, "esc")
        log += f" seq={seq!r} result={result!r}"
        append_menu_key_debug_log(log + "\n")
        return result
    if ch in (b"\r", b"\n"):
        result = "enter"
    else:
        result = ch.decode("latin-1").lower()
    append_menu_key_debug_log(log + f" result={result!r}\n")
    return result


def portable_select(
    title: str,
    rows: list[str],
    current: int = 0,
    footer: str = "",
    info_lines: list[str] | None = None,
    show_intro: bool = False,
) -> int | None:
    enable_ansi()
    idx = max(0, min(current, len(rows) - 1))
    status_cache = status_lines()[:5]
    out_fd = sys.stdout.fileno()
    out_is_tty = os.isatty(out_fd) if os.name != "nt" else True
    if out_is_tty:
        sys.stdout.write("\033[?25l")
        sys.stdout.flush()
    fd = sys.stdin.fileno()
    old_settings = None
    in_is_tty = os.isatty(fd) if os.name != "nt" else False
    if in_is_tty:
        try:
            import termios
            old_settings = termios.tcgetattr(fd)
            new = termios.tcgetattr(fd)
            new[3] = new[3] & ~(termios.ECHO | termios.ICANON)
            new[6][termios.VMIN] = 1
            new[6][termios.VTIME] = 0
            termios.tcsetattr(fd, termios.TCSANOW, new)
        except Exception:
            fd = -1
    try:
        while True:
            screen: list[str] = []
            columns = shutil.get_terminal_size((100, 24)).columns
            if show_intro:
                screen.extend(intro_panel_lines(columns))
            screen.append(ansi(title, "1"))
            for line in status_cache:
                color = "32" if line.startswith(("provider:", "model:")) else "2"
                screen.append("  " + ansi(line, color))
            screen.append("")
            for i, row in enumerate(rows):
                prefix = "> " if i == idx else "  "
                text = prefix + row
                if i == idx:
                    screen.append(ansi(text, "7;1"))
                elif row.startswith(("Quit", "종료", "終了", "退出")):
                    screen.append(ansi(text, "31"))
                elif "Launch" in row or "실행" in row or "起動" in row or "启动" in row:
                    screen.append(ansi(text, "32;1"))
                else:
                    screen.append(text)
            if info_lines:
                screen.append("")
                screen.append(ansi("-" * min(120, max(72, columns - 4)), "38;5;208"))
                for line in info_lines:
                    screen.append(ansi(line, "1;38;5;208"))
            screen.append("")
            screen.append(ansi(footer or "Up/Down moves. Enter selects. Esc/q cancels.", "2"))
            rendered = "\n".join(screen) + "\n"
            sys.stdout.write("\033[2J\033[H" + rendered)
            sys.stdout.flush()
            key = read_menu_key(fd) if fd >= 0 else read_menu_key()
            if key in ("up", "k"):
                idx = (idx - 1) % len(rows)
            elif key in ("down", "j"):
                idx = (idx + 1) % len(rows)
            elif key == "enter":
                return idx
            elif key in ("esc", "q"):
                return None
    finally:
        if old_settings is not None:
            try:
                import termios
                termios.tcsetattr(fd, termios.TCSANOW, old_settings)
            except Exception:
                pass
        sys.stdout.write("\033[?25h")
        sys.stdout.flush()


def pause() -> None:
    input("Press Enter to continue...")


def compact_text(value: Any, width: int = 72) -> str:
    return fit_cells(value, width)


def provider_menu_label(provider: str, pcfg: dict[str, Any]) -> str:
    if provider == "anthropic":
        return "Anthropic routed" if anthropic_routed_enabled(provider, pcfg) else "Claude Native"
    return PROVIDER_LABELS.get(provider, provider)


def current_provider_panel_choice(provider: str, pcfg: dict[str, Any]) -> str:
    if provider == "anthropic":
        return ANTHROPIC_ROUTED_PROVIDER_CHOICE if anthropic_routed_enabled(provider, pcfg) else ANTHROPIC_NATIVE_PROVIDER_CHOICE
    return provider


def main_menu_rows(cfg: dict[str, Any], provider: str, pcfg: dict[str, Any], lang: str) -> list[str]:
    return [
        f"0. {ui_text('language', lang)}  [{LANGUAGES.get(lang, lang)}]",
        f"1. {ui_text('provider', lang)}  [{provider_menu_label(provider, pcfg)}]",
        f"2. {ui_text('api_key', lang)}  [{stored_api_key_mask(provider, pcfg)}]",
        f"3. {ui_text('base_url', lang)}  [{compact_text(pcfg.get('base_url', 'unset'), 62)}]",
        f"4. {ui_text('model', lang)}  [{compact_text(pcfg.get('current_model', 'unset'), 62)}]",
        f"5. {ui_text('advisor_model', lang)}  [{'Claude Code native /advisor' if provider == 'anthropic' else compact_text(pcfg.get('advisor_model') or 'off', 62)}]",
        f"6. {ui_text('options', lang)}  [{compact_text(llm_options_status(provider, pcfg), 62)}]",
        f"7. {ui_text('log_level', lang)}  [{log_level_status()}]",
        f"8. {ui_text('test', lang)}",
        f"9. {ui_text('launch', lang)}",
        ui_text("quit", lang),
    ]


def provider_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    rows: list[str] = []
    values: list[str] = []
    current = cfg.get("current_provider", "nvidia-hosted")
    for key, label in PROVIDER_LABELS.items():
        pcfg = cfg.get("providers", {}).get(key, {})
        if key == "anthropic":
            routed = anthropic_routed_enabled(key, pcfg)
            native_mark = "*" if current == key and not routed else " "
            routed_mark = "*" if current == key and routed else " "
            rows.append(f"{native_mark} {'Claude Native':<16} {'anthropic-native':<17} {compact_text(pcfg.get('base_url', ''), 52)}")
            values.append(ANTHROPIC_NATIVE_PROVIDER_CHOICE)
            suffix = "router via Claude Code auth" if not provider_has_api_key(key, pcfg) else "router features"
            rows.append(f"{routed_mark} {'Anthropic routed':<16} {'anthropic-routed':<17} {suffix}")
            values.append(ANTHROPIC_ROUTED_PROVIDER_CHOICE)
            continue
        mark = "*" if key == current else " "
        rows.append(f"{mark} {label:<16} {key:<15} {compact_text(pcfg.get('base_url', ''), 54)}")
        values.append(key)
    return rows, values


def language_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    rows: list[str] = []
    values: list[str] = []
    current = cfg.get("language", "en")
    for code, label in LANGUAGES.items():
        mark = "*" if code == current else " "
        rows.append(f"{mark} {code:<2} {label}")
        values.append(code)
    return rows, values


def log_level_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    rows: list[str] = []
    values: list[str] = []
    current = log_level_name()
    descriptions = {
        "SILENT": "no router log writes",
        "ERROR": "errors only",
        "WARN": "warnings and errors",
        "INFO": "normal diagnostics",
        "DEBUG": "verbose diagnostics",
        "TRACE": "request/response trace detail",
    }
    for numeric in sorted(LOG_LEVEL_NAMES):
        name = LOG_LEVEL_NAMES[numeric]
        mark = "*" if name == current else " "
        rows.append(f"{mark} {name:<6} {numeric}  {descriptions.get(name, '')}")
        values.append(name)
    rows.append(f"Reset to default/env  [{log_level_status()}]")
    values.append("DEFAULT")
    rows.append(ui_text("back", cfg.get("language", "en")))
    values.append("back")
    return rows, values


def model_panel_rows(
    provider: str,
    pcfg: dict[str, Any],
    fetch: bool = True,
    force_refresh: bool = False,
) -> tuple[list[str], list[str]]:
    values = unique_model_ids(
        provider,
        upstream_model_ids(provider, pcfg, force_refresh=force_refresh)
        if fetch else cached_or_configured_model_ids(provider, pcfg),
    )
    rows: list[str] = []
    current = pcfg.get("current_model")
    seen_aliases: set[str] = set()
    deduped_values: list[str] = []
    cache = read_model_list_cache(provider, pcfg)
    cached_info = read_model_info_cache(provider, pcfg)
    rows.append("Refresh provider model list..." if cache is None else "Refresh provider model list")
    deduped_values.append("__refresh_models__")
    for mid in values:
        alias = alias_for(provider, mid)
        suffix = ""
        info = cached_info.get(normalize_model_id(provider, mid), {})
        max_context = positive_int(info.get("max_model_len"))
        if max_context:
            suffix += f"  [ctx {format_context_tokens(max_context)}]"
        parameter_count = format_parameter_count(info.get("parameter_count"))
        if parameter_count:
            suffix += f"  [{parameter_count} params]"
        if provider in OPENCODE_PROVIDER_NAMES:
            suffix += f"  [{opencode_endpoint_display(provider, mid, pcfg)}]"
        alias_key = alias.casefold()
        if alias_key in seen_aliases:
            continue
        seen_aliases.add(alias_key)
        deduped_values.append(mid)
        mark = "*" if mid == current else " "
        rows.append(f"{mark} {mid}  {alias}{suffix}")
    rows.append("+ Custom model id...")
    deduped_values.append("__custom__")
    rows.append("Back")
    deduped_values.append("back")
    return rows, deduped_values


def advisor_model_panel_rows(
    provider: str,
    pcfg: dict[str, Any],
    fetch: bool = True,
    force_refresh: bool = False,
) -> tuple[list[str], list[str]]:
    if provider == "anthropic":
        return (
            [
                "Claude native and Anthropic routed sessions use Claude Code's",
                "built-in /advisor (run /advisor in the session to pick its model).",
                "Back",
            ],
            ["back", "back", "back"],
        )
    current = normalize_model_id(provider, pcfg.get("advisor_model", ""))
    values = unique_model_ids(
        provider,
        (
            upstream_model_ids(provider, pcfg, force_refresh=force_refresh)
            if fetch else cached_or_configured_model_ids(provider, pcfg)
        )
        + ([current] if current else []),
    )
    rows: list[str] = []
    rows.append(("* Disable Advisor Model" if not current else "  Disable Advisor Model"))
    deduped_values = [""]
    cache = read_model_list_cache(provider, pcfg)
    rows.append("Refresh provider model list..." if cache is None else "Refresh provider model list")
    deduped_values.append("__refresh_models__")
    seen: set[str] = set()
    for mid in values:
        if not mid or mid in seen:
            continue
        seen.add(mid)
        mark = "*" if mid == current else " "
        suffix = "  recommended for long context" if mid == "deepseek-v4-pro" else ""
        rows.append(f"{mark} {mid}{suffix}")
        deduped_values.append(mid)
    rows.append("+ Custom advisor model id...")
    deduped_values.append("__custom__")
    rows.append("Back")
    deduped_values.append("back")
    return rows, deduped_values


def channel_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    channels = set(channel_specs(cfg))
    cache = read_channel_probe_cache()
    records = cache.get("servers") or []
    if not any(isinstance(r, dict) and r.get("name") == "claude-any-router" for r in records):
        records = [_builtin_router_probe_record(), *records]
    probed_at = cache.get("probed_at") or 0
    capable_records = [r for r in records if channel_probe_record_bucket(r) == "capable"]
    non_capable_records = [r for r in records if channel_probe_record_bucket(r) == "non_capable"]
    inconclusive_records = [r for r in records if channel_probe_record_bucket(r) == "inconclusive"]
    skipped_records = [r for r in records if channel_probe_record_bucket(r) == "skipped"]

    rows: list[str] = []
    values: list[str] = []

    rows.append("[Auto-detected channel-capable]")
    values.append("__heading__")
    if capable_records:
        for r in capable_records:
            name = str(r.get("name") or "")
            spec = f"server:{name}"
            mark = "*" if spec in channels else " "
            transport = str(r.get("transport") or "?")
            source = str(r.get("source_path") or "")
            if source == "<built-in>":
                rows.append(f"{mark} {name:<14} ({transport}, built-in)")
            else:
                rows.append(f"{mark} {name:<14} ({transport})")
            values.append(spec)
    else:
        hint = "press Re-probe now" if not probed_at else "none capable"
        rows.append(f"  ({hint})")
        values.append("__noop__")

    if non_capable_records:
        rows.append("[Detected but not channel-capable]")
        values.append("__heading__")
        for r in non_capable_records:
            name = str(r.get("name") or "")
            transport = str(r.get("transport") or "?")
            reason = str(r.get("reason") or "-")
            rows.append(f"  {name:<14} ({transport}) {reason}")
            values.append("__noop__")

    if inconclusive_records:
        rows.append("[Probe inconclusive / selectable anyway]")
        values.append("__heading__")
        for r in inconclusive_records:
            name = str(r.get("name") or "")
            spec = f"server:{name}"
            mark = "*" if spec in channels else " "
            transport = str(r.get("transport") or "?")
            reason = str(r.get("reason") or "-")
            rows.append(f"{mark} {name:<14} ({transport}) {reason}  [select anyway]")
            values.append(spec)

    if skipped_records:
        rows.append("[Not probed]")
        values.append("__heading__")
        for r in skipped_records:
            name = str(r.get("name") or "")
            transport = str(r.get("transport") or "?")
            reason = str(r.get("reason") or "-")
            rows.append(f"  {name:<14} ({transport}) {reason}")
            values.append("__noop__")

    rows.append("[Official plugins]")
    values.append("__heading__")
    for plugin_name, spec in OFFICIAL_CHANNEL_PLUGINS.items():
        mark = "*" if spec in channels else " "
        rows.append(f"{mark} {plugin_name:<10} {spec}")
        values.append(spec)

    covered: set[str] = set(OFFICIAL_CHANNEL_PLUGINS.values())
    for r in records:
        covered.add(f"server:{r.get('name')}")
    custom_specs = [spec for spec in channel_specs(cfg) if spec not in covered]
    if custom_specs:
        rows.append("[Configured custom / imported]")
        values.append("__heading__")
        for spec in custom_specs:
            rows.append(f"* {spec}")
            values.append(spec)

    rows.append("[Actions]")
    values.append("__heading__")
    if probed_at:
        ts_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(probed_at))
        rows.append(f"Re-probe now (last: {ts_str})")
    else:
        rows.append("Re-probe now (no cache yet)")
    values.append("__reprobe__")
    rows.append("+ Add custom channel...")
    values.append("__add_custom__")
    if channels:
        rows.append("- Remove channel...")
        values.append("__remove__")
        rows.append("Clear all channels")
        values.append("__clear__")
    rows.append("Back")
    values.append("back")
    return rows, values


def _channel_panel_first_selectable(values: list[str]) -> int:
    for idx, value in enumerate(values):
        if value not in ("__heading__", "__noop__"):
            return idx
    return 0


def _channel_panel_step(values: list[str], start: int, delta: int) -> int:
    if not values:
        return 0
    n = len(values)
    idx = start
    for _ in range(n):
        idx = (idx + delta) % n
        if values[idx] not in ("__heading__", "__noop__"):
            return idx
    return start


def channel_delivery_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    current = channel_delivery_mode(cfg)
    rows = [
        f"{'*' if current == 'llm' else ' '} llm    inject channel messages into next model request",
        f"{'*' if current == 'native' else ' '} native Claude Code claude/channel bridge; requires Channels",
        f"{'*' if current == 'stdin' else ' '} stdin  PTY wake proxy; terminal input fallback",
        "Back",
    ]
    return rows, ["llm", "native", "stdin", "back"]


def api_key_panel_rows(provider: str, pcfg: dict[str, Any] | None = None) -> tuple[list[str], list[str]]:
    rows = [
        "Type or paste API key as hidden input",
        "Type or paste multiple API keys (comma/newline separated)",
        "Read API key from an environment variable",
        "Read API keys from an environment variable",
        "Read API key from clipboard",
        "Read API keys from clipboard",
        "Back",
    ]
    values = ["input", "multi-input", "env", "multi-env", "clipboard", "multi-clipboard", "back"]
    if os.name != "nt":
        rows[4] = "Read API key from desktop clipboard if available"
        rows[5] = "Read API keys from desktop clipboard if available"
    if pcfg is not None and provider_api_key_count(provider, pcfg):
        rows.insert(-1, "Clear stored API key(s)")
        values.insert(-1, "clear")
    return rows, values


def base_url_panel_rows(provider: str, pcfg: dict[str, Any]) -> tuple[list[str], list[str]]:
    return (
        [
            f"Edit Base URL  [{compact_text(pcfg.get('base_url') or default_base_url(provider), 72)}]",
            f"Reset to provider default  [{default_base_url(provider)}]",
            "Back",
        ],
        ["edit", "default", "back"],
    )


def visible_rows(rows: list[str], selected: int, limit: int) -> list[tuple[int | None, str]]:
    if len(rows) <= limit:
        return [(i, row) for i, row in enumerate(rows)]
    limit = max(4, limit)
    start = max(0, min(selected - limit // 2, len(rows) - limit))
    end = min(len(rows), start + limit)
    visible: list[tuple[int | None, str]] = []
    if start > 0:
        visible.append((None, f"... {start} above"))
    visible.extend((i, rows[i]) for i in range(start, end))
    if end < len(rows):
        visible.append((None, f"... {len(rows) - end} below"))
    return visible


def render_prelaunch_screen(
    main_idx: int,
    panel: str | None,
    panel_idx: int,
    panel_rows: list[str],
    checks: list[str],
    messages: list[str],
    first_render: bool,
) -> bool:
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    lang = cfg.get("language", "en")
    columns, height = shutil.get_terminal_size((110, 32))
    render_width = max(40, columns - 1)
    screen: list[str] = []
    def add(text: str = "", code: str | None = None) -> None:
        # Redraws start at cursor home. Each row must overwrite the full
        # previous row; otherwise Windows cmd leaves stale text on the right.
        fitted = pad_cells(text, render_width)
        screen.append(ansi(fitted, code) if code else fitted)

    def add_rendered(visible_text: str, rendered_text: str) -> None:
        visible = fit_cells(visible_text, render_width)
        padding = " " * max(0, render_width - cell_width(visible))
        screen.append(rendered_text + padding)

    mode_line = f"mode: {provider_mode_label(provider, pcfg)}"
    title_text = f"Claude Any v{VERSION}"
    add_rendered(title_text, animated_ansi_text(title_text))
    add(CREDITS, "2")
    add("")
    add(f"provider: {provider}    language: {lang}    {mode_line}", "32")
    add(f"base_url: {pcfg.get('base_url')}", "2")
    add(f"model: {pcfg.get('current_model')}", "32")
    add(api_key_status_line(provider, pcfg), "2")
    add("")
    rows = main_menu_rows(cfg, provider, pcfg, lang)
    for i, row in enumerate(rows):
        line = ("> " if i == main_idx and panel is None else "  ") + row
        if i == main_idx and panel is None:
            add(line, "7;1")
        elif "Launch" in row or "실행" in row or "起動" in row or "启动" in row:
            add(line, "32;1")
        elif row == ui_text("quit", lang):
            add(line, "31")
        else:
            add(line)
    if panel:
        titles = {
            "language": "Language",
            "provider": "Provider",
            "api-key": "API key",
            "base-url": "Base URL",
            "model": "Model",
            "advisor-model": "Advisor Model",
            "test": "Compatibility test",
            "options": ui_text("options", lang),
            "channel-delivery": ui_text("channel_delivery", lang),
            "log-level": ui_text("log_level", lang),
            "channels": "Channels",
            "context": ui_text("context_setup", lang),
            "preset": ui_text("presets", lang),
            "timeout": ui_text("timeout_preset", lang),
        }
        add("")
        add("-" * render_width, "38;5;208")
        panel_title = titles.get(panel, panel)
        title_suffix = "" if panel_title.lower().endswith(("options", "presets", "setup", "설정", "옵션", "프리셋", "設定", "オプション", "プリセット", "设置", "选项", "预设")) else " options"
        add(f"{panel_title}{title_suffix}", "1;38;5;208")
        # Reserve an extra line for the per-row description when shown.
        description_reserve = 2 if panel == "options" else 0
        fixed = len(screen) + len(checks) + len(messages) + 5 + description_reserve
        limit = max(5, height - fixed)
        for actual, row in visible_rows(panel_rows, panel_idx, limit):
            if actual is None:
                add("    " + row, "2")
            elif actual == panel_idx:
                add("  > " + row, "7;1")
            else:
                add("    " + row)
        if panel == "options" and panel_rows:
            # Map panel_idx back to its option key, then show its localized
            # description below the panel so the user always sees the meaning of
            # the currently-highlighted row.
            try:
                _, panel_values = llm_option_panel_rows(provider, pcfg, lang)
            except Exception:
                panel_values = []
            current_key = panel_values[panel_idx] if 0 <= panel_idx < len(panel_values) else ""
            description = llm_option_description_for_value(provider, pcfg, current_key, lang) if current_key else ""
            add("")
            if description:
                add("  " + description, "2")
            else:
                add("")
    if messages:
        add("")
        for line in messages[-8:]:
            add("  " + line, "36;1")
    if checks:
        add("")
        add("-" * render_width, "38;5;208")
        for line in checks[:2]:
            add("  " + line, "1;38;5;208")
    add("")
    help_text = "Up/Down moves. Enter selects. Esc/Left closes submenu. q quits. Actions expand in place."
    add(help_text, "2")
    rendered = "\n".join(screen) + "\n"
    if sys.stdout.isatty():
        prefix = "\033[2J\033[H" if first_render else "\033[H"
        sys.stdout.write(prefix + rendered + "\033[J")
        sys.stdout.flush()
    else:
        print(rendered, end="")
    return False


def _prompt_menu_value_raw(label: str, default: str = "", secret: bool = False) -> str | None:
    if os.name == "nt" or not sys.stdin.isatty():
        return None
    try:
        import codecs
        import select
        import termios
    except Exception:
        return None
    fd = sys.stdin.fileno()
    try:
        old_settings = termios.tcgetattr(fd)
        new_settings = termios.tcgetattr(fd)
        new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON)
        new_settings[6][termios.VMIN] = 1
        new_settings[6][termios.VTIME] = 0
        termios.tcsetattr(fd, termios.TCSANOW, new_settings)
    except Exception:
        return None

    chars: list[str] = []
    decoder = codecs.getincrementaldecoder("utf-8")("replace")
    try:
        sys.stdout.write("\n" + ansi(label, "1;38;5;208"))
        sys.stdout.flush()
        while True:
            first = os.read(fd, 1)
            if not first:
                continue
            data = bytearray(first)
            try:
                while select.select([fd], [], [], 0)[0]:
                    more = os.read(fd, 4096)
                    if not more:
                        break
                    data.extend(more)
            except Exception:
                pass

            display: list[str] = []
            done = False
            for byte in data:
                b = bytes((byte,))
                if b in (b"\r", b"\n"):
                    display.append("\n")
                    done = True
                    break
                if b in (b"\x03",):
                    raise KeyboardInterrupt
                if b in (b"\x04", b"\x1b"):
                    display.append("\n")
                    sys.stdout.write("".join(display))
                    sys.stdout.flush()
                    return default
                if b in (b"\x7f", b"\x08"):
                    if chars:
                        chars.pop()
                        if not secret:
                            display.append("\b \b")
                    continue
                if b == b"\x15":
                    if chars and not secret:
                        display.append("\b \b" * len(chars))
                    chars.clear()
                    continue
                if byte < 0x20:
                    continue
                text = decoder.decode(b)
                if not text:
                    continue
                for ch in text:
                    if ch in ("\ufffd", "\r", "\n"):
                        continue
                    chars.append(ch)
                    if not secret:
                        display.append(ch)
            if display:
                sys.stdout.write("".join(display))
                sys.stdout.flush()
            if done:
                break
        return "".join(chars).strip() or default
    finally:
        try:
            termios.tcsetattr(fd, termios.TCSANOW, old_settings)
        except Exception:
            pass


def prompt_menu_value(prompt: str, default: str = "", secret: bool = False, restore_tty: Callable[[], None] | None = None, raw_tty: Callable[[], None] | None = None) -> str:
    label = f"{prompt}"
    if default:
        label += f" [{default}]"
    label += ": "
    if restore_tty:
        restore_tty()
    if sys.stdout.isatty():
        sys.stdout.write("\033[?25h")
        sys.stdout.flush()
    try:
        raw_value = _prompt_menu_value_raw(label, default, secret)
        if raw_value is not None:
            value = raw_value
        else:
            sys.stdout.write("\n" + ansi(label, "1;38;5;208"))
            sys.stdout.flush()
            if secret:
                value = getpass.getpass("")
            else:
                value = input()
    finally:
        if sys.stdout.isatty():
            sys.stdout.write("\033[?25l")
            sys.stdout.flush()
        if raw_tty:
            raw_tty()
    value = value.strip()
    return value or default


def portable_provider_menu() -> int:
    cfg = load_config()
    rows, values = provider_panel_rows(cfg)
    selected = portable_select("Select claude-any provider", rows, values.index(cfg.get("current_provider", "nvidia-hosted")))
    if selected is None:
        print("Cancelled.")
        return 1
    for line in set_provider_config(values[selected]):
        print(line)
    return 0


def portable_language_menu() -> int:
    cfg = load_config()
    rows, values = language_panel_rows(cfg)
    selected = portable_select("Select display language", rows, values.index(cfg.get("language", "en")))
    if selected is None:
        print("Cancelled.")
        return 1
    cfg["language"] = values[selected]
    save_config(cfg)
    print(f"Language set to {values[selected]} ({LANGUAGES[values[selected]]}).")
    return 0


def portable_prelaunch_menu(passthrough: list[str] | None = None) -> int:
    passthrough = list(passthrough or [])
    enable_ansi()
    main_idx = 9 if settings_ready_except_api_key() else 0
    panel: str | None = None
    panel_idx = 0
    panel_rows: list[str] = []
    panel_values: list[str] = []
    panel_last_idx: dict[str, int] = {}
    checks = preflight_lines()
    messages: list[str] = []
    first_render = True

    def open_panel(name: str) -> None:
        nonlocal panel, panel_idx, panel_rows, panel_values, messages, first_render
        cfg = load_config()
        provider, pcfg = get_current_provider(cfg)
        panel = name
        panel_idx = panel_last_idx.get(name, 0)
        if name == "language":
            panel_rows, panel_values = language_panel_rows(cfg)
            panel_idx = panel_values.index(cfg.get("language", "en"))
        elif name == "provider":
            panel_rows, panel_values = provider_panel_rows(cfg)
            current_choice = current_provider_panel_choice(provider, pcfg)
            panel_idx = panel_values.index(current_choice) if current_choice in panel_values else 0
        elif name == "api-key":
            panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
        elif name == "base-url":
            panel_rows, panel_values = base_url_panel_rows(provider, pcfg)
        elif name == "model":
            try:
                panel_rows, panel_values = model_panel_rows(
                    provider,
                    pcfg,
                    fetch=provider == "anthropic" and read_model_list_cache(provider, pcfg) is None,
                )
            except Exception as exc:
                panel_rows, panel_values = [f"Model list failed: {type(exc).__name__}: {exc}", "+ Custom model id..."], []
        elif name == "advisor-model":
            try:
                panel_rows, panel_values = advisor_model_panel_rows(
                    provider,
                    pcfg,
                    fetch=provider == "anthropic" and read_model_list_cache(provider, pcfg) is None,
                )
            except Exception as exc:
                panel_rows, panel_values = [f"Advisor model list failed: {type(exc).__name__}: {exc}", "+ Custom advisor model id..."], []
        elif name == "test":
            panel_rows, panel_values = ["Run compatibility test", "Back"], ["run", "back"]
        elif name == "options":
            panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
        elif name == "channel-delivery":
            panel_rows, panel_values = channel_delivery_panel_rows(cfg)
        elif name == "log-level":
            panel_rows, panel_values = log_level_panel_rows(cfg)
        elif name == "channels":
            panel_rows, panel_values, probe_messages = channel_panel_rows_for_menu(cfg, passthrough)
            if probe_messages:
                messages = probe_messages
            if panel_values:
                panel_idx = _channel_panel_first_selectable(panel_values)
        elif name == "context":
            panel_rows, panel_values = context_setup_panel_rows(provider, pcfg, cfg.get("language", "en"))
        elif name == "preset":
            panel_rows, panel_values = llm_preset_panel_rows(provider, pcfg, cfg.get("language", "en"))
        elif name == "timeout":
            panel_rows, panel_values = timeout_profile_panel_rows(pcfg, cfg.get("language", "en"))
        if panel_rows:
            panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))

    def close_panel(next_idx: int | None = None) -> None:
        nonlocal panel, panel_idx, panel_rows, panel_values, main_idx
        if panel:
            panel_last_idx[panel] = panel_idx
        panel = None
        panel_idx = 0
        panel_rows = []
        panel_values = []
        if next_idx is not None:
            main_idx = next_idx

    def refresh_checks() -> None:
        nonlocal checks
        checks = preflight_lines()

    fd = sys.stdin.fileno()
    old_settings = None
    if os.name != "nt" and os.isatty(fd):
        try:
            import termios
            old_settings = termios.tcgetattr(fd)
            new = termios.tcgetattr(fd)
            new[3] = new[3] & ~(termios.ECHO | termios.ICANON)
            new[6][termios.VMIN] = 1
            new[6][termios.VTIME] = 0
            termios.tcsetattr(fd, termios.TCSANOW, new)
        except Exception:
            fd = -1
    if sys.stdout.isatty():
        sys.stdout.write("\033[?25l")
        sys.stdout.flush()
    def restore_line_mode() -> None:
        if old_settings is not None and fd >= 0:
            try:
                import termios
                termios.tcsetattr(fd, termios.TCSANOW, old_settings)
            except Exception:
                pass

    def restore_raw_mode() -> None:
        if old_settings is not None and fd >= 0:
            try:
                import termios
                new = termios.tcgetattr(fd)
                new[3] = new[3] & ~(termios.ECHO | termios.ICANON)
                new[6][termios.VMIN] = 1
                new[6][termios.VTIME] = 0
                termios.tcsetattr(fd, termios.TCSANOW, new)
            except Exception:
                pass

    try:
        while True:
            first_render = render_prelaunch_screen(main_idx, panel, panel_idx, panel_rows, checks, messages, first_render)
            key = read_menu_key(fd) if fd >= 0 else read_menu_key()
            if panel:
                panel_name = panel
                if key in ("up", "k"):
                    if panel == "channels":
                        panel_idx = _channel_panel_step(panel_values, panel_idx, -1)
                    else:
                        panel_idx = (panel_idx - 1) % max(1, len(panel_rows))
                    panel_last_idx[panel_name] = panel_idx
                    continue
                if key in ("down", "j"):
                    if panel == "channels":
                        panel_idx = _channel_panel_step(panel_values, panel_idx, 1)
                    else:
                        panel_idx = (panel_idx + 1) % max(1, len(panel_rows))
                    panel_last_idx[panel_name] = panel_idx
                    continue
                if key in ("esc", "left", "q"):
                    close_panel()
                    continue
                if key != "enter":
                    continue
                cfg = load_config()
                provider, pcfg = get_current_provider(cfg)
                value = panel_values[panel_idx] if panel_idx < len(panel_values) else ""
                if panel == "language" and value:
                    cfg["language"] = value
                    save_config(cfg)
                    messages = [f"Language set to {value} ({LANGUAGES[value]})."]
                    refresh_checks()
                    close_panel(1)
                elif panel == "provider" and value:
                    messages = set_provider_choice_config(value)
                    refresh_checks()
                    main_idx = 4
                    open_panel("model")
                elif panel == "model":
                    if value == "back":
                        close_panel()
                        continue
                    if value == "__refresh_models__":
                        panel_rows, panel_values = ["Refreshing provider model list..."], []
                        first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
                        try:
                            panel_rows, panel_values = model_panel_rows(provider, pcfg, fetch=True, force_refresh=True)
                            messages = [f"Model list refreshed: {max(0, len(panel_values) - 3)} model(s)."]
                        except Exception as exc:
                            messages = [f"Model list refresh failed: {type(exc).__name__}: {exc}"]
                            panel_rows, panel_values = model_panel_rows(provider, pcfg, fetch=False)
                        panel_idx = 0
                        panel_last_idx["model"] = 0
                        refresh_checks()
                        continue
                    if value == "__custom__" or panel_idx >= len(panel_values):
                        model_value = prompt_menu_value("Model id or alias", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                    else:
                        model_value = value
                    if model_value:
                        messages = set_model_config(model_value)
                        refresh_checks()
                    close_panel(5)
                elif panel == "advisor-model":
                    if value == "back":
                        close_panel()
                        continue
                    if value == "__refresh_models__":
                        panel_rows, panel_values = ["Refreshing provider model list..."], []
                        first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
                        try:
                            panel_rows, panel_values = advisor_model_panel_rows(provider, pcfg, fetch=True, force_refresh=True)
                            messages = [f"Model list refreshed: {max(0, len(panel_values) - 4)} advisor model(s)."]
                        except Exception as exc:
                            messages = [f"Model list refresh failed: {type(exc).__name__}: {exc}"]
                            panel_rows, panel_values = advisor_model_panel_rows(provider, pcfg, fetch=False)
                        panel_idx = 0
                        panel_last_idx["advisor-model"] = 0
                        refresh_checks()
                        continue
                    if value == "__custom__" or panel_idx >= len(panel_values):
                        advisor_value = prompt_menu_value("Advisor model id", "deepseek-v4-pro", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                    else:
                        advisor_value = value
                    messages = set_advisor_model_config(advisor_value)
                    refresh_checks()
                    close_panel(6)
                elif panel == "api-key":
                    if value == "back":
                        close_panel()
                    elif value == "input":
                        key_value = prompt_menu_value(f"API key for {provider}", secret=True, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        if key_value:
                            messages = store_api_key_input_config(provider, key_value)
                            refresh_checks()
                        close_panel(3)
                    elif value == "multi-input":
                        key_value = prompt_menu_value(
                            f"API keys for {provider} (comma/newline separated)",
                            secret=True,
                            restore_tty=restore_line_mode,
                            raw_tty=restore_raw_mode,
                        )
                        if key_value:
                            messages = store_api_keys_config(provider, parse_api_key_list(key_value))
                            refresh_checks()
                        close_panel(3)
                    elif value == "env":
                        default_env = {
                            "anthropic": "ANTHROPIC_API_KEY",
                            "deepseek": "DEEPSEEK_API_KEY",
                            "opencode": "OPENCODE_API_KEY",
                            "opencode-go": "OPENCODE_API_KEY",
                            "nvidia-hosted": "NVIDIA_API_KEY",
                            "ollama-cloud": "OLLAMA_API_KEY",
                            "openrouter": "OPENROUTER_API_KEY",
                            "fireworks": "FIREWORKS_API_KEY",
                        }.get(provider, "API_KEY")
                        env_name = prompt_menu_value("Environment variable name", default_env, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        key_value = os.environ.get(env_name, "").strip()
                        if key_value:
                            messages = store_api_key_input_config(provider, key_value)
                        else:
                            messages = [f"Environment variable {env_name} is empty or not set."]
                        refresh_checks()
                        close_panel(3)
                    elif value == "multi-env":
                        default_env = {
                            "anthropic": "ANTHROPIC_API_KEYS",
                            "deepseek": "DEEPSEEK_API_KEYS",
                            "opencode": "OPENCODE_API_KEYS",
                            "opencode-go": "OPENCODE_API_KEYS",
                            "nvidia-hosted": "NVIDIA_API_KEYS",
                            "ollama-cloud": "OLLAMA_API_KEYS",
                            "openrouter": "OPENROUTER_API_KEYS",
                            "fireworks": "FIREWORKS_API_KEYS",
                        }.get(provider, "API_KEYS")
                        env_name = prompt_menu_value("Environment variable name", default_env, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        key_value = os.environ.get(env_name, "").strip()
                        if key_value:
                            messages = store_api_keys_config(provider, parse_api_key_list(key_value))
                        else:
                            messages = [f"Environment variable {env_name} is empty or not set."]
                        refresh_checks()
                        close_panel(3)
                    elif value == "clipboard":
                        key_value = read_clipboard_text()
                        if not key_value:
                            messages = ["Clipboard did not contain readable text."]
                        else:
                            confirm = prompt_menu_value(f"Clipboard contains {mask_secret(key_value)}. Store it? y/N", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                            if confirm.lower().startswith("y"):
                                messages = store_api_key_input_config(provider, key_value)
                            else:
                                messages = ["Clipboard API key was not stored."]
                        refresh_checks()
                        close_panel(3)
                    elif value == "multi-clipboard":
                        key_value = read_clipboard_text()
                        keys = parse_api_key_list(key_value)
                        if not keys:
                            messages = ["Clipboard did not contain readable API keys."]
                        else:
                            primary = f"{mask_secret(keys[0])}; fp {secret_fingerprint(keys[0])}"
                            confirm = prompt_menu_value(
                                f"Clipboard contains {len(keys)} key(s); primary {primary}. Store with round-robin? y/N",
                                restore_tty=restore_line_mode,
                                raw_tty=restore_raw_mode,
                            )
                            if confirm.lower().startswith("y"):
                                messages = store_api_keys_config(provider, keys)
                            else:
                                messages = ["Clipboard API keys were not stored."]
                        refresh_checks()
                        close_panel(3)
                    elif value == "clear":
                        messages = clear_api_key_config(provider)
                        refresh_checks()
                        close_panel(3)
                elif panel == "base-url":
                    if value == "back":
                        close_panel()
                    elif value == "default":
                        messages = set_base_url_config(provider, default_base_url(provider))
                        refresh_checks()
                        close_panel(4)
                    elif value == "edit":
                        default = pcfg.get("base_url") or default_base_url(provider)
                        url = prompt_menu_value(f"Base URL for {provider}", default, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        if url:
                            messages = set_base_url_config(provider, url)
                            refresh_checks()
                        close_panel(4)
                elif panel == "test":
                    if value == "back":
                        close_panel()
                    else:
                        panel_rows, panel_values = ["Testing current provider/model..."], []
                        first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
                        _, out = self_cmd(["test"])
                        lines = [line for line in out.splitlines() if line.strip()]
                        messages = lines[-8:] if lines else ["Test produced no output."]
                        test_ok = "Compatibility: OK" in out
                        refresh_checks()
                        close_panel(9 if test_ok else 4)
                elif panel == "log-level":
                    if value == "back":
                        close_panel()
                    elif value:
                        messages = set_log_level_config(value)
                        refresh_checks()
                        cfg = load_config()
                        panel_rows, panel_values = log_level_panel_rows(cfg)
                        panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
                elif panel == "channel-delivery":
                    if value == "back":
                        close_panel()
                    elif value:
                        messages = set_channel_delivery_config(value)
                        refresh_checks()
                        cfg = load_config()
                        panel_rows, panel_values = channel_delivery_panel_rows(cfg)
                        panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
                elif panel == "channels":
                    if value == "back":
                        close_panel()
                    elif value in ("__heading__", "__noop__"):
                        continue
                    elif value == "__reprobe__":
                        panel_rows, panel_values = ["Re-probing MCP channel capability..."], []
                        first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
                        try:
                            result = refresh_channel_probe_cache(passthrough)
                            messages = [channel_probe_summary_message("Probe complete", result)]
                        except Exception as exc:
                            messages = [f"Re-probe failed: {type(exc).__name__}: {exc}"]
                        cfg = load_config()
                        panel_rows, panel_values = channel_panel_rows(cfg)
                        if panel_values:
                            panel_idx = _channel_panel_first_selectable(panel_values)
                    elif value == "__add_custom__":
                        spec = prompt_menu_value("Channel spec (for example plugin:ainet@local or server:ainet)", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        if spec:
                            messages = add_channel_spec(spec)
                            cfg = load_config()
                            panel_rows, panel_values = channel_panel_rows(cfg)
                            if panel_values:
                                panel_idx = _channel_panel_first_selectable(panel_values)
                    elif value == "__remove__":
                        spec = prompt_menu_value("Channel spec to remove", "", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        if spec:
                            messages = remove_channel_spec(spec)
                            cfg = load_config()
                            panel_rows, panel_values = channel_panel_rows(cfg)
                            if panel_values:
                                panel_idx = _channel_panel_first_selectable(panel_values)
                    elif value == "__clear__":
                        messages = clear_channel_specs()
                        cfg = load_config()
                        panel_rows, panel_values = channel_panel_rows(cfg)
                        if panel_values:
                            panel_idx = _channel_panel_first_selectable(panel_values)
                    elif value:
                        if value in channel_specs(cfg):
                            messages = remove_channel_spec(value)
                        else:
                            messages = add_channel_spec(value)
                        cfg = load_config()
                        panel_rows, panel_values = channel_panel_rows(cfg)
                    refresh_checks()
                elif panel == "options":
                    if value == "back":
                        close_panel()
                    elif value == "context_setup":
                        open_panel("context")
                    elif value == "preset":
                        open_panel("preset")
                    elif value == "timeout_profile":
                        open_panel("timeout")
                    elif value in LLM_OPTION_TOGGLE_KEYS:
                        # Boolean toggles flip on Enter — no input prompt.
                        current = llm_option_current_bool(provider, pcfg, value)
                        try:
                            messages = set_llm_option_config(provider, value, "false" if current else "true")
                        except Exception as exc:
                            messages = [f"Option update failed: {type(exc).__name__}: {exc}"]
                        refresh_checks()
                        cfg = load_config()
                        provider, pcfg = get_current_provider(cfg)
                        old_idx = panel_idx
                        panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
                        panel_idx = max(0, min(old_idx, len(panel_rows) - 1))
                        panel_last_idx["options"] = panel_idx
                    else:
                        default = llm_option_prompt_default(provider, pcfg, value)
                        entered = prompt_menu_value(f"{value} for {provider} (default/unset clears)", default, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
                        try:
                            messages = set_llm_option_config(provider, value, entered)
                        except Exception as exc:
                            messages = [f"Option update failed: {type(exc).__name__}: {exc}"]
                        refresh_checks()
                        cfg = load_config()
                        provider, pcfg = get_current_provider(cfg)
                        old_idx = panel_idx
                        panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
                        panel_idx = max(0, min(old_idx, len(panel_rows) - 1))
                        panel_last_idx["options"] = panel_idx
                elif panel == "context":
                    if value == "back":
                        open_panel("options")
                    elif value == "__info__":
                        continue
                    else:
                        try:
                            messages = apply_context_setup_config(provider, value)
                        except Exception as exc:
                            messages = [f"Context setup failed: {type(exc).__name__}: {exc}"]
                        refresh_checks()
                        cfg = load_config()
                        provider, pcfg = get_current_provider(cfg)
                        panel = "options"
                        panel_idx = panel_last_idx.get("options", 0)
                        panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
                        panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
                        panel_last_idx["options"] = panel_idx
                elif panel == "preset":
                    if value == "back":
                        open_panel("options")
                    elif value == "__info__":
                        continue
                    else:
                        try:
                            messages = apply_llm_preset_config(provider, value)
                        except Exception as exc:
                            messages = [f"Preset failed: {type(exc).__name__}: {exc}"]
                        refresh_checks()
                        cfg = load_config()
                        provider, pcfg = get_current_provider(cfg)
                        panel = "options"
                        panel_idx = panel_last_idx.get("options", 0)
                        panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
                        panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
                        panel_last_idx["options"] = panel_idx
                elif panel == "timeout":
                    if value == "back":
                        open_panel("options")
                    elif value == "__info__":
                        continue
                    else:
                        try:
                            cfg = load_config()
                            provider, pcfg = get_current_provider(cfg)
                            messages = apply_timeout_profile_to_provider(pcfg, value, cfg.get("language", "en"))
                            save_config(cfg)
                            clear_model_cache()
                        except Exception as exc:
                            messages = [f"Timeout preset failed: {type(exc).__name__}: {exc}"]
                        refresh_checks()
                        cfg = load_config()
                        provider, pcfg = get_current_provider(cfg)
                        panel = "options"
                        panel_idx = panel_last_idx.get("options", 0)
                        panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
                        panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
                        panel_last_idx["options"] = panel_idx
                continue

            if key in ("up", "k"):
                cfg = load_config()
                provider, pcfg = get_current_provider(cfg)
                main_idx = (main_idx - 1) % len(main_menu_rows(cfg, provider, pcfg, cfg.get("language", "en")))
            elif key in ("down", "j"):
                cfg = load_config()
                provider, pcfg = get_current_provider(cfg)
                main_idx = (main_idx + 1) % len(main_menu_rows(cfg, provider, pcfg, cfg.get("language", "en")))
            elif key in ("esc", "q"):
                return 10
            elif key == "enter":
                actions = ["language", "provider", "api-key", "base-url", "model", "advisor-model", "options", "log-level", "test", "launch", "quit"]
                action = actions[main_idx]
                if action == "launch":
                    blockers = launch_readiness_errors()
                    if blockers:
                        messages = blockers
                        if launch_blockers_require_api_key(blockers):
                            cfg = load_config()
                            provider, _ = get_current_provider(cfg)
                            main_idx = actions.index("api-key")
                            open_panel("api-key")
                            if "input" in panel_values:
                                panel_idx = panel_values.index("input")
                            messages = [
                                *blockers,
                                f"Opening API key setup for {PROVIDER_LABELS.get(provider, provider)}.",
                            ]
                        refresh_checks()
                        continue
                    return 0
                if action == "quit":
                    return 10
                open_panel(action)
    finally:
        if old_settings is not None:
            try:
                import termios
                termios.tcsetattr(fd, termios.TCSANOW, old_settings)
            except Exception:
                pass
        sys.stdout.write("\033[?25h")
        sys.stdout.flush()


def run_external_menu(name: str) -> int | None:
    if os.name == "nt":
        return None
    exe = find_executable(name)
    if not exe:
        return None
    return subprocess.call([exe])


def has_noninteractive_claude_args(passthrough: list[str]) -> bool:
    return any(arg == "-p" or arg == "--print" or arg.startswith("--print=") for arg in passthrough)


def run_prelaunch_menu(passthrough: list[str], skip_menu: bool = False, force_menu: bool = False) -> int:
    if not force_menu and (
        skip_menu or has_noninteractive_claude_args(passthrough) or os.environ.get("CLAUDE_ANY_SKIP_MENU") == "1"
    ):
        return 0
    if not (sys.stdin.isatty() and sys.stdout.isatty()):
        return 0
    if os.environ.get("CLAUDE_ANY_USE_LEGACY_MENU") == "1":
        rc = run_external_menu("claude-any-menu")
        if rc is not None:
            return rc
    return portable_prelaunch_menu(passthrough)


def start_router_if_needed() -> bool:
    health = router_health()
    if health is not None:
        active_clients = active_router_client_pids()
        if router_health_matches_current(health):
            if active_clients:
                router_log(
                    "INFO",
                    "router_check_state running=True spawn=False "
                    f"base={ROUTER_BASE} active_clients={','.join(map(str, active_clients))}",
                )
                return True
            if env_bool(os.environ.get("CLAUDE_ANY_REUSE_ROUTER"), False):
                router_log("INFO", f"router_check_state running=True spawn=False base={ROUTER_BASE} reuse=env")
                return True
            router_log(
                "INFO",
                "router_prelaunch_replace "
                f"running_version={health.get('version') or '-'} current_version={VERSION} "
                f"running_source={health.get('source_fingerprint') or '-'} current_source={SOURCE_FINGERPRINT} "
                f"pid={health.get('pid') or '-'}",
            )
            ensure_router_port_available_for_spawn("prelaunch_replace", health)
        else:
            if router_health_config_matches_current(health) and active_clients:
                raise RuntimeError(
                    f"claude-any router on {ROUTER_BASE} belongs to this config but has active clients "
                    f"({','.join(map(str, active_clients))}) and differs from this launch "
                    f"(running_version={health.get('version') or '-'}, current_version={VERSION}). "
                    "Stop the other Claude Code session or launch this instance with a different "
                    "CLAUDE_ANY_ROUTER_PORT."
                )
            running_version = str(health.get("version") or "")
            running_fingerprint = str(health.get("source_fingerprint") or "")
            router_log(
                "WARN",
                "router_version_mismatch_restart "
                f"running_version={running_version or '-'} current_version={VERSION} "
                f"running_source={running_fingerprint or '-'} current_source={SOURCE_FINGERPRINT}",
            )
            ensure_router_port_available_for_spawn("version_mismatch", health)
    else:
        ensure_router_port_available_for_spawn("pre_spawn", None)
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    cmd = [sys.executable, str(Path(__file__).resolve()), "serve"]
    kwargs: dict[str, Any] = {}
    if os.name == "nt":
        flags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
        if flags:
            kwargs["creationflags"] = flags
    else:
        kwargs["start_new_session"] = True
    router_log("INFO", f"router_check_state running=False spawn=True base={ROUTER_BASE}")
    router_env = os.environ.copy()
    router_env["CLAUDE_ANY_MANAGED_ROUTER"] = "1"
    router_env["CLAUDE_ANY_ROUTER_OWNER_PID"] = str(os.getpid())
    with open(LOG_PATH, "ab", buffering=0) as log:
        subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=log, stderr=log, env=router_env, **kwargs)
    deadline = time.time() + 30
    while time.time() < deadline:
        if router_up():
            router_log("INFO", f"router_spawned running=True base={ROUTER_BASE} elapsed={time.time()-(deadline-30):.1f}s")
            return True
        time.sleep(0.5)
    raise RuntimeError(f"claude-any router did not start. See {LOG_PATH}")


def should_attach_web_search(provider: str, cfg: dict[str, Any], override: bool | None) -> bool:
    if override is not None:
        return override
    return provider != "anthropic" and bool(cfg.get("web_search", {}).get("auto_for_non_native", True))


def should_append_compat_prompt(provider: str, cfg: dict[str, Any]) -> bool:
    return provider != "anthropic" and bool(cfg.get("claude_code", {}).get("compat_prompt_for_non_anthropic", True))


_CLAUDE_PERMISSION_MODE_SUPPORT_CACHE: dict[str, bool] = {}


def claude_supports_permission_mode_arg(claude: str) -> bool:
    cache_key = str(claude or "")
    if cache_key in _CLAUDE_PERMISSION_MODE_SUPPORT_CACHE:
        return _CLAUDE_PERMISSION_MODE_SUPPORT_CACHE[cache_key]
    supported = False
    try:
        proc = subprocess.run(
            [claude, "--help"],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            timeout=5,
            check=False,
        )
        help_text = proc.stdout or ""
        supported = "--permission-mode" in help_text and "bypassPermissions" in help_text
    except Exception:
        supported = False
    _CLAUDE_PERMISSION_MODE_SUPPORT_CACHE[cache_key] = supported
    return supported


def has_passthrough_option(passthrough: list[str], *names: str) -> bool:
    return any(arg in names or any(arg.startswith(name + "=") for name in names) for arg in passthrough)


CLAUDE_CODE_GENERATED_GREEDY_OPTIONS = {
    "--mcp-config",
    "--dangerously-load-development-channels",
}


def should_insert_passthrough_option_boundary(extra_args: list[str], passthrough: list[str]) -> bool:
    if not passthrough:
        return False
    first = passthrough[0]
    if first == "--" or first.startswith("-"):
        return False
    return any(arg in CLAUDE_CODE_GENERATED_GREEDY_OPTIONS for arg in extra_args)


def claude_session_control_requested(passthrough: list[str]) -> bool:
    return has_passthrough_option(
        passthrough,
        "-c",
        "--continue",
        "-r",
        "--resume",
        "--session-id",
        "--fork-session",
        "--from-pr",
    )


def current_launch_cwd_key() -> str:
    try:
        return str(Path.cwd().resolve())
    except Exception:
        return os.getcwd()


def launch_mode_name(provider: str, pcfg: dict[str, Any], use_native_anthropic: bool) -> str:
    if use_native_anthropic:
        return "anthropic-native"
    if anthropic_routed_enabled(provider, pcfg):
        return "anthropic-routed"
    return f"router:{provider}"


def read_launch_state() -> dict[str, Any]:
    try:
        data = json.loads(LAUNCH_STATE_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return data if isinstance(data, dict) else {}


def write_launch_state(state: dict[str, Any]) -> None:
    try:
        CONFIG_DIR.mkdir(parents=True, exist_ok=True)
        tmp = LAUNCH_STATE_PATH.with_name(f"{LAUNCH_STATE_PATH.name}.{os.getpid()}.{time.time_ns()}.tmp")
        tmp.write_text(json.dumps(state, indent=2, sort_keys=True) + "\n", encoding="utf-8")
        os.chmod(tmp, 0o600)
        tmp.replace(LAUNCH_STATE_PATH)
    except Exception as exc:
        router_log("WARN", f"launch_state_write_failed error={type(exc).__name__}: {exc}")


def previous_launch_state_for_cwd(cwd_key: str) -> dict[str, Any]:
    state = read_launch_state()
    by_cwd = state.get("by_cwd")
    if isinstance(by_cwd, dict):
        item = by_cwd.get(cwd_key)
        if isinstance(item, dict):
            return item
    legacy = state.get("last")
    if isinstance(legacy, dict) and str(legacy.get("cwd") or "") == cwd_key:
        return legacy
    return {}


def record_launch_state_for_cwd(cwd_key: str, provider: str, mode: str, model: str) -> None:
    state = read_launch_state()
    by_cwd = state.get("by_cwd")
    if not isinstance(by_cwd, dict):
        by_cwd = {}
    item = {
        "cwd": cwd_key,
        "provider": provider,
        "mode": mode,
        "model": model,
        "pid": os.getpid(),
        "time": time.time(),
    }
    by_cwd[cwd_key] = item
    state["by_cwd"] = by_cwd
    state["last"] = item
    write_launch_state(state)


def should_fork_native_session_after_mode_switch(
    provider: str,
    pcfg: dict[str, Any],
    use_native_anthropic: bool,
    passthrough: list[str],
    cwd_key: str,
) -> tuple[bool, str]:
    if not use_native_anthropic:
        return False, ""
    if claude_session_control_requested(passthrough):
        return False, "explicit_session_control"
    previous = previous_launch_state_for_cwd(cwd_key)
    previous_mode = str(previous.get("mode") or "")
    if not previous_mode or previous_mode == launch_mode_name(provider, pcfg, use_native_anthropic):
        return False, previous_mode or "no_previous_mode"
    return True, previous_mode


def normalize_channel_passthrough(passthrough: list[str]) -> list[str]:
    normalized: list[str] = []
    i = 0
    while i < len(passthrough):
        arg = passthrough[i]
        if arg == "--channels":
            normalized.append("--dangerously-load-development-channels")
            i += 1
            while i < len(passthrough) and is_channel_spec_tagged(passthrough[i]):
                normalized.append(passthrough[i])
                i += 1
            continue
        if arg.startswith("--channels="):
            value = arg.split("=", 1)[1].strip()
            if value:
                normalized.extend(["--dangerously-load-development-channels", value])
            else:
                normalized.append("--dangerously-load-development-channels")
            i += 1
            continue
        normalized.append(arg)
        i += 1
    return normalized


def native_channel_passthrough_requested(passthrough: list[str]) -> bool:
    return has_passthrough_option(passthrough, "--channels", "--dangerously-load-development-channels")


def claude_channel_args(
    cfg: dict[str, Any],
    passthrough: list[str],
    extra_specs: list[str] | None = None,
    *,
    native_channel_bridge: bool = False,
) -> list[str]:
    if not native_channel_bridge:
        return []
    if native_channel_passthrough_requested(passthrough):
        return []
    specs = [
        spec
        for spec in channel_specs_for_launch(cfg, passthrough, extra_specs)
        if not (str(spec).startswith("server:") and str(spec).split(":", 1)[1].strip().lower() in _NATIVE_ROUTER_CHANNEL_NAMES)
    ]
    if not specs:
        return []
    return ["--dangerously-load-development-channels", *specs]


def claude_channels_requested(cfg: dict[str, Any], passthrough: list[str], extra_specs: list[str] | None = None) -> bool:
    return native_channel_passthrough_requested(passthrough)


def should_use_native_channel_bridge(use_router_mode: bool, cfg: dict[str, Any], passthrough: list[str]) -> bool:
    return bool(
        not use_router_mode
        and channel_delivery_mode(cfg) == "native"
        and not native_channel_passthrough_requested(passthrough)
    )


def should_use_channel_llm_delivery(use_router_mode: bool, passthrough: list[str], cfg: dict[str, Any] | None = None) -> bool:
    if not use_router_mode or native_channel_passthrough_requested(passthrough):
        return False
    return True


def channel_specs_include_external_server(specs: list[str]) -> bool:
    for spec in specs:
        text = str(spec or "").strip()
        if not text:
            continue
        name = text.split(":", 1)[1] if ":" in text else text
        if name.strip().lower() not in _NATIVE_ROUTER_CHANNEL_NAMES:
            return True
    return False


def claude_code_channels_auth_available(claude: str) -> tuple[bool, str]:
    try:
        proc = subprocess.run(
            [claude, "auth", "status"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=5,
        )
    except Exception as exc:
        return True, f"auth_status_unavailable:{type(exc).__name__}"
    if proc.returncode != 0:
        return True, f"auth_status_rc_{proc.returncode}"
    try:
        data = json.loads(proc.stdout or "{}")
    except Exception:
        return True, "auth_status_unparseable"
    if not bool(data.get("loggedIn")):
        return False, "not_logged_in"
    return True, str(data.get("authMethod") or "logged_in")


def write_web_tools_mcp_config(cfg: dict[str, Any]) -> Path:
    web = cfg.get("web_search", {})
    package = web.get("package") or "ddg-mcp-search"
    npx = find_executable("npx") or ("npx.cmd" if os.name == "nt" else "npx")
    servers: dict[str, Any] = {
        "duckduckgo": {
            "command": npx,
            "args": ["-y", package],
        }
    }
    if web.get("fetch_enabled", True):
        fetch_args = [web.get("fetch_package") or "mcp-server-fetch"]
        if web.get("fetch_user_agent"):
            fetch_args.extend(["--user-agent", str(web["fetch_user_agent"])])
        if web.get("fetch_ignore_robots_txt", False):
            fetch_args.append("--ignore-robots-txt")
        fetch_command = find_executable("uvx")
        fetch_command_args = fetch_args
        if not fetch_command:
            uv = find_executable("uv")
            if uv:
                fetch_command = uv
                fetch_command_args = ["tool", "run", *fetch_args]
            elif importlib.util.find_spec("uv") is not None:
                fetch_command = sys.executable
                fetch_command_args = ["-m", "uv", "tool", "run", *fetch_args]
            else:
                pipx = find_executable("pipx")
                if pipx:
                    fetch_command = pipx
                    fetch_command_args = ["run", *fetch_args]
        if fetch_command:
            servers["web_fetch"] = {
                "command": fetch_command,
                "args": fetch_command_args,
                "claude_any_stdio": "jsonl",
            }
        else:
            router_log("WARN", "web_fetch_disabled_missing_runner install=uvx_or_uv")
    data = {"mcpServers": servers}
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    WEB_TOOLS_MCP_CONFIG.write_text(json.dumps(data, indent=2) + "\n")
    try:
        os.chmod(WEB_TOOLS_MCP_CONFIG, 0o600)
    except Exception:
        pass
    return WEB_TOOLS_MCP_CONFIG


def write_duckduckgo_mcp_config(cfg: dict[str, Any]) -> Path:
    path = write_web_tools_mcp_config(cfg)
    try:
        DUCKDUCKGO_MCP_CONFIG.write_text(path.read_text())
    except Exception:
        pass
    return path


def write_channel_mcp_config() -> Path:
    data = {
        "mcpServers": {
            "claude-any-router": {
                "type": "sse",
                "url": f"{ROUTER_BASE}/ca/mcp/sse",
            }
        }
    }
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    CHANNEL_MCP_CONFIG.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
    try:
        os.chmod(CHANNEL_MCP_CONFIG, 0o600)
    except Exception:
        pass
    _channel_mcp_ensure_cursor_initialized()
    return CHANNEL_MCP_CONFIG


def write_mcp_proxy_config(
    passthrough: list[str],
    *,
    extra_config_paths: list[Path | str] | None = None,
    force_proxy_server_names: set[str] | None = None,
    disable_proxy_notification_stream_names: set[str] | None = None,
    cwd: Path | None = None,
    home: Path | None = None,
) -> Path | None:
    cwd = cwd or Path.cwd()
    force_proxy_server_names = set(force_proxy_server_names or set())
    disable_proxy_notification_stream_names = set(disable_proxy_notification_stream_names or set())
    extra = [Path(item).expanduser() for item in (extra_config_paths or [])]
    paths = [*extra, *claude_mcp_config_paths(passthrough, cwd, home)]
    servers: dict[str, Any] = {}
    seen: set[str] = set()
    server_dir = CONFIG_DIR / "mcp-proxy-servers"
    for path in paths:
        if not path.exists() or not path.is_file():
            continue
        for name, server in _read_mcp_servers_from_json(path, cwd):
            if name in seen:
                continue
            seen.add(name)
            streamable_http = _mcp_server_is_streamable_http(server)
            force_streamable_proxy = streamable_http and (name in force_proxy_server_names or _mcp_server_force_proxy(server))
            if _mcp_server_is_stdio(server) or force_streamable_proxy:
                server_dir.mkdir(parents=True, exist_ok=True)
                server_path = server_dir / f"{_safe_mcp_proxy_name(name)}.json"
                saved_server = dict(server)
                if streamable_http and name in disable_proxy_notification_stream_names:
                    saved_server["claude_any_disable_notification_stream"] = True
                server_path.write_text(json.dumps(saved_server, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
                try:
                    os.chmod(server_path, 0o600)
                except Exception:
                    pass
                servers[name] = {
                    "command": sys.executable,
                    "args": [
                        str(Path(__file__).resolve()),
                        "mcp-proxy",
                        "--server-name",
                        name,
                        "--server-config",
                        str(server_path),
                    ],
                }
            else:
                servers[name] = server
    if not servers:
        return None
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    MCP_PROXY_CONFIG.write_text(json.dumps({"mcpServers": servers}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    try:
        os.chmod(MCP_PROXY_CONFIG, 0o600)
    except Exception:
        pass
    router_log("INFO", f"mcp_proxy_config_written servers={','.join(sorted(servers))}")
    return MCP_PROXY_CONFIG


def should_use_channel_stdin_proxy(use_router_mode: bool, passthrough: list[str], cfg: dict[str, Any] | None = None) -> bool:
    if not use_router_mode or native_channel_passthrough_requested(passthrough):
        return False
    if has_passthrough_option(passthrough, "-p", "--print"):
        return False
    ccfg = (cfg or {}).get("claude_code") if isinstance(cfg, dict) else {}
    if isinstance(ccfg, dict) and ccfg.get("web_chat_session_bridge") is False:
        return False
    return channel_delivery_mode(cfg) == "llm"


def should_launch_process_start_channel_sse(
    stdin_channel_proxy: bool,
    native_channel_bridge: bool,
    llm_channel_delivery: bool,
) -> bool:
    return bool((stdin_channel_proxy or native_channel_bridge) and not llm_channel_delivery)


def should_use_channel_screen_summary_proxy(
    llm_channel_delivery: bool,
    detected_channel_specs: list[str],
    claude_passthrough: list[str],
) -> bool:
    return bool(
        llm_channel_delivery
        and channel_specs_include_external_server(detected_channel_specs)
        and not has_passthrough_option(claude_passthrough, "-p", "--print")
        and env_bool(os.environ.get("CLAUDE_ANY_CHANNEL_SCREEN_SUMMARY"), False)
    )


def format_channel_wake_prompt(message: dict[str, Any]) -> str:
    channel = str(message.get("channel") or "default")
    sender = str(message.get("sender_id") or "channel")
    mid = str(message.get("id") or "")
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    room = str(meta.get("room_id") or meta.get("room") or channel)
    thread = str(message.get("thread_id") or meta.get("thread_id") or "")
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
    fields = [f"channel={channel}", f"room={room}", f"from={sender}"]
    if mid:
        fields.append(f"id={mid}")
    if thread:
        fields.append(f"thread={thread}")
    return (
        "[claude-any external channel message] "
        + " ".join(fields)
        + f" text={json.dumps(body, ensure_ascii=False)}"
        + ". "
        + "If this is a claude-any-web-chat message, answer back through the claude-any-router send_message tool on the same channel/thread with recipients='web' and delivery=['web']; use send_file when returning a file attachment. "
        + "If relevant to current work, respond or act now; otherwise keep working."
    )


def _format_channel_web_chat_wake_item(message: dict[str, Any]) -> str:
    channel = str(message.get("channel") or "default")
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    reply_channel = str(meta.get("reply_channel") or channel)
    thread = str(message.get("thread_id") or meta.get("thread_id") or reply_channel)
    mid = str(message.get("id") or "")
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
    fields = [f"id={mid}", f"channel={reply_channel}", f"thread={thread}"]
    return " ".join(field for field in fields if not field.endswith("=")) + f" user={json.dumps(body, ensure_ascii=False)}"


def format_channel_web_chat_wake_batch_prompt(messages: list[dict[str, Any]]) -> str:
    items = " ; ".join(_format_channel_web_chat_wake_item(message) for message in messages)
    count = len(messages)
    return (
        f"[claude-any web chat] {count} browser message(s): {items}. "
        "Answer in the active Claude Code session. Use current context and tools/MCP if useful. "
        "Reply to the browser with claude-any-router send_message on the listed channel/thread_id, recipients='web', delivery=['web']; use send_file when returning a file attachment."
    )


def _channel_wake_message_noise_reason(message: dict[str, Any]) -> str | None:
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip().lower()
    kind = str(message.get("kind") or "").strip().lower()
    if not body:
        return "empty"
    if kind in {"connection", "connected", "heartbeat", "keepalive"}:
        return kind
    if re.fullmatch(r"[a-z0-9_.:-]{1,80}\.(ws|sse)\.connected", body):
        return "transport_connected"
    return None


def _channel_llm_message_skip_reason(message: dict[str, Any]) -> str | None:
    visibility = str(message.get("visibility") or "user").strip().lower()
    if visibility in {"hidden", "internal", "transport", "control", "system"}:
        return f"visibility_{visibility}"
    recipients = {item.strip().lower() for item in _as_string_list(message.get("recipients"))}
    if "internal" in recipients:
        return "recipient_internal"
    delivery = _as_string_list(message.get("delivery"))
    if delivery:
        normalized_delivery = {item.strip().lower() for item in delivery}
        if not ({"all", "*", "llm"} & normalized_delivery):
            return "delivery_not_llm"
    wake_reason = _channel_wake_message_noise_reason(message)
    if wake_reason:
        return wake_reason
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    sse_source = str(meta.get("sse_source") or meta.get("source") or "").strip().lower()
    sender = str(message.get("sender_id") or meta.get("sender_id") or "").strip().lower()
    if sse_source in _NATIVE_ROUTER_CHANNEL_NAMES or sender in _NATIVE_ROUTER_CHANNEL_NAMES:
        return "native_router_self_echo"
    meta_kind = str(meta.get("kind") or meta.get("type") or meta.get("event_type") or meta.get("eventType") or meta.get("event") or meta.get("status") or "").strip().lower()
    if meta_kind in _CHANNEL_CONTROL_KINDS:
        return meta_kind
    return None


def _channel_wake_message_is_noise(message: dict[str, Any]) -> bool:
    return _channel_wake_message_noise_reason(message) is not None


def format_channel_wake_batch_prompt(messages: list[dict[str, Any]]) -> str:
    if len(messages) == 1:
        return format_channel_wake_prompt(messages[0])
    parts: list[str] = []
    for message in messages:
        channel = str(message.get("channel") or "default")
        sender = str(message.get("sender_id") or "channel")
        mid = str(message.get("id") or "")
        meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
        room = str(meta.get("room_id") or meta.get("room") or channel)
        thread = str(message.get("thread_id") or meta.get("thread_id") or "")
        body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
        fields = [f"id={mid}", f"room={room}", f"from={sender}"]
        if thread:
            fields.append(f"thread={thread}")
        parts.append("(" + " ".join(fields) + ") " + json.dumps(body, ensure_ascii=False))
    return (
        f"[claude-any external channel messages] {len(messages)} new messages: "
        + " ; ".join(parts)
        + ". If any item is a claude-any-web-chat message, answer back through the claude-any-router send_message tool on the same channel/thread with recipients='web' and delivery=['web']. "
        + "If relevant to current work, respond or act now; otherwise keep working."
    )


def format_channel_llm_batch_prompt(messages: list[dict[str, Any]]) -> str:
    parts: list[str] = []
    for message in messages:
        channel = str(message.get("channel") or "default")
        sender = str(message.get("sender_id") or "channel")
        recipients = message.get("recipients")
        mid = str(message.get("id") or "")
        meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
        room = str(meta.get("room_id") or meta.get("room") or channel)
        thread = str(message.get("thread_id") or meta.get("thread_id") or "")
        body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip()
        fields = [f"id={mid}", f"channel={channel}", f"room={room}", f"from={sender}"]
        if recipients:
            fields.append(f"to={_compact_json_for_prompt(recipients, max_chars=400)}")
        if thread:
            fields.append(f"thread={thread}")
        parts.append(
            f"<< {channel} >> incoming channel message for the current agent.\n"
            f"<< 메시지 >>\n"
            + " ".join(fields)
            + f"\ntext={json.dumps(body, ensure_ascii=False)}"
        )
    return (
        "[claude-any channel inbox]\n"
        "아래 항목은 claude-any가 자동 수신한 외부 채널/MCP 메시지입니다. "
        "이 메시지는 현재 Claude Code 세션의 에이전트에게 도착한 실제 업무 메시지입니다. "
        "이 inbox는 현재 에이전트의 MCP/channel 자격으로 구독된 수신함입니다. "
        "room name, DM label, 또는 'New message from ...' 같은 짧은 알림만 보고 현재 에이전트가 수신자가 아니라고 결론내리지 마세요. "
        "수신자 정체성이 애매하면 안전한 read/profile 도구로 실제 메시지와 현재 에이전트 정보를 확인한 뒤 판단하세요. "
        "이 턴은 외부 채널 수신함을 처리하는 자율 처리 턴입니다. "
        "자동 회신 루프를 만들지 마세요. 단순 수신 확인, 감사, 준비 완료/대기 중, 진행상황 공유처럼 새 질문이나 새 업무 지시가 없는 메시지는 같은 내용으로 다시 예의상 답장하지 마세요. "
        "그 경우에는 회신 도구를 호출하지 말고 화면에는 새로 보낼 답장이 없다는 짧은 처리 요약만 남기세요. "
        "이전 자동 답장에서 이미 차단 사유나 대기 요청을 전달했다면, 반복 업데이트를 보내지 말고 로컬 요약만 남기세요. "
        "sender/from, recipients/to, room/channel, text를 기준으로 누가 누구에게 보낸 DM/그룹 메시지인지 먼저 판단하세요. "
        "알림 본문이 'New message...' 같은 짧은 통지이고 room/message id가 있으면 먼저 사용 가능한 read/get_messages 계열 도구로 실제 메시지를 조회하세요. "
        "metadata의 message_id/source_message_id는 찾아야 할 실제 원본 메시지 id이지 after_id/cursor가 아닙니다. "
        "짧은 통지를 조회할 때 그 id를 after_id/cursor로 쓰지 말고, 같은 room/channel의 최근 메시지를 충분히 읽은 뒤 id가 일치하는 항목을 찾으세요. "
        "DM/업무 지시/상태 확인/컨텍스트 요청처럼 안전한 협업 응답은 로컬 사용자 승인 없이 같은 채널/DM에 답장하거나 필요한 읽기/쓰기 도구를 호출하세요. "
        "로컬 사용자에게 '답장할까요?'처럼 답장 여부를 묻고 멈추지 마세요. "
        "짧은 DM/상태 확인/컨텍스트 요청은 범위를 작게 유지하세요. 실제 메시지를 읽은 뒤 같은 DM/채널에 직접 답장하는 것을 우선하세요. "
        "수신 메시지가 명시적으로 요구하지 않았는데 새 방 생성, 멤버 초대, 태스크 배정, 새 워크플로 시작처럼 범위를 넓히는 작업을 하지 마세요. "
        "승인이 필요한 경우는 실제 결제/투자 실행, credential/secret 공개, 파괴적 파일/시스템/관리자 작업, "
        "명시적으로 승인이 필요한 외부 부작용처럼 되돌리기 어려운 고위험 작업뿐입니다. "
        "단순 온보딩/인사/중복 테스트 메시지를 새로 만들지 말고, 받은 메시지의 요청에 맞게 답장하거나 작업하세요. "
        "'진행하겠습니다', '착수합니다', '보고하겠습니다', '결과를 공유하겠습니다', "
        "'Let me send...', 'I will reply...', 'I'll respond...'처럼 미래 행동을 약속하는 말만 남기고 턴을 끝내지 마세요. "
        "그런 말을 할 상황이면 같은 턴에서 필요한 조사/도구 호출/채널 보고까지 수행하고, 수행할 수 없으면 구체적 차단 사유를 보고하세요. "
        "메시지 metadata source가 claude-any-web-chat 이거나 reply_channel/reply_recipient가 있으면, 답변 내용은 반드시 사용 가능한 claude-any-router send_message 계열 도구로 같은 channel/thread_id에 recipients='web', delivery=['web']로 보내세요. "
        "웹 채팅 요청도 현재 Claude Code 세션의 기존 Read/Bash/Edit/MCP 도구를 사용할 수 있는 실제 작업 요청입니다. "
        "다음 응답에는 사용자가 화면에서 볼 수 있도록 수신 메시지 요약과 수행한 처리 또는 필요한 다음 조치를 간단히 보여주세요. "
        "도구를 호출했다면 tool_result 후속 턴에서 그 결과를 LLM이 다시 검토한 뒤 사용자에게 요약하고, 필요한 경우 후속 답장/작업까지 완료하세요.\n\n"
        + "\n\n".join(parts)
    )


_CHANNEL_LLM_TOOL_CONTEXT_LOCK = threading.Lock()
_CHANNEL_LLM_TOOL_CONTEXT: dict[str, dict[str, Any]] = {}
_CHANNEL_LLM_TOOL_CONTEXT_LIMIT = 200
_CHANNEL_LLM_TOOL_CONTEXT_MAX_INJECT = 8
_CHANNEL_LLM_TOOL_CONTEXT_PROMPT_LIMIT = 4000


def _channel_injected_prompt_text(body: dict[str, Any]) -> str:
    for message in reversed(body.get("messages") or []):
        if not isinstance(message, dict) or message.get("role") != "user":
            continue
        text = anthropic_content_to_text(message.get("content", ""))
        if "[claude-any channel inbox]" in text:
            return truncate_for_prompt(text, _CHANNEL_LLM_TOOL_CONTEXT_PROMPT_LIMIT)
    return ""


def _remember_channel_injected_tool_use(source_body: dict[str, Any] | None, tool_use_id: str, tool_name: str, tool_input: Any) -> None:
    if not isinstance(source_body, dict) or not tool_use_id:
        return
    metadata = source_body.get("metadata") if isinstance(source_body.get("metadata"), dict) else {}
    if not metadata.get("claude_any_channel_injected"):
        return
    context = {
        "created_at": time.time(),
        "channel_message_ids": str(metadata.get("claude_any_channel_message_ids") or ""),
        "prompt": _channel_injected_prompt_text(source_body),
        "tool_name": tool_name,
        "tool_input": tool_input if isinstance(tool_input, (dict, list, str, int, float, bool)) or tool_input is None else str(tool_input),
    }
    with _CHANNEL_LLM_TOOL_CONTEXT_LOCK:
        _CHANNEL_LLM_TOOL_CONTEXT[tool_use_id] = context
        if len(_CHANNEL_LLM_TOOL_CONTEXT) > _CHANNEL_LLM_TOOL_CONTEXT_LIMIT:
            for old_id, _old in sorted(_CHANNEL_LLM_TOOL_CONTEXT.items(), key=lambda item: item[1].get("created_at", 0))[
                : len(_CHANNEL_LLM_TOOL_CONTEXT) - _CHANNEL_LLM_TOOL_CONTEXT_LIMIT
            ]:
                _CHANNEL_LLM_TOOL_CONTEXT.pop(old_id, None)
    router_log(
        "INFO",
        f"channel_llm_tool_context_stored tool_use_id={tool_use_id} tool={tool_name} message_ids={context['channel_message_ids']}",
    )


def remember_channel_injected_tool_uses(source_body: dict[str, Any] | None, message: dict[str, Any]) -> None:
    if not isinstance(message, dict):
        return
    for block in message.get("content") or []:
        if not isinstance(block, dict) or block.get("type") != "tool_use":
            continue
        _remember_channel_injected_tool_use(
            source_body,
            str(block.get("id") or ""),
            str(block.get("name") or "tool"),
            block.get("input"),
        )


def _take_channel_tool_result_contexts_for_body(body: dict[str, Any]) -> list[tuple[str, dict[str, Any]]]:
    found: list[tuple[str, dict[str, Any]]] = []
    with _CHANNEL_LLM_TOOL_CONTEXT_LOCK:
        for message in body.get("messages") or []:
            if not isinstance(message, dict) or message.get("role") != "user":
                continue
            for block in message.get("content") or []:
                if not isinstance(block, dict) or block.get("type") != "tool_result":
                    continue
                tool_use_id = str(block.get("tool_use_id") or "")
                context = _CHANNEL_LLM_TOOL_CONTEXT.pop(tool_use_id, None)
                if context:
                    found.append((tool_use_id, dict(context)))
                    if len(found) >= _CHANNEL_LLM_TOOL_CONTEXT_MAX_INJECT:
                        return found
    return found


def body_with_channel_tool_result_context(body: dict[str, Any]) -> dict[str, Any]:
    metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
    if body.get("claude_any_channel_direct") or metadata.get("claude_any_channel_direct"):
        return body
    if metadata.get("claude_any_channel_tool_result_followup"):
        return body
    contexts = _take_channel_tool_result_contexts_for_body(body)
    if not contexts:
        return body
    parts = [
        "[claude-any channel tool_result follow-up]",
        "아래 tool_result는 이전 턴에서 claude-any가 SSE 알림을 LLM에 주입해 자동 처리한 도구 호출의 결과입니다.",
        "이 결과를 LLM 컨텍스트로 사용해 사용자 화면에 처리 결과를 요약하세요. 필요한 추가 자동 처리는 계속 수행해도 됩니다.",
    ]
    for tool_use_id, context in contexts:
        parts.append(
            "\n".join(
                [
                    f"tool_use_id={tool_use_id}",
                    f"tool={context.get('tool_name') or 'tool'}",
                    f"channel_message_ids={context.get('channel_message_ids') or ''}",
                    f"tool_input={json.dumps(context.get('tool_input'), ensure_ascii=False)}",
                    f"original_channel_prompt:\n{context.get('prompt') or '(not captured)'}",
                ]
            )
        )
    out = dict(body)
    messages = [m for m in body.get("messages", []) if isinstance(m, dict)]
    messages.append({"role": "user", "content": [{"type": "text", "text": "\n\n".join(parts)}]})
    out["messages"] = messages
    out_metadata = dict(metadata)
    out_metadata["claude_any_channel_tool_result_followup"] = True
    out["metadata"] = out_metadata
    router_log(
        "INFO",
        "channel_llm_tool_result_context_injected tool_use_ids="
        + ",".join(tool_use_id for tool_use_id, _context in contexts),
    )
    return out


def _mark_channel_payload_direct_llm_pending(payload: dict[str, Any]) -> dict[str, Any]:
    try:
        cfg = load_config()
        if not should_use_channel_llm_delivery(True, [], cfg):
            return payload
        if _channel_llm_message_skip_reason(payload):
            return payload
    except Exception as exc:
        router_log("WARN", f"channel_llm_direct_mark_failed error={type(exc).__name__}: {exc}")
        return payload
    out = dict(payload)
    meta = dict(out.get("meta") if isinstance(out.get("meta"), dict) else {})
    meta["llm_direct_pending"] = True
    out["meta"] = meta
    return out


def _channel_message_is_direct_llm_owned(message: dict[str, Any]) -> bool:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    return bool(meta.get("llm_direct_pending") or meta.get("llm_direct_delivered"))


def _channel_message_is_web_chat_request(message: dict[str, Any]) -> bool:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    source = str(meta.get("source") or "").strip().lower()
    kind = str(message.get("kind") or meta.get("kind") or "").strip().lower()
    return bool(
        source == "claude-any-web-chat"
        or kind == "web_chat"
        or meta.get("reply_channel")
        or meta.get("reply_recipient")
    )


def _anthropic_message_text(message: dict[str, Any]) -> str:
    parts: list[str] = []
    for block in message.get("content") or []:
        if isinstance(block, dict) and block.get("type") == "text":
            parts.append(str(block.get("text") or ""))
    return "".join(parts).strip()


def _channel_direct_worker_limit() -> int:
    raw = os.environ.get("CLAUDE_ANY_CHANNEL_DIRECT_WORKERS", "1")
    try:
        return max(1, min(8, int(str(raw).strip())))
    except Exception:
        return 1


def _ensure_channel_direct_workers_locked() -> None:
    global _CHANNEL_LLM_DIRECT_WORKERS_STARTED
    wanted = _channel_direct_worker_limit()
    while _CHANNEL_LLM_DIRECT_WORKERS_STARTED < wanted:
        _CHANNEL_LLM_DIRECT_WORKERS_STARTED += 1
        worker_no = _CHANNEL_LLM_DIRECT_WORKERS_STARTED
        thread = threading.Thread(
            target=_channel_direct_llm_queue_worker,
            daemon=True,
            name=f"ca-channel-llm-worker-{worker_no}",
        )
        thread.start()
        router_log("INFO", f"channel_llm_direct_worker_started worker={worker_no} limit={wanted}")


def schedule_channel_direct_llm_delivery(message: dict[str, Any]) -> bool:
    try:
        message_id = int(message.get("id") or 0)
    except Exception:
        message_id = 0
    if message_id <= 0:
        return False
    try:
        cfg = load_config()
        if not should_use_channel_llm_delivery(True, [], cfg):
            return False
        skip_reason = _channel_llm_message_skip_reason(message)
        if skip_reason:
            router_log("INFO", f"channel_llm_direct_skipped message_id={message_id} channel={message.get('channel')} reason={skip_reason}")
            return False
    except Exception as exc:
        router_log("WARN", f"channel_llm_direct_skipped message_id={message_id} error={type(exc).__name__}: {exc}")
        return False
    with _CHANNEL_LLM_DIRECT_LOCK:
        if message_id in _CHANNEL_LLM_DIRECT_INFLIGHT or message_id in _CHANNEL_LLM_DIRECT_DELIVERED:
            return False
        _CHANNEL_LLM_DIRECT_INFLIGHT.add(message_id)
        _CHANNEL_LLM_DIRECT_QUEUE.put(dict(message))
        queue_depth = _CHANNEL_LLM_DIRECT_QUEUE.qsize()
        _ensure_channel_direct_workers_locked()
    router_log("INFO", f"channel_llm_direct_queued message_id={message_id} channel={message.get('channel')} queue_depth={queue_depth}")
    return True


def _channel_direct_llm_queue_worker() -> None:
    while True:
        message = _CHANNEL_LLM_DIRECT_QUEUE.get()
        try:
            _channel_direct_llm_worker(message)
        finally:
            _CHANNEL_LLM_DIRECT_QUEUE.task_done()


def _channel_direct_source_state_name(message: dict[str, Any]) -> str | None:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    source = str(meta.get("sse_source") or meta.get("source") or "").strip()
    if source:
        found = _channel_sse_state_name_for_mcp_server(source)
        if found:
            return found
    with _CHANNEL_SSE_LOCK:
        for name, state in _CHANNEL_SSE_CONNECTIONS.items():
            if str(name).strip().lower() in _NATIVE_ROUTER_CHANNEL_NAMES:
                continue
            if state.get("mcp_initialized"):
                return name
    return None


_CHANNEL_DIRECT_EXACT_TOOL_ALLOWLIST = {
    "echo",
    "send_dm",
    "send_message",
    "reply",
    "send_reply",
    "post_message",
    "post_dm",
}
_CHANNEL_DIRECT_REPLY_TOOL_NAMES = {
    "send_dm",
    "send_message",
    "reply",
    "send_reply",
    "post_message",
    "post_dm",
}
_CHANNEL_DIRECT_READ_TOOL_PREFIXES = ("get_", "list_", "read_", "search_")
_CHANNEL_DIRECT_COLLAB_WRITE_TOOL_PREFIXES = (
    "ack_",
    "add_",
    "assign_",
    "create_",
    "evaluate_",
    "record_",
    "submit_",
)
_CHANNEL_DIRECT_BLOCKED_TOOL_PREFIXES = (
    "clear_",
    "delete_",
    "destroy_",
    "disable_",
    "drop_",
    "kill_",
    "purge_",
    "remove_",
    "reset_",
    "revoke_",
    "shutdown_",
    "stop_",
    "truncate_",
    "wait_",
    "watch_",
)
_CHANNEL_DIRECT_BLOCKED_TOOL_TERMS = (
    "credential",
    "payment",
    "purchase",
    "secret",
    "trade",
    "transfer",
    "withdraw",
)


def _channel_direct_tool_is_allowed(tool_name: str) -> bool:
    normalized = re.sub(r"[^a-z0-9_]+", "_", str(tool_name or "").strip().lower()).strip("_")
    if not normalized:
        return False
    if any(normalized.startswith(prefix) for prefix in _CHANNEL_DIRECT_BLOCKED_TOOL_PREFIXES):
        return False
    if any(term in normalized for term in _CHANNEL_DIRECT_BLOCKED_TOOL_TERMS):
        return False
    if normalized in _CHANNEL_DIRECT_EXACT_TOOL_ALLOWLIST:
        return True
    if any(normalized.startswith(prefix) for prefix in _CHANNEL_DIRECT_READ_TOOL_PREFIXES):
        return True
    return any(normalized.startswith(prefix) for prefix in _CHANNEL_DIRECT_COLLAB_WRITE_TOOL_PREFIXES)


def _channel_direct_tool_is_reply_action(tool_name: str) -> bool:
    normalized = re.sub(r"[^a-z0-9_]+", "_", str(tool_name or "").strip().lower()).strip("_")
    return normalized in _CHANNEL_DIRECT_REPLY_TOOL_NAMES


def _channel_direct_tool_is_collab_write_action(tool_name: str) -> bool:
    normalized = re.sub(r"[^a-z0-9_]+", "_", str(tool_name or "").strip().lower()).strip("_")
    return _channel_direct_tool_is_allowed(normalized) and any(
        normalized.startswith(prefix) for prefix in _CHANNEL_DIRECT_COLLAB_WRITE_TOOL_PREFIXES
    )


def _channel_direct_tool_schema_short_name(tool: dict[str, Any]) -> str:
    parts = _channel_direct_tool_name_parts(str(tool.get("name") or ""))
    if parts is None:
        return str(tool.get("name") or "")
    return parts[1]


def _channel_direct_reply_tool_schemas(tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return [tool for tool in tools if _channel_direct_tool_is_reply_action(_channel_direct_tool_schema_short_name(tool))]


def _channel_direct_reply_followup_tool_schemas(tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return [
        tool
        for tool in tools
        if (
            _channel_direct_tool_is_reply_action(_channel_direct_tool_schema_short_name(tool))
            or _channel_direct_tool_is_collab_write_action(_channel_direct_tool_schema_short_name(tool))
        )
    ]


def _channel_direct_tool_schemas(message: dict[str, Any]) -> list[dict[str, Any]]:
    state_name = _channel_direct_source_state_name(message)
    if not state_name:
        return []
    public_server = _channel_sse_public_mcp_name(state_name)
    try:
        response = _channel_sse_rpc_request(state_name, "tools/list", {}, timeout=20.0)
    except Exception as exc:
        router_log("WARN", f"channel_llm_tools_list_failed server={state_name} error={type(exc).__name__}: {exc}")
        return []
    if response.get("error"):
        router_log("WARN", f"channel_llm_tools_list_failed server={state_name} error={truncate_for_prompt(json.dumps(response.get('error'), ensure_ascii=False), 500)}")
        return []
    result = response.get("result") if isinstance(response.get("result"), dict) else {}
    tools = result.get("tools") if isinstance(result.get("tools"), list) else []
    converted: list[dict[str, Any]] = []
    filtered = 0
    for tool in tools:
        if not isinstance(tool, dict):
            continue
        raw_name = str(tool.get("name") or "").strip()
        if not raw_name:
            continue
        if not _channel_direct_tool_is_allowed(raw_name):
            filtered += 1
            continue
        schema = tool.get("inputSchema") if isinstance(tool.get("inputSchema"), dict) else None
        if schema is None:
            schema = tool.get("input_schema") if isinstance(tool.get("input_schema"), dict) else None
        if schema is None:
            schema = {"type": "object", "properties": {}}
        converted.append(
            {
                "name": f"mcp__{public_server}__{raw_name}",
                "description": str(tool.get("description") or f"MCP tool {raw_name} on {public_server}"),
                "input_schema": schema,
            }
        )
    suffix = f" filtered={filtered}" if filtered else ""
    router_log("INFO", f"channel_llm_tools_list server={state_name} count={len(converted)}{suffix}")
    return converted


def _channel_direct_no_tools_summary(message: dict[str, Any]) -> str:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    source = str(meta.get("sse_source") or meta.get("source") or "channel")
    channel = str(message.get("channel") or meta.get("room_id") or "default")
    incoming = truncate_for_prompt(str(message.get("message") or ""), 1000)
    return (
        "채널 메시지는 수신됐지만 자동 처리에 필요한 MCP 도구 목록을 가져오지 못했습니다. "
        "없는 도구를 가정한 텍스트 명령은 실행하지 않았습니다.\n\n"
        f"- source: {source}\n"
        f"- channel: {channel}\n"
        f"- incoming: {incoming}\n\n"
        "SSE/MCP endpoint가 재연결 중이거나 stale session일 수 있습니다. "
        "다음 알림에서 endpoint가 재초기화되면 자동 처리가 재개됩니다."
    )


def _channel_direct_tool_name_parts(name: str) -> tuple[str, str] | None:
    text = str(name or "").strip()
    if not text.startswith("mcp__"):
        return None
    rest = text[len("mcp__"):]
    if "__" not in rest:
        return None
    server_name, tool_name = rest.split("__", 1)
    server_name = server_name.strip()
    tool_name = tool_name.strip()
    if not server_name or not tool_name:
        return None
    return server_name, tool_name


def _channel_direct_mcp_result_text(response: dict[str, Any]) -> tuple[str, bool]:
    if response.get("error"):
        return json.dumps(response.get("error"), ensure_ascii=False, indent=2), True
    result = response.get("result") if isinstance(response.get("result"), dict) else {}
    is_error = bool(result.get("isError") or result.get("is_error"))
    content = result.get("content")
    parts: list[str] = []
    if isinstance(content, list):
        for block in content:
            if not isinstance(block, dict):
                parts.append(str(block))
                continue
            if block.get("type") == "text":
                parts.append(str(block.get("text") or ""))
            else:
                parts.append(json.dumps(block, ensure_ascii=False))
    elif content is not None:
        parts.append(anthropic_content_to_text(content))
    if not parts:
        parts.append(json.dumps(result, ensure_ascii=False, indent=2))
    return "\n".join(part for part in parts if part).strip(), is_error


def _channel_direct_execute_tool(tool_use: dict[str, Any]) -> tuple[str, bool]:
    tool_id = str(tool_use.get("id") or "")
    name = str(tool_use.get("name") or "")
    parts = _channel_direct_tool_name_parts(name)
    if parts is None:
        return f"Unsupported automatic channel tool: {name}", True
    server_name, tool_name = parts
    if not _channel_direct_tool_is_allowed(tool_name):
        router_log("WARN", f"channel_llm_tool_blocked tool_use_id={tool_id} tool={tool_name}")
        return f"Automatic channel handling is not allowed to call tool {tool_name}", True
    state_name = _channel_sse_state_name_for_mcp_server(server_name)
    if not state_name:
        return f"MCP SSE server for tool {name} is not connected", True
    arguments = tool_use.get("input") if isinstance(tool_use.get("input"), dict) else {}
    router_log("INFO", f"channel_llm_tool_call tool_use_id={tool_id} server={state_name} tool={tool_name}")
    try:
        response = _channel_sse_rpc_request(
            state_name,
            "tools/call",
            {"name": tool_name, "arguments": arguments},
            timeout=120.0,
        )
        text, is_error = _channel_direct_mcp_result_text(response)
        router_log(
            "INFO",
            f"channel_llm_tool_result_forwarded tool_use_id={tool_id} server={state_name} tool={tool_name} chars={len(text)} is_error={is_error}",
        )
        return text or "(empty MCP tool result)", is_error
    except Exception as exc:
        router_log("WARN", f"channel_llm_tool_call_failed tool_use_id={tool_id} server={state_name} tool={tool_name} error={type(exc).__name__}: {exc}")
        return f"{type(exc).__name__}: {exc}", True


def _channel_direct_reply_tool_content(tool_use: dict[str, Any]) -> str:
    arguments = tool_use.get("input") if isinstance(tool_use.get("input"), dict) else {}
    for key in ("content", "message", "text", "body"):
        if key in arguments:
            return str(arguments.get(key) or "")
    return ""


def _channel_direct_text_contains_no_reply_directive(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    lowered = body.lower()
    return bool(
        _channel_direct_text_declines_reply(body)
        or re.search(r"\bno[_ -]?reply\s*:", lowered)
        or "no reply is needed" in lowered
        or "no response needed" in lowered
        or "reply is not needed" in lowered
        or "회신 불필요" in body
        or "응답 불필요" in body
        or "추가 답장은 불필요" in body
    )


def _channel_direct_reply_content_should_suppress(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    lowered = body.lower()
    if _channel_direct_text_contains_no_reply_directive(body):
        return True
    if _channel_direct_fallback_text_is_diagnostic_failure(body):
        return True
    internal_markers = (
        "claude-any",
        "## tool_result",
        "tool_result",
        "direct_handler_summary",
        "background auto handling summary",
        "background message handling summary",
        "백그라운드 자동 처리 요약",
        "fallback loop",
        "fallback 루프",
        "fallback 라우터",
        "claude-any 라우터",
        "claude-any router",
        "system-generated activity alert",
        "kind: activity",
        "사용자 액션 필요",
    )
    return any(marker in lowered for marker in internal_markers)


_CHANNEL_DIRECT_MESSAGE_CURSOR_KEYS = (
    "after_id",
    "after",
    "before_id",
    "before",
    "cursor",
    "page_token",
    "next_cursor",
)
_CHANNEL_DIRECT_MESSAGE_LIMIT_KEYS = ("limit", "count", "max_results", "page_size")


def _channel_direct_tool_reads_messages(tool_name: str) -> bool:
    normalized = re.sub(r"[^a-z0-9_]+", "_", str(tool_name or "").strip().lower()).strip("_")
    return bool(normalized and "message" in normalized and normalized.startswith(("get_", "list_", "read_", "search_")))


def _channel_direct_message_has_reference_notification(message: dict[str, Any]) -> bool:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    if not (meta.get("message_id") or meta.get("source_message_id")):
        return False
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip().lower()
    kind = str(meta.get("kind") or meta.get("event") or meta.get("type") or "").strip().lower()
    if kind in {"activity", "message", "messages", "mention", "mentioned", "dm", "direct", "direct_message"}:
        return True
    return bool(
        "new message" in body
        or "mentioned" in body
        or "mention" in body
        or "dm" in body
        or "direct message" in body
        or "멘션" in body
    )


def _channel_direct_prepare_read_tool_use(tool_use: dict[str, Any], message: dict[str, Any]) -> dict[str, Any]:
    parts = _channel_direct_tool_name_parts(str(tool_use.get("name") or ""))
    if parts is None or not _channel_direct_tool_reads_messages(parts[1]):
        return tool_use
    if not _channel_direct_message_has_reference_notification(message):
        return tool_use
    raw_input = tool_use.get("input") if isinstance(tool_use.get("input"), dict) else {}
    adjusted = dict(raw_input)
    removed = [key for key in _CHANNEL_DIRECT_MESSAGE_CURSOR_KEYS if key in adjusted]
    for key in removed:
        adjusted.pop(key, None)
    if not any(key in adjusted for key in _CHANNEL_DIRECT_MESSAGE_LIMIT_KEYS):
        adjusted["limit"] = 20
    else:
        for key in _CHANNEL_DIRECT_MESSAGE_LIMIT_KEYS:
            if key not in adjusted:
                continue
            try:
                current = int(float(adjusted.get(key) or 0))
            except Exception:
                current = 0
            if current < 20:
                adjusted[key] = 20
            break
    if adjusted == raw_input:
        return tool_use
    out = dict(tool_use)
    out["input"] = adjusted
    if removed:
        meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
        router_log(
            "INFO",
            "channel_llm_read_cursor_normalized "
            f"tool={parts[1]} message_ref={meta.get('message_id') or meta.get('source_message_id') or ''} "
            f"removed={','.join(removed)}",
        )
    return out


def _channel_direct_tool_uses(message: dict[str, Any]) -> list[dict[str, Any]]:
    found: list[dict[str, Any]] = []
    for block in message.get("content") or []:
        if isinstance(block, dict) and block.get("type") == "tool_use":
            found.append(block)
    return found


_CHANNEL_DIRECT_DEFERRED_ACTION_PATTERNS: tuple[re.Pattern[str], ...] = (
    re.compile(
        r"\b(?:let me|i(?:'|’)ll|i will|i need to|i should|now i(?:'| will)?|next,?\s*i(?:'| will)?)\b"
        r".{0,220}\b(?:send|respond|reply|post|message|dm|report|share|follow\s*up|call|fetch|check|read|look\s*up)\b",
        re.IGNORECASE | re.DOTALL,
    ),
    re.compile(
        r"\b(?:send|respond|reply|post|message|dm|report|share|follow\s*up)\b"
        r".{0,120}\b(?:now|next)\b",
        re.IGNORECASE | re.DOTALL,
    ),
    re.compile(
        r"(?:진행|착수|보고|공유|전송|답장|회신|확인|조회|조사|처리)"
        r".{0,80}(?:하겠습니다|하겠다|하겠어요|할게요|하겠습니다\.|하겠습니다!)",
        re.DOTALL,
    ),
    re.compile(
        r"(?:보내겠습니다|답장하겠습니다|회신하겠습니다|보고하겠습니다|공유하겠습니다|처리하겠습니다)",
        re.DOTALL,
    ),
    re.compile(
        r"(?:이제|먼저|다음으로).{0,100}(?:보내|답장|회신|보고|공유|조회|확인|조사|처리)"
        r".{0,80}(?:하겠습니다|하겠다|합니다)",
        re.DOTALL,
    ),
)


_CHANNEL_DIRECT_MAX_ROUTER_TURNS = 10


def _channel_direct_text_is_deferred_action(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    completed_markers = (
        "sent",
        "replied",
        "responded",
        "posted",
        "completed",
        "done",
        "전송 완료",
        "답장 완료",
        "회신 완료",
        "처리 완료",
        "보고 완료",
        "공유 완료",
    )
    lowered = body.lower()
    if any(marker in lowered for marker in completed_markers[:6]) or any(marker in body for marker in completed_markers[6:]):
        return False
    return any(pattern.search(body) for pattern in _CHANNEL_DIRECT_DEFERRED_ACTION_PATTERNS)


def _channel_direct_deferred_action_prompt(text: str) -> str:
    return (
        "[claude-any channel action required]\n"
        "Your previous assistant message announced a future safe channel action instead of performing it. "
        "Do not end this autonomous channel turn with 'Let me...', 'I will...', or '하겠습니다'. "
        "If the action is safe and an MCP tool can perform it, call the appropriate tool now. "
        "If no tool can perform it, give a concise blocker summary instead.\n\n"
        "previous_assistant_text="
        + json.dumps(truncate_for_prompt(str(text or ""), 2000), ensure_ascii=False)
    )


def _channel_direct_message_requires_reply(message: dict[str, Any]) -> bool:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    kind = str(meta.get("kind") or meta.get("event") or meta.get("type") or "").strip().lower()
    body = re.sub(r"\s+", " ", str(message.get("message") or "")).strip().lower()
    has_message_reference = bool(meta.get("message_id") or meta.get("source_message_id"))
    if kind in {"activity", "message", "messages", "mention", "mentioned", "dm", "direct", "direct_message"}:
        return True
    if has_message_reference and any(marker in body for marker in ("new message", "mentioned", "mention", "dm", "direct message")):
        return True
    if body.startswith("new message from ") or "new message from " in body:
        return True
    if "mentioned you" in body or "멘션" in body:
        return True
    return False


def _channel_direct_text_declines_reply(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    lowered = body.lower()
    return (
        lowered.startswith("no_reply:")
        or lowered.startswith("no reply:")
        or lowered.startswith("no response needed")
        or lowered.startswith("no reply needed")
        or body.startswith("응답 불필요:")
        or body.startswith("회신 불필요:")
    )


def _channel_direct_reply_action_prompt(text: str) -> str:
    return (
        "[claude-any channel reply required]\n"
        "You handled an incoming channel notification but ended without calling any reply/send tool. "
        "This inbox is attached to the current agent's configured MCP/channel credentials; do not conclude you are not the recipient merely from a room name, DM label, or terse 'New message from ...' notification. "
        "If recipient identity is uncertain, use safe read/profile tools before choosing not to reply. "
        "Do not create an automatic reply loop: acknowledgements, thanks, readiness/waiting confirmations, or progress updates that ask no new question and assign no new task should end with NO_REPLY: instead of sending another courtesy reply. "
        "If the incoming message is addressed to this agent, asks for status/context, or expects a normal collaboration response, "
        "call the appropriate safe channel reply tool now. "
        "Do not only summarize. Do not ask the local user whether to reply. "
        "If and only if no channel reply is appropriate, end with a concise final answer that starts exactly with NO_REPLY:.\n\n"
        "previous_assistant_text="
        + json.dumps(truncate_for_prompt(str(text or ""), 2000), ensure_ascii=False)
    )


def _channel_direct_max_turns_summary(message: dict[str, Any], last_text: str, tool_turns: int) -> str:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    channel = str(message.get("channel") or meta.get("room_id") or "default")
    incoming = truncate_for_prompt(str(message.get("message") or ""), 1000)
    previous = truncate_for_prompt(str(last_text or ""), 1200)
    return (
        "채널 메시지 자동 처리가 도구 호출 한도에 도달해 완료되지 않았습니다. "
        "마지막 응답이 미래 행동 약속이어서 실제 처리 완료로 표시하지 않았습니다.\n\n"
        f"- channel: {channel}\n"
        f"- incoming: {incoming}\n"
        f"- tool_turns: {tool_turns}\n"
        f"- last_assistant_text: {previous}"
    )


def _channel_direct_reply_required_summary(message: dict[str, Any], last_text: str, tool_turns: int) -> str:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    channel = str(message.get("channel") or meta.get("room_id") or "default")
    incoming = truncate_for_prompt(str(message.get("message") or ""), 1000)
    previous = truncate_for_prompt(str(last_text or ""), 1200)
    return (
        "채널 DM/멘션은 수신됐지만 reply/send 도구 호출 없이 자동 처리가 종료되어 실제 회신하지 않았습니다. "
        "이 결과는 완료가 아니라 미처리 상태입니다.\n\n"
        f"- channel: {channel}\n"
        f"- incoming: {incoming}\n"
        f"- tool_turns: {tool_turns}\n"
        f"- last_assistant_text: {previous}"
    )


def _channel_direct_schema_properties(tool: dict[str, Any]) -> dict[str, Any]:
    schema = tool.get("input_schema") if isinstance(tool.get("input_schema"), dict) else {}
    props = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
    return props


def _channel_direct_pick_schema_key(props: dict[str, Any], candidates: tuple[str, ...], default: str) -> str:
    for key in candidates:
        if key in props:
            return key
    return default


def _channel_direct_same_channel_reply_tool(tools: list[dict[str, Any]]) -> dict[str, Any] | None:
    preferred = (
        "send_message",
        "post_message",
        "reply_message",
        "send_channel_message",
        "post_channel_message",
    )
    for wanted in preferred:
        for tool in tools:
            if _channel_direct_tool_schema_short_name(tool) == wanted:
                return tool
    return None


def _channel_direct_fallback_reply_prompt(message: dict[str, Any], last_text: str) -> str:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    author = str(meta.get("author_name") or message.get("sender_id") or "the sender")
    channel = str(message.get("channel") or meta.get("room_id") or "default")
    incoming = truncate_for_prompt(str(message.get("message") or ""), 2000)
    previous = truncate_for_prompt(str(last_text or ""), 2000)
    return (
        "[claude-any channel fallback reply]\n"
        "The incoming channel DM/mention still needs a practical reply, but previous assistant turns did not call a reply/send tool. "
        "Write only the exact message body that should be sent now to the same channel. "
        "Do not mention internal routing, fallback handling, tool failures, or this instruction. "
        "If tool results in this conversation show that a room was created or an action succeeded, include the useful public details. "
        "If and only if the sender's latest message truly requires no response, output exactly 'NO_REPLY:' followed by one concise reason. "
        "Otherwise produce a concise collaboration reply in the agent's voice.\n\n"
        f"sender={author}\n"
        f"channel={channel}\n"
        f"incoming_notification={incoming}\n"
        "previous_assistant_text="
        + json.dumps(previous, ensure_ascii=False)
    )


def _channel_direct_message_actor(message: dict[str, Any]) -> str:
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    author = str(meta.get("author_name") or "").strip()
    if author:
        return author
    incoming = str(message.get("message") or "")
    for pattern in (
        r"\bNew message from\s+([A-Za-z0-9_.@ -]{1,80})",
        r"\b([A-Za-z0-9_.@ -]{1,80})\s+@mentioned you\b",
    ):
        match = re.search(pattern, incoming, re.IGNORECASE)
        if match:
            actor = re.sub(r"\s+", " ", match.group(1)).strip(" -")
            if actor:
                return actor
    return "there"


def _channel_direct_generate_fallback_reply_text(
    message_id: int,
    messages: list[dict[str, Any]],
    message: dict[str, Any],
    last_text: str,
    provider: str,
    pcfg: dict[str, Any],
    model: str,
) -> str:
    body: dict[str, Any] = {
        "model": model,
        "max_tokens": 512,
        "stream": False,
        "metadata": {
            "claude_any_channel_direct": True,
            "channel_message_id": str(message_id),
            "channel_fallback_reply": True,
        },
        "messages": messages
        + [
            {
                "role": "user",
                "content": [{"type": "text", "text": _channel_direct_fallback_reply_prompt(message, last_text)}],
            }
        ],
    }
    try:
        parsed = _channel_direct_llm_http_message(message_id, body, provider, pcfg, model)
    except Exception as exc:
        router_log("WARN", f"channel_llm_fallback_reply_generation_failed message_id={message_id} error={type(exc).__name__}: {exc}")
        return ""
    return _anthropic_message_text(parsed).strip()


def _channel_direct_fallback_reply_text(message: dict[str, Any], last_text: str) -> str:
    author = _channel_direct_message_actor(message)
    prior = re.sub(r"\s+", " ", str(last_text or "")).strip()
    if (
        prior
        and prior.lower().strip(" .!。") not in {"acknowledged", "ok", "okay", "understood", "noted", "확인했습니다", "알겠습니다"}
        and not _channel_direct_text_declines_reply(prior)
        and not _channel_direct_text_is_deferred_action(prior)
        and not _channel_direct_fallback_text_is_internal_action(prior)
    ):
        return truncate_for_prompt(prior, 1200)
    return (
        f"{author}, 메시지 확인했습니다. 현재 채널 컨텍스트를 확인했고 필요한 조치를 이어서 진행하겠습니다. "
        "세부 상태를 정리해서 다시 공유하겠습니다."
    )


def _channel_direct_fallback_text_is_internal_action(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    lowered = body.lower()
    if _channel_direct_fallback_text_is_diagnostic_failure(text):
        return True
    if re.match(r"^(?:right[,. ]*)?(?:let me|i should|i need to|i will|i'll|now let me)\b", lowered):
        return True
    if re.search(
        r"\b(?:let me|i should|i need to|i will|i'll|now let me)\s+"
        r"(?:check|fetch|read|send|reply|respond|acknowledge|provide|post|create|look|investigate|try|assess|confirm|share|continue|do)\b",
        lowered,
    ):
        return True
    if re.match(r"^(?:i have been|i've been|i am|i'm)\s+(?:scrolling|checking|looking|reading|trying|fetching)\b", lowered):
        return True
    if re.match(r"^(?:i cannot|i can't|unable to|could not)\s+(?:fetch|read|retrieve|access)\b", lowered):
        return True
    if "messages i've retrieved" in lowered or "messages i have retrieved" in lowered:
        return True
    if "notification-only event" in lowered or "hasn't appeared in the messages" in lowered:
        return True
    return bool(re.match(r"^(?:이제|먼저|다음으로)\b.{0,120}(?:보내|답장|회신|조회|확인|조사|처리).{0,80}(?:하겠습니다|하겠다|합니다)", body))


def _channel_direct_fallback_text_is_diagnostic_failure(text: str) -> bool:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return False
    lowered = body.lower()
    internal_markers = (
        "[claude-any]",
        "upstream model",
        "empty end_turn",
        "no work was performed",
        "retry or ask me to continue",
        "internal routing",
        "fallback handling",
        "reply/send tool",
        "tool_result",
    )
    return any(marker in lowered for marker in internal_markers)


def _channel_direct_send_same_channel_fallback_reply(
    message_id: int,
    message: dict[str, Any],
    tools: list[dict[str, Any]],
    reply_text: str,
) -> tuple[str, bool]:
    text = str(reply_text or "").strip()
    if not text or _channel_direct_text_declines_reply(text):
        return text, False
    tool = _channel_direct_same_channel_reply_tool(tools)
    if not tool:
        return "No same-channel reply tool is available", False
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    channel = str(message.get("channel") or meta.get("room_id") or "").strip()
    if not channel:
        return "No channel/room id is available for fallback reply", False
    props = _channel_direct_schema_properties(tool)
    room_key = _channel_direct_pick_schema_key(
        props,
        ("room_id", "channel_id", "channel", "room", "conversation_id", "thread_id"),
        "room_id",
    )
    content_key = _channel_direct_pick_schema_key(
        props,
        ("content", "message", "text", "body"),
        "content",
    )
    tool_use = {
        "id": f"channel_fallback_reply_{message_id}",
        "name": str(tool.get("name") or ""),
        "input": {room_key: channel, content_key: text},
    }
    result_text, is_error = _channel_direct_execute_tool(tool_use)
    return result_text, not is_error


def _channel_direct_fallback_reply_summary(reply_text: str, tool_result: str) -> str:
    sent = truncate_for_prompt(str(reply_text or "").strip(), 4000)
    result = _channel_direct_fallback_tool_result_summary(tool_result)
    return (
        "reply-required 채널 메시지가 일반 재시도에서는 reply/send 호출 없이 끝나서, "
        "라우터가 같은 채널에 안전 fallback 회신을 직접 전송했습니다.\n\n"
        "## 보낸 메시지\n"
        f"{sent}\n\n"
        "## 전송 결과\n"
        f"{result}"
    )


def _channel_direct_fallback_tool_result_summary(tool_result: str) -> str:
    raw = str(tool_result or "").strip()
    if not raw:
        return "tool returned an empty result"
    try:
        parsed = json.loads(raw)
    except Exception:
        return truncate_for_prompt(re.sub(r"\s+", " ", raw), 500)
    if not isinstance(parsed, dict):
        return truncate_for_prompt(re.sub(r"\s+", " ", raw), 500)
    success = parsed.get("success")
    data = parsed.get("data") if isinstance(parsed.get("data"), dict) else {}
    fields: list[str] = []
    if success is not None:
        fields.append(f"success={bool(success)}")
    for key in ("id", "message_id", "room_id", "channel", "sender_name", "created_at"):
        value = data.get(key) if key in data else parsed.get(key)
        if value:
            fields.append(f"{key}={value}")
    if fields:
        return ", ".join(fields)
    if parsed.get("error"):
        return "error=" + truncate_for_prompt(json.dumps(parsed.get("error"), ensure_ascii=False), 400)
    return truncate_for_prompt(json.dumps(parsed, ensure_ascii=False, separators=(",", ":")), 500)


def _channel_direct_append_summary(message: dict[str, Any], text: str, stop_reason: str, tool_turns: int = 0) -> None:
    try:
        message_id = int(message.get("id") or 0)
    except Exception:
        message_id = 0
    if message_id <= 0:
        return
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    record = {
        "message_id": message_id,
        "created_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "channel": message.get("channel") or meta.get("room_id") or "default",
        "source": meta.get("sse_source") or meta.get("source") or "",
        "sender_id": message.get("sender_id") or meta.get("sender_id") or "",
        "stop_reason": stop_reason,
        "tool_turns": tool_turns,
        "incoming": truncate_for_prompt(str(message.get("message") or ""), 4000),
        "summary": truncate_for_prompt(text.strip(), 12000),
    }
    try:
        CHANNEL_LLM_SUMMARY_QUEUE_PATH.parent.mkdir(parents=True, exist_ok=True)
        line = json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n"
        with _CHANNEL_LLM_SUMMARY_LOCK:
            with CHANNEL_LLM_SUMMARY_QUEUE_PATH.open("a", encoding="utf-8") as fh:
                fh.write(line)
        router_log("INFO", f"channel_llm_summary_queued message_id={message_id} chars={len(record['summary'])} stop_reason={stop_reason}")
    except Exception as exc:
        router_log("WARN", f"channel_llm_summary_queue_failed message_id={message_id} error={type(exc).__name__}: {exc}")


def _channel_direct_llm_http_message(message_id: int, body: dict[str, Any], provider: str, pcfg: dict[str, Any], model: str) -> dict[str, Any]:
    data = json.dumps(body, ensure_ascii=False).encode("utf-8")
    headers = {
        "content-type": "application/json",
        "anthropic-version": "2023-06-01",
        "x-claude-any-channel-direct": "1",
    }
    timeout = max(30.0, min(provider_request_timeout_seconds(pcfg), 300.0))
    router_log(
        "INFO",
        f"channel_llm_direct_router_request message_id={message_id} provider={provider} model={model} bytes={len(data)}",
    )
    req = urllib.request.Request(join_url(ROUTER_BASE, "/v1/messages"), data=data, headers=headers, method="POST")
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        raw = resp.read(2_000_000).decode("utf-8", errors="replace")
    parsed = json.loads(raw)
    if not isinstance(parsed, dict):
        raise RuntimeError("direct router response was not a JSON object")
    return parsed


def _channel_direct_llm_http_response(message_id: int, prompt: str, provider: str, pcfg: dict[str, Any], model: str) -> tuple[str, str]:
    body = {
        "model": model,
        "max_tokens": 512,
        "stream": False,
        "metadata": {
            "claude_any_channel_direct": True,
            "channel_message_id": str(message_id),
        },
        "messages": [{"role": "user", "content": [{"type": "text", "text": prompt}]}],
    }
    parsed = _channel_direct_llm_http_message(message_id, body, provider, pcfg, model)
    text = _anthropic_message_text(parsed) if isinstance(parsed, dict) else ""
    stop_reason = str(parsed.get("stop_reason") if isinstance(parsed, dict) else "")
    return text, stop_reason


def _channel_direct_llm_router_response(message_id: int, prompt: str, message: dict[str, Any], provider: str, pcfg: dict[str, Any], model: str) -> tuple[str, str, int]:
    tools = _channel_direct_tool_schemas(message)
    if not tools:
        router_log("WARN", f"channel_llm_no_tools message_id={message_id}")
        return _channel_direct_no_tools_summary(message), "no_tools", 0
    messages: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "text", "text": prompt}]}]
    last_text = ""
    stop_reason = ""
    tool_turns = 0
    deferred_action_retried = False
    reply_action_retried = False
    reply_required = _channel_direct_message_requires_reply(message)
    executed_tool_names: list[str] = []
    for _turn in range(_CHANNEL_DIRECT_MAX_ROUTER_TURNS):
        reply_already_attempted = any(_channel_direct_tool_is_reply_action(name) for name in executed_tool_names)
        active_tools = tools
        if reply_required and reply_action_retried and not reply_already_attempted:
            reply_tools = _channel_direct_reply_tool_schemas(tools)
            if not reply_tools:
                router_log("WARN", f"channel_llm_reply_required_no_tool message_id={message_id} turn={_turn + 1}")
                return _channel_direct_reply_required_summary(message, last_text, tool_turns), "reply_required_no_tool", tool_turns
            active_tools = _channel_direct_reply_followup_tool_schemas(tools)
        body: dict[str, Any] = {
            "model": model,
            "max_tokens": 768,
            "stream": False,
            "metadata": {
                "claude_any_channel_direct": True,
                "channel_message_id": str(message_id),
            },
            "messages": messages,
        }
        if active_tools:
            body["tools"] = active_tools
        parsed = _channel_direct_llm_http_message(message_id, body, provider, pcfg, model)
        stop_reason = str(parsed.get("stop_reason") or "")
        text = _anthropic_message_text(parsed)
        if text.strip():
            last_text = text
        tool_uses = _channel_direct_tool_uses(parsed)
        messages.append({"role": "assistant", "content": parsed.get("content") or []})
        if not tool_uses:
            if (
                tools
                and reply_required
                and not any(_channel_direct_tool_is_reply_action(name) for name in executed_tool_names)
                and not _channel_direct_text_declines_reply(text)
            ):
                if reply_action_retried and not deferred_action_retried and _channel_direct_text_is_deferred_action(text):
                    deferred_action_retried = True
                    router_log("INFO", f"channel_llm_deferred_action_retry message_id={message_id} turn={_turn + 1} reason=reply_required")
                    messages.append(
                        {
                            "role": "user",
                            "content": [{"type": "text", "text": _channel_direct_deferred_action_prompt(text)}],
                        }
                    )
                    continue
                if not reply_action_retried:
                    reply_action_retried = True
                    router_log("INFO", f"channel_llm_reply_action_retry message_id={message_id} turn={_turn + 1}")
                    messages.append(
                        {
                            "role": "user",
                            "content": [{"type": "text", "text": _channel_direct_reply_action_prompt(text)}],
                        }
                    )
                    continue
                router_log("WARN", f"channel_llm_reply_required_unfulfilled message_id={message_id} turn={_turn + 1}")
                if _channel_direct_same_channel_reply_tool(tools):
                    fallback_text = _channel_direct_generate_fallback_reply_text(
                        message_id,
                        messages,
                        message,
                        text or last_text,
                        provider,
                        pcfg,
                        model,
                    )
                    if _channel_direct_text_declines_reply(fallback_text):
                        return fallback_text, "end_turn", tool_turns
                    if _channel_direct_fallback_text_is_diagnostic_failure(fallback_text):
                        router_log(
                            "WARN",
                            f"channel_llm_fallback_reply_unsafe_not_sent message_id={message_id} chars={len(fallback_text)}",
                        )
                        return _channel_direct_reply_required_summary(message, text or last_text, tool_turns), "reply_required_unfulfilled", tool_turns
                    if _channel_direct_fallback_text_is_internal_action(fallback_text) or not fallback_text.strip():
                        fallback_text = _channel_direct_fallback_reply_text(message, text or last_text)
                    fallback_result, fallback_sent = _channel_direct_send_same_channel_fallback_reply(
                        message_id,
                        message,
                        tools,
                        fallback_text,
                    )
                    if fallback_sent:
                        router_log("INFO", f"channel_llm_fallback_reply_sent message_id={message_id} chars={len(fallback_text)}")
                        return (
                            _channel_direct_fallback_reply_summary(fallback_text, fallback_result),
                            "fallback_reply_sent",
                            tool_turns + 1,
                        )
                    router_log("WARN", f"channel_llm_fallback_reply_failed message_id={message_id} result={truncate_for_prompt(fallback_result, 300)}")
                return _channel_direct_reply_required_summary(message, text, tool_turns), "reply_required_unfulfilled", tool_turns
            if tools and not deferred_action_retried and _channel_direct_text_is_deferred_action(text):
                deferred_action_retried = True
                router_log("INFO", f"channel_llm_deferred_action_retry message_id={message_id} turn={_turn + 1}")
                messages.append(
                    {
                        "role": "user",
                        "content": [{"type": "text", "text": _channel_direct_deferred_action_prompt(text)}],
                    }
                )
                continue
            return last_text, stop_reason or "end_turn", tool_turns
        tool_turns += 1
        results: list[dict[str, Any]] = []
        allowed_tool_names = {str(tool.get("name") or "") for tool in active_tools}
        for tool_use in tool_uses:
            requested_name = str(tool_use.get("name") or "")
            parts = _channel_direct_tool_name_parts(str(tool_use.get("name") or ""))
            if requested_name not in allowed_tool_names:
                router_log("WARN", f"channel_llm_tool_not_in_turn_schema tool_use_id={tool_use.get('id')} tool={requested_name}")
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": str(tool_use.get("id") or ""),
                        "content": f"Tool {requested_name} is not available for this reply-required turn. Use an available reply/send tool or end with NO_REPLY: if no reply is appropriate.",
                        "is_error": True,
                    }
                )
                continue
            if parts is not None:
                if _channel_direct_tool_is_reply_action(parts[1]):
                    reply_content = _channel_direct_reply_tool_content(tool_use)
                    if _channel_direct_reply_content_should_suppress(reply_content):
                        router_log(
                            "WARN",
                            f"channel_llm_reply_suppressed_internal_content tool_use_id={tool_use.get('id')} tool={parts[1]} chars={len(reply_content)}",
                        )
                        results.append(
                            {
                                "type": "tool_result",
                                "tool_use_id": str(tool_use.get("id") or ""),
                                "content": (
                                    "Automatic channel reply was suppressed because the proposed message body contained "
                                    "an internal no-reply directive or router/tool diagnostic text. End with NO_REPLY: "
                                    "if no external response is needed, or call a reply tool again with only the public message body."
                                ),
                                "is_error": False,
                            }
                        )
                        continue
                executed_tool_names.append(parts[1])
            tool_use_for_call = _channel_direct_prepare_read_tool_use(tool_use, message)
            result_text, is_error = _channel_direct_execute_tool(tool_use_for_call)
            results.append(
                {
                    "type": "tool_result",
                    "tool_use_id": str(tool_use.get("id") or ""),
                    "content": result_text,
                    "is_error": is_error,
                }
            )
        messages.append({"role": "user", "content": results})
    if _channel_direct_text_is_deferred_action(last_text):
        return _channel_direct_max_turns_summary(message, last_text, tool_turns), "max_tool_turns", tool_turns
    if reply_required and not any(_channel_direct_tool_is_reply_action(name) for name in executed_tool_names):
        if _channel_direct_same_channel_reply_tool(tools):
            fallback_text = _channel_direct_generate_fallback_reply_text(
                message_id,
                messages,
                message,
                last_text,
                provider,
                pcfg,
                model,
            )
            if _channel_direct_text_declines_reply(fallback_text):
                return fallback_text, "end_turn", tool_turns
            if _channel_direct_fallback_text_is_diagnostic_failure(fallback_text):
                router_log(
                    "WARN",
                    f"channel_llm_fallback_reply_unsafe_not_sent message_id={message_id} chars={len(fallback_text)} reason=max_turns",
                )
                return _channel_direct_reply_required_summary(message, last_text, tool_turns), "reply_required_unfulfilled", tool_turns
            if _channel_direct_fallback_text_is_internal_action(fallback_text) or not fallback_text.strip():
                fallback_text = _channel_direct_fallback_reply_text(message, last_text)
            fallback_result, fallback_sent = _channel_direct_send_same_channel_fallback_reply(
                message_id,
                message,
                tools,
                fallback_text,
            )
            if fallback_sent:
                router_log("INFO", f"channel_llm_fallback_reply_sent message_id={message_id} chars={len(fallback_text)} reason=max_turns")
                return (
                    _channel_direct_fallback_reply_summary(fallback_text, fallback_result),
                    "fallback_reply_sent",
                    tool_turns + 1,
                )
            router_log("WARN", f"channel_llm_fallback_reply_failed message_id={message_id} result={truncate_for_prompt(fallback_result, 300)} reason=max_turns")
        return _channel_direct_reply_required_summary(message, last_text, tool_turns), "reply_required_unfulfilled", tool_turns
    return last_text, "max_tool_turns", tool_turns


def _channel_direct_terminal_notice(message: dict[str, Any], text: str, stop_reason: str) -> None:
    if not env_bool(os.environ.get("CLAUDE_ANY_CHANNEL_TERMINAL_NOTICE"), False):
        return
    if not text.strip():
        return
    try:
        if not sys.stdout.isatty():
            return
    except Exception:
        return
    meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
    author = str(meta.get("author_name") or message.get("sender_id") or "channel")
    channel = str(message.get("channel") or meta.get("room_id") or "default")
    message_id = str(message.get("id") or "-")
    body = truncate_for_prompt(text.strip(), 3000)
    try:
        sys.stdout.write(
            "\n\n"
            f"[claude-any channel] handled message_id={message_id} channel={channel} from={author} stop_reason={stop_reason}\n"
            f"{body}\n"
        )
        sys.stdout.flush()
    except Exception:
        pass


def _channel_direct_llm_worker(message: dict[str, Any]) -> None:
    global _CHANNEL_LLM_CURSOR_LAST_ID
    try:
        message_id = int(message.get("id") or 0)
    except Exception:
        message_id = 0
    try:
        cfg = load_config()
        provider, pcfg = get_current_provider(cfg)
        model = current_alias(cfg)
        prompt = format_channel_llm_batch_prompt([message])
        text, stop_reason, tool_turns = _channel_direct_llm_router_response(message_id, prompt, message, provider, pcfg, model)
        if stop_reason == "no_tools":
            router_log("WARN", f"channel_llm_direct_unhandled message_id={message_id} reason=no_tools")
            router_log("INFO", f"channel_llm_summary_skipped message_id={message_id} reason=no_tools")
        else:
            with _CHANNEL_LLM_DIRECT_LOCK:
                _CHANNEL_LLM_DIRECT_DELIVERED.add(message_id)
                if len(_CHANNEL_LLM_DIRECT_DELIVERED) > 1000:
                    for old_id in sorted(_CHANNEL_LLM_DIRECT_DELIVERED)[:500]:
                        _CHANNEL_LLM_DIRECT_DELIVERED.discard(old_id)
            with _CHANNEL_LLM_CURSOR_LOCK:
                current = _channel_llm_read_cursor_locked()
                if message_id > current:
                    _CHANNEL_LLM_CURSOR_LAST_ID = message_id
                    _channel_llm_write_cursor_locked(message_id)
            _channel_direct_append_summary(message, text, stop_reason, tool_turns=tool_turns)
        router_log("INFO", f"channel_llm_direct_response message_id={message_id} chars={len(text)} stop_reason={stop_reason} tool_turns={tool_turns}")
        _channel_direct_terminal_notice(message, text, stop_reason)
    except Exception as exc:
        router_log("WARN", f"channel_llm_direct_failed message_id={message_id} error={type(exc).__name__}: {exc}")
    finally:
        with _CHANNEL_LLM_DIRECT_LOCK:
            _CHANNEL_LLM_DIRECT_INFLIGHT.discard(message_id)


def _channel_llm_write_cursor_locked(last_id: int) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    tmp_path = CHANNEL_LLM_CURSOR_PATH.with_suffix(".json.tmp")
    tmp_path.write_text(json.dumps({"last_id": max(0, int(last_id))}, separators=(",", ":")) + "\n", encoding="utf-8")
    tmp_path.replace(CHANNEL_LLM_CURSOR_PATH)


def _channel_llm_read_cursor_locked() -> int:
    global _CHANNEL_LLM_CURSOR_LAST_ID
    if _CHANNEL_LLM_CURSOR_LAST_ID is not None:
        return _CHANNEL_LLM_CURSOR_LAST_ID
    if CHANNEL_LLM_CURSOR_PATH.exists():
        try:
            data = json.loads(CHANNEL_LLM_CURSOR_PATH.read_text(encoding="utf-8"))
            _CHANNEL_LLM_CURSOR_LAST_ID = max(0, int(data.get("last_id") or 0))
            return _CHANNEL_LLM_CURSOR_LAST_ID
        except Exception as exc:
            router_log("WARN", f"channel_llm_cursor_read_failed error={type(exc).__name__}: {exc}")
    _CHANNEL_LLM_CURSOR_LAST_ID = max(0, _chat_scan_max_id())
    try:
        _channel_llm_write_cursor_locked(_CHANNEL_LLM_CURSOR_LAST_ID)
    except Exception:
        pass
    return _CHANNEL_LLM_CURSOR_LAST_ID


def reset_channel_llm_delivery_cursor(last_id: int | None = None) -> int:
    global _CHANNEL_LLM_CURSOR_LAST_ID
    with _CHANNEL_LLM_CURSOR_LOCK:
        _CHANNEL_LLM_CURSOR_LAST_ID = max(0, int(last_id if last_id is not None else _chat_scan_max_id()))
        try:
            _channel_llm_write_cursor_locked(_CHANNEL_LLM_CURSOR_LAST_ID)
        except Exception as exc:
            router_log("WARN", f"channel_llm_cursor_write_failed error={type(exc).__name__}: {exc}")
        return _CHANNEL_LLM_CURSOR_LAST_ID


def ensure_channel_llm_delivery_cursor_initialized() -> int:
    with _CHANNEL_LLM_CURSOR_LOCK:
        return _channel_llm_read_cursor_locked()


def _metadata_int(metadata: dict[str, Any], key: str) -> int | None:
    try:
        value = metadata.get(key)
        if value is None or value == "":
            return None
        return max(0, int(value))
    except Exception:
        return None


def _commit_channel_llm_cursor_if_newer(last_id: int | None) -> None:
    global _CHANNEL_LLM_CURSOR_LAST_ID
    if last_id is None:
        return
    with _CHANNEL_LLM_CURSOR_LOCK:
        current = _channel_llm_read_cursor_locked()
        if last_id <= current:
            return
        _CHANNEL_LLM_CURSOR_LAST_ID = last_id
        try:
            _channel_llm_write_cursor_locked(last_id)
        except Exception as exc:
            router_log("WARN", f"channel_llm_cursor_write_failed error={type(exc).__name__}: {exc}")


def _commit_channel_llm_summary_cursor_if_newer(last_id: int | None) -> None:
    global _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    if last_id is None:
        return
    with _CHANNEL_LLM_SUMMARY_LOCK:
        current = _channel_llm_summary_read_cursor_locked()
        if last_id <= current:
            return
        _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID = last_id
        try:
            _channel_llm_summary_write_cursor_locked(last_id)
        except Exception as exc:
            router_log("WARN", f"channel_llm_summary_cursor_write_failed error={type(exc).__name__}: {exc}")


CLAUDE_ANY_INTERNAL_METADATA_PREFIX = "claude_any_"


def body_without_claude_any_internal_metadata(body: dict[str, Any]) -> dict[str, Any]:
    """Return an upstream-safe copy with claude-any private metadata removed."""
    metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else None
    if not metadata:
        return body
    internal_keys = [
        key
        for key in metadata
        if str(key).startswith(CLAUDE_ANY_INTERNAL_METADATA_PREFIX)
    ]
    if not internal_keys:
        return body
    public_metadata = {
        key: value
        for key, value in metadata.items()
        if not str(key).startswith(CLAUDE_ANY_INTERNAL_METADATA_PREFIX)
    }
    out = dict(body)
    if public_metadata:
        out["metadata"] = public_metadata
    else:
        out.pop("metadata", None)
    return out


def commit_pending_channel_delivery_cursors(
    body: dict[str, Any],
    handler: BaseHTTPRequestHandler | None = None,
    metadata: dict[str, Any] | None = None,
) -> None:
    if not isinstance(metadata, dict):
        metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
    if not metadata:
        return
    if handler is not None:
        status = _handler_response_status(handler)
        if status is None or status < 200 or status >= 400:
            router_log(
                "INFO",
                f"channel_delivery_cursor_deferred status={status if status is not None else '-'}",
            )
            return
    message_cursor = _metadata_int(metadata, "claude_any_channel_cursor_last_id")
    summary_cursor = _metadata_int(metadata, "claude_any_channel_summary_cursor_last_id")
    _commit_channel_llm_cursor_if_newer(message_cursor)
    _commit_channel_llm_summary_cursor_if_newer(summary_cursor)


def _channel_llm_summary_write_cursor_locked(last_id: int) -> None:
    CHANNEL_LLM_SUMMARY_CURSOR_PATH.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = CHANNEL_LLM_SUMMARY_CURSOR_PATH.with_suffix(".json.tmp")
    tmp_path.write_text(json.dumps({"last_id": max(0, int(last_id))}, separators=(",", ":")) + "\n", encoding="utf-8")
    tmp_path.replace(CHANNEL_LLM_SUMMARY_CURSOR_PATH)


def _channel_llm_summary_scan_max_id_locked() -> int:
    if not CHANNEL_LLM_SUMMARY_QUEUE_PATH.exists():
        return 0
    max_id = 0
    try:
        with CHANNEL_LLM_SUMMARY_QUEUE_PATH.open("r", encoding="utf-8") as fh:
            for line in fh:
                try:
                    item = json.loads(line)
                except Exception:
                    continue
                if not isinstance(item, dict):
                    continue
                try:
                    message_id = int(item.get("message_id") or 0)
                except Exception:
                    message_id = 0
                if message_id > max_id:
                    max_id = message_id
    except Exception as exc:
        router_log("WARN", f"channel_llm_summary_queue_scan_failed error={type(exc).__name__}: {exc}")
    return max_id


def _channel_llm_summary_read_cursor_locked() -> int:
    global _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    if _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID is not None:
        return _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    if CHANNEL_LLM_SUMMARY_CURSOR_PATH.exists():
        try:
            data = json.loads(CHANNEL_LLM_SUMMARY_CURSOR_PATH.read_text(encoding="utf-8"))
            _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID = max(0, int(data.get("last_id") or 0))
            return _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
        except Exception as exc:
            router_log("WARN", f"channel_llm_summary_cursor_read_failed error={type(exc).__name__}: {exc}")
    _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID = max(0, _channel_llm_summary_scan_max_id_locked())
    try:
        _channel_llm_summary_write_cursor_locked(_CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID)
    except Exception as exc:
        router_log("WARN", f"channel_llm_summary_cursor_write_failed error={type(exc).__name__}: {exc}")
    return _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID


def _read_channel_llm_summary_records(after_id: int, limit: int = 20) -> list[dict[str, Any]]:
    if not CHANNEL_LLM_SUMMARY_QUEUE_PATH.exists():
        return []
    records: list[dict[str, Any]] = []
    try:
        with CHANNEL_LLM_SUMMARY_QUEUE_PATH.open("r", encoding="utf-8") as fh:
            for line in fh:
                if len(records) >= limit:
                    break
                line = line.strip()
                if not line:
                    continue
                try:
                    item = json.loads(line)
                except Exception:
                    continue
                if not isinstance(item, dict):
                    continue
                try:
                    message_id = int(item.get("message_id") or 0)
                except Exception:
                    message_id = 0
                if message_id > after_id:
                    records.append(item)
    except Exception as exc:
        router_log("WARN", f"channel_llm_summary_queue_read_failed error={type(exc).__name__}: {exc}")
    return records


def _channel_llm_summary_prompt_note(text: str) -> str:
    body = re.sub(r"\s+", " ", str(text or "")).strip()
    if not body:
        return ""
    body = re.sub(r"##\s*tool_result\b.*", "", body, flags=re.IGNORECASE | re.DOTALL).strip()
    body = re.sub(r"##\s*전송 결과\b.*", "", body, flags=re.IGNORECASE | re.DOTALL).strip()
    body = re.sub(r"\bclaude-any\b", "channel bridge", body, flags=re.IGNORECASE)
    body = body.replace("direct_handler_summary", "handler summary")
    body = body.replace("백그라운드 자동 처리 요약", "자동 처리 요약")
    body = body.replace("라우터가 같은 채널에 안전 fallback 회신을 직접 전송했습니다", "same-channel fallback reply was sent")
    return truncate_for_prompt(body, 700)


def _channel_llm_summary_source_name(item: dict[str, Any]) -> str:
    source = str(item.get("source") or "").strip()
    if source:
        return _channel_sse_public_mcp_name(source)
    sender = str(item.get("sender_id") or "").strip()
    if sender:
        return _channel_sse_public_mcp_name(sender)
    return "external-channel"


def _channel_llm_summary_record_id(item: dict[str, Any]) -> int:
    try:
        return int(item.get("message_id") or 0)
    except Exception:
        return 0


def _channel_llm_summary_digest_lines(records: list[dict[str, Any]]) -> list[str]:
    visible = _channel_llm_summary_notice_records(records)
    if not visible:
        return []
    grouped: dict[str, list[dict[str, Any]]] = {}
    for item in visible:
        grouped.setdefault(_channel_llm_summary_source_name(item), []).append(item)
    lines: list[str] = ["[channel mailbox digest]"]
    for source in sorted(grouped):
        items = grouped[source]
        ids = sorted(message_id for message_id in (_channel_llm_summary_record_id(item) for item in items) if message_id > 0)
        if len(ids) >= 2:
            id_text = f"{ids[0]}..{ids[-1]}"
        elif ids:
            id_text = str(ids[0])
        else:
            id_text = "-"
        channels = ", ".join(
            sorted(
                {
                    str(item.get("channel") or "default")
                    for item in items
                    if str(item.get("channel") or "default").strip()
                }
            )[:6]
        )
        channel_text = f" channels={channels}" if channels else ""
        lines.append(
            f"{source}에서 전달된 알림이 {len(items)}개 있습니다. {source}에서 확인하세요. "
            f"message_ids={id_text}{channel_text}"
        )
    return lines


def format_channel_llm_summary_prompt(records: list[dict[str, Any]]) -> str:
    return "\n".join(_channel_llm_summary_digest_lines(records))


def _channel_llm_summary_notice_actor(item: dict[str, Any]) -> str:
    meta_sender = str(item.get("sender_id") or "").strip()
    incoming = str(item.get("incoming") or "")
    for pattern in (
        r"\bNew message from\s+([A-Za-z0-9_.@ -]{1,80})",
        r"\b([A-Za-z0-9_.@ -]{1,80})\s+@mentioned you\b",
        r"\bFrom:\*\*\s*([^*\n]{1,80})",
        r"\*\*발신[:：]\*\*\s*([^(\n]{1,80})",
    ):
        match = re.search(pattern, incoming, re.IGNORECASE)
        if match:
            actor = re.sub(r"\s+", " ", match.group(1)).strip(" -")
            if actor:
                return actor
    if meta_sender and meta_sender != "ai-net-sse":
        return meta_sender
    return "channel"


def _channel_llm_summary_notice_is_quiet(item: dict[str, Any]) -> bool:
    summary = re.sub(r"\s+", " ", str(item.get("summary") or "")).strip()
    stop_reason = str(item.get("stop_reason") or "").strip().lower()
    if summary.lower().startswith("no_reply:"):
        return True
    if summary.startswith("NO_REPLY:") or summary.startswith("응답 불필요:") or summary.startswith("회신 불필요:"):
        return True
    return stop_reason in {"no_reply", "no-reply"}


def _channel_llm_summary_notice_records(records: list[dict[str, Any]]) -> list[dict[str, Any]]:
    if env_bool(os.environ.get("CLAUDE_ANY_CHANNEL_SCREEN_SUMMARY_SHOW_NO_REPLY"), False):
        return records
    return [item for item in records if not _channel_llm_summary_notice_is_quiet(item)]


def _format_channel_llm_summary_notice_verbose(records: list[dict[str, Any]]) -> str:
    lines = [
        "",
        "[claude-any channel] background message handling summary",
    ]
    for item in records:
        summary = re.sub(r"\s+", " ", str(item.get("summary") or "")).strip()
        incoming = re.sub(r"\s+", " ", str(item.get("incoming") or "")).strip()
        lines.extend(
            [
                f"- message_id={item.get('message_id')} channel={item.get('channel') or 'default'} sender={item.get('sender_id') or ''} stop_reason={item.get('stop_reason') or ''} tool_turns={item.get('tool_turns') or 0}",
                f"  incoming: {truncate_for_prompt(incoming, 500)}",
                f"  result: {truncate_for_prompt(summary, 1200)}",
            ]
        )
    lines.append("")
    return "\r\n".join(lines)


def _format_channel_llm_summary_notice_compact(records: list[dict[str, Any]]) -> str:
    lines = _channel_llm_summary_digest_lines(records)
    if not lines:
        return ""
    return "\r\n" + " ".join(lines) + "\r\n"


def format_channel_llm_summary_notice(records: list[dict[str, Any]]) -> str:
    style = str(os.environ.get("CLAUDE_ANY_CHANNEL_SCREEN_SUMMARY_STYLE") or "compact").strip().lower()
    if style in {"verbose", "full", "long"}:
        return _format_channel_llm_summary_notice_verbose(records)
    return _format_channel_llm_summary_notice_compact(records)


def body_with_pending_channel_summaries(body: dict[str, Any]) -> dict[str, Any]:
    global _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
    if body.get("claude_any_channel_direct") or metadata.get("claude_any_channel_direct"):
        return body
    if metadata.get("claude_any_channel_summary_injected"):
        return body
    if plan_mode_active(body):
        router_log("INFO", "channel_llm_summary_inject_skipped reason=plan_mode_active")
        return body
    cfg = load_config()
    if channel_delivery_mode(cfg) != "llm":
        return body
    with _CHANNEL_LLM_SUMMARY_LOCK:
        last_id = _channel_llm_summary_read_cursor_locked()
        records = _read_channel_llm_summary_records(last_id, 20)
        if not records:
            return body
        max_seen = last_id
        for item in records:
            try:
                max_seen = max(max_seen, int(item.get("message_id") or 0))
            except Exception:
                pass
    prompt = format_channel_llm_summary_prompt(records)
    if not prompt.strip():
        _commit_channel_llm_summary_cursor_if_newer(max_seen)
        return body
    out = dict(body)
    messages = [m for m in body.get("messages", []) if isinstance(m, dict)]
    messages.append({"role": "user", "content": [{"type": "text", "text": prompt}]})
    out["messages"] = messages
    out_metadata = dict(metadata)
    out_metadata["claude_any_channel_summary_injected"] = True
    out_metadata["claude_any_channel_summary_message_ids"] = ",".join(str(item.get("message_id") or "") for item in records)
    out_metadata["claude_any_channel_summary_cursor_last_id"] = str(max_seen)
    out["metadata"] = out_metadata
    _commit_channel_llm_summary_cursor_if_newer(max_seen)
    router_log("INFO", f"channel_llm_summary_injected count={len(records)} message_ids={out_metadata['claude_any_channel_summary_message_ids']}")
    return out


def body_with_pending_channel_messages(body: dict[str, Any]) -> dict[str, Any]:
    global _CHANNEL_LLM_CURSOR_LAST_ID
    metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
    if body.get("claude_any_channel_direct") or metadata.get("claude_any_channel_direct"):
        return body
    if plan_mode_active(body):
        router_log("INFO", "channel_llm_inject_skipped reason=plan_mode_active")
        return body
    cfg = load_config()
    if channel_delivery_mode(cfg) != "llm":
        return body
    with _CHANNEL_LLM_CURSOR_LOCK:
        last_id = _channel_llm_read_cursor_locked()
        pending: list[dict[str, Any]] = []
        max_seen = last_id
        for message in read_chat_messages(last_id, None, None, 100):
            try:
                max_seen = max(max_seen, int(message.get("id") or 0))
            except Exception:
                continue
            skip_reason = _channel_llm_message_skip_reason(message)
            if skip_reason:
                router_log(
                    "INFO",
                    f"channel_llm_inject_skipped message_id={message.get('id')} channel={message.get('channel')} reason={skip_reason}",
                )
                continue
            try:
                message_id = int(message.get("id") or 0)
            except Exception:
                message_id = 0
            with _CHANNEL_LLM_DIRECT_LOCK:
                direct_inflight = message_id in _CHANNEL_LLM_DIRECT_INFLIGHT
                direct_delivered = message_id in _CHANNEL_LLM_DIRECT_DELIVERED
            meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
            persisted_direct_delivered = bool(meta.get("llm_direct_delivered"))
            if direct_inflight or direct_delivered or persisted_direct_delivered:
                reason = (
                    "llm_direct_inflight"
                    if direct_inflight
                    else "llm_direct_delivered"
                )
                router_log(
                    "INFO",
                    f"channel_llm_inject_skipped message_id={message.get('id')} channel={message.get('channel')} reason={reason}",
                )
                continue
            if meta.get("llm_direct_pending"):
                router_log(
                    "INFO",
                    f"channel_llm_inject_fallback message_id={message.get('id')} channel={message.get('channel')} reason=stale_llm_direct_pending",
                )
            with _CHANNEL_STDIN_WAKE_LOCK:
                stdin_wake_delivered = message_id in _CHANNEL_STDIN_WAKE_DELIVERED
            if stdin_wake_delivered:
                router_log(
                    "INFO",
                    f"channel_llm_inject_skipped message_id={message.get('id')} channel={message.get('channel')} reason=stdin_wake_delivered",
                )
                continue
            pending.append(message)
            max_seen = message_id
            break
        if not pending:
            if max_seen != last_id:
                _CHANNEL_LLM_CURSOR_LAST_ID = max_seen
                try:
                    _channel_llm_write_cursor_locked(max_seen)
                except Exception as exc:
                    router_log("WARN", f"channel_llm_cursor_write_failed error={type(exc).__name__}: {exc}")
            return body
    messages = [m for m in body.get("messages", []) if isinstance(m, dict)]
    messages.append({"role": "user", "content": [{"type": "text", "text": format_channel_llm_batch_prompt(pending)}]})
    out = dict(body)
    out["messages"] = messages
    ids = ",".join(str(message.get("id") or "") for message in pending)
    channels = ",".join(sorted({str(message.get("channel") or "default") for message in pending}))
    out_metadata = dict(out.get("metadata") if isinstance(out.get("metadata"), dict) else {})
    out_metadata["claude_any_channel_injected"] = True
    out_metadata["claude_any_channel_message_ids"] = ids
    out_metadata["claude_any_channel_cursor_last_id"] = str(max_seen)
    out["metadata"] = out_metadata
    router_log("INFO", f"channel_llm_injected count={len(pending)} message_ids={ids} channels={channels}")
    return out


def _write_fd_all(fd: int, data: bytes) -> None:
    view = memoryview(data)
    while view:
        written = os.write(fd, view)
        view = view[written:]


def _channel_platform_default_enter_bytes(platform: str | None = None, os_name: str | None = None) -> bytes:
    sys_platform = str(platform if platform is not None else sys.platform).lower()
    os_family = str(os_name if os_name is not None else os.name).lower()
    if os_family == "nt" or sys_platform.startswith(("win", "cygwin", "msys")):
        return b"\r\n"
    if os_family == "posix":
        return b"\r\n"
    return b"\r\n"


def _channel_wake_enter_bytes(value: str | bytes | None = None) -> bytes:
    raw: str | bytes | None = value
    if raw is None:
        raw = os.environ.get("CLAUDE_ANY_CHANNEL_WAKE_ENTER")
        if raw is None:
            return _channel_platform_default_enter_bytes()
    if isinstance(raw, bytes):
        return raw if raw in (b"\n", b"\r", b"\r\n") else _channel_platform_default_enter_bytes()
    normalized = str(raw or "").strip().lower()
    if normalized in {"", "auto", "default", "platform"}:
        return _channel_platform_default_enter_bytes()
    if normalized in {"lf", "nl", "newline", "linefeed", "\\n"}:
        return b"\n"
    if normalized in {"cr", "return", "carriage-return", "carriage_return", "\\r"}:
        return b"\r"
    if normalized in {"crlf", "cr-lf", "return-newline", "\\r\\n"}:
        return b"\r\n"
    return _channel_platform_default_enter_bytes()


def _channel_wake_enter_env_is_fixed() -> bool:
    raw = os.environ.get("CLAUDE_ANY_CHANNEL_WAKE_ENTER")
    if raw is None:
        return False
    return str(raw).strip().lower() not in {"", "auto", "default", "platform"}


def _channel_enter_bytes_from_user_input(data: bytes) -> bytes | None:
    if not data:
        return None
    if data in (b"\n", b"\r", b"\r\n"):
        return data
    last_lf = data.rfind(b"\n")
    last_cr = data.rfind(b"\r")
    if last_lf < 0 and last_cr < 0:
        return None
    if last_lf > last_cr:
        return b"\r\n" if last_lf > 0 and data[last_lf - 1 : last_lf] == b"\r" else b"\n"
    return b"\r\n" if last_cr + 1 < len(data) and data[last_cr + 1 : last_cr + 2] == b"\n" else b"\r"


def _channel_synthetic_enter_bytes_from_user_input(data: bytes) -> bytes | None:
    observed = _channel_enter_bytes_from_user_input(data)
    if observed == b"\r":
        # Bare CR is common from raw POSIX terminals, but synthetic CR-only
        # writes can sit in Claude Code's line editor on some PTY stacks.
        return b"\r\n"
    return observed


def _channel_enter_label(enter_bytes: bytes) -> str:
    if enter_bytes == b"\r":
        return "cr"
    if enter_bytes == b"\r\n":
        return "crlf"
    return "lf"


def _channel_wake_input_bytes(prompt: str, enter_bytes: bytes | None = None) -> bytes:
    # Ctrl-U clears any stale line editor text before submitting the synthetic prompt.
    # Claude Code's interactive input is terminal-driven; the submit byte can
    # differ by PTY/input stack, so callers pass the observed Enter sequence.
    return b"\x15" + prompt.encode("utf-8", errors="replace") + _channel_wake_enter_bytes(enter_bytes)


def _channel_wake_submit_delay_seconds() -> float:
    raw = os.environ.get("CLAUDE_ANY_CHANNEL_WAKE_SUBMIT_DELAY_MS")
    if raw is None:
        return 0.08
    try:
        return max(0.0, min(2.0, float(raw) / 1000.0))
    except Exception:
        return 0.08


def _write_channel_wake_prompt(master_fd: int, prompt: str, enter_bytes: bytes | None = None) -> None:
    _write_fd_all(master_fd, b"\x15" + prompt.encode("utf-8", errors="replace"))
    delay = _channel_wake_submit_delay_seconds()
    if delay > 0:
        time.sleep(delay)
    _write_fd_all(master_fd, _channel_wake_enter_bytes(enter_bytes))


def _inject_pending_channel_messages(
    master_fd: int,
    last_id: int,
    enter_bytes: bytes | None = None,
    *,
    web_chat_only: bool = False,
) -> int:
    pending: list[dict[str, Any]] = []
    for message in read_chat_messages(last_id, None, None, 100):
        try:
            message_id = int(message.get("id") or 0)
            last_id = max(last_id, message_id)
        except Exception:
            continue
        meta = message.get("meta") if isinstance(message.get("meta"), dict) else {}
        with _CHANNEL_LLM_DIRECT_LOCK:
            direct_delivered = message_id in _CHANNEL_LLM_DIRECT_DELIVERED
        if direct_delivered or meta.get("llm_direct_delivered"):
            router_log(
                "INFO",
                f"channel_stdin_proxy_skipped_noise message_id={message.get('id')} channel={message.get('channel')} reason=llm_direct_delivered",
            )
            continue
        if meta.get("llm_direct_pending"):
            router_log(
                "INFO",
                f"channel_stdin_proxy_inject_fallback message_id={message.get('id')} channel={message.get('channel')} reason=llm_direct_pending",
            )
        if web_chat_only and not _channel_message_is_web_chat_request(message):
            router_log(
                "INFO",
                f"channel_stdin_proxy_skipped_noise message_id={message.get('id')} channel={message.get('channel')} reason=not_web_chat",
            )
            continue
        noise_reason = _channel_wake_message_noise_reason(message)
        if noise_reason:
            router_log(
                "INFO",
                f"channel_stdin_proxy_skipped_noise message_id={message.get('id')} channel={message.get('channel')} reason={noise_reason}",
            )
            continue
        pending.append(message)
        last_id = message_id
        break
    if pending:
        if web_chat_only and all(_channel_message_is_web_chat_request(message) for message in pending):
            prompt = format_channel_web_chat_wake_batch_prompt(pending)
        else:
            prompt = format_channel_wake_batch_prompt(pending)
        submit_bytes = _channel_wake_enter_bytes(enter_bytes)
        _write_channel_wake_prompt(master_fd, prompt, submit_bytes)
        with _CHANNEL_STDIN_WAKE_LOCK:
            for message in pending:
                try:
                    _CHANNEL_STDIN_WAKE_DELIVERED.add(int(message.get("id") or 0))
                except Exception:
                    continue
            if len(_CHANNEL_STDIN_WAKE_DELIVERED) > 1000:
                for old_id in sorted(_CHANNEL_STDIN_WAKE_DELIVERED)[:500]:
                    _CHANNEL_STDIN_WAKE_DELIVERED.discard(old_id)
        ids = ",".join(str(message.get("id") or "") for message in pending)
        channels = ",".join(sorted({str(message.get("channel") or "default") for message in pending}))
        router_log(
            "INFO",
            f"channel_stdin_proxy_injected count={len(pending)} message_ids={ids} channels={channels} enter={_channel_enter_label(submit_bytes)}",
        )
    return last_id


def _inject_pending_channel_summaries(master_fd: int, enter_bytes: bytes | None = None) -> int:
    global _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    with _CHANNEL_LLM_SUMMARY_LOCK:
        last_id = _channel_llm_summary_read_cursor_locked()
        records = _read_channel_llm_summary_records(last_id, 20)
        if not records:
            return last_id
        max_seen = last_id
        for item in records:
            try:
                max_seen = max(max_seen, int(item.get("message_id") or 0))
            except Exception:
                pass
    prompt = format_channel_llm_summary_prompt(records)
    if not prompt.strip():
        ids = ",".join(str(item.get("message_id") or "") for item in records)
        router_log("INFO", f"channel_stdin_summary_skipped_quiet count={len(records)} message_ids={ids}")
        _commit_channel_llm_summary_cursor_if_newer(max_seen)
        return max_seen
    submit_bytes = _channel_wake_enter_bytes(enter_bytes)
    _write_channel_wake_prompt(master_fd, prompt, submit_bytes)
    _commit_channel_llm_summary_cursor_if_newer(max_seen)
    ids = ",".join(str(item.get("message_id") or "") for item in records)
    router_log(
        "INFO",
        f"channel_stdin_summary_injected count={len(records)} message_ids={ids} enter={_channel_enter_label(submit_bytes)}",
    )
    return max_seen


def _print_pending_channel_summaries(stdout_fd: int) -> int:
    global _CHANNEL_LLM_SUMMARY_CURSOR_LAST_ID
    with _CHANNEL_LLM_SUMMARY_LOCK:
        last_id = _channel_llm_summary_read_cursor_locked()
        records = _read_channel_llm_summary_records(last_id, 20)
        if not records:
            return last_id
        max_seen = last_id
        for item in records:
            try:
                max_seen = max(max_seen, int(item.get("message_id") or 0))
            except Exception:
                pass
    ids = ",".join(str(item.get("message_id") or "") for item in records)
    notice = format_channel_llm_summary_notice(records)
    if not notice.strip():
        router_log("INFO", f"channel_screen_summary_skipped_quiet count={len(records)} message_ids={ids}")
        _commit_channel_llm_summary_cursor_if_newer(max_seen)
        return max_seen
    _write_fd_all(stdout_fd, notice.encode("utf-8", errors="replace"))
    _commit_channel_llm_summary_cursor_if_newer(max_seen)
    router_log("INFO", f"channel_screen_summary_printed count={len(records)} message_ids={ids}")
    return max_seen


def _chat_messages_file_marker() -> tuple[float, int]:
    try:
        stat = CHAT_MESSAGES_PATH.stat()
        return (stat.st_mtime, stat.st_size)
    except Exception:
        return (0.0, 0)


def _channel_llm_summary_file_marker() -> tuple[float, int]:
    try:
        stat = CHANNEL_LLM_SUMMARY_QUEUE_PATH.stat()
        return (stat.st_mtime, stat.st_size)
    except Exception:
        return (0.0, 0)


def _terminal_winsize_from_fd(fd: int) -> tuple[int, int]:
    """Return terminal size as (rows, columns), never 0x0."""
    try:
        size = os.get_terminal_size(fd)
        rows = int(size.lines)
        cols = int(size.columns)
    except Exception:
        rows = 0
        cols = 0
    if rows > 0 and cols > 0:
        return rows, cols
    fallback = shutil.get_terminal_size((80, 24))
    rows = int(getattr(fallback, "lines", 0) or 0)
    cols = int(getattr(fallback, "columns", 0) or 0)
    if rows <= 0:
        rows = 24
    if cols <= 0:
        cols = 80
    return rows, cols


def _apply_pty_winsize(pty_fd: int, rows: int, cols: int) -> bool:
    if os.name != "posix" or rows <= 0 or cols <= 0:
        return False
    try:
        import fcntl
        import struct
        import termios

        fcntl.ioctl(pty_fd, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
        return True
    except Exception:
        return False


def subprocess_call_with_channel_wake_proxy(
    cmd: list[str],
    env: dict[str, str],
    *,
    inject_channel_messages: bool = True,
    inject_channel_summaries: bool = True,
    print_channel_summaries: bool = False,
    inject_web_chat_only: bool = False,
) -> int:
    if os.name != "posix" or not sys.stdin.isatty() or not sys.stdout.isatty():
        router_log("INFO", "channel_stdin_proxy_unavailable; using direct subprocess call")
        return subprocess.call(cmd, env=env)
    import pty
    import select
    import termios
    import tty

    last_id = _chat_scan_max_id()
    last_channel_marker = _chat_messages_file_marker()
    last_summary_marker = _channel_llm_summary_file_marker()
    master_fd, slave_fd = pty.openpty()
    stdout_fd = sys.stdout.fileno()
    rows, cols = _terminal_winsize_from_fd(stdout_fd)
    if _apply_pty_winsize(slave_fd, rows, cols):
        router_log("INFO", f"channel_stdin_proxy_winsize_init rows={rows} cols={cols}")
    proc = subprocess.Popen(cmd, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, env=env, close_fds=True)
    os.close(slave_fd)
    stdin_fd = sys.stdin.fileno()
    old_attrs = termios.tcgetattr(stdin_fd)
    old_sigwinch = None
    sigwinch_installed = False
    last_channel_poll = 0.0
    channel_enter_bytes = _channel_wake_enter_bytes()
    router_log(
        "INFO",
        f"channel_stdin_proxy_enter_default enter={_channel_enter_label(channel_enter_bytes)} os={os.name} platform={sys.platform}",
    )

    def _handle_sigwinch(signum: int, frame: Any) -> None:
        new_rows, new_cols = _terminal_winsize_from_fd(stdout_fd)
        if _apply_pty_winsize(master_fd, new_rows, new_cols):
            router_log("INFO", f"channel_stdin_proxy_winsize_resize rows={new_rows} cols={new_cols}")
        if callable(old_sigwinch):
            old_sigwinch(signum, frame)

    try:
        try:
            old_sigwinch = signal.getsignal(signal.SIGWINCH)
            signal.signal(signal.SIGWINCH, _handle_sigwinch)
            sigwinch_installed = True
        except Exception:
            sigwinch_installed = False
        tty.setraw(stdin_fd)
        if print_channel_summaries:
            _print_pending_channel_summaries(stdout_fd)
        while proc.poll() is None:
            try:
                readable, _, _ = select.select([stdin_fd, master_fd], [], [], 0.2)
            except OSError:
                break
            if stdin_fd in readable:
                data = os.read(stdin_fd, 4096)
                if data:
                    observed_enter = _channel_synthetic_enter_bytes_from_user_input(data)
                    if observed_enter and not _channel_wake_enter_env_is_fixed():
                        if observed_enter != channel_enter_bytes:
                            router_log(
                                "INFO",
                                f"channel_stdin_proxy_enter_observed enter={_channel_enter_label(observed_enter)}",
                            )
                        channel_enter_bytes = observed_enter
                    _write_fd_all(master_fd, data)
            if master_fd in readable:
                try:
                    data = os.read(master_fd, 4096)
                except OSError:
                    break
                if data:
                    _write_fd_all(stdout_fd, data)
            now = time.time()
            if now - last_channel_poll >= 0.5:
                last_channel_poll = now
                marker = _chat_messages_file_marker()
                if inject_channel_messages and marker != last_channel_marker:
                    last_channel_marker = marker
                    last_id = _inject_pending_channel_messages(
                        master_fd,
                        last_id,
                        channel_enter_bytes,
                        web_chat_only=inject_web_chat_only,
                    )
                summary_marker = _channel_llm_summary_file_marker()
                if inject_channel_summaries and summary_marker != last_summary_marker:
                    last_summary_marker = summary_marker
                    if print_channel_summaries:
                        _print_pending_channel_summaries(stdout_fd)
                    else:
                        _inject_pending_channel_summaries(master_fd, channel_enter_bytes)
        while True:
            try:
                readable, _, _ = select.select([master_fd], [], [], 0)
                if master_fd not in readable:
                    break
                data = os.read(master_fd, 4096)
                if not data:
                    break
                _write_fd_all(stdout_fd, data)
            except OSError:
                break
        return proc.returncode if proc.returncode is not None else 0
    finally:
        if sigwinch_installed:
            try:
                signal.signal(signal.SIGWINCH, old_sigwinch)
            except Exception:
                pass
        try:
            termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_attrs)
        except Exception:
            pass
        try:
            os.close(master_fd)
        except Exception:
            pass
        if proc.poll() is None:
            try:
                proc.terminate()
            except Exception:
                pass


def subprocess_call_with_channel_screen_summary_proxy(cmd: list[str], env: dict[str, str]) -> int:
    return subprocess_call_with_channel_wake_proxy(
        cmd,
        env,
        inject_channel_messages=False,
        inject_channel_summaries=True,
        print_channel_summaries=True,
    )


def _mcp_proxy_notification_payload(server_name: str, message: dict[str, Any]) -> dict[str, Any] | None:
    method = str(message.get("method") or "").strip()
    if not method.startswith("notifications/"):
        return None
    params = message.get("params") if isinstance(message.get("params"), dict) else {}
    payload = params.get("payload") if isinstance(params.get("payload"), dict) else {}
    data = params.get("data") if isinstance(params.get("data"), dict) else {}
    event = params.get("event") if isinstance(params.get("event"), dict) else {}
    meta: dict[str, Any] = {
        "mcp_server": server_name,
        "mcp_method": method,
        "mcp_json": _json_safe_metadata(message),
    }
    if message.get("jsonrpc") is not None:
        meta["jsonrpc"] = message.get("jsonrpc")
    if message.get("id") is not None:
        meta["rpc_id"] = message.get("id")
    meta.update(_event_meta_from_sources(message, params, payload, data, event))
    content = (
        _event_payload_text(params)
        or _event_payload_text(payload)
        or _event_payload_text(data)
        or _event_payload_text(event)
    )
    if not content and params:
        content = json.dumps(params, ensure_ascii=False, separators=(",", ":"), default=str)
    if not content:
        return None
    channel = str(meta.get("channel") or meta.get("room_id") or meta.get("room") or server_name)
    return {
        "channel": channel,
        "sender_id": str(meta.get("sender_id") or meta.get("agent_id") or server_name),
        "recipients": meta.get("recipient_id") or "all",
        "thread_id": meta.get("thread_id"),
        "parent_id": meta.get("parent_id"),
        "kind": method.replace("notifications/claude/", "").replace("notifications/", "").replace("/", "."),
        "message": content,
        "meta": meta,
    }


def _mcp_proxy_stable_event_identity(chat_payload: dict[str, Any]) -> tuple[str, str] | None:
    meta = chat_payload.get("meta") if isinstance(chat_payload.get("meta"), dict) else {}
    for key in (
        "stream_id",
        "sse_id",
        "message_id",
        "source_message_id",
        "event_id",
        "cursor",
        "assignment_id",
        "poll_id",
        "task_id",
        "sequence",
        "seq",
    ):
        value = meta.get(key)
        if value is not None and str(value).strip():
            return key, str(value).strip()
    return None


def _mcp_proxy_notification_dedupe_key(server_name: str, chat_payload: dict[str, Any]) -> tuple[str, bool]:
    meta = chat_payload.get("meta") if isinstance(chat_payload.get("meta"), dict) else {}
    body = re.sub(r"\s+", " ", str(chat_payload.get("message") or "")).strip()
    room = str(meta.get("room_id") or meta.get("room") or chat_payload.get("channel") or server_name)
    kind = str(meta.get("kind") or chat_payload.get("kind") or "")
    stable_identity = _mcp_proxy_stable_event_identity(chat_payload)
    if stable_identity:
        stable_key, stable_value = stable_identity
        return (
            json.dumps(
                ["stable", room, kind, stable_key, stable_value],
                ensure_ascii=False,
                separators=(",", ":"),
            ),
            True,
        )
    sender = str(chat_payload.get("sender_id") or meta.get("sender_id") or meta.get("agent_id") or server_name)
    thread = str(chat_payload.get("thread_id") or meta.get("thread_id") or "")
    parent = str(chat_payload.get("parent_id") or meta.get("parent_id") or "")
    return json.dumps(
        [server_name, room, sender, thread, parent, body],
        ensure_ascii=False,
        separators=(",", ":"),
    ), False


def _mcp_proxy_should_skip_duplicate_notification(server_name: str, chat_payload: dict[str, Any]) -> tuple[bool, str | None]:
    meta = chat_payload.get("meta") if isinstance(chat_payload.get("meta"), dict) else {}
    method = str(meta.get("mcp_method") or "")
    if not method.startswith("notifications/"):
        return False, None
    key, has_stable_identity = _mcp_proxy_notification_dedupe_key(server_name, chat_payload)
    now = time.time()
    with _MCP_NOTIFICATION_DEDUP_LOCK:
        stale = [
            item_key
            for item_key, (_, seen_at) in _MCP_NOTIFICATION_DEDUP_RECENT.items()
            if now - seen_at > _MCP_NOTIFICATION_DEDUP_TTL_SECONDS
        ]
        for item_key in stale:
            _MCP_NOTIFICATION_DEDUP_RECENT.pop(item_key, None)
        previous = _MCP_NOTIFICATION_DEDUP_RECENT.get(key)
        _MCP_NOTIFICATION_DEDUP_RECENT[key] = (method, now)
    if not previous:
        return False, None
    previous_method, previous_seen_at = previous
    if has_stable_identity and now - previous_seen_at <= _MCP_NOTIFICATION_DEDUP_TTL_SECONDS:
        return True, previous_method
    is_native_pair = _NATIVE_CHANNEL_NOTIFICATION_METHOD in {previous_method, method}
    if previous_method != method and is_native_pair and now - previous_seen_at <= _MCP_NOTIFICATION_DEDUP_TTL_SECONDS:
        return True, previous_method
    return False, None


def _mcp_proxy_observe_json_message(server_name: str, payload: Any, *, schedule_direct: bool = True) -> dict[str, Any] | None:
    if not isinstance(payload, dict):
        return None
    chat_payload = _mcp_proxy_notification_payload(server_name, payload)
    if not chat_payload:
        return None
    skip_duplicate, previous_method = _mcp_proxy_should_skip_duplicate_notification(server_name, chat_payload)
    if skip_duplicate:
        router_log(
            "INFO",
            f"mcp_proxy_notification_skipped_duplicate server={server_name} method={payload.get('method')} previous_method={previous_method}",
        )
        return None
    try:
        if schedule_direct:
            chat_payload = _mark_channel_payload_direct_llm_pending(chat_payload)
        saved = append_chat_message(chat_payload)
        if saved.get("_claude_any_duplicate"):
            router_log(
                "INFO",
                f"mcp_proxy_notification_skipped_duplicate_persisted server={server_name} method={payload.get('method')} existing_id={saved.get('id')}",
            )
            return saved
        router_log(
            "INFO",
            f"mcp_proxy_notification server={server_name} method={payload.get('method')} message_id={saved.get('id')}",
        )
        if schedule_direct:
            schedule_channel_direct_llm_delivery(saved)
        return saved
    except Exception as exc:
        router_log("WARN", f"mcp_proxy_notification_failed server={server_name} error={type(exc).__name__}: {exc}")
    return None


def _mcp_proxy_observe_stdout_line(server_name: str, line: bytes) -> None:
    try:
        text = line.decode("utf-8", errors="replace").strip()
        if not text or not text.startswith("{"):
            return
        payload = json.loads(text)
    except Exception:
        return
    _mcp_proxy_observe_json_message(server_name, payload)


def _mcp_proxy_header_end(buffer: bytes) -> tuple[int, int] | None:
    crlf = buffer.find(b"\r\n\r\n")
    lf = buffer.find(b"\n\n")
    candidates: list[tuple[int, int]] = []
    if crlf >= 0:
        candidates.append((crlf, 4))
    if lf >= 0:
        candidates.append((lf, 2))
    return min(candidates, key=lambda item: item[0]) if candidates else None


def _mcp_proxy_frame_header(buffer: bytes) -> tuple[int, int, int] | None:
    header = _mcp_proxy_header_end(buffer)
    if not header:
        return None
    header_end, delimiter_len = header
    length = _mcp_proxy_content_length(buffer[:header_end])
    if length is None:
        return None
    return header_end, delimiter_len, length


def _mcp_proxy_content_length(header_bytes: bytes) -> int | None:
    try:
        header_text = header_bytes.decode("ascii", errors="replace")
    except Exception:
        return None
    for line in re.split(r"\r?\n", header_text):
        name, sep, value = line.partition(":")
        if sep and name.strip().lower() == "content-length":
            try:
                length = int(value.strip())
            except Exception:
                return None
            return length if length >= 0 else None
    return None


class _McpStdoutObserver:
    def __init__(self, server_name: str) -> None:
        self.server_name = server_name
        self.buffer = bytearray()

    def feed(self, chunk: bytes) -> None:
        if not chunk:
            return
        self.buffer.extend(chunk)
        self._drain()

    def _drop_until_candidate(self) -> bool:
        data = bytes(self.buffer)
        if not data:
            return False
        stripped = data.lstrip()
        if len(stripped) != len(data):
            del self.buffer[: len(data) - len(stripped)]
            data = stripped
        if _mcp_proxy_frame_header(data) or data.startswith(b"{"):
            return True
        lowered = data.lower()
        content_idx = lowered.find(b"content-length:")
        json_idx = data.find(b"{")
        candidates = [idx for idx in (content_idx, json_idx) if idx >= 0]
        newline_idx = data.find(b"\n")
        if candidates:
            keep_from = min(candidates)
            if newline_idx >= 0 and newline_idx < keep_from:
                del self.buffer[: newline_idx + 1]
            elif keep_from > 0:
                del self.buffer[:keep_from]
            return True
        if newline_idx >= 0:
            del self.buffer[: newline_idx + 1]
            return True
        if len(self.buffer) > 1024 * 1024:
            del self.buffer[:-4096]
        return False

    def _drain(self) -> None:
        while self.buffer:
            if not self._drop_until_candidate():
                return
            data = bytes(self.buffer)
            frame = _mcp_proxy_frame_header(data)
            if frame:
                header_end, delimiter_len, length = frame
                body_start = header_end + delimiter_len
                body_end = body_start + length
                if len(data) < body_end:
                    return
                body = data[body_start:body_end]
                del self.buffer[:body_end]
                try:
                    payload = json.loads(body.decode("utf-8", errors="replace"))
                except Exception:
                    continue
                _mcp_proxy_observe_json_message(self.server_name, payload)
                continue
            if data.startswith(b"{"):
                newline_idx = data.find(b"\n")
                if newline_idx < 0:
                    return
                line = data[:newline_idx]
                del self.buffer[: newline_idx + 1]
                _mcp_proxy_observe_stdout_line(self.server_name, line)
                continue
            return


def _mcp_proxy_forward_stdin(proc: subprocess.Popen[bytes]) -> None:
    try:
        stdin_fd = sys.stdin.fileno()
        while True:
            chunk = os.read(stdin_fd, 65536)
            if not chunk:
                break
            if proc.stdin:
                proc.stdin.write(chunk)
                proc.stdin.flush()
    except Exception:
        pass
    finally:
        try:
            if proc.stdin:
                proc.stdin.close()
        except Exception:
            pass


def _mcp_proxy_stdio_mode(server: dict[str, Any]) -> str:
    mode = str(server.get("claude_any_stdio") or server.get("stdio_mode") or "").strip().lower()
    if mode in ("jsonl", "json-lines", "json_lines", "newline-json", "line-json"):
        return "jsonl"
    return "framed"


def _mcp_proxy_write_proc_jsonl(proc: subprocess.Popen[bytes], body: bytes) -> None:
    line = body.strip()
    if not line or not proc.stdin:
        return
    proc.stdin.write(line + b"\n")
    proc.stdin.flush()


def _mcp_proxy_drain_stdin_jsonl_buffer(proc: subprocess.Popen[bytes], buffer: bytearray, *, final: bool = False) -> None:
    while buffer:
        data = bytes(buffer)
        stripped = data.lstrip()
        if len(stripped) != len(data):
            del buffer[: len(data) - len(stripped)]
            data = stripped
        frame = _mcp_proxy_frame_header(data)
        if frame:
            header_end, delimiter_len, length = frame
            body_start = header_end + delimiter_len
            body_end = body_start + length
            if len(data) < body_end:
                return
            body = data[body_start:body_end]
            del buffer[:body_end]
            _mcp_proxy_write_proc_jsonl(proc, body)
            continue
        if data.startswith(b"{"):
            newline_idx = data.find(b"\n")
            if newline_idx >= 0:
                line = data[:newline_idx]
                del buffer[: newline_idx + 1]
                _mcp_proxy_write_proc_jsonl(proc, line)
                continue
            if final:
                del buffer[:]
                _mcp_proxy_write_proc_jsonl(proc, data)
            return
        lowered = data.lower()
        content_idx = lowered.find(b"content-length:")
        json_idx = data.find(b"{")
        candidates = [idx for idx in (content_idx, json_idx) if idx >= 0]
        newline_idx = data.find(b"\n")
        if candidates:
            keep_from = min(candidates)
            if keep_from > 0:
                del buffer[:keep_from]
            continue
        if newline_idx >= 0:
            del buffer[: newline_idx + 1]
            continue
        if len(buffer) > 1024 * 1024:
            del buffer[:-4096]
        return


def _mcp_proxy_forward_stdin_jsonl(proc: subprocess.Popen[bytes]) -> None:
    buffer = bytearray()
    try:
        stdin_fd = sys.stdin.fileno()
        while True:
            chunk = os.read(stdin_fd, 65536)
            if not chunk:
                break
            buffer.extend(chunk)
            _mcp_proxy_drain_stdin_jsonl_buffer(proc, buffer)
        _mcp_proxy_drain_stdin_jsonl_buffer(proc, buffer, final=True)
    except Exception:
        pass
    finally:
        try:
            if proc.stdin:
                proc.stdin.close()
        except Exception:
            pass


def _mcp_proxy_write_stdout_frame(body: bytes) -> None:
    with _CHANNEL_MCP_LOCK:
        sys.stdout.buffer.write(b"Content-Length: " + str(len(body)).encode("ascii") + b"\r\n\r\n" + body)
        sys.stdout.buffer.flush()


# Framing the Streamable HTTP proxy uses when replying to the MCP client on
# stdout. Claude Code's stdio MCP client speaks newline-delimited JSON (JSONL);
# replying with LSP-style Content-Length frames makes Claude Code fail to
# connect ("Failed to connect"). The stdio proxy paths are unaffected -- they
# keep using _mcp_proxy_write_stdout_frame directly. Default JSONL (Claude
# Code's format); switched to "framed" if the client actually sends frames.
_MCP_PROXY_HTTP_CLIENT_FRAMING = "jsonl"


def _mcp_proxy_set_http_client_framing(mode: str) -> None:
    global _MCP_PROXY_HTTP_CLIENT_FRAMING
    if mode in ("jsonl", "framed"):
        _MCP_PROXY_HTTP_CLIENT_FRAMING = mode


def _mcp_proxy_write_client_message(body: bytes) -> None:
    with _CHANNEL_MCP_LOCK:
        if _MCP_PROXY_HTTP_CLIENT_FRAMING == "framed":
            sys.stdout.buffer.write(b"Content-Length: " + str(len(body)).encode("ascii") + b"\r\n\r\n" + body)
        else:
            sys.stdout.buffer.write(body.strip() + b"\n")
        sys.stdout.buffer.flush()


def _mcp_proxy_write_json_response(payload: dict[str, Any]) -> None:
    _mcp_proxy_write_client_message(json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))


def _mcp_proxy_error_response(request_id: Any, message: str, code: int = -32000) -> dict[str, Any]:
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "error": {"code": code, "message": str(message)},
    }


def _mcp_proxy_tool_call_name(payload: dict[str, Any]) -> str:
    if str(payload.get("method") or "") != "tools/call":
        return ""
    params = payload.get("params") if isinstance(payload.get("params"), dict) else {}
    return str(params.get("name") or "").strip()


def _mcp_proxy_tool_call_arguments(payload: dict[str, Any]) -> dict[str, Any]:
    params = payload.get("params") if isinstance(payload.get("params"), dict) else {}
    arguments = params.get("arguments") if isinstance(params.get("arguments"), dict) else {}
    return arguments


def _mcp_proxy_tool_is_notification_wait(tool_name: str) -> bool:
    normalized = re.sub(r"[^a-z0-9_]+", "_", str(tool_name or "").strip().lower()).strip("_")
    if not normalized:
        return False
    waits = normalized.startswith(("wait_", "watch_")) or normalized in {"wait", "watch"}
    if not waits:
        return False
    return any(
        term in normalized
        for term in (
            "notification",
            "notifications",
            "message",
            "messages",
            "event",
            "events",
            "response",
            "responses",
            "inbox",
            "mailbox",
            "channel",
            "channels",
        )
    )


def _mcp_proxy_wait_timeout_seconds(arguments: dict[str, Any]) -> float:
    def _float_env(name: str, default: float) -> float:
        try:
            return float(str(os.environ.get(name, default)).strip())
        except Exception:
            return default

    default_timeout = max(0.0, min(60.0, _float_env("CLAUDE_ANY_MCP_WAIT_DEFAULT_SECONDS", 10.0)))
    max_timeout = max(1.0, min(120.0, _float_env("CLAUDE_ANY_MCP_WAIT_MAX_SECONDS", 30.0)))
    value: Any = None
    scale = 1.0
    for key in ("timeout_ms", "wait_ms", "poll_ms"):
        if key in arguments:
            value = arguments.get(key)
            scale = 0.001
            break
    if value is None:
        for key in ("timeout_seconds", "wait_seconds"):
            if key in arguments:
                value = arguments.get(key)
                scale = 1.0
                break
    if value is None and "timeout" in arguments:
        value = arguments.get("timeout")
        scale = 0.001 if isinstance(value, (int, float)) and float(value) > 1000 else 1.0
    if value is None:
        return min(default_timeout, max_timeout)
    try:
        timeout = float(value) * scale
    except Exception:
        timeout = default_timeout
    return max(0.0, min(max_timeout, timeout))


def _mcp_proxy_notification_wait_response(
    request_id: Any,
    server_name: str,
    notifications: list[dict[str, Any]],
    *,
    timed_out: bool,
) -> dict[str, Any]:
    status = "timeout" if timed_out and not notifications else "ok"
    body = {
        "status": status,
        "source": server_name,
        "count": len(notifications),
        "notifications": notifications,
    }
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps(body, ensure_ascii=False, separators=(",", ":"), default=str),
                }
            ],
            "isError": False,
        },
    }


def _mcp_proxy_drain_input_messages(buffer: bytearray, *, final: bool = False) -> list[dict[str, Any]]:
    messages: list[dict[str, Any]] = []
    while buffer:
        data = bytes(buffer)
        stripped = data.lstrip()
        if len(stripped) != len(data):
            del buffer[: len(data) - len(stripped)]
            data = stripped
        frame = _mcp_proxy_frame_header(data)
        if frame:
            # Client sent an LSP-style Content-Length frame: reply in kind.
            _mcp_proxy_set_http_client_framing("framed")
            header_end, delimiter_len, length = frame
            body_start = header_end + delimiter_len
            body_end = body_start + length
            if len(data) < body_end:
                return messages
            body = data[body_start:body_end]
            del buffer[:body_end]
        elif data.startswith(b"{"):
            # Client sent newline-delimited JSON (Claude Code): reply in kind.
            _mcp_proxy_set_http_client_framing("jsonl")
            newline_idx = data.find(b"\n")
            if newline_idx < 0:
                if not final:
                    return messages
                body = bytes(data)
                del buffer[:]
            else:
                body = data[:newline_idx]
                del buffer[: newline_idx + 1]
        else:
            content_idx = data.lower().find(b"content-length:")
            json_idx = data.find(b"{")
            candidates = [idx for idx in (content_idx, json_idx) if idx >= 0]
            if candidates:
                keep_from = min(candidates)
                if keep_from > 0:
                    del buffer[:keep_from]
                continue
            newline_idx = data.find(b"\n")
            if newline_idx >= 0:
                del buffer[: newline_idx + 1]
                continue
            if len(buffer) > 1024 * 1024:
                del buffer[:-4096]
            return messages
        try:
            payload = json.loads(body.decode("utf-8", errors="replace"))
        except Exception as exc:
            _mcp_proxy_write_json_response(_mcp_proxy_error_response(None, f"invalid JSON-RPC payload: {type(exc).__name__}", -32700))
            continue
        if isinstance(payload, dict):
            messages.append(payload)
    return messages


def _mcp_proxy_emit_jsonl_stdout_line(server_name: str, line: bytes) -> None:
    body = line.strip()
    if not body:
        return
    try:
        payload = json.loads(body.decode("utf-8", errors="replace"))
    except Exception:
        try:
            sys.stderr.buffer.write(body + b"\n")
            sys.stderr.buffer.flush()
        except Exception:
            pass
        return
    _mcp_proxy_observe_json_message(server_name, payload)
    _mcp_proxy_write_stdout_frame(body)


def _mcp_proxy_forward_stdout_jsonl(server_name: str, proc: subprocess.Popen[bytes]) -> None:
    if not proc.stdout:
        return
    buffer = bytearray()
    while True:
        chunk = proc.stdout.read(65536)
        if not chunk:
            break
        buffer.extend(chunk)
        while True:
            newline_idx = buffer.find(b"\n")
            if newline_idx < 0:
                break
            line = bytes(buffer[:newline_idx])
            del buffer[: newline_idx + 1]
            _mcp_proxy_emit_jsonl_stdout_line(server_name, line)
    if buffer.strip():
        _mcp_proxy_emit_jsonl_stdout_line(server_name, bytes(buffer))


def _mcp_proxy_forward_stderr(proc: subprocess.Popen[bytes]) -> None:
    try:
        if not proc.stderr:
            return
        while True:
            chunk = proc.stderr.read(4096)
            if not chunk:
                break
            sys.stderr.buffer.write(chunk)
            sys.stderr.buffer.flush()
    except Exception:
        pass


def _mcp_proxy_streamable_http_request(
    endpoint: str,
    headers: dict[str, str],
    payload: dict[str, Any],
    timeout: float,
    protocol_version: str,
    session_id: str | None,
) -> tuple[Any, str | None]:
    return _mcp_streamable_post_json(endpoint, headers, payload, timeout, protocol_version, session_id)


def run_mcp_streamable_http_proxy(server_name: str, server_config_path: Path) -> int:
    try:
        server = json.loads(server_config_path.read_text(encoding="utf-8"))
    except Exception as exc:
        router_log("ERROR", f"mcp_http_proxy_config_read_failed server={server_name} error={type(exc).__name__}: {exc}")
        print(f"claude-any mcp-proxy: cannot read server config: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
        return 2
    if not isinstance(server, dict) or not _mcp_server_is_streamable_http(server):
        router_log("ERROR", f"mcp_http_proxy_invalid_config server={server_name}")
        print("claude-any mcp-proxy: server config is not a Streamable HTTP MCP server", file=sys.stderr, flush=True)
        return 2
    endpoint = str(server.get("url") or server.get("endpoint") or "").strip()
    custom_headers = server.get("headers") if isinstance(server.get("headers"), dict) else {}
    headers = {str(k): str(v) for k, v in custom_headers.items() if str(k).strip()}
    token = str(server.get("bearer_token") or server.get("token") or "").strip()
    if token and "Authorization" not in headers:
        headers["Authorization"] = f"Bearer {token}"
    protocol_version = str(server.get("mcp_protocol_version") or server.get("protocolVersion") or server.get("protocol_version") or MCP_STREAMABLE_HTTP_PROTOCOL_VERSION)
    timeout = max(5.0, min(120.0, float(server.get("mcp_timeout_seconds") or server.get("timeout") or 20.0)))
    requires_session = parse_bool(server.get("streamable_requires_session", server.get("require_session", server.get("mcp_session_required", True))), True)
    notification_stream_enabled = not _mcp_server_disable_proxy_notification_stream(server)
    session_id: str | None = None
    initialize_payload: dict[str, Any] | None = None
    initialized_payload: dict[str, Any] | None = None
    session_lock = threading.Lock()
    stream_stop = threading.Event()
    # Single-owner notification-stream lifecycle. ONE manager thread owns the
    # backend session and the notification GET stream. It initializes the
    # session, streams notifications, and on session loss re-initializes IN THE
    # SAME thread -- so a second stream can never exist (no leak) and the stream
    # never stays dead while Claude Code is idle (no zombie). The stdin loop
    # NEVER initializes a session or opens a stream; when a tool call needs a
    # session it asks the manager via session_cond and waits briefly.
    session_cond = threading.Condition(session_lock)
    session_requested = False
    initialize_result: dict[str, Any] | None = None
    manager_thread: threading.Thread | None = None
    pending_notifications: list[dict[str, Any]] = []
    pending_wait_count = 0
    notification_condition = threading.Condition()
    router_log("INFO", f"mcp_http_proxy_started server={server_name} endpoint={endpoint}")

    def queue_proxy_notification(payload: dict[str, Any], saved: dict[str, Any] | None) -> None:
        queued = _json_safe_metadata(payload)
        if saved and saved.get("id") is not None:
            queued["claude_any_message_id"] = saved.get("id")
        with notification_condition:
            pending_notifications.append(queued)
            if len(pending_notifications) > 200:
                del pending_notifications[:-200]
            notification_condition.notify_all()

    def has_pending_notification_wait() -> bool:
        with notification_condition:
            return pending_wait_count > 0

    def wait_for_proxy_notifications(payload: dict[str, Any]) -> dict[str, Any]:
        nonlocal pending_wait_count
        request_id = payload.get("id")
        timeout_seconds = _mcp_proxy_wait_timeout_seconds(_mcp_proxy_tool_call_arguments(payload))
        deadline = time.time() + timeout_seconds
        with notification_condition:
            pending_wait_count += 1
            try:
                while not pending_notifications:
                    remaining = deadline - time.time()
                    if remaining <= 0:
                        break
                    notification_condition.wait(timeout=min(1.0, remaining))
                notifications = list(pending_notifications)
                pending_notifications.clear()
            finally:
                pending_wait_count -= 1
        timed_out = not notifications
        router_log(
            "INFO",
            f"mcp_http_proxy_wait_resolved server={server_name} request_id={request_id} count={len(notifications)} timed_out={timed_out}",
        )
        return _mcp_proxy_notification_wait_response(request_id, server_name, notifications, timed_out=timed_out)

    def emit_streamable_sse_message(data_lines: list[str]) -> None:
        if not data_lines:
            return
        data_text = "\n".join(data_lines).strip()
        if not data_text:
            return
        try:
            payload = json.loads(data_text)
        except Exception as exc:
            router_log("WARN", f"mcp_http_proxy_stream_json_failed server={server_name} error={type(exc).__name__}")
            return
        if not isinstance(payload, dict):
            return
        if _mcp_proxy_notification_payload(server_name, payload):
            saved = _mcp_proxy_observe_json_message(server_name, payload, schedule_direct=False)
            if saved:
                queue_proxy_notification(payload, saved)
                pending_wait = has_pending_notification_wait()
                router_log(
                    "INFO",
                    f"mcp_http_proxy_notification_queued server={server_name} message_id={saved.get('id')} pending_wait={pending_wait}",
                )
            return
        _mcp_proxy_observe_json_message(server_name, payload)
        _mcp_proxy_write_json_response(payload)

    def current_session_id() -> str | None:
        with session_lock:
            return session_id

    def request_session_and_wait(timeout_s: float) -> str | None:
        """Ask the manager for a session and wait briefly. Never inits here.

        The stdin loop calls this when a tool call needs a session but none is
        active. It only signals the manager (which solely owns session creation)
        and waits; it never POSTs initialize or opens a stream itself, so two
        owners can never exist.
        """
        nonlocal session_requested
        with session_cond:
            if session_id is not None:
                return session_id
            session_requested = True
            session_cond.notify_all()
            deadline = time.time() + timeout_s
            while session_id is None and not stream_stop.is_set():
                remaining = deadline - time.time()
                if remaining <= 0:
                    break
                session_cond.wait(timeout=min(1.0, remaining))
            return session_id

    def manager_initialize_locked() -> str | None:
        """Re-establish the backend session. Returns the new session id or None.

        Called ONLY by the manager thread, so session_id has a single writer.
        Publishes the initialize result (for the stdin initialize reply) and the
        new session id under session_cond, waking any waiter.
        """
        nonlocal session_id, initialize_result
        init_payload = initialize_payload
        if not init_payload:
            return None
        try:
            result, returned_session = _mcp_proxy_streamable_http_request(
                endpoint, headers, init_payload, timeout, protocol_version, None,
            )
        except Exception as exc:
            router_log("WARN", f"mcp_http_proxy_session_init_failed server={server_name} error={type(exc).__name__}: {exc}")
            return None
        if isinstance(result, dict):
            _mcp_proxy_observe_json_message(server_name, result)
        with session_cond:
            session_id = returned_session or session_id
            if isinstance(result, dict):
                initialize_result = result
            new_session = session_id
            session_cond.notify_all()
        if new_session and initialized_payload:
            try:
                _mcp_proxy_streamable_http_request(
                    endpoint, headers, initialized_payload, timeout, protocol_version, new_session,
                )
            except Exception:
                pass
        if new_session:
            router_log("INFO", f"mcp_http_proxy_session_initialized server={server_name} session={new_session}")
        return new_session

    def stream_manager() -> None:
        """Single owner of the backend session and notification GET stream.

        One thread for the whole proxy lifetime. It (re)initializes the session
        when missing or requested, then runs the GET event-stream; on any
        session loss / reconnect it loops back and re-initializes here, so there
        is never a second stream worker and the stream never stays dead while
        Claude Code is idle.
        """
        nonlocal session_requested, session_id
        last_event_id: str | None = None
        retry_seconds = max(1.0, min(60.0, float(server.get("retry_seconds") or 5.0)))
        read_timeout = max(5.0, min(3600.0, float(server.get("read_timeout_seconds") or server.get("stream_timeout") or 300.0)))
        while not stream_stop.is_set():
            with session_cond:
                current_session = session_id
                requested = session_requested
            # (Re)initialize when we have no session, or a tool call requested one.
            if current_session is None or requested:
                with session_cond:
                    session_requested = False
                current_session = manager_initialize_locked()
                if current_session is None:
                    # Init failed; back off and retry. Wake early on shutdown.
                    with session_cond:
                        if not stream_stop.is_set():
                            session_cond.wait(timeout=retry_seconds)
                    continue
                last_event_id = None
            if not notification_stream_enabled:
                # No stream to own; just idle until shutdown or a session request.
                with session_cond:
                    if not stream_stop.is_set() and not session_requested:
                        session_cond.wait(timeout=read_timeout)
                continue
            worker_session = current_session
            event_name = "message"
            data_lines: list[str] = []
            try:
                request_headers = _mcp_streamable_headers(
                    headers, protocol_version, worker_session, accept="text/event-stream",
                )
                if last_event_id:
                    request_headers["Last-Event-ID"] = last_event_id
                req = urllib.request.Request(endpoint, headers=request_headers, method="GET")
                with urllib.request.urlopen(req, timeout=read_timeout) as response:
                    router_log("INFO", f"mcp_http_proxy_stream_connected server={server_name} session={worker_session} last_event_id={last_event_id or '-'}")
                    while not stream_stop.is_set():
                        with session_cond:
                            if session_requested or session_id != worker_session:
                                break
                        raw = response.readline()
                        if raw == b"":
                            raise ConnectionError("Streamable HTTP MCP notification stream ended")
                        line = raw.decode("utf-8", errors="replace").rstrip("\r\n")
                        if not line:
                            emit_streamable_sse_message(data_lines)
                            data_lines = []
                            event_name = "message"
                            continue
                        if line.startswith(":"):
                            continue
                        field, _, value = line.partition(":")
                        if value.startswith(" "):
                            value = value[1:]
                        if field == "event":
                            event_name = value or "message"
                        elif field == "data":
                            data_lines.append(value)
                        elif field == "id":
                            last_event_id = value
                        elif field == "retry":
                            try:
                                retry_seconds = max(1.0, min(60.0, int(value) / 1000.0))
                            except Exception:
                                pass
                continue
            except urllib.error.HTTPError as exc:
                body_text = _http_error_body_text(exc)
                if _streamable_http_session_not_found(exc, body_text):
                    with session_cond:
                        if session_id == worker_session:
                            session_id = None
                    last_event_id = None
                    router_log("WARN", f"mcp_http_proxy_stream_session_lost server={server_name} error=HTTPError:{exc.code}:{exc.reason}")
                    continue  # re-init at loop top
                router_log("WARN", f"mcp_http_proxy_stream_reconnect server={server_name} event={event_name} error=HTTPError:{exc.code}:{exc.reason}")
            except Exception as exc:
                router_log("WARN", f"mcp_http_proxy_stream_reconnect server={server_name} event={event_name} error={type(exc).__name__}: {exc}")
            # Reconnect backoff. A plain reset retries the same session; a stream
            # that ended because the backend dropped the session will fail the
            # GET with 404 next round and re-init above.
            with session_cond:
                if not stream_stop.is_set():
                    session_cond.wait(timeout=retry_seconds)

    def ensure_manager_started() -> None:
        nonlocal manager_thread
        if manager_thread is not None:
            return
        manager_thread = threading.Thread(
            target=stream_manager, daemon=True, name=f"ca-mcp-http-stream-{server_name}",
        )
        manager_thread.start()

    buffer = bytearray()
    try:
        stdin_fd = sys.stdin.fileno()
        while True:
            chunk = os.read(stdin_fd, 65536)
            if not chunk:
                break
            buffer.extend(chunk)
            for payload in _mcp_proxy_drain_input_messages(buffer):
                request_id = payload.get("id")
                method = str(payload.get("method") or "")
                if method == "notifications/initialized":
                    # Cache it (the manager re-sends it after each re-init) and,
                    # if a session is already up, forward it now so the initial
                    # handshake completes in order.
                    with session_cond:
                        initialized_payload = payload
                        active = session_id
                    if active:
                        try:
                            _mcp_proxy_streamable_http_request(endpoint, headers, payload, timeout, protocol_version, active)
                        except Exception:
                            pass
                    continue
                if method == "initialize":
                    # Hand the initialize off to the single session owner: cache
                    # the payload, wake the manager to (re)initialize, and reply
                    # with the result it produces. stdin never POSTs initialize
                    # itself, so session_id has exactly one writer (the manager).
                    with session_cond:
                        initialize_payload = payload
                        initialize_result = None
                        session_requested = True
                        session_cond.notify_all()
                    ensure_manager_started()
                    with session_cond:
                        deadline = time.time() + timeout
                        while initialize_result is None and not stream_stop.is_set():
                            remaining = deadline - time.time()
                            if remaining <= 0:
                                break
                            session_cond.wait(timeout=min(1.0, remaining))
                        resp = initialize_result
                    if payload.get("id") is not None:
                        if isinstance(resp, dict):
                            reply = dict(resp)
                            reply["id"] = request_id
                            _mcp_proxy_write_json_response(reply)
                        else:
                            _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, "initialize timeout"))
                    continue
                try:
                    if _mcp_proxy_tool_is_notification_wait(_mcp_proxy_tool_call_name(payload)):
                        _mcp_proxy_write_json_response(wait_for_proxy_notifications(payload))
                        continue
                    active_session = current_session_id()
                    if requires_session and not active_session:
                        active_session = request_session_and_wait(timeout)
                        if not active_session:
                            raise RuntimeError("Streamable HTTP MCP session is not initialized")
                    result, _returned = _mcp_proxy_streamable_http_request(
                        endpoint, headers, payload, timeout, protocol_version, active_session,
                    )
                    if isinstance(result, dict):
                        _mcp_proxy_observe_json_message(server_name, result)
                    if payload.get("id") is not None:
                        if isinstance(result, dict):
                            _mcp_proxy_write_json_response(result)
                        else:
                            _mcp_proxy_write_json_response(
                                {"jsonrpc": "2.0", "id": request_id, "result": result if result is not None else {}}
                            )
                except urllib.error.HTTPError as exc:
                    body_text = _http_error_body_text(exc)
                    if _streamable_http_session_not_found(exc, body_text) and initialize_payload:
                        # Backend dropped the session mid tool-call. Ask the
                        # manager to re-init (single owner) and retry once on the
                        # session it provides. The stream resumes on that same
                        # re-init -- no second owner.
                        router_log("WARN", f"mcp_http_proxy_session_lost server={server_name} method={method} error=HTTPError:{exc.code}:{exc.reason}")
                        with session_cond:
                            if session_id is not None:
                                session_id = None
                            session_requested = True
                            session_cond.notify_all()
                        active_session = request_session_and_wait(timeout)
                        if active_session:
                            try:
                                result, _r = _mcp_proxy_streamable_http_request(
                                    endpoint, headers, payload, timeout, protocol_version, active_session,
                                )
                                if isinstance(result, dict):
                                    _mcp_proxy_observe_json_message(server_name, result)
                                if payload.get("id") is not None:
                                    _mcp_proxy_write_json_response(result if isinstance(result, dict) else {"jsonrpc": "2.0", "id": request_id, "result": result if result is not None else {}})
                                continue
                            except Exception as retry_exc:
                                _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, f"{type(retry_exc).__name__}: {retry_exc}"))
                                continue
                        _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, "Streamable HTTP MCP session is not initialized"))
                        continue
                    _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, f"HTTPError:{exc.code}: {exc.reason}; {body_text}".strip()))
                except Exception as exc:
                    _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, f"{type(exc).__name__}: {exc}"))
        for payload in _mcp_proxy_drain_input_messages(buffer, final=True):
            request_id = payload.get("id")
            try:
                if _mcp_proxy_tool_is_notification_wait(_mcp_proxy_tool_call_name(payload)):
                    _mcp_proxy_write_json_response(wait_for_proxy_notifications(payload))
                    continue
                result, _r = _mcp_proxy_streamable_http_request(endpoint, headers, payload, timeout, protocol_version, current_session_id())
                if payload.get("id") is not None:
                    _mcp_proxy_write_json_response(result if isinstance(result, dict) else {"jsonrpc": "2.0", "id": request_id, "result": result if result is not None else {}})
            except Exception as exc:
                _mcp_proxy_write_json_response(_mcp_proxy_error_response(request_id, f"{type(exc).__name__}: {exc}"))
        router_log("INFO", f"mcp_http_proxy_exited server={server_name}")
        return 0
    except Exception as exc:
        router_log("ERROR", f"mcp_http_proxy_failed server={server_name} error={type(exc).__name__}: {exc}")
        print(f"claude-any mcp-proxy: Streamable HTTP bridge failed: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
        return 1
    finally:
        stream_stop.set()
        with session_cond:
            session_cond.notify_all()
        if manager_thread is not None:
            manager_thread.join(timeout=2.0)


def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
    try:
        server = json.loads(server_config_path.read_text(encoding="utf-8"))
    except Exception as exc:
        router_log("ERROR", f"mcp_proxy_config_read_failed server={server_name} error={type(exc).__name__}: {exc}")
        print(f"claude-any mcp-proxy: cannot read server config: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
        return 2
    if not isinstance(server, dict) or not _mcp_server_is_stdio(server):
        router_log("ERROR", f"mcp_proxy_invalid_config server={server_name}")
        print("claude-any mcp-proxy: server config is not a stdio MCP server", file=sys.stderr, flush=True)
        return 2
    command = str(server.get("command") or "").strip()
    args = [str(item) for item in server.get("args", [])] if isinstance(server.get("args"), list) else []
    command, args = resolve_mcp_server_process(command, args)
    env = os.environ.copy()
    raw_env = server.get("env")
    if isinstance(raw_env, dict):
        env.update({str(k): str(v) for k, v in raw_env.items() if str(k)})
    cwd_value = server.get("cwd") or server.get("workingDirectory")
    cwd = str(cwd_value) if cwd_value else None
    try:
        proc = subprocess.Popen(
            [command, *args],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=cwd,
            env=env,
            bufsize=0,
        )
    except Exception as exc:
        router_log("ERROR", f"mcp_proxy_start_failed server={server_name} command={command} error={type(exc).__name__}: {exc}")
        print(f"claude-any mcp-proxy: failed to start {command}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
        return 127
    stdio_mode = _mcp_proxy_stdio_mode(server)
    router_log("INFO", f"mcp_proxy_started server={server_name} command={command} stdio={stdio_mode}")
    stdin_target = _mcp_proxy_forward_stdin_jsonl if stdio_mode == "jsonl" else _mcp_proxy_forward_stdin
    threading.Thread(target=stdin_target, args=(proc,), daemon=True, name=f"mcp-proxy-stdin-{server_name}").start()
    threading.Thread(target=_mcp_proxy_forward_stderr, args=(proc,), daemon=True, name=f"mcp-proxy-stderr-{server_name}").start()
    try:
        if stdio_mode == "jsonl":
            _mcp_proxy_forward_stdout_jsonl(server_name, proc)
        elif proc.stdout:
            observer = _McpStdoutObserver(server_name)
            while True:
                chunk = proc.stdout.read(65536)
                if not chunk:
                    break
                observer.feed(chunk)
                sys.stdout.buffer.write(chunk)
                sys.stdout.buffer.flush()
        rc = proc.wait()
        level = "INFO" if rc == 0 else "WARN"
        router_log(level, f"mcp_proxy_exited server={server_name} rc={rc}")
        return rc
    finally:
        if proc.poll() is None:
            try:
                proc.terminate()
            except Exception:
                pass


def cmd_mcp_proxy(argv: list[str]) -> int:
    parser = argparse.ArgumentParser(prog="claude-any mcp-proxy")
    parser.add_argument("--server-name", required=True)
    parser.add_argument("--server-config", required=True)
    args = parser.parse_args(argv)
    server_config_path = Path(args.server_config).expanduser()
    try:
        server = json.loads(server_config_path.read_text(encoding="utf-8"))
    except Exception:
        server = None
    if isinstance(server, dict) and _mcp_server_is_streamable_http(server):
        return run_mcp_streamable_http_proxy(args.server_name, server_config_path)
    return run_mcp_stdio_proxy(args.server_name, server_config_path)


def run_claude_update_check(claude: str, enabled: bool = True) -> None:
    if not enabled:
        return
    if os.environ.get("CLAUDE_ANY_SKIP_CLAUDE_UPDATE") == "1":
        return
    print("Checking Claude Code update before launch...", flush=True)
    current = claude_code_current_version(claude)
    if current:
        print(f"Current Claude Code version: {current}", flush=True)
    npm = find_executable("npm")
    if not npm:
        print("Claude Code update check skipped: npm was not found.", flush=True)
        return
    package_spec = os.environ.get("CLAUDE_ANY_CLAUDE_CODE_PACKAGE", "@anthropic-ai/claude-code@latest")
    latest = npm_latest_package_version(npm, package_spec)
    if not latest:
        print("Claude Code update check could not read the latest npm version; continuing.", flush=True)
        return
    if current and not version_newer(latest, current):
        print(f"Claude Code is up to date ({current}).", flush=True)
        return
    current_label = current or "unknown"
    print(f"Claude Code update available: {current_label} -> {latest}", flush=True)
    if not (sys.stdin.isatty() and sys.stdout.isatty()):
        print("Claude Code update requires confirmation; skipping in non-interactive mode.", flush=True)
        return
    answer = input("Update Claude Code now with `claude update`? [y/N] ").strip().lower()
    if answer not in ("y", "yes"):
        return
    update_env = os.environ.copy()
    update_env["PATH"] = path_with_claude_any_user_dirs(update_env)
    try:
        p = subprocess.run(
            [claude, "update"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            env=update_env,
            timeout=180,
        )
    except subprocess.TimeoutExpired:
        print("Claude Code update timed out; continuing with current version.", flush=True)
        return
    except Exception as exc:
        print(f"Claude Code update failed ({type(exc).__name__}); continuing.", flush=True)
        return
    out = (p.stdout or "").strip()
    if out:
        print(out, flush=True)
    if p.returncode != 0:
        print(f"Claude Code update exited with {p.returncode}; continuing with current version.", flush=True)
        return
    new_version = claude_code_current_version(find_executable("claude") or claude)
    if new_version:
        print(f"Claude Code version after update: {new_version}", flush=True)


def parse_version_tuple(value: str) -> tuple[int, ...]:
    parts: list[int] = []
    for item in re.split(r"[^0-9]+", value.strip()):
        if item:
            parts.append(int(item))
    return tuple(parts)


def version_newer(latest: str, current: str) -> bool:
    left = list(parse_version_tuple(latest))
    right = list(parse_version_tuple(current))
    size = max(len(left), len(right), 1)
    left.extend([0] * (size - len(left)))
    right.extend([0] * (size - len(right)))
    return tuple(left) > tuple(right)


def npm_latest_package_version(npm: str, package_spec: str, timeout: float = 8.0) -> str:
    try:
        p = subprocess.run(
            [npm, "view", package_spec, "version"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            timeout=timeout,
        )
    except Exception:
        return ""
    if p.returncode != 0:
        return ""
    out = (p.stdout or "").strip()
    return out.splitlines()[-1].strip() if out else ""


def npm_global_package_root(npm: str, package_name: str = "@oneciel-ai/claude-any", timeout: float = 8.0) -> Path | None:
    try:
        p = subprocess.run(
            [npm, "root", "-g"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            timeout=timeout,
        )
    except Exception:
        return None
    if p.returncode != 0:
        return None
    root = (p.stdout or "").strip()
    if not root:
        return None
    package_path = Path(root)
    for part in package_name.split("/"):
        if part:
            package_path /= part
    return package_path


def npm_prefix_from_package_root(package_root: Path) -> Path | None:
    """Infer the npm install prefix from an installed package root.

    npm global installs normally land under either:
    - <prefix>/lib/node_modules/@scope/name on POSIX
    - <prefix>/node_modules/@scope/name on Windows

    Updating without this prefix can write to npm's current default global
    prefix, which may not be the prefix that supplied the running executable.
    """
    parts = package_root.parts
    for idx, part in enumerate(parts):
        if part != "node_modules":
            continue
        try:
            node_modules = Path(*parts[: idx + 1])
        except Exception:
            return None
        parent = node_modules.parent
        if parent.name == "lib":
            return parent.parent
        return parent
    return None


def current_npm_install_prefix() -> Path | None:
    root = current_npm_package_root()
    return npm_prefix_from_package_root(root) if root else None


def npm_global_install_command(npm: str, package_spec: str, prefix: Path | None = None) -> list[str]:
    cmd = [npm, "install", "-g"]
    if prefix is not None:
        cmd.extend(["--prefix", str(prefix)])
    cmd.append(package_spec)
    return cmd


def npm_global_bin_dir_from_prefix(prefix: Path) -> Path:
    if os.name == "nt":
        return prefix
    return prefix / "bin"


def claude_code_current_version(claude: str) -> str:
    try:
        p = subprocess.run(
            [claude, "--version"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            timeout=8,
        )
    except Exception:
        return ""
    if p.returncode != 0:
        return ""
    match = re.search(r"\d+(?:\.\d+)+", p.stdout or "")
    return match.group(0) if match else ""


def running_from_npm_package() -> bool:
    if os.environ.get("CLAUDE_ANY_NPM_MODE") is not None:
        return True
    path = str(Path(__file__).resolve()).replace("\\", "/")
    return "/node_modules/@oneciel-ai/claude-any/" in path


def package_root_from_installed_path(path: Path) -> Path | None:
    """Return the npm package root when a path lives inside this package."""
    try:
        resolved = path.resolve(strict=False)
    except Exception:
        resolved = path
    parts = resolved.parts
    for idx in range(0, max(0, len(parts) - 2)):
        if parts[idx] == "node_modules" and parts[idx + 1] == "@oneciel-ai" and parts[idx + 2] == "claude-any":
            try:
                return Path(*parts[: idx + 3])
            except Exception:
                return None
    return None


def current_npm_package_root() -> Path | None:
    return package_root_from_installed_path(Path(__file__))


def claude_any_launcher_candidate_dirs() -> list[Path]:
    raw_dirs: list[Path] = []
    for entry in os.environ.get("PATH", "").split(os.pathsep):
        if entry:
            raw_dirs.append(Path(entry))
    raw_dirs.extend(executable_extra_dirs())
    raw_dirs.extend([HOME / ".npm-global" / "bin", HOME / "bin"])
    if os.name != "nt":
        raw_dirs.extend([Path("/usr/local/bin"), Path("/usr/bin")])
    seen: set[str] = set()
    out: list[Path] = []
    for directory in raw_dirs:
        key = str(directory)
        if key in seen:
            continue
        seen.add(key)
        out.append(directory)
    return out


def claude_any_launcher_candidates() -> list[Path]:
    names = ["claude-any"]
    if os.name == "nt":
        names.extend(["claude-any.cmd", "claude-any.exe"])
    out: list[Path] = []
    seen: set[str] = set()
    for directory in claude_any_launcher_candidate_dirs():
        for name in names:
            candidate = directory / name
            if not candidate.exists():
                continue
            try:
                key = str(candidate.resolve(strict=False))
            except Exception:
                key = str(candidate)
            if key in seen:
                continue
            seen.add(key)
            out.append(candidate)
    return out


def claude_any_launcher_version(path: Path, timeout: float = 5.0) -> str:
    env = os.environ.copy()
    env["CLAUDE_ANY_SKIP_INSTALL_DIAGNOSTIC"] = "1"
    env["CLAUDE_ANY_SKIP_SELF_UPDATE"] = "1"
    env["CLAUDE_ANY_SELF_UPDATE_CHECK"] = "off"
    try:
        proc = subprocess.run(
            [str(path), "--version"],
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            env=env,
            timeout=timeout,
        )
    except Exception:
        return ""
    if proc.returncode != 0:
        return ""
    match = re.search(r"claude-any\s+(.+)", proc.stdout or "", re.IGNORECASE)
    return match.group(1).strip() if match else (proc.stdout or "").strip().splitlines()[-1].strip()


def claude_any_install_diagnostics() -> list[dict[str, str]]:
    rows: list[dict[str, str]] = []
    for launcher in claude_any_launcher_candidates():
        root = package_root_from_installed_path(launcher)
        rows.append(
            {
                "launcher": str(launcher),
                "resolved": str(launcher.resolve(strict=False)),
                "package_root": str(root) if root else "",
                "version": claude_any_launcher_version(launcher),
            }
        )
    return rows


def warn_if_multiple_claude_any_installs() -> None:
    if os.environ.get("CLAUDE_ANY_SKIP_INSTALL_DIAGNOSTIC") == "1":
        return
    if not (sys.stdin.isatty() and sys.stdout.isatty()):
        return
    rows = claude_any_install_diagnostics()
    roots = {row["package_root"] for row in rows if row.get("package_root")}
    if len(roots) <= 1:
        return
    current_root = str(current_npm_package_root() or "")
    first = rows[0] if rows else {}
    newest = max((row for row in rows if row.get("version")), key=lambda row: parse_version_tuple(row["version"]), default=None)
    print("Claude Any warning: multiple claude-any npm installs are visible.", file=sys.stderr, flush=True)
    if first:
        print(
            f"  shell resolves claude-any to: {first.get('launcher')} ({first.get('version') or 'unknown version'})",
            file=sys.stderr,
            flush=True,
        )
    if current_root:
        print(f"  current package root: {current_root}", file=sys.stderr, flush=True)
    if newest and newest is not first:
        print(
            f"  newer visible install: {newest.get('launcher')} ({newest.get('version')})",
            file=sys.stderr,
            flush=True,
        )
    print("  Fix by keeping one install prefix: update or uninstall the stale higher-priority install.", file=sys.stderr, flush=True)


def claude_any_restart_user_args() -> list[str]:
    args = list(sys.argv[1:])
    if args and args[0] == "cli":
        return args[1:]
    return args


def restart_claude_any_after_update(npm: str, package_root: Path | None = None) -> None:
    os.environ["CLAUDE_ANY_SKIP_SELF_UPDATE"] = "1"
    user_args = claude_any_restart_user_args()
    package_root = package_root or current_npm_package_root() or npm_global_package_root(npm)
    package_script = package_root / "claude_any.py" if package_root else None
    if package_script and package_script.exists():
        os.execv(sys.executable, [sys.executable, str(package_script), "cli", *user_args])
    launcher = find_executable("claude-any")
    if launcher:
        raise SystemExit(subprocess.call([launcher, *user_args], env=os.environ.copy()))
    os.execv(sys.executable, [sys.executable, *sys.argv])


def run_claude_any_update_check(enabled: bool = True) -> bool:
    if not enabled:
        return False
    if os.environ.get("CLAUDE_ANY_SKIP_SELF_UPDATE") == "1":
        return False
    if env_bool(os.environ.get("CLAUDE_ANY_SELF_UPDATE_CHECK")) is False:
        return False
    if not running_from_npm_package():
        return False
    if not (sys.stdin.isatty() and sys.stdout.isatty()):
        return False
    npm = find_executable("npm")
    if not npm:
        return False
    latest = npm_latest_package_version(npm, "@oneciel-ai/claude-any@latest")
    if not latest or not version_newer(latest, VERSION):
        return False
    print(f"Claude Any update available: {VERSION} -> {latest}", flush=True)
    answer = input("Update now with npm? [y/N] ").strip().lower()
    if answer not in ("y", "yes"):
        return False
    package_root = current_npm_package_root()
    install_prefix = npm_prefix_from_package_root(package_root) if package_root else None
    update_cmd = npm_global_install_command(npm, "@oneciel-ai/claude-any@latest", install_prefix)
    if install_prefix is not None:
        print(f"Updating current Claude Any install prefix: {install_prefix}", flush=True)
    try:
        update = subprocess.run(
            update_cmd,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            timeout=300,
        )
    except subprocess.TimeoutExpired:
        print("Claude Any update timed out; continuing with current version.", flush=True)
        return False
    except Exception as exc:
        print(f"Claude Any update failed ({type(exc).__name__}); continuing.", flush=True)
        return False
    out = (update.stdout or "").strip()
    if out:
        print(out, flush=True)
    if update.returncode != 0:
        print(f"Claude Any update exited with {update.returncode}; continuing with current version.", flush=True)
        if install_prefix is not None:
            print(
                f"Update targeted the active install prefix ({install_prefix}). "
                "If this prefix is not writable, reinstall or update with the permissions used for that prefix.",
                flush=True,
            )
        return False
    print("Claude Any updated. Restarting with the new version...", flush=True)
    try:
        restart_claude_any_after_update(npm, package_root=package_root)
    except SystemExit:
        raise
    except Exception as exc:
        print(f"Restart failed ({type(exc).__name__}); continuing with the current process.", flush=True)
    return True


def run_command_for_upgrade(cmd: list[str], timeout: float = 300.0) -> tuple[int, str]:
    try:
        proc = subprocess.run(
            cmd,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            timeout=timeout,
        )
    except subprocess.TimeoutExpired:
        return 124, "timed out"
    except Exception as exc:
        return 1, f"{type(exc).__name__}: {exc}"
    return proc.returncode, (proc.stdout or "").strip()


def quiet_upgrade_claude_any() -> int:
    npm = find_executable("npm")
    if not npm:
        print("Claude Any update skipped: npm was not found.", flush=True)
        return 1
    latest = npm_latest_package_version(npm, "@oneciel-ai/claude-any@latest")
    if latest and not version_newer(latest, VERSION):
        print(f"Claude Any is up to date ({VERSION}).", flush=True)
        return 0
    target = latest or "latest"
    print(f"Updating Claude Any to {target}...", flush=True)
    package_root = current_npm_package_root()
    install_prefix = npm_prefix_from_package_root(package_root) if package_root else None
    if install_prefix is not None:
        print(f"Updating current Claude Any install prefix: {install_prefix}", flush=True)
    rc, out = run_command_for_upgrade(npm_global_install_command(npm, "@oneciel-ai/claude-any@latest", install_prefix), timeout=300)
    if out:
        print(out, flush=True)
    if rc != 0:
        print(f"Claude Any update failed ({rc}).", flush=True)
        if install_prefix is not None:
            print(
                f"Update targeted the active install prefix ({install_prefix}). "
                "If this prefix is not writable, reinstall or update with the permissions used for that prefix.",
                flush=True,
            )
    return rc


def quiet_upgrade_claude_code() -> int:
    claude = find_executable("claude")
    if not claude:
        claude = install_claude_code_if_missing()
        return 0 if claude else 1
    current = claude_code_current_version(claude)
    npm = find_executable("npm")
    latest = ""
    if npm:
        package_spec = os.environ.get("CLAUDE_ANY_CLAUDE_CODE_PACKAGE", "@anthropic-ai/claude-code@latest")
        latest = npm_latest_package_version(npm, package_spec)
    if current and latest and not version_newer(latest, current):
        print(f"Claude Code is up to date ({current}).", flush=True)
        return 0
    target = latest or "latest"
    current_label = current or "unknown"
    print(f"Updating Claude Code ({current_label} -> {target})...", flush=True)
    rc, out = run_command_for_upgrade([claude, "update"], timeout=180)
    if out:
        print(out, flush=True)
    if rc != 0:
        print(f"Claude Code update failed ({rc}).", flush=True)
    return rc


def install_claude_code_if_missing() -> str | None:
    claude = find_executable("claude")
    if claude:
        return claude
    if os.environ.get("CLAUDE_ANY_SKIP_CLAUDE_INSTALL") == "1":
        return None
    npm = find_executable("npm")
    if not npm:
        print(
            "Claude Code executable was not found, and npm is not available to install @anthropic-ai/claude-code.",
            flush=True,
        )
        return None
    package_spec = os.environ.get("CLAUDE_ANY_CLAUDE_CODE_PACKAGE", "@anthropic-ai/claude-code@latest")
    install_prefix = current_npm_install_prefix()
    cmd = npm_global_install_command(npm, package_spec, install_prefix)
    cmd.insert(3, "--prefer-online")
    print(f"Claude Code executable was not found; installing {package_spec}...", flush=True)
    if install_prefix is not None:
        print(f"Installing Claude Code into active npm prefix: {install_prefix}", flush=True)
    rc, out = run_command_for_upgrade(cmd, timeout=300)
    if out:
        print(out, flush=True)
    if rc != 0:
        print(f"Claude Code install failed ({rc}).", flush=True)
        if install_prefix is not None:
            print(
                f"Install targeted the active install prefix ({install_prefix}). "
                "If this prefix is not writable, install Claude Code with the permissions used for that prefix.",
                flush=True,
            )
        return None
    if install_prefix is not None:
        bin_dir = str(npm_global_bin_dir_from_prefix(install_prefix))
        path = os.environ.get("PATH", "")
        if bin_dir and bin_dir not in path.split(os.pathsep):
            os.environ["PATH"] = bin_dir + (os.pathsep + path if path else "")
    claude = find_executable("claude")
    if claude:
        print(f"Claude Code installed: {claude}", flush=True)
    else:
        print("Claude Code install completed, but the claude executable is still not visible in PATH.", flush=True)
    return claude


def run_quiet_upgrade_and_exit() -> int:
    any_rc = quiet_upgrade_claude_any()
    claude_rc = quiet_upgrade_claude_code()
    return 0 if any_rc == 0 and claude_rc == 0 else 1


def launch_claude(
    passthrough: list[str],
    skip_menu: bool = False,
    force_menu: bool = False,
    web_search_override: bool | None = None,
    update_check: bool = True,
    self_update_check: bool = True,
) -> int:
    if has_noninteractive_claude_args(passthrough):
        update_check = False
        self_update_check = False
    warn_if_multiple_claude_any_installs()
    run_claude_any_update_check(enabled=self_update_check)
    auto_import_passthrough_channels(passthrough)
    rc = run_prelaunch_menu(passthrough, skip_menu=skip_menu, force_menu=force_menu)
    if rc == 10:
        return 0
    if rc != 0:
        return rc
    cfg = load_config()
    provider, pcfg = get_current_provider(cfg)
    blockers = launch_readiness_errors(cfg)
    if blockers:
        print("Claude Any launch blocked:", flush=True)
        for line in blockers:
            print(f"- {line}", flush=True)
        return 2
    use_native_anthropic = direct_native_anthropic_enabled(provider, pcfg)
    use_router_mode = not use_native_anthropic
    launch_cwd_key = current_launch_cwd_key()
    fork_native_session, previous_launch_mode = should_fork_native_session_after_mode_switch(
        provider,
        pcfg,
        use_native_anthropic,
        passthrough,
        launch_cwd_key,
    )
    cleanup_managed_services_for_provider(provider, pcfg, cfg, quiet=True)
    env = os.environ.copy()
    env["PATH"] = path_with_claude_any_user_dirs(env)
    launch_env = env_vars(cfg)
    launch_passthrough = normalize_channel_passthrough(passthrough)
    native_channel_bridge = should_use_native_channel_bridge(use_router_mode, cfg, launch_passthrough)
    stdin_channel_proxy = should_use_channel_stdin_proxy(use_router_mode, launch_passthrough, cfg)
    llm_channel_delivery = should_use_channel_llm_delivery(use_router_mode, launch_passthrough, cfg)
    native_auto_channel_specs: list[str] = []
    if use_native_anthropic and not native_channel_bridge and not native_channel_passthrough_requested(launch_passthrough):
        try:
            auto_channel_names = external_mcp_channel_server_names_from_configs(launch_passthrough)
            native_auto_channel_specs = [f"server:{name}" for name in auto_channel_names]
            if native_auto_channel_specs:
                router_log(
                    "INFO",
                    "channel_native_auto_specs servers=%s" % ",".join(auto_channel_names),
                )
        except Exception as exc:
            router_log("WARN", f"channel_native_auto_probe_failed error={type(exc).__name__}: {exc}")
    manage_router_lifetime = False
    if use_router_mode or llm_channel_delivery:
        manage_router_lifetime = bool(start_router_if_needed())
    if claude_channels_requested(cfg, launch_passthrough) or native_channel_bridge or llm_channel_delivery or native_auto_channel_specs:
        env.pop("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS", None)
        launch_env.pop("CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS", None)
    if use_native_anthropic:
        # Claude Native guarantee — strip every env var claude-any (or a
        # prior claude-any session) might have left behind that would change
        # Claude Code's default model selection, backend, advisor flow, or
        # other behavior. See env_vars() docstring for the contract.
        for key in (
            "ANTHROPIC_BASE_URL",
            "ANTHROPIC_MODEL",
            "ANTHROPIC_CUSTOM_MODEL_OPTION",
            "ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES",
            "ANTHROPIC_DEFAULT_HAIKU_MODEL",
            "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTS",
            "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES",
            "ANTHROPIC_DEFAULT_OPUS_MODEL",
            "ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTS",
            "ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES",
            "ANTHROPIC_DEFAULT_SONNET_MODEL",
            "ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTS",
            "ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES",
            "CLAUDE_CODE_SUBAGENT_MODEL",
            "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
            "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
            "CLAUDE_CODE_MAX_OUTPUT_TOKENS",
            "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
            "CLAUDE_CODE_EFFORT_LEVEL",
            "CLAUDE_CODE_DISABLE_TERMINAL_TITLE",
            "CLAUDE_CODE_ATTRIBUTION_HEADER",
            "CLAUDE_ANY_ADVISOR_MODEL",
            "CLAUDE_ANY_BYPASS_PERMISSIONS",
            "CLAUDE_ANY_MODEL_ALIAS",
        ):
            env.pop(key, None)
            launch_env.pop(key, None)
        if "ANTHROPIC_API_KEY" in launch_env:
            env.pop("ANTHROPIC_AUTH_TOKEN", None)
        router_log(
            "INFO",
            "claude_native_launch model=<defer-to-claude-code> advisor=off backend=<default-anthropic>",
        )
        disable_claude_any_slash_commands_for_native()
    elif anthropic_routed_enabled(provider, pcfg):
        router_log(
            "INFO",
            "claude_anthropic_routed_launch backend=claude-any-router upstream=anthropic",
        )
    env.update(launch_env)
    if not use_native_anthropic:
        preserve_anthropic_auth = anthropic_routed_enabled(provider, pcfg)
        for key in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"):
            if key not in launch_env and not preserve_anthropic_auth:
                env.pop(key, None)
        install_claude_any_slash_commands(include_advisor=provider != "anthropic")
        install_tool_guard_hooks()
        install_claude_any_statusline()
    claude = install_claude_code_if_missing()
    if not claude:
        raise RuntimeError(
            "claude executable was not found in PATH or the Claude Any user bin directories, "
            "and automatic install of @anthropic-ai/claude-code did not make it available"
        )
    run_claude_update_check(claude, enabled=update_check)
    claude = find_executable("claude") or claude
    if native_channel_bridge or native_auto_channel_specs:
        auth_ok, auth_reason = claude_code_channels_auth_available(claude)
        if not auth_ok:
            if native_channel_bridge:
                router_log("WARN", f"channel_native_unavailable_fallback reason={auth_reason} delivery=llm")
                native_channel_bridge = False
                llm_channel_delivery = True
            else:
                router_log("WARN", f"channel_native_auto_disabled reason={auth_reason}")
                native_auto_channel_specs = []
    extra_args: list[str] = []
    mcp_config_paths: list[str] = []
    if should_attach_web_search(provider, cfg, web_search_override):
        mcp_config_paths.append(str(write_duckduckgo_mcp_config(cfg)))
    if llm_channel_delivery:
        mcp_config_paths.append(str(write_channel_mcp_config()))
    native_direct_mcp_config_paths: list[str] = []
    if use_native_anthropic:
        native_mcp_config = write_native_mcp_config_from_discovery(launch_passthrough)
        if native_mcp_config:
            native_direct_mcp_config_paths = [str(native_mcp_config)]
    detected_channel_specs: list[str] = []
    detected_channel_capable_names: list[str] = []
    channel_probe_source_paths: list[Path] = []
    if stdin_channel_proxy or llm_channel_delivery:
        try:
            ensure_channel_probe_cache_for_launch(cfg, launch_passthrough)
            capable_names = cached_channel_capable_server_names()
            detected_channel_capable_names = list(capable_names)
            detected_channel_specs = [f"server:{name}" for name in capable_names]
            channel_launch_specs = channel_specs_for_launch(cfg, launch_passthrough, detected_channel_specs)
            channel_probe_source_paths = cached_channel_source_paths_for_specs(channel_launch_specs)
            if channel_probe_source_paths:
                mcp_config_paths.extend(str(path) for path in channel_probe_source_paths)
            cache_age = read_channel_probe_cache().get("probed_at") or 0
            router_log(
                "INFO",
                "channel_probe_loaded source=cache cache_age_ts=%d count=%d servers=%s sources=%s"
                % (
                    int(cache_age),
                    len(capable_names),
                    ",".join(capable_names) or "-",
                    ",".join(str(path) for path in channel_probe_source_paths) or "-",
                ),
            )
        except Exception as exc:
            router_log("WARN", f"channel_probe_cache_load_failed error={type(exc).__name__}: {exc}")
    claude_passthrough = list(launch_passthrough)
    if use_native_anthropic:
        if native_direct_mcp_config_paths:
            mcp_config_paths.extend(native_direct_mcp_config_paths)
            claude_passthrough = strip_mcp_config_passthrough(launch_passthrough)
    elif stdin_channel_proxy or llm_channel_delivery or native_auto_channel_specs:
        if llm_channel_delivery:
            ensure_channel_llm_delivery_cursor_initialized()
        if should_launch_process_start_channel_sse(stdin_channel_proxy, native_channel_bridge, llm_channel_delivery):
            auto_start_sse_channels_from_mcp_configs(
                launch_passthrough,
                extra_config_paths=[Path(path) for path in mcp_config_paths],
            )
        else:
            router_log("INFO", "channel_sse_auto_start_skipped reason=router_managed_llm_delivery")
        # Channel-capable streamable-HTTP backends (e.g. ai-net-http) are forced
        # through claude-any's own mcp-proxy so there is exactly ONE backend
        # connection: the proxy serves Claude Code's tool calls AND owns the
        # notification stream + idle-death wake handling. Because the proxy now
        # OWNS the stream, it must NOT also be in the disable set -- forcing a
        # server while disabling its stream would leave zero notification owners
        # and the agent would never wake. force and disable are mutually
        # exclusive per server, so the disable set excludes anything we force.
        forced_channel_names = (
            set(detected_channel_capable_names)
            if (stdin_channel_proxy or llm_channel_delivery)
            else set()
        )
        proxy_config = write_mcp_proxy_config(
            launch_passthrough,
            extra_config_paths=[Path(path) for path in mcp_config_paths],
            force_proxy_server_names=forced_channel_names or None,
            disable_proxy_notification_stream_names=None,
        )
        if proxy_config:
            mcp_config_paths = [str(proxy_config)]
            claude_passthrough = strip_mcp_config_passthrough(launch_passthrough)
    if mcp_config_paths:
        extra_args.extend(["--mcp-config", *mcp_config_paths])
    if should_append_compat_prompt(provider, cfg) and not has_passthrough_option(launch_passthrough, "--system-prompt"):
        extra_args.extend(["--append-system-prompt", NON_ANTHROPIC_COMPAT_PROMPT])
    extra_args.extend(
        claude_channel_args(
            cfg,
            launch_passthrough,
            extra_specs=native_auto_channel_specs if native_auto_channel_specs else detected_channel_specs,
            native_channel_bridge=bool(native_channel_bridge or native_auto_channel_specs),
        )
    )
    append_claude_code_runtime_settings_args(extra_args, launch_passthrough, provider, pcfg)
    if fork_native_session:
        session_id = str(uuid.uuid4())
        extra_args.extend(["--session-id", session_id])
        router_log(
            "INFO",
            f"claude_native_session_boundary previous_mode={previous_launch_mode} cwd={launch_cwd_key} session_id={session_id}",
        )
    cmd = [
        claude,
        "--dangerously-skip-permissions",
    ]
    if (
        not use_native_anthropic
        and not has_passthrough_option([*extra_args, *claude_passthrough], "--permission-mode")
        and claude_supports_permission_mode_arg(claude)
    ):
        cmd.extend(["--permission-mode", "bypassPermissions"])
    model = env.get("CLAUDE_ANY_MODEL_ALIAS")
    if model:
        cmd.extend(["--model", model])
    cmd.extend(extra_args)
    if should_insert_passthrough_option_boundary(extra_args, claude_passthrough):
        cmd.append("--")
    cmd.extend(claude_passthrough)
    _log_claude_command_for_diagnostics(cmd, env)
    record_launch_state_for_cwd(
        launch_cwd_key,
        provider,
        launch_mode_name(provider, pcfg, use_native_anthropic),
        str(pcfg.get("current_model") or env.get("CLAUDE_ANY_MODEL_ALIAS") or ""),
    )
    capture_stderr = env_bool(os.environ.get("CLAUDE_ANY_CAPTURE_CC_STDERR"), False)
    screen_summary_proxy = should_use_channel_screen_summary_proxy(
        llm_channel_delivery,
        detected_channel_specs,
        claude_passthrough,
    )
    def run_claude_process() -> int:
        if stdin_channel_proxy or screen_summary_proxy:
            if screen_summary_proxy and not stdin_channel_proxy:
                return subprocess_call_with_channel_screen_summary_proxy(cmd, env)
            return subprocess_call_with_channel_wake_proxy(cmd, env)
        if capture_stderr:
            return _subprocess_call_capturing_stderr(cmd, env)
        return subprocess.call(cmd, env=env)

    return run_with_router_lifetime(run_claude_process, manage_router_lifetime)


CLAUDE_CODE_STDERR_LOG = CONFIG_DIR / "claude-code-stderr.log"


def _log_claude_command_for_diagnostics(cmd: list[str], env: dict[str, str]) -> None:
    try:
        mcp_idx = cmd.index("--mcp-config") if "--mcp-config" in cmd else -1
        mcp_value = cmd[mcp_idx + 1] if 0 <= mcp_idx < len(cmd) - 1 else "-"
    except Exception:
        mcp_value = "-"
    channel_specs_in_cmd: list[str] = []
    if "--dangerously-load-development-channels" in cmd:
        start = cmd.index("--dangerously-load-development-channels") + 1
        for arg in cmd[start:]:
            if arg.startswith("--"):
                break
            channel_specs_in_cmd.append(arg)
    router_log(
        "INFO",
        "claude_launch_cmd mcp_config=%s channels=%s argv_len=%d"
        % (
            mcp_value,
            ",".join(channel_specs_in_cmd) or "-",
            len(cmd),
        ),
    )
    relevant_env_keys = (
        "ANTHROPIC_BASE_URL",
        "ANTHROPIC_MODEL",
        "ANTHROPIC_API_KEY",
        "ANTHROPIC_AUTH_TOKEN",
        "CLAUDE_ANY_PROVIDER",
        "CLAUDE_ANY_MODEL_ALIAS",
        "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
        "CLAUDE_CODE_EFFORT_LEVEL",
        "ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTS",
        "ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTS",
        "ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTS",
    )
    env_summary = []
    for key in relevant_env_keys:
        if key in env:
            val = env[key]
            if "KEY" in key or "TOKEN" in key:
                val = mask_secret(val)
            env_summary.append(f"{key}={val}")
    if env_summary:
        router_log("INFO", "claude_launch_env " + " ".join(env_summary))


def _subprocess_call_capturing_stderr(cmd: list[str], env: dict[str, str]) -> int:
    """Like subprocess.call but tees Claude Code's stderr into
    ~/.config/claude-any/claude-code-stderr.log so the user can collect
    the exact context around messages like
    `--dangerously-load-development-channels ignored (server:...)`.

    Enabled via CLAUDE_ANY_CAPTURE_CC_STDERR=1."""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    try:
        if CLAUDE_CODE_STDERR_LOG.exists() and CLAUDE_CODE_STDERR_LOG.stat().st_size > 2_000_000:
            CLAUDE_CODE_STDERR_LOG.replace(CLAUDE_CODE_STDERR_LOG.with_suffix(".log.1"))
    except Exception:
        pass
    try:
        log_handle = CLAUDE_CODE_STDERR_LOG.open("ab", buffering=0)
    except Exception as exc:
        router_log("WARN", f"claude_stderr_capture_open_failed error={type(exc).__name__}: {exc}")
        return subprocess.call(cmd, env=env)
    header = f"\n===== claude launch at {time.strftime('%Y-%m-%dT%H:%M:%S')} =====\n".encode("utf-8")
    try:
        log_handle.write(header)
    except Exception:
        pass
    try:
        proc = subprocess.Popen(cmd, env=env, stderr=subprocess.PIPE)
    except Exception as exc:
        router_log("WARN", f"claude_stderr_capture_spawn_failed error={type(exc).__name__}: {exc}")
        try:
            log_handle.close()
        except Exception:
            pass
        return subprocess.call(cmd, env=env)

    def _tee_stderr() -> None:
        try:
            if proc.stderr is None:
                return
            while True:
                chunk = proc.stderr.read(4096)
                if not chunk:
                    break
                try:
                    sys.stderr.buffer.write(chunk)
                    sys.stderr.buffer.flush()
                except Exception:
                    pass
                try:
                    log_handle.write(chunk)
                except Exception:
                    pass
        finally:
            try:
                log_handle.close()
            except Exception:
                pass

    tee_thread = threading.Thread(target=_tee_stderr, daemon=True, name="claude-stderr-tee")
    tee_thread.start()
    rc = proc.wait()
    tee_thread.join(timeout=2.0)
    router_log("INFO", f"claude_exit code={rc} stderr_log={CLAUDE_CODE_STDERR_LOG}")
    return rc


def cli_usage() -> str:
    return """Usage:
  claude-any                         Launch Claude Code through claude-any router

Control plane, runs before Claude Code and does not require LLM connectivity:
  claude-any version                 Print claude-any version
  claude-any language [en|ko|ja|zh] Set display language
  claude-any provider                Pick provider with arrow-key TUI
  claude-any provider list           List providers
  claude-any provider PROVIDER       Set provider
  claude-any base-url PROVIDER URL   Set provider base URL
  claude-any model MODEL_ID          Set current provider model
  claude-any advisor-model MODEL_ID  Set current provider advisor model (off disables)
  claude-any models [PROVIDER]       List models
  claude-any api-key PROVIDER        Store API key securely
  claude-any api-key PROVIDER clear  Clear stored API key(s)
  claude-any set-api-key PROVIDER KEY
  claude-any set-api-keys PROVIDER KEY1,KEY2
  claude-any web-search [on|off]     Auto-attach DuckDuckGo MCP for non-native providers
  claude-any web-fetch [on|off]      Auto-attach fetch MCP for web page content
  claude-any log-level [LEVEL]       Show or set router log level
  claude-any channels [cmd]          Configure external channel specs
  claude-any channel-delivery [stdin|native]
                                      Select PTY wake proxy or native claude/channel bridge
  claude-any ollama-native [on|off]  Use Ollama's official Claude Code env path
  claude-any ollama-options [provider] [key=value ...]
                                      Set Ollama num_ctx/options/keep_alive/think
  claude-any provider-options [provider] [key=value ...]
                                      Set vLLM/NIM/NVIDIA output/context/timeouts
  claude-any ollama-catalog          Download Ollama model/context catalog
  claude-any test [seconds] [mode]   Test compatibility; mode is auto, quick, smoke, or full
  claude-any stop                    Stop router/proxy

Headless setup flags, namespaced to avoid Claude CLI collisions:
  claude-any --ca-provider PROVIDER  Set provider, then launch
  claude-any --ca-env-file PATH      Load CLAUDE_ANY_* values from a .env file
  claude-any --ca-menu               Apply setup values, then open the menu
  claude-any --ca-language en|ko|ja|zh
  claude-any --ca-base-url URL       Set current provider base URL, then launch
  claude-any --ca-model MODEL_ID     Set provider model, then launch
  claude-any --ca-advisor-model MODEL_ID
  claude-any --ca-auto-llm-options [MODEL_ID]
                                      Apply recommended LLM options for MODEL_ID or the saved model
  claude-any --ca-api-key KEY        Set current provider API key, then launch
  claude-any --ca-api-key clear      Clear current provider API key(s), then launch
  claude-any --ca-api-key-env ENVVAR Set current provider API key from env, then launch
  claude-any --ca-api-keys KEY1,KEY2 Set current provider API keys with round-robin
  claude-any --ca-api-keys-env ENVVAR
                                      Set current provider API keys from env, then launch
  claude-any --ca-set-api-key PROVIDER KEY
  claude-any --ca-set-api-key-env PROVIDER ENVVAR
  claude-any --ca-set-api-keys PROVIDER KEY1,KEY2
  claude-any --ca-set-api-keys-env PROVIDER ENVVAR
  claude-any --ca-provider-option KEY=VALUE
                                      Set a provider option for the current provider
  claude-any --ca-set-provider-option PROVIDER KEY=VALUE
                                      Set a provider option for a specific provider
  claude-any --ca-ollama-num-ctx VALUE
  claude-any --ca-ollama-ctx-range MIN MAX
  claude-any --ca-ollama-option KEY=VALUE
  claude-any --ca-max-output-tokens VALUE
  claude-any --ca-context-window VALUE
  claude-any --ca-request-timeout-ms VALUE
  claude-any --ca-stream-idle-timeout-ms VALUE
  claude-any --ca-rate-limit-rpm VALUE
  claude-any --ca-rate-limit-status on|off
  claude-any --ca-stream on|off
  claude-any --ca-stream-word-chunking on|off
  claude-any --ca-log-level LEVEL    Set router log level: SILENT, ERROR, WARN, INFO, DEBUG, TRACE
  claude-any --ca-web-search         Force DuckDuckGo MCP for this launch
  claude-any --ca-no-web-search      Disable DuckDuckGo MCP for this launch
  claude-any --ca-web-fetch          Enable fetch MCP
  claude-any --ca-no-web-fetch       Disable fetch MCP
  claude-any --ca-channel SPEC       Add an official/approved Claude Code channel
  claude-any --ca-channel-delivery MODE
                                      Set channel delivery: stdin or native
  claude-any --ca-clear-channels     Clear saved channel specs
  claude-any --ca-no-self-update-check
                                      Skip Claude Any npm self-update check
  claude-any --ca-no-update-check    Skip Claude Code update check for this launch
  claude-any --ca-upgrade-and-exit   Update Claude Any and Claude Code without prompts, then exit
  claude-any --ca-no-launch          Apply setup flags/env values, then exit without launching Claude
  claude-any --ca-stop               Stop router/proxy
  claude-any --                      Pass all following args directly to Claude Code

Provider names: anthropic, ollama, ollama-cloud, deepseek, opencode, opencode-go, vllm, lm-studio, nvidia-hosted, self-hosted-nim, openrouter, fireworks
Any other arguments are passed through to claude. Use -- before Claude flags that
collide with claude-any setup flags."""


def pop_headless_env_file_args(argv: list[str]) -> list[str]:
    cleaned: list[str] = []
    i = 0
    while i < len(argv):
        arg = argv[i]
        if arg == "--ca-env-file" or arg.startswith("--ca-env-file="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing path for --ca-env-file")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            path = Path(value).expanduser()
            if not path.exists():
                raise SystemExit(f"--ca-env-file not found: {path}")
            load_dotenv_into_environ(path, override=True)
        else:
            cleaned.append(arg)
            i += 1
    return cleaned


def apply_headless_env_config() -> tuple[bool, bool | None, bool | None, bool | None, bool]:
    skip_menu = os.environ.get("CLAUDE_ANY_SKIP_MENU") == "1"
    force_menu = bool(env_bool(os.environ.get("CLAUDE_ANY_FORCE_MENU"), False))
    web_search_override = env_bool(os.environ.get("CLAUDE_ANY_WEB_SEARCH"))
    update_check_override = env_bool(os.environ.get("CLAUDE_ANY_UPDATE_CHECK"))
    self_update_check_override = env_bool(os.environ.get("CLAUDE_ANY_SELF_UPDATE_CHECK"))
    language = os.environ.get("CLAUDE_ANY_LANGUAGE", "").strip()
    if language:
        cmd_language(argparse.Namespace(value=language))
        skip_menu = True
    web_fetch = env_bool(os.environ.get("CLAUDE_ANY_WEB_FETCH"))
    if web_fetch is not None:
        cmd_web_fetch(argparse.Namespace(value="on" if web_fetch else "off"))
        skip_menu = True
    provider = os.environ.get("CLAUDE_ANY_PROVIDER", "").strip()
    if provider:
        cmd_provider(argparse.Namespace(name=provider))
        skip_menu = True
    api_key_env = os.environ.get("CLAUDE_ANY_API_KEY_ENV", "").strip()
    api_key = os.environ.get("CLAUDE_ANY_API_KEY", "").strip()
    api_keys_env = os.environ.get("CLAUDE_ANY_API_KEYS_ENV", "").strip()
    api_keys = os.environ.get("CLAUDE_ANY_API_KEYS", "").strip()
    current_provider, _ = get_current_provider(load_config())
    if api_keys_env:
        value = os.environ.get(api_keys_env, "")
        if not value:
            raise SystemExit(f"Environment variable {api_keys_env} is empty or not set")
        cmd_set_api_keys(argparse.Namespace(provider=current_provider, keys=[value]))
        skip_menu = True
    elif api_keys:
        cmd_set_api_keys(argparse.Namespace(provider=current_provider, keys=[api_keys]))
        skip_menu = True
    elif api_key_env:
        value = os.environ.get(api_key_env, "")
        if not value:
            raise SystemExit(f"Environment variable {api_key_env} is empty or not set")
        cmd_set_api_key(argparse.Namespace(provider=current_provider, key=value))
        skip_menu = True
    elif api_key:
        cmd_set_api_key(argparse.Namespace(provider=current_provider, key=api_key))
        skip_menu = True
    base_url = os.environ.get("CLAUDE_ANY_BASE_URL", "").strip()
    if base_url:
        current_provider, _ = get_current_provider(load_config())
        cmd_base_url(argparse.Namespace(provider=current_provider, url=base_url))
        skip_menu = True
    model = os.environ.get("CLAUDE_ANY_MODEL", "").strip()
    if model:
        cmd_model(argparse.Namespace(value=[model]))
        skip_menu = True
    advisor_model = os.environ.get("CLAUDE_ANY_ADVISOR_MODEL", "").strip()
    if advisor_model:
        set_advisor_model_config(advisor_model)
        skip_menu = True
    provider_option_keys = {
        "CLAUDE_ANY_MAX_OUTPUT_TOKENS": "max_output_tokens",
        "CLAUDE_ANY_CONTEXT_WINDOW": "context_window",
        "CLAUDE_ANY_REQUEST_TIMEOUT_MS": "request_timeout_ms",
        "CLAUDE_ANY_STREAM_IDLE_TIMEOUT_MS": "stream_idle_timeout_ms",
        "CLAUDE_ANY_RATE_LIMIT_RPM": "rate_limit_rpm",
        "CLAUDE_ANY_RATE_LIMIT_STATUS": "rate_limit_status",
        "CLAUDE_ANY_STREAM": "stream_enabled",
        "CLAUDE_ANY_STREAM_WORD_CHUNKING": "stream_word_chunking",
    }
    provider_values = [
        f"{option_key}={os.environ[env_key].strip()}"
        for env_key, option_key in provider_option_keys.items()
        if os.environ.get(env_key, "").strip()
    ]
    if provider_values:
        cmd_provider_options(argparse.Namespace(values=provider_values))
        skip_menu = True
    ollama_values: list[str] = []
    if os.environ.get("CLAUDE_ANY_OLLAMA_NUM_CTX", "").strip():
        ollama_values.append(f"num_ctx={os.environ['CLAUDE_ANY_OLLAMA_NUM_CTX'].strip()}")
    for item in os.environ.get("CLAUDE_ANY_OLLAMA_OPTIONS", "").replace(",", " ").split():
        if item.strip():
            ollama_values.append(item.strip())
    if ollama_values:
        cmd_ollama_options(argparse.Namespace(values=ollama_values))
        skip_menu = True
    channel_values = [
        item.strip()
        for item in re.split(r"[\s,]+", os.environ.get("CLAUDE_ANY_CHANNELS", "").strip())
        if item.strip()
    ]
    for channel_value in channel_values:
        add_channel_spec(channel_value)
        skip_menu = True
    dev_channel_values = [
        item.strip()
        for item in re.split(r"[\s,]+", os.environ.get("CLAUDE_ANY_DEV_CHANNELS", "").strip())
        if item.strip()
    ]
    for channel_value in dev_channel_values:
        add_channel_spec(channel_value)
        skip_menu = True
    channel_delivery = os.environ.get("CLAUDE_ANY_CHANNEL_DELIVERY", "").strip()
    if channel_delivery:
        set_channel_delivery_config(channel_delivery)
        skip_menu = True
    return skip_menu, web_search_override, update_check_override, self_update_check_override, force_menu


def run_cli(argv: list[str]) -> int:
    if argv and argv[0] == "mcp-proxy":
        return cmd_mcp_proxy(argv[1:])
    if argv and argv[0] in ("help", "--help", "-h"):
        print(cli_usage())
        return 0
    argv = pop_headless_env_file_args(argv)
    if any(arg in ("--ca-upgrade-and-exit", "--ca-quiet-upgrade", "--ca-upgrade-exit") for arg in argv):
        return run_quiet_upgrade_and_exit()
    if argv:
        head, rest = argv[0], argv[1:]
        if head in ("version", "--version", "-v"):
            print(f"claude-any {VERSION}")
            return 0
        if head in ("language", "lang"):
            cmd_language(argparse.Namespace(value=rest[0] if rest else None))
            return 0
        if head == "provider":
            if not rest:
                rc = run_external_menu("claude-any-provider")
                return portable_provider_menu() if rc is None else rc
            if rest[0] in ("list", "ls"):
                cmd_provider(argparse.Namespace(name=None))
                return 0
            cmd_provider(argparse.Namespace(name=rest[0]))
            return 0
        if head == "model":
            if not rest:
                raise SystemExit("Missing model id")
            cmd_model(argparse.Namespace(value=rest))
            return 0
        if head in ("advisor-model", "advisormodel", "advisor"):
            cmd_advisor_model(argparse.Namespace(value=rest))
            return 0
        if head == "base-url":
            if len(rest) < 2:
                raise SystemExit("Usage: claude-any base-url PROVIDER URL")
            cmd_base_url(argparse.Namespace(provider=rest[0], url=rest[1]))
            return 0
        if head == "models":
            cmd_models(argparse.Namespace(provider=rest[0] if rest else None))
            return 0
        if head in ("api-key", "apikey"):
            if not rest:
                raise SystemExit("Missing provider")
            cmd_api_key(argparse.Namespace(provider=rest[0], action=rest[1] if len(rest) > 1 else None))
            return 0
        if head in ("set-api-key", "set-apikey"):
            if len(rest) < 2:
                raise SystemExit("Usage: claude-any set-api-key PROVIDER KEY")
            cmd_set_api_key(argparse.Namespace(provider=rest[0], key=rest[1]))
            return 0
        if head in ("set-api-keys", "set-apikeys"):
            if len(rest) < 2:
                raise SystemExit("Usage: claude-any set-api-keys PROVIDER KEY1,KEY2")
            cmd_set_api_keys(argparse.Namespace(provider=rest[0], keys=rest[1:]))
            return 0
        if head in ("web-search", "websearch"):
            cmd_web_search(argparse.Namespace(value=rest[0] if rest else None))
            return 0
        if head in ("web-fetch", "webfetch"):
            cmd_web_fetch(argparse.Namespace(value=rest[0] if rest else None))
            return 0
        if head in ("log-level", "loglevel", "logging"):
            cmd_log_level(argparse.Namespace(value=rest[0] if rest else None))
            return 0
        if head in ("channels", "channel"):
            cmd_channels(argparse.Namespace(values=rest))
            return 0
        if head in ("channel-delivery", "channel_delivery"):
            if rest:
                for line in set_channel_delivery_config(rest[0]):
                    print(line)
            else:
                print(f"channel_delivery: {channel_delivery_mode()}")
            return 0
        if head in ("ollama-native", "ollama-compat"):
            cmd_ollama_native(argparse.Namespace(value=rest[0] if rest else None))
            return 0
        if head in ("ollama-options", "ollama-option", "ollama-opts"):
            cmd_ollama_options(argparse.Namespace(values=rest))
            return 0
        if head in ("provider-options", "provider-option", "provider-opts", "vllm-options", "nim-options"):
            cmd_provider_options(argparse.Namespace(values=rest))
            return 0
        if head in ("ollama-catalog", "ollama-catalog-refresh"):
            no_contexts = "--no-contexts" in rest
            timeout = 10.0
            for item in rest:
                if item.startswith("--timeout="):
                    try:
                        timeout = float(item.split("=", 1)[1])
                    except ValueError:
                        raise SystemExit("Usage: claude-any ollama-catalog [--no-contexts] [--timeout=SECONDS]")
            cmd_ollama_catalog(argparse.Namespace(no_contexts=no_contexts, timeout=timeout))
            return 0
        if head in ("test", "compat", "compatibility"):
            timeout = 60.0
            mode = "auto"
            if rest and rest[0] in ("auto", "quick", "smoke", "full"):
                mode = rest[0]
                rest = rest[1:]
            if rest:
                try:
                    timeout = float(rest[0])
                except ValueError:
                    raise SystemExit("Usage: claude-any test [timeout_seconds] [auto|quick|smoke|full]")
                if len(rest) > 1:
                    mode = rest[1]
            if mode not in ("auto", "quick", "smoke", "full"):
                raise SystemExit("Usage: claude-any test [timeout_seconds] [auto|quick|smoke|full]")
            cmd_test(argparse.Namespace(timeout=timeout, mode=mode))
            return 0
        if head == "status":
            cmd_status(argparse.Namespace())
            return 0
        if head == "stop":
            cmd_stop(argparse.Namespace())
            ncp = find_executable("ncp")
            if ncp:
                subprocess.run([ncp, "kill"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return 0

    passthrough: list[str] = []
    configure_only = False
    auto_llm_options = False
    auto_llm_model: str | None = None
    skip_menu, web_search_override, update_check_override, self_update_check_override, force_menu = apply_headless_env_config()
    update_check = True
    if update_check_override is not None:
        update_check = update_check_override
    self_update_check = True
    if self_update_check_override is not None:
        self_update_check = self_update_check_override
    i = 0
    while i < len(argv):
        arg = argv[i]
        if arg in ("--ca-menu", "--ca-interactive"):
            force_menu = True
            i += 1
        elif arg in ("--ca-no-launch", "--ca-configure-only", "--ca-setup-only"):
            configure_only = True
            skip_menu = True
            i += 1
        elif arg == "--ca-language" or arg.startswith("--ca-language="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing language for --ca-language")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_language(argparse.Namespace(value=value))
            skip_menu = True
        elif arg == "--ca-provider" or arg.startswith("--ca-provider="):
            provider_value = arg.split("=", 1)[1] if "=" in arg else None
            if provider_value:
                cmd_provider(argparse.Namespace(name=provider_value))
                skip_menu = True
                i += 1
            elif i + 1 < len(argv) and not argv[i + 1].startswith("--"):
                cmd_provider(argparse.Namespace(name=argv[i + 1]))
                skip_menu = True
                i += 2
            else:
                rc = run_external_menu("claude-any-provider")
                if rc is None:
                    rc = portable_provider_menu()
                if rc != 0:
                    return rc
                skip_menu = True
                i += 1
        elif arg == "--ca-base-url" or arg.startswith("--ca-base-url="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing URL for --ca-base-url")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            provider, _ = get_current_provider(load_config())
            cmd_base_url(argparse.Namespace(provider=provider, url=value))
            skip_menu = True
        elif arg == "--ca-model" or arg.startswith("--ca-model="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing model id for --ca-model")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_model(argparse.Namespace(value=[value]))
            skip_menu = True
        elif arg == "--ca-advisor-model" or arg.startswith("--ca-advisor-model="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing model id for --ca-advisor-model")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in set_advisor_model_config(value):
                print(line)
            skip_menu = True
        elif arg in ("--ca-auto-llm-options", "--ca-auto-llm", "--ca-recommended-llm") or arg.startswith(
            ("--ca-auto-llm-options=", "--ca-auto-llm=", "--ca-recommended-llm=")
        ):
            auto_llm_options = True
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None and i + 1 < len(argv) and not argv[i + 1].startswith("--"):
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            if value:
                auto_llm_model = value
            skip_menu = True
        elif arg == "--ca-models":
            cmd_models(argparse.Namespace(provider=None))
            return 0
        elif arg == "--ca-api-key" or arg.startswith("--ca-api-key="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing key for --ca-api-key")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            provider, _ = get_current_provider(load_config())
            cmd_set_api_key(argparse.Namespace(provider=provider, key=value))
            skip_menu = True
        elif arg == "--ca-api-key-env" or arg.startswith("--ca-api-key-env="):
            env_name = arg.split("=", 1)[1] if "=" in arg else None
            if env_name is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing env var name for --ca-api-key-env")
                env_name = argv[i + 1]
                i += 2
            else:
                i += 1
            value = os.environ.get(env_name, "")
            if not value:
                raise SystemExit(f"Environment variable {env_name} is empty or not set")
            provider, _ = get_current_provider(load_config())
            cmd_set_api_key(argparse.Namespace(provider=provider, key=value))
            skip_menu = True
        elif arg == "--ca-api-keys" or arg.startswith("--ca-api-keys="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing key list for --ca-api-keys")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            provider, _ = get_current_provider(load_config())
            cmd_set_api_keys(argparse.Namespace(provider=provider, keys=[value]))
            skip_menu = True
        elif arg == "--ca-api-keys-env" or arg.startswith("--ca-api-keys-env="):
            env_name = arg.split("=", 1)[1] if "=" in arg else None
            if env_name is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing env var name for --ca-api-keys-env")
                env_name = argv[i + 1]
                i += 2
            else:
                i += 1
            value = os.environ.get(env_name, "")
            if not value:
                raise SystemExit(f"Environment variable {env_name} is empty or not set")
            provider, _ = get_current_provider(load_config())
            cmd_set_api_keys(argparse.Namespace(provider=provider, keys=[value]))
            skip_menu = True
        elif arg == "--ca-set-api-key":
            if i + 2 >= len(argv):
                raise SystemExit("Usage: --ca-set-api-key PROVIDER KEY")
            cmd_set_api_key(argparse.Namespace(provider=argv[i + 1], key=argv[i + 2]))
            skip_menu = True
            i += 3
        elif arg == "--ca-set-api-key-env":
            if i + 2 >= len(argv):
                raise SystemExit("Usage: --ca-set-api-key-env PROVIDER ENVVAR")
            value = os.environ.get(argv[i + 2], "")
            if not value:
                raise SystemExit(f"Environment variable {argv[i + 2]} is empty or not set")
            cmd_set_api_key(argparse.Namespace(provider=argv[i + 1], key=value))
            skip_menu = True
            i += 3
        elif arg == "--ca-set-api-keys":
            if i + 2 >= len(argv):
                raise SystemExit("Usage: --ca-set-api-keys PROVIDER KEY1,KEY2")
            cmd_set_api_keys(argparse.Namespace(provider=argv[i + 1], keys=[argv[i + 2]]))
            skip_menu = True
            i += 3
        elif arg == "--ca-set-api-keys-env":
            if i + 2 >= len(argv):
                raise SystemExit("Usage: --ca-set-api-keys-env PROVIDER ENVVAR")
            value = os.environ.get(argv[i + 2], "")
            if not value:
                raise SystemExit(f"Environment variable {argv[i + 2]} is empty or not set")
            cmd_set_api_keys(argparse.Namespace(provider=argv[i + 1], keys=[value]))
            skip_menu = True
            i += 3
        elif arg in ("--ca-provider-option", "--ca-provider-options") or arg.startswith(
            ("--ca-provider-option=", "--ca-provider-options=")
        ):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing KEY=VALUE for --ca-provider-option")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[value]))
            skip_menu = True
        elif arg == "--ca-set-provider-option":
            if i + 2 >= len(argv):
                raise SystemExit("Usage: --ca-set-provider-option PROVIDER KEY=VALUE")
            cmd_provider_options(argparse.Namespace(values=[argv[i + 1], argv[i + 2]]))
            skip_menu = True
            i += 3
        elif arg == "--ca-ollama-num-ctx" or arg.startswith("--ca-ollama-num-ctx="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-ollama-num-ctx")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_ollama_options(argparse.Namespace(values=[f"num_ctx={value}"]))
            skip_menu = True
        elif arg == "--ca-ollama-ctx-range" or arg.startswith("--ca-ollama-ctx-range="):
            if "=" in arg:
                raw = arg.split("=", 1)[1]
                sep = ":" if ":" in raw else "-"
                parts = [p.strip() for p in raw.split(sep, 1)]
                if len(parts) != 2 or not parts[0] or not parts[1]:
                    raise SystemExit("Usage: --ca-ollama-ctx-range MIN MAX")
                min_value, max_value = parts
                i += 1
            else:
                if i + 2 >= len(argv):
                    raise SystemExit("Usage: --ca-ollama-ctx-range MIN MAX")
                min_value, max_value = argv[i + 1], argv[i + 2]
                i += 3
            cmd_ollama_options(
                argparse.Namespace(values=[f"min={min_value}", f"max={max_value}", "num_ctx=auto"])
            )
            skip_menu = True
        elif arg == "--ca-ollama-option" or arg.startswith("--ca-ollama-option="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing KEY=VALUE for --ca-ollama-option")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_ollama_options(argparse.Namespace(values=[value]))
            skip_menu = True
        elif arg == "--ca-max-output-tokens" or arg.startswith("--ca-max-output-tokens="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-max-output-tokens")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"max_output_tokens={value}"]))
            skip_menu = True
        elif arg == "--ca-context-window" or arg.startswith("--ca-context-window="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-context-window")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"context_window={value}"]))
            skip_menu = True
        elif arg == "--ca-request-timeout-ms" or arg.startswith("--ca-request-timeout-ms="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-request-timeout-ms")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"request_timeout_ms={value}"]))
            skip_menu = True
        elif arg == "--ca-stream-idle-timeout-ms" or arg.startswith("--ca-stream-idle-timeout-ms="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-stream-idle-timeout-ms")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"stream_idle_timeout_ms={value}"]))
            skip_menu = True
        elif arg == "--ca-rate-limit-rpm" or arg.startswith("--ca-rate-limit-rpm="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing value for --ca-rate-limit-rpm")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"rate_limit_rpm={value}"]))
            skip_menu = True
        elif arg == "--ca-rate-limit-status" or arg.startswith("--ca-rate-limit-status="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing on/off for --ca-rate-limit-status")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"rate_limit_status={value}"]))
            skip_menu = True
        elif arg == "--ca-stream" or arg.startswith("--ca-stream="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing on/off for --ca-stream")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"stream_enabled={value}"]))
            skip_menu = True
        elif arg == "--ca-stream-word-chunking" or arg.startswith("--ca-stream-word-chunking="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing on/off for --ca-stream-word-chunking")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            cmd_provider_options(argparse.Namespace(values=[f"stream_word_chunking={value}"]))
            skip_menu = True
        elif arg == "--ca-log-level" or arg.startswith("--ca-log-level="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing level for --ca-log-level")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in set_log_level_config(value):
                print(line)
            skip_menu = True
        elif arg == "--ca-web-search":
            web_search_override = True
            skip_menu = True
            i += 1
        elif arg == "--ca-no-web-search":
            web_search_override = False
            skip_menu = True
            i += 1
        elif arg == "--ca-web-fetch":
            cmd_web_fetch(argparse.Namespace(value="on"))
            skip_menu = True
            i += 1
        elif arg == "--ca-no-web-fetch":
            cmd_web_fetch(argparse.Namespace(value="off"))
            skip_menu = True
            i += 1
        elif arg == "--ca-channel" or arg.startswith("--ca-channel="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing channel spec for --ca-channel")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in add_channel_spec(value):
                print(line)
            skip_menu = True
        elif arg == "--ca-channel-delivery" or arg.startswith("--ca-channel-delivery="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing mode for --ca-channel-delivery")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in set_channel_delivery_config(value):
                print(line)
            skip_menu = True
        elif arg == "--ca-dev-channel" or arg.startswith("--ca-dev-channel="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing channel spec for --ca-dev-channel")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in add_channel_spec(value):
                print(line)
            skip_menu = True
        elif arg == "--ca-development-channels" or arg.startswith("--ca-development-channels="):
            value = arg.split("=", 1)[1] if "=" in arg else None
            if value is None:
                if i + 1 >= len(argv):
                    raise SystemExit("Missing on/off for --ca-development-channels")
                value = argv[i + 1]
                i += 2
            else:
                i += 1
            for line in set_channel_development_enabled(True):
                print(line)
            skip_menu = True
        elif arg == "--ca-clear-channels":
            for line in clear_channel_specs():
                print(line)
            skip_menu = True
            i += 1
        elif arg == "--ca-no-update-check":
            update_check = False
            skip_menu = True
            i += 1
        elif arg == "--ca-no-self-update-check":
            self_update_check = False
            skip_menu = True
            i += 1
        elif arg == "--ca-status":
            cmd_status(argparse.Namespace())
            return 0
        elif arg == "--ca-stop":
            cmd_stop(argparse.Namespace())
            return 0
        elif arg == "--":
            passthrough.extend(argv[i + 1 :])
            break
        else:
            passthrough.append(arg)
            i += 1
    if auto_llm_options:
        for line in apply_auto_llm_options_config(auto_llm_model):
            print(line)
    if configure_only:
        return 0
    return launch_claude(
        passthrough,
        skip_menu=skip_menu,
        force_menu=force_menu,
        web_search_override=web_search_override,
        update_check=update_check,
        self_update_check=self_update_check,
    )


def cmd_cli(args: argparse.Namespace) -> None:
    raise SystemExit(run_cli(args.argv))


def cmd_launch(args: argparse.Namespace) -> None:
    raise SystemExit(launch_claude(args.argv))


def cmd_version(args: argparse.Namespace) -> None:
    print(f"claude-any {VERSION}")


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(prog="claude-anyctl")
    sub = p.add_subparsers(dest="cmd", required=True)
    cli = sub.add_parser("cli", add_help=False)
    cli.add_argument("argv", nargs=argparse.REMAINDER)
    cli.set_defaults(func=cmd_cli)
    launch = sub.add_parser("launch", add_help=False)
    launch.add_argument("argv", nargs=argparse.REMAINDER)
    launch.set_defaults(func=cmd_launch)
    sub.add_parser("serve").set_defaults(func=serve)
    sub.add_parser("version").set_defaults(func=cmd_version)
    sub.add_parser("status").set_defaults(func=cmd_status)
    sub.add_parser("env").set_defaults(func=cmd_env)
    sub.add_parser("stop").set_defaults(func=cmd_stop)
    lang = sub.add_parser("language")
    lang.add_argument("value", nargs="?")
    lang.set_defaults(func=cmd_language)
    ws = sub.add_parser("web-search")
    ws.add_argument("value", nargs="?")
    ws.set_defaults(func=cmd_web_search)
    wf = sub.add_parser("web-fetch")
    wf.add_argument("value", nargs="?")
    wf.set_defaults(func=cmd_web_fetch)
    ll = sub.add_parser("log-level")
    ll.add_argument("value", nargs="?")
    ll.set_defaults(func=cmd_log_level)
    ch = sub.add_parser("channels")
    ch.add_argument("values", nargs="*")
    ch.set_defaults(func=cmd_channels)
    cd = sub.add_parser("channel-delivery")
    cd.add_argument("value", nargs="?")
    cd.set_defaults(func=cmd_channel_delivery)
    on = sub.add_parser("ollama-native")
    on.add_argument("value", nargs="?")
    on.set_defaults(func=cmd_ollama_native)
    oo = sub.add_parser("ollama-options")
    oo.add_argument("values", nargs="*")
    oo.set_defaults(func=cmd_ollama_options)
    po = sub.add_parser("provider-options")
    po.add_argument("values", nargs="*")
    po.set_defaults(func=cmd_provider_options)
    oc = sub.add_parser("ollama-catalog")
    oc.add_argument("--no-contexts", action="store_true")
    oc.add_argument("--timeout", type=float, default=10.0)
    oc.set_defaults(func=cmd_ollama_catalog)
    test = sub.add_parser("test")
    test.add_argument("timeout", nargs="?", type=float, default=120.0)
    test.add_argument("mode", nargs="?", choices=("auto", "quick", "smoke", "full"), default="auto")
    test.set_defaults(func=cmd_test)
    pp = sub.add_parser("provider")
    pp.add_argument("name", nargs="?")
    pp.set_defaults(func=cmd_provider)
    ak = sub.add_parser("api-key")
    ak.add_argument("provider", nargs="?")
    ak.add_argument("action", nargs="?")
    ak.set_defaults(func=cmd_api_key)
    sak = sub.add_parser("set-api-key")
    sak.add_argument("provider")
    sak.add_argument("key")
    sak.set_defaults(func=cmd_set_api_key)
    saks = sub.add_parser("set-api-keys")
    saks.add_argument("provider")
    saks.add_argument("keys", nargs="+")
    saks.set_defaults(func=cmd_set_api_keys)
    bu = sub.add_parser("base-url")
    bu.add_argument("provider")
    bu.add_argument("url")
    bu.set_defaults(func=cmd_base_url)
    mo = sub.add_parser("model")
    mo.add_argument("value", nargs="*")
    mo.set_defaults(func=cmd_model)
    am = sub.add_parser("advisor-model")
    am.add_argument("value", nargs="*")
    am.set_defaults(func=cmd_advisor_model)
    ml = sub.add_parser("models")
    ml.add_argument("provider", nargs="?")
    ml.set_defaults(func=cmd_models)
    return p


def main() -> None:
    if len(sys.argv) >= 2 and sys.argv[1] == "mcp-proxy":
        raise SystemExit(cmd_mcp_proxy(sys.argv[2:]))
    if len(sys.argv) >= 2 and sys.argv[1] == "cli":
        raise SystemExit(run_cli(sys.argv[2:]))
    if len(sys.argv) >= 2 and sys.argv[1] == "launch":
        raise SystemExit(launch_claude(sys.argv[2:]))
    parser = build_parser()
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
