auto-model-router 0.2.0 → 0.2.1

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.0",
10
+ "version": "0.2.1",
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.0",
17
+ "version": "0.2.1",
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.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
6
6
  "type": "module",
@@ -43,9 +43,18 @@ const W_LARGE_CONTENT = 0.05;
43
43
  const W_HUGE_CONTENT = 0.1;
44
44
  const W_TURN_DEPTH = 0.004; // long conversations accumulate entangled context
45
45
  const CAP_TURN_DEPTH = 0.08;
46
- const W_LOOP_DEPTH = -0.008; // deep loops are mechanical
46
+ const W_LOOP_DEPTH = -0.008; // a single deep step is mechanical
47
47
  const CAP_LOOP_DEPTH = -0.06;
48
- const W_AUTONOMOUS_LOOP = 0.06; // but a loop this long implies a substantial underlying task
48
+ // A sustained autonomous loop is the signal the underlying task is substantial:
49
+ // the agent keeps grinding without human input. Unlike the mechanical-step
50
+ // penalty above, this term ACCUMULATES with depth past the agentic threshold so
51
+ // long/hard loops climb out of trivial instead of scoring like a one-line edit.
52
+ // Capped so pure depth tops out in `simple` (a competent-but-cheap model);
53
+ // reaching `moderate`/`hard` still requires real complexity signals (tool
54
+ // failure, repeated call, keywords) to stack on top.
55
+ const W_AUTONOMOUS_LOOP = 0.06; // base bonus at the threshold
56
+ const W_AUTONOMOUS_LOOP_PER_DEPTH = 0.018; // added per loop step beyond the threshold
57
+ const CAP_AUTONOMOUS_LOOP = 0.34;
49
58
  const W_IMAGES = 0.04;
50
59
  const W_TOOLS_OFFERED = 0.03;
51
60
 
@@ -100,7 +109,11 @@ export function scoreHeuristic(f: Features, cfg: RouterConfig): Classification {
100
109
  add(Math.min(f.turnDepth * W_TURN_DEPTH, CAP_TURN_DEPTH), `conversation depth ${f.turnDepth}`);
101
110
  add(Math.max(f.toolLoopDepth * W_LOOP_DEPTH, CAP_LOOP_DEPTH), `tool loop depth ${f.toolLoopDepth}`);
102
111
  if (f.toolLoopDepth >= cfg.classifier.agenticLoopDepth && f.toolLoopDepth > 0) {
103
- add(W_AUTONOMOUS_LOOP, "long autonomous loop implies a substantial task");
112
+ const excessDepth = f.toolLoopDepth - cfg.classifier.agenticLoopDepth;
113
+ add(
114
+ Math.min(W_AUTONOMOUS_LOOP + excessDepth * W_AUTONOMOUS_LOOP_PER_DEPTH, CAP_AUTONOMOUS_LOOP),
115
+ `autonomous loop depth ${f.toolLoopDepth} (sustained task)`,
116
+ );
104
117
  }
105
118
  if (f.hasImages) add(W_IMAGES, "image input");
106
119
  if (f.toolCount > 0) add(W_TOOLS_OFFERED, `${f.toolCount} tools offered`);
@@ -37,6 +37,32 @@ function featuresFor(messages: unknown[], tools?: unknown[] | undefined): Featur
37
37
  return extractFeatures(r, 5000);
38
38
  }
39
39
 
40
+ /** A tool-result continuation at a given autonomous-loop depth, no other signals. */
41
+ function contFeatures(toolLoopDepth: number, over: Partial<Features> = {}): Features {
42
+ return {
43
+ promptTokens: 30000,
44
+ newContentTokens: 200,
45
+ turnDepth: toolLoopDepth,
46
+ toolCount: 12,
47
+ toolSchemaBytes: 9783,
48
+ isToolResultContinuation: true,
49
+ toolLoopDepth,
50
+ distinctToolsUsed: 3,
51
+ lastToolFailed: false,
52
+ repeatedToolCall: false,
53
+ hasImages: false,
54
+ codeBlocks: 0,
55
+ codeBytes: 0,
56
+ looksLikeDiff: false,
57
+ complexityKeywords: [],
58
+ trivialityKeywords: [],
59
+ requestedReasoning: undefined,
60
+ questionCount: 0,
61
+ isTerseInstruction: false,
62
+ ...over,
63
+ };
64
+ }
65
+
40
66
  const SYSTEM = { role: "system", content: "You are a coding agent." };
41
67
 
42
68
  /** Upstream double that fails loudly if the adjudicator is consulted. */
@@ -160,6 +186,39 @@ describe("scoreHeuristic", () => {
160
186
  expect(c.confidence).toBeGreaterThanOrEqual(0);
161
187
  expect(c.confidence).toBeLessThanOrEqual(1);
162
188
  });
189
+
190
+ test("a shallow tool-result continuation stays trivial", () => {
191
+ const shallow = scoreHeuristic(contFeatures(2), BASE);
192
+ expect(shallow.tier).toBe("trivial");
193
+ });
194
+
195
+ test("a sustained autonomous loop climbs out of trivial", () => {
196
+ // The failure mode this fixes: a long coding loop pinned to the cheapest
197
+ // tier for dozens of turns because agentic complexity never accumulated.
198
+ const shallow = scoreHeuristic(contFeatures(2), BASE);
199
+ const deep = scoreHeuristic(contFeatures(20), BASE);
200
+ expect(tierIdx(deep.tier)).toBeGreaterThan(tierIdx(shallow.tier));
201
+ expect(deep.tier).not.toBe("trivial");
202
+ });
203
+
204
+ test("score increases monotonically with loop depth past the agentic threshold", () => {
205
+ const depths = [4, 6, 8, 10, 15, 20, 30];
206
+ let prev = -1;
207
+ for (const d of depths) {
208
+ const s = scoreHeuristic(contFeatures(d), BASE).score;
209
+ expect(s).toBeGreaterThanOrEqual(prev);
210
+ prev = s;
211
+ }
212
+ });
213
+
214
+ test("pure loop depth tops out at simple; complexity signals stack to moderate", () => {
215
+ // Depth alone means competent-but-cheap (simple), not a frontier model.
216
+ const veryDeep = scoreHeuristic(contFeatures(90), BASE);
217
+ expect(tierIdx(veryDeep.tier)).toBeLessThanOrEqual(tierIdx("simple"));
218
+ // A failing tool result on top of a deep loop is genuinely hard.
219
+ const deepAndFailing = scoreHeuristic(contFeatures(20, { lastToolFailed: true }), BASE);
220
+ expect(tierIdx(deepAndFailing.tier)).toBeGreaterThanOrEqual(tierIdx("moderate"));
221
+ });
163
222
  });
164
223
 
165
224
  describe("pickQualityAxis", () => {