-
Notifications
You must be signed in to change notification settings - Fork 0
feat(minimax): auto-detect CN/global endpoint and region label #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,13 @@ | ||
| (function () { | ||
| const PRIMARY_USAGE_URL = "https://api.minimax.io/v1/api/openplatform/coding_plan/remains" | ||
| const FALLBACK_USAGE_URLS = [ | ||
| const GLOBAL_PRIMARY_USAGE_URL = "https://api.minimax.io/v1/api/openplatform/coding_plan/remains" | ||
| const GLOBAL_FALLBACK_USAGE_URLS = [ | ||
| "https://api.minimax.io/v1/coding_plan/remains", | ||
| "https://www.minimax.io/v1/api/openplatform/coding_plan/remains", | ||
| ] | ||
| const API_KEY_ENV_VARS = ["MINIMAX_API_KEY", "MINIMAX_API_TOKEN"] | ||
| const CN_PRIMARY_USAGE_URL = "https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains" | ||
| const CN_FALLBACK_USAGE_URLS = ["https://api.minimaxi.com/v1/coding_plan/remains"] | ||
| const GLOBAL_API_KEY_ENV_VARS = ["MINIMAX_API_KEY", "MINIMAX_API_TOKEN"] | ||
| const CN_API_KEY_ENV_VARS = ["MINIMAX_CN_API_KEY", "MINIMAX_API_KEY", "MINIMAX_API_TOKEN"] | ||
| const CODING_PLAN_WINDOW_MS = 5 * 60 * 60 * 1000 | ||
| const CODING_PLAN_WINDOW_TOLERANCE_MS = 10 * 60 * 1000 | ||
| const PROMPT_LIMIT_TO_PLAN = { | ||
|
|
@@ -96,9 +99,10 @@ | |
| return secOverflow <= msOverflow ? asSecondsMs : asMillisecondsMs | ||
| } | ||
|
|
||
| function loadApiKey(ctx) { | ||
| for (let i = 0; i < API_KEY_ENV_VARS.length; i += 1) { | ||
| const name = API_KEY_ENV_VARS[i] | ||
| function loadApiKey(ctx, endpointSelection) { | ||
| const envVars = endpointSelection === "CN" ? CN_API_KEY_ENV_VARS : GLOBAL_API_KEY_ENV_VARS | ||
| for (let i = 0; i < envVars.length; i += 1) { | ||
| const name = envVars[i] | ||
| let value = null | ||
| try { | ||
| value = ctx.host.env.get(name) | ||
|
|
@@ -108,13 +112,44 @@ | |
| const key = readString(value) | ||
| if (key) { | ||
| ctx.host.log.info("api key loaded from " + name) | ||
| return key | ||
| return { value: key, source: name } | ||
| } | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| function parsePayloadShape(ctx, payload) { | ||
| function loadEndpointSelection(ctx) { | ||
| // Always auto-detect region from available keys and probe result. | ||
| return "AUTO" | ||
| } | ||
|
|
||
| function getUsageUrls(endpointSelection) { | ||
| if (endpointSelection === "CN") { | ||
| return [CN_PRIMARY_USAGE_URL].concat(CN_FALLBACK_USAGE_URLS) | ||
| } | ||
| return [GLOBAL_PRIMARY_USAGE_URL].concat(GLOBAL_FALLBACK_USAGE_URLS) | ||
| } | ||
|
|
||
| function endpointAttempts(ctx, selection) { | ||
| if (selection === "CN") return ["CN"] | ||
| if (selection === "GLOBAL") return ["GLOBAL"] | ||
|
|
||
| // AUTO: if CN key exists, try CN first; otherwise try GLOBAL first. | ||
| let cnApiKeyValue = null | ||
| try { | ||
| cnApiKeyValue = ctx.host.env.get("MINIMAX_CN_API_KEY") | ||
| } catch (e) { | ||
| ctx.host.log.warn("env read failed for MINIMAX_CN_API_KEY: " + String(e)) | ||
| } | ||
| if (readString(cnApiKeyValue)) return ["CN", "GLOBAL"] | ||
| return ["GLOBAL", "CN"] | ||
| } | ||
|
|
||
| function formatAuthError() { | ||
| return "Session expired. Check your MiniMax API key." | ||
| } | ||
|
|
||
| function parsePayloadShape(ctx, payload, endpointSelection, keySource) { | ||
| if (!payload || typeof payload !== "object") return null | ||
|
|
||
| const data = payload.data && typeof payload.data === "object" ? payload.data : payload | ||
|
|
@@ -130,7 +165,7 @@ | |
| normalized.includes("log in") || | ||
| normalized.includes("login") | ||
| ) { | ||
| throw "Session expired. Check your MiniMax API key." | ||
| throw formatAuthError(endpointSelection, keySource) | ||
| } | ||
| throw statusMessage | ||
| ? "MiniMax API error: " + statusMessage | ||
|
|
@@ -232,8 +267,8 @@ | |
| } | ||
| } | ||
|
|
||
| function fetchUsagePayload(ctx, apiKey) { | ||
| const urls = [PRIMARY_USAGE_URL].concat(FALLBACK_USAGE_URLS) | ||
| function fetchUsagePayload(ctx, apiKey, endpointSelection, keySource) { | ||
| const urls = getUsageUrls(endpointSelection) | ||
| let lastStatus = null | ||
| let hadNetworkError = false | ||
| let authStatusCount = 0 | ||
|
|
@@ -279,23 +314,44 @@ | |
| } | ||
|
|
||
| if (authStatusCount > 0 && lastStatus === null && !hadNetworkError) { | ||
| throw "Session expired. Check your MiniMax API key." | ||
| throw formatAuthError(endpointSelection, keySource) | ||
| } | ||
| if (lastStatus !== null) throw "Request failed (HTTP " + lastStatus + "). Try again later." | ||
| if (hadNetworkError) throw "Request failed. Check your connection." | ||
| throw "Could not parse usage data." | ||
| } | ||
|
|
||
| function probe(ctx) { | ||
| const apiKey = loadApiKey(ctx) | ||
| if (!apiKey) throw "MiniMax API key missing. Set MINIMAX_API_KEY." | ||
| const endpointSelection = loadEndpointSelection(ctx) | ||
| const attempts = endpointAttempts(ctx, endpointSelection) | ||
| let lastError = null | ||
| let parsed = null | ||
| let resolvedEndpoint = null | ||
|
|
||
| for (let i = 0; i < attempts.length; i += 1) { | ||
| const endpoint = attempts[i] | ||
| const apiKeyInfo = loadApiKey(ctx, endpoint) | ||
| if (!apiKeyInfo) continue | ||
| try { | ||
| const payload = fetchUsagePayload(ctx, apiKeyInfo.value, endpoint, apiKeyInfo.source) | ||
| parsed = parsePayloadShape(ctx, payload, endpoint, apiKeyInfo.source) | ||
| if (parsed) { | ||
| resolvedEndpoint = endpoint | ||
| break | ||
| } | ||
| lastError = "Could not parse usage data." | ||
| } catch (e) { | ||
| lastError = String(e) | ||
| } | ||
| } | ||
|
|
||
| const payload = fetchUsagePayload(ctx, apiKey) | ||
| const parsed = parsePayloadShape(ctx, payload) | ||
| if (!parsed) throw "Could not parse usage data." | ||
| if (!parsed) { | ||
| if (lastError) throw lastError | ||
| throw "MiniMax API key missing. Set MINIMAX_API_KEY." | ||
| } | ||
|
|
||
| const line = { | ||
| label: "Session", | ||
| label: resolvedEndpoint ? "Session (" + resolvedEndpoint + ")" : "Session", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Changing the emitted label to Useful? React with 👍 / 👎. |
||
| used: parsed.used, | ||
| limit: parsed.total, | ||
| format: { kind: "count", suffix: "prompts" }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment unconditionally replaces prior attempt failures with the last endpoint’s error, so AUTO mode can misreport outages as credential problems. For example, if GLOBAL returns a real server failure (
Request failed (HTTP 500)from line 319) and the CN retry returns auth, the final error becomesSession expired, which sends users to rotate keys instead of treating it as service unavailability. Keep the higher-signal non-auth failure when later retries only add auth noise.Useful? React with 👍 / 👎.