auto-model-router 0.2.12 → 0.2.13

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.2.12",
10
+ "version": "0.2.13",
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.2.12",
17
+ "version": "0.2.13",
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.2.12",
3
+ "version": "0.2.13",
4
4
  "private": false,
5
5
  "description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
6
6
  "type": "module",
@@ -335,7 +335,15 @@ export async function runTurn(
335
335
  if (ttftMs === null) ttftMs = Date.now() - startedAt;
336
336
  if (doxActive) assistantText += ev.delta;
337
337
  break;
338
+ // A tool call is output too. Only text/reasoning used to stamp
339
+ // TTFT, so a dispatch that emitted nothing BUT tool calls —
340
+ // the dominant shape in an agentic loop, 83% of dispatches —
341
+ // recorded ttft_ms NULL and was then discarded by
342
+ // LATENCY_SELECT, which requires it. Latency/throughput
343
+ // scoring was therefore measuring the minority of turns that
344
+ // happened to narrate, and steering all the rest with it.
338
345
  case "reasoning":
346
+ case "tool_call":
339
347
  if (ttftMs === null) ttftMs = Date.now() - startedAt;
340
348
  break;
341
349
  case "finish":
package/test/turn.test.ts CHANGED
@@ -694,3 +694,36 @@ describe("spend reaches the conversation total however the dispatch ends", () =>
694
694
  expect(accrued.get("conv-test")?.spentUsd).toBeCloseTo(0.002, 10);
695
695
  });
696
696
  });
697
+
698
+ describe("latency measurement covers the work the router actually does", () => {
699
+ test("a tool-call-only dispatch still records TTFT", async () => {
700
+ // 83% of dispatches in an agentic loop finish with `tool_calls`, and 64.5%
701
+ // of those recorded ttft_ms NULL because only text/reasoning stamped it.
702
+ // LATENCY_SELECT requires ttft_ms, so throughput scoring (weight 0.75) was
703
+ // measuring the narrating minority and steering everything else with it.
704
+ const { router } = mkRouter([mkDecision("simple", "cheap/model", { maxTokens: 1, escalateTo: null })]);
705
+ const { upstream } = mkUpstream([
706
+ {
707
+ kind: "chunks",
708
+ chunks: [
709
+ startChunk("cheap/model"),
710
+ chunk([{ type: "tool_call", index: 0, id: "c1", name: "read", argsDelta: '{"path":"a.ts"}' }]),
711
+ finishChunk("tool_calls"),
712
+ usageChunk({ promptTokens: 50_000, completionTokens: 160 }, 0.004),
713
+ ],
714
+ },
715
+ ]);
716
+ const { ledger, entries } = mkLedger();
717
+ const { store } = mkConversations();
718
+ const { sink, errors } = mkSink();
719
+
720
+ await runTurn(mkReq(), sink, { config: mkConfig(), router, upstream, ledger, conversations: store, catalog, context: createDisabledBridge() }, new AbortController().signal);
721
+
722
+ expect(errors).toHaveLength(0);
723
+ expect(entries).toHaveLength(1);
724
+ expect(entries[0]?.finishReason).toBe("tool_calls");
725
+ // The qualifying condition for latency stats: a real, positive TTFT.
726
+ expect(entries[0]?.ttftMs).not.toBeNull();
727
+ expect(entries[0]?.ttftMs ?? -1).toBeGreaterThanOrEqual(0);
728
+ });
729
+ });