2026-01-04 22:37:09 -05:00
/ * *
* Usage Fetcher - Get usage data from provider APIs
* /
2026-03-12 05:20:46 -04:00
import { CLIENT _METADATA , getPlatformUserAgent } from "../config/appConstants.js" ;
2026-04-28 06:28:57 -04:00
import { proxyAwareFetch } from "../utils/proxyFetch.js" ;
2026-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
// GitHub API config
const GITHUB _CONFIG = {
apiVersion : "2022-11-28" ,
userAgent : "GitHubCopilotChat/0.26.7" ,
} ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
// GLM quota endpoints (region-aware)
const GLM _QUOTA _URLS = {
international : "https://api.z.ai/api/monitor/usage/quota/limit" ,
china : "https://open.bigmodel.cn/api/monitor/usage/quota/limit" ,
} ;
// MiniMax usage endpoints (try in order, fallback on transient errors)
const MINIMAX _USAGE _URLS = {
minimax : [
"https://www.minimax.io/v1/token_plan/remains" ,
"https://api.minimax.io/v1/api/openplatform/coding_plan/remains" ,
] ,
"minimax-cn" : [
"https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains" ,
"https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains" ,
] ,
} ;
2026-01-04 22:37:09 -05:00
// Antigravity API config (from Quotio)
const ANTIGRAVITY _CONFIG = {
quotaApiUrl : "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels" ,
loadProjectApiUrl : "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist" ,
tokenUrl : "https://oauth2.googleapis.com/token" ,
clientId : "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com" ,
clientSecret : "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" ,
2026-02-20 02:44:29 -05:00
userAgent : getPlatformUserAgent ( ) ,
2026-01-04 22:37:09 -05:00
} ;
// Codex (OpenAI) API config
const CODEX _CONFIG = {
usageUrl : "https://chatgpt.com/backend-api/wham/usage" ,
} ;
// Claude API config
const CLAUDE _CONFIG = {
2026-03-11 22:42:17 -04:00
oauthUsageUrl : "https://api.anthropic.com/api/oauth/usage" ,
2026-01-04 22:37:09 -05:00
usageUrl : "https://api.anthropic.com/v1/organizations/{org_id}/usage" ,
settingsUrl : "https://api.anthropic.com/v1/settings" ,
2026-03-11 22:42:17 -04:00
apiVersion : "2023-06-01" ,
2026-01-04 22:37:09 -05:00
} ;
/ * *
* Get usage data for a provider connection
* @ param { Object } connection - Provider connection with accessToken
* @ returns { Object } Usage data with quotas
* /
2026-04-28 06:28:57 -04:00
export async function getUsageForProvider ( connection , proxyOptions = null ) {
2026-05-26 00:23:47 -04:00
const { provider , accessToken , apiKey , providerSpecificData , projectId } = connection ;
const providerDataWithProjectId = {
... ( providerSpecificData || { } ) ,
... ( projectId ? { projectId } : { } ) ,
} ;
2026-01-04 22:37:09 -05:00
switch ( provider ) {
case "github" :
2026-04-28 06:28:57 -04:00
return await getGitHubUsage ( accessToken , providerSpecificData , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
case "gemini-cli" :
2026-05-26 00:23:47 -04:00
return await getGeminiUsage ( accessToken , providerDataWithProjectId , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
case "antigravity" :
2026-04-28 06:28:57 -04:00
return await getAntigravityUsage ( accessToken , providerSpecificData , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
case "claude" :
2026-04-28 06:28:57 -04:00
return await getClaudeUsage ( accessToken , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
case "codex" :
2026-04-28 06:28:57 -04:00
return await getCodexUsage ( accessToken , proxyOptions ) ;
2026-02-03 21:54:11 -05:00
case "kiro" :
2026-04-28 06:28:57 -04:00
return await getKiroUsage ( accessToken , providerSpecificData , proxyOptions ) ;
2026-05-23 03:31:50 -04:00
case "qoder" :
return await getQoderUsage ( accessToken , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
case "qwen" :
return await getQwenUsage ( accessToken , providerSpecificData ) ;
case "iflow" :
return await getIflowUsage ( accessToken ) ;
2026-04-21 23:32:28 -04:00
case "ollama" :
return await getOllamaUsage ( accessToken ) ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
case "glm" :
case "glm-cn" :
return await getGlmUsage ( apiKey , provider , proxyOptions ) ;
case "minimax" :
case "minimax-cn" :
return await getMiniMaxUsage ( apiKey , provider , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
default :
return { message : ` Usage API not implemented for ${ provider } ` } ;
}
}
2026-02-05 23:23:08 -05:00
/ * *
* Parse reset date / time to ISO string
* Handles multiple formats : Unix timestamp ( ms ) , ISO date string , etc .
* /
function parseResetTime ( resetValue ) {
if ( ! resetValue ) return null ;
2026-02-20 02:44:29 -05:00
2026-02-05 23:23:08 -05:00
try {
// If it's already a Date object
if ( resetValue instanceof Date ) {
return resetValue . toISOString ( ) ;
}
2026-02-20 02:44:29 -05:00
2026-04-27 23:05:54 -04:00
// Unix timestamps from provider APIs may be seconds or milliseconds.
2026-02-05 23:23:08 -05:00
if ( typeof resetValue === 'number' ) {
2026-04-27 23:05:54 -04:00
return new Date ( resetValue < 1e12 ? resetValue * 1000 : resetValue ) . toISOString ( ) ;
2026-02-05 23:23:08 -05:00
}
2026-02-20 02:44:29 -05:00
2026-04-27 23:05:54 -04:00
// If it's a numeric string, treat it like a Unix timestamp too.
2026-02-05 23:23:08 -05:00
if ( typeof resetValue === 'string' ) {
2026-04-27 23:05:54 -04:00
if ( /^\d+$/ . test ( resetValue ) ) {
const timestamp = Number ( resetValue ) ;
return new Date ( timestamp < 1e12 ? timestamp * 1000 : timestamp ) . toISOString ( ) ;
}
2026-02-05 23:23:08 -05:00
return new Date ( resetValue ) . toISOString ( ) ;
}
2026-02-20 02:44:29 -05:00
2026-02-05 23:23:08 -05:00
return null ;
} catch ( error ) {
console . warn ( ` Failed to parse reset time: ${ resetValue } ` , error ) ;
return null ;
}
}
2026-01-04 22:37:09 -05:00
/ * *
* GitHub Copilot Usage
2026-02-05 06:38:50 -05:00
* Uses GitHub accessToken ( not copilotToken ) to call copilot _internal / user API
2026-01-04 22:37:09 -05:00
* /
2026-04-28 06:28:57 -04:00
async function getGitHubUsage ( accessToken , providerSpecificData , proxyOptions = null ) {
2026-01-04 22:37:09 -05:00
try {
2026-02-05 06:38:50 -05:00
if ( ! accessToken ) {
throw new Error ( "No GitHub access token available. Please re-authorize the connection." ) ;
}
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
// copilot_internal/user API requires GitHub OAuth token, not copilotToken
2026-04-28 06:28:57 -04:00
const response = await proxyAwareFetch ( "https://api.github.com/copilot_internal/user" , {
2026-01-04 22:37:09 -05:00
headers : {
2026-02-05 06:38:50 -05:00
"Authorization" : ` token ${ accessToken } ` ,
2026-01-04 22:37:09 -05:00
"Accept" : "application/json" ,
"X-GitHub-Api-Version" : GITHUB _CONFIG . apiVersion ,
"User-Agent" : GITHUB _CONFIG . userAgent ,
2026-02-05 06:38:50 -05:00
"Editor-Version" : "vscode/1.100.0" ,
"Editor-Plugin-Version" : "copilot-chat/0.26.7" ,
2026-01-04 22:37:09 -05:00
} ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
if ( ! response . ok ) {
const error = await response . text ( ) ;
throw new Error ( ` GitHub API error: ${ error } ` ) ;
}
const data = await response . json ( ) ;
// Handle different response formats (paid vs free)
if ( data . quota _snapshots ) {
// Paid plan format
const snapshots = data . quota _snapshots ;
2026-02-05 23:23:08 -05:00
const resetAt = parseResetTime ( data . quota _reset _date ) ;
2026-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
return {
plan : data . copilot _plan ,
resetDate : data . quota _reset _date ,
quotas : {
2026-02-05 23:23:08 -05:00
chat : { ... formatGitHubQuotaSnapshot ( snapshots . chat ) , resetAt } ,
completions : { ... formatGitHubQuotaSnapshot ( snapshots . completions ) , resetAt } ,
premium _interactions : { ... formatGitHubQuotaSnapshot ( snapshots . premium _interactions ) , resetAt } ,
2026-01-04 22:37:09 -05:00
} ,
} ;
} else if ( data . monthly _quotas || data . limited _user _quotas ) {
// Free/limited plan format
const monthlyQuotas = data . monthly _quotas || { } ;
const usedQuotas = data . limited _user _quotas || { } ;
2026-02-05 23:23:08 -05:00
const resetAt = parseResetTime ( data . limited _user _reset _date ) ;
2026-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
return {
plan : data . copilot _plan || data . access _type _sku ,
resetDate : data . limited _user _reset _date ,
quotas : {
chat : {
used : usedQuotas . chat || 0 ,
total : monthlyQuotas . chat || 0 ,
unlimited : false ,
2026-02-05 23:23:08 -05:00
resetAt ,
2026-01-04 22:37:09 -05:00
} ,
completions : {
used : usedQuotas . completions || 0 ,
total : monthlyQuotas . completions || 0 ,
unlimited : false ,
2026-02-05 23:23:08 -05:00
resetAt ,
2026-01-04 22:37:09 -05:00
} ,
} ,
} ;
}
return { message : "GitHub Copilot connected. Unable to parse quota data." } ;
} catch ( error ) {
throw new Error ( ` Failed to fetch GitHub usage: ${ error . message } ` ) ;
}
}
function formatGitHubQuotaSnapshot ( quota ) {
if ( ! quota ) return { used : 0 , total : 0 , unlimited : true } ;
2026-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
return {
used : quota . entitlement - quota . remaining ,
total : quota . entitlement ,
remaining : quota . remaining ,
unlimited : quota . unlimited || false ,
} ;
}
/ * *
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
* Gemini CLI Usage — fetch per - model quota via Cloud Code Assist API .
* Uses retrieveUserQuota ( same endpoint as ` gemini /stats ` ) returning
* per - model buckets with remainingFraction + resetTime .
2026-01-04 22:37:09 -05:00
* /
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
async function getGeminiUsage ( accessToken , providerSpecificData , proxyOptions = null ) {
if ( ! accessToken ) {
return { plan : "Free" , message : "Gemini CLI access token not available." } ;
}
try {
2026-05-26 00:23:47 -04:00
// Resolve project id: prefer connection-stored id, else loadCodeAssist lookup.
// #1271: OAuth save stores projectId on the connection, not providerSpecificData.
let projectId = normalizeCloudCodeProjectId ( providerSpecificData ? . projectId ) ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
let plan = "Free" ;
if ( ! projectId ) {
const subInfo = await getGeminiSubscriptionInfo ( accessToken , proxyOptions ) ;
2026-05-26 00:23:47 -04:00
projectId = normalizeCloudCodeProjectId ( subInfo ? . cloudaicompanionProject ) ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
plan = subInfo ? . currentTier ? . name || plan ;
}
if ( ! projectId ) {
2026-05-26 00:23:47 -04:00
return {
plan ,
message : "Gemini CLI project ID not available. Reconnect Gemini CLI, or configure a Google Cloud project with Gemini Code Assist access before checking quota." ,
} ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
}
const controller = new AbortController ( ) ;
const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ;
let response ;
try {
response = await proxyAwareFetch (
"https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota" ,
{
method : "POST" ,
headers : {
Authorization : ` Bearer ${ accessToken } ` ,
"Content-Type" : "application/json" ,
} ,
body : JSON . stringify ( { project : projectId } ) ,
signal : controller . signal ,
} ,
proxyOptions
) ;
} finally {
clearTimeout ( timeoutId ) ;
}
if ( ! response . ok ) {
return { plan , message : ` Gemini CLI quota error ( ${ response . status } ). ` } ;
}
const data = await response . json ( ) ;
const quotas = { } ;
if ( Array . isArray ( data . buckets ) ) {
for ( const bucket of data . buckets ) {
if ( ! bucket . modelId || bucket . remainingFraction == null ) continue ;
const remainingFraction = Number ( bucket . remainingFraction ) || 0 ;
const total = 1000 ; // Normalized base, matches antigravity convention
const remaining = Math . round ( total * remainingFraction ) ;
const used = Math . max ( 0 , total - remaining ) ;
quotas [ bucket . modelId ] = {
used ,
total ,
resetAt : parseResetTime ( bucket . resetTime ) ,
remainingPercentage : remainingFraction * 100 ,
unlimited : false ,
} ;
}
}
return { plan , quotas } ;
} catch ( error ) {
return { message : ` Gemini CLI error: ${ error . message } ` } ;
}
}
2026-05-26 00:23:47 -04:00
function normalizeCloudCodeProjectId ( project ) {
if ( typeof project === "string" ) return project . trim ( ) || null ;
if ( project && typeof project === "object" && typeof project . id === "string" ) {
return project . id . trim ( ) || null ;
}
return null ;
}
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
/ * *
* Get Gemini CLI subscription info via loadCodeAssist
* /
async function getGeminiSubscriptionInfo ( accessToken , proxyOptions = null ) {
const controller = new AbortController ( ) ;
const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ;
2026-01-04 22:37:09 -05:00
try {
2026-04-28 06:28:57 -04:00
const response = await proxyAwareFetch (
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist" ,
2026-01-04 22:37:09 -05:00
{
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
method : "POST" ,
2026-01-04 22:37:09 -05:00
headers : {
Authorization : ` Bearer ${ accessToken } ` ,
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
"Content-Type" : "application/json" ,
2026-01-04 22:37:09 -05:00
} ,
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
body : JSON . stringify ( {
2026-05-17 18:25:33 -04:00
metadata : CLIENT _METADATA ,
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
} ) ,
signal : controller . signal ,
2026-04-28 06:28:57 -04:00
} ,
proxyOptions
2026-01-04 22:37:09 -05:00
) ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
if ( ! response . ok ) return null ;
return await response . json ( ) ;
} catch {
return null ;
} finally {
clearTimeout ( timeoutId ) ;
2026-01-04 22:37:09 -05:00
}
}
/ * *
* Antigravity Usage - Fetch quota from Google Cloud Code API
* /
2026-04-28 06:28:57 -04:00
async function getAntigravityUsage ( accessToken , providerSpecificData , proxyOptions = null ) {
2026-01-04 22:37:09 -05:00
try {
2026-03-13 22:37:29 -04:00
// Fetch subscription info once — reuse for both projectId and plan
2026-04-28 06:28:57 -04:00
const subscriptionInfo = await getAntigravitySubscriptionInfo ( accessToken , proxyOptions ) ;
2026-03-13 22:37:29 -04:00
const projectId = subscriptionInfo ? . cloudaicompanionProject || null ;
2026-02-20 02:44:29 -05:00
2026-02-28 00:10:55 -05:00
// Fetch quota data with timeout
const controller = new AbortController ( ) ;
const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ; // 10s timeout
2026-03-13 22:37:29 -04:00
let response ;
try {
2026-04-28 06:28:57 -04:00
response = await proxyAwareFetch ( ANTIGRAVITY _CONFIG . quotaApiUrl , {
2026-03-13 22:37:29 -04:00
method : "POST" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"User-Agent" : ANTIGRAVITY _CONFIG . userAgent ,
"Content-Type" : "application/json" ,
"X-Client-Name" : "antigravity" ,
"X-Client-Version" : "1.107.0" ,
"x-request-source" : "local" , // MITM bypass
} ,
body : JSON . stringify ( {
... ( projectId ? { project : projectId } : { } )
} ) ,
signal : controller . signal ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-03-13 22:37:29 -04:00
} finally {
clearTimeout ( timeoutId ) ;
}
2026-01-04 22:37:09 -05:00
if ( response . status === 403 ) {
2026-02-24 23:40:50 -05:00
return {
message : "Antigravity quota API access forbidden. Chat may still work." ,
quotas : { }
} ;
}
if ( response . status === 401 ) {
return {
message : "Antigravity quota API authentication expired. Chat may still work." ,
quotas : { }
} ;
2026-01-04 22:37:09 -05:00
}
if ( ! response . ok ) {
throw new Error ( ` Antigravity API error: ${ response . status } ` ) ;
}
const data = await response . json ( ) ;
const quotas = { } ;
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
// Parse model quotas (inspired by vscode-antigravity-cockpit)
2026-01-04 22:37:09 -05:00
if ( data . models ) {
2026-02-05 06:38:50 -05:00
// Filter only recommended/important models (must match PROVIDER_MODELS ag ids)
const importantModels = [
2026-05-22 22:26:10 -04:00
'gemini-3-flash-agent' ,
'gemini-3.5-flash-low' ,
'gemini-pro-agent' ,
2026-02-22 03:20:24 -05:00
'gemini-3.1-pro-low' ,
2026-05-22 22:26:10 -04:00
'claude-sonnet-4-6' ,
'claude-opus-4-6-thinking' ,
2026-02-22 03:20:24 -05:00
'gpt-oss-120b-medium' ,
2026-05-22 22:26:10 -04:00
'gemini-3-flash' ,
2026-02-05 06:38:50 -05:00
] ;
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
for ( const [ modelKey , info ] of Object . entries ( data . models ) ) {
// Skip models without quota info
if ( ! info . quotaInfo ) {
continue ;
}
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
// Skip internal models and non-important models
if ( info . isInternal || ! importantModels . includes ( modelKey ) ) {
continue ;
2026-01-04 22:37:09 -05:00
}
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
const remainingFraction = info . quotaInfo . remainingFraction || 0 ;
const remainingPercentage = remainingFraction * 100 ;
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
// Convert percentage to used/total for UI compatibility
const total = 1000 ; // Normalized base
const remaining = Math . round ( total * remainingFraction ) ;
const used = total - remaining ;
2026-02-20 02:44:29 -05:00
2026-02-05 06:38:50 -05:00
// Use modelKey as key (matches PROVIDER_MODELS id)
quotas [ modelKey ] = {
used ,
total ,
2026-02-05 23:23:08 -05:00
resetAt : parseResetTime ( info . quotaInfo . resetTime ) ,
2026-02-05 06:38:50 -05:00
remainingPercentage ,
unlimited : false ,
displayName : info . displayName || modelKey ,
} ;
2026-01-04 22:37:09 -05:00
}
}
return {
plan : subscriptionInfo ? . currentTier ? . name || "Unknown" ,
quotas ,
subscriptionInfo ,
} ;
} catch ( error ) {
2026-02-28 00:10:55 -05:00
console . error ( "[Antigravity Usage] Error:" , error . message , error . cause ) ;
2026-01-04 22:37:09 -05:00
return { message : ` Antigravity error: ${ error . message } ` } ;
}
}
/ * *
* Get Antigravity project ID from subscription info
* /
async function getAntigravityProjectId ( accessToken ) {
try {
const info = await getAntigravitySubscriptionInfo ( accessToken ) ;
return info ? . cloudaicompanionProject || null ;
} catch {
return null ;
}
}
/ * *
* Get Antigravity subscription info
* /
2026-04-28 06:28:57 -04:00
async function getAntigravitySubscriptionInfo ( accessToken , proxyOptions = null ) {
2026-03-13 22:37:29 -04:00
const controller = new AbortController ( ) ;
const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ; // 10s timeout
2026-01-04 22:37:09 -05:00
try {
2026-04-28 06:28:57 -04:00
const response = await proxyAwareFetch ( ANTIGRAVITY _CONFIG . loadProjectApiUrl , {
2026-01-04 22:37:09 -05:00
method : "POST" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"User-Agent" : ANTIGRAVITY _CONFIG . userAgent ,
"Content-Type" : "application/json" ,
2026-02-28 00:10:55 -05:00
"x-request-source" : "local" , // MITM bypass
2026-01-04 22:37:09 -05:00
} ,
2026-02-20 02:44:29 -05:00
body : JSON . stringify ( { metadata : CLIENT _METADATA , mode : 1 } ) ,
2026-02-28 00:10:55 -05:00
signal : controller . signal ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
if ( ! response . ok ) return null ;
return await response . json ( ) ;
2026-02-28 00:10:55 -05:00
} catch ( error ) {
console . error ( "[Antigravity Subscription] Error:" , error . message ) ;
2026-01-04 22:37:09 -05:00
return null ;
2026-03-13 22:37:29 -04:00
} finally {
clearTimeout ( timeoutId ) ;
2026-01-04 22:37:09 -05:00
}
}
/ * *
2026-03-11 22:42:17 -04:00
* Claude Usage - Primary : OAuth endpoint , Fallback : legacy settings / org endpoint
2026-01-04 22:37:09 -05:00
* /
2026-04-28 06:28:57 -04:00
async function getClaudeUsage ( accessToken , proxyOptions = null ) {
2026-01-04 22:37:09 -05:00
try {
2026-03-11 22:42:17 -04:00
// Primary: OAuth usage endpoint (Claude Code consumer OAuth tokens)
2026-04-28 06:28:57 -04:00
const oauthResponse = await proxyAwareFetch ( CLAUDE _CONFIG . oauthUsageUrl , {
2026-01-04 22:37:09 -05:00
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
2026-03-11 22:42:17 -04:00
"anthropic-beta" : "oauth-2025-04-20" ,
"anthropic-version" : CLAUDE _CONFIG . apiVersion ,
} ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-03-11 22:42:17 -04:00
if ( oauthResponse . ok ) {
const data = await oauthResponse . json ( ) ;
const quotas = { } ;
// utilization = % USED (e.g. 87 means 87% used, 13% remaining)
const hasUtilization = ( window ) =>
window && typeof window === "object" && typeof window . utilization === "number" ;
const createQuotaObject = ( window ) => {
const used = window . utilization ;
const remaining = Math . max ( 0 , 100 - used ) ;
return {
used ,
total : 100 ,
remaining ,
remainingPercentage : remaining ,
resetAt : parseResetTime ( window . resets _at ) ,
unlimited : false ,
} ;
} ;
if ( hasUtilization ( data . five _hour ) ) {
quotas [ "session (5h)" ] = createQuotaObject ( data . five _hour ) ;
}
if ( hasUtilization ( data . seven _day ) ) {
quotas [ "weekly (7d)" ] = createQuotaObject ( data . seven _day ) ;
}
// Parse model-specific weekly windows (e.g. seven_day_sonnet, seven_day_opus)
for ( const [ key , value ] of Object . entries ( data ) ) {
if ( key . startsWith ( "seven_day_" ) && key !== "seven_day" && hasUtilization ( value ) ) {
const modelName = key . replace ( "seven_day_" , "" ) ;
quotas [ ` weekly ${ modelName } (7d) ` ] = createQuotaObject ( value ) ;
}
}
return {
plan : "Claude Code" ,
extraUsage : data . extra _usage ? ? null ,
quotas ,
} ;
}
// Fallback: legacy settings + org usage endpoint
console . warn ( ` [Claude Usage] OAuth endpoint returned ${ oauthResponse . status } , falling back to legacy ` ) ;
2026-04-28 06:28:57 -04:00
return await getClaudeUsageLegacy ( accessToken , proxyOptions ) ;
2026-03-11 22:42:17 -04:00
} catch ( error ) {
return { message : ` Claude connected. Unable to fetch usage: ${ error . message } ` } ;
}
}
/ * *
* Legacy Claude usage for API key / org admin users
* /
2026-04-28 06:28:57 -04:00
async function getClaudeUsageLegacy ( accessToken , proxyOptions = null ) {
2026-03-11 22:42:17 -04:00
try {
2026-04-28 06:28:57 -04:00
const settingsResponse = await proxyAwareFetch ( CLAUDE _CONFIG . settingsUrl , {
2026-03-11 22:42:17 -04:00
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"anthropic-version" : CLAUDE _CONFIG . apiVersion ,
2026-01-04 22:37:09 -05:00
} ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
if ( settingsResponse . ok ) {
const settings = await settingsResponse . json ( ) ;
2026-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
if ( settings . organization _id ) {
2026-04-28 06:28:57 -04:00
const usageResponse = await proxyAwareFetch (
2026-03-11 22:42:17 -04:00
CLAUDE _CONFIG . usageUrl . replace ( "{org_id}" , settings . organization _id ) ,
2026-01-04 22:37:09 -05:00
{
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
2026-03-11 22:42:17 -04:00
"anthropic-version" : CLAUDE _CONFIG . apiVersion ,
2026-01-04 22:37:09 -05:00
} ,
2026-04-28 06:28:57 -04:00
} ,
proxyOptions
2026-01-04 22:37:09 -05:00
) ;
if ( usageResponse . ok ) {
const usage = await usageResponse . json ( ) ;
return {
plan : settings . plan || "Unknown" ,
organization : settings . organization _name ,
quotas : usage ,
} ;
}
}
return {
plan : settings . plan || "Unknown" ,
organization : settings . organization _name ,
message : "Claude connected. Usage details require admin access." ,
} ;
}
return { message : "Claude connected. Usage API requires admin permissions." } ;
} catch ( error ) {
return { message : ` Claude connected. Unable to fetch usage: ${ error . message } ` } ;
}
}
/ * *
* Codex ( OpenAI ) Usage - Fetch from ChatGPT backend API
* /
2026-05-03 03:57:33 -04:00
function toFiniteNumber ( value , fallback = 0 ) {
if ( typeof value === "number" && Number . isFinite ( value ) ) return value ;
if ( typeof value === "string" && value . trim ( ) ) {
const parsed = Number ( value ) ;
if ( Number . isFinite ( parsed ) ) return parsed ;
}
return fallback ;
}
function getCodexRateLimitBody ( snapshot ) {
if ( ! snapshot || typeof snapshot !== "object" || Array . isArray ( snapshot ) ) return null ;
return snapshot . rate _limit && typeof snapshot . rate _limit === "object"
? snapshot . rate _limit
: snapshot ;
}
function formatCodexWindow ( window ) {
const used = Math . max ( 0 , Math . min ( 100 , toFiniteNumber ( window ? . used _percent ? ? window ? . percent _used , 0 ) ) ) ;
return {
used ,
total : 100 ,
remaining : Math . max ( 0 , 100 - used ) ,
resetAt : parseResetTime ( window ? . reset _at ? ? window ? . resets _at ? ? window ? . resetAt ? ? null ) ,
unlimited : false ,
} ;
}
function appendCodexQuotaWindows ( quotas , prefix , snapshot ) {
const rateLimit = getCodexRateLimitBody ( snapshot ) ;
if ( ! rateLimit ) return false ;
const primary = rateLimit . primary _window || rateLimit . primary || snapshot . primary _window || snapshot . primary ;
const secondary = rateLimit . secondary _window || rateLimit . secondary || snapshot . secondary _window || snapshot . secondary ;
let added = false ;
if ( primary ) {
quotas [ prefix ? ` ${ prefix } _session ` : "session" ] = formatCodexWindow ( primary ) ;
added = true ;
}
if ( secondary ) {
quotas [ prefix ? ` ${ prefix } _weekly ` : "weekly" ] = formatCodexWindow ( secondary ) ;
added = true ;
}
return added ;
}
function getCodexReviewRateLimit ( data ) {
if ( data . code _review _rate _limit || data . review _rate _limit ) {
return data . code _review _rate _limit || data . review _rate _limit ;
}
const byLimitId = data . rate _limits _by _limit _id ;
if ( byLimitId && typeof byLimitId === "object" && ! Array . isArray ( byLimitId ) ) {
return byLimitId . code _review || byLimitId . codex _review || byLimitId . review || null ;
}
const additional = Array . isArray ( data . additional _rate _limits ) ? data . additional _rate _limits : [ ] ;
return additional . find ( ( entry ) => {
const id = String ( entry ? . limit _name || entry ? . metered _feature || entry ? . id || "" ) . toLowerCase ( ) ;
return id === "code_review" || id === "codex_review" || id === "review" || id . includes ( "review" ) ;
} ) || null ;
}
2026-04-28 06:28:57 -04:00
async function getCodexUsage ( accessToken , proxyOptions = null ) {
2026-01-04 22:37:09 -05:00
try {
2026-04-28 06:28:57 -04:00
const response = await proxyAwareFetch ( CODEX _CONFIG . usageUrl , {
2026-01-04 22:37:09 -05:00
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"Accept" : "application/json" ,
} ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-01-04 22:37:09 -05:00
if ( ! response . ok ) {
2026-04-15 00:44:27 -04:00
return { message : ` Codex connected. Usage API temporarily unavailable ( ${ response . status } ). ` } ;
2026-01-04 22:37:09 -05:00
}
const data = await response . json ( ) ;
2026-05-03 03:57:33 -04:00
const normalRateLimit = data . rate _limit || data . rate _limits || data . rate _limits _by _limit _id ? . codex || { } ;
const reviewRateLimit = getCodexReviewRateLimit ( data ) ;
const quotas = { } ;
2026-02-20 02:44:29 -05:00
2026-05-03 03:57:33 -04:00
appendCodexQuotaWindows ( quotas , "" , normalRateLimit ) ;
appendCodexQuotaWindows ( quotas , "review" , reviewRateLimit ) ;
2026-01-04 22:37:09 -05:00
return {
2026-05-03 03:57:33 -04:00
plan : data . plan _type || data . summary ? . plan || "unknown" ,
limitReached : getCodexRateLimitBody ( normalRateLimit ) ? . limit _reached || false ,
reviewLimitReached : getCodexRateLimitBody ( reviewRateLimit ) ? . limit _reached || false ,
quotas ,
2026-01-04 22:37:09 -05:00
} ;
} catch ( error ) {
throw new Error ( ` Failed to fetch Codex usage: ${ error . message } ` ) ;
}
}
2026-02-03 21:54:11 -05:00
/ * *
* Kiro ( AWS CodeWhisperer ) Usage
* /
2026-04-15 00:44:46 -04:00
function parseKiroQuotaData ( data ) {
const usageList = data . usageBreakdownList || [ ] ;
const quotaInfo = { } ;
const resetAt = parseResetTime ( data . nextDateReset || data . resetDate ) ;
usageList . forEach ( ( breakdown ) => {
const resourceType = breakdown . resourceType ? . toLowerCase ( ) || "unknown" ;
const used = breakdown . currentUsageWithPrecision || 0 ;
const total = breakdown . usageLimitWithPrecision || 0 ;
quotaInfo [ resourceType ] = {
used ,
total ,
remaining : total - used ,
resetAt ,
unlimited : false ,
2026-02-03 21:54:11 -05:00
} ;
2026-04-15 00:44:46 -04:00
// Add free trial if available
if ( breakdown . freeTrialInfo ) {
const freeUsed = breakdown . freeTrialInfo . currentUsageWithPrecision || 0 ;
const freeTotal = breakdown . freeTrialInfo . usageLimitWithPrecision || 0 ;
2026-02-20 02:44:29 -05:00
2026-04-15 00:44:46 -04:00
quotaInfo [ ` ${ resourceType } _freetrial ` ] = {
used : freeUsed ,
total : freeTotal ,
remaining : freeTotal - freeUsed ,
resetAt : parseResetTime ( breakdown . freeTrialInfo . freeTrialExpiry || resetAt ) ,
2026-02-03 21:54:11 -05:00
unlimited : false ,
} ;
2026-04-15 00:44:46 -04:00
}
} ) ;
2026-02-03 21:54:11 -05:00
2026-04-15 00:44:46 -04:00
return {
plan : data . subscriptionInfo ? . subscriptionTitle || "Kiro" ,
quotas : quotaInfo ,
} ;
}
2026-03-05 12:21:27 -05:00
2026-04-28 06:28:57 -04:00
async function getKiroUsage ( accessToken , providerSpecificData , proxyOptions = null ) {
2026-04-15 00:44:46 -04:00
// Default profileArn fallback
const DEFAULT _PROFILE _ARN = "arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX" ;
const profileArn = providerSpecificData ? . profileArn || DEFAULT _PROFILE _ARN ;
const authMethod = providerSpecificData ? . authMethod || "builder-id" ;
const getUsageParams = new URLSearchParams ( {
isEmailRequired : "true" ,
origin : "AI_EDITOR" ,
resourceType : "AGENTIC_REQUEST" ,
} ) ;
// For compatibility, try multiple known Kiro usage endpoints
const attempts = [
{
name : "codewhisperer-get" ,
2026-04-28 06:28:57 -04:00
run : async ( ) => proxyAwareFetch (
2026-04-15 00:44:46 -04:00
` https://codewhisperer.us-east-1.amazonaws.com/getUsageLimits? ${ getUsageParams . toString ( ) } ` ,
{
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"Accept" : "application/json" ,
"x-amz-user-agent" : "aws-sdk-js/1.0.0 KiroIDE" ,
"user-agent" : "aws-sdk-js/1.0.0 KiroIDE" ,
} ,
} ,
2026-04-28 06:28:57 -04:00
proxyOptions
2026-04-15 00:44:46 -04:00
) ,
} ,
{
name : "codewhisperer-post" ,
2026-04-28 06:28:57 -04:00
run : async ( ) => proxyAwareFetch ( "https://codewhisperer.us-east-1.amazonaws.com" , {
2026-04-15 00:44:46 -04:00
method : "POST" ,
2026-03-05 12:21:27 -05:00
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
2026-04-15 00:44:46 -04:00
"Content-Type" : "application/x-amz-json-1.0" ,
"x-amz-target" : "AmazonCodeWhispererService.GetUsageLimits" ,
2026-03-05 12:21:27 -05:00
"Accept" : "application/json" ,
} ,
2026-04-15 00:44:46 -04:00
body : JSON . stringify ( {
origin : "AI_EDITOR" ,
profileArn ,
resourceType : "AGENTIC_REQUEST" ,
} ) ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ,
2026-04-15 00:44:46 -04:00
} ,
{
name : "q-get" ,
run : async ( ) => {
const params = new URLSearchParams ( {
origin : "AI_EDITOR" ,
profileArn ,
resourceType : "AGENTIC_REQUEST" ,
} ) ;
2026-04-28 06:28:57 -04:00
return proxyAwareFetch ( ` https://q.us-east-1.amazonaws.com/getUsageLimits? ${ params } ` , {
2026-04-15 00:44:46 -04:00
method : "GET" ,
headers : {
"Authorization" : ` Bearer ${ accessToken } ` ,
"Accept" : "application/json" ,
} ,
2026-04-28 06:28:57 -04:00
} , proxyOptions ) ;
2026-04-15 00:44:46 -04:00
} ,
} ,
] ;
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
let sawAuthError = false ;
const errors = [ ] ;
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
for ( const attempt of attempts ) {
try {
const response = await attempt . run ( ) ;
if ( ! response . ok ) {
const errorText = await response . text ( ) . catch ( ( ) => "" ) ;
if ( response . status === 401 || response . status === 403 ) {
sawAuthError = true ;
}
errors . push ( ` ${ attempt . name } : ${ response . status } ${ errorText ? ` : ${ errorText } ` : "" } ` ) ;
continue ;
}
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
const data = await response . json ( ) ;
return parseKiroQuotaData ( data ) ;
} catch ( error ) {
errors . push ( ` ${ attempt . name } : ${ error . message } ` ) ;
}
}
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
if ( sawAuthError && authMethod === "idc" ) {
return {
message : "Kiro quota API is unavailable for the current AWS IAM Identity Center session. Chat may still work. If this persists after renewing your session, reconnect Kiro." ,
quotas : { } ,
} ;
}
2026-03-05 12:21:27 -05:00
2026-04-17 01:24:35 -04:00
// Social auth (Google/GitHub) - these use a different token format that may not work with AWS CodeWhisperer quota APIs
if ( sawAuthError && ( authMethod === "google" || authMethod === "github" ) ) {
return {
message : "Kiro quota API authentication expired. Chat may still work." ,
quotas : { } ,
} ;
}
2026-04-15 00:44:46 -04:00
if ( sawAuthError ) {
return {
message : "Kiro quota API rejected the current token. Chat may still work." ,
quotas : { } ,
} ;
}
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
const fallbackMessage =
errors . length > 0
? ` Unable to fetch Kiro usage right now. ( ${ errors [ errors . length - 1 ] } ) `
: "Unable to fetch Kiro usage right now." ;
2026-03-05 12:21:27 -05:00
2026-04-15 00:44:46 -04:00
return {
message : fallbackMessage ,
quotas : { } ,
} ;
2026-02-03 21:54:11 -05:00
}
2026-01-04 22:37:09 -05:00
/ * *
* Qwen Usage
* /
async function getQwenUsage ( accessToken , providerSpecificData ) {
try {
const resourceUrl = providerSpecificData ? . resourceUrl ;
if ( ! resourceUrl ) {
return { message : "Qwen connected. No resource URL available." } ;
}
// Qwen may have usage endpoint at resource URL
return { message : "Qwen connected. Usage tracked per request." } ;
} catch ( error ) {
return { message : "Unable to fetch Qwen usage." } ;
}
}
/ * *
* iFlow Usage
* /
async function getIflowUsage ( accessToken ) {
try {
// iFlow may have usage endpoint
return { message : "iFlow connected. Usage tracked per request." } ;
} catch ( error ) {
return { message : "Unable to fetch iFlow usage." } ;
}
}
2026-04-21 23:32:28 -04:00
/ * *
* Ollama Cloud Usage
* Ollama Cloud uses an API key from ollama . com / settings / keys
* and has no public usage API — free tier has light usage limits ( resets every 5 h & 7 d ) .
* This returns an informational message with the plan details .
* /
async function getOllamaUsage ( accessToken , providerSpecificData ) {
try {
// Ollama Cloud does not expose a public quota/usage API.
// The provider is configured as noAuth with a notice explaining limits.
// We return a graceful message so the UI shows a friendly state instead of an error.
const plan = providerSpecificData ? . plan || "Free" ;
return {
plan ,
message : "Ollama Cloud uses a free tier with light usage limits (resets every 5h & 7d). For detailed usage tracking, visit ollama.com/settings/keys." ,
quotas : [ ] ,
} ;
} catch ( error ) {
return { message : "Unable to fetch Ollama Cloud usage." } ;
}
}
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
/ * *
* GLM Coding Plan usage ( international + China regions )
* /
async function getGlmUsage ( apiKey , provider , proxyOptions = null ) {
if ( ! apiKey ) {
return { message : "GLM API key not available." } ;
}
const region = provider === "glm-cn" ? "china" : "international" ;
const quotaUrl = GLM _QUOTA _URLS [ region ] ;
try {
const response = await proxyAwareFetch ( quotaUrl , {
headers : {
Authorization : ` Bearer ${ apiKey } ` ,
Accept : "application/json" ,
} ,
} , proxyOptions ) ;
if ( ! response . ok ) {
if ( response . status === 401 ) {
return { message : "GLM API key invalid or expired." } ;
}
return { message : ` GLM quota API error ( ${ response . status } ). ` } ;
}
const json = await response . json ( ) ;
const data = json ? . data && typeof json . data === "object" ? json . data : { } ;
const limits = Array . isArray ( data . limits ) ? data . limits : [ ] ;
const quotas = { } ;
for ( const limit of limits ) {
if ( ! limit || limit . type !== "TOKENS_LIMIT" ) continue ;
const usedPercent = Number ( limit . percentage ) || 0 ;
const resetMs = Number ( limit . nextResetTime ) || 0 ;
const remaining = Math . max ( 0 , 100 - usedPercent ) ;
quotas [ "session" ] = {
used : usedPercent ,
total : 100 ,
remaining ,
remainingPercentage : remaining ,
resetAt : resetMs > 0 ? new Date ( resetMs ) . toISOString ( ) : null ,
unlimited : false ,
} ;
}
const levelRaw = typeof data . level === "string" ? data . level : "" ;
const plan = levelRaw
? levelRaw . charAt ( 0 ) . toUpperCase ( ) + levelRaw . slice ( 1 ) . toLowerCase ( )
: "Unknown" ;
return { plan , quotas } ;
} catch ( error ) {
return { message : ` GLM error: ${ error . message } ` } ;
}
}
// ── MiniMax helpers ──────────────────────────────────────────────────────
function getMiniMaxField ( model , snakeKey , camelKey ) {
if ( ! model || typeof model !== "object" ) return null ;
return model [ snakeKey ] ? ? model [ camelKey ] ? ? null ;
}
2026-05-13 04:34:10 -04:00
function getMiniMaxModelName ( model ) {
return String ( getMiniMaxField ( model , "model_name" , "modelName" ) || "" ) . trim ( ) ;
}
function formatMiniMaxQuotaName ( model ) {
const rawName = getMiniMaxModelName ( model ) ;
if ( ! rawName ) return "MiniMax" ;
return rawName
. replace ( /[_-]+/g , " " )
. replace ( /\s+/g , " " )
. trim ( )
. replace ( /\b\w/g , ( ch ) => ch . toUpperCase ( ) )
. replace ( /\bTo\b/g , "to" )
. replace ( /\bTts\b/g , "TTS" )
. replace ( /\bHd\b/g , "HD" ) ;
}
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
function getMiniMaxSessionTotal ( model ) {
return Math . max ( 0 , Number ( getMiniMaxField ( model , "current_interval_total_count" , "currentIntervalTotalCount" ) ) || 0 ) ;
}
function getMiniMaxWeeklyTotal ( model ) {
return Math . max ( 0 , Number ( getMiniMaxField ( model , "current_weekly_total_count" , "currentWeeklyTotalCount" ) ) || 0 ) ;
}
2026-05-13 04:34:10 -04:00
function hasMiniMaxQuota ( model ) {
return getMiniMaxSessionTotal ( model ) > 0 || getMiniMaxWeeklyTotal ( model ) > 0 ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
}
function getMiniMaxResetAt ( model , capturedAtMs , remainsSnake , remainsCamel , endSnake , endCamel ) {
const remainsMs = Number ( getMiniMaxField ( model , remainsSnake , remainsCamel ) ) || 0 ;
if ( remainsMs > 0 ) return new Date ( capturedAtMs + remainsMs ) . toISOString ( ) ;
return parseResetTime ( getMiniMaxField ( model , endSnake , endCamel ) ) ;
}
function buildMiniMaxQuota ( total , count , resetAt , countMeansRemaining ) {
const safeTotal = Math . max ( 0 , total ) ;
const used = countMeansRemaining ? Math . max ( safeTotal - count , 0 ) : Math . min ( Math . max ( 0 , count ) , safeTotal ) ;
const remaining = Math . max ( safeTotal - used , 0 ) ;
return {
used ,
total : safeTotal ,
remaining ,
remainingPercentage : safeTotal > 0 ? Math . max ( 0 , Math . min ( 100 , ( remaining / safeTotal ) * 100 ) ) : 0 ,
resetAt ,
unlimited : false ,
} ;
}
2026-05-13 04:34:10 -04:00
function addMiniMaxQuota ( quotas , key , model , getTotal , countSnake , countCamel , resetArgs , countMeansRemaining ) {
const total = getTotal ( model ) ;
if ( total <= 0 ) return ;
const count = Math . max ( 0 , Number ( getMiniMaxField ( model , countSnake , countCamel ) ) || 0 ) ;
quotas [ key ] = buildMiniMaxQuota (
total ,
count ,
getMiniMaxResetAt ( model , ... resetArgs ) ,
countMeansRemaining
) ;
}
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
/ * *
* MiniMax Token Plan / Coding Plan usage
* /
async function getMiniMaxUsage ( apiKey , provider , proxyOptions = null ) {
if ( ! apiKey ) {
return { message : "MiniMax API key not available." } ;
}
const usageUrls = MINIMAX _USAGE _URLS [ provider ] || [ ] ;
let lastErrorMessage = "" ;
for ( let index = 0 ; index < usageUrls . length ; index += 1 ) {
const usageUrl = usageUrls [ index ] ;
const canFallback = index < usageUrls . length - 1 ;
try {
const response = await proxyAwareFetch ( usageUrl , {
method : "GET" ,
headers : {
Authorization : ` Bearer ${ apiKey } ` ,
Accept : "application/json" ,
"Content-Type" : "application/json" ,
} ,
} , proxyOptions ) ;
const rawText = await response . text ( ) ;
let payload = { } ;
if ( rawText ) {
try { payload = JSON . parse ( rawText ) ; } catch { payload = { } ; }
}
const baseResp = ( payload ? . base _resp ? ? payload ? . baseResp ) || { } ;
const apiStatusCode = Number ( baseResp . status _code ? ? baseResp . statusCode ) || 0 ;
const apiStatusMessage = String ( baseResp . status _msg ? ? baseResp . statusMsg ? ? "" ) . trim ( ) ;
const combined = ` ${ apiStatusMessage } ${ rawText } ` . trim ( ) ;
const authLike = /token plan|coding plan|invalid api key|invalid key|unauthorized|inactive/i ;
if ( response . status === 401 || response . status === 403 || apiStatusCode === 1004 || authLike . test ( combined ) ) {
return { message : "MiniMax API key invalid or inactive. Use an active Token/Coding Plan key." } ;
}
if ( ! response . ok ) {
lastErrorMessage = ` MiniMax usage endpoint error ( ${ response . status } ) ` ;
if ( ( response . status === 404 || response . status === 405 || response . status >= 500 ) && canFallback ) continue ;
return { message : ` MiniMax connected. ${ lastErrorMessage } ` } ;
}
if ( apiStatusCode !== 0 ) {
return { message : ` MiniMax connected. ${ apiStatusMessage || "Upstream quota API error" } ` } ;
}
const modelRemains = payload ? . model _remains ? ? payload ? . modelRemains ;
const allModels = Array . isArray ( modelRemains ) ? modelRemains : [ ] ;
2026-05-13 04:34:10 -04:00
const quotaModels = allModels . filter ( hasMiniMaxQuota ) ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
2026-05-13 04:34:10 -04:00
if ( quotaModels . length === 0 ) {
return { message : "MiniMax connected. No quota data was returned." } ;
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
}
const capturedAtMs = Date . now ( ) ;
const countMeansRemaining = usageUrl . includes ( "/coding_plan/remains" ) ;
const quotas = { } ;
2026-05-13 04:34:10 -04:00
for ( const model of quotaModels ) {
const displayName = formatMiniMaxQuotaName ( model ) ;
addMiniMaxQuota (
quotas ,
` ${ displayName } (5h) ` ,
model ,
getMiniMaxSessionTotal ,
"current_interval_usage_count" ,
"currentIntervalUsageCount" ,
[ capturedAtMs , "remains_time" , "remainsTime" , "end_time" , "endTime" ] ,
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
countMeansRemaining
) ;
2026-05-13 04:34:10 -04:00
addMiniMaxQuota (
quotas ,
` ${ displayName } (7d) ` ,
model ,
getMiniMaxWeeklyTotal ,
"current_weekly_usage_count" ,
"currentWeeklyUsageCount" ,
[ capturedAtMs , "weekly_remains_time" , "weeklyRemainsTime" , "weekly_end_time" , "weeklyEndTime" ] ,
feat: add STT support, Gemini TTS, and expand usage tracking
- Speech-to-Text: full pipeline with sttCore handler, /v1/audio/transcriptions
endpoint, sttConfig for OpenAI, Gemini, Groq, Deepgram, AssemblyAI,
HuggingFace, NVIDIA Parakeet; new 9router-stt skill
- Gemini TTS: add gemini provider with 30 prebuilt voices and TTS_PROVIDER_CONFIG
- Usage: implement GLM (intl/cn) and MiniMax (intl/cn) quota fetchers; refactor
Gemini CLI usage to use retrieveUserQuota with per-model buckets
- Disabled models: lowdb-backed disabledModelsDb + /api/models/disabled route
- Header search: reusable Zustand store (headerSearchStore) wired into Header
- CLI tools: add Claude Cowork tool card and cowork-settings API
- Providers: introduce mediaPriority sorting in getProvidersByKind, add
Kimi K2.6, reorder hermes, drop qwen STT kind
- UI: expand media-providers/[kind]/[id] page (+314), enhance OAuthModal,
ModelSelectModal, ProviderTopology, ProxyPools, ProviderLimits
- Assets: refresh provider PNGs (alicode, byteplus, cloudflare-ai, nvidia,
ollama, vertex, volcengine-ark) and add aws-polly, fal-ai, jina-ai, recraft,
runwayml, stability-ai, topaz, black-forest-labs
2026-05-04 23:32:59 -04:00
countMeansRemaining
) ;
}
if ( Object . keys ( quotas ) . length === 0 ) {
return { message : "MiniMax connected. Unable to extract quota usage." } ;
}
return { quotas } ;
} catch ( error ) {
lastErrorMessage = error . message ;
if ( ! canFallback ) break ;
}
}
return { message : lastErrorMessage ? ` MiniMax connected. Unable to fetch usage: ${ lastErrorMessage } ` : "MiniMax connected. Unable to fetch usage." } ;
}
2026-05-23 03:31:50 -04:00
async function getQoderUsage ( accessToken , proxyOptions = null ) {
if ( ! accessToken ) {
return { message : "Qoder usage unavailable: no access token" } ;
}
try {
const response = await proxyAwareFetch (
"https://openapi.qoder.sh/api/v2/quota/usage" ,
{
method : "GET" ,
headers : {
Authorization : ` Bearer ${ accessToken } ` ,
Accept : "application/json" ,
} ,
} ,
proxyOptions ,
) ;
if ( ! response . ok ) {
return { message : ` Qoder connected. Usage fetch returned ${ response . status } . ` } ;
}
const body = await response . json ( ) . catch ( ( ) => null ) ;
if ( ! body ) {
return { message : "Qoder connected. Usage response was not JSON." } ;
}
2026-05-23 09:50:53 -04:00
// Quota records live under `quotas`; scalar metadata
// (totalUsagePercentage, isQuotaExceeded, expiresAt) are surfaced as
// siblings so the dashboard parser doesn't try to render them as rows.
2026-05-23 03:31:50 -04:00
const userQuota = body . userQuota || { } ;
const orgQuota = body . orgResourcePackage || { } ;
2026-05-23 09:50:53 -04:00
// Qoder publishes a single absolute reset timestamp (`expiresAt` in ms);
// surface it on every quota record as ISO so the table can render
// "resets at" alongside used/total.
const expiresAtMs = Number . isFinite ( Number ( body . expiresAt ) ) && Number ( body . expiresAt ) > 0
? Number ( body . expiresAt )
: null ;
const resetAt = expiresAtMs ? new Date ( expiresAtMs ) . toISOString ( ) : null ;
2026-05-23 03:31:50 -04:00
const quotas = {
user : {
total : Number ( userQuota . total ) || 0 ,
used : Number ( userQuota . used ) || 0 ,
remaining : Number ( userQuota . remaining ) || 0 ,
unit : userQuota . unit || "credits" ,
2026-05-23 09:50:53 -04:00
resetAt ,
2026-05-23 03:31:50 -04:00
} ,
organization : {
total : Number ( orgQuota . total ) || 0 ,
used : Number ( orgQuota . used ) || 0 ,
remaining : Number ( orgQuota . remaining ) || 0 ,
unit : orgQuota . unit || "credits" ,
2026-05-23 09:50:53 -04:00
resetAt ,
2026-05-23 03:31:50 -04:00
} ,
2026-05-23 09:50:53 -04:00
} ;
return {
quotas ,
2026-05-23 03:31:50 -04:00
totalUsagePercentage : Number ( body . totalUsagePercentage ) || 0 ,
isQuotaExceeded : ! ! body . isQuotaExceeded ,
2026-05-23 09:50:53 -04:00
expiresAt : expiresAtMs ,
2026-05-23 03:31:50 -04:00
} ;
} catch ( error ) {
return { message : ` Qoder connected. Unable to fetch usage: ${ error . message } ` } ;
}
}