refactor(app): DRY pass — split large files, extract shared utils

S1: delete page.new.js (1724L abandoned) + remove dead getAntigravityProjectId
S2: split large files by natural seams
  - usage.js → usage/{github,google,claude,codex,kiro,minimax,misc,shared}.js
  - media-providers page → components/{Embedding,Tts,Generic,Stt}ExampleCard.js
  - EndpointPageClient → endpointConstants.js + endpointPing.js + components/
  - tokenRefresh.js → tokenRefresh/{dedup,providers}.js
  - ProviderLimits/index.js: 16 pure fn + 9 constants → utils.js
  - oauth/providers.js: 7 pure helpers → providerHelpers.js
S3: shared utils
  - getModelKind(m, fallback) → shared/constants/models.js (replaces 20× m.kind||m.type)
  - getStatusVariant → shared/utils/connectionStatus.js (dedup ConnectionRow/ConnectionsCard)
  - sseChunk → open-sse/utils/sse.js (dedup grok-web/perplexity-web)
  - fetchWithTimeout → usage/shared.js (replace 4× AbortController pattern in google.js)
fix: enableObservability2 field name in requestDetailsRepo
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
decolua
2026-06-14 19:31:09 +07:00
co-authored by Cursor
parent d3f61aac2f
commit fbf973f2e7
44 changed files with 4211 additions and 5875 deletions
+75 -680
View File
@@ -1,73 +1,35 @@
import { PROVIDERS, PROVIDER_OAUTH } from "../config/providers.js";
import { OAUTH_ENDPOINTS, GITHUB_COPILOT, REFRESH_LEAD_MS } from "../config/appConstants.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { PROVIDERS } from "../config/providers.js";
import { OAUTH_ENDPOINTS, REFRESH_LEAD_MS } from "../config/appConstants.js";
import {
refreshXaiToken,
refreshAccessToken,
refreshClaudeOAuthToken,
refreshGoogleToken,
refreshQwenToken,
refreshCodexToken,
refreshKiroToken,
refreshIflowToken,
refreshGitHubToken,
refreshCopilotToken,
classifyOAuthRefreshError,
} from "./tokenRefresh/providers.js";
// xAI refresh — wraps the class method from src/lib/oauth/services/xai.js so
// the token-refresh switches below can stay flat (one function per provider).
let _xaiServiceSingleton = null;
async function refreshXaiToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("xai", refreshToken, async () => {
try {
if (!_xaiServiceSingleton) {
const mod = await import("../../src/lib/oauth/services/xai.js");
_xaiServiceSingleton = new mod.XaiService();
}
const tokens = await _xaiServiceSingleton.refreshAccessToken(refreshToken);
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
expiresIn: tokens.expires_in,
idToken: tokens.id_token,
};
} catch (e) {
log?.warn?.("TOKEN_REFRESH", `xai refresh failed: ${e?.message || e}`);
const msg = String(e?.message || "");
if (msg.includes("invalid_grant") || msg.includes("invalid_request")) {
return { error: "invalid_grant" };
}
return null;
}
}, log);
}
// Re-export all provider refresh functions (preserves public API for all consumers)
export {
refreshAccessToken,
refreshClaudeOAuthToken,
refreshGoogleToken,
refreshQwenToken,
refreshCodexToken,
refreshKiroToken,
refreshIflowToken,
refreshGitHubToken,
refreshCopilotToken,
classifyOAuthRefreshError,
};
// Default token expiry buffer (refresh if expires within 5 minutes)
export const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
// Dedup: cache in-flight promise + recent result to prevent refresh_token_reused (Auth0 family revoke)
const REFRESH_RESULT_TTL_MS = 10_000;
const refreshDedupCache = new Map();
async function dedupRefresh(provider, oldToken, fn, log) {
if (!oldToken) return fn();
const key = `${provider}:${oldToken}`;
const hit = refreshDedupCache.get(key);
if (hit) {
if (hit.promise) {
log?.info?.("TOKEN_REFRESH", `Reusing in-flight refresh for ${provider}`);
return hit.promise;
}
if (hit.expiresAt > Date.now()) {
log?.info?.("TOKEN_REFRESH", `Reusing recent refresh result for ${provider}`);
return hit.result;
}
refreshDedupCache.delete(key);
}
const promise = (async () => {
try {
const result = await fn();
refreshDedupCache.set(key, { result, expiresAt: Date.now() + REFRESH_RESULT_TTL_MS });
return result;
} catch (err) {
refreshDedupCache.delete(key);
throw err;
}
})();
refreshDedupCache.set(key, { promise });
return promise;
}
// Check if refresh result indicates unrecoverable error (caller should stop retry, force re-auth)
export function isUnrecoverableRefreshError(result) {
return (
result &&
@@ -79,543 +41,82 @@ export function isUnrecoverableRefreshError(result) {
);
}
// Get provider-specific refresh lead time, falls back to default buffer
export function getRefreshLeadMs(provider) {
return REFRESH_LEAD_MS[provider] || TOKEN_EXPIRY_BUFFER_MS;
}
/**
* Refresh OAuth access token using refresh token
*/
export async function refreshAccessToken(provider, refreshToken, credentials, log) {
const config = PROVIDERS[provider];
if (!config || !config.refreshUrl) {
log?.warn?.("TOKEN_REFRESH", `No refresh URL configured for provider: ${provider}`);
return null;
}
if (!refreshToken) {
log?.warn?.("TOKEN_REFRESH", `No refresh token available for provider: ${provider}`);
return null;
}
return dedupRefresh(provider, refreshToken, async () => {
export function parseVertexSaJson(apiKey) {
if (typeof apiKey !== "string") return null;
try {
const response = await fetch(config.refreshUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: config.clientId,
client_secret: config.clientSecret,
}),
});
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", `Failed to refresh token for ${provider}`, {
status: response.status,
error: errorText,
});
return null;
const parsed = JSON.parse(apiKey);
if (parsed.type === "service_account" && parsed.client_email && parsed.private_key && parsed.project_id) {
return parsed;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", `Successfully refreshed token for ${provider}`, {
hasNewAccessToken: !!tokens.access_token,
hasNewRefreshToken: !!tokens.refresh_token,
expiresIn: tokens.expires_in,
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
expiresIn: tokens.expires_in,
};
} catch (error) {
log?.error?.("TOKEN_REFRESH", `Error refreshing token for ${provider}`, {
error: error.message,
});
return null;
}
}, log);
}
/**
* Specialized refresh for Claude OAuth tokens
*/
export async function refreshClaudeOAuthToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("claude", refreshToken, async () => {
try {
const response = await fetch(OAUTH_ENDPOINTS.anthropic.token, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: PROVIDERS.claude.clientId,
}),
});
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh Claude OAuth token", { status: response.status, error: errorText });
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Claude OAuth token", { hasNewAccessToken: !!tokens.access_token, expiresIn: tokens.expires_in });
return { accessToken: tokens.access_token, refreshToken: tokens.refresh_token || refreshToken, expiresIn: tokens.expires_in };
} catch (error) {
log?.error?.("TOKEN_REFRESH", `Network error refreshing Claude token: ${error.message}`);
return null;
}
}, log);
}
/**
* Specialized refresh for Google providers (Gemini, Antigravity)
*/
export async function refreshGoogleToken(refreshToken, clientId, clientSecret, log) {
if (!refreshToken) return null;
return dedupRefresh(`google:${clientId}`, refreshToken, async () => {
try {
const response = await fetch(OAUTH_ENDPOINTS.google.token, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret,
}),
});
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh Google token", { status: response.status, error: errorText });
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Google token", { hasNewAccessToken: !!tokens.access_token, expiresIn: tokens.expires_in });
return { accessToken: tokens.access_token, refreshToken: tokens.refresh_token || refreshToken, expiresIn: tokens.expires_in };
} catch (error) {
log?.error?.("TOKEN_REFRESH", `Network error refreshing Google token: ${error.message}`);
return null;
}
}, log);
}
/**
* Specialized refresh for Qwen OAuth tokens
*/
export async function refreshQwenToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("qwen", refreshToken, async () => {
const endpoint = OAUTH_ENDPOINTS.qwen.token;
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: PROVIDERS.qwen.clientId,
}),
});
if (response.status === 200) {
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Qwen token", {
hasNewAccessToken: !!tokens.access_token,
hasNewRefreshToken: !!tokens.refresh_token,
expiresIn: tokens.expires_in,
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
expiresIn: tokens.expires_in,
providerSpecificData: tokens.resource_url
? { resourceUrl: tokens.resource_url }
: undefined,
};
} else {
const errorText = await response.text().catch(() => "");
log?.warn?.("TOKEN_REFRESH", `Error with Qwen endpoint`, {
status: response.status,
error: errorText,
});
}
} catch (error) {
log?.warn?.("TOKEN_REFRESH", `Network error trying Qwen endpoint`, {
error: error.message,
});
}
log?.error?.("TOKEN_REFRESH", "Failed to refresh Qwen token");
return null;
}, log);
}
export function classifyOAuthRefreshError(errorText = "", status = 0) {
let parsed = null;
try {
parsed = errorText ? JSON.parse(errorText) : null;
} catch {
parsed = null;
}
const code = parsed?.error?.code || parsed?.error || parsed?.error_code || "";
const description = parsed?.error_description || parsed?.message || errorText || "";
const combined = `${code} ${description}`.toLowerCase();
const permanent = [
"refresh_token_expired",
"refresh_token_reused",
"refresh_token_invalidated",
"invalid_grant",
].some((marker) => combined.includes(marker));
return { status, code, description, permanent };
}
/**
* Specialized refresh for Codex (OpenAI) OAuth tokens.
* OpenAI uses rotating (one-time-use) refresh tokens.
* Returns { error: 'unrecoverable_refresh_error' } when token already consumed/invalid,
* so callers stop retrying and request re-authentication.
*/
export async function refreshCodexToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("codex", refreshToken, async () => {
try {
const response = await fetch(OAUTH_ENDPOINTS.openai.token, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
client_id: PROVIDERS.codex.clientId,
grant_type: "refresh_token",
refresh_token: refreshToken,
}),
});
if (!response.ok) {
const errorText = await response.text();
const failure = classifyOAuthRefreshError(errorText, response.status);
if (failure.permanent) {
log?.error?.("TOKEN_REFRESH", "Codex refresh token already used or invalid. Re-auth required.", {
status: response.status,
code: failure.code,
});
return { error: "unrecoverable_refresh_error", code: failure.code };
}
log?.error?.("TOKEN_REFRESH", "Failed to refresh Codex token", {
status: response.status,
error: errorText,
code: failure.code,
permanent: failure.permanent,
});
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Codex token", {
hasNewAccessToken: !!tokens.access_token,
hasNewRefreshToken: !!tokens.refresh_token,
hasIdToken: !!tokens.id_token,
expiresIn: tokens.expires_in,
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
idToken: tokens.id_token,
expiresIn: tokens.expires_in,
};
} catch (error) {
log?.error?.("TOKEN_REFRESH", `Network error refreshing Codex token: ${error.message}`);
return null;
}
}, log);
}
/**
* Specialized refresh for Kiro (AWS CodeWhisperer) tokens
* Supports both AWS SSO OIDC (Builder ID/IDC) and Social Auth (Google/GitHub)
*/
// Backfill missing Kiro profileArn on refresh so existing IDC connections self-heal
async function resolveKiroProfileArnPatch(providerSpecificData, accessToken, refreshedArn) {
if (providerSpecificData?.profileArn) return {};
let profileArn = refreshedArn?.trim?.() || null;
if (!profileArn) {
const { fetchKiroProfileArn } = await import("../../src/lib/oauth/providers.js");
profileArn = await fetchKiroProfileArn(accessToken);
}
return profileArn ? { providerSpecificData: { profileArn } } : {};
}
export async function refreshKiroToken(refreshToken, providerSpecificData, log, proxyOptions = null) {
if (!refreshToken) return null;
return dedupRefresh("kiro", refreshToken, async () => {
const authMethod = providerSpecificData?.authMethod;
const clientId = providerSpecificData?.clientId;
const clientSecret = providerSpecificData?.clientSecret;
const region = providerSpecificData?.region;
// AWS SSO OIDC (Builder ID or IDC)
// If clientId and clientSecret exist, assume AWS SSO OIDC (default to builder-id if authMethod not specified)
if (clientId && clientSecret) {
const isIDC = authMethod === "idc";
const endpoint = isIDC && region
? `https://oidc.${region}.amazonaws.com/token`
: "https://oidc.us-east-1.amazonaws.com/token";
const response = await proxyAwareFetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refreshToken,
grantType: "refresh_token",
}),
}, proxyOptions);
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh Kiro AWS token", {
status: response.status,
error: errorText,
});
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Kiro AWS token", {
hasNewAccessToken: !!tokens.accessToken,
expiresIn: tokens.expiresIn,
});
return {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken || refreshToken,
expiresIn: tokens.expiresIn,
...(await resolveKiroProfileArnPatch(providerSpecificData, tokens.accessToken, tokens.profileArn)),
};
}
// Social Auth (Google/GitHub) - use Kiro's refresh endpoint
const response = await proxyAwareFetch(PROVIDERS.kiro.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"User-Agent": "kiro-cli/1.0.0",
},
body: JSON.stringify({
refreshToken: refreshToken,
}),
}, proxyOptions);
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh Kiro social token", {
status: response.status,
error: errorText,
});
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Kiro social token", {
hasNewAccessToken: !!tokens.accessToken,
expiresIn: tokens.expiresIn,
});
return {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken || refreshToken,
expiresIn: tokens.expiresIn,
...(await resolveKiroProfileArnPatch(providerSpecificData, tokens.accessToken, tokens.profileArn)),
};
}, log);
}
/**
* Specialized refresh for iFlow OAuth tokens
*/
export async function refreshIflowToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("iflow", refreshToken, async () => {
const basicAuth = btoa(`${PROVIDERS.iflow.clientId}:${PROVIDERS.iflow.clientSecret}`);
// Cache Vertex tokens keyed by service account email { token, expiresAt }
const vertexTokenCache = new Map();
const response = await fetch(OAUTH_ENDPOINTS.iflow.token, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: PROVIDERS.iflow.clientId,
client_secret: PROVIDERS.iflow.clientSecret,
}),
});
export async function refreshVertexToken(saJson, log) {
const cacheKey = saJson.client_email;
const cached = vertexTokenCache.get(cacheKey);
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh iFlow token", {
status: response.status,
error: errorText,
});
return null;
if (cached && cached.expiresAt - Date.now() > 5 * 60 * 1000) {
return { accessToken: cached.token, expiresAt: cached.expiresAt };
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed iFlow token", {
hasNewAccessToken: !!tokens.access_token,
hasNewRefreshToken: !!tokens.refresh_token,
expiresIn: tokens.expires_in,
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
expiresIn: tokens.expires_in,
};
}, log);
}
/**
* Specialized refresh for GitHub Copilot OAuth tokens
*/
export async function refreshGitHubToken(refreshToken, log) {
if (!refreshToken) return null;
return dedupRefresh("github", refreshToken, async () => {
const params = {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: PROVIDERS.github.clientId,
};
if (PROVIDERS.github.clientSecret) {
params.client_secret = PROVIDERS.github.clientSecret;
}
const response = await fetch(OAUTH_ENDPOINTS.github.token, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams(params),
});
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh GitHub token", {
status: response.status,
error: errorText,
});
return null;
}
const tokens = await response.json();
log?.info?.("TOKEN_REFRESH", "Successfully refreshed GitHub token", {
hasNewAccessToken: !!tokens.access_token,
hasNewRefreshToken: !!tokens.refresh_token,
expiresIn: tokens.expires_in,
});
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token || refreshToken,
expiresIn: tokens.expires_in,
};
}, log);
}
/**
* Refresh GitHub Copilot token using GitHub access token
*/
export async function refreshCopilotToken(githubAccessToken, log) {
if (!githubAccessToken) return null;
return dedupRefresh("copilot", githubAccessToken, async () => {
try {
const response = await fetch(PROVIDER_OAUTH["github"]?.copilotTokenUrl, {
headers: {
"Authorization": `token ${githubAccessToken}`,
"User-Agent": GITHUB_COPILOT.USER_AGENT,
"Editor-Version": `vscode/${GITHUB_COPILOT.VSCODE_VERSION}`,
"Editor-Plugin-Version": `copilot-chat/${GITHUB_COPILOT.COPILOT_CHAT_VERSION}`,
"Accept": "application/json",
"x-github-api-version": GITHUB_COPILOT.API_VERSION
}
const { SignJWT, importPKCS8 } = await import("jose");
log?.debug?.("TOKEN_REFRESH", `Vertex minting token for ${saJson.client_email}`);
const privateKey = await importPKCS8(saJson.private_key.replace(/\\n/g, "\n"), "RS256");
const now = Math.floor(Date.now() / 1000);
const jwt = await new SignJWT({ scope: "https://www.googleapis.com/auth/cloud-platform" })
.setProtectedHeader({ alg: "RS256" })
.setIssuer(saJson.client_email)
.setAudience(OAUTH_ENDPOINTS.google.token)
.setIssuedAt(now)
.setExpirationTime(now + 3600)
.sign(privateKey);
const res = await fetch(OAUTH_ENDPOINTS.google.token, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: jwt,
}),
});
if (!response.ok) {
const errorText = await response.text();
log?.error?.("TOKEN_REFRESH", "Failed to refresh Copilot token", {
status: response.status,
error: errorText
});
if (!res.ok) {
const err = await res.text();
log?.error?.("TOKEN_REFRESH", `Vertex token mint failed: ${err}`);
return null;
}
const data = await response.json();
const { access_token, expires_in } = await res.json();
const expiresAt = Date.now() + (expires_in ?? 3600) * 1000;
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Copilot token", {
hasToken: !!data.token,
expiresAt: data.expires_at
});
vertexTokenCache.set(cacheKey, { token: access_token, expiresAt });
log?.info?.("TOKEN_REFRESH", `Vertex token minted for ${saJson.client_email}`);
return {
token: data.token,
expiresAt: data.expires_at
};
return { accessToken: access_token, expiresAt };
} catch (error) {
log?.error?.("TOKEN_REFRESH", "Error refreshing Copilot token", {
error: error.message
});
log?.error?.("TOKEN_REFRESH", `Vertex token error: ${error.message}`);
return null;
}
}, log);
}
// Single source of per-provider refresh dispatch (logic stays in each refreshXxx fn).
// Each handler: (credentials, log) => Promise<tokens|null>
function vertexRefreshHandler(c, log) {
const saJson = parseVertexSaJson(c.apiKey);
if (!saJson) return null;
return refreshVertexToken(saJson, log);
}
const REFRESH_HANDLERS = {
"gemini-cli": (c, log) => refreshGoogleToken(c.refreshToken, PROVIDERS["gemini-cli"].clientId, PROVIDERS["gemini-cli"].clientSecret, log),
antigravity: (c, log) => refreshGoogleToken(c.refreshToken, PROVIDERS.antigravity.clientId, PROVIDERS.antigravity.clientSecret, log),
@@ -630,28 +131,15 @@ const REFRESH_HANDLERS = {
"vertex-partner": vertexRefreshHandler
};
function vertexRefreshHandler(c, log) {
const saJson = parseVertexSaJson(c.apiKey);
if (!saJson) return null;
return refreshVertexToken(saJson, log);
}
/**
* Get access token for a specific provider (with in-flight dedup).
* If a refresh is already in-flight for same provider+token, share the promise
* to prevent parallel OAuth requests → Auth0 'refresh_token_reused' family revoke.
*/
export async function getAccessToken(provider, credentials, log) {
if (!credentials || !credentials.refreshToken || typeof credentials.refreshToken !== "string") {
log?.warn?.("TOKEN_REFRESH", `No valid refresh token available for provider: ${provider}`);
return null;
}
// Dedup is handled inside each refreshXxxToken function
return _getAccessTokenInternal(provider, credentials, log);
}
async function _getAccessTokenInternal(provider, credentials, log) {
// "gemini" shares Google refresh here (unlike refreshTokenByProvider)
if (provider === "gemini") {
return refreshGoogleToken(credentials.refreshToken, PROVIDERS.gemini.clientId, PROVIDERS.gemini.clientSecret, log);
}
@@ -663,19 +151,12 @@ async function _getAccessTokenInternal(provider, credentials, log) {
return handler(credentials, log);
}
/**
* Refresh token by provider type (helper for handlers)
*/
export async function refreshTokenByProvider(provider, credentials, log) {
if (!credentials.refreshToken) return null;
const handler = REFRESH_HANDLERS[provider];
// default: generic refresh (note: "gemini" is NOT special-cased here, by design)
return handler ? handler(credentials, log) : refreshAccessToken(provider, credentials.refreshToken, credentials, log);
}
/**
* Format credentials for provider
*/
export function formatProviderCredentials(provider, credentials, log) {
const config = PROVIDERS[provider];
if (!config) {
@@ -725,9 +206,6 @@ export function formatProviderCredentials(provider, credentials, log) {
}
}
/**
* Get all access tokens for a user
*/
export async function getAllAccessTokens(userInfo, log) {
const results = {};
@@ -748,89 +226,6 @@ export async function getAllAccessTokens(userInfo, log) {
return results;
}
/**
* Parse Vertex AI Service Account JSON from apiKey string
*/
export function parseVertexSaJson(apiKey) {
if (typeof apiKey !== "string") return null;
try {
const parsed = JSON.parse(apiKey);
if (parsed.type === "service_account" && parsed.client_email && parsed.private_key && parsed.project_id) {
return parsed;
}
return null;
} catch {
return null;
}
}
// Cache Vertex tokens keyed by service account email { token, expiresAt }
const vertexTokenCache = new Map();
/**
* Mint a short-lived OAuth2 Bearer token for Google Cloud Vertex AI
* using Service Account JSON + jose (RS256 JWT assertion flow).
* Token is cached until 5 minutes before expiry.
*/
export async function refreshVertexToken(saJson, log) {
const cacheKey = saJson.client_email;
const cached = vertexTokenCache.get(cacheKey);
// Return cached token if still valid (5-min buffer)
if (cached && cached.expiresAt - Date.now() > 5 * 60 * 1000) {
return { accessToken: cached.token, expiresAt: cached.expiresAt };
}
try {
const { SignJWT, importPKCS8 } = await import("jose");
log?.debug?.("TOKEN_REFRESH", `Vertex minting token for ${saJson.client_email}`);
const privateKey = await importPKCS8(saJson.private_key.replace(/\\n/g, "\n"), "RS256");
const now = Math.floor(Date.now() / 1000);
const jwt = await new SignJWT({ scope: "https://www.googleapis.com/auth/cloud-platform" })
.setProtectedHeader({ alg: "RS256" })
.setIssuer(saJson.client_email)
.setAudience(OAUTH_ENDPOINTS.google.token)
.setIssuedAt(now)
.setExpirationTime(now + 3600)
.sign(privateKey);
const res = await fetch(OAUTH_ENDPOINTS.google.token, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: jwt,
}),
});
if (!res.ok) {
const err = await res.text();
log?.error?.("TOKEN_REFRESH", `Vertex token mint failed: ${err}`);
return null;
}
const { access_token, expires_in } = await res.json();
const expiresAt = Date.now() + (expires_in ?? 3600) * 1000;
vertexTokenCache.set(cacheKey, { token: access_token, expiresAt });
log?.info?.("TOKEN_REFRESH", `Vertex token minted for ${saJson.client_email}`);
return { accessToken: access_token, expiresAt };
} catch (error) {
log?.error?.("TOKEN_REFRESH", `Vertex token error: ${error.message}`);
return null;
}
}
/**
* Refresh token with retry and exponential backoff
* Retries on failure with increasing delay: 1s, 2s, 3s...
* @param {function} refreshFn - Async function that returns token or null
* @param {number} maxRetries - Max retry attempts (default 3)
* @param {object} log - Logger instance (optional)
* @returns {Promise<object|null>} Token result or null if all retries fail
*/
export async function refreshWithRetry(refreshFn, maxRetries = 3, log = null) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
if (attempt > 0) {