@qadanm/opencode-smart-router 0.0.0-stage → 0.6.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +286 -0
  2. package/CONTRIBUTING.md +103 -0
  3. package/LICENSE +21 -0
  4. package/README.md +425 -2
  5. package/SECURITY.md +174 -0
  6. package/config.example.json +107 -0
  7. package/dist/npm/index.js +40 -0
  8. package/docs/ARCHITECTURE.md +104 -0
  9. package/docs/DEBUGGING-GUI.md +70 -0
  10. package/docs/INSTALL.md +151 -0
  11. package/docs/LIMITATIONS.md +230 -0
  12. package/docs/MODELS.md +124 -0
  13. package/docs/OPENDESIGN-INTEGRATION.md +635 -0
  14. package/docs/QA.md +142 -0
  15. package/docs/SHARED-CORE.md +114 -0
  16. package/install +75 -0
  17. package/package.json +94 -4
  18. package/scripts/install.sh +324 -0
  19. package/scripts/uninstall.sh +91 -0
  20. package/src/allowance.ts +135 -0
  21. package/src/core/classify/classifier.ts +305 -0
  22. package/src/core/config-writer.ts +167 -0
  23. package/src/core/config.ts +1047 -0
  24. package/src/core/context/strategy.ts +188 -0
  25. package/src/core/context-health.ts +39 -0
  26. package/src/core/failures.ts +398 -0
  27. package/src/core/history/opencode.ts +173 -0
  28. package/src/core/models/protocol.ts +22 -0
  29. package/src/core/models/registry.ts +204 -0
  30. package/src/core/models/seed.ts +781 -0
  31. package/src/core/offline/mode.ts +321 -0
  32. package/src/core/plan-page.ts +390 -0
  33. package/src/core/plans/catalog.ts +487 -0
  34. package/src/core/policy/calibration.ts +259 -0
  35. package/src/core/policy/eligibility.ts +630 -0
  36. package/src/core/providers/enablement.ts +143 -0
  37. package/src/core/providers/opendesign-credits.ts +206 -0
  38. package/src/core/providers/opendesign-models.ts +654 -0
  39. package/src/core/providers/opendesign.ts +474 -0
  40. package/src/core/quota/calibration.ts +166 -0
  41. package/src/core/quota/commandcode.ts +471 -0
  42. package/src/core/quota/credentials.ts +490 -0
  43. package/src/core/quota/ledger.ts +1304 -0
  44. package/src/core/quota/opencode-go.ts +148 -0
  45. package/src/core/quota/opendesign.ts +1043 -0
  46. package/src/core/quota/reconcile.ts +155 -0
  47. package/src/core/quota/report.ts +261 -0
  48. package/src/core/quota/service.ts +273 -0
  49. package/src/core/reprompt.ts +48 -0
  50. package/src/core/routing/engine.ts +978 -0
  51. package/src/core/types.ts +866 -0
  52. package/src/core/ui.ts +236 -0
  53. package/src/core/util.ts +114 -0
  54. package/src/gui/index.tsx +1675 -0
  55. package/src/gui/viewmodel.ts +456 -0
  56. package/src/index.ts +33 -0
  57. package/src/rpc.ts +204 -0
  58. package/src/server/containment.ts +131 -0
  59. package/src/server/controller.ts +3405 -0
  60. package/src/server/route-tracker.ts +60 -0
  61. package/src/server/shared-core.ts +701 -0
  62. 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
+ }