Files
OmniRoute/open-sse/services/usage/moonshotOpenPlatform.ts
Bob.Hou 831ea040c3 feat(quota): Moonshot Open Platform balance and TPD lock for custom nodes (#12590)
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.

O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
2026-09-03 12:47:50 -03:00

77 lines
2.7 KiB
TypeScript

/**
* Moonshot Open Platform host recognition.
*
* Distinguishes prepaid Open Platform keys (api.moonshot.cn / api.moonshot.ai)
* from Kimi Coding Plan (api.kimi.com/coding). Custom compatible nodes are
* identified by baseUrl host, not by provider id (those ids are uuids).
*/
import { moonshotProvider } from "../../config/providers/registry/moonshot/index.ts";
import { kimiProvider } from "../../config/providers/registry/kimi/index.ts";
export const MOONSHOT_OPEN_PLATFORM_HOSTS: ReadonlySet<string> = new Set([
"api.moonshot.cn",
"api.moonshot.ai",
]);
export type MoonshotOriginConnection = {
provider?: string;
providerSpecificData?: unknown;
};
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
export function parseMoonshotOrigin(baseUrl: string | null | undefined): string | null {
if (typeof baseUrl !== "string" || baseUrl.trim() === "") return null;
let url: URL;
try {
url = new URL(baseUrl.trim());
} catch {
return null;
}
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
const host = url.hostname.toLowerCase();
if (!MOONSHOT_OPEN_PLATFORM_HOSTS.has(host)) return null;
const port = url.port ? `:${url.port}` : "";
return `${url.protocol}//${host}${port}`;
}
export function moonshotBalanceUrl(origin: string): string {
return `${origin}/v1/users/me/balance`;
}
function registryDefaultOrigin(provider: string | undefined): string | null {
if (provider === "moonshot") return parseMoonshotOrigin(moonshotProvider.baseUrl);
if (provider === "kimi") return parseMoonshotOrigin(kimiProvider.baseUrl);
return null;
}
export function resolveMoonshotOrigin(
connection: MoonshotOriginConnection,
nodeBaseUrl?: string | null,
): string | null {
const psd = asRecord(connection.providerSpecificData);
const fromPsd = typeof psd.baseUrl === "string" ? parseMoonshotOrigin(psd.baseUrl) : null;
if (fromPsd) return fromPsd;
const fromNode = parseMoonshotOrigin(nodeBaseUrl);
if (fromNode) return fromNode;
return registryDefaultOrigin(connection.provider);
}
export function isMoonshotOpenPlatformConnection(
connection: MoonshotOriginConnection,
nodeBaseUrl?: string | null,
): boolean {
return resolveMoonshotOrigin(connection, nodeBaseUrl) !== null;
}
/** Account-level empty wallet on Open Platform. Narrower than any compatible 429. */
export function isMoonshotAccountBalanceExhausted(errorText: string | null | undefined): boolean {
const lower = String(errorText || "").toLowerCase();
return lower.includes("insufficient balance") || lower.includes("exceeded_current_quota");
}