feat: add Alibaba Coding Plan provider with .env base_url support (#200)

* feat(providers): 新增 Alibaba Cloud (Coding Plan) 内置 provider

对齐 hermes-agent 上游 PR #15045(commit 727d1088),新增
alibaba-coding-plan provider,鉴权使用 ALIBABA_CODING_PLAN_API_KEY
环境变量,base_url 可通过 ALIBABA_CODING_PLAN_BASE_URL 覆盖。

默认 base_url 使用国际版端点 coding-intl.dashscope.aliyuncs.com/v1,
与上游 auth.py:255 保持一致。中国大陆 DashScope 账号
(dashscope.aliyun.com 颁发的 sk-sp-* 密钥)需要通过
ALIBABA_CODING_PLAN_BASE_URL=https://coding.dashscope.aliyuncs.com/v1
(不带 -intl)覆盖,因为 -intl 端点对该类密钥返回 HTTP 401。
该差异在源码注释中已说明。

模型列表覆盖 8 个 Coding Plan 支持的模型:qwen3.5-plus、
qwen3-max-2026-01-23、qwen3-coder-next/plus、glm-5、glm-4.7、
kimi-k2.5、MiniMax-M2.5(基于实测可用列表)。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(providers): Alibaba Coding Plan 添加国内/国际区域切换

在 ProviderFormModal 中针对 alibaba-coding-plan preset 增加一个
"区域"字段,可在国际版(coding-intl)与中国大陆(coding,无 -intl)
两个端点之间切换,切换时自动更新 base_url。

默认选中国际版以对齐上游 hermes-agent 默认值。中国大陆 DashScope
账号(dashscope.aliyun.com 颁发的 sk-sp-* 密钥)只需在表单里点一下
"中国大陆"即可,无需手动改 base_url 或设环境变量。

8 个 locale(zh/en/de/es/fr/ja/ko/pt)都补全了 region/regionIntl/
regionCn 三个 i18n key。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(providers): builtin provider 列表优先读取 base_url env override

之前服务端 getAvailable 在渲染 builtin provider 列表时直接
用 PROVIDER_PRESETS 里的默认 base_url,忽略了用户保存到 .env
的 base_url override。这导致用户在 Alibaba Coding Plan 选了"中国
大陆"保存后,列表里仍然显示国际版 URL。

修复:envMapping.base_url_env 如果存在且 .env 中有值,优先
使用该值;否则 fallback 到 preset 默认。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
ww
2026-04-25 14:00:07 +08:00
committed by GitHub
parent 12ae840234
commit 4bdcaa6258
13 changed files with 92 additions and 2 deletions
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, watch, computed, onMounted } from 'vue'
import { NModal, NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, useMessage } from 'naive-ui'
import { NModal, NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NRadioGroup, NRadioButton, useMessage } from 'naive-ui'
import { useModelsStore } from '@/stores/hermes/models'
import { useI18n } from 'vue-i18n'
import CodexLoginModal from './CodexLoginModal.vue'
@@ -36,9 +36,16 @@ const modelOptions = ref<Array<{ label: string; value: string }>>([])
const CODEX_KEY = 'openai-codex'
const NOUS_KEY = 'nous'
const ALIBABA_CODING_KEY = 'alibaba-coding-plan'
const ALIBABA_CODING_REGIONS = {
intl: 'https://coding-intl.dashscope.aliyuncs.com/v1',
cn: 'https://coding.dashscope.aliyuncs.com/v1',
} as const
const isCodex = computed(() => selectedPreset.value === CODEX_KEY)
const isNous = computed(() => selectedPreset.value === NOUS_KEY)
const isAlibabaCoding = computed(() => selectedPreset.value === ALIBABA_CODING_KEY)
const alibabaCodingRegion = ref<'intl' | 'cn'>('intl')
const presetOptions = computed(() =>
modelsStore.allProviders.map(g => ({ label: g.label, value: g.provider })),
@@ -55,6 +62,7 @@ function autoGenerateName(url: string): string {
watch(selectedPreset, (val) => {
formData.value.model = ''
alibabaCodingRegion.value = 'intl'
if (val) {
const group = modelsStore.allProviders.find(g => g.provider === val)
if (group) {
@@ -68,6 +76,12 @@ watch(selectedPreset, (val) => {
}
})
watch(alibabaCodingRegion, (region) => {
if (isAlibabaCoding.value) {
formData.value.base_url = ALIBABA_CODING_REGIONS[region]
}
})
watch(() => formData.value.base_url, (url) => {
if (providerType.value === 'custom' && url.trim() && !formData.value.name) {
formData.value.name = autoGenerateName(url.trim())
@@ -236,6 +250,13 @@ function handleClose() {
/>
</NFormItem>
<NFormItem v-if="isAlibabaCoding" :label="t('models.region')">
<NRadioGroup v-model:value="alibabaCodingRegion">
<NRadioButton value="intl">{{ t('models.regionIntl') }}</NRadioButton>
<NRadioButton value="cn">{{ t('models.regionCn') }}</NRadioButton>
</NRadioGroup>
</NFormItem>
<NFormItem v-if="!isCodex && !isNous" :label="t('models.baseUrl')" required>
<NInput
v-model:value="formData.base_url"