265 lines
9.9 KiB
Python
265 lines
9.9 KiB
Python
import os
|
|
from typing import Any, Dict, List, Optional
|
|
from datetime import datetime, timezone
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from sqlmodel import Session
|
|
|
|
from core.settings import DEFAULT_BOT_SYSTEM_TIMEZONE
|
|
from models.bot import BotInstance
|
|
from services.bot_storage_service import (
|
|
_bot_data_root,
|
|
_clear_bot_dashboard_direct_session,
|
|
_clear_bot_sessions,
|
|
_migrate_bot_resources_store,
|
|
_normalize_env_params,
|
|
_normalize_resource_limits,
|
|
_read_bot_config,
|
|
_read_bot_resources,
|
|
_read_cron_store,
|
|
_read_env_store,
|
|
_safe_float,
|
|
_safe_int,
|
|
_workspace_root,
|
|
_write_bot_config,
|
|
_write_bot_resources,
|
|
_write_cron_store,
|
|
_write_env_store,
|
|
)
|
|
from services.bot_channel_service import (
|
|
_channel_api_to_cfg,
|
|
_get_bot_channels_from_config,
|
|
_normalize_channel_extra,
|
|
_normalize_initial_channels,
|
|
_read_global_delivery_flags,
|
|
_sync_workspace_channels_impl,
|
|
)
|
|
from services.bot_mcp_service import (
|
|
_merge_mcp_servers_preserving_extras,
|
|
_normalize_mcp_servers,
|
|
_sanitize_mcp_servers_in_config_data,
|
|
)
|
|
from services.template_service import get_agent_md_templates
|
|
|
|
__all__ = [
|
|
"_bot_data_root",
|
|
"_channel_api_to_cfg",
|
|
"_clear_bot_dashboard_direct_session",
|
|
"_clear_bot_sessions",
|
|
"_get_bot_channels_from_config",
|
|
"_migrate_bot_resources_store",
|
|
"_normalize_channel_extra",
|
|
"_normalize_env_params",
|
|
"_normalize_initial_channels",
|
|
"_normalize_mcp_servers",
|
|
"_normalize_resource_limits",
|
|
"_normalize_system_timezone",
|
|
"_provider_defaults",
|
|
"_read_bot_config",
|
|
"_read_bot_resources",
|
|
"_read_bot_runtime_snapshot",
|
|
"_read_cron_store",
|
|
"_read_env_store",
|
|
"_read_global_delivery_flags",
|
|
"_resolve_bot_env_params",
|
|
"_safe_float",
|
|
"_safe_int",
|
|
"_sanitize_mcp_servers_in_config_data",
|
|
"_serialize_bot",
|
|
"_serialize_bot_list_item",
|
|
"_sync_workspace_channels",
|
|
"_workspace_root",
|
|
"_write_bot_config",
|
|
"_write_bot_resources",
|
|
"_write_cron_store",
|
|
"_write_env_store",
|
|
"_merge_mcp_servers_preserving_extras",
|
|
]
|
|
def _get_default_system_timezone() -> str:
|
|
value = str(DEFAULT_BOT_SYSTEM_TIMEZONE or "").strip() or "Asia/Shanghai"
|
|
try:
|
|
ZoneInfo(value)
|
|
return value
|
|
except Exception:
|
|
return "Asia/Shanghai"
|
|
|
|
|
|
def _normalize_system_timezone(raw: Any) -> str:
|
|
value = str(raw or "").strip()
|
|
if not value:
|
|
return _get_default_system_timezone()
|
|
try:
|
|
ZoneInfo(value)
|
|
except Exception as exc:
|
|
raise ValueError("Invalid system timezone. Use an IANA timezone such as Asia/Shanghai.") from exc
|
|
return value
|
|
|
|
|
|
def _resolve_bot_env_params(bot_id: str, raw: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
env_params = _normalize_env_params(raw if isinstance(raw, dict) else _read_env_store(bot_id))
|
|
try:
|
|
env_params["TZ"] = _normalize_system_timezone(env_params.get("TZ"))
|
|
except ValueError:
|
|
env_params["TZ"] = _get_default_system_timezone()
|
|
return env_params
|
|
|
|
|
|
def _provider_defaults(provider: str) -> tuple[str, str]:
|
|
normalized = provider.lower().strip()
|
|
if normalized in {"openai"}:
|
|
return "openai", "https://api.openai.com/v1"
|
|
if normalized in {"openrouter"}:
|
|
return "openrouter", "https://openrouter.ai/api/v1"
|
|
if normalized in {"dashscope", "aliyun", "qwen", "aliyun-qwen"}:
|
|
return "dashscope", "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
if normalized in {"deepseek"}:
|
|
return "deepseek", "https://api.deepseek.com/v1"
|
|
if normalized in {"xunfei", "iflytek", "xfyun"}:
|
|
return "openai", "https://spark-api-open.xf-yun.com/v1"
|
|
if normalized in {"vllm"}:
|
|
return "openai", ""
|
|
if normalized in {"kimi", "moonshot"}:
|
|
return "kimi", "https://api.moonshot.cn/v1"
|
|
if normalized in {"minimax"}:
|
|
return "minimax", "https://api.minimax.chat/v1"
|
|
return normalized, ""
|
|
def _read_workspace_md(bot_id: str, filename: str, default_value: str) -> str:
|
|
path = os.path.join(_workspace_root(bot_id), filename)
|
|
if not os.path.isfile(path):
|
|
return default_value
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return f.read().strip()
|
|
except Exception:
|
|
return default_value
|
|
|
|
def _read_bot_runtime_snapshot(bot: BotInstance) -> Dict[str, Any]:
|
|
config_data = _read_bot_config(bot.id)
|
|
env_params = _resolve_bot_env_params(bot.id)
|
|
template_defaults = get_agent_md_templates()
|
|
|
|
provider_name = ""
|
|
provider_cfg: Dict[str, Any] = {}
|
|
providers_cfg = config_data.get("providers")
|
|
if isinstance(providers_cfg, dict):
|
|
for p_name, p_cfg in providers_cfg.items():
|
|
provider_name = str(p_name or "").strip()
|
|
if isinstance(p_cfg, dict):
|
|
provider_cfg = p_cfg
|
|
break
|
|
|
|
agents_defaults: Dict[str, Any] = {}
|
|
agents_cfg = config_data.get("agents")
|
|
if isinstance(agents_cfg, dict):
|
|
defaults = agents_cfg.get("defaults")
|
|
if isinstance(defaults, dict):
|
|
agents_defaults = defaults
|
|
|
|
channels_cfg = config_data.get("channels")
|
|
send_progress, send_tool_hints = _read_global_delivery_flags(channels_cfg)
|
|
|
|
llm_provider = provider_name or ""
|
|
llm_model = str(agents_defaults.get("model") or "")
|
|
api_key = str(provider_cfg.get("apiKey") or "").strip()
|
|
api_base = str(provider_cfg.get("apiBase") or "").strip()
|
|
api_base_lower = api_base.lower()
|
|
provider_alias = str(provider_cfg.get("dashboardProviderAlias") or "").strip().lower()
|
|
if llm_provider == "openai" and provider_alias in {"xunfei", "iflytek", "xfyun", "vllm"}:
|
|
llm_provider = "xunfei" if provider_alias in {"iflytek", "xfyun"} else provider_alias
|
|
elif llm_provider == "openai" and ("spark-api-open.xf-yun.com" in api_base_lower or "xf-yun.com" in api_base_lower):
|
|
llm_provider = "xunfei"
|
|
|
|
soul_md = _read_workspace_md(bot.id, "SOUL.md", template_defaults.get("soul_md", ""))
|
|
resources = _read_bot_resources(bot.id, config_data=config_data)
|
|
return {
|
|
"llm_provider": llm_provider,
|
|
"llm_model": llm_model,
|
|
"api_key": api_key,
|
|
"api_base": api_base,
|
|
"temperature": _safe_float(agents_defaults.get("temperature"), 0.2),
|
|
"top_p": _safe_float(agents_defaults.get("topP"), 1.0),
|
|
"max_tokens": _safe_int(agents_defaults.get("maxTokens"), 8192),
|
|
"cpu_cores": resources["cpu_cores"],
|
|
"memory_mb": resources["memory_mb"],
|
|
"storage_gb": resources["storage_gb"],
|
|
"system_timezone": env_params.get("TZ") or _get_default_system_timezone(),
|
|
"send_progress": send_progress,
|
|
"send_tool_hints": send_tool_hints,
|
|
"soul_md": soul_md,
|
|
"agents_md": _read_workspace_md(bot.id, "AGENTS.md", template_defaults.get("agents_md", "")),
|
|
"user_md": _read_workspace_md(bot.id, "USER.md", template_defaults.get("user_md", "")),
|
|
"tools_md": _read_workspace_md(bot.id, "TOOLS.md", template_defaults.get("tools_md", "")),
|
|
"identity_md": _read_workspace_md(bot.id, "IDENTITY.md", template_defaults.get("identity_md", "")),
|
|
"system_prompt": soul_md,
|
|
}
|
|
|
|
def _serialize_bot(bot: BotInstance) -> Dict[str, Any]:
|
|
runtime = _read_bot_runtime_snapshot(bot)
|
|
return {
|
|
"id": bot.id,
|
|
"name": bot.name,
|
|
"enabled": bool(getattr(bot, "enabled", True)),
|
|
"access_password": bot.access_password or "",
|
|
"has_access_password": bool(str(bot.access_password or "").strip()),
|
|
"avatar_model": "base",
|
|
"avatar_skin": "blue_suit",
|
|
"image_tag": bot.image_tag,
|
|
"llm_provider": runtime.get("llm_provider") or "",
|
|
"llm_model": runtime.get("llm_model") or "",
|
|
"system_prompt": runtime.get("system_prompt") or "",
|
|
"api_base": runtime.get("api_base") or "",
|
|
"temperature": _safe_float(runtime.get("temperature"), 0.2),
|
|
"top_p": _safe_float(runtime.get("top_p"), 1.0),
|
|
"max_tokens": _safe_int(runtime.get("max_tokens"), 8192),
|
|
"cpu_cores": _safe_float(runtime.get("cpu_cores"), 1.0),
|
|
"memory_mb": _safe_int(runtime.get("memory_mb"), 1024),
|
|
"storage_gb": _safe_int(runtime.get("storage_gb"), 10),
|
|
"system_timezone": str(runtime.get("system_timezone") or _get_default_system_timezone()),
|
|
"send_progress": bool(runtime.get("send_progress")),
|
|
"send_tool_hints": bool(runtime.get("send_tool_hints")),
|
|
"soul_md": runtime.get("soul_md") or "",
|
|
"agents_md": runtime.get("agents_md") or "",
|
|
"user_md": runtime.get("user_md") or "",
|
|
"tools_md": runtime.get("tools_md") or "",
|
|
"identity_md": runtime.get("identity_md") or "",
|
|
"workspace_dir": bot.workspace_dir,
|
|
"docker_status": bot.docker_status,
|
|
"current_state": bot.current_state,
|
|
"last_action": bot.last_action,
|
|
"created_at": bot.created_at,
|
|
"updated_at": bot.updated_at,
|
|
}
|
|
|
|
def _serialize_bot_list_item(bot: BotInstance) -> Dict[str, Any]:
|
|
return {
|
|
"id": bot.id,
|
|
"name": bot.name,
|
|
"enabled": bool(getattr(bot, "enabled", True)),
|
|
"has_access_password": bool(str(bot.access_password or "").strip()),
|
|
"image_tag": bot.image_tag,
|
|
"docker_status": bot.docker_status,
|
|
"current_state": bot.current_state,
|
|
"last_action": bot.last_action,
|
|
"updated_at": bot.updated_at,
|
|
}
|
|
|
|
def _sync_workspace_channels(
|
|
session: Session,
|
|
bot_id: str,
|
|
channels_override: Optional[List[Dict[str, Any]]] = None,
|
|
global_delivery_override: Optional[Dict[str, Any]] = None,
|
|
runtime_overrides: Optional[Dict[str, Any]] = None,
|
|
) -> None:
|
|
bot = session.get(BotInstance, bot_id)
|
|
if not bot:
|
|
return
|
|
snapshot = _read_bot_runtime_snapshot(bot)
|
|
_sync_workspace_channels_impl(
|
|
session,
|
|
bot_id,
|
|
snapshot,
|
|
channels_override=channels_override,
|
|
global_delivery_override=global_delivery_override,
|
|
runtime_overrides=runtime_overrides,
|
|
)
|