auto-model-router 0.4.10 → 0.4.12
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 +2 -1
- package/package.json +7 -1
- package/src/cost/report.ts +15 -7
- package/src/cost/summary.ts +5 -4
- package/src/lib.ts +23 -0
- package/test/report.test.ts +14 -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.4.
|
|
10
|
+
"version": "0.4.12",
|
|
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.
|
|
17
|
+
"version": "0.4.12",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "drewappling",
|
|
20
20
|
"email": "drewappling@gmail.com"
|
package/README.md
CHANGED
|
@@ -706,7 +706,8 @@ escalation signal, error. Three views aggregate it, all from the same
|
|
|
706
706
|
transcript. Falls back to reading the ledger directly if the router is
|
|
707
707
|
unreachable.
|
|
708
708
|
- `auto-model-router report --days 7 [--harness <id>] [--json]` on the terminal.
|
|
709
|
-
- `GET /v1/router/report?days=7&harness=<id>` for dashboards
|
|
709
|
+
- `GET /v1/router/report?days=7&harness=<id>` for dashboards (`harness` may be
|
|
710
|
+
a comma-separated set of ids, for a group).
|
|
710
711
|
- `GET /v1/router/summary?harness=<id>` — the daily summary as JSON (`auto=1`
|
|
711
712
|
applies the once-a-day gate and returns `due: false` when nothing is due).
|
|
712
713
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-model-router",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"main": "./src/lib.ts",
|
|
8
|
+
"types": "./src/lib.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/lib.ts",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
7
13
|
"bin": {
|
|
8
14
|
"auto-model-router": "./src/index.ts"
|
|
9
15
|
},
|
package/src/cost/report.ts
CHANGED
|
@@ -187,9 +187,20 @@ function toRow(r: RawRow, windowSpend: number): ReportRow {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/** A harness filter: one id, or several comma-separated (a team's members); empty ⇒ everything. */
|
|
191
|
+
export function harnessFilter(harnessId: string, param = "$harness"): { sql: string[]; bind: Record<string, string> } {
|
|
192
|
+
const ids = harnessId.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
193
|
+
if (ids.length === 0) return { sql: [], bind: {} };
|
|
194
|
+
if (ids.length === 1) return { sql: [`harness_id = ${param}`], bind: { [param]: ids[0]! } };
|
|
195
|
+
const bind: Record<string, string> = {};
|
|
196
|
+
ids.forEach((id, i) => (bind[`${param}${i}`] = id));
|
|
197
|
+
return { sql: [`harness_id IN (${ids.map((_, i) => `${param}${i}`).join(", ")})`], bind };
|
|
198
|
+
}
|
|
199
|
+
|
|
190
200
|
/**
|
|
191
201
|
* Builds the report for the last `windowDays`. `harnessId` narrows to one
|
|
192
|
-
* harness (the `X-Omp-Harness` header)
|
|
202
|
+
* harness (the `X-Omp-Harness` header) or a comma-separated set of them (a
|
|
203
|
+
* team edition group); empty means everything.
|
|
193
204
|
*/
|
|
194
205
|
export function buildUsageReport(
|
|
195
206
|
db: Database,
|
|
@@ -200,12 +211,9 @@ export function buildUsageReport(
|
|
|
200
211
|
const sinceMs = nowMs - windowDays * 86_400_000;
|
|
201
212
|
const harnessId = opts.harnessId ?? "";
|
|
202
213
|
const untilMs = opts.untilMs;
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
...(harnessId === "" ? [] : ["harness_id = $harness"]),
|
|
207
|
-
].join(" AND ");
|
|
208
|
-
const bind = { $since: sinceMs, ...(untilMs === undefined ? {} : { $until: untilMs }), ...(harnessId === "" ? {} : { $harness: harnessId }) };
|
|
214
|
+
const hf = harnessFilter(harnessId);
|
|
215
|
+
const where = ["created_at_ms >= $since", ...(untilMs === undefined ? [] : ["created_at_ms < $until"]), ...hf.sql].join(" AND ");
|
|
216
|
+
const bind = { $since: sinceMs, ...(untilMs === undefined ? {} : { $until: untilMs }), ...hf.bind };
|
|
209
217
|
|
|
210
218
|
const t = db
|
|
211
219
|
.query(
|
package/src/cost/summary.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { Database } from "bun:sqlite";
|
|
15
15
|
import { TIER_ORDER } from "../router/types.ts";
|
|
16
|
-
import { buildUsageReport, type BaselinePrice, type BaselineRow, type UsageReport } from "./report.ts";
|
|
16
|
+
import { buildUsageReport, harnessFilter, type BaselinePrice, type BaselineRow, type UsageReport } from "./report.ts";
|
|
17
17
|
import type { SoftFailureSpike } from "./types.ts";
|
|
18
18
|
|
|
19
19
|
/** One 24-hour window's headline numbers. */
|
|
@@ -89,8 +89,9 @@ function windowOf(r: UsageReport): SummaryWindow {
|
|
|
89
89
|
|
|
90
90
|
/** Counts tier moves up and down between consecutive kept turns of each conversation since `sinceMs`. */
|
|
91
91
|
export function countTierChanges(db: Database, sinceMs: number, harnessId: string): { up: number; down: number } {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
92
|
+
const hf = harnessFilter(harnessId);
|
|
93
|
+
const where = ["created_at_ms >= $since", ...hf.sql].join(" AND ");
|
|
94
|
+
const bind = { $since: sinceMs, ...hf.bind };
|
|
94
95
|
const seq = db
|
|
95
96
|
.query(`SELECT conversation_key AS ck, tier FROM ledger WHERE ${where} AND wasted = 0 AND requested_model <> 'digest' ORDER BY conversation_key, created_at_ms`)
|
|
96
97
|
.all(bind) as { ck: string; tier: string }[];
|
|
@@ -155,7 +156,7 @@ function delta(current: number, previous: number): string {
|
|
|
155
156
|
|
|
156
157
|
/** Renders the summary as a few plain lines for the transcript. */
|
|
157
158
|
export function renderDailySummary(s: DailySummary): string {
|
|
158
|
-
const scope = s.harnessId === "" ? "all harnesses" : `harness ${s.harnessId}`;
|
|
159
|
+
const scope = s.harnessId === "" ? "all harnesses" : s.harnessId.includes(",") ? `${s.harnessId.split(",").length} harnesses` : `harness ${s.harnessId}`;
|
|
159
160
|
const out: string[] = [`auto-model-router daily summary — last 24h (${scope})`];
|
|
160
161
|
const c = s.current;
|
|
161
162
|
if (c.dispatches === 0) {
|
package/src/lib.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public programmatic API of auto-model-router, for projects that embed the
|
|
3
|
+
* router rather than run the binary (the team edition does). Everything else
|
|
4
|
+
* under src/ is internal and may change between patch releases; this file is
|
|
5
|
+
* the contract.
|
|
6
|
+
*
|
|
7
|
+
* import { startServer, loadConfig } from "auto-model-router";
|
|
8
|
+
*
|
|
9
|
+
* const cfg = loadConfig({ overrides: { server: { host: "127.0.0.1", port: 0, apiKey: "internal" } } });
|
|
10
|
+
* const router = startServer(cfg);
|
|
11
|
+
* router.server.port; // the bound port; talk HTTP to it, or embed further
|
|
12
|
+
* await router.stop();
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { startServer, type StartedServer } from "./server/http.ts";
|
|
16
|
+
export { loadConfig, apiKeySource } from "./config/load.ts";
|
|
17
|
+
export { DEFAULT_CONFIG } from "./config/defaults.ts";
|
|
18
|
+
export type { RouterConfig } from "./config/types.ts";
|
|
19
|
+
export { buildUsageReport, renderUsageReport, type UsageReport, type ReportTotals } from "./cost/report.ts";
|
|
20
|
+
export { buildDailySummary, renderDailySummary, type DailySummary } from "./cost/summary.ts";
|
|
21
|
+
export { openDb } from "./util/sqlite.ts";
|
|
22
|
+
export { createLedger } from "./cost/ledger.ts";
|
|
23
|
+
export type { Ledger, LedgerEntry } from "./cost/types.ts";
|
package/test/report.test.ts
CHANGED
|
@@ -156,6 +156,20 @@ describe("buildUsageReport", () => {
|
|
|
156
156
|
db.close();
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
test("a comma-separated harness list reports the union (a team group)", () => {
|
|
160
|
+
const { db, ledger } = seeded();
|
|
161
|
+
try {
|
|
162
|
+
ledger.record(entry({ harnessId: "u_a", reportedUsd: 1 }));
|
|
163
|
+
ledger.record(entry({ harnessId: "u_b", reportedUsd: 2 }));
|
|
164
|
+
ledger.record(entry({ harnessId: "u_c", reportedUsd: 4 }));
|
|
165
|
+
expect(buildUsageReport(db, { windowDays: 1, nowMs: NOW, harnessId: "u_a,u_b" }).totals.spendUsd).toBeCloseTo(3, 6);
|
|
166
|
+
expect(buildUsageReport(db, { windowDays: 1, nowMs: NOW, harnessId: " u_c , u_a " }).totals.spendUsd).toBeCloseTo(5, 6);
|
|
167
|
+
expect(buildUsageReport(db, { windowDays: 1, nowMs: NOW, harnessId: "u_b" }).totals.spendUsd).toBeCloseTo(2, 6);
|
|
168
|
+
} finally {
|
|
169
|
+
db.close();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
159
173
|
test("prompt anatomy averages the recorded byte shares", () => {
|
|
160
174
|
const { db, ledger } = seeded();
|
|
161
175
|
const feat = (tool: number, older: number, stale: number) => ({ toolSchemaBytes: 1000, anatomy: { messages: 30, systemBytes: 1000, userBytes: 500, assistantBytes: 500, toolBytes: tool, olderHalfBytes: older, staleToolBytes: stale } });
|