Commit be7a36e
authored
feat(model): per-model context limits and pricing, generated from Cursor's docs (#89)
* feat(model): per-model context window limits from Cursor docs
* feat(model): per-model pricing from Cursor docs for TUI cost display
* feat(model): per-model output limits for high-output frontier models
* fix(model): emit limits and cost on the config channel opencode reads
* feat(model): generate context limits and pricing from Cursor docs
* docs(model): point MODEL_OUTPUT_LIMITS edits at the generator template
* fix(model): make the model-data drift check unable to pass without checking
The drift mechanism had three ways to report success without having
verified anything, and the function holding every hard contract had no
test coverage.
- Remove the fail-open CLI guard. `isInvokedDirectly()` compared
`process.argv[1]` against `import.meta.url`; on any invocation where
they differ, `main()` never ran and the process exited 0 having done
nothing. The symlink fix addressed one instance, not the class. The
generator module is now import-pure and `scripts/sync-model-limits-cli.mjs`
calls `main()` unconditionally. Verified through a symlink, an absolute
path from another cwd, and `npm exec`.
- The CI step now captures stdout and requires the run summary line, so
"exited 0 having done nothing" fails instead of passing for free.
- An empty price cell no longer becomes $0. `parseDocsTable` leaves an
absent cell unset (distinct from present-but-empty) and throws when a
requested column is missing from a row; `parsePrice("")` throws.
`"-"` still means $0, which is what Cursor documents it as.
- `generate()` takes injectable `modelIds`/`overrides`, so strict
overrides, ambiguity on both tables, docs-over-override precedence and
the deterministic sort are exercised against the fixtures rather than
resting on one-time manual probes. `normalizeForComparison` is covered
too: it is what keeps `--check` from failing daily.
- Matching consults the `Provider` column. `claude`/`gpt` are dropped from
both sides to survive word-order differences, which also discarded vendor
identity; an id naming a dropped vendor now matches only a row whose
Provider agrees. Counts unchanged: 29 exact / 0 ambiguous on context,
27 exact / 6 overridden on pricing.
- Fixtures gain the verbatim `Claude Opus 4.7 (fast mode)` row ($30/$150,
6x Opus's rate) and assert `claude-opus-4-7` refuses it.
- Write-mode I/O failure exits 2, not 1: a failed write does not establish
that the committed file is stale.
- `Synced:` relabelled `Data last changed:`. Write mode short-circuits when
only the date would move, so the committed date was never a verification
date and read as though the file were a year stale.
- `schedule`/`workflow_dispatch` are workflow-wide triggers, so `build` and
`integration` are now scoped to push/pull_request.
- `SOURCES.context` points at the URL that currently serves markdown. The
`.md` form began returning HTTP 404, which made every run exit 2 — the
scheduled job's exit-2-is-a-warning branch would have swallowed that
indefinitely. No emitted value changes.
Zero numeric values change in the regenerated `src/model-limits.ts`: key
sets, key order, `DEFAULT_*`, `MODEL_OUTPUT_LIMITS` and all three
`resolve*` functions are byte-identical. The only diff is the header.
Tests 416 -> 442. Each new test was mutation-checked against the
pre-change behaviour to confirm it can fail.
* fix(model): use .md doc URLs and fail the drift job on unverified
Two corrections to the drift check:
- SOURCES.context pointed at the extensionless docs URL, justified by a
comment claiming the .md form returned 404. Not reproducible: measured
against both URL shapes, the .md form returns the markdown table under
both a markdown-preferring and a wildcard Accept header, while the
extensionless form returns a ~110KB HTML page under a wildcard Accept.
The .md form does not depend on the Accept header staying
markdown-preferring, so both sources now use it. Comment corrected to
the measured behavior.
- Exit 2 (docs unreachable/unparseable, or a model id matching nothing)
annotated a warning and exited 0, so a permanently broken check would
pass every week indefinitely. It now fails the job: unverified is not
the same as verified-clean.
Regenerated output changes one line, the source URL recorded in the
generated header. No numeric value changed.1 parent 4f49156 commit be7a36e
13 files changed
Lines changed: 1730 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
3 | 6 | | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
7 | 13 | | |
8 | 14 | | |
9 | 15 | | |
10 | 16 | | |
11 | 17 | | |
12 | 18 | | |
13 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
14 | 23 | | |
15 | 24 | | |
16 | 25 | | |
| |||
45 | 54 | | |
46 | 55 | | |
47 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
48 | 103 | | |
49 | 104 | | |
| 105 | + | |
| 106 | + | |
50 | 107 | | |
51 | 108 | | |
52 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
0 commit comments