feat(quota): add opt-in Codex auto-ping

Generalize Claude 5h auto-ping into a provider-generic scheduler and add
opt-in Codex auto-ping that warms the next 5h window via a tiny gpt-5.5
request when session.resetAt slides. Default off, per-connection toggle,
failure cooldown, blocking-quota skip, drains stream before success.

Closes #2107

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EmirhanandCursor authored and decolua committed 2026-06-29 15:13:17 +07:00
1 parent fc8722e897
commit b66b5c68ce
9 files changed
+757 -168

No files matched your search

@@ -23,6 +23,9 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
: hasLegacyProxy
? `Legacy: ${connection.providerSpecificData?.connectionProxyUrl}`
: "";
const autoPingTooltip = autoPing?.provider === "codex"
? "Auto-starts the next 5h Codex window after reset by sending a tiny gpt-5.5 request. Consumes a small amount of quota."
: "When your 5h quota runs out, auto-sends a request the moment it resets so a new window starts right away.";
let maskedProxyUrl = "";
if (boundProxyPool?.proxyUrl || connection.providerSpecificData?.connectionProxyUrl) {
@@ -244,7 +247,7 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
</div>
)}
{autoPing && (
<Tooltip text="When your 5h quota runs out, auto-sends a request the moment it resets so a new window starts right away.">
<Tooltip text={autoPingTooltip}>
<button
onClick={() => autoPing.onToggle(!autoPing.on)}
className={`flex w-full flex-col items-center rounded px-2 py-1 transition-colors hover:bg-black/5 dark:hover:bg-white/5 ${autoPing.on ? "text-primary" : "text-text-muted hover:text-primary"}`}
@@ -310,5 +313,6 @@ ConnectionRow.propTypes = {
autoPing: PropTypes.shape({
on: PropTypes.bool,
onToggle: PropTypes.func,
provider: PropTypes.string,
}),
};
@@ -23,6 +23,11 @@ import BulkImportCodexModal from "./BulkImportCodexModal";
const ONE_BY_ONE_DELAY_MS = 1000;
const AUTO_PING_SETTINGS_KEYS = {
claude: "claudeAutoPing",
codex: "codexAutoPing",
};
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@@ -273,7 +278,8 @@ export default function ProviderDetailPage() {
// Load per-provider thinking config
const thinkingCfg = (settingsData.providerThinking || {})[providerId] || {};
setThinkingMode(thinkingCfg.mode || "auto");
const apCfg = settingsData.claudeAutoPing || {};
const autoPingSettingsKey = AUTO_PING_SETTINGS_KEYS[providerId];
const apCfg = autoPingSettingsKey ? settingsData[autoPingSettingsKey] || {} : {};
setAutoPing({ enabled: apCfg.enabled === true, connections: apCfg.connections || {} });
if (nodesRes.ok) {
let node = (nodesData.nodes || []).find((entry) => entry.id === providerId) || null;
@@ -388,12 +394,15 @@ export default function ProviderDetailPage() {
};
const saveAutoPing = async (next) => {
const autoPingSettingsKey = AUTO_PING_SETTINGS_KEYS[providerId];
if (!autoPingSettingsKey) return;
setAutoPing(next);
try {
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ claudeAutoPing: next }),
body: JSON.stringify({ [autoPingSettingsKey]: next }),
});
} catch (error) {
console.log("Error saving auto-ping config:", error);
@@ -888,9 +897,10 @@ export default function ProviderDetailPage() {
onMoveUp={() => handleSwapPriority(index, index - 1)}
onMoveDown={() => handleSwapPriority(index, index + 1)}
onToggleActive={(isActive) => handleUpdateConnectionStatus(conn.id, isActive)}
autoPing={providerId === "claude" && conn.authType === "oauth" ? {
autoPing={AUTO_PING_SETTINGS_KEYS[providerId] && conn.authType === "oauth" ? {
on: autoPing.connections[conn.id] === true,
onToggle: (on) => handleAutoPingConnection(conn.id, on),
provider: providerId,
} : null}
onUpdateProxy={async (proxyPoolId) => {
try {