@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.
- package/CHANGELOG.md +286 -0
- package/CONTRIBUTING.md +103 -0
- package/LICENSE +21 -0
- package/README.md +425 -2
- package/SECURITY.md +174 -0
- package/config.example.json +107 -0
- package/dist/npm/index.js +40 -0
- package/docs/ARCHITECTURE.md +104 -0
- package/docs/DEBUGGING-GUI.md +70 -0
- package/docs/INSTALL.md +151 -0
- package/docs/LIMITATIONS.md +230 -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 +114 -0
- package/install +75 -0
- package/package.json +94 -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 +1047 -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 +204 -0
- package/src/core/models/seed.ts +781 -0
- package/src/core/offline/mode.ts +321 -0
- package/src/core/plan-page.ts +390 -0
- package/src/core/plans/catalog.ts +487 -0
- package/src/core/policy/calibration.ts +259 -0
- package/src/core/policy/eligibility.ts +630 -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 +654 -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 +1304 -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 +978 -0
- package/src/core/types.ts +866 -0
- package/src/core/ui.ts +236 -0
- package/src/core/util.ts +114 -0
- package/src/gui/index.tsx +1675 -0
- package/src/gui/viewmodel.ts +456 -0
- package/src/index.ts +33 -0
- package/src/rpc.ts +204 -0
- package/src/server/containment.ts +131 -0
- package/src/server/controller.ts +3405 -0
- package/src/server/route-tracker.ts +60 -0
- package/src/server/shared-core.ts +701 -0
- package/src/tui/index.tsx +168 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared domain types for the router.
|
|
3
|
+
*
|
|
4
|
+
* These types are intentionally provider-neutral. Provider adapters translate
|
|
5
|
+
* live OpenCode and provider metadata into these records.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { OfflineSettings } from "./offline/mode.js"
|
|
9
|
+
|
|
10
|
+
export type Mode = "efficiency" | "balanced" | "premium"
|
|
11
|
+
export type TaskTier = 0 | 1 | 2 | 3
|
|
12
|
+
export type RuntimeMode = "automatic" | "manual" | "paused"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How the router treats conversation size.
|
|
16
|
+
*
|
|
17
|
+
* - `native` never compacts except to avoid a verified overflow.
|
|
18
|
+
* - `conservative` preserves history; compacts only to prevent overflow.
|
|
19
|
+
* - `budget-optimized` the legacy aggressive strategy: compact early to keep
|
|
20
|
+
* a session on the cheapest provider. Kept for small
|
|
21
|
+
* plans, metered APIs and small-window models.
|
|
22
|
+
*/
|
|
23
|
+
export type ContextManagementMode = "native" | "conservative" | "budget-optimized"
|
|
24
|
+
|
|
25
|
+
export type TaskKind =
|
|
26
|
+
| "deterministic"
|
|
27
|
+
| "mechanical"
|
|
28
|
+
| "feature"
|
|
29
|
+
| "debug"
|
|
30
|
+
| "refactor"
|
|
31
|
+
| "design"
|
|
32
|
+
| "architecture"
|
|
33
|
+
| "security"
|
|
34
|
+
| "test"
|
|
35
|
+
| "docs"
|
|
36
|
+
| "unknown"
|
|
37
|
+
|
|
38
|
+
export type FailureClass =
|
|
39
|
+
| "context_overflow"
|
|
40
|
+
| "rate_limit"
|
|
41
|
+
| "network"
|
|
42
|
+
| "auth"
|
|
43
|
+
| "provider_outage"
|
|
44
|
+
| "invalid_request"
|
|
45
|
+
| "image_rejected"
|
|
46
|
+
| "tool"
|
|
47
|
+
| "capability"
|
|
48
|
+
| "interrupted"
|
|
49
|
+
/**
|
|
50
|
+
* The provider refused because the account has no allowance left.
|
|
51
|
+
*
|
|
52
|
+
* Distinguished from `auth` (the credential is wrong) and from
|
|
53
|
+
* `rate_limit` (too fast): here the credential is fine and the request is
|
|
54
|
+
* well formed, there is simply no credit. It matters because it is the one
|
|
55
|
+
* reliable signal available for a provider whose quota cannot be read —
|
|
56
|
+
* retrying cannot help, and the provider should be left alone until its
|
|
57
|
+
* window resets.
|
|
58
|
+
*/
|
|
59
|
+
| "quota_exhausted"
|
|
60
|
+
/**
|
|
61
|
+
* The provider rejected the request shape itself (an Anthropic-protocol
|
|
62
|
+
* discriminator failure is the observed case) before generating anything.
|
|
63
|
+
* Retrying is useless; the router repairs, or fails the turn over to the
|
|
64
|
+
* next best choice.
|
|
65
|
+
*/
|
|
66
|
+
| "prompt_rejected"
|
|
67
|
+
| "unknown"
|
|
68
|
+
|
|
69
|
+
export interface CostTier {
|
|
70
|
+
readonly input: number
|
|
71
|
+
readonly output: number
|
|
72
|
+
readonly cache: { readonly read: number; readonly write: number }
|
|
73
|
+
readonly tier?: { readonly type: "context"; readonly size: number }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PeakPricing {
|
|
77
|
+
/** Multiplier applied to every token rate inside the peak windows. */
|
|
78
|
+
readonly multiplier: number
|
|
79
|
+
/** UTC hour ranges [startHour, endHour); 0-24. */
|
|
80
|
+
readonly windowsUtc: ReadonlyArray<{ readonly startHour: number; readonly endHour: number }>
|
|
81
|
+
/** When true, peak pricing applies Monday-Friday only. */
|
|
82
|
+
readonly weekdaysOnly: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ModelRecord {
|
|
86
|
+
readonly key: string
|
|
87
|
+
readonly providerID: string
|
|
88
|
+
readonly modelID: string
|
|
89
|
+
readonly name: string
|
|
90
|
+
readonly context: number
|
|
91
|
+
readonly maxOutput: number
|
|
92
|
+
readonly inputModalities: ReadonlyArray<string>
|
|
93
|
+
readonly outputModalities: ReadonlyArray<string>
|
|
94
|
+
readonly tools: boolean
|
|
95
|
+
readonly cost: ReadonlyArray<CostTier>
|
|
96
|
+
/** Documented time-of-day pricing, when the provider changes rates. */
|
|
97
|
+
readonly peakPricing?: PeakPricing
|
|
98
|
+
/** Curated aggregate coding-intelligence score. Higher is better. */
|
|
99
|
+
readonly intelligence: number
|
|
100
|
+
/** Curated front-end and UX quality score. */
|
|
101
|
+
readonly design: number
|
|
102
|
+
/**
|
|
103
|
+
* Per-kind capability evidence, when measured benchmarks exist. Absent
|
|
104
|
+
* axes are unknown, never zero: an unscored model stays neutral in
|
|
105
|
+
* ranking rather than being treated as incapable.
|
|
106
|
+
*/
|
|
107
|
+
readonly capabilities?: {
|
|
108
|
+
/** Agentic coding benchmark, percent. */
|
|
109
|
+
readonly coding?: number
|
|
110
|
+
/** Complex-debugging / automation benchmark, percent. */
|
|
111
|
+
readonly debugging?: number
|
|
112
|
+
/** Long-horizon autonomous-execution benchmark, percent. */
|
|
113
|
+
readonly agentic?: number
|
|
114
|
+
/** Curated architectural-reasoning score, same 0-100 scale. */
|
|
115
|
+
readonly architecture?: number
|
|
116
|
+
}
|
|
117
|
+
readonly designEvidence: "benchmark" | "curated" | "observed" | "unverified"
|
|
118
|
+
/**
|
|
119
|
+
* Credit multiplier applied to this model's usage value. One for every
|
|
120
|
+
* provider that bills in plain USD. OpenDesign uses it to convert the
|
|
121
|
+
* dollar value of model usage into Design Plan credits.
|
|
122
|
+
*/
|
|
123
|
+
readonly creditMultiplier?: number
|
|
124
|
+
/** True when the model is funded from a pool reserved for escalation. */
|
|
125
|
+
readonly escalationOnly?: boolean
|
|
126
|
+
readonly premium: boolean
|
|
127
|
+
readonly training: boolean
|
|
128
|
+
readonly free: boolean
|
|
129
|
+
readonly tier: 1 | 2 | 3
|
|
130
|
+
/** False for discovered models that have not been qualified. */
|
|
131
|
+
readonly approved: boolean
|
|
132
|
+
readonly approvalSource: "seed" | "user" | "discovered"
|
|
133
|
+
/** Monthly included usage allowance in US dollars at public list prices. */
|
|
134
|
+
readonly monthlyAllowanceUsd: number
|
|
135
|
+
/**
|
|
136
|
+
* Which wire protocol the provider's serving path speaks. Seeded records
|
|
137
|
+
* may state it; `src/core/models/protocol.ts` otherwise infers it from the
|
|
138
|
+
* model family. Only the Anthropic Messages protocol forces prompt repair.
|
|
139
|
+
*/
|
|
140
|
+
readonly protocol?: "anthropic" | "openai-compat"
|
|
141
|
+
readonly notes?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ModelRef {
|
|
145
|
+
readonly providerID: string
|
|
146
|
+
readonly id: string
|
|
147
|
+
readonly variant?: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ModelKey {
|
|
151
|
+
readonly providerID: string
|
|
152
|
+
readonly modelID: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface TaskScope {
|
|
156
|
+
readonly files: number
|
|
157
|
+
readonly codeBlocks: number
|
|
158
|
+
readonly longestCodeBlockLines: number
|
|
159
|
+
readonly hasStackTrace: boolean
|
|
160
|
+
readonly hasImage: boolean
|
|
161
|
+
readonly hasScreenshot: boolean
|
|
162
|
+
readonly promptChars: number
|
|
163
|
+
readonly promptTokens: number
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface TaskConstraints {
|
|
167
|
+
/** The user explicitly asked to avoid premium or expensive models. */
|
|
168
|
+
readonly avoidPremium: boolean
|
|
169
|
+
/** The user explicitly asked for the strongest model. */
|
|
170
|
+
readonly requestPremium: boolean
|
|
171
|
+
/** The user explicitly asked for a cheap/economy model. */
|
|
172
|
+
readonly requestEconomy: boolean
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface TaskClass {
|
|
176
|
+
readonly tier: TaskTier
|
|
177
|
+
readonly kind: TaskKind
|
|
178
|
+
readonly requiresVision: boolean
|
|
179
|
+
readonly designIntensive: boolean
|
|
180
|
+
readonly sophisticatedDesign: boolean
|
|
181
|
+
readonly needsLongContext: boolean
|
|
182
|
+
readonly confidence: "low" | "medium" | "high"
|
|
183
|
+
readonly signals: ReadonlyArray<string>
|
|
184
|
+
readonly reasons: ReadonlyArray<string>
|
|
185
|
+
readonly constraints: TaskConstraints
|
|
186
|
+
readonly scope: TaskScope
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface CandidateEvaluation {
|
|
190
|
+
readonly model: ModelKey
|
|
191
|
+
readonly name: string
|
|
192
|
+
readonly tier: 1 | 2 | 3
|
|
193
|
+
readonly eligible: boolean
|
|
194
|
+
readonly reason: string
|
|
195
|
+
readonly estimatedUsd: number
|
|
196
|
+
readonly effectiveCost: number
|
|
197
|
+
readonly successProbability: number
|
|
198
|
+
readonly switchPenaltyUsd: number
|
|
199
|
+
readonly quotaPressure: number
|
|
200
|
+
readonly retained: boolean
|
|
201
|
+
/** Credit multiplier applied to the estimate. One for plain USD providers. */
|
|
202
|
+
readonly creditMultiplier?: number
|
|
203
|
+
/** Multiplier applied for measured generation speed. One when neutral. */
|
|
204
|
+
readonly speedFactor?: number
|
|
205
|
+
/**
|
|
206
|
+
* Multiplier applied to the price because the provider's prepaid allowance is
|
|
207
|
+
* expiring. One when the allowance is fresh money that will be consumed.
|
|
208
|
+
*/
|
|
209
|
+
readonly marginalPriceFactor?: number
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface QuotaWindow {
|
|
213
|
+
readonly usedPct: number
|
|
214
|
+
readonly remainingPct: number
|
|
215
|
+
readonly usedUsd?: number
|
|
216
|
+
readonly capUsd?: number
|
|
217
|
+
readonly resetsAt?: number
|
|
218
|
+
readonly exceeded?: boolean
|
|
219
|
+
readonly source: "provider" | "estimate" | "unknown"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface ModelQuotaSnapshot {
|
|
223
|
+
readonly model: ModelKey
|
|
224
|
+
readonly monthlyAllowanceUsd: number
|
|
225
|
+
readonly monthlyUsedUsd: number
|
|
226
|
+
readonly monthlyRemainingUsd: number
|
|
227
|
+
readonly monthlyUsedPct: number
|
|
228
|
+
readonly fiveHourUsedUsd: number
|
|
229
|
+
readonly fiveHourCapUsd: number
|
|
230
|
+
readonly fiveHourUsedPct: number
|
|
231
|
+
readonly weeklyUsedUsd: number
|
|
232
|
+
readonly weeklyCapUsd: number
|
|
233
|
+
readonly weeklyUsedPct: number
|
|
234
|
+
readonly blocked: boolean
|
|
235
|
+
readonly blockReason?: string
|
|
236
|
+
readonly source: "provider" | "estimate" | "unknown"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface ExtraCredits {
|
|
240
|
+
readonly purchasedUsd: number
|
|
241
|
+
readonly freeUsd: number
|
|
242
|
+
readonly totalUsd: number
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* One funding pool belonging to a plan.
|
|
247
|
+
*
|
|
248
|
+
* Pools are separate allowances. `monthlyUsd` is the pool's own ceiling; the
|
|
249
|
+
* weekly and five-hour values nest inside it and are never added to it. A
|
|
250
|
+
* pool with no window values is metered per billing cycle only.
|
|
251
|
+
*/
|
|
252
|
+
export interface CreditPoolSnapshot {
|
|
253
|
+
readonly id: string
|
|
254
|
+
readonly label: string
|
|
255
|
+
readonly source: "provider" | "estimate" | "unknown"
|
|
256
|
+
readonly monthlyUsd: number
|
|
257
|
+
readonly usedUsd: number
|
|
258
|
+
readonly remainingUsd: number
|
|
259
|
+
readonly usedPct: number
|
|
260
|
+
readonly weeklyUsd?: number
|
|
261
|
+
readonly weeklyUsedPct?: number
|
|
262
|
+
readonly fiveHourUsd?: number
|
|
263
|
+
readonly fiveHourUsedPct?: number
|
|
264
|
+
readonly resetKind: "billing-cycle" | "fixed-7-day" | "rolling-7-day"
|
|
265
|
+
readonly resetsAt?: number
|
|
266
|
+
readonly note: string
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface ProviderQuotaSnapshot {
|
|
270
|
+
readonly providerID: string
|
|
271
|
+
readonly label: string
|
|
272
|
+
readonly plan: string
|
|
273
|
+
readonly source: "provider" | "estimate" | "unknown"
|
|
274
|
+
readonly fetchedAt: number
|
|
275
|
+
readonly stale: boolean
|
|
276
|
+
readonly rolling: QuotaWindow
|
|
277
|
+
readonly weekly: QuotaWindow
|
|
278
|
+
readonly monthly: QuotaWindow
|
|
279
|
+
readonly monthlyGrantUsd?: number
|
|
280
|
+
readonly monthlyRemainingUsd?: number
|
|
281
|
+
readonly extraCredits?: ExtraCredits
|
|
282
|
+
/** Every funding pool the plan exposes, in draw order. */
|
|
283
|
+
readonly pools?: ReadonlyArray<CreditPoolSnapshot>
|
|
284
|
+
readonly planStatus?: string
|
|
285
|
+
readonly periodStart?: number
|
|
286
|
+
readonly periodEnd?: number
|
|
287
|
+
readonly models: Readonly<Record<string, ModelQuotaSnapshot>>
|
|
288
|
+
readonly notes: ReadonlyArray<string>
|
|
289
|
+
readonly error?: string
|
|
290
|
+
/** Provider-supplied retry delay on a rate-limited probe. */
|
|
291
|
+
readonly retryAfterMs?: number
|
|
292
|
+
/**
|
|
293
|
+
* Explicit confidence state. `provider` and `estimate` map onto `source`;
|
|
294
|
+
* this field also distinguishes an exhausted allowance from an unavailable
|
|
295
|
+
* service, which `source` alone cannot express.
|
|
296
|
+
*/
|
|
297
|
+
readonly quotaState?: "verified" | "estimated" | "stale" | "unknown" | "exhausted" | "unavailable"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface PremiumState {
|
|
301
|
+
readonly mode: "off" | "ask" | "allowed" | "auto"
|
|
302
|
+
readonly monthlyBudgetUsd: number
|
|
303
|
+
readonly spentUsd: number
|
|
304
|
+
readonly remainingUsd: number
|
|
305
|
+
/**
|
|
306
|
+
* Remaining plan-model pool allowance for the month. In auto mode this, not
|
|
307
|
+
* `remainingUsd`, is what escalation draws against; undefined when the
|
|
308
|
+
* caller has no pool identity for the model in play.
|
|
309
|
+
*/
|
|
310
|
+
readonly planPoolRemainingUsd?: number
|
|
311
|
+
readonly oneShotGranted: boolean
|
|
312
|
+
readonly oneShotGrantedAt?: number
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface RoutingDecision {
|
|
316
|
+
readonly id: string
|
|
317
|
+
readonly at: number
|
|
318
|
+
readonly sessionID: string
|
|
319
|
+
readonly mode: Mode
|
|
320
|
+
readonly dryRun: boolean
|
|
321
|
+
readonly runtime: RuntimeMode
|
|
322
|
+
readonly task: TaskClass
|
|
323
|
+
readonly current?: ModelRef
|
|
324
|
+
readonly chosen?: ModelRef
|
|
325
|
+
readonly switched: boolean
|
|
326
|
+
readonly switchReason: string
|
|
327
|
+
readonly reasons: ReadonlyArray<string>
|
|
328
|
+
readonly warnings: ReadonlyArray<string>
|
|
329
|
+
readonly candidates: ReadonlyArray<CandidateEvaluation>
|
|
330
|
+
readonly premium: PremiumState
|
|
331
|
+
readonly quota: ReadonlyArray<ProviderQuotaSnapshot>
|
|
332
|
+
readonly contextTokens: number
|
|
333
|
+
readonly cachedTokens: number
|
|
334
|
+
readonly estimatedUsd: number
|
|
335
|
+
readonly needsPremiumApproval: boolean
|
|
336
|
+
readonly manualOverride: boolean
|
|
337
|
+
readonly error?: string
|
|
338
|
+
/**
|
|
339
|
+
* Present when the router recovered a deterministic provider rejection for
|
|
340
|
+
* this prompt: which model rejected the request and what happened next.
|
|
341
|
+
*/
|
|
342
|
+
readonly selfheal?: { readonly rejected: string; readonly action: string }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface ModelFailureRecord {
|
|
346
|
+
readonly class: FailureClass
|
|
347
|
+
readonly at: number
|
|
348
|
+
readonly message: string
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface SessionState {
|
|
352
|
+
readonly sessionID: string
|
|
353
|
+
readonly parentID?: string
|
|
354
|
+
readonly projectID?: string
|
|
355
|
+
readonly directory?: string
|
|
356
|
+
manualOverride: boolean
|
|
357
|
+
manualModel?: ModelRef
|
|
358
|
+
pinnedModel?: ModelRef
|
|
359
|
+
premiumOneShot: boolean
|
|
360
|
+
premiumOneShotAt?: number
|
|
361
|
+
paused: boolean
|
|
362
|
+
lastModel?: ModelRef
|
|
363
|
+
lastRoutedModel?: ModelRef
|
|
364
|
+
lastDecisionID?: string
|
|
365
|
+
/** Original user or generated title, without the router status prefix. */
|
|
366
|
+
baseTitle?: string
|
|
367
|
+
/** Last title written by the router, used to avoid update loops. */
|
|
368
|
+
indicatorTitle?: string
|
|
369
|
+
contextTokens: number
|
|
370
|
+
cacheReadTokens: number
|
|
371
|
+
stepCount: number
|
|
372
|
+
status: "idle" | "busy" | "unknown"
|
|
373
|
+
failures: ReadonlyArray<ModelFailureRecord>
|
|
374
|
+
retries: number
|
|
375
|
+
createdAt: number
|
|
376
|
+
updatedAt: number
|
|
377
|
+
title?: string
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface UsageEvent {
|
|
381
|
+
readonly id: string
|
|
382
|
+
readonly at: number
|
|
383
|
+
readonly providerID: string
|
|
384
|
+
readonly modelID: string
|
|
385
|
+
readonly sessionID?: string
|
|
386
|
+
readonly usd: number
|
|
387
|
+
readonly input: number
|
|
388
|
+
readonly output: number
|
|
389
|
+
readonly cacheRead: number
|
|
390
|
+
readonly cacheWrite: number
|
|
391
|
+
readonly source: "step" | "bootstrap" | "manual"
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export interface ModelMetrics {
|
|
395
|
+
requests: number
|
|
396
|
+
input: number
|
|
397
|
+
output: number
|
|
398
|
+
cacheRead: number
|
|
399
|
+
cacheWrite: number
|
|
400
|
+
usd: number
|
|
401
|
+
failures: number
|
|
402
|
+
/** Output tokens produced per second of generation, when measurable. */
|
|
403
|
+
outputTokensPerSecond: number
|
|
404
|
+
/** Milliseconds from request start to a completed response, when measurable. */
|
|
405
|
+
latencyMsTotal: number
|
|
406
|
+
latencySamples: number
|
|
407
|
+
timeoutFailures: number
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export interface Metrics {
|
|
411
|
+
startedAt: number
|
|
412
|
+
requests: number
|
|
413
|
+
inputTokens: number
|
|
414
|
+
cacheReadTokens: number
|
|
415
|
+
cacheWriteTokens: number
|
|
416
|
+
outputTokens: number
|
|
417
|
+
estimatedUsd: number
|
|
418
|
+
switches: number
|
|
419
|
+
escalations: number
|
|
420
|
+
retries: number
|
|
421
|
+
preventedPremium: number
|
|
422
|
+
manualOverrides: number
|
|
423
|
+
dryRunDecisions: number
|
|
424
|
+
premiumRequests: number
|
|
425
|
+
premiumUsd: number
|
|
426
|
+
/** Lifetime spend against the plan-model pool (escalation allowance). */
|
|
427
|
+
planPoolUsd: number
|
|
428
|
+
latencyMsTotal: number
|
|
429
|
+
latencySamples: number
|
|
430
|
+
lastLatencyMs?: number
|
|
431
|
+
failures: Record<FailureClass, number>
|
|
432
|
+
byModel: Record<string, ModelMetrics>
|
|
433
|
+
/** Request-level latency samples, separate from per-model aggregates. */
|
|
434
|
+
timeouts: number
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* A durable per-hour usage bucket.
|
|
439
|
+
*
|
|
440
|
+
* Retaining thousands of raw events in the ledger does not scale: 2,000
|
|
441
|
+
* events covered under seven hours of heavy use, which is useless for weekly
|
|
442
|
+
* or monthly forecasting. Events are folded into these buckets as they age,
|
|
443
|
+
* so long-horizon aggregation costs a bounded number of rows instead of
|
|
444
|
+
* growing without limit, and the totals survive restarts.
|
|
445
|
+
*/
|
|
446
|
+
export interface UsageBucket {
|
|
447
|
+
/** Hour start, epoch milliseconds, truncated to the hour. */
|
|
448
|
+
readonly hour: number
|
|
449
|
+
readonly providerID: string
|
|
450
|
+
readonly modelID: string
|
|
451
|
+
/** Billing period the bucket belongs to, so plan changes are separable. */
|
|
452
|
+
readonly periodID: string
|
|
453
|
+
requests: number
|
|
454
|
+
input: number
|
|
455
|
+
output: number
|
|
456
|
+
cacheRead: number
|
|
457
|
+
cacheWrite: number
|
|
458
|
+
usd: number
|
|
459
|
+
failures: number
|
|
460
|
+
latencyMsTotal: number
|
|
461
|
+
latencySamples: number
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export interface UsageRollup {
|
|
465
|
+
readonly version: 1
|
|
466
|
+
buckets: UsageBucket[]
|
|
467
|
+
/** Raw events newer than the fold horizon, retained for exact per-request detail. */
|
|
468
|
+
recent: UsageEvent[]
|
|
469
|
+
updatedAt: number
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export interface Ledger {
|
|
473
|
+
version: 1
|
|
474
|
+
bootstrappedAt?: number
|
|
475
|
+
bootstrapSource?: string
|
|
476
|
+
usage: UsageEvent[]
|
|
477
|
+
/** Hourly aggregates for anything older than the raw-event horizon. */
|
|
478
|
+
usageRollup?: UsageRollup
|
|
479
|
+
premiumSpend: Array<{ month: string; usd: number }>
|
|
480
|
+
/**
|
|
481
|
+
* Monthly spend against the plan-model pool (the escalation allowance),
|
|
482
|
+
* separate from `premiumSpend` so the pool gate reads exactly what the
|
|
483
|
+
* pool funded. The hydrator always materializes this array, so ledgers
|
|
484
|
+
* written before the field existed read as zero spend.
|
|
485
|
+
*/
|
|
486
|
+
planPoolSpend: Array<{ month: string; usd: number }>
|
|
487
|
+
sessions: Record<string, SessionState>
|
|
488
|
+
decisions: RoutingDecision[]
|
|
489
|
+
metrics: Metrics
|
|
490
|
+
/** Model key to the epoch millisecond when its failure cooldown ends. */
|
|
491
|
+
cooldowns?: Record<string, number>
|
|
492
|
+
/** Provider id to the epoch millisecond when its outage backoff ends. */
|
|
493
|
+
providerCooldowns?: Record<string, number>
|
|
494
|
+
updatedAt: number
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export interface ProviderSettings {
|
|
498
|
+
readonly enabled: boolean
|
|
499
|
+
/**
|
|
500
|
+
* Plan identifier resolved against the plan catalog. A plan is described as
|
|
501
|
+
* data, so a different user's tier needs no code change.
|
|
502
|
+
*/
|
|
503
|
+
readonly plan: string
|
|
504
|
+
readonly reserveMonthlyPct: number
|
|
505
|
+
readonly reserveWeeklyPct: number
|
|
506
|
+
readonly reserveFiveHourPct: number
|
|
507
|
+
readonly allowExtraUsage: boolean
|
|
508
|
+
readonly probeUsage: boolean
|
|
509
|
+
/**
|
|
510
|
+
* Explicit caps. Each overrides the plan catalog value when set, which is
|
|
511
|
+
* how a user can correct a stale catalog entry without a code change.
|
|
512
|
+
*/
|
|
513
|
+
readonly monthlyCreditsUsd?: number
|
|
514
|
+
readonly fiveHourCapUsd?: number
|
|
515
|
+
readonly weeklyCapUsd?: number
|
|
516
|
+
/**
|
|
517
|
+
* When set, every pool is treated as having no window ceiling of the given
|
|
518
|
+
* kind, regardless of what the catalog says. Used to model a plan that has
|
|
519
|
+
* advertised the removal of a window.
|
|
520
|
+
*/
|
|
521
|
+
readonly ignoreFiveHourWindow?: boolean
|
|
522
|
+
readonly ignoreWeeklyWindow?: boolean
|
|
523
|
+
/** Optional explicit credential file for a read-only quota probe. */
|
|
524
|
+
readonly credentialPath?: string
|
|
525
|
+
/**
|
|
526
|
+
* A reading of the account's own usage, supplied by the user, that the router
|
|
527
|
+
* adds its own traffic to.
|
|
528
|
+
*
|
|
529
|
+
* This exists because OpenDesign publishes no quota endpoint for an inference
|
|
530
|
+
* key and the router refuses to scrape a browser session. Without a control
|
|
531
|
+
* key the best available number was "traffic this router routed", which
|
|
532
|
+
* under-reports badly on any account that was used before the router was
|
|
533
|
+
* installed — one user saw 5% while the account was 42% used.
|
|
534
|
+
*
|
|
535
|
+
* A baseline is not a guess: it is a figure the user read off their own
|
|
536
|
+
* account, with the time they read it. The router labels the result as
|
|
537
|
+
* "reported by your account at <time> plus this router's traffic" and drops it
|
|
538
|
+
* once the relevant window has reset, because a stale reading inside a fresh
|
|
539
|
+
* window would be a new lie in the other direction.
|
|
540
|
+
*/
|
|
541
|
+
readonly baseline?: {
|
|
542
|
+
/** Epoch ms when the user read these figures. */
|
|
543
|
+
readonly at: number
|
|
544
|
+
/** Design Plan credits used at that moment, in USD-equivalent. */
|
|
545
|
+
readonly designPlanUsedUsd?: number
|
|
546
|
+
/**
|
|
547
|
+
* Design Plan credits used at that moment, as the percentage the account
|
|
548
|
+
* page displays.
|
|
549
|
+
*
|
|
550
|
+
* The page shows a percentage, not dollars, so asking for a dollar figure
|
|
551
|
+
* asks the user to do conversion arithmetic against a cap they may not know.
|
|
552
|
+
* Taking the percentage and converting it here removes that step and the
|
|
553
|
+
* mistake it invites — reading "42%" as 42% of the month when the page means
|
|
554
|
+
* 42% of the week overstates usage fourfold.
|
|
555
|
+
*/
|
|
556
|
+
readonly designPlanUsedPct?: number
|
|
557
|
+
/** Plan model credits used at that moment, as the percentage shown. */
|
|
558
|
+
readonly planModelsUsedPct?: number
|
|
559
|
+
/**
|
|
560
|
+
* Which window a supplied percentage refers to. Defaults to `weekly`,
|
|
561
|
+
* because that is the window OpenDesign's account page reports.
|
|
562
|
+
*/
|
|
563
|
+
readonly pctOf?: "weekly" | "monthly"
|
|
564
|
+
/** Plan model credits used at that moment, in USD-equivalent. */
|
|
565
|
+
readonly planModelsUsedUsd?: number
|
|
566
|
+
/**
|
|
567
|
+
* When the seven-day window resets, if the account page showed it.
|
|
568
|
+
*
|
|
569
|
+
* Without it the router has to guess, and the guess is wrong in a way that
|
|
570
|
+
* matters: "the reading is younger than seven days" is not the same as "the
|
|
571
|
+
* window has not reset". A weekly window can reset an hour after you read
|
|
572
|
+
* the page, after which your 42% describes a window that no longer exists
|
|
573
|
+
* and seeding it into the new one overstates usage.
|
|
574
|
+
*/
|
|
575
|
+
readonly weeklyResetsAt?: number
|
|
576
|
+
/** When the monthly allowance resets, if the account page showed it. */
|
|
577
|
+
readonly monthlyResetsAt?: number
|
|
578
|
+
/** Where the reading came from, for display. Defaults to the account page. */
|
|
579
|
+
readonly source?: string
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Whether the router may read well-known credential files without being
|
|
583
|
+
* told exactly where. `explicit` (the default) reads only configured paths
|
|
584
|
+
* and the environment; `auto` also scans the provider's own files; `off`
|
|
585
|
+
* reads nothing from disk.
|
|
586
|
+
*
|
|
587
|
+
* The default is deliberately conservative: automatically harvesting
|
|
588
|
+
* secrets from predictable paths is not something a router should do
|
|
589
|
+
* without the operator saying so.
|
|
590
|
+
*/
|
|
591
|
+
readonly credentialSource?: "explicit" | "auto" | "off"
|
|
592
|
+
/**
|
|
593
|
+
* Gateway base URL. Only providers with a configurable endpoint use it.
|
|
594
|
+
* OpenDesign accepts the OpenAI-compatible gateway root; a path that
|
|
595
|
+
* already contains the version is never extended again.
|
|
596
|
+
*/
|
|
597
|
+
readonly baseURL?: string
|
|
598
|
+
/** Account-control base URL, used only for an explicitly supplied control key. */
|
|
599
|
+
readonly controlBaseURL?: string
|
|
600
|
+
/** Optional explicit file holding an account-control key. */
|
|
601
|
+
readonly controlKeyPath?: string
|
|
602
|
+
/**
|
|
603
|
+
* Refuse to route when the subscription allowance is exhausted, so the
|
|
604
|
+
* gateway cannot fall back to purchased wallet balance.
|
|
605
|
+
*/
|
|
606
|
+
readonly subscriptionOnly?: boolean
|
|
607
|
+
/** Credits per USD, when the provider denominates its allowance in credits. */
|
|
608
|
+
readonly creditsPerUsd?: number
|
|
609
|
+
/** Safety margin applied to locally estimated usage, in percent. */
|
|
610
|
+
readonly uncertaintyMarginPct?: number
|
|
611
|
+
/** What to do when the provider's quota state cannot be established. */
|
|
612
|
+
readonly unknownQuotaPolicy?: "route" | "block" | "ask"
|
|
613
|
+
/** Explicit per-model credit multiplier overrides. */
|
|
614
|
+
readonly multiplierOverrides?: Readonly<Record<string, number>>
|
|
615
|
+
/**
|
|
616
|
+
* True when the provider's allowance is one shared pool rather than a
|
|
617
|
+
* per-model allowance. Per-model quota snapshots are skipped in that case,
|
|
618
|
+
* because they would count the same pool more than once.
|
|
619
|
+
*/
|
|
620
|
+
readonly providerScopedQuota?: boolean
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export interface PrivacySettings {
|
|
624
|
+
readonly allowTrainingModels: boolean
|
|
625
|
+
readonly allowFreeModels: boolean
|
|
626
|
+
readonly requireZdr: boolean
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export interface PremiumSettings {
|
|
630
|
+
readonly mode: "off" | "ask" | "allowed" | "auto"
|
|
631
|
+
readonly monthlyBudgetUsd: number
|
|
632
|
+
readonly oneShotApproval: boolean
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export interface RoutingSettings {
|
|
636
|
+
readonly intelligenceFloor: number
|
|
637
|
+
readonly designFloor: number
|
|
638
|
+
readonly sophisticatedDesignFloor: number
|
|
639
|
+
readonly switchHysteresisPct: number
|
|
640
|
+
readonly largeContextTokens: number
|
|
641
|
+
readonly largeContextHysteresisPct: number
|
|
642
|
+
readonly maxEscalationsPerTask: number
|
|
643
|
+
readonly preferSameProvider: boolean
|
|
644
|
+
/**
|
|
645
|
+
* Block a prompt when this plugin cannot produce a routing decision.
|
|
646
|
+
*
|
|
647
|
+
* Default false, and that default is the point: routing is an optimization,
|
|
648
|
+
* and a defect inside the router must degrade to "do not route", never to
|
|
649
|
+
* "do not work". An internal error leaves the current model in place and lets
|
|
650
|
+
* the prompt through.
|
|
651
|
+
*
|
|
652
|
+
* This does NOT govern quota safety. A provider that is configured to be
|
|
653
|
+
* probed but has no reading is always refused, because that is a spending
|
|
654
|
+
* decision about a provider the router cannot see, not a response to its own
|
|
655
|
+
* failure. Set this true only if you would rather be stopped than pay for an
|
|
656
|
+
* unrouted request.
|
|
657
|
+
*/
|
|
658
|
+
readonly failClosed: boolean
|
|
659
|
+
/**
|
|
660
|
+
* Refuse to route a task to a model funded from a pool reserved for
|
|
661
|
+
* escalation while a design-plan-funded model can serve it. This keeps a
|
|
662
|
+
* plan-model pool from being drained by routine work.
|
|
663
|
+
*/
|
|
664
|
+
readonly protectEscalationPool: boolean
|
|
665
|
+
/** Minimum model window considered adequate when a task needs long context. */
|
|
666
|
+
readonly longContextFloorTokens: number
|
|
667
|
+
/**
|
|
668
|
+
* Derive the intelligence floor from recorded spend instead of trusting the
|
|
669
|
+
* hand-picked `intelligenceFloor`.
|
|
670
|
+
*
|
|
671
|
+
* The configured floor remains the ceiling: calibration may only lower it.
|
|
672
|
+
* This is opt-in because it changes which model serves real work, and a
|
|
673
|
+
* router that silently re-derives its own quality gate on upgrade would be
|
|
674
|
+
* surprising.
|
|
675
|
+
*/
|
|
676
|
+
readonly calibrateFloor?: boolean
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export interface QuotaSettings {
|
|
680
|
+
readonly probeOpenCodeGo: boolean
|
|
681
|
+
readonly probeCommandCode: boolean
|
|
682
|
+
readonly probeOpenDesign: boolean
|
|
683
|
+
readonly probeIntervalMinutes: number
|
|
684
|
+
readonly bootstrapSessions: number
|
|
685
|
+
readonly bootstrapDays: number
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export interface ContextHealthSettings {
|
|
689
|
+
readonly enabled: boolean
|
|
690
|
+
readonly warnAtTokens: number
|
|
691
|
+
readonly recommendAtTokens: number
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Router-driven context management.
|
|
696
|
+
*
|
|
697
|
+
* `compactAtTokens` is only consulted in `budget-optimized` mode. It exists so
|
|
698
|
+
* a budget-conscious user can set an explicit early threshold; it is
|
|
699
|
+
* deliberately ignored in the other two modes so a leftover value from an old
|
|
700
|
+
* configuration can never reintroduce eager compaction.
|
|
701
|
+
*/
|
|
702
|
+
export interface ContextManagementSettings {
|
|
703
|
+
readonly mode: ContextManagementMode
|
|
704
|
+
readonly enabled: boolean
|
|
705
|
+
readonly compactAtTokens: number
|
|
706
|
+
/** Minimum time between router-initiated compactions for one session. */
|
|
707
|
+
readonly cooldownMinutes: number
|
|
708
|
+
/**
|
|
709
|
+
* When `native` mode hits the verified window, allow one emergency
|
|
710
|
+
* compaction rather than failing the turn. Disabling it means the host's own
|
|
711
|
+
* handling is the only recovery.
|
|
712
|
+
*/
|
|
713
|
+
readonly allowEmergencyCompaction: boolean
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** Measured-speed awareness in routing. */
|
|
717
|
+
export interface SpeedSettings {
|
|
718
|
+
readonly enabled: boolean
|
|
719
|
+
/**
|
|
720
|
+
* How much a slow route is penalised relative to the fastest eligible one.
|
|
721
|
+
* Zero treats speed as irrelevant; one doubles the effective cost of a
|
|
722
|
+
* route that is twice as slow as the fastest.
|
|
723
|
+
*/
|
|
724
|
+
readonly weightPct: number
|
|
725
|
+
/** Samples required before measured speed is trusted over a neutral prior. */
|
|
726
|
+
readonly minSamples: number
|
|
727
|
+
/** A route slower than this multiple of the fastest is heavily penalised. */
|
|
728
|
+
readonly slowRouteMultiple: number
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export interface UiSettings {
|
|
732
|
+
/** Send a native macOS notification when the provider or model changes. */
|
|
733
|
+
readonly notifications: boolean
|
|
734
|
+
/** Use a shorter, denser indicator title and UI chrome (compact mode). */
|
|
735
|
+
readonly compactMode: boolean
|
|
736
|
+
/** Alias for compactMode. */
|
|
737
|
+
readonly compact: boolean
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export interface LoggingSettings {
|
|
741
|
+
readonly storePromptText: boolean
|
|
742
|
+
readonly decisionLogSize: number
|
|
743
|
+
readonly diagnostics: boolean
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export interface ModelDefinition {
|
|
747
|
+
readonly key: string
|
|
748
|
+
readonly providerID: string
|
|
749
|
+
readonly modelID: string
|
|
750
|
+
readonly name: string
|
|
751
|
+
readonly context: number
|
|
752
|
+
readonly maxOutput: number
|
|
753
|
+
readonly input: ReadonlyArray<string>
|
|
754
|
+
readonly cost: ReadonlyArray<CostTier>
|
|
755
|
+
readonly intelligence: number
|
|
756
|
+
readonly design: number
|
|
757
|
+
readonly premium?: boolean
|
|
758
|
+
readonly training?: boolean
|
|
759
|
+
readonly free?: boolean
|
|
760
|
+
readonly tier: 1 | 2 | 3
|
|
761
|
+
readonly approved?: boolean
|
|
762
|
+
readonly monthlyAllowanceUsd: number
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export interface ModelSettings {
|
|
766
|
+
readonly approved: ReadonlyArray<string>
|
|
767
|
+
readonly excluded: ReadonlyArray<string>
|
|
768
|
+
readonly tierOverrides: Readonly<Record<string, 1 | 2 | 3>>
|
|
769
|
+
/** User-defined metadata for qualifying models that are not in the seed. */
|
|
770
|
+
readonly definitions: ReadonlyArray<ModelDefinition>
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export interface RouterSettings {
|
|
774
|
+
readonly enabled: boolean
|
|
775
|
+
readonly mode: Mode
|
|
776
|
+
readonly dryRun: boolean
|
|
777
|
+
/** Top-level alias for ui.compactMode (compact mode toggle). */
|
|
778
|
+
readonly compactMode?: boolean
|
|
779
|
+
/** Top-level alias for ui.compactMode (compact mode toggle). */
|
|
780
|
+
readonly compact?: boolean
|
|
781
|
+
readonly providers: Readonly<Record<string, ProviderSettings>>
|
|
782
|
+
readonly privacy: PrivacySettings
|
|
783
|
+
readonly premium: PremiumSettings
|
|
784
|
+
readonly routing: RoutingSettings
|
|
785
|
+
readonly quota: QuotaSettings
|
|
786
|
+
readonly contextHealth: ContextHealthSettings
|
|
787
|
+
readonly contextManagement: ContextManagementSettings
|
|
788
|
+
readonly speed: SpeedSettings
|
|
789
|
+
readonly ui: UiSettings
|
|
790
|
+
readonly logging: LoggingSettings
|
|
791
|
+
readonly models: ModelSettings
|
|
792
|
+
/**
|
|
793
|
+
* Offline development mode. Absent means the defaults, which keep it off.
|
|
794
|
+
*
|
|
795
|
+
* This is a separate top-level block rather than a field inside `routing`
|
|
796
|
+
* so that enabling offline mode cannot alter a single online routing value.
|
|
797
|
+
*/
|
|
798
|
+
readonly offline?: OfflineSettings
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
export interface ContextHealth {
|
|
802
|
+
readonly tokens: number
|
|
803
|
+
readonly cacheRead: number
|
|
804
|
+
readonly cacheSharePct: number
|
|
805
|
+
readonly state: "healthy" | "watch" | "recommend_new_session"
|
|
806
|
+
readonly message: string
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export interface RouterStatus {
|
|
810
|
+
readonly enabled: boolean
|
|
811
|
+
readonly mode: Mode
|
|
812
|
+
readonly runtime: RuntimeMode
|
|
813
|
+
readonly dryRun: boolean
|
|
814
|
+
readonly sessionID?: string
|
|
815
|
+
readonly current?: ModelRef
|
|
816
|
+
readonly currentTier?: 1 | 2 | 3
|
|
817
|
+
readonly manualOverride: boolean
|
|
818
|
+
readonly pinnedModel?: ModelRef
|
|
819
|
+
readonly premium: PremiumState
|
|
820
|
+
readonly quota: ReadonlyArray<ProviderQuotaSnapshot>
|
|
821
|
+
readonly context: ContextHealth
|
|
822
|
+
readonly indicator: {
|
|
823
|
+
readonly state: "AUTO" | "MANUAL" | "PAUSED" | "DRY RUN" | "BLOCKED"
|
|
824
|
+
readonly title?: string
|
|
825
|
+
readonly provider?: string
|
|
826
|
+
readonly model?: string
|
|
827
|
+
readonly modelLabel?: string
|
|
828
|
+
}
|
|
829
|
+
readonly lastDecision?: RoutingDecision
|
|
830
|
+
readonly lastDecisions: ReadonlyArray<RoutingDecision>
|
|
831
|
+
readonly metrics: Metrics
|
|
832
|
+
readonly approvedModels: ReadonlyArray<{
|
|
833
|
+
key: string
|
|
834
|
+
tier: 1 | 2 | 3
|
|
835
|
+
name: string
|
|
836
|
+
intelligence: number
|
|
837
|
+
design: number
|
|
838
|
+
vision: boolean
|
|
839
|
+
premium: boolean
|
|
840
|
+
}>
|
|
841
|
+
/**
|
|
842
|
+
* Every model in the catalog, with the facts a routing decision turns on.
|
|
843
|
+
*
|
|
844
|
+
* The panel needs to answer "why is this model not being used" without the
|
|
845
|
+
* user reading the source. That means carrying approval and exclusion, not
|
|
846
|
+
* just the approved subset, plus the cost and window that drove the ranking.
|
|
847
|
+
*/
|
|
848
|
+
readonly models: ReadonlyArray<{
|
|
849
|
+
readonly key: string
|
|
850
|
+
readonly providerID: string
|
|
851
|
+
readonly name: string
|
|
852
|
+
readonly tier: 1 | 2 | 3
|
|
853
|
+
readonly intelligence: number
|
|
854
|
+
readonly design: number
|
|
855
|
+
readonly vision: boolean
|
|
856
|
+
readonly premium: boolean
|
|
857
|
+
readonly context: number
|
|
858
|
+
readonly costInput: number
|
|
859
|
+
readonly costOutput: number
|
|
860
|
+
readonly approved: boolean
|
|
861
|
+
readonly excluded: boolean
|
|
862
|
+
/** Why it is not approved, when that is a decision rather than a gap. */
|
|
863
|
+
readonly approvalNote?: string
|
|
864
|
+
}>
|
|
865
|
+
readonly warnings: ReadonlyArray<string>
|
|
866
|
+
}
|