omp-llm-role 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -13
- package/docs/dev/architecture.md +35 -11
- package/docs/dev/ci.md +21 -1
- package/docs/dev/current-state.md +99 -27
- package/docs/dev/data-sources.md +33 -4
- package/docs/dev/explorer.md +126 -21
- package/docs/dev/quirks.md +32 -0
- package/docs/dev/releasing.md +63 -32
- package/docs/dev/scoring.md +153 -39
- package/docs/dev/testing.md +112 -24
- package/docs/dev/writes-and-state.md +86 -0
- package/docs/llm-role-rankings.md +126 -126
- package/package.json +92 -22
- package/src/agent-create.ts +4 -2
- package/src/availability.ts +87 -9
- package/src/cli/create-role.ts +1 -1
- package/src/engine.ts +190 -78
- package/src/explorer/boot.ts +43 -1
- package/src/explorer/explain.ts +113 -10
- package/src/explorer/levers.ts +100 -0
- package/src/explorer/pool.ts +46 -0
- package/src/explorer/server.ts +107 -23
- package/src/extension.ts +189 -16
- package/src/features.ts +9 -7
- package/src/provider-display.ts +67 -0
- package/src/settings.ts +39 -2
- package/src/updater.ts +97 -14
- package/src/usage-ledger.ts +127 -0
- package/web/app.js +156 -1
- package/web/style.css +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-llm-role",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "omp plugin: daily model-role updater driven by llm-stats/OpenRouter rankings",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,6 +78,16 @@
|
|
|
78
78
|
"auto"
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
|
+
"tokenProfile.inputTokens": {
|
|
82
|
+
"type": "number",
|
|
83
|
+
"description": "Input tokens per task for the per-task price (used when the role has no recorded usage)",
|
|
84
|
+
"min": 0
|
|
85
|
+
},
|
|
86
|
+
"tokenProfile.outputTokens": {
|
|
87
|
+
"type": "number",
|
|
88
|
+
"description": "Output tokens per task for the per-task price (used when the role has no recorded usage)",
|
|
89
|
+
"min": 0
|
|
90
|
+
},
|
|
81
91
|
"features.endpointCeilings": {
|
|
82
92
|
"type": "boolean",
|
|
83
93
|
"description": "Drop routes that cannot call tools and routes whose output ceiling is below 16 384 tokens",
|
|
@@ -90,7 +100,7 @@
|
|
|
90
100
|
},
|
|
91
101
|
"features.providerPinning": {
|
|
92
102
|
"type": "boolean",
|
|
93
|
-
"description": "
|
|
103
|
+
"description": "Bind each request to the model's best provider route (per-model policy; never drops a model) and prefer routes at or above 50 tok/s",
|
|
94
104
|
"default": false
|
|
95
105
|
},
|
|
96
106
|
"features.costCap": {
|
|
@@ -147,7 +157,7 @@
|
|
|
147
157
|
},
|
|
148
158
|
"roles.default.features.providerPinning": {
|
|
149
159
|
"type": "boolean",
|
|
150
|
-
"description": "Enable the
|
|
160
|
+
"description": "Enable the Best-route provider pinning capability for the default role",
|
|
151
161
|
"default": false
|
|
152
162
|
},
|
|
153
163
|
"roles.default.features.costCap": {
|
|
@@ -157,7 +167,7 @@
|
|
|
157
167
|
},
|
|
158
168
|
"roles.default.preferOwnProvider": {
|
|
159
169
|
"type": "boolean",
|
|
160
|
-
"description": "
|
|
170
|
+
"description": "Bind default requests to each model's best provider route (soft preference; never drops a model)",
|
|
161
171
|
"default": false
|
|
162
172
|
},
|
|
163
173
|
"roles.default.required": {
|
|
@@ -193,6 +203,12 @@
|
|
|
193
203
|
"default": 0,
|
|
194
204
|
"min": 0
|
|
195
205
|
},
|
|
206
|
+
"roles.default.filters.preferredMinThroughput": {
|
|
207
|
+
"type": "number",
|
|
208
|
+
"description": "Soft throughput floor for default routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
209
|
+
"default": 0,
|
|
210
|
+
"min": 0
|
|
211
|
+
},
|
|
196
212
|
"roles.default.lambda": {
|
|
197
213
|
"type": "number",
|
|
198
214
|
"description": "Explicit λ ($ per quality point) override for default",
|
|
@@ -295,7 +311,7 @@
|
|
|
295
311
|
},
|
|
296
312
|
"roles.smol.features.providerPinning": {
|
|
297
313
|
"type": "boolean",
|
|
298
|
-
"description": "Enable the
|
|
314
|
+
"description": "Enable the Best-route provider pinning capability for the smol role",
|
|
299
315
|
"default": false
|
|
300
316
|
},
|
|
301
317
|
"roles.smol.features.costCap": {
|
|
@@ -305,7 +321,7 @@
|
|
|
305
321
|
},
|
|
306
322
|
"roles.smol.preferOwnProvider": {
|
|
307
323
|
"type": "boolean",
|
|
308
|
-
"description": "
|
|
324
|
+
"description": "Bind smol requests to each model's best provider route (soft preference; never drops a model)",
|
|
309
325
|
"default": false
|
|
310
326
|
},
|
|
311
327
|
"roles.smol.required": {
|
|
@@ -341,6 +357,12 @@
|
|
|
341
357
|
"default": 0,
|
|
342
358
|
"min": 0
|
|
343
359
|
},
|
|
360
|
+
"roles.smol.filters.preferredMinThroughput": {
|
|
361
|
+
"type": "number",
|
|
362
|
+
"description": "Soft throughput floor for smol routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
363
|
+
"default": 0,
|
|
364
|
+
"min": 0
|
|
365
|
+
},
|
|
344
366
|
"roles.smol.lambda": {
|
|
345
367
|
"type": "number",
|
|
346
368
|
"description": "Explicit λ ($ per quality point) override for smol",
|
|
@@ -435,7 +457,7 @@
|
|
|
435
457
|
},
|
|
436
458
|
"roles.slow.features.providerPinning": {
|
|
437
459
|
"type": "boolean",
|
|
438
|
-
"description": "Enable the
|
|
460
|
+
"description": "Enable the Best-route provider pinning capability for the slow role",
|
|
439
461
|
"default": false
|
|
440
462
|
},
|
|
441
463
|
"roles.slow.features.costCap": {
|
|
@@ -445,7 +467,7 @@
|
|
|
445
467
|
},
|
|
446
468
|
"roles.slow.preferOwnProvider": {
|
|
447
469
|
"type": "boolean",
|
|
448
|
-
"description": "
|
|
470
|
+
"description": "Bind slow requests to each model's best provider route (soft preference; never drops a model)",
|
|
449
471
|
"default": false
|
|
450
472
|
},
|
|
451
473
|
"roles.slow.required": {
|
|
@@ -481,6 +503,12 @@
|
|
|
481
503
|
"default": 0,
|
|
482
504
|
"min": 0
|
|
483
505
|
},
|
|
506
|
+
"roles.slow.filters.preferredMinThroughput": {
|
|
507
|
+
"type": "number",
|
|
508
|
+
"description": "Soft throughput floor for slow routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
509
|
+
"default": 0,
|
|
510
|
+
"min": 0
|
|
511
|
+
},
|
|
484
512
|
"roles.slow.lambda": {
|
|
485
513
|
"type": "number",
|
|
486
514
|
"description": "Explicit λ ($ per quality point) override for slow",
|
|
@@ -575,7 +603,7 @@
|
|
|
575
603
|
},
|
|
576
604
|
"roles.vision.features.providerPinning": {
|
|
577
605
|
"type": "boolean",
|
|
578
|
-
"description": "Enable the
|
|
606
|
+
"description": "Enable the Best-route provider pinning capability for the vision role",
|
|
579
607
|
"default": false
|
|
580
608
|
},
|
|
581
609
|
"roles.vision.features.costCap": {
|
|
@@ -585,7 +613,7 @@
|
|
|
585
613
|
},
|
|
586
614
|
"roles.vision.preferOwnProvider": {
|
|
587
615
|
"type": "boolean",
|
|
588
|
-
"description": "
|
|
616
|
+
"description": "Bind vision requests to each model's best provider route (soft preference; never drops a model)",
|
|
589
617
|
"default": false
|
|
590
618
|
},
|
|
591
619
|
"roles.vision.required": {
|
|
@@ -621,6 +649,12 @@
|
|
|
621
649
|
"default": 0,
|
|
622
650
|
"min": 0
|
|
623
651
|
},
|
|
652
|
+
"roles.vision.filters.preferredMinThroughput": {
|
|
653
|
+
"type": "number",
|
|
654
|
+
"description": "Soft throughput floor for vision routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
655
|
+
"default": 0,
|
|
656
|
+
"min": 0
|
|
657
|
+
},
|
|
624
658
|
"roles.vision.lambda": {
|
|
625
659
|
"type": "number",
|
|
626
660
|
"description": "Explicit λ ($ per quality point) override for vision",
|
|
@@ -723,7 +757,7 @@
|
|
|
723
757
|
},
|
|
724
758
|
"roles.plan.features.providerPinning": {
|
|
725
759
|
"type": "boolean",
|
|
726
|
-
"description": "Enable the
|
|
760
|
+
"description": "Enable the Best-route provider pinning capability for the plan role",
|
|
727
761
|
"default": false
|
|
728
762
|
},
|
|
729
763
|
"roles.plan.features.costCap": {
|
|
@@ -733,7 +767,7 @@
|
|
|
733
767
|
},
|
|
734
768
|
"roles.plan.preferOwnProvider": {
|
|
735
769
|
"type": "boolean",
|
|
736
|
-
"description": "
|
|
770
|
+
"description": "Bind plan requests to each model's best provider route (soft preference; never drops a model)",
|
|
737
771
|
"default": false
|
|
738
772
|
},
|
|
739
773
|
"roles.plan.required": {
|
|
@@ -769,6 +803,12 @@
|
|
|
769
803
|
"default": 0,
|
|
770
804
|
"min": 0
|
|
771
805
|
},
|
|
806
|
+
"roles.plan.filters.preferredMinThroughput": {
|
|
807
|
+
"type": "number",
|
|
808
|
+
"description": "Soft throughput floor for plan routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
809
|
+
"default": 0,
|
|
810
|
+
"min": 0
|
|
811
|
+
},
|
|
772
812
|
"roles.plan.lambda": {
|
|
773
813
|
"type": "number",
|
|
774
814
|
"description": "Explicit λ ($ per quality point) override for plan",
|
|
@@ -855,7 +895,7 @@
|
|
|
855
895
|
},
|
|
856
896
|
"roles.commit.features.providerPinning": {
|
|
857
897
|
"type": "boolean",
|
|
858
|
-
"description": "Enable the
|
|
898
|
+
"description": "Enable the Best-route provider pinning capability for the commit role",
|
|
859
899
|
"default": false
|
|
860
900
|
},
|
|
861
901
|
"roles.commit.features.costCap": {
|
|
@@ -865,7 +905,7 @@
|
|
|
865
905
|
},
|
|
866
906
|
"roles.commit.preferOwnProvider": {
|
|
867
907
|
"type": "boolean",
|
|
868
|
-
"description": "
|
|
908
|
+
"description": "Bind commit requests to each model's best provider route (soft preference; never drops a model)",
|
|
869
909
|
"default": false
|
|
870
910
|
},
|
|
871
911
|
"roles.commit.required": {
|
|
@@ -901,6 +941,12 @@
|
|
|
901
941
|
"default": 0,
|
|
902
942
|
"min": 0
|
|
903
943
|
},
|
|
944
|
+
"roles.commit.filters.preferredMinThroughput": {
|
|
945
|
+
"type": "number",
|
|
946
|
+
"description": "Soft throughput floor for commit routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
947
|
+
"default": 0,
|
|
948
|
+
"min": 0
|
|
949
|
+
},
|
|
904
950
|
"roles.commit.lambda": {
|
|
905
951
|
"type": "number",
|
|
906
952
|
"description": "Explicit λ ($ per quality point) override for commit",
|
|
@@ -987,7 +1033,7 @@
|
|
|
987
1033
|
},
|
|
988
1034
|
"roles.tiny.features.providerPinning": {
|
|
989
1035
|
"type": "boolean",
|
|
990
|
-
"description": "Enable the
|
|
1036
|
+
"description": "Enable the Best-route provider pinning capability for the tiny role",
|
|
991
1037
|
"default": false
|
|
992
1038
|
},
|
|
993
1039
|
"roles.tiny.features.costCap": {
|
|
@@ -997,7 +1043,7 @@
|
|
|
997
1043
|
},
|
|
998
1044
|
"roles.tiny.preferOwnProvider": {
|
|
999
1045
|
"type": "boolean",
|
|
1000
|
-
"description": "
|
|
1046
|
+
"description": "Bind tiny requests to each model's best provider route (soft preference; never drops a model)",
|
|
1001
1047
|
"default": false
|
|
1002
1048
|
},
|
|
1003
1049
|
"roles.tiny.required": {
|
|
@@ -1033,6 +1079,12 @@
|
|
|
1033
1079
|
"default": 0,
|
|
1034
1080
|
"min": 0
|
|
1035
1081
|
},
|
|
1082
|
+
"roles.tiny.filters.preferredMinThroughput": {
|
|
1083
|
+
"type": "number",
|
|
1084
|
+
"description": "Soft throughput floor for tiny routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
1085
|
+
"default": 0,
|
|
1086
|
+
"min": 0
|
|
1087
|
+
},
|
|
1036
1088
|
"roles.tiny.lambda": {
|
|
1037
1089
|
"type": "number",
|
|
1038
1090
|
"description": "Explicit λ ($ per quality point) override for tiny",
|
|
@@ -1111,7 +1163,7 @@
|
|
|
1111
1163
|
},
|
|
1112
1164
|
"roles.task.features.providerPinning": {
|
|
1113
1165
|
"type": "boolean",
|
|
1114
|
-
"description": "Enable the
|
|
1166
|
+
"description": "Enable the Best-route provider pinning capability for the task role",
|
|
1115
1167
|
"default": false
|
|
1116
1168
|
},
|
|
1117
1169
|
"roles.task.features.costCap": {
|
|
@@ -1121,7 +1173,7 @@
|
|
|
1121
1173
|
},
|
|
1122
1174
|
"roles.task.preferOwnProvider": {
|
|
1123
1175
|
"type": "boolean",
|
|
1124
|
-
"description": "
|
|
1176
|
+
"description": "Bind task requests to each model's best provider route (soft preference; never drops a model)",
|
|
1125
1177
|
"default": false
|
|
1126
1178
|
},
|
|
1127
1179
|
"roles.task.required": {
|
|
@@ -1157,6 +1209,12 @@
|
|
|
1157
1209
|
"default": 0,
|
|
1158
1210
|
"min": 0
|
|
1159
1211
|
},
|
|
1212
|
+
"roles.task.filters.preferredMinThroughput": {
|
|
1213
|
+
"type": "number",
|
|
1214
|
+
"description": "Soft throughput floor for task routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
1215
|
+
"default": 0,
|
|
1216
|
+
"min": 0
|
|
1217
|
+
},
|
|
1160
1218
|
"roles.task.lambda": {
|
|
1161
1219
|
"type": "number",
|
|
1162
1220
|
"description": "Explicit λ ($ per quality point) override for task",
|
|
@@ -1259,7 +1317,7 @@
|
|
|
1259
1317
|
},
|
|
1260
1318
|
"roles.advisor.features.providerPinning": {
|
|
1261
1319
|
"type": "boolean",
|
|
1262
|
-
"description": "Enable the
|
|
1320
|
+
"description": "Enable the Best-route provider pinning capability for the advisor role",
|
|
1263
1321
|
"default": false
|
|
1264
1322
|
},
|
|
1265
1323
|
"roles.advisor.features.costCap": {
|
|
@@ -1269,7 +1327,7 @@
|
|
|
1269
1327
|
},
|
|
1270
1328
|
"roles.advisor.preferOwnProvider": {
|
|
1271
1329
|
"type": "boolean",
|
|
1272
|
-
"description": "
|
|
1330
|
+
"description": "Bind advisor requests to each model's best provider route (soft preference; never drops a model)",
|
|
1273
1331
|
"default": false
|
|
1274
1332
|
},
|
|
1275
1333
|
"roles.advisor.required": {
|
|
@@ -1305,6 +1363,12 @@
|
|
|
1305
1363
|
"default": 0,
|
|
1306
1364
|
"min": 0
|
|
1307
1365
|
},
|
|
1366
|
+
"roles.advisor.filters.preferredMinThroughput": {
|
|
1367
|
+
"type": "number",
|
|
1368
|
+
"description": "Soft throughput floor for advisor routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
1369
|
+
"default": 0,
|
|
1370
|
+
"min": 0
|
|
1371
|
+
},
|
|
1308
1372
|
"roles.advisor.lambda": {
|
|
1309
1373
|
"type": "number",
|
|
1310
1374
|
"description": "Explicit λ ($ per quality point) override for advisor",
|
|
@@ -1391,7 +1455,7 @@
|
|
|
1391
1455
|
},
|
|
1392
1456
|
"roles.designer.features.providerPinning": {
|
|
1393
1457
|
"type": "boolean",
|
|
1394
|
-
"description": "Enable the
|
|
1458
|
+
"description": "Enable the Best-route provider pinning capability for the designer role",
|
|
1395
1459
|
"default": false
|
|
1396
1460
|
},
|
|
1397
1461
|
"roles.designer.features.costCap": {
|
|
@@ -1401,7 +1465,7 @@
|
|
|
1401
1465
|
},
|
|
1402
1466
|
"roles.designer.preferOwnProvider": {
|
|
1403
1467
|
"type": "boolean",
|
|
1404
|
-
"description": "
|
|
1468
|
+
"description": "Bind designer requests to each model's best provider route (soft preference; never drops a model)",
|
|
1405
1469
|
"default": false
|
|
1406
1470
|
},
|
|
1407
1471
|
"roles.designer.required": {
|
|
@@ -1437,6 +1501,12 @@
|
|
|
1437
1501
|
"default": 0,
|
|
1438
1502
|
"min": 0
|
|
1439
1503
|
},
|
|
1504
|
+
"roles.designer.filters.preferredMinThroughput": {
|
|
1505
|
+
"type": "number",
|
|
1506
|
+
"description": "Soft throughput floor for designer routes (tok/s; 0 = off) — a below-floor route sorts last but is never dropped",
|
|
1507
|
+
"default": 0,
|
|
1508
|
+
"min": 0
|
|
1509
|
+
},
|
|
1440
1510
|
"roles.designer.lambda": {
|
|
1441
1511
|
"type": "number",
|
|
1442
1512
|
"description": "Explicit λ ($ per quality point) override for designer",
|
package/src/agent-create.ts
CHANGED
|
@@ -424,8 +424,10 @@ function coveredValues(models: readonly Model[], metric: string, fill: number):
|
|
|
424
424
|
return values;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
/** Linear-interpolated quantile (R type 7) of a value list
|
|
428
|
-
|
|
427
|
+
/** Linear-interpolated quantile (R type 7) of a value list — the repo's one
|
|
428
|
+
* quantile convention: the focus-axis dispersion stats and the usage ledger's
|
|
429
|
+
* per-turn median (`quantile(values, 0.5)`) both read it. */
|
|
430
|
+
export function quantile(values: readonly number[], p: number): number {
|
|
429
431
|
const sorted = [...values].sort((a, b) => a - b);
|
|
430
432
|
const h = (sorted.length - 1) * p;
|
|
431
433
|
const lo = Math.floor(h);
|
package/src/availability.ts
CHANGED
|
@@ -88,6 +88,24 @@ export type ProbeVerdict = "ok" | "blocked" | "unknown";
|
|
|
88
88
|
|
|
89
89
|
const OPENROUTER_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
90
90
|
|
|
91
|
+
/** POST a one-token completion to OpenRouter with the shared request skeleton
|
|
92
|
+
* (Bearer auth, JSON body, 20s timeout). Shared by `probeModel` and
|
|
93
|
+
* `fetchAllowedProviders`. */
|
|
94
|
+
function completionRequest(token: string, body: unknown, fetchImpl: typeof fetch): Promise<Response> {
|
|
95
|
+
return fetchImpl(OPENROUTER_COMPLETIONS_URL, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
98
|
+
body: JSON.stringify(body),
|
|
99
|
+
signal: AbortSignal.timeout(20_000),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Extract `error.message` from a parsed OpenRouter error body, or "" when the
|
|
104
|
+
* body does not carry one. */
|
|
105
|
+
function errorMessage(body: unknown): string {
|
|
106
|
+
return isRecord(body) && isRecord(body.error) && typeof body.error.message === "string" ? body.error.message : "";
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
/** 404 body OpenRouter returns when the account's allowed-providers privacy
|
|
92
110
|
* whitelist (or a data-policy toggle) leaves a model without a runnable endpoint. */
|
|
93
111
|
const BLOCKED_RE = /no allowed providers/i;
|
|
@@ -102,22 +120,71 @@ const BLOCKED_RE = /no allowed providers/i;
|
|
|
102
120
|
*/
|
|
103
121
|
export async function probeModel(token: string, catalogId: string, fetchImpl: typeof fetch = fetch): Promise<ProbeVerdict> {
|
|
104
122
|
try {
|
|
105
|
-
const res = await
|
|
106
|
-
method: "POST",
|
|
107
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
108
|
-
body: JSON.stringify({ model: catalogId, messages: [{ role: "user", content: "ok" }], max_tokens: 1 }),
|
|
109
|
-
signal: AbortSignal.timeout(20_000),
|
|
110
|
-
});
|
|
123
|
+
const res = await completionRequest(token, { model: catalogId, messages: [{ role: "user", content: "ok" }], max_tokens: 1 }, fetchImpl);
|
|
111
124
|
if (res.ok) return "ok";
|
|
112
125
|
if (res.status !== 404) return "unknown";
|
|
113
126
|
const body: unknown = await res.json().catch(() => null);
|
|
114
|
-
const message =
|
|
127
|
+
const message = errorMessage(body);
|
|
115
128
|
return BLOCKED_RE.test(message) ? "blocked" : "unknown";
|
|
116
129
|
} catch {
|
|
117
130
|
return "unknown";
|
|
118
131
|
}
|
|
119
132
|
}
|
|
120
133
|
|
|
134
|
+
/** 404 body OpenRouter returns when the account's allowed-providers privacy
|
|
135
|
+
* whitelist leaves the canary provider without a runnable endpoint; the message
|
|
136
|
+
* names the whitelist after "permits only:". */
|
|
137
|
+
const ALLOWED_PROVIDERS_RE = /permits only:\s*([^.]*)\./i;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Harvest the account's allowed-providers privacy whitelist. The whitelist is
|
|
141
|
+
* invisible to `/api/v1/key` and the catalog endpoints; the only place it
|
|
142
|
+
* appears is the 404 body of a request that fails because of it. A canary
|
|
143
|
+
* completion on a valid model with a nonexistent provider slug (`provider.only`)
|
|
144
|
+
* 404s and names the whitelist. Returns null when the body does not name a
|
|
145
|
+
* whitelist (no whitelist set), the response is not a 404, or the request
|
|
146
|
+
* throws — never throws.
|
|
147
|
+
*/
|
|
148
|
+
export async function fetchAllowedProviders(token: string, modelId: string, fetchImpl: typeof fetch = fetch): Promise<Set<string> | null> {
|
|
149
|
+
try {
|
|
150
|
+
const res = await completionRequest(
|
|
151
|
+
token,
|
|
152
|
+
{
|
|
153
|
+
model: modelId,
|
|
154
|
+
messages: [{ role: "user", content: "ok" }],
|
|
155
|
+
max_tokens: 1,
|
|
156
|
+
provider: { only: ["__omp-llm-role-canary__"] },
|
|
157
|
+
},
|
|
158
|
+
fetchImpl,
|
|
159
|
+
);
|
|
160
|
+
if (res.status !== 404) return null;
|
|
161
|
+
const body: unknown = await res.json().catch(() => null);
|
|
162
|
+
const message = errorMessage(body);
|
|
163
|
+
const match = ALLOWED_PROVIDERS_RE.exec(message);
|
|
164
|
+
if (!match) return null;
|
|
165
|
+
const providers = match[1]
|
|
166
|
+
.split(",")
|
|
167
|
+
.map((s) => s.trim())
|
|
168
|
+
.filter((s) => s.length > 0);
|
|
169
|
+
return providers.length > 0 ? new Set(providers) : null;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Whether a route's `providerSlug` is permitted by the account's
|
|
177
|
+
* allowed-providers whitelist. Whitelist entries are bare lab names
|
|
178
|
+
* (`coreweave`) while route slugs are tiered (`coreweave/fp8`), so a match is
|
|
179
|
+
* the entry itself or an `entry/` prefix.
|
|
180
|
+
*/
|
|
181
|
+
export function providerAllowed(allowed: ReadonlySet<string>, slug: string): boolean {
|
|
182
|
+
for (const entry of allowed) {
|
|
183
|
+
if (slug === entry || slug.startsWith(`${entry}/`)) return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
121
188
|
// ---------------------------------------------------------------------------
|
|
122
189
|
// Keyed-catalog availability
|
|
123
190
|
// ---------------------------------------------------------------------------
|
|
@@ -228,12 +295,23 @@ export function rankingIdOf(catalogId: string): string {
|
|
|
228
295
|
return noFree.endsWith("-latest") ? noFree.slice(0, -"-latest".length) : noFree;
|
|
229
296
|
}
|
|
230
297
|
|
|
298
|
+
/** Split a selector's trailing thinking suffix (`provider/model[:level]`):
|
|
299
|
+
* `base` without the suffix, `level` the suffix or null. The single home for
|
|
300
|
+
* the `:level` strip shared by `currentRankingId`, `providerPinOfSelector`, and
|
|
301
|
+
* the extension's selector handling. */
|
|
302
|
+
export function splitThinkingSuffix(selector: string): { base: string; level: string | null } {
|
|
303
|
+
const colon = selector.lastIndexOf(":");
|
|
304
|
+
if (colon !== -1 && selector.slice(colon + 1) in THINKING_LEVELS) {
|
|
305
|
+
return { base: selector.slice(0, colon), level: selector.slice(colon + 1) };
|
|
306
|
+
}
|
|
307
|
+
return { base: selector, level: null };
|
|
308
|
+
}
|
|
309
|
+
|
|
231
310
|
/** Identity of a configured selector for hysteresis/chain keys: drop `:level`,
|
|
232
311
|
* `openrouter/`, and a trailing `@<slug>` provider pin, then rankingIdOf. A
|
|
233
312
|
* tiered pin (`@deepinfra/fp8`) is dropped whole — everything from the last `@`. */
|
|
234
313
|
export function currentRankingId(selector: string): string {
|
|
235
|
-
const
|
|
236
|
-
const base = colon !== -1 && selector.slice(colon + 1) in THINKING_LEVELS ? selector.slice(0, colon) : selector;
|
|
314
|
+
const { base } = splitThinkingSuffix(selector);
|
|
237
315
|
const unprefixed = base.startsWith("openrouter/") ? base.slice("openrouter/".length) : base;
|
|
238
316
|
const at = unprefixed.lastIndexOf("@");
|
|
239
317
|
const unpinned = at !== -1 ? unprefixed.slice(0, at) : unprefixed;
|
package/src/cli/create-role.ts
CHANGED
|
@@ -50,7 +50,7 @@ const USAGE = [
|
|
|
50
50
|
" --lambda <n> explicit λ override ($ per quality point)",
|
|
51
51
|
" --feature <id,...> capability flag(s) to author on the role (see --list-features);",
|
|
52
52
|
" repeatable, comma-separated, merged",
|
|
53
|
-
" --prefer-own-provider
|
|
53
|
+
" --prefer-own-provider bind the role to each model's best provider route where one exists",
|
|
54
54
|
" --list-features print the capability flags and the settings each applies, then exit",
|
|
55
55
|
" --lock <path> settings lock file (default ~/.omp/plugins/omp-plugins.lock.json)",
|
|
56
56
|
" --dry-run validate and print without writing",
|