| |
| import "open-sse/index.js"; |
|
|
| import { getSettings, getProviderConnections, updateProviderConnection } from "@/lib/localDb"; |
| import { getClaudeUsage } from "open-sse/services/usage/claude.js"; |
| import { CLAUDE_CLI_SPOOF_HEADERS } from "open-sse/providers/shared.js"; |
| import { proxyAwareFetch } from "open-sse/utils/proxyFetch.js"; |
| import { resolveConnectionProxyConfig } from "@/lib/network/connectionProxy"; |
| import { refreshAndUpdateCredentials } from "@/app/api/usage/[connectionId]/route.js"; |
| import { CLAUDE_AUTOPING_CONFIG } from "@/shared/constants/config"; |
|
|
| const C = CLAUDE_AUTOPING_CONFIG; |
| const PING_URL = "https://api.anthropic.com/v1/messages?beta=true"; |
|
|
| const g = (global.__claudeAutoPing ??= { interval: null, running: false, resetCache: {} }); |
|
|
| function buildProxyOptions(cfg) { |
| return { |
| connectionProxyEnabled: cfg.connectionProxyEnabled === true, |
| connectionProxyUrl: cfg.connectionProxyUrl || "", |
| connectionNoProxy: cfg.connectionNoProxy || "", |
| vercelRelayUrl: cfg.vercelRelayUrl || "", |
| strictProxy: false, |
| }; |
| } |
|
|
| |
| async function sendPing(accessToken, proxyOptions) { |
| const res = await proxyAwareFetch(PING_URL, { |
| method: "POST", |
| headers: { |
| ...CLAUDE_CLI_SPOOF_HEADERS, |
| "Authorization": `Bearer ${accessToken}`, |
| "content-type": "application/json", |
| }, |
| body: JSON.stringify({ |
| model: C.pingModel, |
| max_tokens: C.pingMaxTokens, |
| messages: [{ role: "user", content: C.pingText }], |
| }), |
| }, proxyOptions); |
| return res.ok; |
| } |
|
|
| async function pingConnection(conn) { |
| |
| const cachedReset = g.resetCache[conn.id]; |
| if (cachedReset && Date.now() < new Date(cachedReset).getTime() - C.refreshAheadMs) return; |
|
|
| const proxyCfg = await resolveConnectionProxyConfig(conn.providerSpecificData); |
| const proxyOptions = buildProxyOptions(proxyCfg); |
|
|
| |
| let connection = conn; |
| try { |
| const r = await refreshAndUpdateCredentials(connection, false, proxyOptions); |
| connection = r.connection; |
| } catch (e) { |
| console.warn(`[AutoPing] ${conn.id}: refresh failed: ${e.message}`); |
| return; |
| } |
|
|
| const usage = await getClaudeUsage(connection.accessToken, proxyOptions); |
| const resetAt = usage?.quotas?.[C.fiveHourKey]?.resetAt; |
| if (!resetAt) return; |
|
|
| |
| g.resetCache[conn.id] = resetAt; |
|
|
| const resetMs = new Date(resetAt).getTime(); |
| const now = Date.now(); |
|
|
| |
| if (now < resetMs - C.pingLeadMs) return; |
| if (connection.lastPingedResetAt === resetAt) return; |
|
|
| const ok = await sendPing(connection.accessToken, proxyOptions); |
| await updateProviderConnection(connection.id, { |
| lastPingedResetAt: resetAt, |
| lastPingAt: new Date().toISOString(), |
| updatedAt: new Date().toISOString(), |
| }); |
| console.log(`[AutoPing] ${connection.id}: ping ${ok ? "sent" : "failed"} (reset ${resetAt})`); |
| } |
|
|
| async function tick() { |
| if (g.running) return; |
| g.running = true; |
| try { |
| const settings = await getSettings(); |
| const enabledMap = settings[C.settingsKey]?.connections || {}; |
| if (Object.keys(enabledMap).length === 0) return; |
|
|
| const conns = await getProviderConnections({ provider: "claude", isActive: true }); |
| |
| const targets = conns.filter((c) => c.authType === "oauth" && enabledMap[c.id] === true); |
| if (targets.length === 0) return; |
|
|
| for (const conn of targets) { |
| try { |
| await pingConnection(conn); |
| } catch (e) { |
| console.warn(`[AutoPing] ${conn.id}: ${e.message}`); |
| } |
| } |
| } catch (e) { |
| console.warn("[AutoPing] tick error:", e.message); |
| } finally { |
| g.running = false; |
| } |
| } |
|
|
| export function startClaudeAutoPing() { |
| if (g.interval) return; |
| g.interval = setInterval(() => { tick().catch(() => {}); }, C.tickIntervalMs); |
| if (g.interval.unref) g.interval.unref(); |
| } |
|
|