auto-model-router 0.4.6 → 0.4.7

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.4.6",
10
+ "version": "0.4.7",
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.4.6",
17
+ "version": "0.4.7",
18
18
  "author": {
19
19
  "name": "drewappling",
20
20
  "email": "drewappling@gmail.com"
@@ -273,9 +273,11 @@ def _why_text(e: Dict[str, Any]) -> str:
273
273
  pt, ct = usage.get("promptTokens", 0) or 0, usage.get("cachedTokens", 0) or 0
274
274
  cache = f"{round(100 * ct / pt)}%" if pt else "n/a"
275
275
  cost = e.get("reportedUsd")
276
+ if cost is None:
277
+ cost = e.get("predictedUsd") or 0
276
278
  lines = [
277
279
  f"last turn: {e.get('servedSlug') or e.get('slug')} [{e.get('tier')}] · {e.get('classificationSource')} (confidence {e.get('confidence')})",
278
- f"cost ${cost if cost is not None else e.get('predictedUsd')} · cache hit {cache} · latency {e.get('latencyMs')}ms",
280
+ f"cost ${float(cost):.5f} · cache hit {cache} · latency {e.get('latencyMs')}ms",
279
281
  ]
280
282
  for r in e.get("reasons") or []:
281
283
  lines.append(f" - {r}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-model-router",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
6
6
  "type": "module",
@@ -121,6 +121,14 @@ interface RecentDigest {
121
121
  atMs: number;
122
122
  ledgerId: string;
123
123
  rerun: boolean;
124
+ /**
125
+ * Whether the call that PRODUCED this digest has been seen. A tool_result
126
+ * digest is made before the next request, and that request's last
127
+ * assistant message carries the producing call; it must not count as a
128
+ * re-run. A compaction digest covers a call already in history, so its
129
+ * origin counts as seen from the start.
130
+ */
131
+ originSeen: boolean;
124
132
  }
125
133
  const RERUN_WINDOW_MS = 2 * 3_600_000;
126
134
  const RECENT_PER_SESSION = 50;
@@ -261,7 +269,7 @@ export function createDigester(deps: DigesterDeps): Digester {
261
269
  if (text === "" || text.length >= inputBytes * 0.9) return { digested: false, reason: "digest did not shrink the output" };
262
270
  if (req.ompSessionId !== "") {
263
271
  const list = recent.get(req.ompSessionId) ?? [];
264
- list.push({ tool: req.toolName.toLowerCase(), arg: primaryArg(JSON.stringify(req.input)), atMs: startedAt, ledgerId: entry.id, rerun: false });
272
+ list.push({ tool: req.toolName.toLowerCase(), arg: primaryArg(JSON.stringify(req.input)), atMs: startedAt, ledgerId: entry.id, rerun: false, originSeen: source === "compaction" });
265
273
  recent.set(req.ompSessionId, list.slice(-RECENT_PER_SESSION));
266
274
  }
267
275
  return {
@@ -284,6 +292,11 @@ export function createDigester(deps: DigesterDeps): Digester {
284
292
  if (arg === null) continue;
285
293
  for (const d of list) {
286
294
  if (d.rerun || d.tool !== tool || d.arg !== arg || nowMs - d.atMs > RERUN_WINDOW_MS) continue;
295
+ if (!d.originSeen) {
296
+ // The producing call, arriving in the next request's history.
297
+ d.originSeen = true;
298
+ continue;
299
+ }
287
300
  d.rerun = true;
288
301
  marked++;
289
302
  try {
@@ -198,6 +198,9 @@ describe("createDigester", () => {
198
198
  expect(dg.noteToolCalls("omp-1", [{ name: "read", argsJson: '{"path":"src/b.ts"}' }, { name: "grep", argsJson: '{"pattern":"src/a.ts"}' }])).toBe(0);
199
199
  expect(dg.noteToolCalls("omp-2", [{ name: "read", argsJson: '{"path":"src/a.ts"}' }])).toBe(0);
200
200
  expect(row().wasted).toBe(false);
201
+ // The next request carries the call that PRODUCED the digest in its last assistant message: not a re-run.
202
+ expect(dg.noteToolCalls("omp-1", [{ name: "read", argsJson: '{"path":"src/a.ts","offset":1}' }])).toBe(0);
203
+ expect(row().wasted).toBe(false);
201
204
  // The same read again (case-insensitive tool name, any other args): the agent wanted the full output.
202
205
  expect(dg.noteToolCalls("omp-1", [{ name: "Read", argsJson: '{"path":"src/a.ts","limit":50}' }])).toBe(1);
203
206
  expect(row().wasted).toBe(true);