diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index 0986a883c0..76bc5b90b8 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -764,6 +764,12 @@ Select **Zhipu AI — BigModel Coding Plan (Responses)** (`zhipu-bigmodel-respon for the `openai-responses` endpoint `https://open.bigmodel.cn/api/v1`. This is separate from `zhipu-bigmodel-coding`, which uses Chat Completions at `/api/coding/paas/v4`. +Both Coding Plan presets use the existing domestic quota monitor with the raw API key +in `Authorization` and refuse redirects. A custom noncanonical destination remains +unsupported by that quota probe. Quota-reader support does not expand the Responses +model roster or establish Flash/model-discovery support; those require separate +endpoint-specific evidence. + The preset uses a **static roster** (`liveModels: false`) taken from the [official BigModel Codex example](https://docs.bigmodel.cn/cn/coding-plan/tool/codex.md): diff --git a/src/providers/quota.ts b/src/providers/quota.ts index 08da6d24ac..4f2876f9ba 100644 --- a/src/providers/quota.ts +++ b/src/providers/quota.ts @@ -2906,7 +2906,8 @@ function keyQuotaReaderForProvider(name: string, provider: OcxProviderConfig): K if (name === "deepseek" && isCanonicalDeepSeekBaseUrl(provider.baseUrl)) return fetchDeepSeekQuota; if (name === "cline-pass" && isCanonicalClineBaseUrl(provider.baseUrl)) return fetchClineQuota; if (isCanonicalOllamaCloudBaseUrl(provider.baseUrl ?? getProviderRegistryEntry(name)?.baseUrl)) return fetchOllamaCloudQuota; - if (["zai", "glm", "glm-cn", "zhipu-bigmodel-coding"].includes(name) && isCanonicalZaiBaseUrl(provider.baseUrl)) return fetchZaiQuota; + // The domestic Responses preset uses the same Coding Plan monitor, not model discovery. + if (["zai", "glm", "glm-cn", "zhipu-bigmodel-coding", "zhipu-bigmodel-responses"].includes(name) && isCanonicalZaiBaseUrl(provider.baseUrl)) return fetchZaiQuota; if (["minimax", "minimax-cn"].includes(name) && isCanonicalMinimaxBaseUrl(provider.baseUrl)) return fetchMinimaxQuota; if (name === "moonshot" && isCanonicalMoonshotBaseUrl(provider.baseUrl)) return fetchMoonshotQuota; if (name === "venice" && isCanonicalVeniceBaseUrl(provider.baseUrl)) return fetchVeniceQuota; diff --git a/tests/providers/provider-quota.test.ts b/tests/providers/provider-quota.test.ts index a8d4bef728..f67e0763ba 100644 --- a/tests/providers/provider-quota.test.ts +++ b/tests/providers/provider-quota.test.ts @@ -1077,12 +1077,12 @@ describe("fetchProviderQuotaReports", () => { expect(seen[0]?.url).toBe("https://api.z.ai/api/monitor/usage/quota/limit"); }); - test("Z.AI quota probes the BigModel Responses endpoint at /api/v1", async () => { - const seen: Array<{ url: string; authorization?: string }> = []; + test.each(["zhipu-bigmodel-coding", "zhipu-bigmodel-responses"])("Z.AI quota probes BigModel /api/v1 for %s", async name => { + const seen: Array<{ url: string; authorization?: string; redirect?: RequestRedirect }> = []; globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); const headers = init?.headers as Record | undefined; - seen.push({ url, authorization: headers?.Authorization }); + seen.push({ url, authorization: headers?.Authorization, redirect: init?.redirect }); return new Response(JSON.stringify({ success: true, data: { @@ -1095,10 +1095,9 @@ describe("fetchProviderQuotaReports", () => { }), { status: 200 }); }) as typeof fetch; - const result = await fetchProviderQuotaReports( - keyQuotaConfig("zhipu-bigmodel-coding", "https://open.bigmodel.cn/api/v1", "zai-secret"), - true, - ); + const config = keyQuotaConfig(name, "https://open.bigmodel.cn/api/v1", "zai-secret"); + config.providers[name]!.adapter = name === "zhipu-bigmodel-responses" ? "openai-responses" : "openai-chat"; + const result = await fetchProviderQuotaReports(config, true); expect(result.reports).toHaveLength(1); expect(result.reports[0]?.source).toBe("zai:quota-limit"); @@ -1110,6 +1109,23 @@ describe("fetchProviderQuotaReports", () => { expect(seen).toHaveLength(1); expect(seen[0]?.url).toBe("https://open.bigmodel.cn/api/monitor/usage/quota/limit"); expect(seen[0]?.authorization).toBe("zai-secret"); + expect(seen[0]?.redirect).toBe("error"); + }); + + test.each([ + "https://gateway.example.test/api/v1", + "https://open.bigmodel.cn/api/paas/v4", + ])("BigModel Responses quota does not probe unsupported destination %s", async baseUrl => { + let calls = 0; + globalThis.fetch = (async () => { + calls += 1; + return new Response("unexpected", { status: 500 }); + }) as typeof fetch; + const config = keyQuotaConfig("zhipu-bigmodel-responses", baseUrl); + config.providers["zhipu-bigmodel-responses"]!.adapter = "openai-responses"; + + expect((await fetchProviderQuotaReports(config, true)).reports).toEqual([]); + expect(calls).toBe(0); }); test("Z.AI quota treats an unsuccessful payload as a no-report", async () => {