mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
feat(vercel-ai-gateway): support embeddings, images and credit usage
Extend Vercel AI Gateway beyond chat: add OpenAI-compatible embeddings and image generation endpoints, credit balance fetch on the usage dashboard, retry on 429, and models catalog fetcher. Thinking/reasoning mapping is omitted pending a project-wide refactor. Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
decolua
co-authored by
Cursor
parent
d9b030011f
commit
b33cbb0280
@@ -30,6 +30,11 @@ const MINIMAX_USAGE_URLS = {
|
||||
],
|
||||
};
|
||||
|
||||
// Vercel AI Gateway credits endpoint
|
||||
// Returns { balance: "95.50", total_used: "4.50" } (USD as decimal strings).
|
||||
// Docs: https://vercel.com/docs/ai-gateway/usage
|
||||
const VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
||||
|
||||
// Antigravity API config (from Quotio)
|
||||
const ANTIGRAVITY_CONFIG = {
|
||||
quotaApiUrl: "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
||||
@@ -92,6 +97,8 @@ export async function getUsageForProvider(connection, proxyOptions = null) {
|
||||
case "minimax":
|
||||
case "minimax-cn":
|
||||
return await getMiniMaxUsage(apiKey, provider, proxyOptions);
|
||||
case "vercel-ai-gateway":
|
||||
return await getVercelAiGatewayUsage(apiKey, proxyOptions);
|
||||
default:
|
||||
return { message: `Usage API not implemented for ${provider}` };
|
||||
}
|
||||
@@ -1203,6 +1210,89 @@ async function getMiniMaxUsage(apiKey, provider, proxyOptions = null) {
|
||||
return { message: lastErrorMessage ? `MiniMax connected. Unable to fetch usage: ${lastErrorMessage}` : "MiniMax connected. Unable to fetch usage." };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Vercel AI Gateway usage — credit balance for the API key
|
||||
*
|
||||
* Calls GET /v1/credits which returns:
|
||||
* { "balance": "95.50", "total_used": "4.50" } (USD as decimal strings)
|
||||
*
|
||||
* We surface this as a single "Balance ($)" quota row so the existing
|
||||
* QuotaTable / progress-bar UI can render it. used = total_used,
|
||||
* total = balance + total_used (the original credit allotment), so the
|
||||
* remaining percentage equals balance / total.
|
||||
*
|
||||
* Docs: https://vercel.com/docs/ai-gateway/usage
|
||||
*/
|
||||
async function getVercelAiGatewayUsage(apiKey, proxyOptions = null) {
|
||||
if (!apiKey) {
|
||||
return { message: "Vercel AI Gateway API key not available." };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await proxyAwareFetch(VERCEL_AI_GATEWAY_CREDITS_URL, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
}, proxyOptions);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { message: "Vercel AI Gateway API key invalid or expired." };
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => "");
|
||||
const trimmed = errorText ? `: ${errorText.slice(0, 200)}` : "";
|
||||
return { message: `Vercel AI Gateway credits API error (${response.status})${trimmed}` };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Vercel returns numeric strings; coerce safely.
|
||||
const balance = Number(data?.balance) || 0;
|
||||
const totalUsed = Number(data?.total_used) || 0;
|
||||
|
||||
// Vercel gives $5/month free credit. The API doesn't return the
|
||||
// monthly allocation so we use the known constant as the denominator.
|
||||
const MONTHLY_CREDIT = 5;
|
||||
const remainingPercentage = (balance / MONTHLY_CREDIT) * 100;
|
||||
|
||||
if (balance <= 0 && totalUsed <= 0) {
|
||||
return {
|
||||
plan: "Pay-as-you-go",
|
||||
message: "Vercel AI Gateway connected. No credit allocation found (BYOK or unfunded account).",
|
||||
quotas: {},
|
||||
};
|
||||
}
|
||||
|
||||
// "Used (USD)": how much has been spent this month (no fixed cap → unlimited).
|
||||
// "Remaining (USD)": balance remaining out of the $5 monthly allocation.
|
||||
return {
|
||||
plan: "Pay-as-you-go",
|
||||
quotas: {
|
||||
"Used (USD)": {
|
||||
used: totalUsed,
|
||||
total: 0,
|
||||
remaining: 0,
|
||||
remainingPercentage: 100,
|
||||
unlimited: true,
|
||||
},
|
||||
"Remaining (USD)": {
|
||||
used: balance,
|
||||
total: MONTHLY_CREDIT,
|
||||
remaining: balance,
|
||||
remainingPercentage,
|
||||
unlimited: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return { message: `Vercel AI Gateway error: ${error.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
async function getQoderUsage(accessToken, proxyOptions = null) {
|
||||
if (!accessToken) {
|
||||
return { message: "Qoder usage unavailable: no access token" };
|
||||
|
||||
Reference in New Issue
Block a user