@aggc/or-info 0.2.16 → 0.2.18
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 +1 -27
- package/bin/or-info.mjs +1 -1
- package/lib/formatter.mjs +14 -1
- package/lib/openrouter.mjs +7 -2
- package/mcp/server.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -352,33 +352,7 @@ Additional entry points:
|
|
|
352
352
|
|
|
353
353
|
## Release
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
The package is public under the `@aggc` npm scope.
|
|
357
|
-
|
|
358
|
-
Release checklist:
|
|
359
|
-
|
|
360
|
-
1. Update `CHANGELOG.md`.
|
|
361
|
-
2. Bump `version` in `package.json` **and** `manifest.json` (must match).
|
|
362
|
-
3. Run `npm test`.
|
|
363
|
-
4. Commit and push to `main`.
|
|
364
|
-
5. Create and push a matching tag:
|
|
365
|
-
|
|
366
|
-
```bash
|
|
367
|
-
git tag v0.x.0
|
|
368
|
-
git push origin v0.x.0
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
The publish workflow runs `npm ci`, `npm run test:local`, then
|
|
372
|
-
`npm publish --provenance --access public`.
|
|
373
|
-
|
|
374
|
-
Repository release requirements:
|
|
375
|
-
|
|
376
|
-
- GitHub secret `NPM_TOKEN` must exist for `jmtrs/or-info`.
|
|
377
|
-
- The token must have npm publish permission for `@aggc/or-info`.
|
|
378
|
-
- `id-token: write` is enabled so npm provenance is attached to published versions.
|
|
379
|
-
|
|
380
|
-
After the initial package bootstrap, prefer migrating to npm Trusted Publishing and then
|
|
381
|
-
remove `NPM_TOKEN` from the repository secrets.
|
|
355
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history and details.
|
|
382
356
|
|
|
383
357
|
## Contributing
|
|
384
358
|
|
package/bin/or-info.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { InvalidArgumentError, program } from 'commander';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
|
|
6
6
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
7
|
-
import { fetchModels, findModel, pricePerMillion, contextLength } from '../lib/openrouter.mjs';
|
|
7
|
+
import { fetchModels, findModel, pricePerMillion, contextLength, isFree } from '../lib/openrouter.mjs';
|
|
8
8
|
import { getElo, getAllElo, loadLeaderboard } from '../lib/lmarena.mjs';
|
|
9
9
|
import { rankModels } from '../lib/scorer.mjs';
|
|
10
10
|
import { clearAll, status } from '../lib/cache.mjs';
|
package/lib/formatter.mjs
CHANGED
|
@@ -18,6 +18,15 @@ function fmtPrice(n) {
|
|
|
18
18
|
return RED(`$${n.toFixed(4)}`);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function maybeFreeWarning(model, price) {
|
|
22
|
+
// OpenRouter sometimes reports pricing="0" for non-free models (e.g. z-ai/glm-5.1).
|
|
23
|
+
// Show a warning so users don't get surprised by charges.
|
|
24
|
+
if (price.input === 0 && price.output === 0 && !model?.id?.endsWith(':free') && model?.id !== 'openrouter/free') {
|
|
25
|
+
return YELLOW(' ⚠ reported free — may incur charges on OpenRouter');
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
function fmtCtx(n) {
|
|
22
31
|
if (!n) return DIM('n/a');
|
|
23
32
|
if (n >= 1_000_000) return CYAN(`${(n / 1_000_000).toFixed(1)}M`);
|
|
@@ -46,8 +55,9 @@ export function modelTable(models, { showTags = false } = {}) {
|
|
|
46
55
|
const price = pricePerMillion(m);
|
|
47
56
|
const ctx = contextLength(m);
|
|
48
57
|
const tags = showTags ? modelTags(m) : [];
|
|
58
|
+
const idLabel = maybeFreeWarning(m, price) ? m.id + YELLOW(' ⚠') : m.id;
|
|
49
59
|
return {
|
|
50
|
-
id:
|
|
60
|
+
id: idLabel,
|
|
51
61
|
name: m.name ?? m.id,
|
|
52
62
|
input: fmtPrice(price.input),
|
|
53
63
|
output: fmtPrice(price.output),
|
|
@@ -95,6 +105,9 @@ export function modelDetail(model, bench = null) {
|
|
|
95
105
|
` Output ${fmtPrice(price.output)} per 1M tokens`,
|
|
96
106
|
];
|
|
97
107
|
|
|
108
|
+
const freeWarn = maybeFreeWarning(model, price);
|
|
109
|
+
if (freeWarn) lines.push(freeWarn);
|
|
110
|
+
|
|
98
111
|
if (price.image !== null) lines.push(` Image ${fmtPrice(price.image)} per 1M tokens`);
|
|
99
112
|
if (price.cacheRead !== null) lines.push(` Cache↓ ${fmtPrice(price.cacheRead)} per 1M tokens`);
|
|
100
113
|
if (price.cacheWrite !== null) lines.push(` Cache↑ ${fmtPrice(price.cacheWrite)} per 1M tokens`);
|
package/lib/openrouter.mjs
CHANGED
|
@@ -135,6 +135,11 @@ export function modelTags(model) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
export function isFree(model) {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// A model is genuinely free only if it carries the :free suffix
|
|
139
|
+
// or is a known free-tier model. OpenRouter sometimes reports
|
|
140
|
+
// pricing.prompt="0" & pricing.completion="0" for models that are NOT free
|
|
141
|
+
// (e.g. z-ai/glm-5.1) — a known API inconsistency.
|
|
142
|
+
if (model?.id?.endsWith(':free')) return true;
|
|
143
|
+
if (model?.id === 'openrouter/free') return true;
|
|
144
|
+
return false;
|
|
140
145
|
}
|
package/mcp/server.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
-
import { fetchModels, findModel, pricePerMillion, contextLength, modelTags } from '../lib/openrouter.mjs';
|
|
6
|
+
import { fetchModels, findModel, pricePerMillion, contextLength, modelTags, isFree } from '../lib/openrouter.mjs';
|
|
7
7
|
import { getElo, getAllElo, loadLeaderboard } from '../lib/lmarena.mjs';
|
|
8
8
|
import { rankModels } from '../lib/scorer.mjs';
|
|
9
9
|
import { getApiKey } from '../lib/secrets.mjs';
|
|
@@ -294,7 +294,7 @@ async function handleTool(name, args) {
|
|
|
294
294
|
|
|
295
295
|
let models = await fetchModels({ apiKey: key });
|
|
296
296
|
if (filter) models = models.filter((m) => m.id.toLowerCase().includes(filter) || (m.name ?? '').toLowerCase().includes(filter));
|
|
297
|
-
if (freeOnly) models = models.filter(
|
|
297
|
+
if (freeOnly) models = models.filter(isFree);
|
|
298
298
|
|
|
299
299
|
if (sortBy === 'price') models.sort((a, b) => (pricePerMillion(a).output ?? Infinity) - (pricePerMillion(b).output ?? Infinity));
|
|
300
300
|
else if (sortBy === 'context') models.sort((a, b) => (contextLength(b) ?? 0) - (contextLength(a) ?? 0));
|