nexus-ai-pro 2.0.0 → 2.2.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.
Files changed (282) hide show
  1. package/API_STABILITY.md +65 -0
  2. package/CHANGELOG.md +267 -1
  3. package/MIGRATING.md +1 -1
  4. package/README.md +17 -8
  5. package/dist/agent/create-agent.d.ts +9 -1
  6. package/dist/agent/create-agent.js +40 -1
  7. package/dist/cache/memory-cache.d.ts +8 -0
  8. package/dist/cache/memory-cache.js +9 -0
  9. package/dist/cache/semantic-cache.d.ts +6 -3
  10. package/dist/cache/semantic-cache.js +5 -2
  11. package/dist/cli/args.js +3 -0
  12. package/dist/cli/db.d.ts +16 -4
  13. package/dist/cli/db.js +169 -22
  14. package/dist/cli/doctor.d.ts +17 -0
  15. package/dist/cli/doctor.js +53 -0
  16. package/dist/cli/graph.d.ts +9 -0
  17. package/dist/cli/graph.js +47 -0
  18. package/dist/cli.js +21 -2
  19. package/dist/core/lifecycle.js +11 -6
  20. package/dist/core/nexus.d.ts +12 -2
  21. package/dist/core/nexus.js +106 -23
  22. package/dist/doctor/index.d.ts +90 -0
  23. package/dist/doctor/index.js +237 -0
  24. package/dist/evals/judge.d.ts +11 -0
  25. package/dist/evals/judge.js +33 -0
  26. package/dist/evaluate/index.d.ts +1 -0
  27. package/dist/evaluate/index.js +1 -0
  28. package/dist/evaluate/provenance.d.ts +22 -0
  29. package/dist/evaluate/provenance.js +10 -0
  30. package/dist/evaluate/run.js +20 -4
  31. package/dist/graph/durability.d.ts +27 -0
  32. package/dist/graph/durability.js +66 -0
  33. package/dist/graph/errors.d.ts +30 -4
  34. package/dist/graph/errors.js +18 -2
  35. package/dist/graph/event-stream.d.ts +75 -0
  36. package/dist/graph/event-stream.js +289 -0
  37. package/dist/graph/functional.d.ts +80 -7
  38. package/dist/graph/functional.js +138 -35
  39. package/dist/graph/graph.d.ts +56 -8
  40. package/dist/graph/graph.js +331 -49
  41. package/dist/graph/index.d.ts +4 -2
  42. package/dist/graph/index.js +3 -1
  43. package/dist/graph/lint.d.ts +57 -0
  44. package/dist/graph/lint.js +141 -0
  45. package/dist/graph/run-control.d.ts +28 -0
  46. package/dist/graph/run-control.js +26 -0
  47. package/dist/images/evals.js +4 -2
  48. package/dist/operations/adapters.d.ts +52 -4
  49. package/dist/operations/adapters.js +194 -7
  50. package/dist/operations/runner.js +14 -15
  51. package/dist/operations/stats.js +8 -1
  52. package/dist/operations/store.d.ts +5 -1
  53. package/dist/operations/store.js +7 -0
  54. package/dist/ops/circuit-breaker.d.ts +52 -0
  55. package/dist/ops/circuit-breaker.js +4 -0
  56. package/dist/ops/circuit-store.d.ts +13 -2
  57. package/dist/ops/circuit-store.js +68 -0
  58. package/dist/ops/circuit-sync.d.ts +17 -0
  59. package/dist/ops/circuit-sync.js +86 -0
  60. package/dist/ops/health.d.ts +24 -0
  61. package/dist/ops/health.js +38 -10
  62. package/dist/ops/index.d.ts +1 -1
  63. package/dist/ops/index.js +1 -1
  64. package/dist/ops/rate-limit-adapters.d.ts +23 -0
  65. package/dist/ops/rate-limit-adapters.js +34 -0
  66. package/dist/ops/rate-limiter.d.ts +14 -2
  67. package/dist/ops/rate-limiter.js +31 -0
  68. package/dist/postgres/circuits.d.ts +21 -4
  69. package/dist/postgres/circuits.js +65 -4
  70. package/dist/postgres/client.d.ts +0 -2
  71. package/dist/postgres/client.js +0 -4
  72. package/dist/postgres/evaluate.d.ts +16 -4
  73. package/dist/postgres/evaluate.js +17 -5
  74. package/dist/postgres/index.d.ts +32 -11
  75. package/dist/postgres/index.js +65 -31
  76. package/dist/postgres/migrations.d.ts +60 -0
  77. package/dist/postgres/migrations.js +121 -0
  78. package/dist/postgres/operations.d.ts +14 -3
  79. package/dist/postgres/operations.js +45 -15
  80. package/dist/postgres/prompts.d.ts +11 -2
  81. package/dist/postgres/prompts.js +14 -3
  82. package/dist/postgres/rollups.d.ts +35 -0
  83. package/dist/postgres/rollups.js +129 -0
  84. package/dist/postgres/store.d.ts +11 -2
  85. package/dist/postgres/store.js +14 -3
  86. package/dist/postgres/traces.d.ts +13 -3
  87. package/dist/postgres/traces.js +25 -3
  88. package/dist/postgres/vectors.d.ts +11 -2
  89. package/dist/postgres/vectors.js +14 -3
  90. package/dist/router/auto-router.d.ts +34 -7
  91. package/dist/router/auto-router.js +201 -68
  92. package/dist/server/assistant.d.ts +44 -0
  93. package/dist/server/assistant.js +39 -0
  94. package/dist/server/errors.d.ts +12 -0
  95. package/dist/server/errors.js +7 -0
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +1 -1
  98. package/dist/server/runs.d.ts +28 -1
  99. package/dist/server/runs.js +110 -14
  100. package/dist/sqlite/index.d.ts +16 -3
  101. package/dist/sqlite/index.js +16 -3
  102. package/dist/sqlite/migrations.d.ts +26 -0
  103. package/dist/sqlite/migrations.js +63 -0
  104. package/dist/sqlite/operations.d.ts +14 -3
  105. package/dist/sqlite/operations.js +42 -15
  106. package/dist/sqlite/store.d.ts +9 -2
  107. package/dist/sqlite/store.js +13 -3
  108. package/dist/sqlite/vectors.d.ts +11 -2
  109. package/dist/sqlite/vectors.js +13 -2
  110. package/dist/store/tenant.d.ts +12 -0
  111. package/dist/store/tenant.js +29 -0
  112. package/dist/tenancy/index.d.ts +113 -0
  113. package/dist/tenancy/index.js +233 -0
  114. package/dist/testing/record.d.ts +42 -0
  115. package/dist/testing/record.js +57 -5
  116. package/dist/tracing/index.d.ts +3 -3
  117. package/dist/tracing/index.js +2 -2
  118. package/dist/tracing/instrument.js +30 -0
  119. package/dist/tracing/otlp.d.ts +91 -0
  120. package/dist/tracing/otlp.js +124 -0
  121. package/dist/tracing/rollups.d.ts +100 -0
  122. package/dist/tracing/rollups.js +139 -0
  123. package/dist/tracing/stores.d.ts +17 -0
  124. package/dist/tracing/stores.js +23 -0
  125. package/dist/tracing/tracer.d.ts +50 -4
  126. package/dist/tracing/tracer.js +85 -6
  127. package/dist/types/config.d.ts +33 -2
  128. package/dist/types/evaluate.d.ts +37 -0
  129. package/dist/types/graph.d.ts +354 -9
  130. package/dist/types/graph.js +3 -1
  131. package/dist/types/messages.d.ts +5 -0
  132. package/dist/types/operations.d.ts +22 -2
  133. package/dist/types/response.d.ts +20 -0
  134. package/dist/types/server.d.ts +18 -0
  135. package/dist/types/tracing.d.ts +11 -0
  136. package/dist/utils/schema-migrations.d.ts +97 -0
  137. package/dist/utils/schema-migrations.js +91 -0
  138. package/dist/utils/signals.d.ts +17 -0
  139. package/dist/utils/signals.js +26 -0
  140. package/dist/utils/tenant.d.ts +14 -0
  141. package/dist/utils/tenant.js +16 -0
  142. package/dist/version.d.ts +5 -0
  143. package/dist/version.js +1 -0
  144. package/dist-cjs/agent/create-agent.d.ts +9 -1
  145. package/dist-cjs/agent/create-agent.js +40 -1
  146. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  147. package/dist-cjs/cache/memory-cache.js +9 -0
  148. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  149. package/dist-cjs/cache/semantic-cache.js +5 -2
  150. package/dist-cjs/cli/args.js +3 -0
  151. package/dist-cjs/cli/db.d.ts +16 -4
  152. package/dist-cjs/cli/db.js +200 -20
  153. package/dist-cjs/cli/doctor.d.ts +17 -0
  154. package/dist-cjs/cli/doctor.js +56 -0
  155. package/dist-cjs/cli/graph.d.ts +9 -0
  156. package/dist-cjs/cli/graph.js +50 -0
  157. package/dist-cjs/core/lifecycle.js +11 -6
  158. package/dist-cjs/core/nexus.d.ts +12 -2
  159. package/dist-cjs/core/nexus.js +106 -23
  160. package/dist-cjs/doctor/index.d.ts +90 -0
  161. package/dist-cjs/doctor/index.js +276 -0
  162. package/dist-cjs/evals/judge.d.ts +11 -0
  163. package/dist-cjs/evals/judge.js +33 -0
  164. package/dist-cjs/evaluate/index.d.ts +1 -0
  165. package/dist-cjs/evaluate/index.js +3 -1
  166. package/dist-cjs/evaluate/provenance.d.ts +22 -0
  167. package/dist-cjs/evaluate/provenance.js +14 -0
  168. package/dist-cjs/evaluate/run.js +20 -4
  169. package/dist-cjs/graph/durability.d.ts +27 -0
  170. package/dist-cjs/graph/durability.js +70 -0
  171. package/dist-cjs/graph/errors.d.ts +30 -4
  172. package/dist-cjs/graph/errors.js +20 -3
  173. package/dist-cjs/graph/event-stream.d.ts +75 -0
  174. package/dist-cjs/graph/event-stream.js +295 -0
  175. package/dist-cjs/graph/functional.d.ts +80 -7
  176. package/dist-cjs/graph/functional.js +137 -34
  177. package/dist-cjs/graph/graph.d.ts +56 -8
  178. package/dist-cjs/graph/graph.js +330 -48
  179. package/dist-cjs/graph/index.d.ts +4 -2
  180. package/dist-cjs/graph/index.js +7 -1
  181. package/dist-cjs/graph/lint.d.ts +57 -0
  182. package/dist-cjs/graph/lint.js +144 -0
  183. package/dist-cjs/graph/run-control.d.ts +28 -0
  184. package/dist-cjs/graph/run-control.js +30 -0
  185. package/dist-cjs/images/evals.js +4 -2
  186. package/dist-cjs/operations/adapters.d.ts +52 -4
  187. package/dist-cjs/operations/adapters.js +193 -6
  188. package/dist-cjs/operations/runner.js +14 -15
  189. package/dist-cjs/operations/stats.js +8 -1
  190. package/dist-cjs/operations/store.d.ts +5 -1
  191. package/dist-cjs/operations/store.js +7 -0
  192. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  193. package/dist-cjs/ops/circuit-breaker.js +4 -0
  194. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  195. package/dist-cjs/ops/circuit-store.js +68 -0
  196. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  197. package/dist-cjs/ops/circuit-sync.js +87 -0
  198. package/dist-cjs/ops/health.d.ts +24 -0
  199. package/dist-cjs/ops/health.js +38 -10
  200. package/dist-cjs/ops/index.d.ts +1 -1
  201. package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
  202. package/dist-cjs/ops/rate-limit-adapters.js +35 -1
  203. package/dist-cjs/ops/rate-limiter.d.ts +14 -2
  204. package/dist-cjs/ops/rate-limiter.js +32 -0
  205. package/dist-cjs/postgres/circuits.d.ts +21 -4
  206. package/dist-cjs/postgres/circuits.js +98 -3
  207. package/dist-cjs/postgres/client.d.ts +0 -2
  208. package/dist-cjs/postgres/client.js +0 -5
  209. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  210. package/dist-cjs/postgres/evaluate.js +50 -4
  211. package/dist-cjs/postgres/index.d.ts +32 -11
  212. package/dist-cjs/postgres/index.js +69 -20
  213. package/dist-cjs/postgres/migrations.d.ts +60 -0
  214. package/dist-cjs/postgres/migrations.js +129 -0
  215. package/dist-cjs/postgres/operations.d.ts +14 -3
  216. package/dist-cjs/postgres/operations.js +78 -14
  217. package/dist-cjs/postgres/prompts.d.ts +11 -2
  218. package/dist-cjs/postgres/prompts.js +47 -2
  219. package/dist-cjs/postgres/rollups.d.ts +35 -0
  220. package/dist-cjs/postgres/rollups.js +168 -0
  221. package/dist-cjs/postgres/store.d.ts +11 -2
  222. package/dist-cjs/postgres/store.js +47 -2
  223. package/dist-cjs/postgres/traces.d.ts +13 -3
  224. package/dist-cjs/postgres/traces.js +58 -2
  225. package/dist-cjs/postgres/vectors.d.ts +11 -2
  226. package/dist-cjs/postgres/vectors.js +47 -2
  227. package/dist-cjs/router/auto-router.d.ts +34 -7
  228. package/dist-cjs/router/auto-router.js +201 -68
  229. package/dist-cjs/server/assistant.d.ts +44 -0
  230. package/dist-cjs/server/assistant.js +39 -0
  231. package/dist-cjs/server/errors.d.ts +12 -0
  232. package/dist-cjs/server/errors.js +9 -1
  233. package/dist-cjs/server/index.d.ts +1 -1
  234. package/dist-cjs/server/index.js +2 -1
  235. package/dist-cjs/server/runs.d.ts +28 -1
  236. package/dist-cjs/server/runs.js +109 -13
  237. package/dist-cjs/sqlite/index.d.ts +16 -3
  238. package/dist-cjs/sqlite/index.js +31 -10
  239. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  240. package/dist-cjs/sqlite/migrations.js +71 -0
  241. package/dist-cjs/sqlite/operations.d.ts +14 -3
  242. package/dist-cjs/sqlite/operations.js +76 -15
  243. package/dist-cjs/sqlite/store.d.ts +9 -2
  244. package/dist-cjs/sqlite/store.js +47 -3
  245. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  246. package/dist-cjs/sqlite/vectors.js +47 -2
  247. package/dist-cjs/store/tenant.d.ts +12 -0
  248. package/dist-cjs/store/tenant.js +33 -0
  249. package/dist-cjs/tenancy/index.d.ts +113 -0
  250. package/dist-cjs/tenancy/index.js +250 -0
  251. package/dist-cjs/testing/record.d.ts +42 -0
  252. package/dist-cjs/testing/record.js +57 -5
  253. package/dist-cjs/tracing/index.d.ts +3 -3
  254. package/dist-cjs/tracing/index.js +4 -1
  255. package/dist-cjs/tracing/instrument.js +30 -0
  256. package/dist-cjs/tracing/otlp.d.ts +91 -0
  257. package/dist-cjs/tracing/otlp.js +129 -0
  258. package/dist-cjs/tracing/rollups.d.ts +100 -0
  259. package/dist-cjs/tracing/rollups.js +147 -0
  260. package/dist-cjs/tracing/stores.d.ts +17 -0
  261. package/dist-cjs/tracing/stores.js +24 -0
  262. package/dist-cjs/tracing/tracer.d.ts +50 -4
  263. package/dist-cjs/tracing/tracer.js +87 -6
  264. package/dist-cjs/types/config.d.ts +33 -2
  265. package/dist-cjs/types/evaluate.d.ts +37 -0
  266. package/dist-cjs/types/graph.d.ts +354 -9
  267. package/dist-cjs/types/graph.js +3 -1
  268. package/dist-cjs/types/messages.d.ts +5 -0
  269. package/dist-cjs/types/operations.d.ts +22 -2
  270. package/dist-cjs/types/response.d.ts +20 -0
  271. package/dist-cjs/types/server.d.ts +18 -0
  272. package/dist-cjs/types/tracing.d.ts +11 -0
  273. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  274. package/dist-cjs/utils/schema-migrations.js +102 -0
  275. package/dist-cjs/utils/signals.d.ts +17 -0
  276. package/dist-cjs/utils/signals.js +29 -0
  277. package/dist-cjs/utils/tenant.d.ts +14 -0
  278. package/dist-cjs/utils/tenant.js +22 -0
  279. package/dist-cjs/version.d.ts +5 -0
  280. package/dist-cjs/version.js +4 -0
  281. package/llms.txt +4 -3
  282. package/package.json +110 -2
@@ -3,38 +3,140 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutoRouter = void 0;
4
4
  exports.routeDirectModel = routeDirectModel;
5
5
  const registry_js_1 = require("../models/registry.js");
6
+ const PLAN_LIMIT = 16;
7
+ const BOOLEAN_CAPABILITIES = ['streaming', 'toolCalling', 'structuredOutputs', 'jsonMode', 'reasoning'];
6
8
  class AutoRouter {
9
+ plans = new Map();
10
+ patterns = new Map();
11
+ registryIds = new WeakMap();
7
12
  route(ctx) {
8
- const strategy = ctx.config.routing?.strategy || 'quality';
9
- const candidates = this.withoutOpenCircuits(this.getCandidates(ctx), ctx).map((candidate) => ({
10
- ...candidate,
11
- score: this.score(candidate.model, strategy, ctx.config, ctx.providers.get(candidate.providerName)?.info?.isLocal === true) +
12
- candidate.weight +
13
- this.healthScore(candidate.providerName, ctx),
14
- reason: `auto route by ${strategy} score`,
15
- }));
16
- candidates.sort((a, b) => b.score - a.score);
17
- const winner = candidates[0];
13
+ const plan = this.plan(ctx);
14
+ const ranked = this.rank(plan, ctx);
15
+ const winner = ranked[0];
18
16
  if (!winner) {
19
17
  throw new Error('No configured providers are available for routing');
20
18
  }
21
19
  return {
22
20
  providerName: winner.providerName,
23
21
  model: winner.model,
24
- reason: `${winner.reason}: ${winner.model}`,
25
- fallbacks: candidates.slice(1).map((c) => ({ providerName: c.providerName, model: c.model })),
22
+ reason: `auto route by ${plan.strategy} score: ${winner.model}`,
23
+ fallbacks: ranked.slice(1).map((candidate) => candidate.attempt),
26
24
  };
27
25
  }
28
- withoutOpenCircuits(candidates, ctx) {
29
- if (!ctx.openCircuits?.length)
30
- return candidates;
31
- const open = new Set(ctx.openCircuits);
32
- const remaining = candidates.filter((candidate) => !open.has(candidate.providerName));
33
- return remaining.length > 0 ? remaining : candidates;
26
+ get planCount() {
27
+ return this.plans.size;
28
+ }
29
+ plan(ctx) {
30
+ const key = this.planKey(ctx);
31
+ const cached = this.plans.get(key);
32
+ if (cached) {
33
+ this.plans.delete(key);
34
+ this.plans.set(key, cached);
35
+ return cached;
36
+ }
37
+ const plan = this.buildPlan(ctx);
38
+ this.plans.set(key, plan);
39
+ if (this.plans.size > PLAN_LIMIT)
40
+ this.plans.delete(this.plans.keys().next().value);
41
+ return plan;
42
+ }
43
+ planKey(ctx) {
44
+ const routing = ctx.config.routing;
45
+ const models = ctx.config.models;
46
+ const providers = [];
47
+ for (const [name, provider] of ctx.providers)
48
+ providers.push(`${name}${provider.info?.isLocal === true ? '+local' : ''}`);
49
+ return JSON.stringify([
50
+ routing?.strategy ?? 'quality',
51
+ routing?.candidateModels ?? null,
52
+ routing?.modelPreferences ?? null,
53
+ routing?.allowModels ?? null,
54
+ routing?.denyModels ?? null,
55
+ normalizedRequirements(routing?.requiredCapabilities),
56
+ models?.includeDefaults ?? null,
57
+ models?.aliases ?? null,
58
+ models?.registry ? this.registryId(models.registry) : 0,
59
+ providers,
60
+ ]);
34
61
  }
35
- getCandidates(ctx) {
62
+ registryId(registry) {
63
+ let id = this.registryIds.get(registry);
64
+ if (id === undefined) {
65
+ id = this.registryIds.get(this.registryIds) ?? 1;
66
+ this.registryIds.set(registry, id);
67
+ this.registryIds.set(this.registryIds, id + 1);
68
+ }
69
+ return id;
70
+ }
71
+ buildPlan(ctx) {
72
+ const strategy = ctx.config.routing?.strategy || 'quality';
73
+ const registry = (0, registry_js_1.getModelRegistry)(ctx.config);
74
+ const requirements = compileRequirements(ctx.config.routing?.requiredCapabilities);
75
+ const candidates = this.getCandidates(ctx, registry, requirements);
76
+ const planned = candidates.map((candidate, index) => ({
77
+ providerName: candidate.providerName,
78
+ model: candidate.model,
79
+ base: this.score(candidate.model, strategy, registry, ctx.providers.get(candidate.providerName)?.info?.isLocal === true) + candidate.weight,
80
+ index,
81
+ attempt: Object.freeze({ providerName: candidate.providerName, model: candidate.model }),
82
+ }));
83
+ const ranked = [...planned].sort(byScore);
84
+ const byProvider = new Map();
85
+ for (const candidate of ranked) {
86
+ const list = byProvider.get(candidate.providerName);
87
+ if (list)
88
+ list.push(candidate);
89
+ else
90
+ byProvider.set(candidate.providerName, [candidate]);
91
+ }
92
+ return { strategy, ranked, byProvider };
93
+ }
94
+ rank(plan, ctx) {
95
+ let excluded;
96
+ if (ctx.openCircuits?.length) {
97
+ const open = new Set(ctx.openCircuits);
98
+ if (plan.ranked.some((candidate) => !open.has(candidate.providerName)))
99
+ excluded = open;
100
+ }
101
+ const offsets = this.healthOffsets(ctx);
102
+ if (!offsets) {
103
+ return excluded ? plan.ranked.filter((candidate) => !excluded.has(candidate.providerName)) : plan.ranked;
104
+ }
105
+ const lists = [];
106
+ let total = 0;
107
+ for (const [providerName, list] of plan.byProvider) {
108
+ if (excluded?.has(providerName))
109
+ continue;
110
+ lists.push({ list, offset: offsets.get(providerName) ?? 0, at: 0 });
111
+ total += list.length;
112
+ }
113
+ const merged = new Array(total);
114
+ for (let position = 0; position < total; position += 1) {
115
+ let best = -1;
116
+ let bestScore = Number.NEGATIVE_INFINITY;
117
+ let bestIndex = Number.POSITIVE_INFINITY;
118
+ for (let which = 0; which < lists.length; which += 1) {
119
+ const head = lists[which];
120
+ const candidate = head.list[head.at];
121
+ if (!candidate)
122
+ continue;
123
+ const score = candidate.base + head.offset;
124
+ if (score > bestScore || (score === bestScore && candidate.index < bestIndex)) {
125
+ best = which;
126
+ bestScore = score;
127
+ bestIndex = candidate.index;
128
+ }
129
+ }
130
+ const chosen = lists[best];
131
+ merged[position] = chosen.list[chosen.at];
132
+ chosen.at += 1;
133
+ }
134
+ return merged;
135
+ }
136
+ getCandidates(ctx, registry, requirements) {
36
137
  const candidates = [];
37
138
  const configuredModels = ctx.config.routing?.candidateModels;
139
+ const passes = (model) => !requirements || this.matchesRequirements(model, registry, requirements);
38
140
  if (configuredModels?.length) {
39
141
  for (const model of configuredModels) {
40
142
  const resolved = (0, registry_js_1.resolveModel)(model, ctx.config);
@@ -43,23 +145,19 @@ class AutoRouter {
43
145
  candidates.push({ providerName, model: resolved.model, weight: 0 });
44
146
  }
45
147
  }
46
- return candidates.filter((candidate) => this.matchesRequirements(candidate.model, ctx.config));
148
+ return candidates.filter((candidate) => passes(candidate.model));
47
149
  }
48
150
  const preferences = ctx.config.routing?.modelPreferences?.[ctx.config.routing?.strategy || 'quality'];
49
151
  for (const providerName of ctx.providers.keys()) {
50
- const preferred = this.preferencesForProvider(providerName, preferences, ctx.config);
152
+ const preferred = this.preferencesForProvider(providerName, preferences, ctx.config, registry);
51
153
  if (preferred.length) {
52
154
  candidates.push(...preferred);
53
155
  }
54
156
  }
55
- return candidates.filter((candidate) => {
56
- return this.isAllowed(candidate.model, ctx.config) && this.matchesRequirements(candidate.model, ctx.config);
57
- });
157
+ return candidates.filter((candidate) => this.isAllowed(candidate.model, ctx.config) && passes(candidate.model));
58
158
  }
59
- score(model, strategy, config, providerIsLocal = false) {
60
- const registry = (0, registry_js_1.getModelRegistry)(config);
61
- const normalized = model.startsWith('ollama/') ? model.slice(7) : model;
62
- const caps = registry[model] || registry[normalized] || this.localFallbackCapabilities(model);
159
+ score(model, strategy, registry, providerIsLocal = false) {
160
+ const caps = this.capabilitiesOf(model, registry);
63
161
  if (strategy === 'privacy')
64
162
  return providerIsLocal || isLocalModel(model) ? 100 : 30;
65
163
  if (strategy === 'speed')
@@ -68,6 +166,10 @@ class AutoRouter {
68
166
  return this.costScore(model, caps);
69
167
  return this.qualityScore(model, caps);
70
168
  }
169
+ capabilitiesOf(model, registry) {
170
+ const normalized = model.startsWith('ollama/') ? model.slice(7) : model;
171
+ return registry[model] || registry[normalized] || this.localFallbackCapabilities(model);
172
+ }
71
173
  localFallbackCapabilities(model) {
72
174
  return {
73
175
  inputModalities: ['text'],
@@ -113,7 +215,7 @@ class AutoRouter {
113
215
  return 65;
114
216
  return Math.min(85, caps.maxContextTokens / 2000);
115
217
  }
116
- preferencesForProvider(providerName, preferences, config) {
218
+ preferencesForProvider(providerName, preferences, config, registry) {
117
219
  if (preferences?.length) {
118
220
  return preferences
119
221
  .map((preference) => typeof preference === 'string'
@@ -132,7 +234,7 @@ class AutoRouter {
132
234
  const registryModels = (0, registry_js_1.listModelsForProvider)(providerName, config);
133
235
  if (registryModels.length) {
134
236
  return registryModels
135
- .filter((model) => this.isProviderRunnable(providerName, model, config))
237
+ .filter((model) => this.isProviderRunnable(providerName, model, registry))
136
238
  .map((model) => ({ providerName, model, weight: 0 }));
137
239
  }
138
240
  if (providerName === 'ollama') {
@@ -173,52 +275,44 @@ class AutoRouter {
173
275
  return false;
174
276
  return !deny.some((entry) => this.matchesModelPattern(model, entry));
175
277
  }
176
- matchesRequirements(model, config) {
177
- const requirements = config.routing?.requiredCapabilities;
178
- if (!requirements)
179
- return true;
180
- const registry = (0, registry_js_1.getModelRegistry)(config);
181
- const normalized = model.startsWith('ollama/') ? model.slice(7) : model;
182
- const caps = registry[model] || registry[normalized] || this.localFallbackCapabilities(model);
183
- if (requirements.streaming !== undefined && caps.streaming !== requirements.streaming)
184
- return false;
185
- if (requirements.toolCalling !== undefined && caps.toolCalling !== requirements.toolCalling)
186
- return false;
187
- if (requirements.structuredOutputs !== undefined && caps.structuredOutputs !== requirements.structuredOutputs)
278
+ matchesRequirements(model, registry, requirements) {
279
+ const caps = this.capabilitiesOf(model, registry);
280
+ const { mustBe, mustNotBe } = capabilityMasks(caps);
281
+ if ((mustBe & requirements.mustBe) !== requirements.mustBe)
188
282
  return false;
189
- if (requirements.jsonMode !== undefined && caps.jsonMode !== requirements.jsonMode)
283
+ if ((mustNotBe & requirements.mustNotBe) !== requirements.mustNotBe)
190
284
  return false;
191
- if (requirements.reasoning !== undefined && Boolean(caps.reasoning) !== requirements.reasoning)
285
+ const source = requirements.source;
286
+ if (source.minContextTokens && caps.maxContextTokens < source.minContextTokens)
192
287
  return false;
193
- if (requirements.minContextTokens && caps.maxContextTokens < requirements.minContextTokens)
288
+ if (source.maxInputCostPer1k !== undefined && caps.costPer1kInput > source.maxInputCostPer1k)
194
289
  return false;
195
- if (requirements.maxInputCostPer1k !== undefined && caps.costPer1kInput > requirements.maxInputCostPer1k)
290
+ if (source.maxOutputCostPer1k !== undefined && caps.costPer1kOutput > source.maxOutputCostPer1k)
196
291
  return false;
197
- if (requirements.maxOutputCostPer1k !== undefined && caps.costPer1kOutput > requirements.maxOutputCostPer1k)
292
+ if (source.statuses?.length && caps.status && !source.statuses.includes(caps.status))
198
293
  return false;
199
- if (requirements.statuses?.length && caps.status && !requirements.statuses.includes(caps.status))
294
+ if (source.inputModalities?.some((modality) => !caps.inputModalities.includes(modality)))
200
295
  return false;
201
- if (requirements.inputModalities?.some((modality) => !caps.inputModalities.includes(modality)))
202
- return false;
203
- if (requirements.outputModalities?.some((modality) => !caps.outputModalities.includes(modality)))
296
+ if (source.outputModalities?.some((modality) => !caps.outputModalities.includes(modality)))
204
297
  return false;
205
298
  return true;
206
299
  }
207
300
  matchesModelPattern(model, pattern) {
208
301
  if (pattern === model)
209
302
  return true;
210
- if (!pattern.includes('*'))
211
- return false;
212
- const regex = new RegExp(`^${pattern
213
- .split('*')
214
- .map((part) => {
215
- return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
216
- })
217
- .join('.*')}$`);
218
- return regex.test(model);
303
+ let regex = this.patterns.get(pattern);
304
+ if (regex === undefined) {
305
+ regex = pattern.includes('*')
306
+ ? new RegExp(`^${pattern
307
+ .split('*')
308
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
309
+ .join('.*')}$`)
310
+ : null;
311
+ this.patterns.set(pattern, regex);
312
+ }
313
+ return regex ? regex.test(model) : false;
219
314
  }
220
- isProviderRunnable(providerName, model, config) {
221
- const registry = (0, registry_js_1.getModelRegistry)(config);
315
+ isProviderRunnable(providerName, model, registry) {
222
316
  const caps = registry[model];
223
317
  if (!caps?.endpoints)
224
318
  return true;
@@ -230,16 +324,55 @@ class AutoRouter {
230
324
  return caps.endpoints.includes('generateContent');
231
325
  return true;
232
326
  }
233
- healthScore(providerName, ctx) {
327
+ healthOffsets(ctx) {
234
328
  if (!ctx.config.health?.enabled || !ctx.health?.length)
235
- return 0;
236
- const snapshot = ctx.health.find((item) => item.providerName === providerName);
237
- if (!snapshot)
238
- return 0;
239
- return snapshot.healthy ? snapshot.score / 10 : -100;
329
+ return undefined;
330
+ const offsets = new Map();
331
+ for (const snapshot of ctx.health) {
332
+ if (offsets.has(snapshot.providerName))
333
+ continue;
334
+ offsets.set(snapshot.providerName, snapshot.healthy ? snapshot.score / 10 : -100);
335
+ }
336
+ return offsets;
240
337
  }
241
338
  }
242
339
  exports.AutoRouter = AutoRouter;
340
+ function compileRequirements(requirements) {
341
+ if (!requirements)
342
+ return undefined;
343
+ let mustBe = 0;
344
+ let mustNotBe = 0;
345
+ BOOLEAN_CAPABILITIES.forEach((name, bit) => {
346
+ const wanted = requirements[name];
347
+ if (wanted === true)
348
+ mustBe |= 1 << bit;
349
+ else if (wanted === false)
350
+ mustNotBe |= 1 << bit;
351
+ });
352
+ return { mustBe, mustNotBe, source: requirements };
353
+ }
354
+ function capabilityMasks(caps) {
355
+ let mustBe = 0;
356
+ let mustNotBe = 0;
357
+ BOOLEAN_CAPABILITIES.forEach((name, bit) => {
358
+ const value = name === 'reasoning' ? Boolean(caps.reasoning) : caps[name];
359
+ if (value === true)
360
+ mustBe |= 1 << bit;
361
+ else if (value === false)
362
+ mustNotBe |= 1 << bit;
363
+ });
364
+ return { mustBe, mustNotBe };
365
+ }
366
+ function normalizedRequirements(requirements) {
367
+ if (!requirements)
368
+ return null;
369
+ return Object.keys(requirements)
370
+ .sort()
371
+ .map((key) => [key, requirements[key]]);
372
+ }
373
+ function byScore(a, b) {
374
+ return b.base - a.base || a.index - b.index;
375
+ }
243
376
  function isLocalModel(model) {
244
377
  return (model.startsWith('ollama/') ||
245
378
  model.startsWith('lmstudio/') ||
@@ -1,3 +1,4 @@
1
+ import type { GraphStreamProjection } from '../types/graph.js';
1
2
  import type { AssistantRunContext, ServerAssistant } from '../types/server.js';
2
3
  /**
3
4
  * The part of a compiled graph the adapter uses.
@@ -11,11 +12,15 @@ export interface GraphLike {
11
12
  threadId?: string;
12
13
  signal?: AbortSignal;
13
14
  metadata?: Record<string, unknown>;
15
+ tenantId?: string;
16
+ control?: DrainSwitch;
14
17
  }): AsyncIterable<unknown>;
15
18
  /** Answers an interrupt and continues the thread. */
16
19
  resume(threadId: string, value: unknown, options: {
17
20
  signal?: AbortSignal;
18
21
  metadata?: Record<string, unknown>;
22
+ tenantId?: string;
23
+ control?: DrainSwitch;
19
24
  }): AsyncIterable<unknown>;
20
25
  /** The thread's checkpoint, at its latest step or at one given. */
21
26
  state(threadId: string, step?: number): Promise<{
@@ -28,18 +33,56 @@ export interface GraphLike {
28
33
  continue?(threadId: string, options: {
29
34
  signal?: AbortSignal;
30
35
  metadata?: Record<string, unknown>;
36
+ tenantId?: string;
37
+ control?: DrainSwitch;
31
38
  }): AsyncIterable<unknown>;
32
39
  /** Writes values into a thread's state, which is how a rollback is applied. */
33
40
  updateState?(threadId: string, values: Record<string, unknown>, options?: {
34
41
  asNode?: string;
35
42
  }): Promise<unknown>;
43
+ /** Runs an input as a stream of typed events. Used when the assistant serves `events`. */
44
+ events?(input: unknown, options: GraphEventOptionsLike): GraphEventsLike;
45
+ /** Like `events()`, answering an interrupt. */
46
+ resumeEvents?(threadId: string, value: unknown, options: GraphEventOptionsLike): GraphEventsLike;
47
+ /** Like `events()`, continuing a thread from its latest checkpoint. */
48
+ continueEvents?(threadId: string, options: GraphEventOptionsLike): GraphEventsLike;
36
49
  }
50
+ /** The options of a graph's `events()` the adapter passes. */
51
+ type GraphEventOptionsLike = {
52
+ threadId?: string;
53
+ tenantId?: string;
54
+ signal?: AbortSignal;
55
+ metadata?: Record<string, unknown>;
56
+ control?: DrainSwitch;
57
+ include?: GraphStreamProjection[];
58
+ };
59
+ /** A graph's event stream, as the adapter reads it: the events, then the run's result. */
60
+ type GraphEventsLike = AsyncIterable<unknown> & {
61
+ result: Promise<{
62
+ status: string;
63
+ state: unknown;
64
+ interrupt?: unknown;
65
+ interrupts?: unknown[];
66
+ }>;
67
+ };
68
+ /** Asks a graph to stop after its current superstep. A graph's `RunControl` has this shape. */
69
+ type DrainSwitch = {
70
+ readonly draining: boolean;
71
+ readonly reason?: string;
72
+ };
37
73
  /** Options for `graphAssistant()`. */
38
74
  export interface GraphAssistantOptions {
39
75
  /** What the assistant is, reported by the assistants endpoint. */
40
76
  description?: string;
41
77
  /** Metadata recorded on every checkpoint the server's runs create. */
42
78
  metadata?: Record<string, unknown>;
79
+ /**
80
+ * Records these projections of the graph's event stream on each run, such as `messages` and
81
+ * `tools`, so a client following the run sees model output and tool calls as they happen. The
82
+ * run's last event still carries its state and any question, as without it. Defaults to none: one
83
+ * event per superstep.
84
+ */
85
+ events?: GraphStreamProjection[];
43
86
  }
44
87
  /**
45
88
  * Serves a compiled graph as an assistant.
@@ -61,3 +104,4 @@ export declare function graphAssistant(graph: GraphLike, options?: GraphAssistan
61
104
  export declare function functionAssistant(run: (input: unknown, context: AssistantRunContext) => AsyncIterable<unknown> | Promise<unknown> | unknown, options?: {
62
105
  description?: string;
63
106
  }): ServerAssistant;
107
+ export {};
@@ -3,19 +3,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.graphAssistant = graphAssistant;
4
4
  exports.functionAssistant = functionAssistant;
5
5
  function graphAssistant(graph, options = {}) {
6
+ const projections = options.events?.length ? options.events : undefined;
7
+ const eventOptions = (context, threadId) => ({
8
+ ...(threadId ? { threadId } : {}),
9
+ signal: context.signal,
10
+ metadata: { ...options.metadata, runId: context.runId },
11
+ ...tenantOf(context),
12
+ ...(context.control ? { control: context.control } : {}),
13
+ include: [...(projections ?? []), 'values'],
14
+ });
6
15
  return {
7
16
  description: options.description,
8
17
  stream(input, context) {
18
+ if (projections && graph.events) {
19
+ return withResult(graph.events(input ?? {}, eventOptions(context, context.threadId)));
20
+ }
9
21
  return graph.stream(input ?? {}, {
10
22
  threadId: context.threadId,
11
23
  signal: context.signal,
12
24
  metadata: { ...options.metadata, runId: context.runId },
25
+ ...tenantOf(context),
26
+ ...(context.control ? { control: context.control } : {}),
13
27
  });
14
28
  },
15
29
  resume(threadId, value, context) {
30
+ if (projections && graph.resumeEvents) {
31
+ return withResult(graph.resumeEvents(threadId, value, eventOptions(context)));
32
+ }
16
33
  return graph.resume(threadId, value, {
17
34
  signal: context.signal,
18
35
  metadata: { ...options.metadata, runId: context.runId },
36
+ ...tenantOf(context),
37
+ ...(context.control ? { control: context.control } : {}),
19
38
  });
20
39
  },
21
40
  async state(threadId) {
@@ -33,9 +52,13 @@ function graphAssistant(graph, options = {}) {
33
52
  if (checkpoint.status !== 'running' && checkpoint.status !== 'failed' && checkpoint.status !== 'interrupted') {
34
53
  return undefined;
35
54
  }
55
+ if (projections && graph.continueEvents)
56
+ return withResult(graph.continueEvents(threadId, eventOptions(context)));
36
57
  return graph.continue(threadId, {
37
58
  signal: context.signal,
38
59
  metadata: { ...options.metadata, runId: context.runId },
60
+ ...tenantOf(context),
61
+ ...(context.control ? { control: context.control } : {}),
39
62
  });
40
63
  },
41
64
  async restore(threadId, step) {
@@ -61,3 +84,19 @@ function functionAssistant(run, options = {}) {
61
84
  },
62
85
  };
63
86
  }
87
+ function tenantOf(context) {
88
+ const tenantId = context.principal?.tenantId;
89
+ return tenantId ? { tenantId } : {};
90
+ }
91
+ async function* withResult(events) {
92
+ for await (const event of events)
93
+ yield event;
94
+ const result = await events.result;
95
+ yield {
96
+ type: result.status === 'awaiting_input' ? 'interrupt' : 'done',
97
+ status: result.status,
98
+ state: result.state,
99
+ ...(result.interrupt === undefined ? {} : { interrupt: result.interrupt }),
100
+ ...(result.interrupts === undefined ? {} : { interrupts: result.interrupts }),
101
+ };
102
+ }
@@ -32,6 +32,18 @@ export declare class ForbiddenError extends ServerError {
32
32
  /** The scope the route required. */
33
33
  scope: string);
34
34
  }
35
+ /**
36
+ * Thrown by an assistant to hand its run to another worker while this replica drains: the server puts
37
+ * the run back on the queue as its next attempt instead of failing it. A function assistant checks
38
+ * `context.control?.draining` between its own steps, saves where it got with `context.saveProgress()`,
39
+ * and throws this; the next worker reads `context.progress`. Thrown when the replica is not draining,
40
+ * it fails the run like any error.
41
+ */
42
+ export declare class RunHandOffError extends Error {
43
+ /** Always `RUN_HANDED_OFF`. */
44
+ readonly code = "RUN_HANDED_OFF";
45
+ constructor(message?: string);
46
+ }
35
47
  /** Raised when a thread is already running something and the busy policy is `reject`. */
36
48
  export declare class ThreadBusyError extends ServerError {
37
49
  /** The thread. */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AssistantCapabilityError = exports.TenantLimitError = exports.ThreadBusyError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.NotFoundError = exports.ServerError = void 0;
3
+ exports.AssistantCapabilityError = exports.TenantLimitError = exports.ThreadBusyError = exports.RunHandOffError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.NotFoundError = exports.ServerError = void 0;
4
4
  class ServerError extends Error {
5
5
  code;
6
6
  status;
@@ -43,6 +43,14 @@ class ForbiddenError extends ServerError {
43
43
  }
44
44
  }
45
45
  exports.ForbiddenError = ForbiddenError;
46
+ class RunHandOffError extends Error {
47
+ code = 'RUN_HANDED_OFF';
48
+ constructor(message = 'Handed to another worker while this one drains') {
49
+ super(message);
50
+ this.name = 'RunHandOffError';
51
+ }
52
+ }
53
+ exports.RunHandOffError = RunHandOffError;
46
54
  class ThreadBusyError extends ServerError {
47
55
  threadId;
48
56
  runId;
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export { functionAssistant, type GraphAssistantOptions, graphAssistant, type GraphLike, } from './assistant.js';
11
11
  export { CronScheduler, type CronSchedulerOptions, type DueCronJob, parseCron } from './cron.js';
12
- export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, ServerError, type TenantLimit, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
12
+ export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, RunHandOffError, ServerError, type TenantLimit, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
13
13
  export { MemoryRunEventLog, type MemoryRunEventLogOptions, type RedisEventLogLikeClient, RedisRunEventLog, type RedisRunEventLogOptions, } from './events.js';
14
14
  export { type NodeListenerOptions, type NodeRequestLike, type NodeResponseLike, toNodeListener, } from './node.js';
15
15
  export { type DrainResult, RunManager, type RunManagerOptions, type RunQueueOptions, type StartRunOptions, } from './runs.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MemoryServerStore = exports.fromStore = exports.createAgentServer = exports.RunManager = exports.toNodeListener = exports.RedisRunEventLog = exports.MemoryRunEventLog = exports.UnauthorizedError = exports.ThreadBusyError = exports.TenantLimitError = exports.ServerError = exports.NotFoundError = exports.ForbiddenError = exports.BadRequestError = exports.AssistantCapabilityError = exports.parseCron = exports.CronScheduler = exports.graphAssistant = exports.functionAssistant = void 0;
3
+ exports.MemoryServerStore = exports.fromStore = exports.createAgentServer = exports.RunManager = exports.toNodeListener = exports.RedisRunEventLog = exports.MemoryRunEventLog = exports.UnauthorizedError = exports.ThreadBusyError = exports.TenantLimitError = exports.ServerError = exports.RunHandOffError = exports.NotFoundError = exports.ForbiddenError = exports.BadRequestError = exports.AssistantCapabilityError = exports.parseCron = exports.CronScheduler = exports.graphAssistant = exports.functionAssistant = void 0;
4
4
  var assistant_js_1 = require("./assistant.js");
5
5
  Object.defineProperty(exports, "functionAssistant", { enumerable: true, get: function () { return assistant_js_1.functionAssistant; } });
6
6
  Object.defineProperty(exports, "graphAssistant", { enumerable: true, get: function () { return assistant_js_1.graphAssistant; } });
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "AssistantCapabilityError", { enumerable: true, g
12
12
  Object.defineProperty(exports, "BadRequestError", { enumerable: true, get: function () { return errors_js_1.BadRequestError; } });
13
13
  Object.defineProperty(exports, "ForbiddenError", { enumerable: true, get: function () { return errors_js_1.ForbiddenError; } });
14
14
  Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_js_1.NotFoundError; } });
15
+ Object.defineProperty(exports, "RunHandOffError", { enumerable: true, get: function () { return errors_js_1.RunHandOffError; } });
15
16
  Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return errors_js_1.ServerError; } });
16
17
  Object.defineProperty(exports, "TenantLimitError", { enumerable: true, get: function () { return errors_js_1.TenantLimitError; } });
17
18
  Object.defineProperty(exports, "ThreadBusyError", { enumerable: true, get: function () { return errors_js_1.ThreadBusyError; } });
@@ -97,6 +97,10 @@ export declare class RunManager {
97
97
  private readonly local;
98
98
  /** Runs holding one of this replica's execution slots. */
99
99
  private readonly slots;
100
+ /** The drain switch of each run this replica is executing, so `drain()` can reach graph runs. */
101
+ private readonly controls;
102
+ /** Runs handed back to the queue during a drain, as each graph run stopped at a boundary. */
103
+ private readonly drainedRuns;
100
104
  private readonly state;
101
105
  private readonly events;
102
106
  private readonly now;
@@ -155,6 +159,8 @@ export declare class RunManager {
155
159
  * it with a `TenantLimitError`.
156
160
  */
157
161
  start(options: StartRunOptions): Promise<RunRecord>;
162
+ /** The rest of `start()`, once the thread, if any, is this run's. */
163
+ private startClaimed;
158
164
  /** Cancels a run, including one another replica is executing. */
159
165
  cancel(runId: string, principal?: Principal, reason?: string): Promise<RunRecord>;
160
166
  /**
@@ -206,8 +212,29 @@ export declare class RunManager {
206
212
  private freeSlot;
207
213
  private schedule;
208
214
  private tick;
209
- /** Applies the busy policy, returning the thread once it is free to run something new. */
215
+ /**
216
+ * Applies the busy policy, returning the thread once this run holds it. The thread is claimed
217
+ * atomically, so of several replicas starting runs on one thread at once, exactly one proceeds and
218
+ * the rest meet the policy; a thread still busy after the policy has run its course is refused.
219
+ */
210
220
  private settleBusyThread;
221
+ /**
222
+ * Claims a thread for a run. The claim is a record in the operation store under a unique
223
+ * idempotency key, moved from run to run by compare-and-set, so two replicas cannot both take a free
224
+ * thread: creating it races on the key, and taking it over races on the sequence. Resolves
225
+ * undefined once this run holds the thread, or the id of the unfinished run that does.
226
+ *
227
+ * A store without `findByIdempotencyKey` falls back to the thread record's `activeRunId`, which is
228
+ * what every release before 2.2 used, and which two replicas racing can both see empty.
229
+ */
230
+ private claimThread;
231
+ /**
232
+ * Whether a thread's holder no longer needs it: its run finished, or it was never recorded and its
233
+ * claim is old enough that the replica starting it must have died.
234
+ */
235
+ private runIsFree;
236
+ /** Frees a thread this run holds. Losing the compare-and-set means another run took it over, which is fine. */
237
+ private releaseClaim;
211
238
  /** Waits for a run to finish, for the `enqueue` policy. */
212
239
  private waitForRun;
213
240
  private releaseThread;