@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,370 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const API_BASE = "https://api.fireworks.ai/v1"
|
|
3
|
+
const ACCOUNTS_URL = API_BASE + "/accounts?pageSize=200"
|
|
4
|
+
const KEYCHAIN_SERVICE = "OpenUsage Fireworks AI API Key"
|
|
5
|
+
const ENV_VAR = "FIREWORKS_API_KEY"
|
|
6
|
+
const BILLING_WINDOW_DAYS = 30
|
|
7
|
+
const TIER_BY_CAP = { 50: "Tier 1", 500: "Tier 2", 5000: "Tier 3", 50000: "Tier 4" }
|
|
8
|
+
const HEALTHY_STATES = ["READY", "ACTIVE", "STATE_UNSPECIFIED"]
|
|
9
|
+
const HEALTHY_SUSPEND = ["UNSUSPENDED", "SUSPEND_STATE_UNSPECIFIED", null]
|
|
10
|
+
const TOTAL_TOKENS_NAMES = ["serverless-inference-total-tokens", "serverless-inference-tokens", "serverless-usage-tokens"]
|
|
11
|
+
|
|
12
|
+
function text(value) {
|
|
13
|
+
if (typeof value !== "string") return null
|
|
14
|
+
const trimmed = value.trim()
|
|
15
|
+
return trimmed || null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function num(value) {
|
|
19
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
|
20
|
+
const raw = text(value)
|
|
21
|
+
if (!raw) return null
|
|
22
|
+
const parsed = Number(raw)
|
|
23
|
+
return Number.isFinite(parsed) ? parsed : null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function title(value) {
|
|
27
|
+
const raw = text(value)
|
|
28
|
+
return raw ? raw.replace(/[_-]+/g, " ").replace(/\b[a-z]/g, (m) => m.toUpperCase()) : null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function lastPathPart(value) {
|
|
32
|
+
const raw = text(value)
|
|
33
|
+
return raw ? raw.split("/").filter(Boolean).pop() || null : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function firstFinite(values) {
|
|
37
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
38
|
+
if (Number.isFinite(values[i])) return values[i]
|
|
39
|
+
}
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatGroupedNumber(value) {
|
|
44
|
+
const parts = String(value).split(".")
|
|
45
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
|
46
|
+
return parts.join(".")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatFixed(value, decimals) {
|
|
50
|
+
const scale = Math.pow(10, decimals)
|
|
51
|
+
const rounded = Math.round(value * scale) / scale
|
|
52
|
+
const fixed = String(rounded.toFixed(decimals))
|
|
53
|
+
return decimals > 0 ? fixed.replace(/\.?0+$/, "") : fixed
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatDollars(value) {
|
|
57
|
+
if (!Number.isFinite(value)) return null
|
|
58
|
+
const rounded = Math.round(value * 100) / 100
|
|
59
|
+
const fixed = formatFixed(rounded, rounded % 1 === 0 ? 0 : 2)
|
|
60
|
+
const parts = fixed.split(".")
|
|
61
|
+
return "$" + formatGroupedNumber(parts.join("."))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatCompactCount(value) {
|
|
65
|
+
if (!Number.isFinite(value)) return null
|
|
66
|
+
const abs = Math.abs(value)
|
|
67
|
+
const units = [{ threshold: 1e12, suffix: "T" }, { threshold: 1e9, suffix: "B" }, { threshold: 1e6, suffix: "M" }, { threshold: 1e3, suffix: "K" }]
|
|
68
|
+
for (let i = 0; i < units.length; i += 1) {
|
|
69
|
+
const unit = units[i]
|
|
70
|
+
if (abs >= unit.threshold) return formatFixed(value / unit.threshold, 2) + unit.suffix
|
|
71
|
+
}
|
|
72
|
+
return formatGroupedNumber(String(Math.round(value)))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function startOfUtcDayMs(ms) {
|
|
76
|
+
const date = new Date(ms)
|
|
77
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildBillingWindow(nowMs) {
|
|
81
|
+
const endMs = startOfUtcDayMs(nowMs) + 24 * 60 * 60 * 1000
|
|
82
|
+
const startMs = endMs - BILLING_WINDOW_DAYS * 24 * 60 * 60 * 1000
|
|
83
|
+
const fmt = (ms) => new Date(ms).toISOString().slice(0, 10)
|
|
84
|
+
return { startTime: fmt(startMs), endTime: fmt(endMs), label: "Last " + BILLING_WINDOW_DAYS + " days" }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseCsvRow(text) {
|
|
88
|
+
const out = []
|
|
89
|
+
let current = ""
|
|
90
|
+
let inQuotes = false
|
|
91
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
92
|
+
const char = text.charAt(i)
|
|
93
|
+
if (char === '"') {
|
|
94
|
+
if (inQuotes && text.charAt(i + 1) === '"') {
|
|
95
|
+
current += '"'
|
|
96
|
+
i += 1
|
|
97
|
+
} else {
|
|
98
|
+
inQuotes = !inQuotes
|
|
99
|
+
}
|
|
100
|
+
} else if (char === "," && !inQuotes) {
|
|
101
|
+
out.push(current)
|
|
102
|
+
current = ""
|
|
103
|
+
} else {
|
|
104
|
+
current += char
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
out.push(current)
|
|
108
|
+
return out
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseBillingMetricsCsv(text) {
|
|
112
|
+
const raw = text && String(text).trim()
|
|
113
|
+
if (!raw) return []
|
|
114
|
+
const lines = raw.split(/\r?\n/).filter(Boolean)
|
|
115
|
+
if (lines.length < 2) return []
|
|
116
|
+
const header = parseCsvRow(lines[0]).map((value) => value.trim())
|
|
117
|
+
const rows = []
|
|
118
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
119
|
+
const cols = parseCsvRow(lines[i])
|
|
120
|
+
if (!cols.length) continue
|
|
121
|
+
const row = {}
|
|
122
|
+
for (let j = 0; j < header.length; j += 1) row[header[j]] = cols[j] || ""
|
|
123
|
+
rows.push(row)
|
|
124
|
+
}
|
|
125
|
+
return rows
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readKeychainApiKey(ctx) {
|
|
129
|
+
const keychain = ctx.host.keychain
|
|
130
|
+
if (!keychain) return null
|
|
131
|
+
if (typeof keychain.readGenericPasswordForCurrentUser === "function") {
|
|
132
|
+
try {
|
|
133
|
+
const value = text(keychain.readGenericPasswordForCurrentUser(KEYCHAIN_SERVICE))
|
|
134
|
+
if (value) return { value, source: "keychain-current-user" }
|
|
135
|
+
} catch (e) {
|
|
136
|
+
ctx.host.log.info("current-user keychain read failed, trying legacy lookup: " + String(e))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (typeof keychain.readGenericPassword !== "function") return null
|
|
140
|
+
try {
|
|
141
|
+
const value = text(keychain.readGenericPassword(KEYCHAIN_SERVICE))
|
|
142
|
+
return value ? { value, source: "keychain-legacy" } : null
|
|
143
|
+
} catch (e) {
|
|
144
|
+
ctx.host.log.info("keychain read failed (may not exist): " + String(e))
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadApiKey(ctx) {
|
|
150
|
+
const keychainValue = readKeychainApiKey(ctx)
|
|
151
|
+
if (keychainValue) return keychainValue
|
|
152
|
+
try {
|
|
153
|
+
const value = text(ctx.host.env.get(ENV_VAR))
|
|
154
|
+
return value ? { value, source: ENV_VAR } : null
|
|
155
|
+
} catch (e) {
|
|
156
|
+
ctx.host.log.warn("env read failed for " + ENV_VAR + ": " + String(e))
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function requestJson(ctx, apiKey, url) {
|
|
162
|
+
let resp
|
|
163
|
+
try {
|
|
164
|
+
resp = ctx.util.request({
|
|
165
|
+
method: "GET",
|
|
166
|
+
url,
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: "Bearer " + apiKey,
|
|
169
|
+
Accept: "application/json",
|
|
170
|
+
"User-Agent": "usagestat/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0"),
|
|
171
|
+
},
|
|
172
|
+
timeoutMs: 15000,
|
|
173
|
+
})
|
|
174
|
+
} catch (e) {
|
|
175
|
+
ctx.host.log.error("request failed: " + String(e))
|
|
176
|
+
throw "Request failed. Check your connection."
|
|
177
|
+
}
|
|
178
|
+
if (ctx.util.isAuthStatus(resp.status)) throw "API key invalid. Check your Fireworks AI API key."
|
|
179
|
+
if (resp.status < 200 || resp.status >= 300) throw "Request failed (HTTP " + resp.status + "). Try again later."
|
|
180
|
+
const parsed = ctx.util.tryParseJson(resp.bodyText)
|
|
181
|
+
if (!parsed || typeof parsed !== "object") throw "Response invalid. Try again later."
|
|
182
|
+
return parsed
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function listAccounts(ctx, apiKey) {
|
|
186
|
+
const parsed = requestJson(ctx, apiKey, ACCOUNTS_URL)
|
|
187
|
+
const accounts = Array.isArray(parsed.accounts) ? parsed.accounts : Array.isArray(parsed.data) ? parsed.data : null
|
|
188
|
+
if (!accounts || !accounts.length) throw "No Fireworks account found for this API key."
|
|
189
|
+
return accounts
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function pickAccount(accounts) {
|
|
193
|
+
let fallback = null
|
|
194
|
+
for (let i = 0; i < accounts.length; i += 1) {
|
|
195
|
+
const item = accounts[i]
|
|
196
|
+
if (!item || typeof item !== "object") continue
|
|
197
|
+
const accountId = lastPathPart(item.name) || text(item.accountId) || text(item.account_id)
|
|
198
|
+
if (!accountId) continue
|
|
199
|
+
const account = {
|
|
200
|
+
accountId,
|
|
201
|
+
state: text(item.state),
|
|
202
|
+
suspendState: text(item.suspendState || item.suspend_state),
|
|
203
|
+
}
|
|
204
|
+
if (!fallback) fallback = account
|
|
205
|
+
if (HEALTHY_STATES.includes(account.state) && HEALTHY_SUSPEND.includes(account.suspendState)) return account
|
|
206
|
+
}
|
|
207
|
+
return fallback
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function listQuotas(ctx, apiKey, accountId) {
|
|
211
|
+
const parsed = requestJson(ctx, apiKey, API_BASE + "/accounts/" + encodeURIComponent(accountId) + "/quotas?pageSize=200")
|
|
212
|
+
return Array.isArray(parsed.quotas) ? parsed.quotas : Array.isArray(parsed.data) ? parsed.data : []
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizeQuota(ctx, raw) {
|
|
216
|
+
if (!raw || typeof raw !== "object") return null
|
|
217
|
+
const name = lastPathPart(raw.name) || text(raw.quotaId) || text(raw.id)
|
|
218
|
+
if (!name) return null
|
|
219
|
+
return {
|
|
220
|
+
name,
|
|
221
|
+
value: num(raw.value),
|
|
222
|
+
maxValue: num(raw.maxValue ?? raw.max_value),
|
|
223
|
+
usage: firstFinite([
|
|
224
|
+
num(raw.usage),
|
|
225
|
+
num(raw.currentUsage ?? raw.current_usage),
|
|
226
|
+
num(raw.consumedValue ?? raw.consumed_value),
|
|
227
|
+
]),
|
|
228
|
+
updateTime: ctx.util.toIso(raw.updateTime ?? raw.update_time),
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function findQuota(quotas, exactNames, pattern) {
|
|
233
|
+
for (let i = 0; i < quotas.length; i += 1) {
|
|
234
|
+
const quota = quotas[i]
|
|
235
|
+
if (!quota) continue
|
|
236
|
+
if (exactNames.includes(quota.name)) return quota
|
|
237
|
+
}
|
|
238
|
+
for (let i = 0; i < quotas.length; i += 1) {
|
|
239
|
+
const quota = quotas[i]
|
|
240
|
+
if (!quota) continue
|
|
241
|
+
if (pattern.test(quota.name)) return quota
|
|
242
|
+
}
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function inferPlan(spendQuota) {
|
|
247
|
+
if (!spendQuota || spendQuota.maxValue === null) return null
|
|
248
|
+
const cap = Math.round(spendQuota.maxValue)
|
|
249
|
+
return TIER_BY_CAP[cap] || null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function buildStatusLine(ctx, account) {
|
|
253
|
+
const suspendState = account && account.suspendState
|
|
254
|
+
if (suspendState && !HEALTHY_SUSPEND.includes(suspendState)) {
|
|
255
|
+
return ctx.line.badge({ label: "Status", text: title(suspendState) || suspendState, color: "#f97316" })
|
|
256
|
+
}
|
|
257
|
+
const state = account && account.state
|
|
258
|
+
if (state && !HEALTHY_STATES.includes(state)) {
|
|
259
|
+
return ctx.line.badge({ label: "Status", text: title(state) || state, color: "#f97316" })
|
|
260
|
+
}
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function tokenAmount(quota) {
|
|
265
|
+
if (!quota) return null
|
|
266
|
+
if (quota.usage !== null && quota.usage >= 0) return quota.usage
|
|
267
|
+
if (quota.maxValue === null && quota.value !== null && quota.value >= 0) return quota.value
|
|
268
|
+
return null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function loadBillingUsage(ctx, apiKey, accountId, nowMs) {
|
|
272
|
+
const fireworks = ctx.host.fireworks
|
|
273
|
+
if (!fireworks || typeof fireworks.exportBillingMetrics !== "function") return { status: "unsupported" }
|
|
274
|
+
const window = buildBillingWindow(nowMs)
|
|
275
|
+
let exported
|
|
276
|
+
try {
|
|
277
|
+
exported = fireworks.exportBillingMetrics({
|
|
278
|
+
apiKey,
|
|
279
|
+
accountId,
|
|
280
|
+
startTime: window.startTime,
|
|
281
|
+
endTime: window.endTime,
|
|
282
|
+
})
|
|
283
|
+
} catch (e) {
|
|
284
|
+
ctx.host.log.warn("billing export failed: " + String(e))
|
|
285
|
+
return { status: "runner_failed" }
|
|
286
|
+
}
|
|
287
|
+
if (!exported || typeof exported.status !== "string") return { status: "runner_failed" }
|
|
288
|
+
if (exported.status !== "ok" || !text(exported.csv)) return { status: exported.status }
|
|
289
|
+
const rows = parseBillingMetricsCsv(exported.csv)
|
|
290
|
+
let promptTokens = 0
|
|
291
|
+
let generatedTokens = 0
|
|
292
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
293
|
+
const row = rows[i]
|
|
294
|
+
const usageType = text(row.usage_type || row.usageType)
|
|
295
|
+
if (usageType && !/inference_usage/i.test(usageType)) continue
|
|
296
|
+
promptTokens += num(row.prompt_tokens || row.promptTokens) || 0
|
|
297
|
+
generatedTokens += num(row.completion_tokens || row.completionTokens) || 0
|
|
298
|
+
}
|
|
299
|
+
const totalTokens = promptTokens + generatedTokens
|
|
300
|
+
return { status: "ok", label: window.label, promptTokens, generatedTokens, totalTokens }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildOutput(ctx, account, quotas, billingUsage) {
|
|
304
|
+
const spendQuota = findQuota(quotas, ["monthly-spend-usd"], /^monthly-spend-usd$/)
|
|
305
|
+
const totalTokensQuota = findQuota(quotas, TOTAL_TOKENS_NAMES, /(serverless|inference).*(total|usage).*tokens/i)
|
|
306
|
+
const plan = inferPlan(spendQuota)
|
|
307
|
+
const lines = []
|
|
308
|
+
|
|
309
|
+
const promptTotal = billingUsage && billingUsage.status === "ok" ? billingUsage.promptTokens : null
|
|
310
|
+
const generatedTotal = billingUsage && billingUsage.status === "ok" ? billingUsage.generatedTokens : null
|
|
311
|
+
const totalTokens =
|
|
312
|
+
billingUsage && billingUsage.status === "ok"
|
|
313
|
+
? billingUsage.totalTokens
|
|
314
|
+
: tokenAmount(totalTokensQuota)
|
|
315
|
+
if (totalTokens !== null) {
|
|
316
|
+
lines.push(ctx.line.text({ label: "Serverless usage", value: formatCompactCount(totalTokens) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
|
|
317
|
+
} else if (billingUsage && billingUsage.status !== "ok") {
|
|
318
|
+
const reason =
|
|
319
|
+
billingUsage.status === "no_runner" || billingUsage.status === "unsupported"
|
|
320
|
+
? "Install firectl for official billing export"
|
|
321
|
+
: billingUsage.status === "runner_failed"
|
|
322
|
+
? "Billing export failed; showing spend fallback"
|
|
323
|
+
: "Token totals not exposed for this account"
|
|
324
|
+
lines.push(ctx.line.text({ label: "Serverless usage", value: "Unavailable", subtitle: reason }))
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (promptTotal !== null) {
|
|
328
|
+
lines.push(ctx.line.text({ label: "Prompt tokens", value: formatCompactCount(promptTotal) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (generatedTotal !== null) {
|
|
332
|
+
lines.push(ctx.line.text({ label: "Generated tokens", value: formatCompactCount(generatedTotal) + " tokens", subtitle: billingUsage && billingUsage.status === "ok" ? billingUsage.label : "Tokens reported by Fireworks" }))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (spendQuota && spendQuota.usage !== null && spendQuota.usage >= 0) {
|
|
336
|
+
const spendSubtitle =
|
|
337
|
+
spendQuota.value !== null && spendQuota.value > 0
|
|
338
|
+
? "This month against budget " + formatDollars(spendQuota.value)
|
|
339
|
+
: "Calendar month to date"
|
|
340
|
+
lines.push(ctx.line.text({ label: "Month spend", value: formatDollars(spendQuota.usage), subtitle: spendSubtitle }))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (spendQuota && spendQuota.maxValue !== null && spendQuota.maxValue > 0) {
|
|
344
|
+
const budgetValue = spendQuota.value !== null && spendQuota.value > 0 ? formatDollars(spendQuota.value) : "Not set"
|
|
345
|
+
lines.push(ctx.line.text({ label: "Budget", value: budgetValue, subtitle: "Tier cap " + formatDollars(spendQuota.maxValue) }))
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const statusLine = buildStatusLine(ctx, account)
|
|
349
|
+
if (statusLine) lines.push(statusLine)
|
|
350
|
+
|
|
351
|
+
if (!lines.length) lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
|
|
352
|
+
return { plan, lines }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function probe(ctx) {
|
|
356
|
+
const nowMs = ctx.util.parseDateMs(ctx.nowIso) || Date.now()
|
|
357
|
+
const apiKey = loadApiKey(ctx)
|
|
358
|
+
if (!apiKey) {
|
|
359
|
+
throw 'No Fireworks API key found. Save it in macOS Keychain as "OpenUsage Fireworks AI API Key" or set FIREWORKS_API_KEY, then restart usagestat.'
|
|
360
|
+
}
|
|
361
|
+
ctx.host.log.info("api key loaded from " + apiKey.source)
|
|
362
|
+
const account = pickAccount(listAccounts(ctx, apiKey.value))
|
|
363
|
+
if (!account) throw "No Fireworks account found for this API key."
|
|
364
|
+
const quotas = listQuotas(ctx, apiKey.value, account.accountId).map((item) => normalizeQuota(ctx, item)).filter(Boolean)
|
|
365
|
+
const billingUsage = loadBillingUsage(ctx, apiKey.value, account.accountId, nowMs)
|
|
366
|
+
return buildOutput(ctx, account, quotas, billingUsage)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
globalThis.__openusage_plugin = { id: "fireworks-ai", probe }
|
|
370
|
+
})()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "fireworks-ai",
|
|
4
|
+
"name": "Fireworks AI",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"icon": "icon.svg",
|
|
8
|
+
"brandColor": "#5019C5",
|
|
9
|
+
"supportedModes": ["api"],
|
|
10
|
+
"autoMode": "api",
|
|
11
|
+
"lines": [
|
|
12
|
+
{ "type": "text", "label": "Serverless usage", "scope": "overview" },
|
|
13
|
+
{ "type": "text", "label": "Prompt tokens", "scope": "overview" },
|
|
14
|
+
{ "type": "text", "label": "Generated tokens", "scope": "overview" },
|
|
15
|
+
{ "type": "text", "label": "Month spend", "scope": "overview" },
|
|
16
|
+
{ "type": "text", "label": "Budget", "scope": "detail" },
|
|
17
|
+
{ "type": "badge", "label": "Status", "scope": "detail" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
|
@@ -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>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>
|