@qadanm/opencode-smart-router 0.0.0-stage → 0.4.0
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/CHANGELOG.md +118 -0
- package/CONTRIBUTING.md +96 -0
- package/LICENSE +21 -0
- package/README.md +341 -2
- package/SECURITY.md +174 -0
- package/config.example.json +108 -0
- package/dist/allowance.js +487 -0
- package/dist/gui-experimental.cjs +1 -0
- package/dist/index.js +11111 -0
- package/dist/npm/index.js +40 -0
- package/dist/tui.js +587 -0
- package/docs/ARCHITECTURE.md +104 -0
- package/docs/DEBUGGING-GUI.md +70 -0
- package/docs/INSTALL.md +120 -0
- package/docs/LIMITATIONS.md +213 -0
- package/docs/MODELS.md +124 -0
- package/docs/OPENDESIGN-INTEGRATION.md +635 -0
- package/docs/QA.md +142 -0
- package/docs/SHARED-CORE.md +72 -0
- package/install +75 -0
- package/package.json +92 -4
- package/scripts/install.sh +324 -0
- package/scripts/uninstall.sh +91 -0
- package/src/allowance.ts +135 -0
- package/src/core/classify/classifier.ts +305 -0
- package/src/core/config-writer.ts +167 -0
- package/src/core/config.ts +1030 -0
- package/src/core/context/strategy.ts +188 -0
- package/src/core/context-health.ts +39 -0
- package/src/core/failures.ts +398 -0
- package/src/core/history/opencode.ts +173 -0
- package/src/core/models/protocol.ts +22 -0
- package/src/core/models/registry.ts +203 -0
- package/src/core/models/seed.ts +771 -0
- package/src/core/offline/mode.ts +321 -0
- package/src/core/plan-page.ts +390 -0
- package/src/core/plans/catalog.ts +481 -0
- package/src/core/policy/calibration.ts +259 -0
- package/src/core/policy/eligibility.ts +578 -0
- package/src/core/providers/enablement.ts +143 -0
- package/src/core/providers/opendesign-credits.ts +206 -0
- package/src/core/providers/opendesign-models.ts +628 -0
- package/src/core/providers/opendesign.ts +474 -0
- package/src/core/quota/calibration.ts +166 -0
- package/src/core/quota/commandcode.ts +471 -0
- package/src/core/quota/credentials.ts +490 -0
- package/src/core/quota/ledger.ts +1273 -0
- package/src/core/quota/opencode-go.ts +148 -0
- package/src/core/quota/opendesign.ts +1043 -0
- package/src/core/quota/reconcile.ts +155 -0
- package/src/core/quota/report.ts +261 -0
- package/src/core/quota/service.ts +273 -0
- package/src/core/reprompt.ts +48 -0
- package/src/core/routing/engine.ts +919 -0
- package/src/core/types.ts +825 -0
- package/src/core/ui.ts +236 -0
- package/src/core/util.ts +114 -0
- package/src/gui/index.tsx +1670 -0
- package/src/gui/viewmodel.ts +456 -0
- package/src/index.ts +48 -0
- package/src/rpc.ts +204 -0
- package/src/server/controller.ts +3281 -0
- package/src/server/route-tracker.ts +60 -0
- package/src/server/shared-core.ts +671 -0
- package/src/tui/index.tsx +168 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import type { ExtraCredits, ProviderQuotaSnapshot, ProviderSettings, QuotaWindow } from "../types.js"
|
|
2
|
+
import { clamp, describePlan, pct, redact } from "../util.js"
|
|
3
|
+
import {
|
|
4
|
+
describeCredentialSource,
|
|
5
|
+
discoverCommandCodeCredentials,
|
|
6
|
+
type CredentialCandidate,
|
|
7
|
+
} from "./credentials.js"
|
|
8
|
+
|
|
9
|
+
const HOST = "https://api.commandcode.ai"
|
|
10
|
+
const TIMEOUT_MS = 8_000
|
|
11
|
+
const MAX_BODY_BYTES = 1_000_000
|
|
12
|
+
const USER_AGENT = "opencode-smart-router/0.1"
|
|
13
|
+
|
|
14
|
+
const PLAN_CREDITS: Record<string, number> = {
|
|
15
|
+
"individual-go": 10,
|
|
16
|
+
"individual-go-v1": 10,
|
|
17
|
+
"individual-goat": 70,
|
|
18
|
+
"individual-pro": 30,
|
|
19
|
+
"individual-pro-v1": 80,
|
|
20
|
+
"individual-provider": 15,
|
|
21
|
+
"individual-max": 150,
|
|
22
|
+
"individual-ultra": 300,
|
|
23
|
+
"teams-pro": 40,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PLAN_NAMES: Record<string, string> = {
|
|
27
|
+
"individual-go": "Go",
|
|
28
|
+
"individual-go-v1": "Go",
|
|
29
|
+
"individual-goat": "GOAT",
|
|
30
|
+
"individual-pro": "Pro",
|
|
31
|
+
"individual-pro-v1": "Pro",
|
|
32
|
+
"individual-provider": "Provider",
|
|
33
|
+
"individual-max": "Max",
|
|
34
|
+
"individual-ultra": "Ultra",
|
|
35
|
+
"teams-pro": "Teams Pro",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FetchLike {
|
|
39
|
+
(input: string, init?: RequestInit): Promise<Response>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CommandCodeProbeInput {
|
|
43
|
+
readonly settings: ProviderSettings
|
|
44
|
+
readonly home?: string
|
|
45
|
+
readonly env?: Record<string, string | undefined>
|
|
46
|
+
readonly explicitPath?: string
|
|
47
|
+
readonly openCodeIntegrationKey?: string
|
|
48
|
+
readonly providerSettingsKey?: string
|
|
49
|
+
readonly fetchImpl?: FetchLike
|
|
50
|
+
readonly now?: () => number
|
|
51
|
+
readonly timeoutMs?: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CommandCodeProbeResult {
|
|
55
|
+
readonly snapshot: ProviderQuotaSnapshot
|
|
56
|
+
readonly credentialSource?: string
|
|
57
|
+
readonly triedSources: ReadonlyArray<string>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
61
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
62
|
+
|
|
63
|
+
const numberValue = (value: unknown): number | undefined => {
|
|
64
|
+
if (typeof value === "number" && Number.isFinite(value)) return value
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
const cleaned = value.trim().replace(/^\$/, "").replaceAll(",", "")
|
|
67
|
+
if (!cleaned) return undefined
|
|
68
|
+
const parsed = Number(cleaned)
|
|
69
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
70
|
+
}
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const timeValue = (value: unknown): number | undefined => {
|
|
75
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
76
|
+
return value >= 1e12 ? value : value * 1000
|
|
77
|
+
}
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
const trimmed = value.trim()
|
|
80
|
+
if (!trimmed) return undefined
|
|
81
|
+
if (/^\d+$/.test(trimmed)) return timeValue(Number(trimmed))
|
|
82
|
+
const parsed = Date.parse(trimmed)
|
|
83
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
84
|
+
}
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const recordAt = (value: unknown, key: string): unknown => (isRecord(value) ? value[key] : undefined)
|
|
89
|
+
|
|
90
|
+
const parseWindow = (value: unknown): QuotaWindow | undefined => {
|
|
91
|
+
if (!isRecord(value)) return undefined
|
|
92
|
+
const used = numberValue(value.used)
|
|
93
|
+
const cap = numberValue(value.cap)
|
|
94
|
+
if (used === undefined || cap === undefined || cap <= 0) return undefined
|
|
95
|
+
return {
|
|
96
|
+
usedPct: pct(used, cap),
|
|
97
|
+
remainingPct: clamp(100 - pct(used, cap), 0, 100),
|
|
98
|
+
usedUsd: used,
|
|
99
|
+
capUsd: cap,
|
|
100
|
+
resetsAt: timeValue(value.resetAt),
|
|
101
|
+
exceeded: value.exceeded === true || used >= cap,
|
|
102
|
+
source: "provider",
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const unknownWindow: QuotaWindow = { usedPct: 0, remainingPct: 100, source: "unknown" }
|
|
107
|
+
|
|
108
|
+
interface FetchResult {
|
|
109
|
+
status: number
|
|
110
|
+
body?: unknown
|
|
111
|
+
error?: string
|
|
112
|
+
retryAfterMs?: number
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fetchJson = async (
|
|
116
|
+
fetchImpl: FetchLike,
|
|
117
|
+
path: string,
|
|
118
|
+
key: string,
|
|
119
|
+
timeoutMs: number,
|
|
120
|
+
): Promise<FetchResult> => {
|
|
121
|
+
const controller = new AbortController()
|
|
122
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetchImpl(`${HOST}${path}`, {
|
|
125
|
+
method: "GET",
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${key}`,
|
|
128
|
+
Accept: "application/json",
|
|
129
|
+
"User-Agent": USER_AGENT,
|
|
130
|
+
},
|
|
131
|
+
redirect: "error",
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
})
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const retryAfter = Number(response.headers.get("retry-after"))
|
|
136
|
+
return {
|
|
137
|
+
status: response.status,
|
|
138
|
+
error: `HTTP ${response.status}`,
|
|
139
|
+
retryAfterMs: Number.isFinite(retryAfter)
|
|
140
|
+
? Math.min(Math.max(0, retryAfter * 1000), 15 * 60_000)
|
|
141
|
+
: undefined,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const text = await response.text()
|
|
145
|
+
if (text.length > MAX_BODY_BYTES) return { status: response.status, error: "Response body too large" }
|
|
146
|
+
try {
|
|
147
|
+
return { status: response.status, body: JSON.parse(text) as unknown }
|
|
148
|
+
} catch {
|
|
149
|
+
return { status: response.status, error: "Invalid JSON response" }
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
status: 0,
|
|
154
|
+
error: controller.signal.aborted ? "Request timed out" : redact(error instanceof Error ? error.message : String(error)),
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timer)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const parseWhoami = (body: unknown): { orgID?: string; userName?: string } | undefined => {
|
|
162
|
+
if (!isRecord(body) || body.success === false) return undefined
|
|
163
|
+
const user = body.user
|
|
164
|
+
const org = body.org
|
|
165
|
+
return {
|
|
166
|
+
orgID: isRecord(org) && typeof org.id === "string" ? org.id : undefined,
|
|
167
|
+
userName: isRecord(user) && typeof user.userName === "string" ? user.userName : undefined,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const parseSubscription = (body: unknown): {
|
|
172
|
+
planID?: string
|
|
173
|
+
status?: string
|
|
174
|
+
periodStart?: number
|
|
175
|
+
periodEnd?: number
|
|
176
|
+
} => {
|
|
177
|
+
const data = recordAt(body, "data")
|
|
178
|
+
if (!isRecord(data)) return {}
|
|
179
|
+
return {
|
|
180
|
+
planID: typeof data.planId === "string" ? data.planId : undefined,
|
|
181
|
+
status: typeof data.status === "string" ? data.status : undefined,
|
|
182
|
+
periodStart: timeValue(data.currentPeriodStart),
|
|
183
|
+
periodEnd: timeValue(data.currentPeriodEnd),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const parseCredits = (body: unknown): {
|
|
188
|
+
monthlyRemaining?: number
|
|
189
|
+
purchased?: number
|
|
190
|
+
free?: number
|
|
191
|
+
fiveHour?: QuotaWindow
|
|
192
|
+
weekly?: QuotaWindow
|
|
193
|
+
} => {
|
|
194
|
+
const credits = recordAt(body, "credits")
|
|
195
|
+
const windowLimits = recordAt(body, "windowLimits")
|
|
196
|
+
return {
|
|
197
|
+
monthlyRemaining: numberValue(recordAt(credits, "monthlyCredits")),
|
|
198
|
+
purchased: numberValue(recordAt(credits, "purchasedCredits")),
|
|
199
|
+
free: numberValue(recordAt(credits, "freeCredits")),
|
|
200
|
+
fiveHour: parseWindow(recordAt(windowLimits, "fiveHour")),
|
|
201
|
+
weekly: parseWindow(recordAt(windowLimits, "weekly")),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const parseSummary = (body: unknown): { used?: number; periodStart?: number; basis?: string } => {
|
|
206
|
+
if (!isRecord(body)) return {}
|
|
207
|
+
return {
|
|
208
|
+
used: numberValue(body.totalMonthlyCredits) ?? numberValue(body.totalCost),
|
|
209
|
+
periodStart: timeValue(body.periodStart),
|
|
210
|
+
basis: typeof body.periodBasis === "string" ? body.periodBasis : undefined,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const orgPath = (path: string, orgID?: string) => (orgID ? `${path}?orgId=${encodeURIComponent(orgID)}` : path)
|
|
215
|
+
|
|
216
|
+
const summaryPath = (orgID: string | undefined, since: number | undefined) => {
|
|
217
|
+
const params: string[] = []
|
|
218
|
+
if (orgID) params.push(`orgId=${encodeURIComponent(orgID)}`)
|
|
219
|
+
if (since) params.push(`since=${encodeURIComponent(new Date(since).toISOString())}`)
|
|
220
|
+
return params.length > 0 ? `/alpha/usage/summary?${params.join("&")}` : "/alpha/usage/summary"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const baseSnapshot = (
|
|
224
|
+
input: CommandCodeProbeInput,
|
|
225
|
+
now: number,
|
|
226
|
+
error?: string,
|
|
227
|
+
retryAfterMs?: number,
|
|
228
|
+
): ProviderQuotaSnapshot => ({
|
|
229
|
+
providerID: "commandcode",
|
|
230
|
+
label: "Command Code",
|
|
231
|
+
plan: input.settings.plan,
|
|
232
|
+
source: "unknown",
|
|
233
|
+
fetchedAt: now,
|
|
234
|
+
stale: false,
|
|
235
|
+
rolling: unknownWindow,
|
|
236
|
+
weekly: unknownWindow,
|
|
237
|
+
monthly: unknownWindow,
|
|
238
|
+
models: {},
|
|
239
|
+
notes: [],
|
|
240
|
+
error,
|
|
241
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const credentialCandidates = (input: CommandCodeProbeInput): CredentialCandidate[] => {
|
|
245
|
+
const discovery = discoverCommandCodeCredentials({
|
|
246
|
+
home: input.home,
|
|
247
|
+
env: input.env,
|
|
248
|
+
explicitPath: input.explicitPath ?? input.settings.credentialPath,
|
|
249
|
+
openCodeIntegrationKey: input.openCodeIntegrationKey,
|
|
250
|
+
providerSettingsKey: input.providerSettingsKey,
|
|
251
|
+
...(input.settings.credentialSource ? { credentialSource: input.settings.credentialSource } : {}),
|
|
252
|
+
})
|
|
253
|
+
return discovery.candidates.slice(0, 4)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Read Command Code's account-level billing picture from the same read-only
|
|
258
|
+
* alpha endpoints the official CLI uses. This function never mutates state and
|
|
259
|
+
* never sends the key anywhere except https://api.commandcode.ai.
|
|
260
|
+
*/
|
|
261
|
+
export const probeCommandCodeQuota = async (input: CommandCodeProbeInput): Promise<CommandCodeProbeResult> => {
|
|
262
|
+
const now = input.now?.() ?? Date.now()
|
|
263
|
+
const fetchImpl = input.fetchImpl ?? fetch
|
|
264
|
+
const timeoutMs = input.timeoutMs ?? TIMEOUT_MS
|
|
265
|
+
const candidates = credentialCandidates(input)
|
|
266
|
+
const triedSources: string[] = []
|
|
267
|
+
|
|
268
|
+
if (candidates.length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
snapshot: baseSnapshot(input, now, "No Command Code credential was found that is authorized for billing"),
|
|
271
|
+
triedSources,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let identity: { orgID?: string; userName?: string } | undefined
|
|
276
|
+
let authorized: CredentialCandidate | undefined
|
|
277
|
+
let lastAuthError = ""
|
|
278
|
+
|
|
279
|
+
for (const candidate of candidates) {
|
|
280
|
+
triedSources.push(describeCredentialSource(candidate))
|
|
281
|
+
const whoami = await fetchJson(fetchImpl, "/alpha/whoami?limits=1", candidate.key, timeoutMs)
|
|
282
|
+
if (whoami.status === 200) {
|
|
283
|
+
const parsed = parseWhoami(whoami.body)
|
|
284
|
+
if (parsed) {
|
|
285
|
+
identity = parsed
|
|
286
|
+
authorized = candidate
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
lastAuthError = "Unrecognized identity response"
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
if (whoami.status === 429) {
|
|
293
|
+
return {
|
|
294
|
+
snapshot: baseSnapshot(input, now, "Command Code billing API is rate limited", whoami.retryAfterMs),
|
|
295
|
+
triedSources,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
lastAuthError = whoami.error ?? `HTTP ${whoami.status}`
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!authorized) {
|
|
302
|
+
return {
|
|
303
|
+
snapshot: baseSnapshot(
|
|
304
|
+
input,
|
|
305
|
+
now,
|
|
306
|
+
`No authorized Command Code billing credential found (${lastAuthError || "authentication failed"})`,
|
|
307
|
+
),
|
|
308
|
+
triedSources,
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const orgID = identity?.orgID
|
|
313
|
+
const [creditsResult, subscriptionResult] = await Promise.all([
|
|
314
|
+
fetchJson(fetchImpl, orgPath("/alpha/billing/credits", orgID), authorized.key, timeoutMs),
|
|
315
|
+
fetchJson(fetchImpl, orgPath("/alpha/billing/subscriptions", orgID), authorized.key, timeoutMs),
|
|
316
|
+
])
|
|
317
|
+
|
|
318
|
+
if (creditsResult.status === 429 || subscriptionResult.status === 429) {
|
|
319
|
+
return {
|
|
320
|
+
snapshot: baseSnapshot(
|
|
321
|
+
input,
|
|
322
|
+
now,
|
|
323
|
+
"Command Code billing API is rate limited",
|
|
324
|
+
creditsResult.retryAfterMs ?? subscriptionResult.retryAfterMs,
|
|
325
|
+
),
|
|
326
|
+
credentialSource: describeCredentialSource(authorized),
|
|
327
|
+
triedSources,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (creditsResult.status === 401 || creditsResult.status === 403 || subscriptionResult.status === 401 || subscriptionResult.status === 403) {
|
|
332
|
+
return {
|
|
333
|
+
snapshot: baseSnapshot(input, now, "Command Code rejected the billing credential"),
|
|
334
|
+
credentialSource: describeCredentialSource(authorized),
|
|
335
|
+
triedSources,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const credits = creditsResult.status === 200 ? parseCredits(creditsResult.body) : undefined
|
|
340
|
+
const subscription = subscriptionResult.status === 200 ? parseSubscription(subscriptionResult.body) : undefined
|
|
341
|
+
const periodStart = subscription?.periodStart
|
|
342
|
+
const summaryResult = await fetchJson(fetchImpl, summaryPath(orgID, periodStart), authorized.key, timeoutMs)
|
|
343
|
+
const summary = summaryResult.status === 200 ? parseSummary(summaryResult.body) : undefined
|
|
344
|
+
|
|
345
|
+
const planID = subscription?.planID ?? input.settings.plan
|
|
346
|
+
const monthlyRemaining = credits?.monthlyRemaining
|
|
347
|
+
const summaryUsable =
|
|
348
|
+
summary?.used !== undefined &&
|
|
349
|
+
(summary.basis === "billing-period" ||
|
|
350
|
+
(summary.periodStart !== undefined && periodStart !== undefined && summary.periodStart === periodStart))
|
|
351
|
+
const summaryUsed = summaryUsable ? summary?.used : undefined
|
|
352
|
+
// The usage summary and the credit balance are different accounting pools.
|
|
353
|
+
// The remaining-credit balance is the authoritative included-credit figure:
|
|
354
|
+
// when it is available, monthly usage is the stable plan grant minus that
|
|
355
|
+
// balance. The summary is retained only as a diagnostic cross-check.
|
|
356
|
+
const configuredGrant = input.settings.monthlyCreditsUsd && input.settings.monthlyCreditsUsd > 0
|
|
357
|
+
? input.settings.monthlyCreditsUsd
|
|
358
|
+
: undefined
|
|
359
|
+
// The provider's 5-hour and weekly caps are 20% and 50% of the monthly
|
|
360
|
+
// grant. Prefer those provider-reported values so a plan change cannot
|
|
361
|
+
// silently shift the derived percentage.
|
|
362
|
+
const windowGrant =
|
|
363
|
+
credits?.fiveHour?.capUsd !== undefined && credits.fiveHour.capUsd > 0
|
|
364
|
+
? credits.fiveHour.capUsd / 0.2
|
|
365
|
+
: credits?.weekly?.capUsd !== undefined && credits.weekly.capUsd > 0
|
|
366
|
+
? credits.weekly.capUsd / 0.5
|
|
367
|
+
: undefined
|
|
368
|
+
const planGrant = windowGrant ?? PLAN_CREDITS[planID] ?? configuredGrant
|
|
369
|
+
const monthlyGrant =
|
|
370
|
+
planGrant ??
|
|
371
|
+
(monthlyRemaining !== undefined && summaryUsed !== undefined ? monthlyRemaining + summaryUsed : undefined)
|
|
372
|
+
const derivedUsed =
|
|
373
|
+
monthlyRemaining !== undefined && monthlyGrant !== undefined
|
|
374
|
+
? Math.max(0, monthlyGrant - monthlyRemaining)
|
|
375
|
+
: summaryUsed
|
|
376
|
+
const monthlyPct = derivedUsed !== undefined && monthlyGrant && monthlyGrant > 0 ? pct(derivedUsed, monthlyGrant) : 0
|
|
377
|
+
|
|
378
|
+
const monthly: QuotaWindow =
|
|
379
|
+
monthlyRemaining !== undefined && monthlyGrant !== undefined
|
|
380
|
+
? {
|
|
381
|
+
usedPct: monthlyPct,
|
|
382
|
+
remainingPct: clamp(100 - monthlyPct, 0, 100),
|
|
383
|
+
usedUsd: derivedUsed,
|
|
384
|
+
capUsd: monthlyGrant,
|
|
385
|
+
resetsAt: subscription?.periodEnd,
|
|
386
|
+
exceeded: monthlyRemaining <= 0,
|
|
387
|
+
source: "provider",
|
|
388
|
+
}
|
|
389
|
+
: unknownWindow
|
|
390
|
+
|
|
391
|
+
const extra: ExtraCredits | undefined =
|
|
392
|
+
credits?.purchased !== undefined || credits?.free !== undefined
|
|
393
|
+
? {
|
|
394
|
+
purchasedUsd: credits?.purchased ?? 0,
|
|
395
|
+
freeUsd: credits?.free ?? 0,
|
|
396
|
+
totalUsd: (credits?.purchased ?? 0) + (credits?.free ?? 0),
|
|
397
|
+
}
|
|
398
|
+
: undefined
|
|
399
|
+
|
|
400
|
+
const notes: string[] = [
|
|
401
|
+
"Provider-reported from Command Code's read-only alpha billing endpoints; polling consumes no credits.",
|
|
402
|
+
]
|
|
403
|
+
if (planGrant !== undefined) {
|
|
404
|
+
const grantSource =
|
|
405
|
+
windowGrant !== undefined
|
|
406
|
+
? "the provider's 5-hour/weekly window caps"
|
|
407
|
+
: PLAN_CREDITS[planID] !== undefined
|
|
408
|
+
? "the plan registry"
|
|
409
|
+
: "the configured monthly allowance"
|
|
410
|
+
notes.push(
|
|
411
|
+
`Monthly allowance is derived from ${grantSource}; provider-reported remaining credits are authoritative.`,
|
|
412
|
+
)
|
|
413
|
+
} else if (monthlyRemaining !== undefined && summaryUsed !== undefined) {
|
|
414
|
+
notes.push("Monthly grant is the sum of remaining included credits and billing-period usage because no plan allowance was available.")
|
|
415
|
+
}
|
|
416
|
+
if (monthlyRemaining !== undefined && summaryUsed !== undefined && derivedUsed !== undefined) {
|
|
417
|
+
const summaryDelta = summaryUsed - derivedUsed
|
|
418
|
+
if (Math.abs(summaryDelta) >= 0.005) {
|
|
419
|
+
notes.push(
|
|
420
|
+
`Billing-period usage summary differs from the credit balance by $${summaryDelta.toFixed(4)}; remaining credits are authoritative.`,
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (summaryResult.status !== 200) notes.push(`Usage summary unavailable (${summaryResult.error ?? summaryResult.status}).`)
|
|
425
|
+
if (creditsResult.status !== 200) notes.push(`Credit windows unavailable (${creditsResult.error ?? creditsResult.status}).`)
|
|
426
|
+
notes.push(`Credential source: ${describeCredentialSource(authorized)}`)
|
|
427
|
+
|
|
428
|
+
const planLabel = PLAN_NAMES[planID] ?? planID
|
|
429
|
+
const error =
|
|
430
|
+
creditsResult.status !== 200 && subscriptionResult.status !== 200 && summaryResult.status !== 200
|
|
431
|
+
? "Command Code billing endpoints returned no usable data"
|
|
432
|
+
: undefined
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
snapshot: {
|
|
436
|
+
providerID: "commandcode",
|
|
437
|
+
label: `Command Code ${planLabel}`,
|
|
438
|
+
plan: planLabel,
|
|
439
|
+
source: credits || subscription || summary ? "provider" : "unknown",
|
|
440
|
+
fetchedAt: now,
|
|
441
|
+
stale: false,
|
|
442
|
+
rolling: credits?.fiveHour ?? unknownWindow,
|
|
443
|
+
weekly: credits?.weekly ?? unknownWindow,
|
|
444
|
+
monthly,
|
|
445
|
+
monthlyGrantUsd: monthlyGrant,
|
|
446
|
+
monthlyRemainingUsd: monthlyRemaining,
|
|
447
|
+
extraCredits: extra,
|
|
448
|
+
planStatus: subscription?.status,
|
|
449
|
+
periodStart: subscription?.periodStart,
|
|
450
|
+
periodEnd: subscription?.periodEnd,
|
|
451
|
+
models: {},
|
|
452
|
+
notes,
|
|
453
|
+
error,
|
|
454
|
+
},
|
|
455
|
+
credentialSource: describeCredentialSource(authorized),
|
|
456
|
+
triedSources,
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Exposed for tests. */
|
|
461
|
+
export const __commandCodeInternals = {
|
|
462
|
+
numberValue,
|
|
463
|
+
timeValue,
|
|
464
|
+
parseWindow,
|
|
465
|
+
parseWhoami,
|
|
466
|
+
parseSubscription,
|
|
467
|
+
parseCredits,
|
|
468
|
+
parseSummary,
|
|
469
|
+
summaryPath,
|
|
470
|
+
orgPath,
|
|
471
|
+
}
|