Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@
"api_base": "https://api.kimi.com/coding",
"timeout": 120,
"proxy": "",
"custom_headers": {"User-Agent": "claude-code/0.1.0"},
"custom_headers": {},
"anth_thinking_config": {"type": "", "budget": 0, "effort": ""},
},
"Moonshot": {
Expand Down Expand Up @@ -1397,7 +1397,7 @@
"api_base": "https://api.minimaxi.com/anthropic",
"timeout": 120,
"proxy": "",
"custom_headers": {"User-Agent": "claude-code/0.1.0"},
"custom_headers": {},
"anth_thinking_config": {"type": "", "budget": 0, "effort": ""},
},
"Xiaomi": {
Expand All @@ -1422,7 +1422,7 @@
"api_base": "https://token-plan-cn.xiaomimimo.com/anthropic",
"timeout": 120,
"proxy": "",
"custom_headers": {"User-Agent": "claude-code/0.1.0"},
"custom_headers": {},
"anth_thinking_config": {"type": "", "budget": 0, "effort": ""},
},
"xAI": {
Expand Down
24 changes: 24 additions & 0 deletions astrbot/core/provider/headers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from astrbot import __version__

DEFAULT_USER_AGENT = f"astrbot/{__version__}"


def build_provider_headers(custom_headers: object = None) -> dict[str, str]:
"""Build provider headers with an overridable AstrBot user agent.

Args:
custom_headers: Optional header mapping from provider configuration.

Returns:
A new header dictionary with string values and one User-Agent header.
"""
headers = {"User-Agent": DEFAULT_USER_AGENT}
if isinstance(custom_headers, dict):
for name, value in custom_headers.items():
name, value = str(name), str(value)
if name.lower() == "user-agent":
if value.strip():
headers["User-Agent"] = value
else:
headers[name] = value
return headers
4 changes: 4 additions & 0 deletions astrbot/core/provider/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
RerankResult,
ToolCallsResult,
)
from astrbot.core.provider.headers import build_provider_headers
from astrbot.core.provider.register import provider_cls_map
from astrbot.core.utils.astrbot_path import get_astrbot_path

Expand All @@ -31,6 +32,9 @@ def __init__(self, provider_config: dict) -> None:
super().__init__()
self.model_name = ""
self.provider_config = provider_config
self.request_headers = build_provider_headers(
provider_config.get("custom_headers")
)

def set_model(self, model_name: str) -> None:
"""Set the current model name"""
Expand Down
7 changes: 5 additions & 2 deletions astrbot/core/provider/sources/anthropic_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
log_connection_failure,
)

from ..headers import build_provider_headers
from ..register import register_provider_adapter
from .request_retry import retry_provider_request, retry_provider_request_context

Expand Down Expand Up @@ -71,12 +72,14 @@ def _resolve_custom_headers(
*,
required_headers: dict[str, str] | None = None,
) -> dict[str, str] | None:
merged_headers = cls._normalize_custom_headers(provider_config) or {}
merged_headers = build_provider_headers(
cls._normalize_custom_headers(provider_config)
)
if required_headers:
for header_name, header_value in required_headers.items():
if not merged_headers.get(header_name, "").strip():
merged_headers[header_name] = header_value
return merged_headers or None
return merged_headers

def __init__(
self,
Expand Down
14 changes: 9 additions & 5 deletions astrbot/core/provider/sources/azure_tts_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from httpx import AsyncClient, Timeout

from astrbot import logger
from astrbot.core.config.default import VERSION
from astrbot.core.provider.headers import build_provider_headers
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
from astrbot.core.utils.datetime_utils import generate_timestamp_id

Expand All @@ -25,6 +25,7 @@

class OTTSProvider:
def __init__(self, config: dict) -> None:
self.request_headers = build_provider_headers(config.get("custom_headers"))
self.skey = config["OTTS_SKEY"]
self.api_url = config["OTTS_URL"]
self.auth_time_url = config["OTTS_AUTH_TIME"]
Expand All @@ -47,7 +48,9 @@ def client(self) -> AsyncClient:

async def __aenter__(self):
self._client = AsyncClient(
timeout=self.timeout, proxy=self.proxy if self.proxy else None
headers=self.request_headers,
timeout=self.timeout,
proxy=self.proxy if self.proxy else None,
)
return self

Expand Down Expand Up @@ -93,7 +96,7 @@ async def get_audio(self, text: str, voice_params: dict) -> str:
"volume": voice_params["volume"],
},
headers={
"User-Agent": f"AstrBot/{VERSION}",
**self.request_headers,
"UAK": "AstrBot/AzureTTS",
},
)
Expand Down Expand Up @@ -148,7 +151,7 @@ def client(self) -> AsyncClient:
async def __aenter__(self):
self._client = AsyncClient(
headers={
"User-Agent": f"AstrBot/{VERSION}",
**self.request_headers,
"Content-Type": "application/ssml+xml",
"X-Microsoft-OutputFormat": "riff-48khz-16bit-mono-pcm",
},
Expand Down Expand Up @@ -194,7 +197,7 @@ async def get_audio(self, text: str) -> str:
content=ssml,
headers={
"Authorization": f"Bearer {self.token}",
"User-Agent": f"AstrBot/{VERSION}",
**self.request_headers,
},
)
response.raise_for_status()
Expand Down Expand Up @@ -223,6 +226,7 @@ def _parse_provider(
raise ValueError("无效的other[...]格式,应形如 other[{...}]")
json_str = match.group(1).strip()
otts_config = json.loads(json_str)
otts_config.setdefault("custom_headers", config.get("custom_headers"))
required = {"OTTS_SKEY", "OTTS_URL", "OTTS_AUTH_TIME"}
if missing := required - otts_config.keys():
raise ValueError(f"缺少OTTS参数: {', '.join(missing)}")
Expand Down
3 changes: 2 additions & 1 deletion astrbot/core/provider/sources/bailian_rerank_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None:
}

self.client = aiohttp.ClientSession(
headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout)
headers={**self.request_headers, **headers},
timeout=aiohttp.ClientTimeout(total=self.timeout),
)

# 设置模型名称
Expand Down
5 changes: 4 additions & 1 deletion astrbot/core/provider/sources/dashscope_embedding_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ async def get_embeddings(self, text: list[str]) -> list[list[float]]:
or self.model.startswith("tongyi-embedding-vision")
)

kwargs: dict = {"base_address": self.base_url}
kwargs: dict = {
"base_address": self.base_url,
"headers": self.request_headers.copy(),
}
if "embedding_dimensions" in self.provider_config:
try:
dimensions = int(self.provider_config["embedding_dimensions"])
Expand Down
8 changes: 7 additions & 1 deletion astrbot/core/provider/sources/dashscope_tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def _call_qwen_tts(self, model: str, text: str):

kwargs = {
"model": model,
"headers": self.request_headers.copy(),
"messages": None,
"api_key": self.chosen_api_key,
"voice": self.voice or "Cherry",
Expand Down Expand Up @@ -122,7 +123,9 @@ async def _download_audio_from_url(self, url: str) -> bytes | None:
timeout = max(self.timeout_ms / 1000, 1) if self.timeout_ms else 20
try:
async with (
aiohttp.ClientSession() as session,
aiohttp.ClientSession(
headers={"User-Agent": self.request_headers["User-Agent"]}
) as session,
session.get(
url,
timeout=aiohttp.ClientTimeout(total=timeout),
Expand All @@ -139,6 +142,9 @@ async def _synthesize_with_cosyvoice(
text: str,
) -> tuple[bytes | None, str]:
synthesizer = SpeechSynthesizer(
headers={
name.lower(): value for name, value in self.request_headers.items()
},
model=model,
voice=self.voice,
format=AudioFormat.WAV_24000HZ_MONO_16BIT,
Expand Down
5 changes: 5 additions & 0 deletions astrbot/core/provider/sources/edge_tts_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
import subprocess

import edge_tts
from edge_tts.constants import WSS_HEADERS

from astrbot.core import logger
from astrbot.core.provider.headers import DEFAULT_USER_AGENT
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
from astrbot.core.utils.datetime_utils import generate_timestamp_id

from ..entities import ProviderType
from ..provider import TTSProvider
from ..register import register_provider_adapter

# Edge TTS exposes synthesis headers as a shared SDK default, not a client option.
WSS_HEADERS["User-Agent"] = DEFAULT_USER_AGENT

"""
edge_tts 方式,能够免费、快速生成语音,使用需要先安装edge-tts库
```
Expand Down
1 change: 1 addition & 0 deletions astrbot/core/provider/sources/elevenlabs_tts_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def __init__(
if proxy:
logger.info(f"[ElevenLabs TTS] 使用代理: {proxy}")
self.client = httpx.AsyncClient(
headers=self.request_headers,
timeout=timeout,
proxy=proxy or None,
trust_env=False,
Expand Down
1 change: 1 addition & 0 deletions astrbot/core/provider/sources/fishaudio_tts_api_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def __init__(
if self.proxy:
logger.info(f"[FishAudio TTS] 使用代理: {self.proxy}")
self.headers = {
**self.request_headers,
"Authorization": f"Bearer {self.chosen_api_key}",
}
# FishAudio API 要求 model 作为 HTTP header 发送,而非请求体字段
Expand Down
6 changes: 5 additions & 1 deletion astrbot/core/provider/sources/gemini_embedding_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None:
api_base: str = provider_config["embedding_api_base"]
timeout: int = int(provider_config.get("timeout", 20))

http_options = types.HttpOptions(timeout=timeout * 1000)
http_options = types.HttpOptions(
timeout=timeout * 1000, headers=self.request_headers
)
if api_base:
api_base = api_base.removesuffix("/")
http_options.base_url = api_base
Expand All @@ -34,6 +36,8 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None:
logger.info(f"[Gemini Embedding] 使用代理: {proxy}")

self.client = genai.Client(api_key=api_key, http_options=http_options).aio
# The SDK adds its own lower-case UA alongside our explicit header.
self.client._api_client._http_options.headers.pop("user-agent", None)

self.model = provider_config.get(
"embedding_model",
Expand Down
3 changes: 3 additions & 0 deletions astrbot/core/provider/sources/gemini_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def _init_client(self) -> None:
"""初始化Gemini客户端"""
proxy = self.provider_config.get("proxy", "")
http_options = types.HttpOptions(
headers=self.request_headers,
base_url=self.api_base,
timeout=self.timeout * 1000, # 毫秒
)
Expand Down Expand Up @@ -114,6 +115,8 @@ def _init_client(self) -> None:
api_key=self.chosen_api_key,
http_options=http_options,
).aio
# The SDK adds its own lower-case UA alongside our explicit header.
self.client._api_client._http_options.headers.pop("user-agent", None)

def _init_safety_settings(self) -> None:
"""初始化安全设置"""
Expand Down
6 changes: 5 additions & 1 deletion astrbot/core/provider/sources/gemini_tts_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def __init__(
api_key: str = provider_config.get("gemini_tts_api_key", "")
api_base: str | None = provider_config.get("gemini_tts_api_base")
timeout: int = int(provider_config.get("gemini_tts_timeout", 20))
http_options = types.HttpOptions(timeout=timeout * 1000)
http_options = types.HttpOptions(
timeout=timeout * 1000, headers=self.request_headers
)

if api_base:
api_base = api_base.removesuffix("/")
Expand All @@ -39,6 +41,8 @@ def __init__(
logger.info(f"[Gemini TTS] 使用代理: {proxy}")

self.client = genai.Client(api_key=api_key, http_options=http_options).aio
# The SDK adds its own lower-case UA alongside our explicit header.
self.client._api_client._http_options.headers.pop("user-agent", None)
self.model: str = provider_config.get(
"gemini_tts_model",
"gemini-2.5-flash-preview-tts",
Expand Down
1 change: 1 addition & 0 deletions astrbot/core/provider/sources/gsv_selfhosted_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(
async def initialize(self) -> None:
"""异步初始化:在 ProviderManager 中被调用"""
self._session = aiohttp.ClientSession(
headers=self.request_headers,
timeout=aiohttp.ClientTimeout(total=self.timeout),
)
try:
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/gsvi_tts_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def get_audio(self, text: str) -> str:
"text_lang": self.text_lang,
}

async with aiohttp.ClientSession() as session:
async with aiohttp.ClientSession(headers=self.request_headers) as session:
async with session.post(url, json=data, headers=headers) as response:
if response.status == 200:
resp_json = await response.json()
Expand Down
3 changes: 2 additions & 1 deletion astrbot/core/provider/sources/kimi_code_source.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from ..headers import DEFAULT_USER_AGENT
from ..register import register_provider_adapter
from .anthropic_source import ProviderAnthropic

KIMI_CODE_API_BASE = "https://api.kimi.com/coding"
KIMI_CODE_DEFAULT_MODEL = "kimi-for-coding"
KIMI_CODE_USER_AGENT = "claude-code/0.1.0"
KIMI_CODE_USER_AGENT = DEFAULT_USER_AGENT


@register_provider_adapter(
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/mimo_stt_api_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async def get_text(self, audio_url: str) -> str:
try:
response = await self.client.post(
build_api_url(self.api_base),
headers=build_headers(self.chosen_api_key),
headers={**build_headers(self.chosen_api_key), **self.request_headers},
json=payload,
)
try:
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/mimo_tts_api_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _build_payload(self, text: str) -> dict:
async def get_audio(self, text: str) -> str:
response = await self.client.post(
build_api_url(self.api_base),
headers=build_headers(self.chosen_api_key),
headers={**build_headers(self.chosen_api_key), **self.request_headers},
json=self._build_payload(text),
)

Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/minimax_token_plan_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def get_models(self) -> list[str]:
logger.warning("No API key configured for MiniMax Token Plan.")
return []
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(headers=self.request_headers) as client:
resp = await client.get(
"https://api.minimaxi.com/v1/models",
headers={"Authorization": f"Bearer {key}"},
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/minimax_tts_api_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async def _call_tts_stream(self, text: str) -> AsyncIterator[str]:
"""进行流式请求"""
try:
async with (
aiohttp.ClientSession() as session,
aiohttp.ClientSession(headers=self.request_headers) as session,
session.post(
self.concat_base_url,
headers=self.headers,
Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/nvidia_embedding_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def _get_client(self):
}
timeout = aiohttp.ClientTimeout(total=self.timeout)
self.client = aiohttp.ClientSession(
headers=headers,
headers={**self.request_headers, **headers},
timeout=timeout,
)
return self.client
Expand Down
3 changes: 2 additions & 1 deletion astrbot/core/provider/sources/nvidia_rerank_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ async def _get_client(self):
"Accept": "application/json",
}
self.client = aiohttp.ClientSession(
headers=headers, timeout=aiohttp.ClientTimeout(total=self.timeout)
headers={**self.request_headers, **headers},
timeout=aiohttp.ClientTimeout(total=self.timeout),
)
return self.client

Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/provider/sources/ollama_embedding_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def _get_client(self):
}
timeout = aiohttp.ClientTimeout(total=self.timeout)
self.client = aiohttp.ClientSession(
headers=headers,
headers={**self.request_headers, **headers},
timeout=timeout,
)
return self.client
Expand Down
Loading
Loading