mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
* fix: stop opencode-go quota lookup defaulting to Z.AI endpoint (#7022) getOpenCodeGoUsage() defaulted OPENCODE_GO_QUOTA_URL to https://api.z.ai/api/monitor/usage/quota/limit, a Zhipu AI (Z.AI/GLM) endpoint unrelated to opencode.ai. Whenever a connection had no dashboard-scraping config (workspaceId/authCookie), the user's real OpenCode Go API key was sent as a Bearer token to that third-party host by default, with no operator opt-in. Remove the hardcoded default: the quota-by-API-key fetch now only runs when the operator explicitly sets OMNIROUTE_OPENCODE_GO_QUOTA_URL. With it unset (the default), getOpenCodeGoUsage() returns a descriptive message and makes zero outbound calls, since OpenCode Go has no public quota API. Also updates .env.example and both EN/zh-CN copies of docs/reference/ENVIRONMENT.md to drop the stale Z.AI default value and fix the stale open-sse/services/usage.ts source-file reference. Regression test: tests/unit/opencode-go-quota-no-zai.test.ts (RED on current code, GREEN after the fix). * fix: align opencode-go-usage tests with opt-in quota URL contract (#7022) The prior commit removed the hardcoded api.z.ai default from OPENCODE_GO_QUOTA_URL, making the quota-by-API-key path opt-in via OMNIROUTE_OPENCODE_GO_QUOTA_URL. Six pre-existing tests in opencode-go-usage.test.ts still asserted the old default-fetch behavior and the old Z.AI-specific error wording, so they broke. Set OMNIROUTE_OPENCODE_GO_QUOTA_URL before the module import (the value is read once at load time) to simulate an operator who opted in, and update the two error-message assertions to the new generic wording ("the configured OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint" instead of "the Z.AI quota API"). Each test still verifies exactly the same behavior it did before (invalid key, fetch failure, 200 with auth error in body, invalid JSON, quota shape) — only the opt-in setup and message wording changed.
This commit is contained in:
committed by
GitHub
parent
fce2bb67ad
commit
3df06e5552
@@ -505,7 +505,9 @@ NEXT_PUBLIC_CLOUD_URL=
|
||||
#OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/
|
||||
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
|
||||
#OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/quota
|
||||
#OMNIROUTE_OPENCODE_GO_QUOTA_URL=https://api.z.ai/api/monitor/usage/quota/limit
|
||||
# OpenCode Go has no public quota API — this has no default and stays
|
||||
# unset unless you explicitly opt in to a self-hosted/mirrored endpoint:
|
||||
#OMNIROUTE_OPENCODE_GO_QUOTA_URL=
|
||||
#OMNIROUTE_OPENCODE_GO_DASHBOARD_URL=https://opencode.ai/workspace
|
||||
#OMNIROUTE_OLLAMA_CLOUD_USAGE_URL=https://ollama.com/settings
|
||||
|
||||
|
||||
1
changelog.d/fixes/7022-opencode-go-quota-url-zai.md
Normal file
1
changelog.d/fixes/7022-opencode-go-quota-url-zai.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(usage): stop opencode-go quota lookup from defaulting to an unrelated Z.AI endpoint (#7022)
|
||||
@@ -272,7 +272,7 @@ OmniRoute 提供两层防护:请求侧的注入扫描和响应侧的 PII 脱
|
||||
| `OMNIROUTE_PUBLIC_URL` | _(未设置)_ | `open-sse/utils/kieTask.ts` | 用于组合异步回调 URL 的公共源。kie.ai 回调的最低优先级回退;也用作其他中继的通用公共 URL。 |
|
||||
| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | Usage 页面使用的 CrofAI 配额查询端点。可覆盖为中继/测试固定件。 |
|
||||
| `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | Usage 页面使用的 OpenCode (zen/go) 配额查询端点。可覆盖为中继/测试固定件。 |
|
||||
| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | `https://api.z.ai/api/monitor/usage/quota/limit` | `open-sse/services/usage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。可覆盖为中继/测试固定件。 |
|
||||
| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(未设置)_ | `open-sse/services/opencodeOllamaUsage.ts` | Usage 页面使用的 OpenCode Go 配额查询端点。OpenCode Go 没有公开的配额 API,因此没有默认值;除非运维人员显式设置该变量选择接入自建/镜像端点,否则不会发起网络请求。 |
|
||||
| `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | 配置了 workspace ID 和 auth Cookie 时用于配额抓取的 OpenCode Go Dashboard 基础 URL。可覆盖为中继/测试固定件。 |
|
||||
| `OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | 用于 Dashboard 配额抓取的 OpenCode Go workspace ID。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 |
|
||||
| `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(未设置)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID 环境变量的备选名,在较短的别名之前使用。配置多个账户时,推荐使用每个连接的 Dashboard 字段。 |
|
||||
|
||||
@@ -280,7 +280,7 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp
|
||||
| `OMNIROUTE_PUBLIC_URL` | _(unset)_ | `open-sse/utils/kieTask.ts` | Public origin used to compose async callback URLs. Lowest-priority fallback for kie.ai callbacks; also used as a generic public URL for other relays. |
|
||||
| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | CrofAI quota lookup endpoint used by the Usage page. Override for relays / test fixtures. |
|
||||
| `OMNIROUTE_OPENCODE_QUOTA_URL` | `https://opencode.ai/zen/go/v1/quota` | `open-sse/services/opencodeQuotaFetcher.ts` | OpenCode (zen/go) quota lookup endpoint used by the Usage page. Override for relays / test fixtures. |
|
||||
| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | `https://api.z.ai/api/monitor/usage/quota/limit` | `open-sse/services/usage.ts` | OpenCode Go quota lookup endpoint used by the Usage page. Override for relays / test fixtures. |
|
||||
| `OMNIROUTE_OPENCODE_GO_QUOTA_URL` | _(unset)_ | `open-sse/services/opencodeOllamaUsage.ts` | OpenCode Go quota lookup endpoint used by the Usage page. OpenCode Go has no public quota API, so this has no default and the network call is skipped unless the operator opts in to a self-hosted/mirrored endpoint. |
|
||||
| `OMNIROUTE_OPENCODE_GO_DASHBOARD_URL` | `https://opencode.ai/workspace` | `open-sse/services/usage.ts` | OpenCode Go dashboard base URL used for quota scraping when a workspace ID and auth cookie are configured. Override for relays / test fixtures. |
|
||||
| `OPENCODE_GO_WORKSPACE_ID` | _(unset)_ | `open-sse/services/usage.ts` | OpenCode Go workspace ID used for dashboard quota scraping. Prefer the per-connection Dashboard field when multiple accounts are configured. |
|
||||
| `OMNIROUTE_OPENCODE_GO_WORKSPACE_ID` | _(unset)_ | `open-sse/services/usage.ts` | Alternate OpenCode Go workspace ID env var used before the shorter alias. Prefer the per-connection Dashboard field when multiple accounts are configured. |
|
||||
|
||||
@@ -13,8 +13,13 @@ type UsageQuota = {
|
||||
currency?: string;
|
||||
};
|
||||
|
||||
const OPENCODE_GO_QUOTA_URL =
|
||||
process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL ?? "https://api.z.ai/api/monitor/usage/quota/limit";
|
||||
// OpenCode Go does not expose a public quota API. There is no working
|
||||
// opencode.ai endpoint to default to (see #7022) — the quota-by-API-key path
|
||||
// below is opt-in only and activates exclusively when the operator sets
|
||||
// OMNIROUTE_OPENCODE_GO_QUOTA_URL explicitly. Never hardcode a third-party
|
||||
// host here (a previous default silently sent the user's API key to an
|
||||
// unrelated Z.AI endpoint).
|
||||
const OPENCODE_GO_QUOTA_URL = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL?.trim() || "";
|
||||
const OPENCODE_GO_DASHBOARD_BASE_URL =
|
||||
process.env.OMNIROUTE_OPENCODE_GO_DASHBOARD_URL ?? "https://opencode.ai/workspace";
|
||||
const OPENCODE_GO_QUOTA_TOTALS = { session: 12, weekly: 30, mcp_monthly: 60 } as const;
|
||||
@@ -335,6 +340,15 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?:
|
||||
};
|
||||
}
|
||||
|
||||
if (!OPENCODE_GO_QUOTA_URL) {
|
||||
return {
|
||||
message:
|
||||
"OpenCode Go does not expose a public quota API. " +
|
||||
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping, " +
|
||||
"or set OMNIROUTE_OPENCODE_GO_QUOTA_URL to opt in to an explicit quota endpoint.",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(OPENCODE_GO_QUOTA_URL, {
|
||||
headers: {
|
||||
@@ -348,7 +362,8 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?:
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
return {
|
||||
message:
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " +
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the configured " +
|
||||
"OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " +
|
||||
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping.",
|
||||
};
|
||||
}
|
||||
@@ -374,7 +389,8 @@ export async function getOpenCodeGoUsage(apiKey: string, providerSpecificData?:
|
||||
) {
|
||||
return {
|
||||
message:
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " +
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the configured " +
|
||||
"OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " +
|
||||
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping.",
|
||||
};
|
||||
}
|
||||
|
||||
31
tests/unit/opencode-go-quota-no-zai.test.ts
Normal file
31
tests/unit/opencode-go-quota-no-zai.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { getOpenCodeGoUsage } from "../../open-sse/services/opencodeOllamaUsage.ts";
|
||||
|
||||
test("getOpenCodeGoUsage does not send the user's OpenCode Go API key to api.z.ai by default", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const originalEnv = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
|
||||
delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
|
||||
|
||||
let calledHost: string | null = null;
|
||||
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
||||
const url = typeof input === "string" ? input : input.toString();
|
||||
calledHost = new URL(url).host;
|
||||
throw new Error(`unexpected outbound fetch to ${url}`);
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
const result = await getOpenCodeGoUsage("sk-fake-opencode-go-key", undefined);
|
||||
assert.notStrictEqual(calledHost, "api.z.ai");
|
||||
assert.strictEqual(calledHost, null);
|
||||
assert.ok(
|
||||
typeof result.message === "string" && result.message.length > 0,
|
||||
"expected a descriptive message when no quota URL is configured"
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
if (originalEnv === undefined) delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
|
||||
else process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = originalEnv;
|
||||
}
|
||||
});
|
||||
@@ -1,9 +1,25 @@
|
||||
import test from "node:test";
|
||||
import test, { after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
// The OpenCode Go quota-by-API-key path is opt-in only (see #7022 — there is no
|
||||
// working default quota endpoint, so OMNIROUTE_OPENCODE_GO_QUOTA_URL must be set
|
||||
// explicitly by the operator). The module reads this env var once at import time,
|
||||
// so it has to be set BEFORE the dynamic import below for the opt-in tests in this
|
||||
// file (which simulate an operator who configured the URL) to exercise the fetch path.
|
||||
const ORIGINAL_OPENCODE_GO_QUOTA_URL = process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
|
||||
process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
||||
|
||||
const usage = await import("../../open-sse/services/usage.ts");
|
||||
const { USAGE_SUPPORTED_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
|
||||
|
||||
after(() => {
|
||||
if (ORIGINAL_OPENCODE_GO_QUOTA_URL === undefined) {
|
||||
delete process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL;
|
||||
} else {
|
||||
process.env.OMNIROUTE_OPENCODE_GO_QUOTA_URL = ORIGINAL_OPENCODE_GO_QUOTA_URL;
|
||||
}
|
||||
});
|
||||
|
||||
test("USAGE_SUPPORTED_PROVIDERS includes opencode-go", () => {
|
||||
assert.ok(
|
||||
(USAGE_SUPPORTED_PROVIDERS as string[]).includes("opencode-go"),
|
||||
@@ -298,7 +314,8 @@ test("getUsageForProvider returns message for invalid OpenCode Go API keys", asy
|
||||
})) as { message: string };
|
||||
assert.equal(
|
||||
result.message,
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " +
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the configured " +
|
||||
"OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " +
|
||||
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping."
|
||||
);
|
||||
} finally {
|
||||
@@ -342,7 +359,8 @@ test("getUsageForProvider returns message when OpenCode Go quota API returns 200
|
||||
})) as { message: string };
|
||||
assert.equal(
|
||||
result.message,
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the Z.AI quota API. " +
|
||||
"OpenCode Go API key is valid for chat/models but cannot read quota from the configured " +
|
||||
"OMNIROUTE_OPENCODE_GO_QUOTA_URL endpoint. " +
|
||||
"Set OPENCODE_GO_WORKSPACE_ID and OPENCODE_GO_AUTH_COOKIE to enable dashboard quota scraping."
|
||||
);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user