nexus-ai-pro 2.1.0 → 2.3.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 (300) hide show
  1. package/API_STABILITY.md +77 -0
  2. package/CHANGELOG.md +286 -1
  3. package/README.md +19 -9
  4. package/dist/agent/capabilities.d.ts +39 -0
  5. package/dist/agent/capabilities.js +36 -0
  6. package/dist/agent/create-agent.d.ts +61 -11
  7. package/dist/agent/create-agent.js +136 -22
  8. package/dist/agent/index.d.ts +3 -2
  9. package/dist/agent/index.js +1 -1
  10. package/dist/agent/middleware/approval.d.ts +26 -0
  11. package/dist/agent/middleware/approval.js +23 -0
  12. package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  13. package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
  14. package/dist/agent/middleware/context-editor.d.ts +52 -0
  15. package/dist/agent/middleware/context-editor.js +66 -0
  16. package/dist/agent/middleware/filesystem.d.ts +44 -0
  17. package/dist/agent/middleware/filesystem.js +83 -0
  18. package/dist/agent/middleware/index.d.ts +19 -0
  19. package/dist/agent/middleware/index.js +9 -0
  20. package/dist/agent/middleware/limits.d.ts +42 -0
  21. package/dist/agent/middleware/limits.js +52 -0
  22. package/dist/agent/middleware/models.d.ts +48 -0
  23. package/dist/agent/middleware/models.js +44 -0
  24. package/dist/agent/middleware/pii.d.ts +67 -0
  25. package/dist/agent/middleware/pii.js +173 -0
  26. package/dist/agent/middleware/retry.d.ts +66 -0
  27. package/dist/agent/middleware/retry.js +59 -0
  28. package/dist/agent/middleware/shared.d.ts +41 -0
  29. package/dist/agent/middleware/shared.js +73 -0
  30. package/dist/agent/middleware/tool-selector.d.ts +64 -0
  31. package/dist/agent/middleware/tool-selector.js +106 -0
  32. package/dist/agent/permissions.d.ts +113 -0
  33. package/dist/agent/permissions.js +180 -0
  34. package/dist/agent/sandbox.d.ts +151 -0
  35. package/dist/agent/sandbox.js +257 -0
  36. package/dist/agent/tool.d.ts +8 -3
  37. package/dist/agent/tool.js +14 -3
  38. package/dist/cache/memory-cache.d.ts +8 -0
  39. package/dist/cache/memory-cache.js +9 -0
  40. package/dist/cache/semantic-cache.d.ts +6 -3
  41. package/dist/cache/semantic-cache.js +5 -2
  42. package/dist/cli/args.js +3 -0
  43. package/dist/cli/db.d.ts +16 -4
  44. package/dist/cli/db.js +169 -22
  45. package/dist/cli/doctor.d.ts +17 -0
  46. package/dist/cli/doctor.js +53 -0
  47. package/dist/cli.js +12 -1
  48. package/dist/core/lifecycle.js +11 -6
  49. package/dist/core/nexus.d.ts +6 -0
  50. package/dist/core/nexus.js +103 -22
  51. package/dist/deep-agent/index.d.ts +109 -0
  52. package/dist/deep-agent/index.js +216 -0
  53. package/dist/doctor/index.d.ts +90 -0
  54. package/dist/doctor/index.js +237 -0
  55. package/dist/graph/functional.d.ts +89 -7
  56. package/dist/graph/functional.js +156 -36
  57. package/dist/graph/graph.d.ts +15 -5
  58. package/dist/graph/graph.js +95 -22
  59. package/dist/graph/lint.d.ts +4 -1
  60. package/dist/graph/lint.js +35 -0
  61. package/dist/images/evals.js +4 -2
  62. package/dist/operations/adapters.d.ts +52 -4
  63. package/dist/operations/adapters.js +194 -7
  64. package/dist/operations/runner.js +1 -0
  65. package/dist/operations/stats.js +8 -1
  66. package/dist/operations/store.d.ts +5 -1
  67. package/dist/operations/store.js +7 -0
  68. package/dist/ops/circuit-breaker.d.ts +52 -0
  69. package/dist/ops/circuit-breaker.js +4 -0
  70. package/dist/ops/circuit-store.d.ts +13 -2
  71. package/dist/ops/circuit-store.js +68 -0
  72. package/dist/ops/circuit-sync.d.ts +17 -0
  73. package/dist/ops/circuit-sync.js +86 -0
  74. package/dist/ops/rate-limiter.d.ts +2 -0
  75. package/dist/ops/rate-limiter.js +2 -0
  76. package/dist/postgres/circuits.d.ts +21 -4
  77. package/dist/postgres/circuits.js +65 -4
  78. package/dist/postgres/client.d.ts +0 -2
  79. package/dist/postgres/client.js +0 -4
  80. package/dist/postgres/evaluate.d.ts +16 -4
  81. package/dist/postgres/evaluate.js +17 -5
  82. package/dist/postgres/fulltext.d.ts +67 -0
  83. package/dist/postgres/fulltext.js +91 -0
  84. package/dist/postgres/index.d.ts +32 -11
  85. package/dist/postgres/index.js +65 -31
  86. package/dist/postgres/migrations.d.ts +60 -0
  87. package/dist/postgres/migrations.js +121 -0
  88. package/dist/postgres/operations.d.ts +14 -3
  89. package/dist/postgres/operations.js +45 -15
  90. package/dist/postgres/prompts.d.ts +11 -2
  91. package/dist/postgres/prompts.js +14 -3
  92. package/dist/postgres/rollups.d.ts +35 -0
  93. package/dist/postgres/rollups.js +129 -0
  94. package/dist/postgres/store.d.ts +11 -2
  95. package/dist/postgres/store.js +14 -3
  96. package/dist/postgres/traces.d.ts +13 -3
  97. package/dist/postgres/traces.js +25 -3
  98. package/dist/postgres/vectors.d.ts +11 -2
  99. package/dist/postgres/vectors.js +14 -3
  100. package/dist/rag/elasticsearch.d.ts +71 -0
  101. package/dist/rag/elasticsearch.js +130 -0
  102. package/dist/rag/pipeline.d.ts +140 -0
  103. package/dist/rag/pipeline.js +187 -0
  104. package/dist/rag/rerankers.d.ts +92 -0
  105. package/dist/rag/rerankers.js +97 -0
  106. package/dist/rag/retrievers.d.ts +12 -1
  107. package/dist/router/auto-router.d.ts +34 -7
  108. package/dist/router/auto-router.js +201 -68
  109. package/dist/server/assistant.d.ts +9 -1
  110. package/dist/server/assistant.js +10 -0
  111. package/dist/server/auth.d.ts +250 -0
  112. package/dist/server/auth.js +392 -0
  113. package/dist/server/errors.d.ts +12 -0
  114. package/dist/server/errors.js +7 -0
  115. package/dist/server/index.d.ts +1 -1
  116. package/dist/server/index.js +1 -1
  117. package/dist/server/runs.d.ts +24 -1
  118. package/dist/server/runs.js +90 -14
  119. package/dist/sqlite/index.d.ts +16 -3
  120. package/dist/sqlite/index.js +16 -3
  121. package/dist/sqlite/migrations.d.ts +26 -0
  122. package/dist/sqlite/migrations.js +63 -0
  123. package/dist/sqlite/operations.d.ts +14 -3
  124. package/dist/sqlite/operations.js +42 -15
  125. package/dist/sqlite/store.d.ts +9 -2
  126. package/dist/sqlite/store.js +13 -3
  127. package/dist/sqlite/vectors.d.ts +11 -2
  128. package/dist/sqlite/vectors.js +13 -2
  129. package/dist/store/memory.d.ts +10 -0
  130. package/dist/store/memory.js +10 -0
  131. package/dist/store/tenant.d.ts +12 -0
  132. package/dist/store/tenant.js +29 -0
  133. package/dist/tenancy/index.d.ts +113 -0
  134. package/dist/tenancy/index.js +233 -0
  135. package/dist/tracing/instrument.js +2 -0
  136. package/dist/types/config.d.ts +24 -2
  137. package/dist/types/graph.d.ts +49 -1
  138. package/dist/types/messages.d.ts +41 -2
  139. package/dist/types/operations.d.ts +6 -0
  140. package/dist/types/principal.d.ts +21 -0
  141. package/dist/types/principal.js +1 -0
  142. package/dist/types/response.d.ts +20 -0
  143. package/dist/types/server.d.ts +12 -10
  144. package/dist/utils/schema-migrations.d.ts +97 -0
  145. package/dist/utils/schema-migrations.js +91 -0
  146. package/dist/utils/signals.d.ts +17 -0
  147. package/dist/utils/signals.js +26 -0
  148. package/dist/utils/tenant.d.ts +25 -0
  149. package/dist/utils/tenant.js +30 -0
  150. package/dist/version.d.ts +1 -1
  151. package/dist/version.js +1 -1
  152. package/dist-cjs/agent/capabilities.d.ts +39 -0
  153. package/dist-cjs/agent/capabilities.js +41 -0
  154. package/dist-cjs/agent/create-agent.d.ts +61 -11
  155. package/dist-cjs/agent/create-agent.js +136 -22
  156. package/dist-cjs/agent/index.d.ts +3 -2
  157. package/dist-cjs/agent/index.js +4 -4
  158. package/dist-cjs/agent/middleware/approval.d.ts +26 -0
  159. package/dist-cjs/agent/middleware/approval.js +26 -0
  160. package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  161. package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
  162. package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
  163. package/dist-cjs/agent/middleware/context-editor.js +70 -0
  164. package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
  165. package/dist-cjs/agent/middleware/filesystem.js +86 -0
  166. package/dist-cjs/agent/middleware/index.d.ts +19 -0
  167. package/dist-cjs/agent/middleware/index.js +28 -0
  168. package/dist-cjs/agent/middleware/limits.d.ts +42 -0
  169. package/dist-cjs/agent/middleware/limits.js +57 -0
  170. package/dist-cjs/agent/middleware/models.d.ts +48 -0
  171. package/dist-cjs/agent/middleware/models.js +48 -0
  172. package/dist-cjs/agent/middleware/pii.d.ts +67 -0
  173. package/dist-cjs/agent/middleware/pii.js +178 -0
  174. package/dist-cjs/agent/middleware/retry.d.ts +66 -0
  175. package/dist-cjs/agent/middleware/retry.js +63 -0
  176. package/dist-cjs/agent/middleware/shared.d.ts +41 -0
  177. package/dist-cjs/agent/middleware/shared.js +83 -0
  178. package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
  179. package/dist-cjs/agent/middleware/tool-selector.js +109 -0
  180. package/dist-cjs/agent/permissions.d.ts +113 -0
  181. package/dist-cjs/agent/permissions.js +189 -0
  182. package/dist-cjs/agent/sandbox.d.ts +151 -0
  183. package/dist-cjs/agent/sandbox.js +297 -0
  184. package/dist-cjs/agent/tool.d.ts +8 -3
  185. package/dist-cjs/agent/tool.js +14 -3
  186. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  187. package/dist-cjs/cache/memory-cache.js +9 -0
  188. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  189. package/dist-cjs/cache/semantic-cache.js +5 -2
  190. package/dist-cjs/cli/args.js +3 -0
  191. package/dist-cjs/cli/db.d.ts +16 -4
  192. package/dist-cjs/cli/db.js +200 -20
  193. package/dist-cjs/cli/doctor.d.ts +17 -0
  194. package/dist-cjs/cli/doctor.js +56 -0
  195. package/dist-cjs/core/lifecycle.js +11 -6
  196. package/dist-cjs/core/nexus.d.ts +6 -0
  197. package/dist-cjs/core/nexus.js +103 -22
  198. package/dist-cjs/deep-agent/index.d.ts +109 -0
  199. package/dist-cjs/deep-agent/index.js +219 -0
  200. package/dist-cjs/doctor/index.d.ts +90 -0
  201. package/dist-cjs/doctor/index.js +276 -0
  202. package/dist-cjs/graph/functional.d.ts +89 -7
  203. package/dist-cjs/graph/functional.js +155 -35
  204. package/dist-cjs/graph/graph.d.ts +15 -5
  205. package/dist-cjs/graph/graph.js +95 -22
  206. package/dist-cjs/graph/lint.d.ts +4 -1
  207. package/dist-cjs/graph/lint.js +35 -0
  208. package/dist-cjs/images/evals.js +4 -2
  209. package/dist-cjs/operations/adapters.d.ts +52 -4
  210. package/dist-cjs/operations/adapters.js +193 -6
  211. package/dist-cjs/operations/runner.js +1 -0
  212. package/dist-cjs/operations/stats.js +8 -1
  213. package/dist-cjs/operations/store.d.ts +5 -1
  214. package/dist-cjs/operations/store.js +7 -0
  215. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  216. package/dist-cjs/ops/circuit-breaker.js +4 -0
  217. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  218. package/dist-cjs/ops/circuit-store.js +68 -0
  219. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  220. package/dist-cjs/ops/circuit-sync.js +87 -0
  221. package/dist-cjs/ops/rate-limiter.d.ts +2 -0
  222. package/dist-cjs/ops/rate-limiter.js +2 -0
  223. package/dist-cjs/postgres/circuits.d.ts +21 -4
  224. package/dist-cjs/postgres/circuits.js +98 -3
  225. package/dist-cjs/postgres/client.d.ts +0 -2
  226. package/dist-cjs/postgres/client.js +0 -5
  227. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  228. package/dist-cjs/postgres/evaluate.js +50 -4
  229. package/dist-cjs/postgres/fulltext.d.ts +67 -0
  230. package/dist-cjs/postgres/fulltext.js +129 -0
  231. package/dist-cjs/postgres/index.d.ts +32 -11
  232. package/dist-cjs/postgres/index.js +69 -20
  233. package/dist-cjs/postgres/migrations.d.ts +60 -0
  234. package/dist-cjs/postgres/migrations.js +129 -0
  235. package/dist-cjs/postgres/operations.d.ts +14 -3
  236. package/dist-cjs/postgres/operations.js +78 -14
  237. package/dist-cjs/postgres/prompts.d.ts +11 -2
  238. package/dist-cjs/postgres/prompts.js +47 -2
  239. package/dist-cjs/postgres/rollups.d.ts +35 -0
  240. package/dist-cjs/postgres/rollups.js +168 -0
  241. package/dist-cjs/postgres/store.d.ts +11 -2
  242. package/dist-cjs/postgres/store.js +47 -2
  243. package/dist-cjs/postgres/traces.d.ts +13 -3
  244. package/dist-cjs/postgres/traces.js +58 -2
  245. package/dist-cjs/postgres/vectors.d.ts +11 -2
  246. package/dist-cjs/postgres/vectors.js +47 -2
  247. package/dist-cjs/rag/elasticsearch.d.ts +71 -0
  248. package/dist-cjs/rag/elasticsearch.js +135 -0
  249. package/dist-cjs/rag/pipeline.d.ts +140 -0
  250. package/dist-cjs/rag/pipeline.js +191 -0
  251. package/dist-cjs/rag/rerankers.d.ts +92 -0
  252. package/dist-cjs/rag/rerankers.js +107 -0
  253. package/dist-cjs/rag/retrievers.d.ts +12 -1
  254. package/dist-cjs/router/auto-router.d.ts +34 -7
  255. package/dist-cjs/router/auto-router.js +201 -68
  256. package/dist-cjs/server/assistant.d.ts +9 -1
  257. package/dist-cjs/server/assistant.js +10 -0
  258. package/dist-cjs/server/auth.d.ts +250 -0
  259. package/dist-cjs/server/auth.js +402 -0
  260. package/dist-cjs/server/errors.d.ts +12 -0
  261. package/dist-cjs/server/errors.js +9 -1
  262. package/dist-cjs/server/index.d.ts +1 -1
  263. package/dist-cjs/server/index.js +2 -1
  264. package/dist-cjs/server/runs.d.ts +24 -1
  265. package/dist-cjs/server/runs.js +89 -13
  266. package/dist-cjs/sqlite/index.d.ts +16 -3
  267. package/dist-cjs/sqlite/index.js +31 -10
  268. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  269. package/dist-cjs/sqlite/migrations.js +71 -0
  270. package/dist-cjs/sqlite/operations.d.ts +14 -3
  271. package/dist-cjs/sqlite/operations.js +76 -15
  272. package/dist-cjs/sqlite/store.d.ts +9 -2
  273. package/dist-cjs/sqlite/store.js +47 -3
  274. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  275. package/dist-cjs/sqlite/vectors.js +47 -2
  276. package/dist-cjs/store/memory.d.ts +10 -0
  277. package/dist-cjs/store/memory.js +10 -0
  278. package/dist-cjs/store/tenant.d.ts +12 -0
  279. package/dist-cjs/store/tenant.js +33 -0
  280. package/dist-cjs/tenancy/index.d.ts +113 -0
  281. package/dist-cjs/tenancy/index.js +250 -0
  282. package/dist-cjs/tracing/instrument.js +2 -0
  283. package/dist-cjs/types/config.d.ts +24 -2
  284. package/dist-cjs/types/graph.d.ts +49 -1
  285. package/dist-cjs/types/messages.d.ts +41 -2
  286. package/dist-cjs/types/operations.d.ts +6 -0
  287. package/dist-cjs/types/principal.d.ts +21 -0
  288. package/dist-cjs/types/principal.js +2 -0
  289. package/dist-cjs/types/response.d.ts +20 -0
  290. package/dist-cjs/types/server.d.ts +12 -10
  291. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  292. package/dist-cjs/utils/schema-migrations.js +102 -0
  293. package/dist-cjs/utils/signals.d.ts +17 -0
  294. package/dist-cjs/utils/signals.js +29 -0
  295. package/dist-cjs/utils/tenant.d.ts +25 -0
  296. package/dist-cjs/utils/tenant.js +37 -0
  297. package/dist-cjs/version.d.ts +1 -1
  298. package/dist-cjs/version.js +1 -1
  299. package/llms.txt +6 -4
  300. package/package.json +188 -2
@@ -0,0 +1,92 @@
1
+ import type { Reranker } from './retrievers.js';
2
+ /** Raised when a rerank API answers with an error. */
3
+ export declare class RerankerError extends Error {
4
+ /** The HTTP status the API answered with. */
5
+ readonly status: number;
6
+ /** The API's response body, as text. */
7
+ readonly body?: string | undefined;
8
+ constructor(message: string,
9
+ /** The HTTP status the API answered with. */
10
+ status: number,
11
+ /** The API's response body, as text. */
12
+ body?: string | undefined);
13
+ }
14
+ /** What every HTTP reranker takes. */
15
+ export interface RerankerHttpOptions {
16
+ /** Replaces the global `fetch`, for a proxy, retries, or tests. */
17
+ fetch?: typeof globalThis.fetch;
18
+ /** Headers added to every request. */
19
+ headers?: Record<string, string>;
20
+ /** Aborts a request that takes longer, in milliseconds. Defaults to 30 seconds. */
21
+ timeoutMs?: number;
22
+ /** Characters of each chunk sent. Defaults to 4,000. */
23
+ maxChunkCharacters?: number;
24
+ }
25
+ /** Options for a hosted rerank API. */
26
+ export interface HostedRerankerOptions extends RerankerHttpOptions {
27
+ /** The API key. */
28
+ apiKey: string;
29
+ /** The rerank model, such as `rerank-v3.5`. */
30
+ model: string;
31
+ /** Replaces the API's endpoint, for a regional or proxied one. */
32
+ url?: string;
33
+ }
34
+ /** Options for `httpReranker()`: any rerank API, described by its request and its response. */
35
+ export interface HttpRerankerOptions extends RerankerHttpOptions {
36
+ /** The endpoint. */
37
+ url: string;
38
+ /** The request body for a query and its passages. */
39
+ body: (query: string, passages: string[]) => unknown;
40
+ /** Reads the response: each passage's index and score. Passages it leaves out score 0. */
41
+ scores: (response: unknown) => ReadonlyArray<{
42
+ index: number;
43
+ score: number;
44
+ }>;
45
+ }
46
+ /**
47
+ * A reranker over any HTTP rerank API: you describe the request body and how to read the scores,
48
+ * and it handles the rest — truncation, the timeout, cancellation, errors, and scores put back in
49
+ * the order of the chunks.
50
+ */
51
+ export declare function httpReranker(options: HttpRerankerOptions): Reranker;
52
+ /** Cohere's rerank API (`/v2/rerank`), such as `rerank-v3.5`. */
53
+ export declare function cohereReranker(options: HostedRerankerOptions): Reranker;
54
+ /** Voyage AI's rerank API (`/v1/rerank`), such as `rerank-2`. */
55
+ export declare function voyageReranker(options: HostedRerankerOptions): Reranker;
56
+ /** Jina AI's rerank API (`/v1/rerank`), such as `jina-reranker-v2-base-multilingual`. */
57
+ export declare function jinaReranker(options: HostedRerankerOptions): Reranker;
58
+ /** Options for `teiReranker()`. */
59
+ export interface TeiRerankerOptions extends RerankerHttpOptions {
60
+ /** The server, such as `http://localhost:8080`. */
61
+ url: string;
62
+ }
63
+ /**
64
+ * A cross-encoder served by Hugging Face's text-embeddings-inference, on your own machine or
65
+ * cluster: `docker run ... --model-id BAAI/bge-reranker-base`, then `teiReranker({ url })`. The
66
+ * scores are the model's own, from 0 to 1.
67
+ */
68
+ export declare function teiReranker(options: TeiRerankerOptions): Reranker;
69
+ /**
70
+ * A cross-encoder run in process: one score per (query, passage) pair, higher meaning more relevant.
71
+ * A model from an ONNX runtime or a transformers library fits it in a few lines, so the package
72
+ * depends on neither.
73
+ */
74
+ export interface CrossEncoder {
75
+ /** Scores each pair, in order. */
76
+ score(pairs: ReadonlyArray<readonly [query: string, passage: string]>): Promise<readonly number[]> | readonly number[];
77
+ }
78
+ /** Options for `crossEncoderReranker()`. */
79
+ export interface CrossEncoderRerankerOptions {
80
+ /** Pairs scored in one call. Defaults to 32. */
81
+ batchSize?: number;
82
+ /** Characters of each chunk scored. Defaults to 2,000. */
83
+ maxChunkCharacters?: number;
84
+ /** Maps raw logits to 0–1 with a sigmoid, for a model that returns logits. Defaults to false. */
85
+ sigmoid?: boolean;
86
+ }
87
+ /**
88
+ * A reranker over a cross-encoder you run in process: no network call, no per-query price, and the
89
+ * passages never leave the machine. Pairs are scored in batches, and the run's signal is checked
90
+ * between batches.
91
+ */
92
+ export declare function crossEncoderReranker(model: CrossEncoder, options?: CrossEncoderRerankerOptions): Reranker;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RerankerError = void 0;
4
+ exports.httpReranker = httpReranker;
5
+ exports.cohereReranker = cohereReranker;
6
+ exports.voyageReranker = voyageReranker;
7
+ exports.jinaReranker = jinaReranker;
8
+ exports.teiReranker = teiReranker;
9
+ exports.crossEncoderReranker = crossEncoderReranker;
10
+ const signals_js_1 = require("../utils/signals.js");
11
+ class RerankerError extends Error {
12
+ status;
13
+ body;
14
+ constructor(message, status, body) {
15
+ super(message);
16
+ this.status = status;
17
+ this.body = body;
18
+ this.name = 'RerankerError';
19
+ }
20
+ }
21
+ exports.RerankerError = RerankerError;
22
+ function httpReranker(options) {
23
+ const fetchImpl = options.fetch ?? globalThis.fetch;
24
+ const limit = options.maxChunkCharacters ?? 4_000;
25
+ return async (query, chunks, signal) => {
26
+ if (chunks.length === 0)
27
+ return [];
28
+ const passages = chunks.map((chunk) => chunk.content.slice(0, limit));
29
+ const linked = (0, signals_js_1.linkSignals)([AbortSignal.timeout(options.timeoutMs ?? 30_000), signal]);
30
+ try {
31
+ const response = await fetchImpl(options.url, {
32
+ method: 'POST',
33
+ headers: { 'content-type': 'application/json', ...options.headers },
34
+ body: JSON.stringify(options.body(query, passages)),
35
+ signal: linked.signal,
36
+ });
37
+ const text = await response.text();
38
+ if (!response.ok)
39
+ throw new RerankerError(`The rerank API answered ${response.status}`, response.status, text);
40
+ const scores = new Array(chunks.length).fill(0);
41
+ for (const { index, score } of options.scores(JSON.parse(text))) {
42
+ if (Number.isInteger(index) && index >= 0 && index < scores.length)
43
+ scores[index] = Number(score);
44
+ }
45
+ return scores;
46
+ }
47
+ finally {
48
+ linked.dispose();
49
+ }
50
+ };
51
+ }
52
+ const bearer = (options) => ({ authorization: `Bearer ${options.apiKey}`, ...options.headers });
53
+ const readScored = (list) => (list ?? []).map((item) => ({ index: item.index, score: item.relevance_score }));
54
+ function cohereReranker(options) {
55
+ return httpReranker({
56
+ ...options,
57
+ url: options.url ?? 'https://api.cohere.com/v2/rerank',
58
+ headers: bearer(options),
59
+ body: (query, documents) => ({ model: options.model, query, documents, top_n: documents.length }),
60
+ scores: (response) => readScored(response.results),
61
+ });
62
+ }
63
+ function voyageReranker(options) {
64
+ return httpReranker({
65
+ ...options,
66
+ url: options.url ?? 'https://api.voyageai.com/v1/rerank',
67
+ headers: bearer(options),
68
+ body: (query, documents) => ({ model: options.model, query, documents, top_k: documents.length }),
69
+ scores: (response) => readScored(response.data),
70
+ });
71
+ }
72
+ function jinaReranker(options) {
73
+ return httpReranker({
74
+ ...options,
75
+ url: options.url ?? 'https://api.jina.ai/v1/rerank',
76
+ headers: bearer(options),
77
+ body: (query, documents) => ({ model: options.model, query, documents, top_n: documents.length }),
78
+ scores: (response) => readScored(response.results),
79
+ });
80
+ }
81
+ function teiReranker(options) {
82
+ return httpReranker({
83
+ ...options,
84
+ url: `${options.url.replace(/\/+$/, '')}/rerank`,
85
+ body: (query, texts) => ({ query, texts, raw_scores: false, truncate: true }),
86
+ scores: (response) => response,
87
+ });
88
+ }
89
+ function crossEncoderReranker(model, options = {}) {
90
+ const batchSize = Math.max(1, options.batchSize ?? 32);
91
+ const limit = options.maxChunkCharacters ?? 2_000;
92
+ return async (query, chunks, signal) => {
93
+ const pairs = chunks.map((chunk) => [query, chunk.content.slice(0, limit)]);
94
+ const scores = [];
95
+ for (let start = 0; start < pairs.length; start += batchSize) {
96
+ signal?.throwIfAborted();
97
+ const batch = pairs.slice(start, start + batchSize);
98
+ const result = await model.score(batch);
99
+ if (result.length !== batch.length) {
100
+ throw new RangeError(`The cross-encoder returned ${result.length} scores for ${batch.length} pairs`);
101
+ }
102
+ for (const raw of result)
103
+ scores.push(options.sigmoid ? 1 / (1 + Math.exp(-raw)) : raw);
104
+ }
105
+ return scores;
106
+ };
107
+ }
@@ -19,6 +19,17 @@ export interface Retriever {
19
19
  /** The chunks that best answer a query, best first. */
20
20
  retrieve(query: string, options?: RetrieveOptions): Promise<VectorSearchResult[]>;
21
21
  }
22
+ /**
23
+ * Keyword search that holds its own chunks: an in-memory BM25 `KeywordIndex`, Postgres full-text
24
+ * search, a search engine. It is a `Retriever`, so `hybridRetriever()` fuses it with vector search,
25
+ * and it takes writes, so ingestion keeps it in step with a vector store.
26
+ */
27
+ export interface SparseRetriever extends Retriever {
28
+ /** Adds chunks, or replaces those whose id exists. */
29
+ add(chunks: readonly RagChunk[]): Promise<void> | void;
30
+ /** Removes chunks by id. An id that is not held is ignored. */
31
+ delete(ids: readonly string[]): Promise<void> | void;
32
+ }
22
33
  /** A client that runs one completion — a `NexusAI` instance, or anything with the same `complete()`. */
23
34
  export interface RetrievalModelClient {
24
35
  /** Runs one completion and returns its text. */
@@ -47,7 +58,7 @@ export declare function tokenize(text: string): string[];
47
58
  * codes, error numbers, names — that keyword search finds; `hybridRetriever()` fuses the two.
48
59
  * Adding a chunk whose id exists replaces it, as in a vector store.
49
60
  */
50
- export declare class KeywordIndex implements Retriever {
61
+ export declare class KeywordIndex implements SparseRetriever {
51
62
  private readonly chunks;
52
63
  private readonly postings;
53
64
  private totalLength;
@@ -1,14 +1,37 @@
1
1
  import type { RouterContext, RouteDecision } from './types.js';
2
+ /**
3
+ * Ranks configured providers' models for a request routed as `auto`.
4
+ *
5
+ * Everything that depends only on configuration — which models are candidates, which pass the
6
+ * allow and deny lists and the capability requirements, and how each scores for the strategy — is
7
+ * computed once into a plan and kept until the configuration changes. A request pays only for what
8
+ * can differ between requests: which circuits are open, and provider health.
9
+ */
2
10
  export declare class AutoRouter {
11
+ private readonly plans;
12
+ private readonly patterns;
13
+ private readonly registryIds;
14
+ /** Routes one request: the best candidate first, every other as a fallback in order. */
3
15
  route(ctx: RouterContext): RouteDecision;
16
+ /** Plans cached by this router, for tests and diagnostics. */
17
+ get planCount(): number;
18
+ /** The plan for a request's configuration, built on first use and kept until the configuration changes. */
19
+ private plan;
4
20
  /**
5
- * Drops providers whose circuit is open.
6
- *
7
- * If that would leave nothing to route to, the original list is kept instead. Every circuit being
8
- * open usually means a shared dependency is down rather than every provider individually, and
9
- * attempting one call is strictly better than failing the request without trying anything.
21
+ * What a plan depends on, normalized into a string: the routing settings, the model settings, and
22
+ * the configured providers. A registry is identified by its object, so replacing
23
+ * `models.registry` builds a new plan; changing one in place does not.
10
24
  */
11
- private withoutOpenCircuits;
25
+ private planKey;
26
+ private registryId;
27
+ private buildPlan;
28
+ /**
29
+ * The plan's candidates in order for this request. Providers whose circuit is open are left out,
30
+ * unless that would leave nothing: every circuit open usually means a shared dependency is down,
31
+ * and one attempt is strictly better than failing without trying. Health moves a whole provider
32
+ * up or down, so the per-provider lists are merged by their adjusted scores rather than re-sorted.
33
+ */
34
+ private rank;
12
35
  private getCandidates;
13
36
  /**
14
37
  * Scores a model for a strategy. For privacy, a model counts as local when its name says so or when
@@ -16,15 +39,19 @@ export declare class AutoRouter {
16
39
  * is preferred without renaming its models.
17
40
  */
18
41
  private score;
42
+ private capabilitiesOf;
19
43
  private localFallbackCapabilities;
20
44
  private costScore;
21
45
  private speedScore;
22
46
  private qualityScore;
23
47
  private preferencesForProvider;
24
48
  private isAllowed;
49
+ /** Whether a model meets the routing requirements: the boolean ones as two bitmask tests, the rest by value. */
25
50
  private matchesRequirements;
51
+ /** Whether a model name matches an allow or deny entry, with `*` as a wildcard. Patterns are compiled once. */
26
52
  private matchesModelPattern;
27
53
  private isProviderRunnable;
28
- private healthScore;
54
+ /** Each provider's health adjustment for this request, or nothing when health does not apply. */
55
+ private healthOffsets;
29
56
  }
30
57
  export declare function routeDirectModel(model: string, ctx: RouterContext): RouteDecision;
@@ -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/') ||