opencode-models 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,591 @@
1
+ """Rank models by score per dollar: score / input-price (or output-price),
2
+ descending. Free models score an infinite ratio and lead their benchmark,
3
+ ordered by score. Models missing the price or the benchmark's score are
4
+ excluded — a ratio cannot be computed for them.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, replace
10
+
11
+ from .data import Model
12
+ from .identity import canonical_id
13
+ from .pricing import nominal_free_rate, variant_of, weighted_cost
14
+ from .scoring import combined, derived_scores, scales, tracked
15
+
16
+ _PRICE_FIELDS = ("price_input", "price_output")
17
+
18
+
19
+ def _price_of(m: Model, field: str) -> float | None:
20
+ if field not in _PRICE_FIELDS:
21
+ raise ValueError(f"unknown price field {field!r}")
22
+ return getattr(m, field)
23
+
24
+
25
+ def by_score_per_price(
26
+ models: list[Model], benchmark: str, price_field: str = "price_input"
27
+ ) -> list[Model]:
28
+ """Best value first. Ties on ratio break toward the higher score.
29
+
30
+ *price_field* selects the cost axis: ``price_input`` (default) or
31
+ ``price_output``.
32
+ """
33
+ ranked = [
34
+ m
35
+ for m in models
36
+ if _price_of(m, price_field) is not None and benchmark in m.scores
37
+ ]
38
+ return sorted(
39
+ ranked,
40
+ key=lambda m: (
41
+ -float("inf")
42
+ if _price_of(m, price_field) == 0
43
+ else -m.scores[benchmark] / _price_of(m, price_field),
44
+ -m.scores[benchmark],
45
+ ),
46
+ )
47
+
48
+
49
+ #: Benchmark families the console list combines, in priority order.
50
+ PRIMARY_FAMILY = "Terminal-Bench"
51
+ SECONDARY_FAMILY = "SWE-Bench"
52
+ #: Terminal-Bench 4.0 (Aug 2026) is a harder task set than 2.x — the best
53
+ #: 4.0 run scores ~58 where 2.1 leaders reach ~87 — so its numbers live on
54
+ #: their own scale. 2.1 still builds the skeleton (37 tier models carry it
55
+ #: against 12 for 4.0); 4.0 fills in like SWE-Bench until coverage flips.
56
+ TB4_FAMILY = "Terminal-Bench 4.0"
57
+
58
+ #: Fill-in axes, applied after the primary skeleton is built.
59
+ SECONDARY_AXES = (SECONDARY_FAMILY, TB4_FAMILY)
60
+
61
+ #: The ordering axes shown side by side in the console: (key, column label,
62
+ #: benchmark family, versioned?). ``tb2`` is versioned so only real 2.x/3.x
63
+ #: runs count — Terminal-Bench Hard and Science are separate benchmarks that
64
+ #: merely share the prefix, and 4.0 is its own scale.
65
+ AXES = (
66
+ ("tb2", "TB2", PRIMARY_FAMILY, True),
67
+ ("tb4", "TB4", TB4_FAMILY, False),
68
+ ("swe", "SWE", SECONDARY_FAMILY, False),
69
+ )
70
+
71
+ #: Share of the list culled into each side table.
72
+ CULL_FRACTION = 0.3
73
+
74
+ #: Rank grades for the console's marker column, read off the 0-100 score
75
+ #: itself rather than off position in the list: 90+ is `++`, 75+ is `+`,
76
+ #: 60+ is `o`, 50+ is `-`, below that `--`. Absolute bands mean a strong
77
+ #: field can share the top and a weak one is never flattered by it.
78
+ GRADE_PLUSPLUS = 90.0
79
+ GRADE_PLUS = 75.0
80
+ GRADE_O = 60.0
81
+ GRADE_MINUS = 50.0
82
+
83
+ BAND_FREE = "free"
84
+ BAND_INCLUDED = "included"
85
+ BAND_PAYG = "payg"
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class Row:
90
+ """One printable line of the console list."""
91
+
92
+ model: Model
93
+ name: str # display label: variant and [free] applied
94
+ price: float # representative cost, $/1M (nominal if free)
95
+ free: bool
96
+ band: str
97
+ score: float | None # primary family score, else secondary
98
+ benchmark: str | None # the key `score` came from
99
+ grade: str = "o" # rank marker: ++ / + / o / -- / ·
100
+ provider: str = "-" # zen / go, "-" when in neither tier
101
+ entry: object | None = None # pricing.PriceEntry when doc-priced
102
+
103
+
104
+ def _best(
105
+ scores: dict[str, float],
106
+ family: str,
107
+ skip: str | None = None,
108
+ versioned: bool = False,
109
+ ) -> tuple[str, float] | None:
110
+ """Highest score under *family*, as ``(key, value)``.
111
+
112
+ Families are matched with spaces folded to dashes so ``Terminal Bench
113
+ 2.1`` and ``Terminal-Bench 2.1`` count as the same benchmark.
114
+
115
+ *skip* excludes keys on a harder scale of the same family — a 4.0 score
116
+ must not win a slot that is compared against 2.x numbers.
117
+
118
+ *versioned* restricts the family to actual version runs: after the
119
+ family prefix a version number must follow. Terminal-Bench Hard and
120
+ Terminal-Bench-Science are separate benchmarks that happen to share the
121
+ prefix — a 20.5 on TB Hard is not a "Terminal-Bench 2.x" run and must
122
+ never fill a slot compared against 2.1 board numbers.
123
+ """
124
+ best = None
125
+ folded_skip = skip.lower().replace(" ", "-") if skip else None
126
+ folded_family = family.lower().replace(" ", "-")
127
+ for key, value in scores.items():
128
+ folded = key.lower().replace(" ", "-")
129
+ if not folded.startswith(folded_family):
130
+ continue
131
+ if versioned and not _version_run(folded[len(folded_family) :]):
132
+ continue
133
+ if folded_skip and folded.startswith(folded_skip):
134
+ continue
135
+ if best is None or value > best[1]:
136
+ best = (key, value)
137
+ return best
138
+
139
+
140
+ def _version_run(after_family: str) -> bool:
141
+ """True when what follows the family prefix is a version, not a word.
142
+
143
+ ``-2.1`` and ``-3.0`` are version runs; ``-Hard`` and ``-Science-0.1``
144
+ are different benchmarks sharing the prefix.
145
+ """
146
+ return after_family.lstrip("-_ ")[:1].isdigit()
147
+
148
+
149
+ def ratio(score: float, price: float) -> float:
150
+ """Score per dollar; a free line beats everything rather than going inf..."""
151
+ return float("inf") if price == 0 else score / price
152
+
153
+
154
+ def build_rows(
155
+ models: list[Model],
156
+ entries=(),
157
+ free_ids=frozenset(),
158
+ axis: str = "weighted",
159
+ provider: str = "-",
160
+ table: dict | None = None,
161
+ free_rates: dict[str, float] | None = None,
162
+ ) -> list[Row]:
163
+ """Price every line of the console list.
164
+
165
+ A documented line replaces the catalog price (the catalog has no notion
166
+ of tier: it carries the Zen rate for a model Go sells cheaper), and one
167
+ model can produce several rows — one per priced variant. Free rows carry
168
+ their model's nominal free-tier rate (pricing.FREE_TIER_NOMINAL_RATES):
169
+ free models are not equally expensive to serve — Step-5-Free burns ~70x
170
+ what Muse-Spark-Contributor-Free does per request — so sharing one price
171
+ would rank them as equals. Every nominal sits below the cheapest paid
172
+ rate, so free still beats paid on value, just not all alike.
173
+
174
+ *axis* picks what a line costs: ``weighted`` (20/60/20 in/cached/out),
175
+ or a single ``input`` / ``output`` rate.
176
+ """
177
+ by_model: dict[str, list] = {}
178
+ for entry in entries:
179
+ if entry.model_id:
180
+ by_model.setdefault(entry.model_id, []).append(entry)
181
+
182
+ def cost_of(model: Model, entry) -> float | None:
183
+ if axis == "input":
184
+ return entry.input if entry else model.price_input
185
+ if axis == "output":
186
+ return entry.output if entry else model.price_output
187
+ if entry:
188
+ return weighted_cost(entry.input, entry.output, entry.cached_read)
189
+ return weighted_cost(
190
+ model.price_input, model.price_output, model.price_cached_input
191
+ )
192
+
193
+ staged = [] # (model, entry, display name, cost, free, monthly limit)
194
+ for m in models:
195
+ slug = m.model_id.split("/")[-1]
196
+ documented = by_model.get(slug, []) + by_model.get(f"{slug}-free", [])
197
+ cost = cost_of(m, None)
198
+ if not documented:
199
+ if cost is None:
200
+ continue # no price at all — nothing to rank it on
201
+ staged.append(
202
+ (m, None, m.name, cost, m.model_id in free_ids or cost == 0, None)
203
+ )
204
+ continue
205
+ variants = set()
206
+ for entry in documented: # first tier in *entries* owns a variant
207
+ suffix = variant_of(entry.name)
208
+ if suffix in variants:
209
+ continue
210
+ variants.add(suffix)
211
+ label = f"{m.name} {suffix}" if suffix else m.name
212
+ staged.append(
213
+ (
214
+ m,
215
+ entry,
216
+ label,
217
+ cost_of(m, entry),
218
+ entry.free or m.model_id in free_ids,
219
+ entry.limit,
220
+ )
221
+ )
222
+
223
+ rows = []
224
+ for model, entry, name, cost, free, limit in staged:
225
+ if free:
226
+ price, band = nominal_free_rate(model.model_id, free_rates), BAND_FREE
227
+ if "free" not in name.lower():
228
+ name = f"{name} [free]"
229
+ else:
230
+ price = cost
231
+ band = BAND_INCLUDED if limit is not None else BAND_PAYG
232
+ # the row's score is the model's own: every benchmark it carries on
233
+ # that benchmark's 0-100 scale, median across them. *table* is the
234
+ # scale of the whole catalog, passed in so every view grades against
235
+ # the same yardstick.
236
+ derived = derived_scores(model, table or scales(models))
237
+ score = combined(derived)
238
+ strongest = max(derived, key=lambda k: derived[k]) if derived else None
239
+ rows.append(
240
+ Row(
241
+ model=model,
242
+ name=name,
243
+ price=price,
244
+ free=free,
245
+ band=band,
246
+ score=score,
247
+ benchmark=strongest,
248
+ provider=provider,
249
+ entry=entry,
250
+ )
251
+ )
252
+ return rows
253
+
254
+
255
+ def merge_rows(*groups: list[Row]) -> list[Row]:
256
+ """Concatenate provider groups onto one free-price scale.
257
+
258
+ Free rows already carry their model's nominal rate from build_rows,
259
+ which is per-model rather than per-group, so there is nothing to
260
+ re-price: the whole view agrees by construction. Provider tags and order
261
+ are kept.
262
+ """
263
+ return [row for group in groups for row in group]
264
+
265
+
266
+ #: How generous a band is, for picking the best deal out of a merged line.
267
+ _BAND_RANK = {BAND_FREE: 0, BAND_INCLUDED: 1, BAND_PAYG: 2}
268
+
269
+
270
+ def dedupe_providers(rows: list[Row]) -> list[Row]:
271
+ """One line per (model, variant, score, cost), providers enumerated.
272
+
273
+ zen and go sell many models at the same rate for the same score, so the
274
+ pair is one offer rather than two: the providers are listed together
275
+ (``go,zen``). When only some of the providers fund the line from a
276
+ monthly allowance, the more generous band wins — the line is available
277
+ that way from at least one of the providers named on it.
278
+ """
279
+ merged: dict[tuple, Row] = {}
280
+ keys: list[tuple] = []
281
+ for row in rows:
282
+ key = (row.model.model_id, row.name, row.score, round(row.price, 9))
283
+ if key not in merged:
284
+ merged[key] = row
285
+ keys.append(key)
286
+ continue
287
+ current = merged[key]
288
+ keep = current if _BAND_RANK[current.band] <= _BAND_RANK[row.band] else row
289
+ providers = sorted(
290
+ {p for side in (current.provider, row.provider) for p in side.split(",")}
291
+ )
292
+ merged[key] = replace(keep, provider=",".join(providers))
293
+ return [merged[key] for key in keys]
294
+
295
+
296
+ def order_rows(rows: list[Row]) -> list[Row]:
297
+ """Best score per dollar first; the rest cannot be valued.
298
+
299
+ The score is already comparable across axes — each benchmark reads on
300
+ its own 0-100 scale and the median combines them — so there is no
301
+ skeleton to build and no percentile to insert at. Value order is the
302
+ combined score divided by the price, ties toward the higher score, and
303
+ a free scored line leads everything paid (infinite value sorts first,
304
+ not last). Rows no benchmark reaches cannot be valued at all: free ones
305
+ close the listing (they cost nothing whatever — maximising free usage
306
+ means seeing them), paid ones follow behind, and rows nothing reaches
307
+ and nobody sells free drop out.
308
+ """
309
+ scored = sorted(
310
+ (r for r in rows if r.score is not None),
311
+ key=lambda r: (-ratio(r.score, r.price), -r.score),
312
+ )
313
+ listed = {id(r) for r in scored}
314
+ tail = [
315
+ r
316
+ for r in rows
317
+ if r.score is None and id(r) not in listed and (r.free or _measured(r))
318
+ ]
319
+ free = [r for r in tail if r.free]
320
+ paid = [r for r in tail if not r.free]
321
+ return scored + free + paid
322
+
323
+
324
+ def _measured(row: Row) -> bool:
325
+ """Whether any scored benchmark reaches this row's model."""
326
+ return any(tracked(name) for name in row.model.scores)
327
+
328
+
329
+ def axis_scores(model: Model) -> dict[str, float | None]:
330
+ """Each axis's raw score for *model*, None where it carries none.
331
+
332
+ The console prints these side by side so the viewer sees the base
333
+ scores the ordering is built from instead of one merged number.
334
+ """
335
+ out: dict[str, float | None] = {}
336
+ for key, _label, family, versioned in AXES:
337
+ best = _best(
338
+ model.scores,
339
+ family,
340
+ skip=TB4_FAMILY if key == "tb2" else None,
341
+ versioned=versioned,
342
+ )
343
+ out[key] = best[1] if best else None
344
+ return out
345
+
346
+
347
+ def grade_for(score: float | None) -> str:
348
+ """One score's marker: the band it clears, or ``·`` with no score."""
349
+ if score is None:
350
+ return "·"
351
+ if score >= GRADE_PLUSPLUS:
352
+ return "++"
353
+ if score >= GRADE_PLUS:
354
+ return "+"
355
+ if score >= GRADE_O:
356
+ return "o"
357
+ if score >= GRADE_MINUS:
358
+ return "-"
359
+ return "--"
360
+
361
+
362
+ def grade_rows(rows: list[Row]) -> list[Row]:
363
+ """Tag every row with a marker for how strong it is, keeping the order.
364
+
365
+ The bands come from the *score*, not from position in the table. Price
366
+ sorts the table — that is the Pareto view — but a model that scores well
367
+ should not be graded "middle" for costing money, and a cheap model
368
+ should not be graded "top" for being cheap.
369
+
370
+ Rows with no score are unranked rather than last: they show ``·``, so a
371
+ free model nobody benchmarks is not mistaken for the bottom.
372
+ """
373
+ return [replace(r, grade=grade_for(r.score)) for r in rows]
374
+
375
+
376
+ #: How much weaker than the strongest model known a pick may be, as a
377
+ #: fraction of its score.
378
+ #:
379
+ #: Eligibility is deliberately *strength*, not the rank marker. The marker
380
+ #: is a position in an ordering that divides by price, so it measures value:
381
+ #: in the live ordering MiMo-V2.6-Pro scores 89.9 — the best model in the
382
+ #: catalog — and is graded ``o``, while a free model scoring 24.58 is graded
383
+ #: ``++``. Gating on it recommends the weak model and misses the strong one.
384
+ RECOMMEND_FLOOR = 0.75
385
+
386
+
387
+ def next_up(
388
+ rows: list[Row],
389
+ count: int = 4,
390
+ grade: str | None = None,
391
+ floor: float = RECOMMEND_FLOOR,
392
+ ) -> list[Row]:
393
+ """The next *count* models to use, in order.
394
+
395
+ Candidates alternate between the free and paid bands slot by slot —
396
+ even slots take the best remaining free pick, odd slots the best
397
+ remaining paid one — so a capped free tier still leaves half the
398
+ sequence usable, and a fresh one still leads with what costs nothing.
399
+ Free picks order by score, since they cost nothing and only the score
400
+ is left to maximise; paid picks order by score per dollar, the measure
401
+ the list view already ranks on.
402
+
403
+ That order is then alternated between Zen and Go wherever both plans
404
+ can supply a pick, so following the sequence spreads across the two
405
+ instead of draining one allowance before touching the other. Plan
406
+ alternation is a preference, not a rule: it gives way when the wanted
407
+ band has nothing on the other plan, and band alternation gives way
408
+ when a band runs dry — the other band fills the slot instead.
409
+
410
+ Eligibility is strength, not the rank marker: a pick must be within
411
+ *floor* of the strongest score in *rows*, and may be narrowed further by
412
+ a *grade*. A short list is a short list, not an error.
413
+ """
414
+ scored = [r for r in rows if r.score is not None]
415
+ if not scored:
416
+ return []
417
+ # the reference is the strongest model in the whole catalog, so the bar
418
+ # cannot be dragged down by a weak set of top rows
419
+ best = max(r.score for r in scored)
420
+ eligible = [r for r in scored if r.score >= best * floor]
421
+ if grade:
422
+ eligible = [r for r in eligible if r.grade == grade]
423
+ free = sorted((r for r in eligible if r.free), key=lambda r: (-r.score, r.name))
424
+ paid = sorted(
425
+ (r for r in eligible if not r.free),
426
+ key=lambda r: (-ratio(r.score, r.price), -r.score, r.name),
427
+ )
428
+ # Band alternation is the slot order; the plan alternation inside each
429
+ # slot prefers a plan the previous pick did not touch.
430
+ return _take_banded(free, paid, count)
431
+
432
+
433
+ def plans_of(row: Row) -> set[str]:
434
+ """The plans a row is sold on; a shared offer counts for each of them.
435
+
436
+ ``dedupe_providers`` labels an offer both tiers sell at the same rate as
437
+ ``"go,zen"``. Treating that as a plan of its own would leave it out of
438
+ the alternation entirely, so it is read as membership of both.
439
+ """
440
+ return set(row.provider.split(","))
441
+
442
+
443
+ def _take_banded(free: list[Row], paid: list[Row], want: int) -> list[Row]:
444
+ """Up to *want* rows, alternating free and paid slots.
445
+
446
+ One model is recommended once, however many lines it appears on: free on
447
+ Zen and paid on Go are two lines for one model, and spending two slots
448
+ on it wastes a recommendation. Even slots draw from the free pool, odd
449
+ slots from the paid pool; a dry band yields its slot to the other pool
450
+ rather than ending the sequence short.
451
+
452
+ Within a slot the plan alternates too: the first unseen row on a plan
453
+ the previous pick did not touch wins, falling back to the strongest
454
+ unseen row in the pool when every plan left touches it. The leading pick
455
+ is the strongest free row available, whichever plan it sits on. A plan
456
+ with nothing left is simply passed over — there is nothing to
457
+ alternate with.
458
+ """
459
+ pools = [list(free), list(paid)]
460
+ seen: set[str] = set()
461
+ taken: list[Row] = []
462
+ previous: set[str] = set()
463
+ for slot in range(want):
464
+ first, second = pools if slot % 2 == 0 else pools[::-1]
465
+ row = (
466
+ _off_plan(first, seen, previous)
467
+ or _first_unseen(first, seen)
468
+ or _off_plan(second, seen, previous)
469
+ or _first_unseen(second, seen)
470
+ )
471
+ if row is None:
472
+ break
473
+ taken.append(row)
474
+ seen.add(canonical_id(row.model.model_id))
475
+ previous = plans_of(row)
476
+ return taken
477
+
478
+
479
+ def _first_unseen(pool: list[Row], seen: set[str]) -> Row | None:
480
+ """The strongest row in *pool* for a model not yet recommended."""
481
+ for row in pool:
482
+ if canonical_id(row.model.model_id) not in seen:
483
+ return row
484
+ return None
485
+
486
+
487
+ def _off_plan(pool: list[Row], seen: set[str], previous: set[str]) -> Row | None:
488
+ """The strongest unseen row in *pool* on a fresh plan.
489
+
490
+ Nothing is fresh before the first pick, so the lead always falls
491
+ through to the strongest row outright.
492
+ """
493
+ for row in pool:
494
+ if (
495
+ canonical_id(row.model.model_id) not in seen
496
+ and not plans_of(row) & previous
497
+ ):
498
+ return row
499
+ return None
500
+
501
+
502
+ def _take(ordered: list[Row], want: int) -> list[Row]:
503
+ """Up to *want* rows from *ordered*, alternating between plans.
504
+
505
+ One model is recommended once, however many lines it appears on: free on
506
+ Zen and paid on Go are two lines for one model, and spending two slots
507
+ on it wastes a recommendation. Free-first ordering means the cheaper
508
+ line is the one already in *ordered*, so it is the one that survives.
509
+
510
+ Plans are visited strongest-first, so the leading pick is the best row
511
+ available whichever plan it sits on. A plan with nothing left is simply
512
+ passed over — there is nothing to alternate with.
513
+ """
514
+ remaining = list(ordered)
515
+ seen: set[str] = set()
516
+ unique: list[Row] = []
517
+ for row in remaining:
518
+ if canonical_id(row.model.model_id) in seen:
519
+ continue
520
+ seen.add(canonical_id(row.model.model_id))
521
+ unique.append(row)
522
+ remaining = unique
523
+ plans = sorted(
524
+ {p for row in remaining for p in plans_of(row)},
525
+ key=lambda p: max((r.score for r in remaining if p in plans_of(r)), default=0),
526
+ reverse=True,
527
+ )
528
+ taken: list[Row] = []
529
+ while len(taken) < want and remaining:
530
+ progressed = False
531
+ for plan in plans:
532
+ if len(taken) >= want:
533
+ break
534
+ for i, row in enumerate(remaining):
535
+ if plan in plans_of(row):
536
+ taken.append(row)
537
+ remaining.pop(i)
538
+ progressed = True
539
+ break
540
+ if not progressed:
541
+ break
542
+ return taken
543
+
544
+
545
+ def partition_rows(
546
+ rows: list[Row], fraction: float = CULL_FRACTION
547
+ ) -> tuple[list[Row], list[Row], list[Row]]:
548
+ """Split into (best value, worst score, highest cost).
549
+
550
+ The split always happens: the worst-scoring and priciest models are
551
+ taken out of best value whether or not a caller goes on to print them.
552
+ A view that shows one table is still a view that hid those rows, which is
553
+ the point of the cull.
554
+
555
+ Each cull takes the nearest whole row to *fraction* of the list (one row
556
+ minimum once there is more than one, never on a single row). The groups
557
+ are disjoint and the cost group has first claim: a model that is both
558
+ priciest and lowest-scoring is reported once, under cost.
559
+ """
560
+ count = len(rows)
561
+ take = 0 if count <= 1 else max(1, round(fraction * count))
562
+
563
+ expensive = sorted(
564
+ range(count),
565
+ key=lambda i: (
566
+ -rows[i].price,
567
+ rows[i].score if rows[i].score is not None else float("inf"),
568
+ ),
569
+ )[:take]
570
+ claimed = set(expensive)
571
+
572
+ rest = [i for i in range(count) if i not in claimed]
573
+ weakest = sorted(
574
+ rest,
575
+ key=lambda i: (
576
+ rows[i].score if rows[i].score is not None else float("inf"),
577
+ -rows[i].price,
578
+ ),
579
+ )[:take]
580
+ weakest_set = set(weakest)
581
+
582
+ main = [rows[i] for i in rest if i not in weakest_set]
583
+ # both cull tables read worst-scoring first; the expensive group still
584
+ # *selects* on price, it just no longer sorts by it
585
+ by_score = lambda i: ( # noqa: E731
586
+ rows[i].score if rows[i].score is not None else float("inf"),
587
+ -rows[i].price,
588
+ )
589
+ worst = [rows[i] for i in sorted(weakest, key=by_score)]
590
+ pricey = [rows[i] for i in sorted(expensive, key=by_score)]
591
+ return main, worst, pricey