auto-model-router 0.35.0 → 0.37.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 +104 -10
- package/omp-extension/remote-logic.ts +15 -0
- package/package.json +1 -1
- package/src/catalog/benchmark-feeds.ts +179 -41
- package/src/catalog/openrouter-catalog.ts +10 -2
- package/src/cli/connect.ts +89 -17
- package/src/cli/context-token.ts +133 -0
- package/src/cli/credential-store.ts +65 -20
- package/src/cli/refresh.ts +26 -1
- package/src/config/defaults.ts +1 -0
- package/src/config/schema.ts +22 -0
- package/src/config/types.ts +31 -0
- package/src/cost/ledger-sql.ts +3 -2
- package/src/cost/ledger.ts +4 -0
- package/src/cost/types.ts +12 -0
- package/src/cost/views.ts +12 -0
- package/src/lib.ts +12 -0
- package/src/router/candidates.ts +17 -0
- package/src/router/index.ts +4 -0
- package/src/router/types.ts +2 -0
- package/src/server/catalog-view.ts +8 -2
- package/src/server/compaction-digest.ts +3 -0
- package/src/server/digest.ts +11 -0
- package/src/server/http.ts +46 -4
- package/src/server/turn.ts +5 -0
- package/src/util/requestid.ts +77 -0
- package/src/util/schema.ts +42 -3
- package/src/util/sqlite.ts +20 -1
- package/src/wire/openai/request.ts +8 -0
- package/src/wire/types.ts +18 -0
- package/test/benchmark-feeds.test.ts +194 -0
- package/test/catalog-view.test.ts +11 -0
- package/test/config.test.ts +26 -0
- package/test/context-token.test.ts +301 -0
- package/test/failover.test.ts +1 -1
- package/test/ledger-sql.test.ts +1 -1
- package/test/mcp-entry.test.ts +14 -5
- package/test/migrations.test.ts +11 -4
- package/test/reconfigure.test.ts +55 -0
- package/test/request-id.test.ts +424 -0
- package/test/schema.test.ts +2 -2
- package/test/tier-plan.test.ts +51 -0
- package/test/trust-attribution.test.ts +2 -2
- package/test/turn.test.ts +56 -1
|
@@ -271,7 +271,7 @@ describe("v4 migration", () => {
|
|
|
271
271
|
await db.close();
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
test("schema is at user_version
|
|
274
|
+
test("schema is at user_version 20", () => {
|
|
275
275
|
// `PRAGMA user_version` is SQLite's own migration marker, so this reads
|
|
276
276
|
// through the bootstrap handle rather than the engine-agnostic one.
|
|
277
277
|
const path = join(tmpdir(), `uv-${process.pid}-${Date.now()}.db`);
|
|
@@ -279,7 +279,7 @@ describe("v4 migration", () => {
|
|
|
279
279
|
try {
|
|
280
280
|
// Our own pragma against our own file; the shape is fixed by SQLite.
|
|
281
281
|
const row = db.query("PRAGMA user_version").get() as { user_version: number } | null;
|
|
282
|
-
expect(row?.user_version).toBe(
|
|
282
|
+
expect(row?.user_version).toBe(20);
|
|
283
283
|
} finally {
|
|
284
284
|
db.close();
|
|
285
285
|
}
|
package/test/turn.test.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { runTurn } from "../src/server/turn.ts";
|
|
|
18
18
|
import { BudgetExceededError } from "../src/router/select.ts";
|
|
19
19
|
import { parseMessagesRequest } from "../src/wire/anthropic/messages.ts";
|
|
20
20
|
import { parseChatRequest } from "../src/wire/openai/request.ts";
|
|
21
|
+
import { isMintedRequestId } from "../src/util/requestid.ts";
|
|
21
22
|
import { UpstreamError, type DispatchOptions, type UpstreamClient } from "../src/upstream/types.ts";
|
|
22
23
|
import type {
|
|
23
24
|
FinishReason,
|
|
@@ -51,7 +52,7 @@ function mkConfig(escalation: Partial<EscalationConfig> = {}): RouterConfig {
|
|
|
51
52
|
data: { axis: "intelligence", minQuality: 0 },
|
|
52
53
|
chat: { axis: "intelligence", minQuality: 0 },
|
|
53
54
|
},
|
|
54
|
-
filters: { allow: [], deny: [], includeFree: false, requireToolSupport: true, agenticAxisForToolTurns: true, minAgenticForToolTurns: 0, minTrust: 0.6, feedbackWeight: 0, feedbackByTask: false, minTrustSamples: 5, trustScopedByHarness: false, trustWindowDays: 0, contextHeadroom: 1.2, reasoningCompletionFloor: 0, latencyWeight: 0, latencyReferenceMs: 5000, latencyReferenceTokensPerSec: 30, cacheReliabilityMinSamples: 10, latencyMinSamples: 20, escalationCostWeight: 0 },
|
|
55
|
+
filters: { allow: [], deny: [], providerLocks: {}, includeFree: false, requireToolSupport: true, agenticAxisForToolTurns: true, minAgenticForToolTurns: 0, minTrust: 0.6, feedbackWeight: 0, feedbackByTask: false, minTrustSamples: 5, trustScopedByHarness: false, trustWindowDays: 0, contextHeadroom: 1.2, reasoningCompletionFloor: 0, latencyWeight: 0, latencyReferenceMs: 5000, latencyReferenceTokensPerSec: 30, cacheReliabilityMinSamples: 10, latencyMinSamples: 20, escalationCostWeight: 0 },
|
|
55
56
|
classifier: {
|
|
56
57
|
ambiguityThreshold: 0,
|
|
57
58
|
model: "test/adjudicator", learnedModelPath: "",
|
|
@@ -603,6 +604,60 @@ describe("the context scope reaches the ledger", () => {
|
|
|
603
604
|
});
|
|
604
605
|
});
|
|
605
606
|
|
|
607
|
+
describe("the request id reaches the ledger", () => {
|
|
608
|
+
const runOne = async (req: NormRequest) => {
|
|
609
|
+
const { router } = mkRouter([mkDecision("trivial", "cheap/model", { escalateTo: "simple" })]);
|
|
610
|
+
const { upstream } = mkUpstream([{ kind: "chunks", chunks: [startChunk("cheap/model"), textChunk("hi"), finishChunk("stop"), usageChunk({ promptTokens: 10, completionTokens: 2 }, 0.0001)] }]);
|
|
611
|
+
const { ledger, entries } = mkLedger();
|
|
612
|
+
const { store } = mkConversations();
|
|
613
|
+
const { sink } = mkSink();
|
|
614
|
+
await runTurn(req, sink, { config: mkConfig(), router, upstream, ledger, conversations: store, catalog, context: createDisabledBridge() }, new AbortController().signal);
|
|
615
|
+
return entries;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
test("the front door's id is recorded verbatim, so a quoted id names this exact turn", async () => {
|
|
619
|
+
const req = parseChatRequest({ model: "auto", messages: [{ role: "user", content: "hi" }] }, new Headers({ "x-request-id": "abc123" }));
|
|
620
|
+
expect((await runOne(req))[0]!.requestId).toBe("abc123");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("a caller that sends none still gets an addressable row, marked as minted", async () => {
|
|
624
|
+
const req = parseChatRequest({ model: "auto", messages: [{ role: "user", content: "hi" }] }, new Headers());
|
|
625
|
+
const id = (await runOne(req))[0]!.requestId ?? "";
|
|
626
|
+
expect(isMintedRequestId(id)).toBe(true);
|
|
627
|
+
// And it is the id the caller was handed back, not a second one invented
|
|
628
|
+
// for the row: the header the wire read is what the ledger stores.
|
|
629
|
+
expect(id).toBe(req.requestId ?? "");
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("every attempt of an escalated turn files under the ONE id the caller holds", async () => {
|
|
633
|
+
// The customer made one request; support must land on the whole turn,
|
|
634
|
+
// including the attempt that was thrown away.
|
|
635
|
+
const { router } = mkRouter([mkDecision("trivial", "cheap/model", { escalateTo: "simple" }), mkDecision("simple", "better/model", { escalateTo: "moderate" })]);
|
|
636
|
+
const { upstream } = mkUpstream([
|
|
637
|
+
{ kind: "chunks", chunks: [startChunk("cheap/model"), textChunk("I'm sorry, but I can't help with that request."), finishChunk("stop")] },
|
|
638
|
+
{ kind: "chunks", chunks: [startChunk("better/model"), textChunk("Here is the answer."), finishChunk("stop"), usageChunk({ promptTokens: 130, completionTokens: 6 }, 0.0009)] },
|
|
639
|
+
]);
|
|
640
|
+
const { ledger, entries } = mkLedger();
|
|
641
|
+
const { store } = mkConversations();
|
|
642
|
+
const { sink } = mkSink();
|
|
643
|
+
const req = parseChatRequest({ model: "auto", messages: [{ role: "user", content: "hi" }] }, new Headers({ "x-request-id": "one-request" }));
|
|
644
|
+
|
|
645
|
+
await runTurn(req, sink, { config: mkConfig(), router, upstream, ledger, conversations: store, catalog, context: createDisabledBridge() }, new AbortController().signal);
|
|
646
|
+
|
|
647
|
+
expect(entries).toHaveLength(2);
|
|
648
|
+
expect(entries.map((e) => e.requestId)).toEqual(["one-request", "one-request"]);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("a hostile header never reaches the row; the turn is recorded under a minted id instead", async () => {
|
|
652
|
+
for (const hostile of ["x".repeat(129), "id with spaces", "amr-forged0000", '"><script>', "tab\tseparated"]) {
|
|
653
|
+
const req = parseChatRequest({ model: "auto", messages: [{ role: "user", content: "hi" }] }, new Headers({ "x-request-id": hostile }));
|
|
654
|
+
const id = (await runOne(req))[0]!.requestId ?? "";
|
|
655
|
+
expect(id).not.toBe(hostile);
|
|
656
|
+
expect(isMintedRequestId(id)).toBe(true);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
606
661
|
describe("exploration reaches the ledger", () => {
|
|
607
662
|
test("an explored turn records the tier it was dropped from", async () => {
|
|
608
663
|
const explored = { ...mkDecision("simple", "cheap/model"), explored: { from: "moderate" as Tier, to: "simple" as Tier } };
|