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:
@@ -129,7 +129,8 @@ export const PROVIDERS = {
|
||||
},
|
||||
"vercel-ai-gateway": {
|
||||
baseUrl: "https://ai-gateway.vercel.sh/v1/chat/completions",
|
||||
format: "openai"
|
||||
format: "openai",
|
||||
retry: { 429: 2 }
|
||||
},
|
||||
glm: {
|
||||
baseUrl: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
|
||||
@@ -6,6 +6,7 @@ import openaiCompatNode from "./openaiCompatNode.js";
|
||||
const OPENAI_COMPAT_PROVIDERS = [
|
||||
"openai", "openrouter", "mistral", "voyage-ai", "fireworks",
|
||||
"together", "nebius", "github", "nvidia", "jina-ai",
|
||||
"vercel-ai-gateway",
|
||||
];
|
||||
|
||||
const ADAPTERS = {
|
||||
|
||||
@@ -12,6 +12,7 @@ const ENDPOINTS = {
|
||||
github: "https://models.github.ai/inference/embeddings",
|
||||
nvidia: "https://integrate.api.nvidia.com/v1/embeddings",
|
||||
"jina-ai": "https://api.jina.ai/v1/embeddings",
|
||||
"vercel-ai-gateway": "https://ai-gateway.vercel.sh/v1/embeddings",
|
||||
};
|
||||
|
||||
export default function createOpenAIEmbeddingAdapter(providerId) {
|
||||
|
||||
@@ -17,6 +17,7 @@ const ADAPTERS = {
|
||||
minimax: createOpenAIAdapter("minimax"),
|
||||
openrouter: createOpenAIAdapter("openrouter"),
|
||||
recraft: createOpenAIAdapter("recraft"),
|
||||
"vercel-ai-gateway": createOpenAIAdapter("vercel-ai-gateway"),
|
||||
xai: createOpenAIAdapter("xai"),
|
||||
gemini,
|
||||
codex,
|
||||
|
||||
@@ -5,6 +5,7 @@ const ENDPOINTS = {
|
||||
minimax: "https://api.minimaxi.com/v1/images/generations",
|
||||
openrouter: "https://openrouter.ai/api/v1/images/generations",
|
||||
recraft: "https://external.api.recraft.ai/v1/images/generations",
|
||||
"vercel-ai-gateway": "https://ai-gateway.vercel.sh/v1/images/generations",
|
||||
xai: "https://api.x.ai/v1/images/generations",
|
||||
};
|
||||
|
||||
|
||||
@@ -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