fix: remove duplicate Zhipu usage provider

Keeps one Zhipu AI Coding Plan entry in usage settings
Preserves existing Zhipu aliases and config lookup
Removes duplicate backend quota registration
This commit is contained in:
Bohdan Triapitsyn
2026-05-19 14:49:03 +03:00
parent 0f015a495b
commit 3ac697adc7
6 changed files with 31 additions and 133 deletions
@@ -18,7 +18,6 @@ export const QUOTA_PROVIDERS: QuotaProviderMeta[] = [
{ id: 'minimax-cn-coding-plan', name: 'MiniMax Coding Plan (minimaxi.com)' },
{ id: 'minimax-coding-plan', name: 'MiniMax Coding Plan (minimax.io)' },
{ id: 'ollama-cloud', name: 'Ollama Cloud' },
{ id: 'zhipuai-coding-plan', name: 'ZhipuAI' },
];
export const QUOTA_PROVIDER_MAP = QUOTA_PROVIDERS.reduce<
+1 -2
View File
@@ -12,8 +12,7 @@ export type QuotaProviderId =
| 'zhipuai-coding-plan'
| 'minimax-coding-plan'
| 'minimax-cn-coding-plan'
| 'ollama-cloud'
| 'zhipuai-coding-plan';
| 'ollama-cloud';
export interface UsageWindow {
usedPercent: number | null;
@@ -26,11 +26,10 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
| `nano-gpt` | NanoGPT | `providers/nanogpt.js` | `nano-gpt`, `nanogpt`, `nano_gpt` |
| `openrouter` | OpenRouter | `providers/openrouter.js` | `openrouter` |
| `zai-coding-plan` | z.ai | `providers/zai.js` | `zai-coding-plan`, `zai`, `z.ai` |
| `zhipuai-coding-plan` | Zhipu AI Coding Plan | `providers/zhipuai-coding-plan.js` | `zhipuai-coding-plan` |
| `zhipuai-coding-plan` | Zhipu AI Coding Plan | `providers/zhipuai-coding-plan.js` | `zhipuai-coding-plan`, `zhipuai`, `zhipu` |
| `minimax-coding-plan` | MiniMax Coding Plan (minimax.io) | `providers/minimax-coding-plan.js` | `minimax-coding-plan` |
| `minimax-cn-coding-plan` | MiniMax Coding Plan (minimaxi.com) | `providers/minimax-cn-coding-plan.js` | `minimax-cn-coding-plan` |
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Cookie file at `~/.config/ollama-quota/cookie` (raw session cookie string) |
| `zhipuai-coding-plan` | ZhipuAI | `providers/zhipuai.js` | `zhipuai-coding-plan`, `zhipuai`, `zhipu` |
## Internal-only provider module
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
@@ -20,7 +20,6 @@ import * as zhipuaiCodingPlan from './zhipuai-coding-plan.js';
import * as minimaxCodingPlan from './minimax-coding-plan.js';
import * as minimaxCnCodingPlan from './minimax-cn-coding-plan.js';
import * as ollamaCloud from './ollama-cloud.js';
import * as zhipuai from './zhipuai.js';
const registry = {
claude: {
@@ -100,12 +99,6 @@ const registry = {
providerName: ollamaCloud.providerName,
isConfigured: ollamaCloud.isConfigured,
fetchQuota: ollamaCloud.fetchQuota
},
'zhipuai-coding-plan': {
providerId: zhipuai.providerId,
providerName: zhipuai.providerName,
isConfigured: zhipuai.isConfigured,
fetchQuota: zhipuai.fetchQuota
}
};
@@ -165,4 +158,4 @@ export const fetchNanoGptQuota = nanogpt.fetchQuota;
export const fetchMinimaxCodingPlanQuota = minimaxCodingPlan.fetchQuota;
export const fetchMinimaxCnCodingPlanQuota = minimaxCnCodingPlan.fetchQuota;
export const fetchOllamaCloudQuota = ollamaCloud.fetchQuota;
export const fetchZhipuaiQuota = zhipuai.fetchQuota;
export const fetchZhipuaiQuota = zhipuaiCodingPlan.fetchQuota;
@@ -26,6 +26,7 @@
* @property {Array<{modelCode: string, usage: number}>} [usageDetails]
*/
import { readAuthFile } from '../../opencode/auth.js';
import { readConfigLayers } from '../../opencode/shared.js';
import {
getAuthEntry,
normalizeAuthEntry,
@@ -37,18 +38,39 @@ import {
export const providerId = 'zhipuai-coding-plan';
export const providerName = 'Zhipu AI Coding Plan';
export const aliases = ['zhipuai-coding-plan'];
export const aliases = ['zhipuai-coding-plan', 'zhipuai', 'zhipu'];
export const isConfigured = () => {
function getApiKey() {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
return Boolean(entry?.key || entry?.token);
const apiKeyFromAuth = entry?.key ?? entry?.token;
if (apiKeyFromAuth) {
return apiKeyFromAuth;
}
try {
const { mergedConfig } = readConfigLayers();
for (const alias of aliases) {
const providerConfig = mergedConfig?.provider?.[alias];
if (providerConfig?.options?.apiKey) {
return providerConfig.options.apiKey;
}
}
} catch {
// Ignore config read errors; the provider will be treated as not configured.
}
return null;
}
export const isConfigured = () => {
return Boolean(getApiKey());
};
export const fetchQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const apiKey = entry?.key ?? entry?.token;
const apiKey = getApiKey();
if (!apiKey) {
return buildResult({
@@ -1,114 +0,0 @@
import { readAuthFile } from '../../opencode/auth.js';
import { readConfigLayers } from '../../opencode/shared.js';
import {
getAuthEntry,
normalizeAuthEntry,
buildResult,
toUsageWindow,
toNumber,
toTimestamp,
resolveWindowSeconds,
resolveWindowLabel,
normalizeTimestamp
} from '../utils/index.js';
export const providerId = 'zhipuai-coding-plan';
export const providerName = 'ZhipuAI';
export const aliases = ['zhipuai-coding-plan', 'zhipuai', 'zhipu'];
function getApiKey() {
const auth = readAuthFile();
const oldEntry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const apiKeyFromOld = oldEntry?.key ?? oldEntry?.token;
if (apiKeyFromOld) {
return apiKeyFromOld;
}
try {
const layers = readConfigLayers();
const { mergedConfig } = layers;
for (const alias of aliases) {
const providerConfig = mergedConfig?.provider?.[alias];
if (providerConfig?.options?.apiKey) {
return providerConfig.options.apiKey;
}
}
} catch (error) {
// Ignore read errors
}
return null;
}
export const isConfigured = () => {
return Boolean(getApiKey());
};
export const fetchQuota = async () => {
const apiKey = getApiKey();
if (!apiKey) {
return buildResult({
providerId,
providerName,
ok: false,
configured: false,
error: 'Not configured'
});
}
try {
const response = await fetch('https://open.bigmodel.cn/api/monitor/usage/quota/limit', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: `API error: ${response.status}`
});
}
const payload = await response.json();
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
const tokensLimit = limits.find((limit) => limit?.type === 'TOKENS_LIMIT');
const windowSeconds = resolveWindowSeconds(tokensLimit);
const windowLabel = resolveWindowLabel(windowSeconds);
const resetAt = tokensLimit?.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
const usedPercent = typeof tokensLimit?.percentage === 'number' ? tokensLimit.percentage : null;
const windows = {};
if (tokensLimit) {
windows[windowLabel] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt
});
}
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};