auto-model-router 0.13.0 → 0.13.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.13.
|
|
10
|
+
"version": "0.13.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.13.
|
|
17
|
+
"version": "0.13.1",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "drewappling",
|
|
20
20
|
"email": "drewappling@gmail.com"
|
package/package.json
CHANGED
|
@@ -47,7 +47,8 @@ export function buildUpstreamModels(entry: UpstreamEntry, openrouter: readonly C
|
|
|
47
47
|
priceTiers: [],
|
|
48
48
|
quality: m.quality !== undefined ? { ...m.quality } : twin === null ? {} : { ...twin.quality },
|
|
49
49
|
tokenizer: tokenizerFor(entry.kind, twin),
|
|
50
|
-
|
|
50
|
+
// A $0 model here is a self-hosted server, not a public provider's rate-limited free tier: never excluded as "free".
|
|
51
|
+
isFree: false,
|
|
51
52
|
createdAtMs: twin?.createdAtMs ?? 0,
|
|
52
53
|
author: entry.id,
|
|
53
54
|
};
|
package/src/config/hot-reload.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { existsSync, readFileSync, watch, type FSWatcher } from "node:fs";
|
|
|
30
30
|
import { parse as parseYaml } from "yaml";
|
|
31
31
|
import { configInputSchema } from "./schema.ts";
|
|
32
32
|
import { assignInPlace } from "./apply.ts";
|
|
33
|
+
import { completeUpstreams } from "./upstreams.ts";
|
|
33
34
|
import { DEFAULT_CONFIG } from "./defaults.ts";
|
|
34
35
|
import { deepMerge, resolveTilde } from "./load.ts";
|
|
35
36
|
import type { RouterConfig } from "./types.ts";
|
|
@@ -182,6 +183,8 @@ export function watchConfig(
|
|
|
182
183
|
// One in-place pass over the whole config: block identity survives, and a
|
|
183
184
|
// knob deleted from the file reverts, exactly as a restart would leave it.
|
|
184
185
|
const changed = assignInPlace(live as unknown as Record<string, unknown>, staged, "", { prune: true });
|
|
186
|
+
// A reloaded upstream list is as sparse as the file; clients read complete records.
|
|
187
|
+
if (changed.some((c) => c === "upstreams" || c.startsWith("upstreams."))) completeUpstreams(live);
|
|
185
188
|
if (changed.length > 0) opts.onReload?.({ changed });
|
|
186
189
|
};
|
|
187
190
|
|
package/src/cost/report.ts
CHANGED
|
@@ -138,12 +138,14 @@ const PT = "json_extract(usage, '$.promptTokens')";
|
|
|
138
138
|
const CT = "json_extract(usage, '$.cachedTokens')";
|
|
139
139
|
const COMP = "json_extract(usage, '$.completionTokens')";
|
|
140
140
|
/** Named upstream ids the ledger's provider derivation knows; set by createProviders from the live config. */
|
|
141
|
-
let knownUpstreamIds: readonly string[] = [];
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
let knownUpstreamIds: () => readonly string[] = () => [];
|
|
142
|
+
/** Ids, or a getter read live so a hot-reloaded list applies; an embedder (the team edition) calls this too, since the registry is per process. */
|
|
143
|
+
export function setKnownUpstreamIds(ids: readonly string[] | (() => readonly string[])): void {
|
|
144
|
+
const read = typeof ids === "function" ? ids : () => ids;
|
|
145
|
+
knownUpstreamIds = () => read().filter((id) => /^[a-z0-9][a-z0-9-]{0,31}$/.test(id));
|
|
144
146
|
}
|
|
145
147
|
export function knownUpstreams(): readonly string[] {
|
|
146
|
-
return knownUpstreamIds;
|
|
148
|
+
return knownUpstreamIds();
|
|
147
149
|
}
|
|
148
150
|
/** The provider of a slug: its namespace when that names a known upstream, else OpenRouter's own. */
|
|
149
151
|
export function providerOfSlug(slug: string): string {
|
|
@@ -151,13 +153,13 @@ export function providerOfSlug(slug: string): string {
|
|
|
151
153
|
const cut = slug.indexOf("/");
|
|
152
154
|
if (cut > 0) {
|
|
153
155
|
const head = slug.slice(0, cut);
|
|
154
|
-
if (knownUpstreamIds.includes(head)) return head;
|
|
156
|
+
if (knownUpstreamIds().includes(head)) return head;
|
|
155
157
|
}
|
|
156
158
|
return "openrouter";
|
|
157
159
|
}
|
|
158
160
|
/** SQL twin of providerOfSlug; ids are validated to a slug alphabet so they can be inlined. */
|
|
159
161
|
function providerCase(): string {
|
|
160
|
-
return `CASE WHEN slug LIKE 'ollama/%' THEN 'ollama' ${knownUpstreamIds.map((id) => `WHEN slug LIKE '${id}/%' THEN '${id}'`).join(" ")} ELSE 'openrouter' END`;
|
|
162
|
+
return `CASE WHEN slug LIKE 'ollama/%' THEN 'ollama' ${knownUpstreamIds().map((id) => `WHEN slug LIKE '${id}/%' THEN '${id}'`).join(" ")} ELSE 'openrouter' END`;
|
|
161
163
|
}
|
|
162
164
|
const STREAMED = "ttft_ms IS NOT NULL AND ttft_ms > 0 AND error IS NULL";
|
|
163
165
|
const EST = "json_extract(usage, '$.cachedEstimated') = 1";
|
package/src/lib.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { loadConfig, apiKeySource } from "./config/load.ts";
|
|
|
17
17
|
export { DEFAULT_CONFIG } from "./config/defaults.ts";
|
|
18
18
|
export type { RouterConfig, UpstreamEntry, UpstreamKind, UpstreamModelConfig } from "./config/types.ts";
|
|
19
19
|
export { RESERVED_UPSTREAM_IDS } from "./config/schema.ts";
|
|
20
|
+
export { setKnownUpstreamIds, providerOfSlug } from "./cost/report.ts";
|
|
20
21
|
export type { DeepPartial } from "./config/load.ts";
|
|
21
22
|
export { buildUsageReport, renderUsageReport, type UsageReport, type ReportTotals } from "./cost/report.ts";
|
|
22
23
|
export { buildDailySummary, renderDailySummary, type DailySummary } from "./cost/summary.ts";
|
package/src/server/providers.ts
CHANGED
|
@@ -82,7 +82,7 @@ export function createProviders(cfg: RouterConfig, db: Database, log: Logger = c
|
|
|
82
82
|
};
|
|
83
83
|
const namedServing = (): string[] => cfg.upstreams.filter((u) => u.enabled && namedServingOne(u.id)).map((u) => u.id);
|
|
84
84
|
const staticCatalog = createStaticCatalogSource(cfg, log);
|
|
85
|
-
setKnownUpstreamIds(cfg.upstreams.map((u) => u.id));
|
|
85
|
+
setKnownUpstreamIds(() => cfg.upstreams.map((u) => u.id));
|
|
86
86
|
return {
|
|
87
87
|
upstream: createMultiUpstream(openrouter, ollama, named, () => cfg.upstreams.map((u) => u.id)),
|
|
88
88
|
catalog: createCompositeCatalog(openrouterCatalog, createOllamaCatalog(cfg.ollama, log, fetch, db), { available: ollamaServing, cooldownUntilMs: () => ollama.cooldownUntilMs(), lastTrip: () => ollama.lastTrip() }, {
|
package/test/upstreams.test.ts
CHANGED
|
@@ -108,7 +108,7 @@ describe("static catalog", () => {
|
|
|
108
108
|
applyConfigPatch(cfg, { upstreams: [{ id: "vllm", kind: "openai", baseUrl: "http://vllm:8000/v1", models: [{ id: "llama", input: 0, output: 0 }] }] } as never);
|
|
109
109
|
const second = src.get(twins);
|
|
110
110
|
expect(second).not.toBe(first);
|
|
111
|
-
expect(second[0]!.isFree).toBe(
|
|
111
|
+
expect(second[0]!.isFree).toBe(false); // $0 self-hosted models must not fall under the free-tier exclusion
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|