auto-model-router 0.3.4 → 0.4.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.
Files changed (48) hide show
  1. package/.omp-plugin/marketplace.json +2 -2
  2. package/README.md +29 -4
  3. package/omp-extension/report-logic.ts +93 -0
  4. package/omp-extension/router-configure.ts +128 -2
  5. package/omp-extension/router-embed.ts +9 -6
  6. package/package.json +1 -1
  7. package/src/catalog/ollama-catalog.ts +30 -2
  8. package/src/cli/config-wizard.ts +11 -0
  9. package/src/cli/report.ts +7 -2
  10. package/src/config/defaults.ts +17 -0
  11. package/src/config/schema.ts +8 -0
  12. package/src/config/types.ts +65 -0
  13. package/src/cost/feedback.ts +81 -0
  14. package/src/cost/ledger.ts +110 -5
  15. package/src/cost/report.ts +118 -2
  16. package/src/cost/types.ts +32 -1
  17. package/src/router/candidates.ts +13 -4
  18. package/src/router/classify.ts +13 -0
  19. package/src/router/features.ts +59 -1
  20. package/src/router/index.ts +23 -5
  21. package/src/router/learned.ts +202 -0
  22. package/src/router/select.ts +64 -8
  23. package/src/router/types.ts +39 -1
  24. package/src/server/http.ts +82 -5
  25. package/src/server/overrides.ts +83 -0
  26. package/src/server/providers.ts +10 -2
  27. package/src/server/turn.ts +16 -1
  28. package/src/upstream/ollama-usage.ts +79 -2
  29. package/src/util/sqlite.ts +30 -0
  30. package/src/wire/openai/request.ts +4 -0
  31. package/src/wire/types.ts +2 -0
  32. package/test/classify.test.ts +13 -0
  33. package/test/config-wizard.test.ts +8 -6
  34. package/test/controls.test.ts +238 -0
  35. package/test/escalate.test.ts +1 -0
  36. package/test/failover.test.ts +6 -4
  37. package/test/features.test.ts +63 -0
  38. package/test/http-resilience.test.ts +1 -1
  39. package/test/learned.test.ts +61 -0
  40. package/test/ollama.test.ts +74 -2
  41. package/test/report-hub.test.ts +6 -2
  42. package/test/report-logic.test.ts +3 -0
  43. package/test/report.test.ts +57 -0
  44. package/test/select.test.ts +126 -1
  45. package/test/trust-attribution.test.ts +95 -0
  46. package/test/turn.test.ts +36 -4
  47. package/tools/replay.ts +267 -156
  48. package/tools/train-classifier.ts +111 -0
package/test/turn.test.ts CHANGED
@@ -29,7 +29,7 @@ import type {
29
29
 
30
30
  function mkConfig(escalation: Partial<EscalationConfig> = {}): RouterConfig {
31
31
  return {
32
- server: { host: "127.0.0.1", port: 8787, maxConcurrentTurns: 24 },
32
+ server: { host: "127.0.0.1", port: 8787, maxConcurrentTurns: 24, subagentProfile: "auto-sub" },
33
33
  openrouter: { baseUrl: "https://openrouter.ai/api/v1", apiKey: "", title: "test", timeoutMs: 30_000, catalogTtlMs: 3_600_000, catalogRefreshMs: 0 },
34
34
  ollama: { enabled: false, baseUrl: "http://127.0.0.1:11434/v1", apiKey: "", timeoutMs: 30_000, catalogTtlMs: 300_000, includeLocal: false, prices: {}, twins: {}, costBias: 1, biasUntilUsage: 0.9, usagePollMs: 0, quotaCooldownMs: 0, rateLimitCooldownMs: 0, planCreditsUsd: 0 },
35
35
  benchmarks: { enabled: false, artificialAnalysisApiKey: "", benchlm: true, refreshMs: 86_400_000, timeoutMs: 30_000, useLocalScores: false },
@@ -46,10 +46,10 @@ function mkConfig(escalation: Partial<EscalationConfig> = {}): RouterConfig {
46
46
  data: { axis: "intelligence", minQuality: 0 },
47
47
  chat: { axis: "intelligence", minQuality: 0 },
48
48
  },
49
- filters: { allow: [], deny: [], includeFree: false, requireToolSupport: true, minTrust: 0.6, minTrustSamples: 5, trustScopedByHarness: false, trustWindowDays: 0, contextHeadroom: 1.2, latencyWeight: 0, latencyReferenceMs: 5000, latencyReferenceTokensPerSec: 30, latencyMinSamples: 20, escalationCostWeight: 0 },
49
+ filters: { allow: [], deny: [], includeFree: false, requireToolSupport: true, minTrust: 0.6, feedbackWeight: 0, minTrustSamples: 5, trustScopedByHarness: false, trustWindowDays: 0, contextHeadroom: 1.2, latencyWeight: 0, latencyReferenceMs: 5000, latencyReferenceTokensPerSec: 30, cacheReliabilityMinSamples: 10, latencyMinSamples: 20, escalationCostWeight: 0 },
50
50
  classifier: {
51
51
  ambiguityThreshold: 0,
52
- model: "test/adjudicator",
52
+ model: "test/adjudicator", learnedModelPath: "",
53
53
  maxCostFraction: 0.1,
54
54
  maxCostUsd: 0.01,
55
55
  timeoutMs: 5000,
@@ -57,7 +57,7 @@ function mkConfig(escalation: Partial<EscalationConfig> = {}): RouterConfig {
57
57
  toolAxis: "coding",
58
58
  chatAxis: "intelligence",
59
59
  agenticLoopDepth: 3,
60
- mechanicalRetryFactor: 0.2,
60
+ mechanicalRetryFactor: 0.2, readOnlyToolWeight: 0,
61
61
  reasoningWeights: { medium: 0.14, high: 0.24, xhigh: 0.3, max: 0.34 },
62
62
  },
63
63
  escalation: {
@@ -76,6 +76,7 @@ function mkConfig(escalation: Partial<EscalationConfig> = {}): RouterConfig {
76
76
  context: { enabled: false, baseUrl: "", token: "", defaultScope: "", timeoutMs: 3_000, maxStalenessMs: 900_000, maxBlockChars: 24_000, memoryLimit: 8, docsLimit: 2, sessionLimit: 6, briefChars: 0, recordTurns: false, maxQueue: 64 },
77
77
  compaction: { enabled: false, budgetTokens: 40_000, floorRatio: 1, fitToWindow: true, protectRecentTurns: 4, maxToolResultBytes: 4_096, keepHeadBytes: 512, keepTailBytes: 512, elideSupersededReads: true, collapseDuplicateResults: true, replanGrowthRatio: 1 },
78
78
  budget: { onExceeded: "downgrade" },
79
+ report: { baselines: [] },
79
80
  profiles: [],
80
81
  ledger: { path: ":memory:", blendWindowDays: 7, blendMinSamples: 20, fallbackBlend: { inputPerMtok: 1, outputPerMtok: 4 }, conversationTtlMs: 86_400_000 },
81
82
  adaptiveTierFloors: true,
@@ -91,6 +92,7 @@ function mkReq(): NormRequest {
91
92
  harnessId: "",
92
93
  ompSessionId: "",
93
94
  agentdoxScope: "",
95
+ isSubagent: false,
94
96
  requestedModel: "auto",
95
97
  messages: [{ role: "user", text: "hi", images: 0, textBytes: 2, toolCalls: [] }],
96
98
  tools: [],
@@ -896,4 +898,34 @@ describe("latency measurement covers the work the router actually does", () => {
896
898
  expect(map.get("conv-test")!.cacheWarmSlug).toBe(ollamaModel.slug);
897
899
  });
898
900
 
901
+ test("an Ollama estimate is scaled by the ledger-vs-meter calibration", async () => {
902
+ const ollamaModel: CatalogModel = {
903
+ slug: "ollama/glm-5.3-flash",
904
+ provider: "ollama",
905
+ canonicalSlug: "ollama/glm-5.3-flash",
906
+ name: "glm",
907
+ contextLength: 1_000_000,
908
+ supportsTools: true,
909
+ supportsReasoning: true,
910
+ reasoningMandatory: false,
911
+ supportsToolChoice: false,
912
+ inputModalities: ["text"],
913
+ price: { prompt: 0.15 / 1e6, cacheRead: 0.03 / 1e6, completion: 0.5 / 1e6 },
914
+ priceTiers: [],
915
+ quality: {},
916
+ tokenizer: "Other",
917
+ isFree: false,
918
+ createdAtMs: 0,
919
+ author: "ollama",
920
+ };
921
+ const priced = { ...catalog, find: (slug: string) => (slug === ollamaModel.slug ? ollamaModel : undefined) };
922
+ const { router } = mkRouter([mkDecision("simple", ollamaModel.slug)]);
923
+ const { upstream } = mkUpstream([{ kind: "chunks", chunks: [startChunk(ollamaModel.slug), textChunk("ok"), finishChunk("stop"), usageChunk({ promptTokens: 100_000, completionTokens: 0 }, null)] }]);
924
+ const { ledger, entries } = mkLedger();
925
+ const { store } = mkConversations();
926
+ await runTurn(mkReq(), mkSink().sink, { config: mkConfig(), router, upstream, ledger, conversations: store, catalog: priced, context: createDisabledBridge(), ollamaCostScale: () => 1.25 }, new AbortController().signal);
927
+ // 100k × $0.15/M = $0.015, scaled ×1.25.
928
+ expect(entries[0]!.reportedUsd).toBeCloseTo(0.01875, 6);
929
+ });
930
+
899
931
  });