auto-model-router 0.2.2 → 0.2.8
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/.claude/skills/agentdox/SKILL.md +143 -0
- package/.mcp.json +11 -0
- package/.omp-plugin/marketplace.json +2 -2
- package/CLAUDE.md +129 -0
- package/README.md +64 -0
- package/docs/AGENTDOX-BRIDGE.md +132 -0
- package/docs/context-optimization.md +362 -0
- package/omp-extension/embed-logic.ts +31 -0
- package/omp-extension/router-embed.ts +7 -1
- package/package.json +1 -1
- package/src/cli/config-cmd.ts +20 -5
- package/src/cli/explain.ts +1 -0
- package/src/config/defaults.ts +33 -1
- package/src/config/load.ts +13 -0
- package/src/config/schema.ts +27 -0
- package/src/config/types.ts +79 -5
- package/src/context/agentdox.ts +113 -0
- package/src/context/bridge.ts +166 -0
- package/src/context/index.ts +33 -0
- package/src/context/store.ts +82 -0
- package/src/context/types.ts +78 -0
- package/src/cost/ledger.ts +53 -14
- package/src/cost/types.ts +15 -4
- package/src/router/candidates.ts +19 -8
- package/src/router/classify.ts +26 -12
- package/src/router/compaction.ts +163 -0
- package/src/router/features.ts +26 -13
- package/src/router/select.ts +37 -4
- package/src/router/state.ts +12 -2
- package/src/router/types.ts +33 -1
- package/src/server/http.ts +18 -1
- package/src/server/turn.ts +88 -1
- package/src/upstream/openrouter.ts +8 -1
- package/src/util/sqlite.ts +34 -1
- package/src/wire/openai/request.ts +86 -1
- package/src/wire/types.ts +36 -0
- package/test/classify.test.ts +63 -5
- package/test/compaction.test.ts +148 -0
- package/test/context-bridge.test.ts +337 -0
- package/test/embed-logic.test.ts +32 -0
- package/test/escalate.test.ts +1 -0
- package/test/exploration.test.ts +6 -2
- package/test/failover.test.ts +51 -6
- package/test/features.test.ts +45 -0
- package/test/helpers/inject.ts +23 -0
- package/test/hold-exploration.test.ts +4 -2
- package/test/select.test.ts +86 -3
- package/test/tokens.test.ts +1 -0
- package/test/trust-attribution.test.ts +49 -9
- package/test/turn.test.ts +20 -10
- package/tools/agentdox-e2e.ts +123 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live end-to-end check of the agentdox bridge against a running server.
|
|
3
|
+
*
|
|
4
|
+
* Exercises the real HTTP client, the real pin/refresh policy, and the real
|
|
5
|
+
* write-back — no fakes. Run with a token that has write on the scope:
|
|
6
|
+
*
|
|
7
|
+
* AGENTDOX_URL=http://localhost:3003 \
|
|
8
|
+
* AGENTDOX_TOKEN=<pat> AGENTDOX_SCOPE=omp-router \
|
|
9
|
+
* bun tools/agentdox-e2e.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createAgentDoxClient } from "../src/context/agentdox.ts";
|
|
13
|
+
import { createContextBridge } from "../src/context/bridge.ts";
|
|
14
|
+
import { createContextStore } from "../src/context/store.ts";
|
|
15
|
+
import type { ContextResolveInput } from "../src/context/types.ts";
|
|
16
|
+
import { createLogger } from "../src/util/log.ts";
|
|
17
|
+
import { openDb } from "../src/util/sqlite.ts";
|
|
18
|
+
|
|
19
|
+
const baseUrl = process.env.AGENTDOX_URL ?? "http://localhost:3003";
|
|
20
|
+
const token = process.env.AGENTDOX_TOKEN ?? "";
|
|
21
|
+
const scope = process.env.AGENTDOX_SCOPE ?? "omp-router";
|
|
22
|
+
|
|
23
|
+
if (token === "") {
|
|
24
|
+
console.error("AGENTDOX_TOKEN is required");
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let failures = 0;
|
|
29
|
+
function check(name: string, ok: boolean, detail = ""): void {
|
|
30
|
+
console.log(`${ok ? "PASS" : "FAIL"} ${name}${detail === "" ? "" : ` (${detail})`}`);
|
|
31
|
+
if (!ok) failures++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const log = createLogger("warn");
|
|
35
|
+
const db = openDb(":memory:");
|
|
36
|
+
const client = createAgentDoxClient({ baseUrl, token, timeoutMs: 5_000, log });
|
|
37
|
+
const bridge = createContextBridge({
|
|
38
|
+
client,
|
|
39
|
+
store: createContextStore(db),
|
|
40
|
+
log,
|
|
41
|
+
maxStalenessMs: 900_000,
|
|
42
|
+
maxBlockChars: 24_000,
|
|
43
|
+
recordTurns: true,
|
|
44
|
+
maxQueue: 64,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const conversationKey = `e2e-${Date.now().toString(36)}`;
|
|
48
|
+
function input(over: Partial<ContextResolveInput> = {}): ContextResolveInput {
|
|
49
|
+
return {
|
|
50
|
+
scope,
|
|
51
|
+
conversationKey,
|
|
52
|
+
pinnedVersion: null,
|
|
53
|
+
pinnedFetchedAtMs: 0,
|
|
54
|
+
modelSwitching: false,
|
|
55
|
+
retrying: false,
|
|
56
|
+
query: "cache prompt context injection",
|
|
57
|
+
...over,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 1. Assemble a real context slice.
|
|
62
|
+
const first = await bridge.resolve(input());
|
|
63
|
+
check("resolve returns a block from the live server", first !== null);
|
|
64
|
+
check(
|
|
65
|
+
"block carries the agentdox delimiter",
|
|
66
|
+
first !== null && first.block.includes("<project-context source=\"agentdox\">"),
|
|
67
|
+
);
|
|
68
|
+
console.log(` block chars: ${first?.block.length ?? 0}, version ${first?.version.slice(0, 12) ?? "-"}`);
|
|
69
|
+
|
|
70
|
+
// 2. Steady state must NOT re-fetch, and must return byte-identical bytes.
|
|
71
|
+
const pinnedInput = input({
|
|
72
|
+
pinnedVersion: first?.version ?? null,
|
|
73
|
+
pinnedFetchedAtMs: first?.fetchedAtMs ?? 0,
|
|
74
|
+
});
|
|
75
|
+
const second = await bridge.resolve(pinnedInput);
|
|
76
|
+
check("pinned turn re-injects identical bytes", second?.block === first?.block);
|
|
77
|
+
check("pinned turn keeps the same version", second?.version === first?.version);
|
|
78
|
+
|
|
79
|
+
// 3. A model switch refreshes — and unchanged content keeps the SAME version,
|
|
80
|
+
// which is what protects the prompt cache.
|
|
81
|
+
const switched = await bridge.resolve({ ...pinnedInput, modelSwitching: true });
|
|
82
|
+
check(
|
|
83
|
+
"model switch refreshes but unchanged content keeps the version",
|
|
84
|
+
switched !== null && switched.version === first?.version,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// 4. Write back a turn, attributed to the model that served it.
|
|
88
|
+
bridge.recordTurn({
|
|
89
|
+
scope,
|
|
90
|
+
conversationKey,
|
|
91
|
+
title: `bridge e2e ${conversationKey}`,
|
|
92
|
+
userText: "does the bridge record which model served this turn?",
|
|
93
|
+
assistantText: "yes - refs carry model: and tier:.",
|
|
94
|
+
slug: "anthropic/claude-haiku-4.5",
|
|
95
|
+
tier: "simple",
|
|
96
|
+
});
|
|
97
|
+
await bridge.flush();
|
|
98
|
+
|
|
99
|
+
// 5. Read it back through the REST API to prove it landed.
|
|
100
|
+
const res = await fetch(`${baseUrl}/sessions?scope=${encodeURIComponent(scope)}`, {
|
|
101
|
+
headers: { authorization: `Bearer ${token}` },
|
|
102
|
+
});
|
|
103
|
+
const sessions = (await res.json()) as { id: string; title: string }[];
|
|
104
|
+
const mine = sessions.find((s) => s.title === `bridge e2e ${conversationKey}`);
|
|
105
|
+
check("write-back created a session in agentdox", mine !== undefined, mine?.id ?? "not found");
|
|
106
|
+
|
|
107
|
+
if (mine !== undefined) {
|
|
108
|
+
const full = await fetch(`${baseUrl}/sessions/${mine.id}`, {
|
|
109
|
+
headers: { authorization: `Bearer ${token}` },
|
|
110
|
+
});
|
|
111
|
+
const session = (await full.json()) as { messages: { role: string; content: string; refs?: string[] }[] };
|
|
112
|
+
const assistant = session.messages.find((m) => m.role === "assistant");
|
|
113
|
+
check("transcript has both turns", session.messages.length === 2, `${session.messages.length} messages`);
|
|
114
|
+
check(
|
|
115
|
+
"assistant turn is model-attributed",
|
|
116
|
+
assistant?.refs?.includes("model:anthropic/claude-haiku-4.5") === true,
|
|
117
|
+
JSON.stringify(assistant?.refs ?? []),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
db.close();
|
|
122
|
+
console.log(failures === 0 ? "\nAll bridge e2e checks passed." : `\n${failures} check(s) failed.`);
|
|
123
|
+
process.exit(failures === 0 ? 0 : 1);
|