Files
OmniRoute/open-sse/services/tokenRefresh/providers/cursor.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

116 lines
3.8 KiB
TypeScript

/**
* Cursor OAuth token refresh via api2.cursor.sh/auth/exchange_user_api_key.
* OpenCodex-compatible (Bearer refresh token, JSON body `{}`).
* Self-contained in open-sse (no import from src/).
*/
const CURSOR_REFRESH_URL = "https://api2.cursor.sh/auth/exchange_user_api_key";
const REFRESH_TIMEOUT_MS = 15_000;
const REFRESH_ATTEMPTS = 3;
const REFRESH_RETRY_BASE_MS = 300;
const EXPIRY_SKEW_MS = 5 * 60 * 1000;
const FALLBACK_TTL_MS = 60 * 60 * 1000;
function isRetryableRefreshStatus(status: number): boolean {
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
}
function refreshRetryDelayMs(attempt: number, baseMs: number): number {
const exp = baseMs * 2 ** attempt;
return Math.floor(exp * (0.8 + Math.random() * 0.4));
}
function decodeExpMs(token: string): number {
try {
const parts = token.split(".");
if (parts.length !== 3) return Date.now() + FALLBACK_TTL_MS;
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8")) as {
exp?: unknown;
};
if (typeof payload.exp === "number") return payload.exp * 1000 - EXPIRY_SKEW_MS;
} catch {
/* ignore */
}
return Date.now() + FALLBACK_TTL_MS;
}
export type RefreshCursorTokenOptions = {
retryBaseMs?: number;
attempts?: number;
};
/**
* @returns {{ accessToken, refreshToken, expiresAt } | { error, code } | null}
*/
export async function refreshCursorToken(
refreshToken: string,
log?: { error?: (...args: unknown[]) => void; info?: (...args: unknown[]) => void },
_proxyConfig: unknown = null,
options: RefreshCursorTokenOptions = {}
) {
if (!refreshToken) {
return { error: "unrecoverable_refresh_error", code: "no_refresh_token" };
}
const attempts = options.attempts ?? REFRESH_ATTEMPTS;
const retryBaseMs = options.retryBaseMs ?? REFRESH_RETRY_BASE_MS;
let lastError: unknown;
for (let attempt = 0; attempt < attempts; attempt++) {
let response: Response;
try {
response = await fetch(CURSOR_REFRESH_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${refreshToken}`,
"Content-Type": "application/json",
},
body: "{}",
signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS),
});
} catch (err) {
lastError = err;
if (attempt === attempts - 1) break;
await new Promise((r) => setTimeout(r, refreshRetryDelayMs(attempt, retryBaseMs)));
continue;
}
if (response.ok) {
const data = (await response.json()) as { accessToken?: string; refreshToken?: string };
if (!data.accessToken) {
log?.error?.("TOKEN_REFRESH", "Cursor refresh response missing access token");
return null;
}
const nextRefresh = data.refreshToken || refreshToken;
log?.info?.("TOKEN_REFRESH", "Successfully refreshed Cursor token");
return {
accessToken: data.accessToken,
refreshToken: nextRefresh,
expiresAt: new Date(decodeExpMs(data.accessToken)).toISOString(),
};
}
if (response.status === 401 || response.status === 403) {
log?.error?.("TOKEN_REFRESH", "Cursor refresh rejected — re-authentication required", {
status: response.status,
});
return { error: "unrecoverable_refresh_error", code: "unauthorized" };
}
if (!isRetryableRefreshStatus(response.status) || attempt === attempts - 1) {
log?.error?.("TOKEN_REFRESH", "Failed to refresh Cursor token", { status: response.status });
return null;
}
lastError = new Error(`Cursor token refresh failed: ${response.status}`);
await response.body?.cancel().catch(() => {});
await new Promise((r) => setTimeout(r, refreshRetryDelayMs(attempt, retryBaseMs)));
}
log?.error?.(
"TOKEN_REFRESH",
lastError instanceof Error ? lastError.message : "Cursor token refresh failed"
);
return null;
}