@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/CONTRIBUTING.md +96 -0
  3. package/LICENSE +21 -0
  4. package/README.md +341 -2
  5. package/SECURITY.md +174 -0
  6. package/config.example.json +108 -0
  7. package/dist/allowance.js +487 -0
  8. package/dist/gui-experimental.cjs +1 -0
  9. package/dist/index.js +11111 -0
  10. package/dist/npm/index.js +40 -0
  11. package/dist/tui.js +587 -0
  12. package/docs/ARCHITECTURE.md +104 -0
  13. package/docs/DEBUGGING-GUI.md +70 -0
  14. package/docs/INSTALL.md +120 -0
  15. package/docs/LIMITATIONS.md +213 -0
  16. package/docs/MODELS.md +124 -0
  17. package/docs/OPENDESIGN-INTEGRATION.md +635 -0
  18. package/docs/QA.md +142 -0
  19. package/docs/SHARED-CORE.md +72 -0
  20. package/install +75 -0
  21. package/package.json +92 -4
  22. package/scripts/install.sh +324 -0
  23. package/scripts/uninstall.sh +91 -0
  24. package/src/allowance.ts +135 -0
  25. package/src/core/classify/classifier.ts +305 -0
  26. package/src/core/config-writer.ts +167 -0
  27. package/src/core/config.ts +1030 -0
  28. package/src/core/context/strategy.ts +188 -0
  29. package/src/core/context-health.ts +39 -0
  30. package/src/core/failures.ts +398 -0
  31. package/src/core/history/opencode.ts +173 -0
  32. package/src/core/models/protocol.ts +22 -0
  33. package/src/core/models/registry.ts +203 -0
  34. package/src/core/models/seed.ts +771 -0
  35. package/src/core/offline/mode.ts +321 -0
  36. package/src/core/plan-page.ts +390 -0
  37. package/src/core/plans/catalog.ts +481 -0
  38. package/src/core/policy/calibration.ts +259 -0
  39. package/src/core/policy/eligibility.ts +578 -0
  40. package/src/core/providers/enablement.ts +143 -0
  41. package/src/core/providers/opendesign-credits.ts +206 -0
  42. package/src/core/providers/opendesign-models.ts +628 -0
  43. package/src/core/providers/opendesign.ts +474 -0
  44. package/src/core/quota/calibration.ts +166 -0
  45. package/src/core/quota/commandcode.ts +471 -0
  46. package/src/core/quota/credentials.ts +490 -0
  47. package/src/core/quota/ledger.ts +1273 -0
  48. package/src/core/quota/opencode-go.ts +148 -0
  49. package/src/core/quota/opendesign.ts +1043 -0
  50. package/src/core/quota/reconcile.ts +155 -0
  51. package/src/core/quota/report.ts +261 -0
  52. package/src/core/quota/service.ts +273 -0
  53. package/src/core/reprompt.ts +48 -0
  54. package/src/core/routing/engine.ts +919 -0
  55. package/src/core/types.ts +825 -0
  56. package/src/core/ui.ts +236 -0
  57. package/src/core/util.ts +114 -0
  58. package/src/gui/index.tsx +1670 -0
  59. package/src/gui/viewmodel.ts +456 -0
  60. package/src/index.ts +48 -0
  61. package/src/rpc.ts +204 -0
  62. package/src/server/controller.ts +3281 -0
  63. package/src/server/route-tracker.ts +60 -0
  64. package/src/server/shared-core.ts +671 -0
  65. package/src/tui/index.tsx +168 -0
@@ -0,0 +1,578 @@
1
+ import type {
2
+ Mode,
3
+ ModelQuotaSnapshot,
4
+ ModelRecord,
5
+ PremiumState,
6
+ ProviderQuotaSnapshot,
7
+ QuotaWindow,
8
+ RouterSettings,
9
+ SessionState,
10
+ TaskClass,
11
+ } from "../types.js"
12
+ import { keyOfRef, modelKey, pct } from "../util.js"
13
+ import {
14
+ CONTEXT_ELIGIBILITY_MARGIN_TOKENS,
15
+ CONTEXT_SAFE_WINDOW_FRACTION,
16
+ replyMarginFor,
17
+ } from "../context/strategy.js"
18
+ import { OFFLINE_PROVIDER_ID } from "../offline/mode.js"
19
+
20
+ export interface PremiumDecision {
21
+ readonly allowed: boolean
22
+ readonly requiresApproval: boolean
23
+ readonly reason: string
24
+ }
25
+
26
+ export const decidePremium = (input: {
27
+ settings: RouterSettings
28
+ mode: Mode
29
+ task: TaskClass
30
+ session?: SessionState
31
+ premium: PremiumState
32
+ }): PremiumDecision => {
33
+ const { settings, mode, task, session, premium } = input
34
+ const premiumTask = task.tier === 3 || task.constraints.requestPremium
35
+ if (!premiumTask) {
36
+ return { allowed: false, requiresApproval: false, reason: "Premium models are reserved for premium-tier work." }
37
+ }
38
+ if (settings.premium.mode === "off") {
39
+ return { allowed: false, requiresApproval: false, reason: "Premium routing is off in settings." }
40
+ }
41
+ if (mode === "efficiency") {
42
+ return { allowed: false, requiresApproval: false, reason: "Maximum Efficiency locks premium models unless you select one manually." }
43
+ }
44
+ if (session?.premiumOneShot && settings.premium.oneShotApproval) {
45
+ return { allowed: true, requiresApproval: false, reason: "A one-shot premium approval is active for this session." }
46
+ }
47
+ if (mode === "premium" && settings.premium.mode === "allowed" && premium.remainingUsd > 0) {
48
+ return {
49
+ allowed: true,
50
+ requiresApproval: false,
51
+ reason: "Premium Allowed mode has a remaining premium budget.",
52
+ }
53
+ }
54
+ if (premium.remainingUsd > 0 && settings.premium.mode === "allowed") {
55
+ return { allowed: false, requiresApproval: true, reason: "Premium is available but requires this session's approval." }
56
+ }
57
+ return {
58
+ allowed: false,
59
+ requiresApproval: true,
60
+ reason: "Premium escalation is disabled or has no remaining budget; approval is required.",
61
+ }
62
+ }
63
+
64
+ export interface EligibilityInput {
65
+ readonly model: ModelRecord
66
+ readonly task: TaskClass
67
+ readonly settings: RouterSettings
68
+ readonly mode: Mode
69
+ readonly contextTokens: number
70
+ readonly providerQuota?: ProviderQuotaSnapshot
71
+ /**
72
+ * Whether any provider reported fresh quota for this decision. Lets the
73
+ * eligibility check tell "this provider is genuinely fine" apart from "we have
74
+ * not probed anyone yet", which are not the same thing.
75
+ */
76
+ readonly quotaIsKnown?: boolean
77
+ readonly modelQuota?: ModelQuotaSnapshot
78
+ readonly premium: PremiumDecision
79
+ readonly currentModelKey?: string
80
+ readonly allowDesignRelaxation?: boolean
81
+ /** Estimated cost of the request, used by the subscription-only guard. */
82
+ readonly projectedUsd?: number
83
+ /**
84
+ * True when an ordinary-allowance route can serve this task. A model funded
85
+ * from an escalation pool is then refused, so routine work cannot drain the
86
+ * capacity reserved for hard problems.
87
+ */
88
+ readonly escalationPoolProtected?: boolean
89
+ /**
90
+ * The intelligence floor to enforce, when it differs from the configured one.
91
+ *
92
+ * Calibration derives the floor from recorded spend, so the gate cannot read
93
+ * `settings.routing.intelligenceFloor` directly: that value is the ceiling
94
+ * calibration is bounded by, not necessarily the value in force. Passing it
95
+ * explicitly keeps one source of truth for the request in progress.
96
+ */
97
+ readonly intelligenceFloor?: number
98
+ }
99
+
100
+ export interface EligibilityResult {
101
+ readonly eligible: boolean
102
+ readonly reason: string
103
+ readonly warnings: string[]
104
+ /**
105
+ * Why the model was rejected, when that reason is worth reporting to a user.
106
+ * `context` is the case that matters: the model is a good route but the
107
+ * transcript no longer fits, which is a different situation from being
108
+ * unqualified or over budget.
109
+ */
110
+ readonly rejectedFor?: "context"
111
+ readonly contextRequiredTokens?: number
112
+ readonly contextWindowTokens?: number
113
+ }
114
+
115
+ const providerAuthFailure = (snapshot: ProviderQuotaSnapshot | undefined): boolean => {
116
+ if (!snapshot?.error) return false
117
+ return /authoriz|authenticat|unauthorized|forbidden|401|403|credential/i.test(snapshot.error)
118
+ }
119
+
120
+ /**
121
+ * Decide whether a provider's allowance is spent.
122
+ *
123
+ * `quotaIsKnown` says whether ANY provider reported fresh quota this decision.
124
+ * That distinction matters: when every provider is unknown we are blind and must
125
+ * not change behaviour, but when some providers have fresh data and this one does
126
+ * not, the asymmetry is a hole. An exhausted provider whose snapshot has not been
127
+ * probed yet looks identical to one that is fine, so a decision made in the ~1.5s
128
+ * before the quota service starts can route straight into a full allowance. That
129
+ * is exactly what happened on 2026-11-02 when GOAT's five-hour window was at 100%.
130
+ */
131
+ const providerLimitReached = (
132
+ snapshot: ProviderQuotaSnapshot | undefined,
133
+ settings: RouterSettings,
134
+ providerID = "",
135
+ quotaIsKnown = false,
136
+ ): string | undefined => {
137
+ if (!snapshot) {
138
+ // Fail CLOSED only in the one case that is actually a hole: this provider is
139
+ // configured to be probed, another provider already has a real reading, and
140
+ // this one has none yet. That is the window before the quota service starts
141
+ // (~1.5s after plugin load), where an exhausted provider is indistinguishable
142
+ // from a healthy one and a decision can route straight into a full allowance.
143
+ //
144
+ // A provider that is simply not probed is NOT an error: absence from the
145
+ // array is how "we do not track this one" is expressed, and refusing there
146
+ // would break legitimate fallbacks to providers with no quota model at all.
147
+ const providerSettings = settings.providers[providerID]
148
+ const expectedProbe = providerSettings?.enabled === true && providerSettings?.probeUsage === true
149
+ if (expectedProbe && quotaIsKnown && settings.routing?.failClosed !== false) {
150
+ return "Provider quota has not been probed yet; refusing rather than assuming it has room"
151
+ }
152
+ return undefined
153
+ }
154
+ const providerSettings = settings.providers[snapshot.providerID]
155
+ if (!providerSettings) return undefined
156
+ const reserveMonthly = providerSettings.reserveMonthlyPct
157
+ const reserveWeekly = providerSettings.reserveWeeklyPct
158
+ const reserveFive = providerSettings.reserveFiveHourPct
159
+ const extras = snapshot.extraCredits?.totalUsd ?? 0
160
+ // A refusal already outlives the reading that produced it: `exceeded` is
161
+ // checked before anything looks at `stale`, so an allowed-out snapshot keeps
162
+ // blocking until a fresh probe clears the flag. `stale` only softens COST
163
+ // (see providerPressure); it never softens a refusal. Verified by reverting a
164
+ // special-case branch for stale+exceeded and watching the tests still pass,
165
+ // which is how that branch was proved dead and removed.
166
+ const windowExceeded = Boolean(snapshot.monthly.exceeded || snapshot.weekly.exceeded || snapshot.rolling.exceeded)
167
+ if (windowExceeded) {
168
+ if (extras > 0 && providerSettings.allowExtraUsage) return undefined
169
+ if (extras > 0) return "Provider would spend purchased credits; automatic extra-credit use is disabled"
170
+ return "Provider allowance is exhausted"
171
+ }
172
+ // Some providers report a zero remaining balance slightly before they set
173
+ // the window's exceeded flag. Treat the balance as authoritative so a
174
+ // request cannot knowingly roll into purchased credits.
175
+ if (snapshot.monthlyRemainingUsd !== undefined && snapshot.monthlyRemainingUsd <= 0) {
176
+ if (extras > 0 && providerSettings.allowExtraUsage) return undefined
177
+ if (extras > 0) return "Provider included credits are exhausted; automatic extra-credit use is disabled"
178
+ return "Provider allowance is exhausted"
179
+ }
180
+ if (snapshot.monthly.source !== "unknown" && snapshot.monthly.usedPct >= 100 - reserveMonthly) {
181
+ return "Provider monthly allowance is at or near its configured reserve"
182
+ }
183
+ if (snapshot.weekly.source !== "unknown" && snapshot.weekly.usedPct >= 100 - reserveWeekly) {
184
+ return "Provider weekly allowance is at or near its configured reserve"
185
+ }
186
+ if (snapshot.rolling.source !== "unknown" && snapshot.rolling.usedPct >= 100 - reserveFive) {
187
+ return "Provider five-hour allowance is at or near its configured reserve"
188
+ }
189
+ return undefined
190
+ }
191
+
192
+ const modelLimitReached = (quota: ModelQuotaSnapshot | undefined, settings: RouterSettings): string | undefined => {
193
+ if (!quota) return undefined
194
+ const provider = settings.providers[quota.model.providerID]
195
+ if (!provider) return undefined
196
+ if (quota.monthlyAllowanceUsd > 0 && quota.monthlyUsedPct >= 100 - provider.reserveMonthlyPct) {
197
+ return "Estimated model monthly allowance is at or near its configured reserve"
198
+ }
199
+ if (quota.fiveHourCapUsd > 0 && quota.fiveHourUsedPct >= 100 - provider.reserveFiveHourPct) {
200
+ return "Estimated model five-hour allowance is at or near its configured reserve"
201
+ }
202
+ if (quota.weeklyCapUsd > 0 && quota.weeklyUsedPct >= 100 - provider.reserveWeeklyPct) {
203
+ return "Estimated model weekly allowance is at or near its configured reserve"
204
+ }
205
+ if (quota.blocked) return quota.blockReason ?? "Estimated model allowance is exhausted"
206
+ return undefined
207
+ }
208
+
209
+ /**
210
+ * Subscription-only guard.
211
+ *
212
+ * When the provider bills from a subscription allowance and purchased balance
213
+ * is off limits, a request is only routed when it fits inside what is left of
214
+ * the subscription. This is a client-side guard: OpenDesign's gateway makes
215
+ * the final funding decision per request, so the router must never assume it
216
+ * can prevent a server-side wallet charge. It only avoids sending a request
217
+ * that it can already see would not fit.
218
+ */
219
+ const subscriptionOnlyLimit = (
220
+ snapshot: ProviderQuotaSnapshot | undefined,
221
+ settings: RouterSettings,
222
+ projectedUsd: number | undefined,
223
+ ): string | undefined => {
224
+ if (!snapshot) return undefined
225
+ const providerSettings = settings.providers[snapshot.providerID]
226
+ if (!providerSettings?.subscriptionOnly) return undefined
227
+ if (projectedUsd === undefined || !Number.isFinite(projectedUsd) || projectedUsd <= 0) return undefined
228
+
229
+ const windows: ReadonlyArray<{ readonly label: string; readonly window: QuotaWindow }> = [
230
+ { label: "five-hour", window: snapshot.rolling },
231
+ { label: "weekly", window: snapshot.weekly },
232
+ { label: "monthly", window: snapshot.monthly },
233
+ ]
234
+ for (const { label, window } of windows) {
235
+ if (window.source === "unknown") continue
236
+ const cap = window.capUsd
237
+ if (cap === undefined || cap <= 0) continue
238
+ const used = window.usedUsd ?? 0
239
+ const remaining = Math.max(0, cap - used)
240
+ if (projectedUsd > remaining) {
241
+ return `Subscription-only policy: the projected request cost ($${projectedUsd.toFixed(4)}) exceeds the remaining ${label} allowance ($${remaining.toFixed(2)})`
242
+ }
243
+ }
244
+ return undefined
245
+ }
246
+
247
+ /**
248
+ * Policy for a provider whose quota state could not be established.
249
+ */
250
+ const unknownQuotaLimit = (
251
+ snapshot: ProviderQuotaSnapshot | undefined,
252
+ settings: RouterSettings,
253
+ ): string | undefined => {
254
+ if (!snapshot) return undefined
255
+ const providerSettings = settings.providers[snapshot.providerID]
256
+ if (!providerSettings) return undefined
257
+ const state = snapshot.quotaState ?? (snapshot.source === "unknown" ? "unknown" : undefined)
258
+ if (state !== "unknown" && state !== "unavailable") return undefined
259
+ const policy = providerSettings.unknownQuotaPolicy ?? "route"
260
+ if (policy === "route") return undefined
261
+ if (policy === "ask") {
262
+ return "Provider quota state is unknown; the configured policy requires explicit approval before routing here"
263
+ }
264
+ return "Provider quota state is unknown; the configured policy blocks routing here"
265
+ }
266
+
267
+ /**
268
+ * True when the online intelligence floor should be enforced for this model.
269
+ *
270
+ * Offline mode deliberately exempts local models: the owner has chosen to work
271
+ * with what runs on the machine, and no model small enough to fit alongside a
272
+ * simulator scores at the hosted standard floor.
273
+ */
274
+ const substanceFloorApplies = (settings: RouterSettings, model: ModelRecord): boolean => {
275
+ if (settings.offline?.enabled !== true) return true
276
+ return model.providerID !== OFFLINE_PROVIDER_ID
277
+ }
278
+
279
+ /**
280
+ * Hard-requirement gate. A model is eligible only when every requirement is
281
+ * met; expected-cost ranking happens later and never overrides these rules.
282
+ */
283
+ export const evaluateEligibility = (input: EligibilityInput): EligibilityResult => {
284
+ const { model, task, settings, mode, providerQuota, modelQuota, premium } = input
285
+ const warnings: string[] = []
286
+ const providerSettings = settings.providers[model.providerID]
287
+ if (!providerSettings?.enabled) return { eligible: false, reason: "Provider is disabled in router settings", warnings }
288
+ if (providerAuthFailure(providerQuota)) {
289
+ return { eligible: false, reason: "Provider authentication is failing; fix the connection before routing here", warnings }
290
+ }
291
+
292
+ /**
293
+ * Approval is an online-policy concept: it means "may this model serve paid
294
+ * work alongside the hosted floor". A local model is excluded from that list
295
+ * on purpose so it never competes with hosted models when the network is up.
296
+ *
297
+ * Offline mode therefore skips this check for local models. The candidate
298
+ * list has already been restricted to them, so this cannot admit a hosted
299
+ * model that would otherwise be refused; it only stops an unapproved local
300
+ * model from being rejected for a reason that does not apply to it.
301
+ */
302
+ const offlineLocal = settings.offline?.enabled === true && model.providerID === OFFLINE_PROVIDER_ID
303
+ if (!model.approved && !offlineLocal) {
304
+ return { eligible: false, reason: "Model is unclassified or not approved", warnings }
305
+ }
306
+ if (model.training && !settings.privacy.allowTrainingModels) {
307
+ return { eligible: false, reason: "Training-enabled model is blocked by privacy policy", warnings }
308
+ }
309
+ if (model.free && !settings.privacy.allowFreeModels) {
310
+ return { eligible: false, reason: "Free promotional model is blocked by privacy policy", warnings }
311
+ }
312
+ if (task.requiresVision && !model.inputModalities.includes("image")) {
313
+ return { eligible: false, reason: "Task includes an image and this model has no native image input", warnings }
314
+ }
315
+ if (!model.tools) {
316
+ return { eligible: false, reason: "Model does not support tool calling", warnings }
317
+ }
318
+
319
+ // The margin and the safety fraction are shared with the compaction strategy
320
+ // rather than repeated as literals. They used to be written out here as
321
+ // `20_000` and `0.95`, which meant editing the constants in strategy.ts would
322
+ // move the compaction threshold while leaving this routing gate behind, so a
323
+ // session would compact at a different size than the router refuses to route
324
+ // at. For DeepSeek V4.1 Flash that boundary is 975,600 of an advertised
325
+ // 1,048,000 window, and the remaining 72,400 tokens are deliberate headroom
326
+ // for the reply, tokenizer differences, and transcript growth mid-loop.
327
+ const requiredContext =
328
+ Math.max(input.contextTokens, task.scope.promptTokens) + replyMarginFor(model.context)
329
+ if (model.context > 0 && requiredContext > model.context * CONTEXT_SAFE_WINDOW_FRACTION) {
330
+ return {
331
+ eligible: false,
332
+ reason: `Context requirement ${requiredContext.toLocaleString()} exceeds this model's safe window`,
333
+ warnings,
334
+ // Machine-readable so callers can explain *this* rejection without
335
+ // pattern-matching the message above.
336
+ rejectedFor: "context",
337
+ contextRequiredTokens: requiredContext,
338
+ contextWindowTokens: model.context,
339
+ }
340
+ }
341
+
342
+ const substantive = task.tier >= 2
343
+ /**
344
+ * The intelligence floor exists so substantive work reaches a capable model.
345
+ * Offline mode is a deliberate exception: the owner has chosen to work with
346
+ * whatever runs locally, and enforcing the hosted floor would make offline
347
+ * mode permanently unusable, because no 4B model on this machine scores 39.5.
348
+ *
349
+ * The exception is scoped to local models only. A hosted model reached while
350
+ * offline is already refused upstream, so this cannot lower the floor for
351
+ * online routing.
352
+ */
353
+ const offlineFloorApplies = substanceFloorApplies(settings, model)
354
+ const floor = input.intelligenceFloor ?? settings.routing.intelligenceFloor
355
+ if (substantive && offlineFloorApplies && model.intelligence < floor) {
356
+ return {
357
+ eligible: false,
358
+ reason: `Intelligence ${model.intelligence.toFixed(1)} is below the ${settings.routing.intelligenceFloor.toFixed(1)} floor for substantive work`,
359
+ warnings,
360
+ }
361
+ }
362
+ if (task.tier <= 1 && model.tier === 3) {
363
+ return { eligible: false, reason: "Premium models are not used for trivial or mechanical work", warnings }
364
+ }
365
+
366
+ let designFallbackThreshold: number | undefined
367
+ if (task.designIntensive) {
368
+ const threshold = task.sophisticatedDesign ? settings.routing.sophisticatedDesignFloor : settings.routing.designFloor
369
+ if (model.design < threshold) {
370
+ if (input.allowDesignRelaxation) {
371
+ designFallbackThreshold = threshold
372
+ warnings.push(
373
+ `Design capability ${model.design} is below the preferred ${threshold} threshold; selected as the best available fallback`,
374
+ )
375
+ } else {
376
+ return {
377
+ eligible: false,
378
+ reason: `Design capability ${model.design} is below the ${threshold} threshold`,
379
+ warnings,
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ if (model.premium) {
386
+ if (!premium.allowed) {
387
+ return { eligible: false, reason: premium.reason, warnings }
388
+ }
389
+ if (task.tier < 3 && !task.constraints.requestPremium) {
390
+ return { eligible: false, reason: "Premium models are reserved for premium-tier work", warnings }
391
+ }
392
+ }
393
+
394
+ // An escalation pool is capacity, not routine allowance. When an
395
+ // ordinary-allowance route can already serve the task, spending the
396
+ // escalation pool on it converts a cheap request into a scarce one.
397
+ if (model.escalationOnly && input.escalationPoolProtected) {
398
+ return {
399
+ eligible: false,
400
+ reason:
401
+ "This model is funded from an escalation pool, and an ordinary-allowance route can serve this task",
402
+ warnings,
403
+ }
404
+ }
405
+
406
+ const providerLimit = providerLimitReached(providerQuota, settings, model.providerID, input.quotaIsKnown === true)
407
+ if (providerLimit) return { eligible: false, reason: providerLimit, warnings }
408
+ const unknownLimit = unknownQuotaLimit(providerQuota, settings)
409
+ if (unknownLimit) return { eligible: false, reason: unknownLimit, warnings }
410
+ const subscriptionLimit = subscriptionOnlyLimit(providerQuota, settings, input.projectedUsd)
411
+ if (subscriptionLimit) return { eligible: false, reason: subscriptionLimit, warnings }
412
+ const modelLimit = modelLimitReached(modelQuota, settings)
413
+ if (modelLimit) return { eligible: false, reason: modelLimit, warnings }
414
+
415
+ // Maximum Efficiency mode prefers economy for qualifying work but still
416
+ // permits standard models when the task clears the standard floor.
417
+ if (mode === "efficiency" && task.tier >= 2 && model.tier === 1) {
418
+ return { eligible: false, reason: "Economy models cannot handle substantive engineering in Maximum Efficiency mode", warnings }
419
+ }
420
+
421
+ if (designFallbackThreshold !== undefined) {
422
+ return {
423
+ eligible: true,
424
+ reason: `Eligible only as a design fallback: design ${model.design} is below the ${designFallbackThreshold} threshold`,
425
+ warnings,
426
+ }
427
+ }
428
+
429
+ return { eligible: true, reason: "Meets every hard requirement", warnings }
430
+ }
431
+
432
+ /**
433
+ * How close to its reset a window must be for its remaining allowance to count
434
+ * as at risk. Further out than this is ordinary quota, not a fire sale.
435
+ */
436
+ const EXPIRY_HORIZON_MS = 24 * 60 * 60 * 1000
437
+
438
+ /**
439
+ * Nominal duration of each quota window, used to turn "spent so far" into a
440
+ * burn rate.
441
+ *
442
+ * The providers report a reset time but not the window's start, so elapsed time
443
+ * is derived from the window's own length: a weekly pool that resets in 21 hours
444
+ * has been running for 147. This is a modelling assumption and it is the reason
445
+ * the waste estimate is a fraction rather than a certainty.
446
+ */
447
+ const WINDOW_LENGTH_MS: Readonly<Record<"rolling" | "weekly" | "monthly", number>> = {
448
+ rolling: 5 * 60 * 60 * 1000,
449
+ weekly: 7 * 24 * 60 * 60 * 1000,
450
+ monthly: 30 * 24 * 60 * 60 * 1000,
451
+ }
452
+
453
+ /**
454
+ * The fraction of a pool's remaining allowance that is on track to expire unused.
455
+ *
456
+ * Zero means the window is being consumed fast enough that the next request
457
+ * displaces real work. One means the window will reset having used none of what
458
+ * it has left, so spending it costs nothing that was not already lost.
459
+ *
460
+ * Derived from the burn rate rather than from fill level alone. A pool at 87%
461
+ * with 21 hours left is not "about to waste its last 13%" if it has been burning
462
+ * 13% every 21 hours; a pool at 50% with three hours left of a week is.
463
+ */
464
+ export const quotaWasteFraction = (
465
+ snapshot: ProviderQuotaSnapshot | undefined,
466
+ now: number,
467
+ ): number => {
468
+ if (!snapshot) return 0
469
+ let worst = 0
470
+ for (const key of ["rolling", "weekly", "monthly"] as const) {
471
+ const window = snapshot[key]
472
+ if (!window || window.source === "unknown") continue
473
+ if (typeof window.resetsAt !== "number") continue
474
+ const remainingMs = window.resetsAt - now
475
+ // Already reset, or so far out that nothing is at risk today.
476
+ if (remainingMs <= 0 || remainingMs > EXPIRY_HORIZON_MS) continue
477
+ // Nothing has been committed to this pool, so there is nothing to recover.
478
+ if (window.usedPct <= 0) continue
479
+ // Without a dollar figure there is no burn rate to read, and guessing one
480
+ // from the percentage alone would assume a window length we were not told.
481
+ if (typeof window.usedUsd !== "number" || typeof window.capUsd !== "number" || window.capUsd <= 0) continue
482
+
483
+ const lengthMs = WINDOW_LENGTH_MS[key]
484
+ const elapsedMs = Math.max(0, lengthMs - remainingMs)
485
+ // Too early in the window for a burn rate to mean anything: a single request
486
+ // in the first minute would project to an absurd rate.
487
+ if (elapsedMs < 0.05 * lengthMs) continue
488
+
489
+ // This also covers a pool that is already out: with nothing left there is no
490
+ // marginal price to lower, and chasing the last percent would strand a task.
491
+ const remainingUsd = Math.max(0, window.capUsd - window.usedUsd)
492
+ if (remainingUsd <= 0) continue
493
+ const burnPerMs = window.usedUsd / elapsedMs
494
+ const projectedSpend = burnPerMs * remainingMs
495
+ worst = Math.max(worst, Math.min(1, Math.max(0, 1 - projectedSpend / remainingUsd)))
496
+ }
497
+ return worst
498
+ }
499
+
500
+ /**
501
+ * The factor to apply to an estimate for spending expiring prepaid allowance,
502
+ * where one means "this is fresh money and costs full price".
503
+ *
504
+ * This is the near-zero marginal pricing the flat pressure discount could not
505
+ * express. A dollar of prepaid allowance that is on track to be abandoned is
506
+ * worth about its own probability of being lost, so a provider about to waste
507
+ * its pool becomes genuinely competitive against a cheaper provider with no
508
+ * deadline.
509
+ *
510
+ * It is a floor rather than a zero on purpose: a provider priced at nothing
511
+ * would win every comparison regardless of how little headroom it had left, and
512
+ * a request that runs the pool dry mid-task is worse than paying full price
513
+ * elsewhere.
514
+ */
515
+ export const quotaMarginalPriceFactor = (
516
+ snapshot: ProviderQuotaSnapshot | undefined,
517
+ now: number,
518
+ ): number => {
519
+ if (!snapshot) return 1
520
+ // The pool must be able to carry real work. Where a window has headroom
521
+ // recorded, refuse to treat the last few percent as a bargain.
522
+ const tightest = [snapshot.rolling, snapshot.weekly, snapshot.monthly]
523
+ .filter((window) => window.source !== "unknown" && typeof window.resetsAt === "number")
524
+ if (tightest.length === 0) return 1
525
+ const usable = tightest.some((window) => window.remainingPct > 0)
526
+ if (!usable) return 1
527
+ const waste = quotaWasteFraction(snapshot, now)
528
+ if (waste <= 0) return 1
529
+ // Bounded well below one so expiring quota is cheap, never free.
530
+ return 1 - MARGINAL_PRICE_DISCOUNT_MAX * waste
531
+ }
532
+
533
+ /** The most of an estimate that can be forgiven for expiring allowance. */
534
+ const MARGINAL_PRICE_DISCOUNT_MAX = 0.95
535
+
536
+ export const providerPressure = (snapshot: ProviderQuotaSnapshot | undefined, now = Date.now()): number => {
537
+ if (!snapshot) return 1.5
538
+ const allUnknown =
539
+ snapshot.monthly.source === "unknown" && snapshot.weekly.source === "unknown" && snapshot.rolling.source === "unknown"
540
+ if (allUnknown) return 1.5
541
+ const monthly = snapshot.monthly.source === "unknown" ? 0 : snapshot.monthly.usedPct
542
+ const weekly = snapshot.weekly.source === "unknown" ? 0 : snapshot.weekly.usedPct
543
+ const rolling = snapshot.rolling.source === "unknown" ? 0 : snapshot.rolling.usedPct
544
+ const base =
545
+ 1 +
546
+ 1.5 * (monthly / 100) ** 2 +
547
+ 2.5 * (weekly / 100) ** 2 +
548
+ 2.5 * (rolling / 100) ** 2 +
549
+ (snapshot.stale ? 0.15 : 0)
550
+ // Fill level only. Expiring allowance is priced where it belongs, in
551
+ // quotaMarginalPriceFactor: pressure is a multiplier on an already-ranked cost,
552
+ // so expressing urgency here could never close a price gap, and counting it in
553
+ // both places charged the same fact twice.
554
+ return base
555
+ }
556
+
557
+ export const modelPressure = (quota: ModelQuotaSnapshot | undefined): number => {
558
+ if (!quota) return 1
559
+ return (
560
+ 1 +
561
+ 1.5 * (quota.monthlyUsedPct / 100) ** 2 +
562
+ 1 * (quota.fiveHourUsedPct / 100) ** 2 +
563
+ 1 * (quota.weeklyUsedPct / 100) ** 2
564
+ )
565
+ }
566
+
567
+ export const providerUtilization = (snapshot: ProviderQuotaSnapshot | undefined): number => {
568
+ if (!snapshot) return 0
569
+ return Math.max(
570
+ snapshot.monthly.source === "unknown" ? 0 : snapshot.monthly.usedPct,
571
+ snapshot.weekly.source === "unknown" ? 0 : snapshot.weekly.usedPct,
572
+ snapshot.rolling.source === "unknown" ? 0 : snapshot.rolling.usedPct,
573
+ )
574
+ }
575
+
576
+ export const modelKeyFor = (model: ModelRecord) => modelKey(model.providerID, model.modelID)
577
+ export const currentKey = (current: { providerID: string; id: string } | undefined) => keyOfRef(current)
578
+ export const utilizationPct = (used: number, cap: number) => pct(used, cap)