from __future__ import annotations import os from functools import lru_cache from pathlib import Path from typing import Any import yaml DEFAULT_LLM_MODEL = "gpt-5.5" DEFAULT_LLM_MODEL_PROVIDER = "openai" DEFAULT_LLM_BASE_URL = "https://yunwu.ai/v1" DEFAULT_IMAGE_MODEL = "gemini-3.1-flash-image-preview" DEFAULT_IMAGE_BASE_URL = "https://yunwu.ai" DEFAULT_VIDEO_MODEL = "veo3.1-fast" DEFAULT_VIDEO_BASE_URL = "https://openrouter.ai/api/v1" DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small" DEFAULT_EMBEDDING_MODEL_PROVIDER = "openai" DEFAULT_RERANKER_MODEL = "BAAI/bge-reranker-v2-m3" @lru_cache(maxsize=4) def load_agent_config(workspace_root: str | Path = ".") -> dict[str, Any]: path = Path(workspace_root).resolve() / "configs" / "agent.local.yaml" if not path.exists(): return {} try: payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {} except yaml.YAMLError as exc: raise RuntimeError(f"Invalid configs/agent.local.yaml: {exc}") from exc if not isinstance(payload, dict): raise RuntimeError("configs/agent.local.yaml must be a YAML mapping") return payload def config_value(section: str, key: str, env_names: list[str], default: str = "", workspace_root: str | Path = ".") -> str: for env_name in env_names: value = os.environ.get(env_name) if value: return value section_payload = load_agent_config(workspace_root).get(section, {}) if isinstance(section_payload, dict): value = section_payload.get(key) if isinstance(value, str) and value: return value return default def llm_model(workspace_root: str | Path = ".") -> str: return config_value("llm", "model", ["VIMAX_LLM_MODEL"], DEFAULT_LLM_MODEL, workspace_root) def llm_model_provider(workspace_root: str | Path = ".") -> str: return config_value("llm", "model_provider", ["VIMAX_LLM_MODEL_PROVIDER"], DEFAULT_LLM_MODEL_PROVIDER, workspace_root) def llm_base_url(workspace_root: str | Path = ".") -> str: return config_value("llm", "base_url", ["VIMAX_LLM_BASE_URL"], DEFAULT_LLM_BASE_URL, workspace_root) def llm_api_key(workspace_root: str | Path = ".") -> str: return config_value("llm", "api_key", ["VIMAX_LLM_API_KEY", "VIMAX_API_KEY"], "", workspace_root) def image_model(workspace_root: str | Path = ".") -> str: return config_value("image", "model", ["VIMAX_IMAGE_MODEL"], DEFAULT_IMAGE_MODEL, workspace_root) def image_base_url(workspace_root: str | Path = ".") -> str: return config_value("image", "base_url", ["VIMAX_IMAGE_BASE_URL"], DEFAULT_IMAGE_BASE_URL, workspace_root) def image_api_key(workspace_root: str | Path = ".") -> str: return config_value("image", "api_key", ["VIMAX_IMAGE_API_KEY", "VIMAX_LLM_API_KEY", "VIMAX_API_KEY"], llm_api_key(workspace_root), workspace_root) def embedding_model(workspace_root: str | Path = ".") -> str: return config_value("embedding", "model", ["VIMAX_EMBEDDING_MODEL"], DEFAULT_EMBEDDING_MODEL, workspace_root) def embedding_model_provider(workspace_root: str | Path = ".") -> str: return config_value("embedding", "model_provider", ["VIMAX_EMBEDDING_MODEL_PROVIDER"], DEFAULT_EMBEDDING_MODEL_PROVIDER, workspace_root) def embedding_base_url(workspace_root: str | Path = ".") -> str: return config_value("embedding", "base_url", ["VIMAX_EMBEDDING_BASE_URL"], "", workspace_root) def embedding_api_key(workspace_root: str | Path = ".") -> str: return config_value("embedding", "api_key", ["VIMAX_EMBEDDING_API_KEY"], "", workspace_root) def reranker_model(workspace_root: str | Path = ".") -> str: return config_value("reranker", "model", ["VIMAX_RERANKER_MODEL"], DEFAULT_RERANKER_MODEL, workspace_root) def reranker_base_url(workspace_root: str | Path = ".") -> str: return config_value("reranker", "base_url", ["VIMAX_RERANKER_BASE_URL"], "", workspace_root) def reranker_api_key(workspace_root: str | Path = ".") -> str: return config_value("reranker", "api_key", ["VIMAX_RERANKER_API_KEY"], "", workspace_root) def video_model(workspace_root: str | Path = ".") -> str: return config_value("video", "model", ["VIMAX_VIDEO_MODEL"], DEFAULT_VIDEO_MODEL, workspace_root) def video_base_url(workspace_root: str | Path = ".") -> str: return config_value("video", "base_url", ["VIMAX_VIDEO_BASE_URL"], DEFAULT_VIDEO_BASE_URL, workspace_root) def video_api_key(workspace_root: str | Path = ".") -> str: return config_value("video", "api_key", ["VIMAX_VIDEO_API_KEY", "VIMAX_LLM_API_KEY", "VIMAX_API_KEY"], llm_api_key(workspace_root), workspace_root) def api_provider_from_base_url(base_url: str) -> str: normalized = base_url.strip().lower() if "openrouter.ai" in normalized: return "openrouter" if "yunwu.ai" in normalized: return "yunwu" return "" def video_provider(workspace_root: str | Path = ".") -> str: """Infer the video API relay/provider from video.base_url. This is not a model provider setting. OpenRouter/Yunwu are transport/API gateways here, so users should configure base_url and let the adapter pick the matching implementation. """ return api_provider_from_base_url(video_base_url(workspace_root))