@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +57744 -0
- package/native-manifest.json +1414 -0
- package/package.json +254 -0
- package/plugins/abacus-ai/icon.svg +18 -0
- package/plugins/abacus-ai/plugin.js +121 -0
- package/plugins/abacus-ai/plugin.json +17 -0
- package/plugins/alibaba/icon-color.svg +1 -0
- package/plugins/alibaba/icon.svg +1 -0
- package/plugins/alibaba/plugin.js +322 -0
- package/plugins/alibaba/plugin.json +20 -0
- package/plugins/alibaba-token-plan/icon-color.svg +1 -0
- package/plugins/alibaba-token-plan/icon.svg +1 -0
- package/plugins/alibaba-token-plan/plugin.js +544 -0
- package/plugins/alibaba-token-plan/plugin.json +19 -0
- package/plugins/amp/icon-color.svg +1 -0
- package/plugins/amp/icon-conflict.svg +6 -0
- package/plugins/amp/icon.svg +1 -0
- package/plugins/amp/plugin.js +175 -0
- package/plugins/amp/plugin.json +16 -0
- package/plugins/antigravity/icon-color.svg +1 -0
- package/plugins/antigravity/icon-conflict.svg +3 -0
- package/plugins/antigravity/icon.svg +1 -0
- package/plugins/antigravity/plugin.js +739 -0
- package/plugins/antigravity/plugin.json +31 -0
- package/plugins/antigravity-cli/icon.svg +3 -0
- package/plugins/antigravity-cli/plugin.js +570 -0
- package/plugins/antigravity-cli/plugin.json +16 -0
- package/plugins/antigravity-ide/icon.svg +3 -0
- package/plugins/antigravity-ide/plugin.js +257 -0
- package/plugins/antigravity-ide/plugin.json +29 -0
- package/plugins/augment/icon.svg +14 -0
- package/plugins/augment/plugin.js +59 -0
- package/plugins/augment/plugin.json +18 -0
- package/plugins/aws-bedrock/icon-color.svg +1 -0
- package/plugins/aws-bedrock/icon.svg +1 -0
- package/plugins/aws-bedrock/plugin.js +206 -0
- package/plugins/aws-bedrock/plugin.json +18 -0
- package/plugins/azure-openai/icon-color.svg +1 -0
- package/plugins/azure-openai/icon.svg +1 -0
- package/plugins/azure-openai/plugin.js +145 -0
- package/plugins/azure-openai/plugin.json +17 -0
- package/plugins/chutes/icon.svg +7 -0
- package/plugins/chutes/plugin.js +236 -0
- package/plugins/chutes/plugin.json +19 -0
- package/plugins/claude/icon-color.svg +1 -0
- package/plugins/claude/icon.svg +1 -0
- package/plugins/claude/plugin.js +1723 -0
- package/plugins/claude/plugin.json +30 -0
- package/plugins/codebuff/icon.svg +92 -0
- package/plugins/codebuff/plugin.js +113 -0
- package/plugins/codebuff/plugin.json +17 -0
- package/plugins/codex/icon-color.svg +5 -0
- package/plugins/codex/icon.svg +1 -0
- package/plugins/codex/plugin.js +1009 -0
- package/plugins/codex/plugin.json +82 -0
- package/plugins/command-code/icon.svg +3 -0
- package/plugins/command-code/plugin.js +184 -0
- package/plugins/command-code/plugin.json +20 -0
- package/plugins/copilot/icon-color.svg +1 -0
- package/plugins/copilot/icon.svg +1 -0
- package/plugins/copilot/plugin.js +684 -0
- package/plugins/copilot/plugin.json +21 -0
- package/plugins/crof/icon.svg +3 -0
- package/plugins/crof/plugin.js +53 -0
- package/plugins/crof/plugin.json +17 -0
- package/plugins/crofai/icon.svg +16 -0
- package/plugins/crofai/plugin.js +81 -0
- package/plugins/crofai/plugin.json +18 -0
- package/plugins/cursor/icon-conflict.svg +3 -0
- package/plugins/cursor/icon.svg +1 -0
- package/plugins/cursor/plugin.js +1379 -0
- package/plugins/cursor/plugin.json +23 -0
- package/plugins/cursor-nightly/icon.png +0 -0
- package/plugins/cursor-nightly/plugin.js +1379 -0
- package/plugins/cursor-nightly/plugin.json +23 -0
- package/plugins/deepgram/icon.svg +1 -0
- package/plugins/deepgram/plugin.js +20 -0
- package/plugins/deepgram/plugin.json +17 -0
- package/plugins/deepseek/icon-color.svg +1 -0
- package/plugins/deepseek/icon.svg +1 -0
- package/plugins/deepseek/plugin.js +71 -0
- package/plugins/deepseek/plugin.json +17 -0
- package/plugins/devin/icon.svg +5 -0
- package/plugins/devin/plugin.js +341 -0
- package/plugins/devin/plugin.json +16 -0
- package/plugins/doubao/icon-color.svg +1 -0
- package/plugins/doubao/icon.svg +1 -0
- package/plugins/doubao/plugin.js +92 -0
- package/plugins/doubao/plugin.json +17 -0
- package/plugins/droid/icon.svg +1 -0
- package/plugins/droid/plugin.json +20 -0
- package/plugins/elevenlabs/icon.svg +4 -0
- package/plugins/elevenlabs/plugin.js +48 -0
- package/plugins/elevenlabs/plugin.json +18 -0
- package/plugins/factory/icon-conflict.svg +1 -0
- package/plugins/factory/icon.svg +1 -0
- package/plugins/factory/plugin.js +487 -0
- package/plugins/factory/plugin.json +15 -0
- package/plugins/fireworks-ai/icon.svg +6 -0
- package/plugins/fireworks-ai/plugin.js +370 -0
- package/plugins/fireworks-ai/plugin.json +19 -0
- package/plugins/gemini/icon-color.svg +1 -0
- package/plugins/gemini/icon.svg +1 -0
- package/plugins/gemini/plugin.js +460 -0
- package/plugins/gemini/plugin.json +16 -0
- package/plugins/grok/icon.svg +1 -0
- package/plugins/grok/plugin.js +297 -0
- package/plugins/grok/plugin.json +19 -0
- package/plugins/groqcloud/icon.svg +1 -0
- package/plugins/groqcloud/plugin.js +117 -0
- package/plugins/groqcloud/plugin.json +19 -0
- package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
- package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
- package/plugins/kilo/icon.svg +1 -0
- package/plugins/kilo/plugin.js +111 -0
- package/plugins/kilo/plugin.json +19 -0
- package/plugins/kimi/icon-color.svg +1 -0
- package/plugins/kimi/icon-conflict.svg +3 -0
- package/plugins/kimi/icon.svg +1 -0
- package/plugins/kimi/plugin.js +358 -0
- package/plugins/kimi/plugin.json +15 -0
- package/plugins/kimi-k2/icon-color.svg +1 -0
- package/plugins/kimi-k2/icon.svg +1 -0
- package/plugins/kimi-k2/plugin.js +58 -0
- package/plugins/kimi-k2/plugin.json +18 -0
- package/plugins/kiro/icon-color.svg +5 -0
- package/plugins/kiro/icon-conflict.svg +1 -0
- package/plugins/kiro/icon.svg +4 -0
- package/plugins/kiro/plugin.js +390 -0
- package/plugins/kiro/plugin.json +16 -0
- package/plugins/litellm/icon.svg +16 -0
- package/plugins/litellm/plugin.js +199 -0
- package/plugins/litellm/plugin.json +16 -0
- package/plugins/llm-proxy/icon.svg +1 -0
- package/plugins/llm-proxy/plugin.js +40 -0
- package/plugins/llm-proxy/plugin.json +17 -0
- package/plugins/manus/icon.svg +6 -0
- package/plugins/manus/plugin.js +50 -0
- package/plugins/manus/plugin.json +17 -0
- package/plugins/mimo/icon.svg +1 -0
- package/plugins/mimo/plugin.js +177 -0
- package/plugins/mimo/plugin.json +19 -0
- package/plugins/minimax/icon-color.svg +1 -0
- package/plugins/minimax/icon-conflict.svg +4 -0
- package/plugins/minimax/icon.svg +1 -0
- package/plugins/minimax/plugin.js +453 -0
- package/plugins/minimax/plugin.json +14 -0
- package/plugins/mistral/icon-color.svg +1 -0
- package/plugins/mistral/icon.svg +1 -0
- package/plugins/mistral/plugin.js +152 -0
- package/plugins/mistral/plugin.json +18 -0
- package/plugins/moonshot/icon.svg +1 -0
- package/plugins/moonshot/plugin.js +43 -0
- package/plugins/moonshot/plugin.json +16 -0
- package/plugins/nanogpt/icon-color.svg +17 -0
- package/plugins/nanogpt/icon.svg +10 -0
- package/plugins/nanogpt/plugin.js +80 -0
- package/plugins/nanogpt/plugin.json +17 -0
- package/plugins/neuralwatt/icon.svg +3 -0
- package/plugins/neuralwatt/plugin.js +152 -0
- package/plugins/neuralwatt/plugin.json +17 -0
- package/plugins/ollama/icon.svg +1 -0
- package/plugins/ollama/plugin.js +92 -0
- package/plugins/ollama/plugin.json +20 -0
- package/plugins/openai-api/icon.svg +1 -0
- package/plugins/openai-api/plugin.js +533 -0
- package/plugins/openai-api/plugin.json +26 -0
- package/plugins/opencode/icon.svg +1 -0
- package/plugins/opencode/plugin.js +342 -0
- package/plugins/opencode/plugin.json +19 -0
- package/plugins/opencode-go/icon-conflict.svg +1 -0
- package/plugins/opencode-go/icon.svg +1 -0
- package/plugins/opencode-go/plugin.js +617 -0
- package/plugins/opencode-go/plugin.json +50 -0
- package/plugins/openrouter/icon.svg +1 -0
- package/plugins/openrouter/plugin.js +156 -0
- package/plugins/openrouter/plugin.json +8 -0
- package/plugins/perplexity/icon-color.svg +1 -0
- package/plugins/perplexity/icon-conflict.svg +1 -0
- package/plugins/perplexity/icon.svg +1 -0
- package/plugins/perplexity/plugin.js +425 -0
- package/plugins/perplexity/plugin.json +19 -0
- package/plugins/poe/icon.svg +1 -0
- package/plugins/poe/plugin.js +190 -0
- package/plugins/poe/plugin.json +19 -0
- package/plugins/stepfun/icon-color.svg +1 -0
- package/plugins/stepfun/icon.svg +1 -0
- package/plugins/stepfun/plugin.js +155 -0
- package/plugins/stepfun/plugin.json +18 -0
- package/plugins/synthetic/icon-conflict.svg +12 -0
- package/plugins/synthetic/icon.svg +14 -0
- package/plugins/synthetic/plugin.js +331 -0
- package/plugins/synthetic/plugin.json +23 -0
- package/plugins/t3chat/icon-color.svg +28 -0
- package/plugins/t3chat/icon.svg +4 -0
- package/plugins/t3chat/plugin.js +243 -0
- package/plugins/t3chat/plugin.json +20 -0
- package/plugins/venice/icon-color.svg +1 -0
- package/plugins/venice/icon.svg +1 -0
- package/plugins/venice/plugin.js +69 -0
- package/plugins/venice/plugin.json +16 -0
- package/plugins/vertex-ai/icon-color.svg +1 -0
- package/plugins/vertex-ai/icon.svg +1 -0
- package/plugins/vertex-ai/plugin.js +172 -0
- package/plugins/vertex-ai/plugin.json +18 -0
- package/plugins/warp/icon-color.svg +10 -0
- package/plugins/warp/icon.svg +6 -0
- package/plugins/warp/plugin.js +108 -0
- package/plugins/warp/plugin.json +18 -0
- package/plugins/windsurf/icon-conflict.svg +3 -0
- package/plugins/windsurf/icon.svg +1 -0
- package/plugins/windsurf/plugin.js +208 -0
- package/plugins/windsurf/plugin.json +16 -0
- package/plugins/zai/icon-conflict.svg +5 -0
- package/plugins/zai/icon.svg +1 -0
- package/plugins/zai/plugin.js +188 -0
- package/plugins/zai/plugin.json +16 -0
- package/plugins/zed/icon.svg +4 -0
- package/plugins/zed/plugin.js +270 -0
- package/plugins/zed/plugin.json +19 -0
- package/usagestat +0 -0
- package/usagestatd +0 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const STATE_KEY = "kiro.kiroAgent"
|
|
3
|
+
const LOG_FILE_NAME = "q-client.log"
|
|
4
|
+
const TOKEN_PATH = "~/.aws/sso/cache/kiro-auth-token.json"
|
|
5
|
+
const REFRESH_URL = "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken"
|
|
6
|
+
const LIVE_STALE_MS = 15 * 60 * 1000
|
|
7
|
+
const REFRESH_BUFFER_MS = 10 * 60 * 1000
|
|
8
|
+
const DEFAULT_REGION = "us-east-1"
|
|
9
|
+
const COUNT_FORMAT = { kind: "count", suffix: "credits" }
|
|
10
|
+
const LOGIN_HINT = "Open Kiro and sign in, then try again."
|
|
11
|
+
const SESSION_HINT = "Kiro session expired. Open Kiro and sign in again."
|
|
12
|
+
const DATA_HINT = "Kiro usage data unavailable. Open the Kiro account dashboard once and try again."
|
|
13
|
+
|
|
14
|
+
function appPath(ctx, suffix) {
|
|
15
|
+
const settings = (ctx.provider && ctx.provider.settings) || {}
|
|
16
|
+
const root = typeof settings.userDataDir === "string" && settings.userDataDir
|
|
17
|
+
? settings.userDataDir
|
|
18
|
+
: ctx.host.fs.appSupportPath("Kiro")
|
|
19
|
+
if (!root) throw { code: "failed", message: "Kiro data directory unavailable. Set settings.userDataDir to the IDE user data directory." }
|
|
20
|
+
return root.replace(/[\\/]+$/, "") + "/" + suffix
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function num(value) {
|
|
24
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
|
25
|
+
if (typeof value !== "string" || !value.trim()) return null
|
|
26
|
+
const parsed = Number(value)
|
|
27
|
+
return Number.isFinite(parsed) ? parsed : null
|
|
28
|
+
}
|
|
29
|
+
function first() {
|
|
30
|
+
for (let i = 0; i < arguments.length; i += 1) {
|
|
31
|
+
const value = num(arguments[i])
|
|
32
|
+
if (value !== null) return value
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
function iso(ctx, value) {
|
|
37
|
+
const normalized = ctx.util.toIso(value)
|
|
38
|
+
return typeof normalized === "string" && normalized ? normalized : null
|
|
39
|
+
}
|
|
40
|
+
function title(value) {
|
|
41
|
+
const trimmed = String(value || "").trim()
|
|
42
|
+
return trimmed
|
|
43
|
+
? trimmed
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.split(/\s+/)
|
|
46
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
47
|
+
.join(" ")
|
|
48
|
+
: null
|
|
49
|
+
}
|
|
50
|
+
function sanitizeAuth(token) {
|
|
51
|
+
if (!token || typeof token !== "object") return null
|
|
52
|
+
if (token.provider === "Google" || token.provider === "Github") return { ...token, authMethod: "social" }
|
|
53
|
+
if (token.provider === "ExternalIdp") return { ...token, authMethod: "external_idp" }
|
|
54
|
+
if (token.provider === "Enterprise" || token.provider === "BuilderId" || token.provider === "Internal") {
|
|
55
|
+
return { ...token, authMethod: "IdC" }
|
|
56
|
+
}
|
|
57
|
+
return token
|
|
58
|
+
}
|
|
59
|
+
function readJsonFile(ctx, path, label) {
|
|
60
|
+
if (!ctx.host.fs.exists(path)) return null
|
|
61
|
+
try {
|
|
62
|
+
return ctx.util.tryParseJson(ctx.host.fs.readText(path))
|
|
63
|
+
} catch (e) {
|
|
64
|
+
ctx.host.log.warn(label + " read failed: " + String(e))
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function loadAuthState(ctx) {
|
|
69
|
+
const parsed = readJsonFile(ctx, TOKEN_PATH, "auth token")
|
|
70
|
+
if (!parsed || typeof parsed !== "object") return null
|
|
71
|
+
const token = sanitizeAuth(parsed)
|
|
72
|
+
return token && (token.refreshToken || token.accessToken) ? { path: TOKEN_PATH, token } : null
|
|
73
|
+
}
|
|
74
|
+
function saveAuthState(ctx, authState) {
|
|
75
|
+
try {
|
|
76
|
+
ctx.host.fs.writeText(authState.path, JSON.stringify(authState.token, null, 2))
|
|
77
|
+
return true
|
|
78
|
+
} catch (e) {
|
|
79
|
+
ctx.host.log.warn("failed to persist refreshed Kiro auth: " + String(e))
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function loadProfileArn(ctx, authState) {
|
|
84
|
+
const fromToken = authState && authState.token && authState.token.profileArn
|
|
85
|
+
if (typeof fromToken === "string" && fromToken) return fromToken
|
|
86
|
+
const parsed = readJsonFile(ctx, appPath(ctx, "User/globalStorage/kiro.kiroagent/profile.json"), "profile")
|
|
87
|
+
return parsed && typeof parsed.arn === "string" && parsed.arn.trim() ? parsed.arn.trim() : null
|
|
88
|
+
}
|
|
89
|
+
function regionFromArn(profileArn) {
|
|
90
|
+
const parts = String(profileArn || "").split(":")
|
|
91
|
+
return parts.length > 3 && parts[3] ? parts[3] : DEFAULT_REGION
|
|
92
|
+
}
|
|
93
|
+
function readStateValue(ctx, key) {
|
|
94
|
+
try {
|
|
95
|
+
const sql = "SELECT value FROM ItemTable WHERE key = '" + String(key).replace(/'/g, "''") + "' LIMIT 1;"
|
|
96
|
+
const rows = ctx.util.tryParseJson(ctx.host.sqlite.query(appPath(ctx, "User/globalStorage/state.vscdb"), sql))
|
|
97
|
+
return Array.isArray(rows) && rows.length && typeof rows[0].value === "string" ? rows[0].value : null
|
|
98
|
+
} catch (e) {
|
|
99
|
+
ctx.host.log.warn("Kiro sqlite read failed: " + String(e))
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function normalizePool(ctx, raw, config) {
|
|
104
|
+
if (!raw || typeof raw !== "object") return null
|
|
105
|
+
if (config.statusKey) {
|
|
106
|
+
const status = raw[config.statusKey]
|
|
107
|
+
if (status && !config.allowedStatuses.includes(status)) return null
|
|
108
|
+
}
|
|
109
|
+
const currentUsage = first(raw[config.preciseCurrent], raw[config.current])
|
|
110
|
+
const usageLimit = first(raw[config.preciseLimit], raw[config.limit])
|
|
111
|
+
if (currentUsage === null || usageLimit === null || usageLimit <= 0) return null
|
|
112
|
+
return {
|
|
113
|
+
currentUsage,
|
|
114
|
+
usageLimit,
|
|
115
|
+
expiryDate: iso(ctx, raw[config.expiryA] || raw[config.expiryB]),
|
|
116
|
+
displayName: typeof raw.displayName === "string" && raw.displayName.trim() ? raw.displayName.trim() : null,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function normalizeBreakdown(ctx, raw) {
|
|
120
|
+
if (!raw || typeof raw !== "object") return null
|
|
121
|
+
const currentUsage = first(raw.currentUsageWithPrecision, raw.currentUsage)
|
|
122
|
+
const usageLimit = first(raw.usageLimitWithPrecision, raw.usageLimit)
|
|
123
|
+
if (currentUsage === null || usageLimit === null || usageLimit <= 0) return null
|
|
124
|
+
const bonuses = Array.isArray(raw.bonuses)
|
|
125
|
+
? raw.bonuses
|
|
126
|
+
.map((item) =>
|
|
127
|
+
normalizePool(ctx, item, {
|
|
128
|
+
current: "currentUsage",
|
|
129
|
+
preciseCurrent: null,
|
|
130
|
+
limit: "usageLimit",
|
|
131
|
+
preciseLimit: null,
|
|
132
|
+
expiryA: "expiresAt",
|
|
133
|
+
expiryB: "expiryDate",
|
|
134
|
+
statusKey: "status",
|
|
135
|
+
allowedStatuses: ["ACTIVE", "EXHAUSTED"],
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
: []
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
type: typeof raw.resourceType === "string" ? raw.resourceType : raw.type,
|
|
143
|
+
currentUsage,
|
|
144
|
+
usageLimit,
|
|
145
|
+
resetDate: iso(ctx, raw.nextDateReset || raw.resetDate),
|
|
146
|
+
freeTrialUsage: normalizePool(ctx, raw.freeTrialInfo || raw.freeTrialUsage, {
|
|
147
|
+
current: "currentUsage",
|
|
148
|
+
preciseCurrent: "currentUsageWithPrecision",
|
|
149
|
+
limit: "usageLimit",
|
|
150
|
+
preciseLimit: "usageLimitWithPrecision",
|
|
151
|
+
expiryA: "freeTrialExpiry",
|
|
152
|
+
expiryB: "expiryDate",
|
|
153
|
+
statusKey: "freeTrialStatus",
|
|
154
|
+
allowedStatuses: ["ACTIVE"],
|
|
155
|
+
}),
|
|
156
|
+
bonuses,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function normalizeCachedState(ctx) {
|
|
160
|
+
const parsed = ctx.util.tryParseJson(readStateValue(ctx, STATE_KEY))
|
|
161
|
+
const usageState = parsed && parsed["kiro.resourceNotifications.usageState"]
|
|
162
|
+
if (!usageState || !Array.isArray(usageState.usageBreakdowns)) return null
|
|
163
|
+
const usageBreakdowns = usageState.usageBreakdowns.map((item) => normalizeBreakdown(ctx, item)).filter(Boolean)
|
|
164
|
+
return usageBreakdowns.length
|
|
165
|
+
? { usageBreakdowns, timestampMs: first(usageState.timestamp), plan: null, overageEnabled: null }
|
|
166
|
+
: null
|
|
167
|
+
}
|
|
168
|
+
function normalizeApiSnapshot(ctx, raw, timestampMs) {
|
|
169
|
+
if (!raw || typeof raw !== "object") return null
|
|
170
|
+
return {
|
|
171
|
+
usageBreakdowns: Array.isArray(raw.usageBreakdownList)
|
|
172
|
+
? raw.usageBreakdownList.map((item) => normalizeBreakdown(ctx, item)).filter(Boolean)
|
|
173
|
+
: [],
|
|
174
|
+
timestampMs: timestampMs !== null ? timestampMs : null,
|
|
175
|
+
plan: title(raw.subscriptionInfo && raw.subscriptionInfo.subscriptionTitle),
|
|
176
|
+
overageEnabled: raw.overageConfiguration ? raw.overageConfiguration.overageStatus === "ENABLED" : null,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function parseUsageLogText(ctx, text) {
|
|
180
|
+
const lines = String(text || "").split(/\r?\n/)
|
|
181
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
182
|
+
const line = lines[i]
|
|
183
|
+
if (line.indexOf('"commandName":"GetUsageLimitsCommand"') === -1) continue
|
|
184
|
+
const jsonStart = line.indexOf("{")
|
|
185
|
+
if (jsonStart === -1) continue
|
|
186
|
+
const parsed = ctx.util.tryParseJson(line.slice(jsonStart))
|
|
187
|
+
if (!parsed || !parsed.output) continue
|
|
188
|
+
const loggedAt = line.slice(0, jsonStart).trim().split(" [")[0]
|
|
189
|
+
return normalizeApiSnapshot(ctx, parsed.output, loggedAt ? ctx.util.parseDateMs(loggedAt.replace(" ", "T")) : null)
|
|
190
|
+
}
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
function loadLoggedState(ctx) {
|
|
194
|
+
const logsRoot = appPath(ctx, "logs")
|
|
195
|
+
let sessions = []
|
|
196
|
+
try {
|
|
197
|
+
sessions = ctx.host.fs.listDir(logsRoot).slice().sort().reverse()
|
|
198
|
+
} catch {
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
for (let i = 0; i < sessions.length && i < 12; i += 1) {
|
|
202
|
+
const sessionRoot = logsRoot + "/" + sessions[i]
|
|
203
|
+
let windows = []
|
|
204
|
+
try {
|
|
205
|
+
windows = ctx.host.fs.listDir(sessionRoot).slice().sort().reverse()
|
|
206
|
+
} catch {
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
for (let j = 0; j < windows.length; j += 1) {
|
|
210
|
+
const logPath = sessionRoot + "/" + windows[j] + "/exthost/kiro.kiroAgent/" + LOG_FILE_NAME
|
|
211
|
+
if (!ctx.host.fs.exists(logPath)) continue
|
|
212
|
+
try {
|
|
213
|
+
const snapshot = parseUsageLogText(ctx, ctx.host.fs.readText(logPath))
|
|
214
|
+
if (snapshot) return snapshot
|
|
215
|
+
} catch (e) {
|
|
216
|
+
ctx.host.log.warn("failed to parse Kiro usage log: " + String(e))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null
|
|
221
|
+
}
|
|
222
|
+
function buildUserAgent(ctx) {
|
|
223
|
+
return "OpenUsage/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0")
|
|
224
|
+
}
|
|
225
|
+
function needsRefresh(ctx, authState, nowMs) {
|
|
226
|
+
return ctx.util.needsRefreshByExpiry({
|
|
227
|
+
nowMs,
|
|
228
|
+
expiresAtMs: ctx.util.parseDateMs(authState.token && authState.token.expiresAt),
|
|
229
|
+
bufferMs: REFRESH_BUFFER_MS,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
function buildUsageHeaders(ctx, authState, accessToken) {
|
|
233
|
+
const headers = { Authorization: "Bearer " + accessToken, Accept: "application/json", "User-Agent": buildUserAgent(ctx) }
|
|
234
|
+
if (authState.token && authState.token.authMethod === "external_idp") headers.TokenType = "EXTERNAL_IDP"
|
|
235
|
+
if (authState.token && authState.token.provider === "Internal") headers["redirect-for-internal"] = "true"
|
|
236
|
+
return headers
|
|
237
|
+
}
|
|
238
|
+
function refreshAccessToken(ctx, authState, nowMs) {
|
|
239
|
+
if (!authState.token || !authState.token.refreshToken) throw SESSION_HINT
|
|
240
|
+
const { resp, json } = ctx.util.requestJson({
|
|
241
|
+
method: "POST",
|
|
242
|
+
url: REFRESH_URL,
|
|
243
|
+
headers: { "Content-Type": "application/json", "User-Agent": buildUserAgent(ctx) },
|
|
244
|
+
bodyText: JSON.stringify({ refreshToken: authState.token.refreshToken }),
|
|
245
|
+
timeoutMs: 15000,
|
|
246
|
+
})
|
|
247
|
+
if (ctx.util.isAuthStatus(resp.status)) throw SESSION_HINT
|
|
248
|
+
if (resp.status < 200 || resp.status >= 300 || !json || typeof json.accessToken !== "string" || !json.accessToken) {
|
|
249
|
+
ctx.host.log.warn("Kiro token refresh failed: HTTP " + resp.status)
|
|
250
|
+
return null
|
|
251
|
+
}
|
|
252
|
+
const expiresIn = first(json.expiresIn, json.expires_in)
|
|
253
|
+
authState.token = sanitizeAuth({
|
|
254
|
+
...authState.token,
|
|
255
|
+
accessToken: json.accessToken,
|
|
256
|
+
refreshToken: typeof json.refreshToken === "string" && json.refreshToken ? json.refreshToken : authState.token.refreshToken,
|
|
257
|
+
profileArn: typeof json.profileArn === "string" && json.profileArn ? json.profileArn : authState.token.profileArn,
|
|
258
|
+
expiresAt: expiresIn !== null && expiresIn > 0 ? new Date(nowMs + expiresIn * 1000).toISOString() : authState.token.expiresAt,
|
|
259
|
+
})
|
|
260
|
+
saveAuthState(ctx, authState)
|
|
261
|
+
return authState.token.accessToken
|
|
262
|
+
}
|
|
263
|
+
function fetchLiveState(ctx, authState, nowMs) {
|
|
264
|
+
const profileArn = loadProfileArn(ctx, authState)
|
|
265
|
+
if (!profileArn) return null
|
|
266
|
+
const url =
|
|
267
|
+
"https://q." +
|
|
268
|
+
regionFromArn(profileArn) +
|
|
269
|
+
".amazonaws.com/getUsageLimits?origin=" +
|
|
270
|
+
encodeURIComponent("AI_EDITOR") +
|
|
271
|
+
"&profileArn=" +
|
|
272
|
+
encodeURIComponent(profileArn) +
|
|
273
|
+
"&resourceType=" +
|
|
274
|
+
encodeURIComponent("AGENTIC_REQUEST")
|
|
275
|
+
|
|
276
|
+
let accessToken = authState.token && authState.token.accessToken
|
|
277
|
+
if (!accessToken || needsRefresh(ctx, authState, nowMs)) {
|
|
278
|
+
const refreshed = refreshAccessToken(ctx, authState, nowMs)
|
|
279
|
+
if (refreshed) accessToken = refreshed
|
|
280
|
+
}
|
|
281
|
+
if (!accessToken) throw SESSION_HINT
|
|
282
|
+
|
|
283
|
+
const resp = ctx.util.retryOnceOnAuth({
|
|
284
|
+
request: (tokenOverride) =>
|
|
285
|
+
ctx.util.request({
|
|
286
|
+
method: "GET",
|
|
287
|
+
url,
|
|
288
|
+
headers: buildUsageHeaders(ctx, authState, tokenOverride || accessToken),
|
|
289
|
+
timeoutMs: 15000,
|
|
290
|
+
}),
|
|
291
|
+
refresh: () => refreshAccessToken(ctx, authState, nowMs),
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (ctx.util.isAuthStatus(resp.status)) throw SESSION_HINT
|
|
295
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
296
|
+
ctx.host.log.warn("Kiro live usage request failed: HTTP " + resp.status)
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
const parsed = ctx.util.tryParseJson(resp.bodyText)
|
|
300
|
+
if (!parsed) {
|
|
301
|
+
ctx.host.log.warn("Kiro live usage response invalid JSON")
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
return normalizeApiSnapshot(ctx, parsed, nowMs)
|
|
305
|
+
}
|
|
306
|
+
function shouldTryLive(localState, loggedState, nowMs) {
|
|
307
|
+
return !localState || !loggedState || !loggedState.plan || localState.timestampMs === null || nowMs - localState.timestampMs > LIVE_STALE_MS
|
|
308
|
+
}
|
|
309
|
+
function mergeSnapshots(localState, loggedState, liveState, nowMs) {
|
|
310
|
+
const usageSource =
|
|
311
|
+
liveState && liveState.usageBreakdowns.length
|
|
312
|
+
? liveState
|
|
313
|
+
: localState && localState.usageBreakdowns.length
|
|
314
|
+
? localState
|
|
315
|
+
: loggedState && loggedState.usageBreakdowns.length
|
|
316
|
+
? loggedState
|
|
317
|
+
: null
|
|
318
|
+
if (!usageSource) return null
|
|
319
|
+
return {
|
|
320
|
+
plan: (liveState && liveState.plan) || (loggedState && loggedState.plan) || null,
|
|
321
|
+
overageEnabled:
|
|
322
|
+
liveState && liveState.overageEnabled !== null
|
|
323
|
+
? liveState.overageEnabled
|
|
324
|
+
: loggedState && loggedState.overageEnabled !== null
|
|
325
|
+
? loggedState.overageEnabled
|
|
326
|
+
: null,
|
|
327
|
+
usageBreakdowns: usageSource.usageBreakdowns,
|
|
328
|
+
timestampMs: usageSource.timestampMs !== null ? usageSource.timestampMs : nowMs,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function pickPrimaryBreakdown(usageBreakdowns) {
|
|
332
|
+
for (let i = 0; i < usageBreakdowns.length; i += 1) if (usageBreakdowns[i].type === "CREDIT") return usageBreakdowns[i]
|
|
333
|
+
return usageBreakdowns.length ? usageBreakdowns[0] : null
|
|
334
|
+
}
|
|
335
|
+
function pickBonusUsage(primary) {
|
|
336
|
+
return !primary ? null : primary.freeTrialUsage && primary.freeTrialUsage.usageLimit > 0 ? primary.freeTrialUsage : primary.bonuses && primary.bonuses.length ? primary.bonuses[0] : null
|
|
337
|
+
}
|
|
338
|
+
function formatAge(nowMs, timestampMs) {
|
|
339
|
+
if (!Number.isFinite(nowMs) || !Number.isFinite(timestampMs)) return null
|
|
340
|
+
const diffMs = Math.max(0, nowMs - timestampMs)
|
|
341
|
+
if (diffMs < 60000) return "Just now"
|
|
342
|
+
const minutes = Math.floor(diffMs / 60000)
|
|
343
|
+
if (minutes < 60) return minutes + "m ago"
|
|
344
|
+
const hours = Math.floor(minutes / 60)
|
|
345
|
+
return hours < 48 ? hours + "h ago" : Math.floor(hours / 24) + "d ago"
|
|
346
|
+
}
|
|
347
|
+
function buildOutput(ctx, snapshot, nowMs) {
|
|
348
|
+
const primary = pickPrimaryBreakdown(snapshot.usageBreakdowns)
|
|
349
|
+
if (!primary) throw DATA_HINT
|
|
350
|
+
const lines = [ctx.line.progress({ label: "Credits", used: primary.currentUsage, limit: primary.usageLimit, format: COUNT_FORMAT, resetsAt: primary.resetDate || undefined })]
|
|
351
|
+
const bonusUsage = pickBonusUsage(primary)
|
|
352
|
+
if (bonusUsage) {
|
|
353
|
+
lines.push(
|
|
354
|
+
ctx.line.progress({
|
|
355
|
+
label: "Bonus Credits",
|
|
356
|
+
used: bonusUsage.currentUsage,
|
|
357
|
+
limit: bonusUsage.usageLimit,
|
|
358
|
+
format: COUNT_FORMAT,
|
|
359
|
+
resetsAt: bonusUsage.expiryDate || undefined,
|
|
360
|
+
})
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
if (snapshot.overageEnabled !== null) lines.push(ctx.line.badge({ label: "Overages", text: snapshot.overageEnabled ? "Enabled" : "Disabled" }))
|
|
364
|
+
return { plan: snapshot.plan || undefined, lines }
|
|
365
|
+
}
|
|
366
|
+
function probe(ctx) {
|
|
367
|
+
const nowMs = ctx.util.parseDateMs(ctx.nowIso) || Date.now()
|
|
368
|
+
const authState = loadAuthState(ctx)
|
|
369
|
+
if (!authState || !authState.token || !authState.token.refreshToken) throw LOGIN_HINT
|
|
370
|
+
const localState = normalizeCachedState(ctx)
|
|
371
|
+
const loggedState = loadLoggedState(ctx)
|
|
372
|
+
let liveState = null
|
|
373
|
+
let liveError = null
|
|
374
|
+
if (shouldTryLive(localState, loggedState, nowMs)) {
|
|
375
|
+
try {
|
|
376
|
+
liveState = fetchLiveState(ctx, authState, nowMs)
|
|
377
|
+
} catch (e) {
|
|
378
|
+
liveError = e
|
|
379
|
+
ctx.host.log.warn("Kiro live fallback failed: " + String(e))
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const snapshot = mergeSnapshots(localState, loggedState, liveState, nowMs)
|
|
383
|
+
if (!snapshot) {
|
|
384
|
+
if (typeof liveError === "string" && liveError) throw liveError
|
|
385
|
+
throw DATA_HINT
|
|
386
|
+
}
|
|
387
|
+
return buildOutput(ctx, snapshot, nowMs)
|
|
388
|
+
}
|
|
389
|
+
globalThis.__openusage_plugin = { id: "kiro", probe }
|
|
390
|
+
})()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "kiro",
|
|
4
|
+
"name": "Kiro",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"brandColor": "#C09CFF",
|
|
9
|
+
"supportedModes": ["oauth", "local"],
|
|
10
|
+
"autoMode": "local",
|
|
11
|
+
"lines": [
|
|
12
|
+
{ "type": "progress", "label": "Credits", "scope": "overview", "primaryOrder": 1 },
|
|
13
|
+
{ "type": "progress", "label": "Bonus Credits", "scope": "overview" },
|
|
14
|
+
{ "type": "badge", "label": "Overages", "scope": "detail" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<path d="M8.1 12.4C12.5 8.8 17.7 6.3 22.4 5.7c0-.5.5-.7 1-.8 1.8-.4 4.1-.5 5.8-.5.6 0 1 .4 1 1v19c0 .6-.4 1-1 1H10c-.7 0-1.3 0-1.9-.1V12.4Z" stroke-width="1.35"/>
|
|
4
|
+
<path d="M8.1 12.4C5 14.8 3 17 2.8 19.2c-.2 1.9 1.8 3.5 5.3 4.5V12.4Z" fill="currentColor" stroke-width="1.35"/>
|
|
5
|
+
<path d="M10.6 19.5h3.7" stroke-width="1.35"/>
|
|
6
|
+
<path d="M20.6 25.5l-1.3 2.6c.5.3 1.1.4 1.7.2.9-.2 1.6-1.2 1.7-2.8" stroke-width="1.2"/>
|
|
7
|
+
<path d="M25.3 25.5c.1 1.8 1.2 3 2.6 3s2.5-1.2 2.6-3" stroke-width="1.2"/>
|
|
8
|
+
<path d="M25.3 15.5h2.9c.5 0 .9.4.9.9v6.9c0 .5-.4.9-.9.9h-2.9c-.5 0-.9-.4-.9-.9v-6.9c0-.5.4-.9.9-.9Z" stroke-width="1.2"/>
|
|
9
|
+
<path d="M17.3 22.6h7.1m4.7 0h1.1" stroke-width="1.05"/>
|
|
10
|
+
</g>
|
|
11
|
+
<path d="M12.9 13.1c4.2-2.3 8.3-3.5 12.7-3.6.8.2 1.8 1.5 2.4 3.1-5.3.2-10.3.8-15.2 2.6-1.2.4-1.5-1.2.1-2.1Z" fill="currentColor"/>
|
|
12
|
+
<path d="M20.4 16.5h4v5.4h-6.6l2.6-5.4Z" fill="currentColor"/>
|
|
13
|
+
<path d="M29.1 16.5h1.1v5.4h-1.1v-5.4Z" fill="currentColor"/>
|
|
14
|
+
<path d="M10.9 25.5h9.5l-1.6 2.3H10c-.8 0-1.1-.7-.7-1.3l1.6-1Z" fill="currentColor"/>
|
|
15
|
+
<path d="M25.8 25.5h4c-.1 1.2-.9 2.1-2 2.1s-1.9-.9-2-2.1Z" fill="currentColor"/>
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
function env(ctx, name) {
|
|
3
|
+
try {
|
|
4
|
+
var value = ctx.host.env.get(name);
|
|
5
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
6
|
+
} catch (_) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function apiKey(ctx) {
|
|
12
|
+
var configured = ctx.provider && typeof ctx.provider.apiKey === "string" ? ctx.provider.apiKey.trim() : "";
|
|
13
|
+
return configured || env(ctx, "LITELLM_API_KEY");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function setting(ctx, names) {
|
|
17
|
+
var settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
|
|
18
|
+
for (var i = 0; i < names.length; i++) {
|
|
19
|
+
var value = settings[names[i]];
|
|
20
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function baseUrl(ctx) {
|
|
26
|
+
var configured = setting(ctx, ["enterpriseHost", "baseUrl", "baseURL", "apiUrl", "apiURL"]);
|
|
27
|
+
var base = configured || env(ctx, "LITELLM_BASE_URL");
|
|
28
|
+
if (!base) throw "Missing LiteLLM base URL. Set LITELLM_BASE_URL or provider settings.enterpriseHost.";
|
|
29
|
+
base = base.trim().replace(/\/+$/, "");
|
|
30
|
+
if (base.toLowerCase().endsWith("/v1")) base = base.slice(0, -3).replace(/\/+$/, "");
|
|
31
|
+
if (!/^https?:\/\//i.test(base)) throw "LiteLLM base URL is invalid.";
|
|
32
|
+
return base;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function numberValue(value) {
|
|
36
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
37
|
+
if (typeof value === "string" && value.trim()) {
|
|
38
|
+
var parsed = Number(value.replace(/[$,]/g, ""));
|
|
39
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nonEmpty(value) {
|
|
45
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseDate(value) {
|
|
49
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
50
|
+
var ms = Date.parse(value);
|
|
51
|
+
return Number.isFinite(ms) ? new Date(ms) : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requestJson(ctx, base, path, key) {
|
|
55
|
+
var resp = ctx.util.request({
|
|
56
|
+
method: "GET",
|
|
57
|
+
url: base + path,
|
|
58
|
+
headers: { Authorization: "Bearer " + key, Accept: "application/json" },
|
|
59
|
+
timeoutMs: 15000,
|
|
60
|
+
});
|
|
61
|
+
if (ctx.util.isAuthStatus(resp.status)) throw "LiteLLM API key was rejected.";
|
|
62
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
63
|
+
throw "LiteLLM API request failed (HTTP " + resp.status + ").";
|
|
64
|
+
}
|
|
65
|
+
var json = ctx.util.tryParseJson(resp.bodyText);
|
|
66
|
+
if (!json) throw "LiteLLM response was not valid JSON.";
|
|
67
|
+
return json;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function keyInfo(json) {
|
|
71
|
+
var info = json && json.info ? json.info : json;
|
|
72
|
+
var userID = nonEmpty(info && (info.user_id || info.userID));
|
|
73
|
+
var teamID = nonEmpty(info && (info.team_id || info.teamID));
|
|
74
|
+
if (!userID && !teamID) throw "LiteLLM key info did not include a user_id or team_id.";
|
|
75
|
+
return {
|
|
76
|
+
userID: userID,
|
|
77
|
+
teamID: teamID,
|
|
78
|
+
keyName: nonEmpty(info.key_name || info.keyName),
|
|
79
|
+
spendUSD: numberValue(info.spend) || 0,
|
|
80
|
+
expiresAt: parseDate(info.expires),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function findTeam(teams, teamID) {
|
|
85
|
+
if (!Array.isArray(teams) || !teamID) return null;
|
|
86
|
+
for (var i = 0; i < teams.length; i++) {
|
|
87
|
+
if (String(teams[i].team_id || teams[i].teamID || "") === teamID) return teams[i];
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function usd(value) {
|
|
93
|
+
return "$" + (Number(value) || 0).toFixed(2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function spendDetail(spend, budget, prefix) {
|
|
97
|
+
var value = budget != null && budget > 0 ? usd(spend) + " / " + usd(budget) : usd(spend);
|
|
98
|
+
return prefix ? prefix + ": " + value : value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function spendLabel(label) {
|
|
102
|
+
return label.replace(/\s+budget$/i, " spend");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function addBudget(lines, label, spend, budget, resetAt, detailPrefix) {
|
|
106
|
+
if (budget != null && budget > 0) {
|
|
107
|
+
var line = ctxLineProgress(label, (spend / budget) * 100);
|
|
108
|
+
if (resetAt) line.resetsAt = resetAt.toISOString();
|
|
109
|
+
line.detail = spendDetail(spend, budget, detailPrefix);
|
|
110
|
+
lines.push(line);
|
|
111
|
+
return true;
|
|
112
|
+
} else if (spend > 0) {
|
|
113
|
+
lines.push({ type: "text", label: spendLabel(label), value: spendDetail(spend, null, detailPrefix) });
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function ctxLineProgress(label, percent) {
|
|
120
|
+
return {
|
|
121
|
+
type: "progress",
|
|
122
|
+
label: label,
|
|
123
|
+
used: Math.max(0, Math.min(100, percent)),
|
|
124
|
+
limit: 100,
|
|
125
|
+
format: { kind: "percent" },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function userSnapshot(ctx, base, key, info) {
|
|
130
|
+
var encoded = encodeURIComponent(info.userID);
|
|
131
|
+
var json = requestJson(ctx, base, "/user/info?user_id=" + encoded, key);
|
|
132
|
+
var user = json.user_info || json.userInfo || json;
|
|
133
|
+
var responseID = nonEmpty(user.user_id || user.userID || json.user_id || json.userID);
|
|
134
|
+
if (responseID && responseID !== info.userID) throw "LiteLLM user_id did not match /key/info.";
|
|
135
|
+
var team = findTeam(json.teams, info.teamID);
|
|
136
|
+
return {
|
|
137
|
+
userID: info.userID,
|
|
138
|
+
email: nonEmpty(user.user_email || user.userEmail || user.user_alias || user.userAlias || (user.metadata && user.metadata.preferred_username)),
|
|
139
|
+
personalSpendUSD: numberValue(user.spend) || 0,
|
|
140
|
+
personalBudgetUSD: numberValue(user.max_budget || user.maxBudget),
|
|
141
|
+
personalResetAt: parseDate(user.budget_reset_at || user.budgetResetAt),
|
|
142
|
+
team: team ? {
|
|
143
|
+
id: String(team.team_id || team.teamID),
|
|
144
|
+
alias: nonEmpty(team.team_alias || team.teamAlias),
|
|
145
|
+
spendUSD: numberValue(team.spend) || 0,
|
|
146
|
+
budgetUSD: numberValue(team.max_budget || team.maxBudget),
|
|
147
|
+
resetAt: parseDate(team.budget_reset_at || team.budgetResetAt),
|
|
148
|
+
} : null,
|
|
149
|
+
keyName: info.keyName,
|
|
150
|
+
keyExpiresAt: info.expiresAt,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function teamSnapshot(ctx, base, key, info) {
|
|
155
|
+
var encoded = encodeURIComponent(info.teamID);
|
|
156
|
+
var json = requestJson(ctx, base, "/team/info?team_id=" + encoded, key);
|
|
157
|
+
var team = json.team_info || json.teamInfo || json;
|
|
158
|
+
var responseID = nonEmpty(team.team_id || team.teamID || json.team_id || json.teamID);
|
|
159
|
+
if (responseID && responseID !== info.teamID) throw "LiteLLM team_id did not match /key/info.";
|
|
160
|
+
return {
|
|
161
|
+
userID: null,
|
|
162
|
+
email: null,
|
|
163
|
+
personalSpendUSD: 0,
|
|
164
|
+
personalBudgetUSD: null,
|
|
165
|
+
personalResetAt: null,
|
|
166
|
+
team: {
|
|
167
|
+
id: info.teamID,
|
|
168
|
+
alias: nonEmpty(team.team_alias || team.teamAlias),
|
|
169
|
+
spendUSD: numberValue(team.spend) || 0,
|
|
170
|
+
budgetUSD: numberValue(team.max_budget || team.maxBudget),
|
|
171
|
+
resetAt: parseDate(team.budget_reset_at || team.budgetResetAt),
|
|
172
|
+
},
|
|
173
|
+
keyName: info.keyName,
|
|
174
|
+
keyExpiresAt: info.expiresAt,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function linesFor(snapshot) {
|
|
179
|
+
var lines = [];
|
|
180
|
+
addBudget(lines, "Personal budget", snapshot.personalSpendUSD, snapshot.personalBudgetUSD, snapshot.personalResetAt);
|
|
181
|
+
if (snapshot.team) addBudget(lines, "Team budget", snapshot.team.spendUSD, snapshot.team.budgetUSD, snapshot.team.resetAt, snapshot.team.alias ? "Team " + snapshot.team.alias : "Team");
|
|
182
|
+
if (!lines.length) lines.push({ type: "text", label: "Spend", value: usd(snapshot.personalSpendUSD || (snapshot.team && snapshot.team.spendUSD) || 0) });
|
|
183
|
+
if (snapshot.keyName) lines.push({ type: "text", label: "Key", value: snapshot.keyName });
|
|
184
|
+
if (snapshot.keyExpiresAt) lines.push({ type: "text", label: "Key Expires", value: snapshot.keyExpiresAt.toISOString().slice(0, 10) });
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function probe(ctx) {
|
|
189
|
+
var key = apiKey(ctx);
|
|
190
|
+
if (!key) throw "Missing LiteLLM API key. Set LITELLM_API_KEY or provider apiKey.";
|
|
191
|
+
var base = baseUrl(ctx);
|
|
192
|
+
var info = keyInfo(requestJson(ctx, base, "/key/info", key));
|
|
193
|
+
var snapshot = info.userID ? userSnapshot(ctx, base, key, info) : teamSnapshot(ctx, base, key, info);
|
|
194
|
+
var plan = snapshot.team && snapshot.team.alias ? snapshot.team.alias : null;
|
|
195
|
+
return { displayName: "LiteLLM", source: "api", plan: plan, lines: linesFor(snapshot) };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
globalThis.__openusage_plugin = { id: "litellm", probe: probe };
|
|
199
|
+
})();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "litellm",
|
|
4
|
+
"name": "LiteLLM",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"brandColor": "#111827",
|
|
9
|
+
"supportedModes": ["api"],
|
|
10
|
+
"autoMode": "api",
|
|
11
|
+
"lines": [
|
|
12
|
+
{ "type": "progress", "label": "Personal budget", "scope": "overview", "primaryOrder": 1 },
|
|
13
|
+
{ "type": "progress", "label": "Team budget", "scope": "overview", "primaryOrder": 2 },
|
|
14
|
+
{ "type": "text", "label": "Spend", "scope": "detail" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"></path></svg>
|