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" ,
} ;
// 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-01-04 22:37:09 -05:00
const { provider , accessToken , providerSpecificData } = connection ;
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-04-28 06:28:57 -04:00
return await getGeminiUsage ( accessToken , 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-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 ) ;
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 ,
} ;
}
/ * *
* Gemini CLI Usage ( Google Cloud )
* /
2026-04-28 06:28:57 -04:00
async function getGeminiUsage ( accessToken , proxyOptions = null ) {
2026-01-04 22:37:09 -05:00
try {
// Gemini CLI uses Google Cloud quotas
// Try to get quota info from Cloud Resource Manager
2026-04-28 06:28:57 -04:00
const response = await proxyAwareFetch (
2026-01-04 22:37:09 -05:00
"https://cloudresourcemanager.googleapis.com/v1/projects?filter=lifecycleState:ACTIVE" ,
{
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 ) {
// Quota API may not be accessible, return generic message
return { message : "Gemini CLI uses Google Cloud quotas. Check Google Cloud Console for details." } ;
}
return { message : "Gemini CLI connected. Usage tracked via Google Cloud Console." } ;
} catch ( error ) {
return { message : "Unable to fetch Gemini usage. Check Google Cloud Console." } ;
}
}
/ * *
* 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-02-06 23:17:06 -05:00
'claude-opus-4-6-thinking' ,
2026-02-20 05:52:15 -05:00
'claude-sonnet-4-6' ,
2026-02-22 03:20:24 -05:00
'gemini-3.1-pro-high' ,
'gemini-3.1-pro-low' ,
2026-02-05 06:38:50 -05:00
'gemini-3-flash' ,
2026-02-22 03:20:24 -05:00
'gpt-oss-120b-medium' ,
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-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-02-20 02:44:29 -05:00
2026-01-04 22:37:09 -05:00
// Parse rate limit info
const rateLimit = data . rate _limit || { } ;
const primaryWindow = rateLimit . primary _window || { } ;
const secondaryWindow = rateLimit . secondary _window || { } ;
2026-02-05 23:23:08 -05:00
// Parse reset dates (reset_at is Unix timestamp in seconds, multiply by 1000 for ms)
const sessionResetAt = parseResetTime ( primaryWindow . reset _at ? primaryWindow . reset _at * 1000 : null ) ;
const weeklyResetAt = parseResetTime ( secondaryWindow . reset _at ? secondaryWindow . reset _at * 1000 : null ) ;
2026-01-04 22:37:09 -05:00
return {
plan : data . plan _type || "unknown" ,
limitReached : rateLimit . limit _reached || false ,
quotas : {
session : {
used : primaryWindow . used _percent || 0 ,
total : 100 ,
remaining : 100 - ( primaryWindow . used _percent || 0 ) ,
2026-02-05 23:23:08 -05:00
resetAt : sessionResetAt ,
2026-01-04 22:37:09 -05:00
unlimited : false ,
} ,
weekly : {
used : secondaryWindow . used _percent || 0 ,
total : 100 ,
remaining : 100 - ( secondaryWindow . used _percent || 0 ) ,
2026-02-05 23:23:08 -05:00
resetAt : weeklyResetAt ,
2026-01-04 22:37:09 -05:00
unlimited : false ,
} ,
} ,
} ;
} 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." } ;
}
}