diff --git a/backend/internal/service/cn_provider_quota_service.go b/backend/internal/service/cn_provider_quota_service.go index 271b18afe1..74c1f91fd6 100644 --- a/backend/internal/service/cn_provider_quota_service.go +++ b/backend/internal/service/cn_provider_quota_service.go @@ -142,6 +142,7 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou var ( targetURL string authHeader string + zhipuOrg string ) switch provider { case PlatformKimi: @@ -150,6 +151,13 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou case PlatformZhipu: targetURL = zhipuQuotaURL(baseURL) authHeader = apiKey // 智谱额度端点鉴权不加 Bearer 前缀 + // 团队版 GLM Coding Plan:额度端点需 ?type=2 + 组织/项目请求头, + // 否则官方 API 回「当前用户不存在coding plan」。组织 ID 存在即视为 + // 团队版(个人版凭据不含该字段,走原个人版查询路径)。 + zhipuOrg = strings.TrimSpace(account.GetCredential("zhipu_organization")) + if zhipuOrg != "" { + targetURL += "?type=2" + } } // 探测发起前过出站 URL 安全策略(与网关转发/Grok 探测同一套校验): @@ -172,6 +180,12 @@ func (s *CNProviderQuotaService) queryUsageForAccount(ctx context.Context, accou if provider == PlatformZhipu { req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept-Language", "en-US,en") + if zhipuOrg != "" { + req.Header.Set("bigmodel-organization", zhipuOrg) + if project := strings.TrimSpace(account.GetCredential("zhipu_project")); project != "" { + req.Header.Set("bigmodel-project", project) + } + } } // 探测与真实转发保持同一套账号级请求头覆写,避免探测通过但转发失败。 account.ApplyHeaderOverrides(req.Header) diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index ba30abfdac..ae2efff8de 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -547,6 +547,36 @@ + +
+
+ + +

{{ t('admin.accounts.cnProviders.zhipuTeam.help.title') }}

+
    +
  1. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step1') }}
  2. +
  3. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step2') }}
  4. +
  5. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step3') }}
  6. +
  7. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step4') }}
  8. +
+

+ {{ t('admin.accounts.cnProviders.zhipuTeam.help.example') }} +

+
+
+
+
+ + +
+
+ + +
+
+

{{ t('admin.accounts.cnProviders.zhipuTeam.hint') }}

+
+
@@ -3764,6 +3794,7 @@ import type { import BaseDialog from '@/components/common/BaseDialog.vue' import ConfirmDialog from '@/components/common/ConfirmDialog.vue' import Select from '@/components/common/Select.vue' +import HelpTooltip from '@/components/common/HelpTooltip.vue' import PlatformIcon from '@/components/common/PlatformIcon.vue' import Icon from '@/components/icons/Icon.vue' import ProxySelector from '@/components/common/ProxySelector.vue' @@ -3969,6 +4000,9 @@ const accountMode = ref('payg') // API 协议决定转发端点与格式:cc=现有转换链,anthropic=原生直通(Claude Code), // responses=deepseek 原生 Responses 端点(Codex)。与账号类型正交。 const apiProtocol = ref('adaptive') +// 智谱团队版 Coding Plan:组织/项目 ID,写入 credentials 供额度探测切换团队端点 +const zhipuOrganization = ref('') +const zhipuProject = ref('') const adaptiveBaseUrls = ref>({ chat_completions: '', anthropic: '', @@ -5544,6 +5578,11 @@ const handleSubmit = async () => { if (apiProtocol.value !== 'adaptive' && resolvedCNBase) { credentials.base_url = resolvedCNBase } + // 智谱团队版 Coding Plan:组织/项目 ID 写入凭据(非空才写) + if (form.platform === 'zhipu' && accountMode.value === 'coding') { + if (zhipuOrganization.value.trim()) credentials.zhipu_organization = zhipuOrganization.value.trim() + if (zhipuProject.value.trim()) credentials.zhipu_project = zhipuProject.value.trim() + } } // Add model mapping if configured(OpenAI 开启自动透传时不应用) diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index b1a6d10e97..8676fc907b 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -118,6 +118,35 @@

{{ t(`admin.accounts.cnProviders.apiProtocol.${cnProtocolDescKey}Desc`) }}

+ +
+
+ + +

{{ t('admin.accounts.cnProviders.zhipuTeam.help.title') }}

+
    +
  1. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step1') }}
  2. +
  3. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step2') }}
  4. +
  5. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step3') }}
  6. +
  7. {{ t('admin.accounts.cnProviders.zhipuTeam.help.step4') }}
  8. +
+

+ {{ t('admin.accounts.cnProviders.zhipuTeam.help.example') }} +

+
+
+
+
+ + +
+
+ + +
+
+

{{ t('admin.accounts.cnProviders.zhipuTeam.hint') }}

+
(() => { }) const editApiProtocol = ref('adaptive') const editAccountMode = ref('payg') +// 智谱团队版 Coding Plan:组织/项目 ID,写入 credentials 供额度探测切换团队端点 +const editZhipuOrganization = ref('') +const editZhipuProject = ref('') const editAdaptiveBaseUrls = ref>({ chat_completions: '', anthropic: '', @@ -3939,6 +3972,11 @@ const syncFormFromAccount = (newAccount: Account | null) => { nextAdaptiveBaseUrls[legacyProtocol] = legacyBaseUrl } editAdaptiveBaseUrls.value = nextAdaptiveBaseUrls + // 智谱团队版 Coding Plan:回填组织/项目 ID + if (newAccount.platform === 'zhipu') { + editZhipuOrganization.value = typeof credentials.zhipu_organization === 'string' ? credentials.zhipu_organization : '' + editZhipuProject.value = typeof credentials.zhipu_project === 'string' ? credentials.zhipu_project : '' + } } const platformDefaultUrl = newAccount.platform === 'openai' @@ -4648,6 +4686,19 @@ const handleSubmit = async () => { } else { delete newCredentials.api_base_urls } + // 智谱团队版 Coding Plan:组织/项目 ID 写入凭据(非空才写,清空即移除回落个人版路径) + if (props.account.platform === 'zhipu') { + const org = editZhipuOrganization.value.trim() + const project = editZhipuProject.value.trim() + if (org) { + newCredentials.zhipu_organization = org + if (project) newCredentials.zhipu_project = project + else delete newCredentials.zhipu_project + } else { + delete newCredentials.zhipu_organization + delete newCredentials.zhipu_project + } + } } // Handle API key diff --git a/frontend/src/i18n/locales/en/admin/accounts.ts b/frontend/src/i18n/locales/en/admin/accounts.ts index ae0867b9f1..5317941e2b 100644 --- a/frontend/src/i18n/locales/en/admin/accounts.ts +++ b/frontend/src/i18n/locales/en/admin/accounts.ts @@ -129,6 +129,22 @@ export default { responses: 'Responses', responsesDesc: 'Provider’s native Responses endpoint — ideal for Codex.', }, + zhipuTeam: { + title: 'Team Plan Organization / Project ID', + organization: 'Organization ID (team plan, optional)', + organizationPlaceholder: 'Organization ID of the team Coding Plan', + project: 'Project ID (team plan, optional)', + projectPlaceholder: 'Project ID of the team Coding Plan', + hint: 'Only required for the team GLM Coding Plan; when set, usage queries go through the team endpoint. Leave empty for personal plans. Click the question mark for how to obtain the IDs.', + help: { + title: 'How to get the Organization / Project ID', + step1: 'Sign in to the Zhipu open platform (bigmodel.cn) with your team account and open "Coding Plan → Team → My Plan".', + step2: 'Press F12 to open browser DevTools, switch to the Network tab, then reload the page.', + step3: 'Type /api/biz/v1/organization into the Network filter box and click the matched request (e.g. api_keys).', + step4: 'In the request URL, the org-… segment is the Organization ID and the proj_… segment is the Project ID (also visible as the bigmodel-organization / bigmodel-project request headers). Fill them into the fields above.', + example: 'Example: …/organization/org-0610bE2D…/projects/proj_0798F20…/api_keys → org-0610bE2D… goes into "Organization ID", proj_0798F20… into "Project ID"', + }, + }, balance: 'Balance --', window5h: '5-hour window', windowWeekly: 'Weekly window', diff --git a/frontend/src/i18n/locales/zh/admin/accounts.ts b/frontend/src/i18n/locales/zh/admin/accounts.ts index c277eb7c2f..48ba2c5546 100644 --- a/frontend/src/i18n/locales/zh/admin/accounts.ts +++ b/frontend/src/i18n/locales/zh/admin/accounts.ts @@ -332,6 +332,22 @@ export default { responses: 'Responses', responsesDesc: '供应商原生 Responses 端点,适配 Codex。', }, + zhipuTeam: { + title: '团队版组织 / 项目 ID', + organization: '组织 ID(团队版可选)', + organizationPlaceholder: '团队版 Coding Plan 的组织 ID', + project: '项目 ID(团队版可选)', + projectPlaceholder: '团队版 Coding Plan 的项目 ID', + hint: '仅团队版 GLM Coding Plan 需要填写,填写后用量查询走团队版端点;个人版留空即可。获取方式点击左侧问号查看教程。', + help: { + title: '如何获取组织 / 项目 ID', + step1: '用团队版账号登录智谱开放平台(bigmodel.cn),进入「Coding Plan → 团队版 → 我的套餐」页面。', + step2: '按 F12 打开浏览器开发者工具,切换到「Network / 网络」标签,然后刷新页面。', + step3: '在 Network 的筛选框中输入 /api/biz/v1/organization,点击命中的请求(如 api_keys)。', + step4: '请求 URL 中 org- 开头的一段即组织 ID、proj_ 开头的一段即项目 ID(也可在 Request Headers 中查看 bigmodel-organization / bigmodel-project 的值),分别填入上方输入框。', + example: '示例:…/organization/org-0610bE2D…/projects/proj_0798F20…/api_keys → org-0610bE2D… 填「组织 ID」,proj_0798F20… 填「项目 ID」', + }, + }, balance: '余额 --', window5h: '5 小时窗口', windowWeekly: '每周窗口',