resolve conflicts with main
This commit is contained in:
@@ -34,6 +34,7 @@ def load_config(config_path: Path | None = None) -> Config:
|
||||
try:
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
data = _migrate_config(data)
|
||||
return Config.model_validate(convert_keys(data))
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
print(f"Warning: Failed to load config from {path}: {e}")
|
||||
@@ -61,6 +62,16 @@ def save_config(config: Config, config_path: Path | None = None) -> None:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
def _migrate_config(data: dict) -> dict:
|
||||
"""Migrate old config formats to current."""
|
||||
# Move tools.exec.restrictToWorkspace → tools.restrictToWorkspace
|
||||
tools = data.get("tools", {})
|
||||
exec_cfg = tools.get("exec", {})
|
||||
if "restrictToWorkspace" in exec_cfg and "restrictToWorkspace" not in tools:
|
||||
tools["restrictToWorkspace"] = exec_cfg.pop("restrictToWorkspace")
|
||||
return data
|
||||
|
||||
|
||||
def convert_keys(data: Any) -> Any:
|
||||
"""Convert camelCase keys to snake_case for Pydantic."""
|
||||
if isinstance(data, dict):
|
||||
|
||||
@@ -17,12 +17,34 @@ class TelegramConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
token: str = "" # Bot token from @BotFather
|
||||
allow_from: list[str] = Field(default_factory=list) # Allowed user IDs or usernames
|
||||
proxy: str | None = None # HTTP/SOCKS5 proxy URL, e.g. "http://127.0.0.1:7890" or "socks5://127.0.0.1:1080"
|
||||
|
||||
|
||||
class FeishuConfig(BaseModel):
|
||||
"""Feishu/Lark channel configuration using WebSocket long connection."""
|
||||
enabled: bool = False
|
||||
app_id: str = "" # App ID from Feishu Open Platform
|
||||
app_secret: str = "" # App Secret from Feishu Open Platform
|
||||
encrypt_key: str = "" # Encrypt Key for event subscription (optional)
|
||||
verification_token: str = "" # Verification Token for event subscription (optional)
|
||||
allow_from: list[str] = Field(default_factory=list) # Allowed user open_ids
|
||||
|
||||
|
||||
class DiscordConfig(BaseModel):
|
||||
"""Discord channel configuration."""
|
||||
enabled: bool = False
|
||||
token: str = "" # Bot token from Discord Developer Portal
|
||||
allow_from: list[str] = Field(default_factory=list) # Allowed user IDs
|
||||
gateway_url: str = "wss://gateway.discord.gg/?v=10&encoding=json"
|
||||
intents: int = 37377 # GUILDS + GUILD_MESSAGES + DIRECT_MESSAGES + MESSAGE_CONTENT
|
||||
|
||||
|
||||
class ChannelsConfig(BaseModel):
|
||||
"""Configuration for chat channels."""
|
||||
whatsapp: WhatsAppConfig = Field(default_factory=WhatsAppConfig)
|
||||
telegram: TelegramConfig = Field(default_factory=TelegramConfig)
|
||||
discord: DiscordConfig = Field(default_factory=DiscordConfig)
|
||||
feishu: FeishuConfig = Field(default_factory=FeishuConfig)
|
||||
|
||||
|
||||
class AgentDefaults(BaseModel):
|
||||
@@ -50,11 +72,13 @@ class ProvidersConfig(BaseModel):
|
||||
anthropic: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
openai: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
openrouter: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
deepseek: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
groq: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
zhipu: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
dashscope: ProviderConfig = Field(default_factory=ProviderConfig) # 阿里云通义千问
|
||||
vllm: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
gemini: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
moonshot: ProviderConfig = Field(default_factory=ProviderConfig)
|
||||
|
||||
|
||||
class GatewayConfig(BaseModel):
|
||||
@@ -74,9 +98,16 @@ class WebToolsConfig(BaseModel):
|
||||
search: WebSearchConfig = Field(default_factory=WebSearchConfig)
|
||||
|
||||
|
||||
class ExecToolConfig(BaseModel):
|
||||
"""Shell exec tool configuration."""
|
||||
timeout: int = 60
|
||||
|
||||
|
||||
class ToolsConfig(BaseModel):
|
||||
"""Tools configuration."""
|
||||
web: WebToolsConfig = Field(default_factory=WebToolsConfig)
|
||||
exec: ExecToolConfig = Field(default_factory=ExecToolConfig)
|
||||
restrict_to_workspace: bool = False # If true, restrict all tool access to workspace directory
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
@@ -92,27 +123,59 @@ class Config(BaseSettings):
|
||||
"""Get expanded workspace path."""
|
||||
return Path(self.agents.defaults.workspace).expanduser()
|
||||
|
||||
def get_api_key(self) -> str | None:
|
||||
"""Get API key in priority order: OpenRouter > Anthropic > OpenAI > Gemini > Zhipu > DashScope > Groq > vLLM."""
|
||||
return (
|
||||
self.providers.openrouter.api_key or
|
||||
self.providers.anthropic.api_key or
|
||||
self.providers.openai.api_key or
|
||||
self.providers.gemini.api_key or
|
||||
self.providers.zhipu.api_key or
|
||||
self.providers.dashscope.api_key or
|
||||
self.providers.groq.api_key or
|
||||
self.providers.vllm.api_key or
|
||||
None
|
||||
)
|
||||
def _match_provider(self, model: str | None = None) -> ProviderConfig | None:
|
||||
"""Match a provider based on model name."""
|
||||
model = (model or self.agents.defaults.model).lower()
|
||||
# Map of keywords to provider configs
|
||||
providers = {
|
||||
"openrouter": self.providers.openrouter,
|
||||
"deepseek": self.providers.deepseek,
|
||||
"anthropic": self.providers.anthropic,
|
||||
"claude": self.providers.anthropic,
|
||||
"openai": self.providers.openai,
|
||||
"gpt": self.providers.openai,
|
||||
"gemini": self.providers.gemini,
|
||||
"zhipu": self.providers.zhipu,
|
||||
"glm": self.providers.zhipu,
|
||||
"zai": self.providers.zhipu,
|
||||
"dashscope": self.providers.dashscope,
|
||||
"qwen": self.providers.dashscope,
|
||||
"groq": self.providers.groq,
|
||||
"moonshot": self.providers.moonshot,
|
||||
"kimi": self.providers.moonshot,
|
||||
"vllm": self.providers.vllm,
|
||||
}
|
||||
for keyword, provider in providers.items():
|
||||
if keyword in model and provider.api_key:
|
||||
return provider
|
||||
return None
|
||||
|
||||
def get_api_key(self, model: str | None = None) -> str | None:
|
||||
"""Get API key for the given model (or default model). Falls back to first available key."""
|
||||
# Try matching by model name first
|
||||
matched = self._match_provider(model)
|
||||
if matched:
|
||||
return matched.api_key
|
||||
# Fallback: return first available key
|
||||
for provider in [
|
||||
self.providers.openrouter, self.providers.deepseek,
|
||||
self.providers.anthropic, self.providers.openai,
|
||||
self.providers.gemini, self.providers.zhipu,
|
||||
self.providers.dashscope, self.providers.moonshot,
|
||||
self.providers.vllm, self.providers.groq,
|
||||
]:
|
||||
if provider.api_key:
|
||||
return provider.api_key
|
||||
return None
|
||||
|
||||
def get_api_base(self) -> str | None:
|
||||
"""Get API base URL if using OpenRouter, Zhipu or vLLM."""
|
||||
if self.providers.openrouter.api_key:
|
||||
def get_api_base(self, model: str | None = None) -> str | None:
|
||||
"""Get API base URL based on model name."""
|
||||
model = (model or self.agents.defaults.model).lower()
|
||||
if "openrouter" in model:
|
||||
return self.providers.openrouter.api_base or "https://openrouter.ai/api/v1"
|
||||
if self.providers.zhipu.api_key:
|
||||
if any(k in model for k in ("zhipu", "glm", "zai")):
|
||||
return self.providers.zhipu.api_base
|
||||
if self.providers.vllm.api_base:
|
||||
if "vllm" in model:
|
||||
return self.providers.vllm.api_base
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user