auto-model-router 0.27.1 → 0.28.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.
@@ -7,14 +7,14 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "auto-model-router: a local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
10
- "version": "0.27.1",
10
+ "version": "0.28.0",
11
11
  "pluginRoot": "."
12
12
  },
13
13
  "plugins": [
14
14
  {
15
15
  "name": "auto-model-router",
16
16
  "description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter. Runs in-process, routes per turn by price and task complexity, with budget caps, mid-stream escalation, and cache-aware hysteresis.",
17
- "version": "0.27.1",
17
+ "version": "0.28.0",
18
18
  "author": {
19
19
  "name": "drewappling",
20
20
  "email": "drewappling@gmail.com"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-model-router",
3
- "version": "0.27.1",
3
+ "version": "0.28.0",
4
4
  "private": false,
5
5
  "description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
6
6
  "type": "module",
@@ -125,16 +125,22 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
125
125
  for (const l of listings) {
126
126
  if (!l.isCloud && !cfg.includeLocal) continue;
127
127
  const priceName = l.remoteModel ?? l.id;
128
- const rate = ollamaRateFor(priceName, cfg.prices);
129
- if (rate === null) {
130
- skipped.push(l.id);
131
- continue;
132
- }
133
128
  // A pin may name the tagged cloud name, its base, or the listed id.
134
129
  const bare = bareCloudName(priceName);
135
130
  const base = bare.includes(":") ? bare.slice(0, bare.indexOf(":")) : bare;
136
131
  const pinned = cfg.twins[bare] ?? cfg.twins[base] ?? cfg.twins[l.id];
137
132
  const twin = (pinned !== undefined ? bySlug.get(pinned) : undefined) ?? twins.get(ollamaTwinKey(priceName)) ?? null;
133
+ // Ollama publishes no prices, so they come from a static table — which means a model
134
+ // Ollama ships today is INVISIBLE until that table gains an entry, however good it is.
135
+ // Measured: deepseek-v4.1-flash was listed by /api/tags and dropped here, so routing
136
+ // never saw it. The OpenRouter twin sells the same weights per token, so it is the
137
+ // honest stand-in until the table catches up; a model with neither is still skipped.
138
+ const rate = ollamaRateFor(priceName, cfg.prices);
139
+ const price = rate !== null ? toPrice(rate.rate) : twin !== null ? { ...twin.price } : null;
140
+ if (price === null) {
141
+ skipped.push(`${l.id} (no price and no twin)`);
142
+ continue;
143
+ }
138
144
  const contextLength = l.contextLength ?? twin?.contextLength ?? null;
139
145
  if (contextLength === null) {
140
146
  skipped.push(`${l.id} (no context length)`);
@@ -156,7 +162,7 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
156
162
  // Ollama's OpenAI-compatible endpoint documents `tool_choice` as unsupported.
157
163
  supportsToolChoice: false,
158
164
  inputModalities: modalities,
159
- price: toPrice(rate.rate),
165
+ price,
160
166
  priceTiers: [],
161
167
  quality: twin === null ? {} : { ...twin.quality },
162
168
  tokenizer: twin?.tokenizer ?? "Other",
@@ -167,7 +173,10 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
167
173
  if (twin?.maxCompletionTokens !== undefined) model.maxCompletionTokens = twin.maxCompletionTokens;
168
174
  out.push(model);
169
175
  }
170
- if (skipped.length > 0) log?.debug("ollama models skipped (no price or context)", { skipped: skipped.join(", ") });
176
+ // A model the provider offers and the router refuses to route is operationally
177
+ // significant, not a debug detail: at `debug` this was invisible, and a newly published
178
+ // model stayed unroutable with nothing in the log to say so.
179
+ if (skipped.length > 0) log?.warn("ollama models listed but not routable", { skipped: skipped.join(", ") });
171
180
  return out;
172
181
  }
173
182
 
@@ -164,6 +164,22 @@ describe("buildOllamaModels", () => {
164
164
  expect(ds.quality).toEqual({});
165
165
  });
166
166
 
167
+ test("a model Ollama lists but the price table does not know is priced from its twin", () => {
168
+ // Ollama publishes no prices, so they come from a static table. deepseek-v4.1-flash was
169
+ // listed by /api/tags and dropped here for want of an entry, so routing never saw it at
170
+ // all — a model the provider was actively offering. The twin sells the same weights.
171
+ const unpriced = { id: "glm-5.3-flash:cloud", remoteModel: "not-in-the-price-table", isCloud: true, contextLength: 262_144, capabilities: ["tools"], modifiedAtMs: 0 };
172
+ const twinPriced = buildOllamaModels({ listings: [{ ...unpriced, remoteModel: "glm-5.3-flash" }], openrouter: OR_MODELS, cfg: OLLAMA, log });
173
+ const or = OR_MODELS.find((m) => m.slug === "z-ai/glm-5.3-flash")!;
174
+ expect(twinPriced).toHaveLength(1);
175
+ expect(twinPriced[0]!.price.prompt).toBeCloseTo(0.15 / 1e6, 12);
176
+
177
+ // Unknown to the table AND matching no twin: still skipped, never priced at zero.
178
+ const orphan = buildOllamaModels({ listings: [{ ...unpriced, id: "brand-new-thing:cloud", remoteModel: "brand-new-thing" }], openrouter: OR_MODELS, cfg: OLLAMA, log });
179
+ expect(orphan).toEqual([]);
180
+ expect(or.price.prompt).toBeGreaterThan(0);
181
+ });
182
+
167
183
  test("a pinned twin beats the name match", () => {
168
184
  const cfg: OllamaConfig = { ...OLLAMA, twins: { "deepseek-v4-pro": "moonshotai/kimi-k3" } };
169
185
  const ds = buildOllamaModels({ listings: listings(), openrouter: OR_MODELS, cfg, log }).find((m) => m.slug.startsWith("ollama/deepseek"))!;