auto-model-router 0.1.3 → 0.2.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.
- package/.omp-plugin/marketplace.json +2 -2
- package/README.md +127 -46
- package/bun.lock +606 -0
- package/omp-extension/router-embed.ts +14 -6
- package/omp-extension/router-toast.ts +6 -1
- package/omp-extension/toast-logic.ts +7 -0
- package/package.json +2 -1
- package/research/analyze-ledger.ts +173 -0
- package/research/apply-cost-tuning.ts +73 -0
- package/research/cost-analysis.ts +150 -0
- package/research/feed-check.ts +64 -0
- package/research/model-recommendations.ts +86 -0
- package/research/project-yield.ts +96 -0
- package/research/run-eval.ts +133 -0
- package/research/status.ts +55 -0
- package/research/tier-fill.ts +109 -0
- package/research/tier-map.ts +123 -0
- package/src/catalog/benchmark-feeds.ts +397 -0
- package/src/catalog/openrouter-catalog.ts +30 -0
- package/src/config/defaults.ts +30 -0
- package/src/config/load.ts +2 -0
- package/src/config/schema.ts +34 -0
- package/src/config/types.ts +106 -0
- package/src/cost/ledger.ts +27 -3
- package/src/cost/types.ts +30 -0
- package/src/eval/calibrate.ts +131 -0
- package/src/eval/grade.ts +115 -0
- package/src/eval/judge.ts +71 -0
- package/src/eval/run.ts +126 -0
- package/src/eval/tasks.ts +272 -0
- package/src/index.ts +0 -1
- package/src/router/candidates.ts +13 -6
- package/src/router/explore.ts +59 -0
- package/src/router/select.ts +54 -4
- package/src/router/tier-plan.ts +57 -1
- package/src/router/types.ts +13 -0
- package/src/server/turn.ts +10 -2
- package/src/util/sqlite.ts +79 -1
- package/src/wire/openai/request.ts +5 -0
- package/src/wire/types.ts +7 -0
- package/test/benchmark-feeds.test.ts +222 -0
- package/test/escalate.test.ts +1 -0
- package/test/eval.test.ts +184 -0
- package/test/exploration.test.ts +251 -0
- package/test/failover.test.ts +5 -0
- package/test/hold-exploration.test.ts +124 -0
- package/test/tier-plan.test.ts +55 -1
- package/test/toast-logic.test.ts +32 -0
- package/test/tokens.test.ts +8 -0
- package/test/trust-attribution.test.ts +110 -2
- package/test/turn.test.ts +46 -0
- package/test/wire-request.test.ts +11 -0
- package/tools/smoke.ts +2 -0
- package/tools/sync-marketplace-version.ts +60 -0
|
@@ -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.
|
|
10
|
+
"version": "0.2.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.
|
|
17
|
+
"version": "0.2.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "drewappling",
|
|
20
20
|
"email": "drewappling@gmail.com"
|
package/README.md
CHANGED
|
@@ -6,13 +6,14 @@ OpenRouter model **per turn** based on measured price and estimated task
|
|
|
6
6
|
complexity — including mid-conversation, when a session shifts from mechanical
|
|
7
7
|
tool-loop churn to genuine reasoning work.
|
|
8
8
|
|
|
9
|
-
All LLM inference is offloaded to OpenRouter. Nothing runs on-device except
|
|
10
|
-
routing arithmetic.
|
|
11
|
-
|
|
12
9
|
auto-model-router runs **embedded inside the omp process** (as an omp extension) — no
|
|
13
10
|
separate server, no orphaned process. It binds a free OS-assigned port and
|
|
14
11
|
lives and dies with the omp session.
|
|
15
12
|
|
|
13
|
+
For non-omp harnesses (Hermes, Claude, any OpenAI-compatible client), run it as
|
|
14
|
+
a standalone process with `auto-model-router serve --port <n>` — the same core,
|
|
15
|
+
on a fixed port, owned by you. See [Hermes](#hermes) below.
|
|
16
|
+
|
|
16
17
|
## Why this exists when OpenRouter already ships routers
|
|
17
18
|
|
|
18
19
|
OpenRouter has `openrouter/auto` (market-spend classifier) and
|
|
@@ -62,7 +63,7 @@ without touching routing.
|
|
|
62
63
|
| `src/router/` | Feature extraction, complexity classification, candidate filtering and scoring, hysteresis, cache-breakpoint placement, budget guard, probe planning. |
|
|
63
64
|
| `src/upstream/` | OpenRouter transport: streaming dispatch, `session_id` stickiness, error classification, fallback arrays. |
|
|
64
65
|
| `src/config/` | Configuration loading, schema validation, and the built-in defaults. |
|
|
65
|
-
| `src/cli/` | `stats`, `models`, `explain`, `config` commands. |
|
|
66
|
+
| `src/cli/` | `serve`, `stats`, `models`, `explain`, `config` commands. |
|
|
66
67
|
| `omp-extension/` | The omp extensions: `router-embed.ts`, `router-toast.ts`, `router-configure.ts`. |
|
|
67
68
|
|
|
68
69
|
### Two cost numbers, never conflated
|
|
@@ -73,16 +74,33 @@ without touching routing.
|
|
|
73
74
|
- **Reported** — `usage.cost` from OpenRouter, authoritative after the fact.
|
|
74
75
|
Drives the ledger, `stats`, and prediction-error calibration.
|
|
75
76
|
|
|
76
|
-
##
|
|
77
|
+
## Installing
|
|
78
|
+
|
|
79
|
+
No separate Bun install is needed for the embedded path. The standalone
|
|
80
|
+
`serve` binary (`npm install -g auto-model-router`) bundles Bun.
|
|
81
|
+
|
|
82
|
+
Two ways to get the router into omp. The **npm package** is the modern path —
|
|
83
|
+
it installs the `auto-model-router` binary and wires the omp extensions; the
|
|
84
|
+
**repo-local installer** is for developing against the source.
|
|
85
|
+
|
|
86
|
+
### Via npm (installs the `auto-model-router` binary)
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g auto-model-router
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then add the shipped extensions to omp's `~/.omp/agent/config.yml`
|
|
93
|
+
(`$PI_CODING_AGENT_DIR/config.yml` when that env var relocates the agent dir):
|
|
81
94
|
|
|
82
|
-
|
|
95
|
+
```yaml
|
|
96
|
+
# ~/.omp/agent/config.yml
|
|
97
|
+
extensions:
|
|
98
|
+
- auto-model-router/omp-extension/router-embed.ts
|
|
99
|
+
- auto-model-router/omp-extension/router-toast.ts # optional: chosen-model toasts
|
|
100
|
+
- auto-model-router/omp-extension/router-configure.ts # optional: /router command
|
|
101
|
+
```
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
(Windows, macOS, Linux) from the repo:
|
|
103
|
+
### From the repo (cross-platform installer)
|
|
86
104
|
|
|
87
105
|
```bash
|
|
88
106
|
bun tools/install.ts
|
|
@@ -138,6 +156,9 @@ or in the TUI:
|
|
|
138
156
|
/marketplace install auto-model-router@auto-model-router
|
|
139
157
|
```
|
|
140
158
|
|
|
159
|
+
After installing, restart the omp session (extensions load at session start),
|
|
160
|
+
then `/model` and pick `auto-model-router/auto`.
|
|
161
|
+
|
|
141
162
|
### Install from the Pi package marketplace
|
|
142
163
|
|
|
143
164
|
The repo is also a Pi package (see the `pi` manifest and `pi-package` keyword
|
|
@@ -154,58 +175,109 @@ or from git:
|
|
|
154
175
|
pi install git:github.com/drewappling/auto-model-router
|
|
155
176
|
```
|
|
156
177
|
|
|
157
|
-
|
|
178
|
+
#### Releasing
|
|
179
|
+
|
|
180
|
+
Cut releases with `npm version` (or `bun run release <patch|minor|major>`), not a
|
|
181
|
+
bare `npm publish`:
|
|
158
182
|
|
|
159
183
|
```bash
|
|
160
|
-
npm
|
|
184
|
+
npm version patch && git push --follow-tags # or: bun run release patch
|
|
161
185
|
```
|
|
162
186
|
|
|
187
|
+
`npm version` runs the `version` lifecycle script
|
|
188
|
+
(`tools/sync-marketplace-version.ts`), which rewrites the Git-marketplace
|
|
189
|
+
catalog (`.omp-plugin/marketplace.json`) to the new version and stages it into
|
|
190
|
+
the version commit — so the npm package and the marketplace catalog can never
|
|
191
|
+
drift. Pushing the `vX.Y.Z` tag triggers the release workflow (npm publish,
|
|
192
|
+
which auto-indexes on pi.dev/packages, plus a GitHub Release). A bare
|
|
193
|
+
`npm publish` skips both the catalog sync and the tag, so avoid it.
|
|
194
|
+
|
|
163
195
|
### Hermes
|
|
164
196
|
|
|
165
|
-
|
|
166
|
-
|
|
197
|
+
Install the router globally (puts the `serve` binary on PATH) and
|
|
198
|
+
install the native plugin, then point Hermes at it:
|
|
167
199
|
|
|
168
|
-
**
|
|
169
|
-
|
|
200
|
+
**1. Install the router binary:**
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm install -g auto-model-router
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**2. Install the Hermes plugin.** Copy `hermes-plugin/` to
|
|
207
|
+
`$HERMES_HOME/plugins/model-providers/auto-model-router/` (where
|
|
208
|
+
`HERMES_HOME` is `C:\Users\<you>\AppData\Local\hermes` on Windows,
|
|
209
|
+
`~/.hermes` on macOS/Linux):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
mkdir -p "$HERMES_HOME/plugins/model-providers"
|
|
213
|
+
cp -r hermes-plugin/ "$HERMES_HOME/plugins/model-providers/auto-model-router/"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**3. Surface the provider in Hermes's picker.** Hermes only lists providers
|
|
217
|
+
that have a credential. The router itself is keyless (it resolves its own
|
|
218
|
+
OpenRouter key), but to make Hermes show it as selectable, add a marker value
|
|
219
|
+
to `$HERMES_HOME/.env`:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
echo "AUTO_MODEL_ROUTER_API_KEY=local" >> "$HERMES_HOME/.env"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**4. Restart Hermes.** On load, the plugin spawns the router (`auto-model-router
|
|
226
|
+
serve`) as a subprocess on port 8788 and registers the provider profile. Select
|
|
227
|
+
`auto-model-router/auto` as the model.
|
|
228
|
+
|
|
229
|
+
The plugin runs the router against its **own** config home
|
|
230
|
+
(`$HERMES_HOME/auto-model-router/`), separate from omp's
|
|
231
|
+
`~/.auto-model-router/`, so the two harnesses never share a ledger or
|
|
232
|
+
conversation state and don't leak routing toasts into each other's UIs.
|
|
233
|
+
|
|
234
|
+
The router serves `GET /v1/models` (returning the `auto`, `auto-cheap`,
|
|
235
|
+
`auto-max` profiles) and `POST /v1/chat/completions`, which Hermes's custom
|
|
236
|
+
endpoint discovery verifies. The router's own OpenRouter key resolution
|
|
237
|
+
(config → env → omp auth store) applies — Hermes does not need its own
|
|
238
|
+
OpenRouter key.
|
|
239
|
+
|
|
240
|
+
**Standalone alternative (no plugin):** run the router yourself, then add a
|
|
241
|
+
custom provider:
|
|
170
242
|
|
|
171
243
|
```bash
|
|
172
244
|
auto-model-router serve --port 8788
|
|
173
245
|
```
|
|
174
246
|
|
|
175
247
|
```yaml
|
|
176
|
-
#
|
|
248
|
+
# $HERMES_HOME/config.yaml
|
|
177
249
|
providers:
|
|
178
250
|
auto-model-router:
|
|
179
251
|
base_url: http://127.0.0.1:8788/v1
|
|
180
|
-
api_key:
|
|
252
|
+
api_key: local
|
|
181
253
|
default_model: auto
|
|
182
254
|
```
|
|
183
255
|
|
|
184
|
-
**Hermes plugin (native):** copy `hermes-plugin/` to
|
|
185
|
-
`$HERMES_HOME/plugins/model-providers/auto-model-router/` and restart Hermes.
|
|
186
|
-
The plugin spawns the router as a subprocess on load and registers the provider
|
|
187
|
-
profile, so Hermes routes each turn through the router automatically.
|
|
188
|
-
|
|
189
|
-
The router serves `GET /v1/models` (returning the `auto`, `auto-cheap`,
|
|
190
|
-
`auto-max` profiles) and `POST /v1/chat/completions`, which Hermes's custom
|
|
191
|
-
endpoint discovery verifies. Select `auto-model-router/auto` as the model and
|
|
192
|
-
the router routes each turn by price and complexity. The router's own OpenRouter
|
|
193
|
-
key resolution (config → env → omp auth store) applies — Hermes does not need
|
|
194
|
-
its own OpenRouter key.
|
|
195
|
-
|
|
196
256
|
### The OpenRouter key
|
|
197
257
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
258
|
+
**omp does not need to be authenticated to OpenRouter.** On a routed turn omp
|
|
259
|
+
never calls OpenRouter directly: the embed extension registers the
|
|
260
|
+
`auto-model-router` provider with a placeholder bearer (`embedded`) pointing at
|
|
261
|
+
the in-process router, and the router holds the real OpenRouter key and makes
|
|
262
|
+
the upstream call. omp only needs to see that the provider "has credentials",
|
|
263
|
+
which the placeholder satisfies.
|
|
264
|
+
|
|
265
|
+
There should be exactly one OpenRouter key on the machine. The router resolves
|
|
266
|
+
it in this order:
|
|
267
|
+
|
|
268
|
+
1. `openrouter.apiKey` in `$AUTO_MODEL_ROUTER_HOME/config.yml` — router-owned,
|
|
269
|
+
never enters omp's environment. Set it with `auto-model-router config` or by
|
|
270
|
+
hand.
|
|
271
|
+
2. `OPENROUTER_API_KEY` in the environment omp launches from (including any
|
|
272
|
+
`.env` omp loaded).
|
|
273
|
+
3. **omp's own auth store** — `~/.omp/agent/agent.db`, provider `openrouter`, so
|
|
274
|
+
`/login openrouter` inside omp is sufficient and nothing needs copying.
|
|
275
|
+
|
|
276
|
+
Options 1–2 give the router its own key with omp left unauthenticated; option 3
|
|
277
|
+
is a zero-config convenience for when you *have* logged omp in. The store is
|
|
278
|
+
opened read-only and never written: omp owns it, including OAuth refresh. An
|
|
279
|
+
expired OAuth access token is rejected rather than sent, because refreshing is
|
|
280
|
+
omp's job and a stale bearer just burns a turn on a 401. Under
|
|
209
281
|
`OMP_AUTH_BROKER_URL` the local store is not consulted at all, since a broker
|
|
210
282
|
replaces it.
|
|
211
283
|
|
|
@@ -307,7 +379,8 @@ disk and back up the previous file to a timestamped `.bak`.
|
|
|
307
379
|
| --- | --- | --- |
|
|
308
380
|
| `OPENROUTER_API_KEY` | OpenRouter key (overrides the auth store). | — |
|
|
309
381
|
| `AUTO_MODEL_ROUTER_HOME` | Config + database directory. | `~/.auto-model-router` |
|
|
310
|
-
| `
|
|
382
|
+
| `AUTO_MODEL_ROUTER_HOST` | Bind address override. | `127.0.0.1` |
|
|
383
|
+
| `AUTO_MODEL_ROUTER_LOG` | Log level: `silent`/`error`/`warn`/`info`/`debug`. | `info` |
|
|
311
384
|
| `AUTO_MODEL_ROUTER_LOG` | Log level: `silent`/`error`/`warn`/`info`/`debug`. | `info` |
|
|
312
385
|
| `AUTO_MODEL_ROUTER_DB` | Override the ledger path. | `$AUTO_MODEL_ROUTER_HOME/router.db` |
|
|
313
386
|
| `AUTO_MODEL_ROUTER_URL` | Toast/base URL override (the toast reads the shared port file first). | — |
|
|
@@ -472,8 +545,15 @@ on each other:
|
|
|
472
545
|
- **Per-harness daily budget** — each harness sends an `X-Omp-Harness` header
|
|
473
546
|
(from the provider block's `headers:`), and the router scopes the rolling
|
|
474
547
|
24h `perDayUsd` ceiling to it. One harness can't exhaust the day for another.
|
|
475
|
-
- **Per-
|
|
476
|
-
extension
|
|
548
|
+
- **Per-session toasts** — the toast surfaces only the decisions made by *its
|
|
549
|
+
own* omp session. The embed extension tags every request with an
|
|
550
|
+
`X-Omp-Session` header (`ctx.sessionManager.getSessionId()`), the router
|
|
551
|
+
records it on each ledger row, and the toast filters on it. Two concurrent
|
|
552
|
+
interactive sessions — even of the same harness — never surface each other's
|
|
553
|
+
model choices. This needs no configuration.
|
|
554
|
+
- **Per-harness toasts** — additionally set `OMP_HARNESS_ID` to the same value
|
|
555
|
+
so the extension only toasts that harness's model choices. Session scoping is
|
|
556
|
+
finer-grained; harness scoping still applies on top when set.
|
|
477
557
|
|
|
478
558
|
Configure a harness by setting `server.harnessId`; set the same id in that
|
|
479
559
|
harness's `OMP_HARNESS_ID` env var.
|
|
@@ -509,7 +589,8 @@ bound, even though it changes every session.
|
|
|
509
589
|
The toast logic is a pure, unit-tested module
|
|
510
590
|
(`omp-extension/toast-logic.ts`, covered by `test/toast-logic.test.ts`): it
|
|
511
591
|
toasts only decisions newer than the last seen one, skips `wasted` escalation
|
|
512
|
-
attempts,
|
|
592
|
+
attempts, prefers the actual serving slug over the requested one, and filters
|
|
593
|
+
to the toast's own omp session id (and harness id, when set).
|
|
513
594
|
|
|
514
595
|
---
|
|
515
596
|
|