mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-25 08:32:11 +03:00
Validado em lote combinado (batch-0824g, junto de #11388/#11397/#11415/#11418) contra o tip de release/v3.8.50: typecheck:core limpo, file-size/changelog/complexity/cognitive-complexity OK, 62/62 testes focados passando. Diagnóstico correto e bem documentado: a falha do nightly Node 26 era um teste que sorteia um número e depende do resultado, não uma quebra de compatibilidade. Comportamento de produção inalterado (a janela de jitter continua aleatória; só o teste ganhou controle sobre ela). Obrigado pela investigação detalhada!
77 lines
3.1 KiB
TypeScript
77 lines
3.1 KiB
TypeScript
import { isKimiTokenExpiringSoon } from "@omniroute/open-sse/utils/kimiJwt.ts";
|
|
import { exchangeKimiRefreshToken } from "@/lib/kimi/tokenRefresh";
|
|
import { updateProviderConnection } from "@/lib/db/providers";
|
|
|
|
/**
|
|
* Refresh window, spread over [60, 240) seconds before expiry so a fleet of
|
|
* connections does not stampede the token endpoint at the same instant.
|
|
*
|
|
* Kept as a named export rather than inline: it is the only nondeterminism in this
|
|
* path, and a caller that needs a decision it can predict — a test — has to be able
|
|
* to replace it. `tests/unit/token-health-check-kimi.test.ts` used a token expiring
|
|
* in 90 s and asserted a refresh, which is a coin the draw loses 1 in 6 times
|
|
* (a refresh needs `jitter >= 90`, i.e. 150 of the 180 possible values). It failed
|
|
* that way on the Node 26 nightly and was triaged as a Node-compat break.
|
|
*/
|
|
export function defaultKimiRefreshJitterSec(): number {
|
|
return 60 + Math.floor(Math.random() * 180);
|
|
}
|
|
|
|
export async function checkKimiWebConnectionIfNeeded(params: {
|
|
conn: any;
|
|
now: string;
|
|
log: (msg: string, ...args: any[]) => void;
|
|
logWarn: (msg: string, ...args: any[]) => void;
|
|
logError: (msg: string, ...args: any[]) => void;
|
|
getConnectionLogLabel: (conn: any) => string;
|
|
logPrefix: string;
|
|
exchangeFn?: typeof exchangeKimiRefreshToken;
|
|
persistFn?: typeof updateProviderConnection;
|
|
/**
|
|
* Seconds before expiry at which a refresh is triggered. Defaults to the random
|
|
* spread below; injectable so a caller — a test above all — can decide the window
|
|
* instead of drawing it.
|
|
*/
|
|
jitterSecFn?: () => number;
|
|
}): Promise<boolean> {
|
|
const { conn, log, logWarn, getConnectionLogLabel, logPrefix } = params;
|
|
const provider = String(conn?.provider || "").toLowerCase();
|
|
if (provider !== "kimi-web" && provider !== "kimi_web") return false;
|
|
|
|
const refreshToken = conn.refreshToken || conn.providerSpecificData?.refreshToken;
|
|
if (!refreshToken) return true; // Handled, but cannot refresh without refresh_token
|
|
|
|
const token = conn.apiKey || conn.accessToken;
|
|
const jitterSec = (params.jitterSecFn ?? defaultKimiRefreshJitterSec)();
|
|
const expiringSoon = isKimiTokenExpiringSoon(token, jitterSec);
|
|
|
|
if (!expiringSoon) return true;
|
|
|
|
log(
|
|
`${logPrefix} Kimi Web connection ${getConnectionLogLabel(conn)} token expiring soon; refreshing in background...`
|
|
);
|
|
|
|
const exchange = params.exchangeFn || exchangeKimiRefreshToken;
|
|
const persist = params.persistFn || updateProviderConnection;
|
|
|
|
const res = await exchange(refreshToken);
|
|
if (res.success && res.accessToken) {
|
|
log(
|
|
`${logPrefix} Kimi Web connection ${getConnectionLogLabel(conn)} token refreshed successfully.`
|
|
);
|
|
await persist(conn.id, {
|
|
apiKey: res.accessToken,
|
|
accessToken: res.accessToken,
|
|
refreshToken: res.refreshToken,
|
|
expiresAt: res.expiresAtSec ? new Date(res.expiresAtSec * 1000).toISOString() : undefined,
|
|
testStatus: "active",
|
|
lastError: null,
|
|
errorCode: null,
|
|
});
|
|
} else {
|
|
logWarn(`${logPrefix} Failed to auto-refresh Kimi Web token: ${res.error}`);
|
|
}
|
|
|
|
return true;
|
|
}
|