nexus-ai-pro 1.18.0 → 1.20.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 (82) hide show
  1. package/API_STABILITY.md +24 -0
  2. package/CHANGELOG.md +105 -1
  3. package/README.md +15 -29
  4. package/dist/cli/db.js +12 -1
  5. package/dist/graph/checkpointer.d.ts +5 -0
  6. package/dist/graph/checkpointer.js +9 -0
  7. package/dist/hallucination/retrieval.d.ts +31 -2
  8. package/dist/hallucination/retrieval.js +31 -14
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/mcp/client.d.ts +6 -1
  12. package/dist/mcp/client.js +7 -0
  13. package/dist/mcp/server.js +4 -4
  14. package/dist/ops/health.js +2 -0
  15. package/dist/ops/metrics.js +2 -0
  16. package/dist/optimizer/index.js +2 -0
  17. package/dist/postgres/index.d.ts +7 -3
  18. package/dist/postgres/index.js +8 -0
  19. package/dist/postgres/vectors.d.ts +46 -0
  20. package/dist/postgres/vectors.js +100 -0
  21. package/dist/providers/google.js +2 -0
  22. package/dist/providers/ollama.js +2 -0
  23. package/dist/rag/ingestion.d.ts +2 -0
  24. package/dist/rag/ingestion.js +1 -0
  25. package/dist/rag/qdrant.d.ts +65 -0
  26. package/dist/rag/qdrant.js +126 -0
  27. package/dist/security/index.js +8 -3
  28. package/dist/security/input-guard.js +6 -1
  29. package/dist/security/pii-detector.d.ts +1 -1
  30. package/dist/security/pii-detector.js +9 -3
  31. package/dist/security/prompt-hardening.d.ts +3 -0
  32. package/dist/security/semantic-injection-classifier.d.ts +6 -0
  33. package/dist/security/semantic-injection-classifier.js +3 -1
  34. package/dist/tracing/instrument.js +2 -0
  35. package/dist/tracing/tracer.d.ts +4 -0
  36. package/dist/tracing/tracer.js +2 -0
  37. package/dist/types/graph.d.ts +2 -0
  38. package/dist/types/security.d.ts +74 -5
  39. package/dist/utils/deprecation.d.ts +12 -0
  40. package/dist/utils/deprecation.js +17 -0
  41. package/dist/workflow/chains.d.ts +3 -3
  42. package/dist/workflow/chains.js +18 -7
  43. package/dist-cjs/cli/db.js +12 -1
  44. package/dist-cjs/graph/checkpointer.d.ts +5 -0
  45. package/dist-cjs/graph/checkpointer.js +9 -0
  46. package/dist-cjs/hallucination/retrieval.d.ts +31 -2
  47. package/dist-cjs/hallucination/retrieval.js +32 -14
  48. package/dist-cjs/index.d.ts +1 -1
  49. package/dist-cjs/index.js +5 -3
  50. package/dist-cjs/mcp/client.d.ts +6 -1
  51. package/dist-cjs/mcp/client.js +7 -0
  52. package/dist-cjs/mcp/server.js +3 -3
  53. package/dist-cjs/ops/health.js +2 -0
  54. package/dist-cjs/ops/metrics.js +2 -0
  55. package/dist-cjs/optimizer/index.js +2 -0
  56. package/dist-cjs/postgres/index.d.ts +7 -3
  57. package/dist-cjs/postgres/index.js +11 -1
  58. package/dist-cjs/postgres/vectors.d.ts +46 -0
  59. package/dist-cjs/postgres/vectors.js +105 -0
  60. package/dist-cjs/providers/google.js +2 -0
  61. package/dist-cjs/providers/ollama.js +2 -0
  62. package/dist-cjs/rag/ingestion.d.ts +2 -0
  63. package/dist-cjs/rag/ingestion.js +7 -0
  64. package/dist-cjs/rag/qdrant.d.ts +65 -0
  65. package/dist-cjs/rag/qdrant.js +131 -0
  66. package/dist-cjs/security/index.js +8 -3
  67. package/dist-cjs/security/input-guard.js +6 -1
  68. package/dist-cjs/security/pii-detector.d.ts +1 -1
  69. package/dist-cjs/security/pii-detector.js +9 -3
  70. package/dist-cjs/security/prompt-hardening.d.ts +3 -0
  71. package/dist-cjs/security/semantic-injection-classifier.d.ts +6 -0
  72. package/dist-cjs/security/semantic-injection-classifier.js +3 -1
  73. package/dist-cjs/tracing/instrument.js +2 -0
  74. package/dist-cjs/tracing/tracer.d.ts +4 -0
  75. package/dist-cjs/tracing/tracer.js +2 -0
  76. package/dist-cjs/types/graph.d.ts +2 -0
  77. package/dist-cjs/types/security.d.ts +74 -5
  78. package/dist-cjs/utils/deprecation.d.ts +12 -0
  79. package/dist-cjs/utils/deprecation.js +21 -0
  80. package/dist-cjs/workflow/chains.d.ts +3 -3
  81. package/dist-cjs/workflow/chains.js +18 -7
  82. package/package.json +36 -5
package/API_STABILITY.md CHANGED
@@ -442,6 +442,30 @@ from the root.
442
442
  - Recovery re-runs a whole run rather than the step it died on, so an assistant with side effects
443
443
  should be idempotent or be left at a single attempt.
444
444
 
445
+ ## Studio stage (1.19.0)
446
+
447
+ `nexus-ai-pro-studio` is a separate package, experimental in production readiness. It is versioned
448
+ with nexus-ai-pro and declares the core versions it works with as a peer dependency.
449
+
450
+ - The `nexus-studio` command, its flags, and the shape of the config module's default export follow
451
+ the 1.x rules. A minor release may add sources and flags.
452
+ - `createStudio()`, `startStudio()`, and the exported types follow the 1.x rules.
453
+ - The JSON routes behind the page are for the page and may change in any release; build on the
454
+ stores themselves rather than on the studio's routes.
455
+ - The access rules are a guarantee: loopback binding by default, a token on every request, the token
456
+ in a header for every change, and loopback hosts only unless others are allowed.
457
+
458
+ ## Retrieval stores stage (1.20.0)
459
+
460
+ `PostgresVectorStore` (`nexus-ai-pro/postgres/vectors`) and `QdrantVectorStore` (`nexus-ai-pro/rag/qdrant`)
461
+ are experimental in production readiness. The `VectorStore` contract they share with
462
+ `MemoryVectorStore` follows the 1.x rules: a minor release may add optional methods and options, not
463
+ remove or reshape these.
464
+
465
+ - The pgvector table layout may gain columns in a minor release; `migrate()` adds what is missing.
466
+ - A Qdrant point id is derived from the chunk id and is stable across releases, so a collection
467
+ written by one version is read by the next.
468
+
445
469
  ## Deprecation process
446
470
 
447
471
  Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
package/CHANGELOG.md CHANGED
@@ -4,6 +4,108 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.20.0] - 2026-09-28
8
+
9
+ Retrieval that scales past one process. Deprecated options now say so at run time, and every guide
10
+ explains every export it lists.
11
+
12
+ ### Added
13
+
14
+ - **Vector stores for retrieval.** `VectorStore` is a shared contract — `add()`, which replaces a chunk
15
+ whose id already exists, `search()`, `searchVector()` for a vector computed elsewhere, `delete()`,
16
+ and an exact-match metadata `filter` — with three implementations checked by one contract test:
17
+ - `PostgresVectorStore` on `nexus-ai-pro/postgres/vectors`, ranking with pgvector in the database,
18
+ with an HNSW index by default or an exact scan. `vectorStoreMigration()` gives its schema, and
19
+ `postgresMigration()` and `nexus db sql` include it when `vectors` is named.
20
+ - `QdrantVectorStore` on `nexus-ai-pro/rag/qdrant`, through Qdrant's REST API with `fetch` and Web
21
+ Crypto, so no Qdrant client is a dependency and it runs on edge runtimes. `migrate()` creates the
22
+ collection and indexes filter fields; failures raise `QdrantError`.
23
+ - `MemoryVectorStore`, which gains `searchVector()`, `delete()`, and the filter.
24
+ - Retrieval is reachable from `nexus-ai-pro/rag` — `MemoryVectorStore`, `createHashEmbeddings()`,
25
+ `cosineSimilarity()`, `normalizeVector()`, `matchesMetadata()`, and the types — so an application
26
+ that ingests and searches no longer loads the root import.
27
+ - **Deprecated options warn once per process** through Node's `DeprecationWarning` channel, with a
28
+ `NEXUS_DEP_*` code each, so `--no-deprecation` silences them and `--throw-deprecation` fails a CI
29
+ job on them: `GoogleProviderConfig.projectId`, `OllamaProviderConfig.timeout`,
30
+ `MetricsConfig.prometheus`, `HealthConfig.latencyHalfLife`, `DensificationConfig.preserveMarkdown`,
31
+ `InjectionDetectionConfig.sensitivity`, and `ToolPolicyConfig.requiresApproval`.
32
+
33
+ ### Changed
34
+
35
+ - `MemoryVectorStore.add()` replaces a chunk whose id already exists instead of storing a second
36
+ copy, so re-ingesting a document no longer duplicates its passages in search results.
37
+ - The guide check enforces that every export is explained in a guide's own text, not only named in
38
+ its generated reference; the five remaining guides — operations, embeddings, evaluation, images,
39
+ and realtime — now are. The README's limitations and production notes moved into the guides they
40
+ belong to.
41
+
42
+ ## [1.19.0] - 2026-09-28
43
+
44
+ A studio to look at it all. `nexus-ai-pro-studio` is a separate package — so the core install never
45
+ carries a UI — that shows what an application records, from its own stores, on the developer's
46
+ machine, with no hosted service.
47
+
48
+ ### Added
49
+
50
+ - **`nexus-ai-pro-studio`**, experimental, with the `nexus-studio` command. Given a config module
51
+ whose default export lists the application's stores, it serves eight views:
52
+ - **Traces**: filtered run lists, run trees with inputs, outputs, cost, and latency, trace
53
+ comparison, and feedback on a run.
54
+ - **Threads**: each graph's diagram with the next nodes highlighted, state at any step, and fork,
55
+ edit, and resume.
56
+ - **Inbox**: every thread waiting for a human and every review item waiting for a grade.
57
+ - **Experiments**: experiment and dataset lists, and comparisons with per-metric intervals and a
58
+ verdict.
59
+ - **Prompts**: versions, labels, history, diffs, gated promotion, rollback, and a playground.
60
+ - **Costs**: cost per day and per model, the most expensive runs, and budgets.
61
+ - **Health**: provider health, local and shared circuit state, metrics, and cache statistics.
62
+ - **Operations**: the operation queue by status, and asset store totals.
63
+ - **Locked down for a local tool.** It binds `127.0.0.1`, requires a token that moves from the URL
64
+ into an HTTP-only same-site cookie, requires the token in a header for every change, refuses
65
+ non-loopback `Host` headers against DNS rebinding, and serves every page under a strict content
66
+ security policy with every value inserted as text.
67
+ - `startStudio()` and `createStudio()` start it from code or mount it in an existing server.
68
+ - `npm run studio:demo` starts it on a demo application with something in every view.
69
+ - The operation-store checkpointer — the one behind Redis and Postgres — lists its threads through
70
+ `threadIds()`, as the in-memory one already did, and `GraphCheckpointer` declares it as optional.
71
+
72
+ ### Fixed
73
+
74
+ - **Secret detection on input now detects under every action.** It only scanned when the action
75
+ was `mask`. Under the default action, `block`, and under `flag`, a request carrying a private
76
+ key, a cloud or payment key, a token, or a secret assignment went out with no finding. Such a
77
+ request is now blocked by default, at every level but `off`. Set `input.secrets.action` to `flag`
78
+ to record secrets without blocking.
79
+ - The PII action `remove` deletes the matched data. It was accepted but behaved like `flag`, so the
80
+ data stayed in the request.
81
+ - The semantic injection check no longer re-embeds its example attacks on every request.
82
+ - `McpServer` answers an unknown method with the JSON-RPC method-not-found code, and an unknown
83
+ resource with invalid-params, instead of an internal error, so a client can tell an unsupported
84
+ feature from a failure.
85
+ - `McpClient` requests made together before the handshake finished each sent `initialize`; they now
86
+ share one handshake.
87
+ - `summarizeVerifyFormat()`'s verify step sent the summary to the model with no instruction, so the
88
+ model answered the summary instead of restating it, and that reply was what got checked against
89
+ the sources. It now asks for a restatement against the sources. The final format is applied only in
90
+ the last step, and a verify step that was skipped is no longer recorded in `steps`.
91
+ - `traceModelClient()` records the model and provider that answered on the run itself. It recorded
92
+ the model the request asked for — often `auto` — and left `provider` empty, so a `provider` query
93
+ never matched a model run and per-model views showed `auto`. `FinishRunOptions` gains `model` and
94
+ `provider` for the same purpose in your own instrumentation.
95
+
96
+ ### Deprecated
97
+
98
+ - `InjectionDetectionConfig.sensitivity` and `ToolPolicyConfig.requiresApproval` have never been
99
+ read. Both will be removed in 2.0; approval before a tool runs belongs to the agent loop.
100
+
101
+ ### Changed
102
+
103
+ - Nineteen guides under `docs/` — agents, batch, caching, core, graphs, grounding, MCP, memory,
104
+ Postgres, prompts, providers, resilience, security, server, telephony, testing, tracing, voice, and
105
+ workflows — now explain each export they list in their text, not only in the generated reference.
106
+ The security configuration's nested settings have doc comments.
107
+ - The documentation coverage check also covers the studio package's exports.
108
+
7
109
  ## [1.18.0] - 2026-09-25
8
110
 
9
111
  A server you can host. Assistants get an HTTP surface — threads, background runs, resumable event
@@ -941,7 +1043,9 @@ shared policy vocabulary is in place for a future release that revisits this.
941
1043
  - URL fetching rejects unsafe private-network targets and limits response reads.
942
1044
  - CLI and audit findings no longer disclose detected secret values.
943
1045
 
944
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.18.0...HEAD
1046
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.20.0...HEAD
1047
+ [1.20.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.19.0...v1.20.0
1048
+ [1.19.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.18.0...v1.19.0
945
1049
  [1.18.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.17.0...v1.18.0
946
1050
  [1.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.16.0...v1.17.0
947
1051
  [1.16.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.15.0...v1.16.0
package/README.md CHANGED
@@ -125,6 +125,7 @@ from the doc comments. The guides live in the repository, so these links go to G
125
125
  | [Realtime voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/realtime.md) | Browser and server realtime sessions with barge-in, tools, and exports |
126
126
  | [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
127
127
  | [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
128
+ | [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A local UI for traces, threads, approvals, experiments, prompts, costs, and health, as a separate package |
128
129
  | [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron |
129
130
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
130
131
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
@@ -196,37 +197,22 @@ Shipped so far:
196
197
  - queryable traces, an evaluation platform, and a CI gate on top of it;
197
198
  - record and replay of provider traffic;
198
199
  - prompt versioning: typed templates, content versions, gated promotion, and serving through outages;
200
+ - a self-hosted agent server, and a local studio as a separate package;
199
201
  - per-entry-point size budgets.
200
202
 
201
- Next is a self-hosted agent server. The image family leaves experimental once recorded live
202
- conformance passes on all three backends.
203
-
204
- ## Known Limitations
205
-
206
- - Voice, telephony, images, local models, and custom providers are optional layers; enable only what
207
- you use.
208
- - Audio and video preprocessing is limited, and provider modality support differs.
209
- - Bundled model metadata is a routing and estimation convenience, not a pricing contract.
210
- - Process-memory cache, queues, and asset storage are not enough for a distributed deployment; use
211
- the Redis, BullMQ, filesystem, or S3 adapters instead.
212
- - Default hash embeddings suit tests and demos, not strong semantic search. Register a real
213
- embedding provider for production retrieval.
214
- - NLI verification is an interface; bring a specialized verifier for high-confidence entailment.
215
- - Guardrails reduce risk but do not replace application authorization, provider-side moderation, or
216
- human review of high-impact actions.
217
- - Realtime sessions are not yet routed through the metrics, audit, and rate-limit path that every
218
- other family uses.
219
- - The image adapters are verified against documented wire shapes; recorded live conformance for all
220
- three backends is still to come, which is why the family is experimental.
221
-
222
- ## Production Notes
223
-
224
- - Use direct or rules-based routing when model choice is already known.
225
- - Keep semantic cache, semantic security, and custom hooks off latency-sensitive routes unless needed.
226
- - Disable response traces with `pipeline.includeTraceInResponse = false`.
227
- - Use shared adapters for distributed apps instead of process memory.
228
- - Treat bundled model pricing and context metadata as defaults, not financial truth.
229
- - Keep server-side authorization and provider moderation around high-risk workflows.
203
+ Next: durable functional workflows and step-level recovery, then retrieval and integration breadth,
204
+ a shared team studio, and self-managed deployment at scale, before 2.0.0 consolidates. The image
205
+ family leaves experimental once recorded live conformance passes on all three backends.
206
+
207
+ ## Before Production
208
+
209
+ Each guide ends with its limitations. The ones worth reading first: the
210
+ [client guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/core.md#in-production) on latency, traces,
211
+ and shared adapters; the [security guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/security.md#limitations)
212
+ on what guardrails do not replace; and the
213
+ [grounding guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/grounding.md#limitations) on embeddings
214
+ and verification. Voice, telephony, images, realtime, local models, and custom providers are optional
215
+ layers; enable only what you use.
230
216
 
231
217
  ## Donations
232
218
 
package/dist/cli/db.js CHANGED
@@ -1,6 +1,14 @@
1
1
  import { postgresMigration } from '../postgres/index.js';
2
2
  import { CliUsageError, listFlag, numberFlag } from './args.js';
3
- const ADAPTERS = ['operations', 'store', 'traces', 'evaluation', 'circuits', 'prompts'];
3
+ const ADAPTERS = [
4
+ 'operations',
5
+ 'store',
6
+ 'traces',
7
+ 'evaluation',
8
+ 'circuits',
9
+ 'prompts',
10
+ 'vectors',
11
+ ];
4
12
  export async function runDbCommand(subcommand, { flags }) {
5
13
  if (subcommand !== 'sql')
6
14
  throw new CliUsageError(`Unknown db command "${subcommand ?? ''}". Use sql.`);
@@ -10,6 +18,9 @@ export async function runDbCommand(subcommand, { flags }) {
10
18
  throw new CliUsageError(`Unknown adapter ${unknown.join(', ')}. Choose from ${ADAPTERS.join(', ')}.`);
11
19
  }
12
20
  const vectorDimensions = numberFlag(flags, 'vector-dimensions');
21
+ if (adapters?.includes('vectors') && vectorDimensions === undefined) {
22
+ throw new CliUsageError('The vectors adapter needs --vector-dimensions.');
23
+ }
13
24
  process.stdout.write(postgresMigration({
14
25
  ...(adapters ? { adapters } : {}),
15
26
  ...(vectorDimensions === undefined ? {} : { vectorDimensions }),
@@ -66,6 +66,11 @@ export declare class OperationStoreCheckpointer implements GraphCheckpointer {
66
66
  history(threadId: string, limit?: number): Promise<GraphCheckpoint[]>;
67
67
  /** Deletes a thread's checkpoints and its head. */
68
68
  delete(threadId: string): Promise<void>;
69
+ /**
70
+ * Every thread with a checkpoint, read from the head records. Empty when the store cannot list,
71
+ * since finding heads any other way would mean scanning every key.
72
+ */
73
+ threadIds(): Promise<string[]>;
69
74
  private readHead;
70
75
  /**
71
76
  * Writes one record, creating it when absent and otherwise updating in place.
@@ -86,6 +86,15 @@ export class OperationStoreCheckpointer {
86
86
  await this.store.delete?.(stepId(threadId, step));
87
87
  await this.store.delete?.(threadId);
88
88
  }
89
+ async threadIds() {
90
+ if (!this.store.list)
91
+ return [];
92
+ const records = await this.store.list();
93
+ return records
94
+ .filter((record) => record.result !== undefined && isHead(record.result))
95
+ .map((record) => record.id)
96
+ .sort();
97
+ }
89
98
  async readHead(threadId) {
90
99
  const record = await this.store.read(threadId);
91
100
  return record?.result && isHead(record.result) ? record.result : undefined;
@@ -17,24 +17,52 @@ export interface VectorSearchOptions {
17
17
  topK?: number;
18
18
  /** Lowest similarity returned. Defaults to 0. */
19
19
  minScore?: number;
20
+ /**
21
+ * Exact matches on top-level `metadata` fields, such as `{ tenant: 'acme' }`. Every field must
22
+ * match. Values are strings, numbers, or booleans, which every adapter can filter on natively.
23
+ */
24
+ filter?: Record<string, string | number | boolean>;
25
+ }
26
+ /**
27
+ * Where retrieval chunks live and how they are searched. `MemoryVectorStore`, `PostgresVectorStore`,
28
+ * and `QdrantVectorStore` implement it, and pass the same contract tests.
29
+ *
30
+ * Adding a chunk whose id already exists replaces it, so re-ingesting a document never duplicates
31
+ * its passages.
32
+ */
33
+ export interface VectorStore {
34
+ /** Adds chunks, or replaces the ones whose id already exists, embedding those without a vector. */
35
+ add(documents: VectorDocument[]): Promise<void>;
36
+ /** The chunks most similar to a query text, best first. */
37
+ search(query: string, options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
38
+ /** The chunks most similar to a vector computed elsewhere, best first. */
39
+ searchVector(vector: number[], options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
40
+ /** Removes chunks by id. An id that is not stored is ignored. */
41
+ delete(ids: readonly string[]): Promise<void>;
20
42
  }
21
43
  /**
22
44
  * Chunks and their vectors in process memory, searched by cosine similarity. Defaults to hashed
23
45
  * term vectors, which need no provider.
24
46
  */
25
- export declare class MemoryVectorStore {
47
+ export declare class MemoryVectorStore implements VectorStore {
26
48
  private documents;
27
49
  private embed;
28
50
  constructor(embed?: EmbeddingProvider);
29
- /** Adds chunks, embedding those without a vector in one batch. */
51
+ /** Adds chunks, or replaces those whose id exists, embedding those without a vector in one batch. */
30
52
  add(documents: VectorDocument[]): Promise<void>;
31
53
  /** The chunks most similar to a query, best first. */
32
54
  search(query: string, options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
55
+ /** The chunks most similar to a vector, best first. */
56
+ searchVector(vector: number[], options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
57
+ /** Removes chunks by id. */
58
+ delete(ids: readonly string[]): Promise<void>;
33
59
  /** Removes every chunk. */
34
60
  clear(): void;
35
61
  /** Chunks held. */
36
62
  size(): number;
37
63
  }
64
+ /** Whether a chunk's metadata satisfies an exact-match filter. Every adapter filters the same way. */
65
+ export declare function matchesMetadata(metadata: Record<string, unknown> | undefined, filter: VectorSearchOptions['filter']): boolean;
38
66
  /**
39
67
  * Hashed term-count vectors, normalized to unit length. Deterministic and free, for tests and a
40
68
  * store without a provider.
@@ -42,4 +70,5 @@ export declare class MemoryVectorStore {
42
70
  export declare function createHashEmbeddings(texts: string[], dimensions?: number): number[][];
43
71
  /** Cosine similarity of two unit-length vectors, as their dot product. */
44
72
  export declare function cosineSimilarity(a: number[], b: number[]): number;
73
+ /** Scales a vector to unit length, so a dot product is its cosine similarity. A zero vector stays zero. */
45
74
  export declare function normalizeVector(vector: number[]): number[];
@@ -1,5 +1,5 @@
1
1
  export class MemoryVectorStore {
2
- documents = [];
2
+ documents = new Map();
3
3
  embed;
4
4
  constructor(embed = createHashEmbeddings) {
5
5
  this.embed = embed;
@@ -10,29 +10,46 @@ export class MemoryVectorStore {
10
10
  let generatedIndex = 0;
11
11
  for (const doc of documents) {
12
12
  const embedding = doc.embedding || generated[generatedIndex++];
13
- this.documents.push({ ...doc, embedding: normalizeVector(embedding) });
13
+ this.documents.set(doc.id, { ...doc, embedding: normalizeVector(embedding) });
14
14
  }
15
15
  }
16
16
  async search(query, options = {}) {
17
+ const [queryEmbedding] = await this.embed([query]);
18
+ return this.searchVector(queryEmbedding, options);
19
+ }
20
+ async searchVector(vector, options = {}) {
17
21
  const topK = options.topK || 5;
18
22
  const minScore = options.minScore ?? 0;
19
- const [queryEmbedding] = await this.embed([query]);
20
- const normalizedQuery = normalizeVector(queryEmbedding);
21
- return this.documents
22
- .map(({ embedding, ...doc }) => ({
23
- ...doc,
24
- score: cosineSimilarity(normalizedQuery, embedding),
25
- }))
26
- .filter((doc) => doc.score >= minScore)
27
- .sort((a, b) => b.score - a.score)
28
- .slice(0, topK);
23
+ const normalizedQuery = normalizeVector(vector);
24
+ const results = [];
25
+ for (const { embedding, ...doc } of this.documents.values()) {
26
+ if (!matchesMetadata(doc.metadata, options.filter))
27
+ continue;
28
+ const score = cosineSimilarity(normalizedQuery, embedding);
29
+ if (score >= minScore)
30
+ results.push({ ...doc, score });
31
+ }
32
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
33
+ }
34
+ async delete(ids) {
35
+ for (const id of ids)
36
+ this.documents.delete(id);
29
37
  }
30
38
  clear() {
31
- this.documents = [];
39
+ this.documents.clear();
32
40
  }
33
41
  size() {
34
- return this.documents.length;
42
+ return this.documents.size;
43
+ }
44
+ }
45
+ export function matchesMetadata(metadata, filter) {
46
+ if (!filter)
47
+ return true;
48
+ for (const [key, expected] of Object.entries(filter)) {
49
+ if (metadata?.[key] !== expected)
50
+ return false;
35
51
  }
52
+ return true;
36
53
  }
37
54
  export function createHashEmbeddings(texts, dimensions = 384) {
38
55
  return texts.map((text) => {
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export { PipelineRunner, createPipelineContext, } from './pipeline/pipeline.js';
38
38
  export type { PipelineConfig, PipelineContext, PipelineHookName, PipelineHooksConfig, PipelineMiddleware, PipelineStep, PipelineStepName, PipelineTrace, PipelineTraceStep, } from './pipeline/types.js';
39
39
  export { withFactualDefaults, asJsonOnly, type FactualOptions, } from './hallucination/factual.js';
40
40
  export { withRagContext, extractCitations, validateCitations, type RagChunk, type RagOptions, } from './hallucination/rag.js';
41
- export { MemoryVectorStore, createHashEmbeddings, cosineSimilarity, type EmbeddingProvider, type VectorDocument, type VectorSearchOptions, type VectorSearchResult, } from './hallucination/retrieval.js';
41
+ export { MemoryVectorStore, createHashEmbeddings, cosineSimilarity, matchesMetadata, normalizeVector, type EmbeddingProvider, type VectorDocument, type VectorSearchOptions, type VectorSearchResult, type VectorStore, } from './hallucination/retrieval.js';
42
42
  export { withKnowledgeGraphContext, selectGraphFacts, type KnowledgeGraph, type KnowledgeGraphEdge, type KnowledgeGraphNode, type KnowledgeGraphOptions, } from './hallucination/knowledge-graph.js';
43
43
  export { completeVerified, verifyAgainstContext, extractFacts, lexicalEntailment, type NliVerifier, type VerificationClient, type VerificationFact, type VerificationOptions, type VerificationReport, } from './hallucination/verification.js';
44
44
  export { completeWithSelfConsistency, selectMostConsistent, textSimilarity, type ConsistencyClient, type SelfConsistencyOptions, } from './hallucination/consistency.js';
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ export { MemoryRateLimitStore, RedisRateLimitStore, } from './ops/rate-limit-ada
37
37
  export { PipelineRunner, createPipelineContext, } from './pipeline/pipeline.js';
38
38
  export { withFactualDefaults, asJsonOnly, } from './hallucination/factual.js';
39
39
  export { withRagContext, extractCitations, validateCitations, } from './hallucination/rag.js';
40
- export { MemoryVectorStore, createHashEmbeddings, cosineSimilarity, } from './hallucination/retrieval.js';
40
+ export { MemoryVectorStore, createHashEmbeddings, cosineSimilarity, matchesMetadata, normalizeVector, } from './hallucination/retrieval.js';
41
41
  export { withKnowledgeGraphContext, selectGraphFacts, } from './hallucination/knowledge-graph.js';
42
42
  export { completeVerified, verifyAgainstContext, extractFacts, lexicalEntailment, } from './hallucination/verification.js';
43
43
  export { completeWithSelfConsistency, selectMostConsistent, textSimilarity, } from './hallucination/consistency.js';
@@ -62,13 +62,18 @@ export declare class McpClient {
62
62
  private readonly pending;
63
63
  private nextId;
64
64
  private initialized;
65
+ private connecting;
65
66
  private serverInfo;
66
67
  constructor(transport: McpTransport, options?: McpClientOptions);
67
- /** Performs the handshake. Called automatically by the first request that needs it. */
68
+ /**
69
+ * Performs the handshake and returns the server's name and version. Called automatically by the
70
+ * first request that needs it; requests made together share one handshake.
71
+ */
68
72
  connect(): Promise<{
69
73
  name?: string;
70
74
  version?: string;
71
75
  }>;
76
+ private handshake;
72
77
  /** Lists the server's tools. */
73
78
  listTools(): Promise<McpToolDescriptor[]>;
74
79
  /**
@@ -5,6 +5,7 @@ export class McpClient {
5
5
  pending = new Map();
6
6
  nextId = 1;
7
7
  initialized = false;
8
+ connecting;
8
9
  serverInfo = {};
9
10
  constructor(transport, options = {}) {
10
11
  this.transport = transport;
@@ -15,6 +16,12 @@ export class McpClient {
15
16
  async connect() {
16
17
  if (this.initialized)
17
18
  return this.serverInfo;
19
+ this.connecting ??= this.handshake().finally(() => {
20
+ this.connecting = undefined;
21
+ });
22
+ return this.connecting;
23
+ }
24
+ async handshake() {
18
25
  await this.transport.start?.();
19
26
  const result = (await this.request('initialize', {
20
27
  protocolVersion: MCP_PROTOCOL_VERSION,
@@ -1,4 +1,4 @@
1
- import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, textContent, } from './protocol.js';
1
+ import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, McpError, textContent, } from './protocol.js';
2
2
  export class McpServer {
3
3
  options;
4
4
  tools = new Map();
@@ -35,7 +35,7 @@ export class McpServer {
35
35
  jsonrpc: '2.0',
36
36
  id: message.id,
37
37
  error: {
38
- code: JSON_RPC_ERRORS.internal,
38
+ code: error instanceof McpError ? error.code : JSON_RPC_ERRORS.internal,
39
39
  message: error instanceof Error ? error.message : String(error),
40
40
  },
41
41
  });
@@ -89,7 +89,7 @@ export class McpServer {
89
89
  const uri = String(params.uri ?? '');
90
90
  const resource = this.options.resources?.find((item) => item.uri === uri);
91
91
  if (!resource)
92
- throw new Error(`Unknown resource "${uri}"`);
92
+ throw new McpError(`Unknown resource "${uri}"`, JSON_RPC_ERRORS.invalidParams);
93
93
  const value = await resource.read();
94
94
  return {
95
95
  contents: [
@@ -102,7 +102,7 @@ export class McpServer {
102
102
  };
103
103
  }
104
104
  default:
105
- throw new Error(`Unknown method "${request.method}"`);
105
+ throw new McpError(`Unknown method "${request.method}"`, JSON_RPC_ERRORS.methodNotFound);
106
106
  }
107
107
  }
108
108
  }
@@ -1,8 +1,10 @@
1
+ import { warnUnreadOption } from '../utils/deprecation.js';
1
2
  export class ProviderHealthMonitor {
2
3
  config;
3
4
  states = new Map();
4
5
  constructor(config = {}) {
5
6
  this.config = config;
7
+ warnUnreadOption(config.latencyHalfLife !== undefined, 'NEXUS_DEP_HEALTH_LATENCY_HALF_LIFE', 'HealthConfig.latencyHalfLife');
6
8
  }
7
9
  recordSuccess(providerName, latencyMs) {
8
10
  if (!this.config.enabled)
@@ -1,3 +1,4 @@
1
+ import { warnUnreadOption } from '../utils/deprecation.js';
1
2
  export class InMemoryMetrics {
2
3
  counters = new Map();
3
4
  histograms = new Map();
@@ -86,6 +87,7 @@ export class MetricsCollector {
86
87
  this.config = config;
87
88
  this.sink = config.sink || this.memory;
88
89
  this.prefix = config.prefix || 'nexus_ai';
90
+ warnUnreadOption(config.prometheus !== undefined, 'NEXUS_DEP_METRICS_PROMETHEUS', 'MetricsConfig.prometheus');
89
91
  }
90
92
  async recordRequest(labels) {
91
93
  if (!this.config.enabled)
@@ -1,6 +1,7 @@
1
1
  import { Tokenizer } from '../utils/tokenizer.js';
2
2
  import { PromptDensifier } from './densifier.js';
3
3
  import { BudgetEnforcer } from './budget.js';
4
+ import { warnUnreadOption } from '../utils/deprecation.js';
4
5
  export class TokenOptimizer {
5
6
  config;
6
7
  tokenizer = new Tokenizer();
@@ -8,6 +9,7 @@ export class TokenOptimizer {
8
9
  budget = new BudgetEnforcer(this.tokenizer);
9
10
  constructor(config = {}) {
10
11
  this.config = config;
12
+ warnUnreadOption(config.densification?.preserveMarkdown !== undefined, 'NEXUS_DEP_DENSIFICATION_PRESERVE_MARKDOWN', 'DensificationConfig.preserveMarkdown');
11
13
  }
12
14
  optimize(request) {
13
15
  if (this.config.enabled === false) {
@@ -5,13 +5,17 @@ export { operationStoreMigration, PostgresOperationStore, type PostgresOperation
5
5
  export { PostgresPromptStore, type PostgresPromptStoreOptions, promptStoreMigration } from './prompts.js';
6
6
  export { PostgresStore, type PostgresStoreOptions, storeMigration } from './store.js';
7
7
  export { PostgresTraceStore, type PostgresTraceStoreOptions, traceStoreMigration } from './traces.js';
8
+ export { PostgresVectorStore, type PostgresVectorStoreOptions, vectorStoreMigration } from './vectors.js';
8
9
  /** The Postgres adapters a migration can include. */
9
- export type PostgresAdapter = 'operations' | 'store' | 'traces' | 'evaluation' | 'circuits' | 'prompts';
10
+ export type PostgresAdapter = 'operations' | 'store' | 'traces' | 'evaluation' | 'circuits' | 'prompts' | 'vectors';
10
11
  /** Options for `postgresMigration()`. */
11
12
  export interface PostgresMigrationOptions {
12
- /** Adapters to include. Defaults to all of them. */
13
+ /**
14
+ * Adapters to include. Defaults to every one but `vectors`, which needs pgvector and
15
+ * `vectorDimensions`, so it is included only when named.
16
+ */
13
17
  adapters?: readonly PostgresAdapter[];
14
- /** Enables pgvector for the long-term store. */
18
+ /** Enables pgvector for the long-term store, and sets the width of the `vectors` table. */
15
19
  vectorDimensions?: number;
16
20
  }
17
21
  /**
@@ -4,6 +4,7 @@ import { operationStoreMigration } from './operations.js';
4
4
  import { promptStoreMigration } from './prompts.js';
5
5
  import { storeMigration } from './store.js';
6
6
  import { traceStoreMigration } from './traces.js';
7
+ import { vectorStoreMigration } from './vectors.js';
7
8
  export { circuitStoreMigration, PostgresCircuitStateStore, } from './circuits.js';
8
9
  export { fromPostgresJs } from './client.js';
9
10
  export { evaluationStoreMigration, PostgresDatasetStore, PostgresExperimentStore, } from './evaluate.js';
@@ -11,6 +12,7 @@ export { operationStoreMigration, PostgresOperationStore } from './operations.js
11
12
  export { PostgresPromptStore, promptStoreMigration } from './prompts.js';
12
13
  export { PostgresStore, storeMigration } from './store.js';
13
14
  export { PostgresTraceStore, traceStoreMigration } from './traces.js';
15
+ export { PostgresVectorStore, vectorStoreMigration } from './vectors.js';
14
16
  export function postgresMigration(options = {}) {
15
17
  const adapters = new Set(options.adapters ?? ['operations', 'store', 'traces', 'evaluation', 'circuits', 'prompts']);
16
18
  const statements = [
@@ -22,6 +24,12 @@ export function postgresMigration(options = {}) {
22
24
  ...(adapters.has('evaluation') ? evaluationStoreMigration() : []),
23
25
  ...(adapters.has('circuits') ? circuitStoreMigration() : []),
24
26
  ...(adapters.has('prompts') ? promptStoreMigration() : []),
27
+ ...(adapters.has('vectors') ? vectorMigrationFor(options.vectorDimensions) : []),
25
28
  ];
26
29
  return `${statements.join(';\n\n')};\n`;
27
30
  }
31
+ function vectorMigrationFor(dimensions) {
32
+ if (dimensions === undefined)
33
+ throw new RangeError('The vectors adapter needs vectorDimensions');
34
+ return vectorStoreMigration({ dimensions });
35
+ }
@@ -0,0 +1,46 @@
1
+ import { type EmbeddingProvider, type VectorDocument, type VectorSearchOptions, type VectorSearchResult, type VectorStore } from '../hallucination/retrieval.js';
2
+ import { type PostgresLikeClient } from './client.js';
3
+ /** Options for the pgvector store. */
4
+ export interface PostgresVectorStoreOptions {
5
+ /** Width of every vector. Must match the embedding function's output. */
6
+ dimensions: number;
7
+ /** Embeds chunks that arrive without a vector, and search queries. Defaults to hashed term vectors. */
8
+ embed?: EmbeddingProvider;
9
+ /** Table name, optionally schema-qualified. Defaults to `nexus_vectors`. */
10
+ table?: string;
11
+ /**
12
+ * The approximate index created by `migrate()`. `hnsw` (the default) answers a search without
13
+ * scanning every row, at the cost of slower writes; `none` scans exactly, which suits a few
14
+ * thousand chunks.
15
+ */
16
+ index?: 'hnsw' | 'none';
17
+ }
18
+ /** The schema, as statements: the pgvector extension, the table, and its index. */
19
+ export declare function vectorStoreMigration(options: Omit<PostgresVectorStoreOptions, 'embed'>): string[];
20
+ /**
21
+ * Retrieval chunks in Postgres with pgvector, ranked by cosine similarity in the database.
22
+ *
23
+ * The same contract as `MemoryVectorStore`, checked by the same tests. Only the nearest chunks leave
24
+ * the database, so a search costs the same whether the table holds a thousand chunks or ten million.
25
+ * Adding a chunk whose id exists replaces it, and a metadata filter runs in SQL.
26
+ */
27
+ export declare class PostgresVectorStore implements VectorStore {
28
+ private readonly client;
29
+ private readonly options;
30
+ private readonly table;
31
+ private readonly embed;
32
+ constructor(client: PostgresLikeClient, options: PostgresVectorStoreOptions);
33
+ /** Creates the extension, table, and index. Never runs implicitly. */
34
+ migrate(): Promise<void>;
35
+ /** Adds chunks, or replaces those whose id exists, embedding those without a vector in one batch. */
36
+ add(documents: VectorDocument[]): Promise<void>;
37
+ /** The chunks most similar to a query, best first. */
38
+ search(query: string, options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
39
+ /** The chunks most similar to a vector, best first. */
40
+ searchVector(vector: number[], options?: VectorSearchOptions): Promise<VectorSearchResult[]>;
41
+ /** Removes chunks by id. */
42
+ delete(ids: readonly string[]): Promise<void>;
43
+ /** Chunks stored. */
44
+ size(): Promise<number>;
45
+ private assertWidth;
46
+ }