From b1983d92cbcc8aa68fa6ef5ca46597266a9c3d01 Mon Sep 17 00:00:00 2001 From: piexian <64474352+piexian@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:16:52 +0800 Subject: [PATCH 1/3] feat: expose custom headers presets for all providers --- astrbot/core/config/default.py | 48 +++++++++++++++++++ .../src/composables/useProviderSources.ts | 18 ++++++- .../en-US/features/config-metadata.json | 19 +++++++- .../zh-CN/features/config-metadata.json | 19 +++++++- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 2b2ad39590..bcd43687b3 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -969,6 +969,7 @@ class ChatProviderTemplate(TypedDict): }, "gm_thinking_config": {"budget": 0, "level": "HIGH"}, "proxy": "", + "custom_headers": {}, }, "Anthropic": { "id": "anthropic", @@ -980,6 +981,7 @@ class ChatProviderTemplate(TypedDict): "api_base": "https://api.anthropic.com/v1", "timeout": 120, "proxy": "", + "custom_headers": {}, "anth_thinking_config": {"type": "", "budget": 0, "effort": ""}, }, "Moonshot": { @@ -1212,6 +1214,7 @@ class ChatProviderTemplate(TypedDict): "variables": {}, "timeout": 60, "proxy": "", + "custom_headers": {}, }, "Coze": { "id": "coze", @@ -1224,6 +1227,7 @@ class ChatProviderTemplate(TypedDict): "coze_api_base": "https://api.coze.cn", "timeout": 60, "proxy": "", + "custom_headers": {}, # "auto_save_history": True, }, "阿里云百炼应用": { @@ -1243,6 +1247,7 @@ class ChatProviderTemplate(TypedDict): "variables": {}, "timeout": 60, "proxy": "", + "custom_headers": {}, }, "FastGPT": { "id": "fastgpt", @@ -1267,6 +1272,7 @@ class ChatProviderTemplate(TypedDict): "api_base": "", "model": "whisper-1", "proxy": "", + "custom_headers": {}, }, "Whisper(Local)": { "provider": "openai", @@ -1275,6 +1281,7 @@ class ChatProviderTemplate(TypedDict): "enable": False, "id": "whisper_selfhost", "model": "tiny", + "custom_headers": {}, }, "SenseVoice(Local)": { "type": "sensevoice_stt_selfhost", @@ -1284,6 +1291,7 @@ class ChatProviderTemplate(TypedDict): "id": "sensevoice", "stt_model": "iic/SenseVoiceSmall", "is_emotion": False, + "custom_headers": {}, }, "OpenAI TTS(API)": { "id": "openai_tts", @@ -1297,6 +1305,7 @@ class ChatProviderTemplate(TypedDict): "openai-tts-voice": "alloy", "timeout": "20", "proxy": "", + "custom_headers": {}, }, "Genie TTS": { "id": "genie_tts", @@ -1310,6 +1319,7 @@ class ChatProviderTemplate(TypedDict): "genie_refer_audio_path": "", "genie_refer_text": "", "timeout": 20, + "custom_headers": {}, }, "Edge TTS": { "id": "edge_tts", @@ -1322,6 +1332,7 @@ class ChatProviderTemplate(TypedDict): "volume": "+0%", "pitch": "+0Hz", "timeout": 20, + "custom_headers": {}, }, "GSV TTS(Local)": { "id": "gsv_tts", @@ -1333,6 +1344,7 @@ class ChatProviderTemplate(TypedDict): "gpt_weights_path": "", "sovits_weights_path": "", "timeout": 60, + "custom_headers": {}, "gsv_default_parms": { "gsv_ref_audio_path": "", "gsv_prompt_text": "", @@ -1365,6 +1377,7 @@ class ChatProviderTemplate(TypedDict): "emotion": "default", "enable": False, "timeout": 20, + "custom_headers": {}, }, "FishAudio TTS(API)": { "id": "fishaudio_tts", @@ -1378,6 +1391,7 @@ class ChatProviderTemplate(TypedDict): "fishaudio-tts-reference-id": "", "timeout": "20", "proxy": "", + "custom_headers": {}, }, "阿里云百炼 TTS(API)": { "hint": "API Key 从 https://bailian.console.aliyun.com/?tab=model#/api-key 获取。模型和音色的选择文档请参考: 阿里云百炼语音合成音色名称。具体可参考 https://help.aliyun.com/zh/model-studio/speech-synthesis-and-speech-recognition", @@ -1390,6 +1404,7 @@ class ChatProviderTemplate(TypedDict): "model": "cosyvoice-v1", "dashscope_tts_voice": "loongstella", "timeout": "20", + "custom_headers": {}, }, "Azure TTS": { "id": "azure_tts", @@ -1405,6 +1420,7 @@ class ChatProviderTemplate(TypedDict): "azure_tts_subscription_key": "", "azure_tts_region": "eastus", "proxy": "", + "custom_headers": {}, }, "MiniMax TTS(API)": { "id": "minimax_tts", @@ -1428,6 +1444,7 @@ class ChatProviderTemplate(TypedDict): "minimax-voice-english-normalization": False, "timeout": 20, "proxy": "", + "custom_headers": {}, }, "火山引擎_TTS(API)": { "id": "volcengine_tts", @@ -1443,6 +1460,7 @@ class ChatProviderTemplate(TypedDict): "api_base": "https://openspeech.bytedance.com/api/v1/tts", "timeout": 20, "proxy": "", + "custom_headers": {}, }, "Gemini TTS": { "id": "gemini_tts", @@ -1457,6 +1475,7 @@ class ChatProviderTemplate(TypedDict): "gemini_tts_prefix": "", "gemini_tts_voice_name": "Leda", "proxy": "", + "custom_headers": {}, }, "OpenAI Embedding": { "id": "openai_embedding", @@ -1470,6 +1489,7 @@ class ChatProviderTemplate(TypedDict): "embedding_dimensions": 1024, "timeout": 20, "proxy": "", + "custom_headers": {}, }, "Gemini Embedding": { "id": "gemini_embedding", @@ -1483,6 +1503,7 @@ class ChatProviderTemplate(TypedDict): "embedding_dimensions": 768, "timeout": 20, "proxy": "", + "custom_headers": {}, }, "vLLM Rerank": { "id": "vllm_rerank", @@ -1494,6 +1515,7 @@ class ChatProviderTemplate(TypedDict): "rerank_api_base": "http://127.0.0.1:8000", "rerank_model": "BAAI/bge-reranker-base", "timeout": 20, + "custom_headers": {}, }, "Xinference Rerank": { "id": "xinference_rerank", @@ -1506,6 +1528,7 @@ class ChatProviderTemplate(TypedDict): "rerank_model": "BAAI/bge-reranker-base", "timeout": 20, "launch_model_if_not_running": False, + "custom_headers": {}, }, "阿里云百炼重排序": { "id": "bailian_rerank", @@ -1519,6 +1542,7 @@ class ChatProviderTemplate(TypedDict): "timeout": 30, "return_documents": False, "instruct": "", + "custom_headers": {}, }, "Xinference STT": { "id": "xinference_stt", @@ -1531,6 +1555,7 @@ class ChatProviderTemplate(TypedDict): "model": "whisper-large-v3", "timeout": 180, "launch_model_if_not_running": False, + "custom_headers": {}, }, }, "items": { @@ -1597,6 +1622,29 @@ class ChatProviderTemplate(TypedDict): "type": "dict", "items": {}, "hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。", + "template_schema": { + "User-Agent": { + "name": "User-Agent", + "description": "User-Agent 请求头", + "hint": "用于覆盖默认客户端标识。", + "type": "string", + "default": "", + }, + "Accept": { + "name": "Accept", + "description": "Accept 请求头", + "hint": "声明可接受的响应类型,通常保持 application/json。", + "type": "string", + "default": "application/json", + }, + "Accept-Language": { + "name": "Accept-Language", + "description": "Accept-Language 请求头", + "hint": "声明偏好语言,例如 zh-CN,zh;q=0.9,en;q=0.8。", + "type": "string", + "default": "", + }, + }, }, "custom_extra_body": { "description": "自定义请求体参数", diff --git a/dashboard/src/composables/useProviderSources.ts b/dashboard/src/composables/useProviderSources.ts index 97eb044da2..4f8b904f50 100644 --- a/dashboard/src/composables/useProviderSources.ts +++ b/dashboard/src/composables/useProviderSources.ts @@ -40,6 +40,19 @@ export function useProviderSources(options: UseProviderSourcesOptions) { const confirmDialog = useConfirmDialog() + function withDefaultCustomHeaders>(entry: T): T { + const normalized = JSON.parse(JSON.stringify(entry || {})) + if ( + !Object.prototype.hasOwnProperty.call(normalized, 'custom_headers') || + typeof normalized.custom_headers !== 'object' || + normalized.custom_headers === null || + Array.isArray(normalized.custom_headers) + ) { + normalized.custom_headers = {} + } + return normalized as T + } + async function askForConfirmation(message: string) { return askForConfirmationDialog(message, confirmDialog) } @@ -549,6 +562,7 @@ export function useProviderSources(options: UseProviderSourcesOptions) { provider_source_id: sourceId, model: modelName, modalities, + custom_headers: {}, custom_extra_body: {}, max_context_tokens: max_context_tokens } @@ -614,8 +628,8 @@ export function useProviderSources(options: UseProviderSourcesOptions) { if (configSchema.value.provider?.config_template) { providerTemplates.value = configSchema.value.provider.config_template } - providerSources.value = response.data.data.provider_sources || [] - providers.value = response.data.data.providers || [] + providerSources.value = (response.data.data.provider_sources || []).map((source: any) => withDefaultCustomHeaders(source)) + providers.value = (response.data.data.providers || []).map((provider: any) => withDefaultCustomHeaders(provider)) } } catch (error) { console.error('Failed to load provider template:', error) diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index 9665e893a2..daab798fec 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -961,7 +961,24 @@ }, "custom_headers": { "description": "Custom request headers", - "hint": "Key/value pairs added here are merged into the OpenAI SDK default_headers for custom HTTP headers. Values must be strings." + "hint": "Key/value pairs added here are merged into the OpenAI SDK default_headers for custom HTTP headers. Values must be strings.", + "template_schema": { + "User-Agent": { + "description": "User-Agent header", + "hint": "Override the default client identity.", + "name": "User-Agent" + }, + "Accept": { + "description": "Accept header", + "hint": "Declares accepted response formats. Usually keep application/json.", + "name": "Accept" + }, + "Accept-Language": { + "description": "Accept-Language header", + "hint": "Declares preferred languages, e.g. zh-CN,zh;q=0.9,en;q=0.8.", + "name": "Accept-Language" + } + } }, "custom_extra_body": { "description": "Custom request body parameters", diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index de7e81bcd2..0f3b79f1a7 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -964,7 +964,24 @@ }, "custom_headers": { "description": "自定义添加请求头", - "hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。" + "hint": "此处添加的键值对将被合并到 OpenAI SDK 的 default_headers 中,用于自定义 HTTP 请求头。值必须为字符串。", + "template_schema": { + "User-Agent": { + "description": "User-Agent 请求头", + "hint": "用于覆盖默认客户端标识。", + "name": "User-Agent" + }, + "Accept": { + "description": "Accept 请求头", + "hint": "声明可接受的响应类型,通常保持 application/json。", + "name": "Accept" + }, + "Accept-Language": { + "description": "Accept-Language 请求头", + "hint": "声明偏好语言,例如 zh-CN,zh;q=0.9,en;q=0.8。", + "name": "Accept-Language" + } + } }, "custom_extra_body": { "description": "自定义请求体参数", From cde59b688556460c1cd2bb88a924101cae4de414 Mon Sep 17 00:00:00 2001 From: piexian <64474352+piexian@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:44:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 4 +-- astrbot/dashboard/routes/config.py | 30 +++++++++++++++++-- .../src/composables/useProviderSources.ts | 18 ++--------- .../en-US/features/config-metadata.json | 2 +- .../zh-CN/features/config-metadata.json | 2 +- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index bcd43687b3..81f1212b91 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1633,9 +1633,9 @@ class ChatProviderTemplate(TypedDict): "Accept": { "name": "Accept", "description": "Accept 请求头", - "hint": "声明可接受的响应类型,通常保持 application/json。", + "hint": "声明可接受的响应类型。默认留空以使用 SDK/服务端默认值,仅在需要时显式覆盖(例如指定 application/json)。", "type": "string", - "default": "application/json", + "default": "", }, "Accept-Language": { "name": "Accept-Language", diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 6d60fb6de0..10222d4596 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -288,6 +288,23 @@ def __init__( ), } self.register_routes() + + @staticmethod + def _with_default_custom_headers(config: dict) -> dict: + normalized = dict(config) if isinstance(config, dict) else {} + headers = normalized.get("custom_headers") + if not isinstance(headers, dict): + normalized["custom_headers"] = {} + return normalized + # 在返回 provider_sources 和 provider 配置时,如果 custom_headers 字段不是 dict,则强制转换为 dict,避免前端使用时出错 + def _normalize_custom_headers_configs(self, configs: list) -> list: + normalized = [] + for config in configs: + if isinstance(config, dict): + normalized.append(self._with_default_custom_headers(config)) + else: + normalized.append(config) + return normalized async def delete_provider_source(self): """删除 provider_source,并更新关联的 providers""" @@ -345,6 +362,8 @@ async def update_provider_source(self): if not isinstance(new_source_config, dict): return Response().error("缺少或错误的配置数据").__dict__ + new_source_config = self._with_default_custom_headers(new_source_config) + # 确保配置中有 id 字段 if not new_source_config.get("id"): new_source_config["id"] = original_id @@ -426,8 +445,12 @@ async def get_provider_template(self): } data = { "config_schema": config_schema, - "providers": astrbot_config["provider"], - "provider_sources": astrbot_config["provider_sources"], + "providers": self._normalize_custom_headers_configs( + astrbot_config["provider"] + ), + "provider_sources": self._normalize_custom_headers_configs( + astrbot_config["provider_sources"] + ), } return Response().ok(data=data).__dict__ @@ -1136,6 +1159,7 @@ async def post_new_platform(self): async def post_new_provider(self): new_provider_config = await request.json + new_provider_config = self._with_default_custom_headers(new_provider_config) try: await self.core_lifecycle.provider_manager.create_provider( @@ -1179,6 +1203,8 @@ async def post_update_provider(self): if not origin_provider_id or not new_config: return Response().error("参数错误").__dict__ + new_config = self._with_default_custom_headers(new_config) + try: await self.core_lifecycle.provider_manager.update_provider( origin_provider_id, new_config diff --git a/dashboard/src/composables/useProviderSources.ts b/dashboard/src/composables/useProviderSources.ts index 4f8b904f50..97eb044da2 100644 --- a/dashboard/src/composables/useProviderSources.ts +++ b/dashboard/src/composables/useProviderSources.ts @@ -40,19 +40,6 @@ export function useProviderSources(options: UseProviderSourcesOptions) { const confirmDialog = useConfirmDialog() - function withDefaultCustomHeaders>(entry: T): T { - const normalized = JSON.parse(JSON.stringify(entry || {})) - if ( - !Object.prototype.hasOwnProperty.call(normalized, 'custom_headers') || - typeof normalized.custom_headers !== 'object' || - normalized.custom_headers === null || - Array.isArray(normalized.custom_headers) - ) { - normalized.custom_headers = {} - } - return normalized as T - } - async function askForConfirmation(message: string) { return askForConfirmationDialog(message, confirmDialog) } @@ -562,7 +549,6 @@ export function useProviderSources(options: UseProviderSourcesOptions) { provider_source_id: sourceId, model: modelName, modalities, - custom_headers: {}, custom_extra_body: {}, max_context_tokens: max_context_tokens } @@ -628,8 +614,8 @@ export function useProviderSources(options: UseProviderSourcesOptions) { if (configSchema.value.provider?.config_template) { providerTemplates.value = configSchema.value.provider.config_template } - providerSources.value = (response.data.data.provider_sources || []).map((source: any) => withDefaultCustomHeaders(source)) - providers.value = (response.data.data.providers || []).map((provider: any) => withDefaultCustomHeaders(provider)) + providerSources.value = response.data.data.provider_sources || [] + providers.value = response.data.data.providers || [] } } catch (error) { console.error('Failed to load provider template:', error) diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index daab798fec..db6d06bd0c 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -970,7 +970,7 @@ }, "Accept": { "description": "Accept header", - "hint": "Declares accepted response formats. Usually keep application/json.", + "hint": "Declares accepted response formats. Leave empty by default to use SDK/provider defaults and only override when needed (e.g. application/json).", "name": "Accept" }, "Accept-Language": { diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index 0f3b79f1a7..2fc3d071fa 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -973,7 +973,7 @@ }, "Accept": { "description": "Accept 请求头", - "hint": "声明可接受的响应类型,通常保持 application/json。", + "hint": "声明可接受的响应类型。默认留空以使用 SDK/服务端默认值,仅在需要时显式覆盖(例如指定 application/json)。", "name": "Accept" }, "Accept-Language": { From 32837bc6b83363e8da24c2ce74d0bf59a86122e6 Mon Sep 17 00:00:00 2001 From: piexian <64474352+piexian@users.noreply.github.com> Date: Wed, 25 Feb 2026 21:26:13 +0800 Subject: [PATCH 3/3] ruff --- astrbot/dashboard/routes/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index 10222d4596..7576ca013f 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -288,7 +288,7 @@ def __init__( ), } self.register_routes() - + @staticmethod def _with_default_custom_headers(config: dict) -> dict: normalized = dict(config) if isinstance(config, dict) else {} @@ -296,6 +296,7 @@ def _with_default_custom_headers(config: dict) -> dict: if not isinstance(headers, dict): normalized["custom_headers"] = {} return normalized + # 在返回 provider_sources 和 provider 配置时,如果 custom_headers 字段不是 dict,则强制转换为 dict,避免前端使用时出错 def _normalize_custom_headers_configs(self, configs: list) -> list: normalized = []