@aggc/or-info 0.2.15 → 0.2.17

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/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: m.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`);
@@ -135,6 +135,11 @@ export function modelTags(model) {
135
135
  }
136
136
 
137
137
  export function isFree(model) {
138
- const p = pricePerMillion(model);
139
- return p.input === 0 && p.output === 0;
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((m) => { const p = pricePerMillion(m); return p.input === 0 && p.output === 0; });
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aggc/or-info",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "CLI + MCP server for OpenRouter models: prices, benchmarks, context and comparisons",
5
5
  "type": "module",
6
6
  "engines": {