| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import http from 'http'; |
| import https from 'https'; |
| import { log } from './config.js'; |
|
|
| const SERVER_HOSTS = [ |
| 'server.codeium.com', |
| 'server.self-serve.windsurf.com', |
| ]; |
| const USER_STATUS_PATH = '/exa.seat_management_pb.SeatManagementService/GetUserStatus'; |
| const MODEL_CONFIGS_PATH = '/exa.api_server_pb.ApiServerService/GetCascadeModelConfigs'; |
| const RATE_LIMIT_PATH = '/exa.api_server_pb.ApiServerService/CheckUserMessageRateLimit'; |
|
|
| import { isSocks, createSocksTunnel } from './socks.js'; |
|
|
| |
| function createProxyTunnel(proxy, targetHost, targetPort) { |
| if (isSocks(proxy)) return createSocksTunnel(proxy, targetHost, targetPort); |
| return new Promise((resolve, reject) => { |
| const proxyHost = proxy.host.replace(/:\d+$/, ''); |
| const proxyPort = proxy.port || 8080; |
| const req = http.request({ |
| host: proxyHost, |
| port: proxyPort, |
| method: 'CONNECT', |
| path: `${targetHost}:${targetPort}`, |
| headers: { |
| Host: `${targetHost}:${targetPort}`, |
| ...(proxy.username ? { |
| 'Proxy-Authorization': `Basic ${Buffer.from(`${proxy.username}:${proxy.password || ''}`).toString('base64')}`, |
| } : {}), |
| }, |
| }); |
| req.on('connect', (res, socket) => { |
| if (res.statusCode === 200) resolve(socket); |
| else { socket.destroy(); reject(new Error(`Proxy CONNECT failed: ${res.statusCode}`)); } |
| }); |
| req.on('error', (err) => reject(new Error(`Proxy tunnel: ${err.message}`))); |
| req.setTimeout(15000, () => { req.destroy(); reject(new Error('Proxy tunnel timeout')); }); |
| req.end(); |
| }); |
| } |
|
|
| |
| function isProxyError(err) { |
| const m = err?.message || ''; |
| return /Proxy CONNECT failed|Proxy tunnel|Proxy connection/i.test(m); |
| } |
|
|
| function postJson(host, path, body, proxy) { |
| return new Promise(async (resolve, reject) => { |
| const postData = JSON.stringify(body); |
| const opts = { |
| hostname: host, |
| port: 443, |
| path, |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| 'Content-Length': Buffer.byteLength(postData), |
| 'Connect-Protocol-Version': '1', |
| 'Accept': 'application/json', |
| 'User-Agent': 'windsurf/1.9600.41', |
| }, |
| }; |
| const onRes = (res) => { |
| const bufs = []; |
| res.on('data', d => bufs.push(d)); |
| res.on('end', () => { |
| const raw = Buffer.concat(bufs).toString('utf8'); |
| try { |
| const parsed = raw ? JSON.parse(raw) : {}; |
| resolve({ status: res.statusCode, data: parsed, raw }); |
| } catch { |
| reject(new Error(`Non-JSON response (${res.statusCode}): ${raw.slice(0, 200)}`)); |
| } |
| }); |
| res.on('error', reject); |
| }; |
| try { |
| let req; |
| if (proxy && proxy.host) { |
| const socket = await createProxyTunnel(proxy, host, 443); |
| opts.socket = socket; |
| opts.agent = false; |
| req = https.request(opts, onRes); |
| } else { |
| req = https.request(opts, onRes); |
| } |
| req.on('error', (err) => reject(new Error(`Request: ${err.message}`))); |
| req.setTimeout(20000, () => { req.destroy(); reject(new Error('Request timeout')); }); |
| req.write(postData); |
| req.end(); |
| } catch (err) { reject(err); } |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function getUserStatus(apiKey, proxy = null) { |
| const body = { |
| metadata: { |
| apiKey, |
| ideName: 'windsurf', |
| ideVersion: '1.9600.41', |
| extensionName: 'windsurf', |
| extensionVersion: '1.9600.41', |
| locale: 'en', |
| }, |
| }; |
|
|
| |
| const proxyModes = proxy ? [proxy, null] : [null]; |
| let lastErr = null; |
| for (const px of proxyModes) { |
| for (const host of SERVER_HOSTS) { |
| try { |
| const res = await postJson(host, USER_STATUS_PATH, body, px); |
| if (res.status >= 400) { |
| lastErr = new Error(`GetUserStatus ${host} → ${res.status}: ${res.raw.slice(0, 160)}`); |
| continue; |
| } |
| return normalizeUserStatus(res.data); |
| } catch (e) { |
| lastErr = e; |
| log.debug(`getCreditUsage ${host} failed: ${e.message}`); |
| if (px && isProxyError(e)) break; |
| } |
| } |
| } |
| throw lastErr || new Error('GetUserStatus: all hosts failed'); |
| } |
|
|
| function normalizeUserStatus(data) { |
| const ps = data?.userStatus?.planStatus || {}; |
| const plan = ps.planInfo || {}; |
|
|
| |
| const legacyDiv = (n) => (typeof n === 'number' ? n / 100 : null); |
|
|
| |
| const asUnix = (v) => { |
| if (v == null) return null; |
| if (typeof v === 'number') return v; |
| const n = parseInt(v, 10); |
| return Number.isFinite(n) ? n : null; |
| }; |
|
|
| const out = { |
| planName: plan.planName || 'Unknown', |
| dailyPercent: typeof ps.dailyQuotaRemainingPercent === 'number' ? ps.dailyQuotaRemainingPercent : null, |
| weeklyPercent: typeof ps.weeklyQuotaRemainingPercent === 'number' ? ps.weeklyQuotaRemainingPercent : null, |
| dailyResetAt: asUnix(ps.dailyQuotaResetAtUnix), |
| weeklyResetAt: asUnix(ps.weeklyQuotaResetAtUnix), |
| overageBalance: typeof ps.overageBalanceMicros === 'number' ? ps.overageBalanceMicros / 1_000_000 : null, |
| prompt: { |
| limit: legacyDiv(plan.monthlyPromptCredits), |
| used: legacyDiv(ps.usedPromptCredits), |
| remaining: legacyDiv(ps.availablePromptCredits), |
| }, |
| flex: { |
| limit: legacyDiv(plan.monthlyFlexCreditPurchaseAmount), |
| used: legacyDiv(ps.usedFlexCredits), |
| remaining: legacyDiv(ps.availableFlexCredits), |
| }, |
| planStart: ps.planStart || null, |
| planEnd: ps.planEnd || null, |
| |
| |
| raw: data, |
| fetchedAt: Date.now(), |
| }; |
|
|
| |
| |
| if (out.dailyPercent != null) { |
| out.percent = out.dailyPercent; |
| } else if (out.prompt.limit && out.prompt.remaining != null) { |
| out.percent = (out.prompt.remaining / out.prompt.limit) * 100; |
| } else { |
| out.percent = null; |
| } |
|
|
| return out; |
| } |
|
|
| |
|
|
| function buildMetadata(apiKey) { |
| return { |
| apiKey, |
| ideName: 'windsurf', |
| ideVersion: '1.9600.41', |
| extensionName: 'windsurf', |
| extensionVersion: '1.9600.41', |
| locale: 'en', |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function getCascadeModelConfigs(apiKey, proxy = null) { |
| const body = { metadata: buildMetadata(apiKey) }; |
|
|
| const proxyModes = proxy ? [proxy, null] : [null]; |
| let lastErr = null; |
| for (const px of proxyModes) { |
| for (const host of SERVER_HOSTS) { |
| try { |
| const res = await postJson(host, MODEL_CONFIGS_PATH, body, px); |
| if (res.status >= 400) { |
| lastErr = new Error(`GetCascadeModelConfigs ${host} → ${res.status}: ${res.raw.slice(0, 160)}`); |
| continue; |
| } |
| return { |
| configs: res.data.clientModelConfigs || [], |
| sorts: res.data.clientModelSorts || [], |
| defaultOverride: res.data.defaultOverrideModelConfig || null, |
| }; |
| } catch (e) { |
| lastErr = e; |
| log.debug(`GetCascadeModelConfigs host ${host} failed: ${e.message}`); |
| if (px && isProxyError(e)) break; |
| } |
| } |
| } |
| throw lastErr || new Error('GetCascadeModelConfigs: all hosts failed'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function registerWithFirebaseToken(firebaseToken, opts = {}) { |
| if (!firebaseToken || typeof firebaseToken !== 'string') { |
| throw new Error('registerWithFirebaseToken: firebase token required'); |
| } |
| const body = { firebase_id_token: firebaseToken }; |
| const bodyStr = JSON.stringify(body); |
| const proxy = opts.proxy || null; |
|
|
| |
| const newUrl = 'https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser'; |
| |
| const legacyUrl = 'https://api.codeium.com/register_user/'; |
|
|
| const tryUrl = async (url, source) => { |
| if (typeof opts.requestFn === 'function') { |
| const headers = { |
| 'Content-Type': 'application/json', |
| 'Content-Length': Buffer.byteLength(bodyStr), |
| 'Connect-Protocol-Version': '1', |
| 'Accept': 'application/json', |
| 'User-Agent': 'windsurf/1.9600.41', |
| }; |
| const r = await opts.requestFn(url, { method: 'POST', headers }, bodyStr); |
| return { status: r.status, data: r.data, raw: r.raw, source }; |
| } |
| |
| const u = new URL(url); |
| const r = await postJson(u.hostname, u.pathname, body, proxy); |
| return { status: r.status, data: r.data, raw: r.raw, source }; |
| }; |
|
|
| const errors = []; |
| for (const [url, source] of [[newUrl, 'new'], [legacyUrl, 'legacy']]) { |
| try { |
| const r = await tryUrl(url, source); |
| |
| |
| const apiKey = r.data?.api_key || r.data?.apiKey; |
| const name = r.data?.name || ''; |
| const apiServerUrl = r.data?.api_server_url || r.data?.apiServerUrl || ''; |
| if (r.status < 400 && apiKey) { |
| if (source === 'legacy') { |
| log.warn(`RegisterUser fell back to legacy api.codeium.com (new endpoint failed)`); |
| } else { |
| log.info(`RegisterUser via register.windsurf.com OK (key=${apiKey.slice(0, 12)}...)`); |
| } |
| return { apiKey, name, apiServerUrl, source }; |
| } |
| errors.push(`${source}=HTTP ${r.status} ${r.raw?.slice(0, 120) || '(empty)'}`); |
| } catch (e) { |
| errors.push(`${source}=${e.message}`); |
| } |
| } |
| throw new Error(`RegisterUser failed both endpoints: ${errors.join(' | ')}`); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function checkMessageRateLimit(apiKey, proxy = null) { |
| const body = { metadata: buildMetadata(apiKey) }; |
|
|
| const proxyModes = proxy ? [proxy, null] : [null]; |
| let lastErr = null; |
| for (const px of proxyModes) { |
| for (const host of SERVER_HOSTS) { |
| try { |
| const res = await postJson(host, RATE_LIMIT_PATH, body, px); |
| if (res.status >= 400) { |
| lastErr = new Error(`CheckRateLimit ${host} → ${res.status}: ${res.raw.slice(0, 160)}`); |
| continue; |
| } |
| return { |
| hasCapacity: res.data.hasCapacity !== false, |
| messagesRemaining: res.data.messagesRemaining ?? -1, |
| maxMessages: res.data.maxMessages ?? -1, |
| retryAfterMs: Number.isFinite(res.data.retryAfterMs) ? res.data.retryAfterMs : null, |
| }; |
| } catch (e) { |
| lastErr = e; |
| log.debug(`CheckRateLimit host ${host} failed: ${e.message}`); |
| if (px && isProxyError(e)) break; |
| } |
| } |
| } |
| |
| log.warn(`CheckRateLimit failed: ${lastErr?.message}`); |
| return { hasCapacity: true, messagesRemaining: -1, maxMessages: -1, retryAfterMs: null }; |
| } |
|
|