@juspay/neurolink 9.88.12 → 9.90.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 (50) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +4 -3
  3. package/dist/adapters/tts/googleTTSHandler.d.ts +10 -0
  4. package/dist/adapters/tts/googleTTSHandler.js +27 -18
  5. package/dist/agent/directTools.js +1 -1
  6. package/dist/browser/neurolink.min.js +370 -356
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/lib/adapters/tts/googleTTSHandler.d.ts +10 -0
  10. package/dist/lib/adapters/tts/googleTTSHandler.js +27 -18
  11. package/dist/lib/agent/directTools.js +1 -1
  12. package/dist/lib/index.d.ts +1 -1
  13. package/dist/lib/index.js +1 -1
  14. package/dist/lib/neurolink.js +1 -1
  15. package/dist/lib/rag/index.d.ts +1 -0
  16. package/dist/lib/rag/index.js +2 -0
  17. package/dist/lib/rag/stores/chroma.d.ts +90 -0
  18. package/dist/lib/rag/stores/chroma.js +281 -0
  19. package/dist/lib/rag/stores/index.d.ts +21 -0
  20. package/dist/lib/rag/stores/index.js +22 -0
  21. package/dist/lib/rag/stores/pgvector.d.ts +95 -0
  22. package/dist/lib/rag/stores/pgvector.js +400 -0
  23. package/dist/lib/rag/stores/pinecone.d.ts +85 -0
  24. package/dist/lib/rag/stores/pinecone.js +159 -0
  25. package/dist/lib/types/index.d.ts +2 -0
  26. package/dist/lib/types/index.js +2 -0
  27. package/dist/lib/types/rag.d.ts +30 -0
  28. package/dist/lib/types/vectorStoreChroma.d.ts +67 -0
  29. package/dist/lib/types/vectorStoreChroma.js +12 -0
  30. package/dist/lib/types/vectorStorePinecone.d.ts +48 -0
  31. package/dist/lib/types/vectorStorePinecone.js +12 -0
  32. package/dist/neurolink.js +1 -1
  33. package/dist/rag/index.d.ts +1 -0
  34. package/dist/rag/index.js +2 -0
  35. package/dist/rag/stores/chroma.d.ts +90 -0
  36. package/dist/rag/stores/chroma.js +280 -0
  37. package/dist/rag/stores/index.d.ts +21 -0
  38. package/dist/rag/stores/index.js +21 -0
  39. package/dist/rag/stores/pgvector.d.ts +95 -0
  40. package/dist/rag/stores/pgvector.js +399 -0
  41. package/dist/rag/stores/pinecone.d.ts +85 -0
  42. package/dist/rag/stores/pinecone.js +158 -0
  43. package/dist/types/index.d.ts +2 -0
  44. package/dist/types/index.js +2 -0
  45. package/dist/types/rag.d.ts +30 -0
  46. package/dist/types/vectorStoreChroma.d.ts +67 -0
  47. package/dist/types/vectorStoreChroma.js +11 -0
  48. package/dist/types/vectorStorePinecone.d.ts +48 -0
  49. package/dist/types/vectorStorePinecone.js +11 -0
  50. package/package.json +12 -8
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Chroma vector store adapter
3
+ *
4
+ * Implements the `VectorStore` contract (see `src/lib/types/rag.ts`) against
5
+ * a Chroma collection. This module has zero runtime dependency on the
6
+ * `chromadb` package: callers construct their own `ChromaClient` (or any
7
+ * compatible object) and inject it here. The structural interfaces below
8
+ * mirror the subset of the `chromadb` API this adapter relies on
9
+ * (`getOrCreateCollection`, `collection.upsert/query/delete`).
10
+ *
11
+ * ## Distance -> similarity scoring
12
+ *
13
+ * Chroma returns *distances*, not similarities, and the meaning of that
14
+ * distance depends entirely on the collection's configured `hnsw:space`
15
+ * (this adapter does not — and cannot — set that; it is chosen when the
16
+ * collection is created on the Chroma side). `distanceMetric` tells this
17
+ * adapter which space the collection uses so it can invert the distance
18
+ * into a `score` consistent with `InMemoryVectorStore` (higher = more
19
+ * similar):
20
+ *
21
+ * - `"cosine"` (default): Chroma returns `distance = 1 - cosineSimilarity`,
22
+ * so `score = 1 - distance` reproduces `InMemoryVectorStore`'s cosine
23
+ * score exactly.
24
+ * - `"ip"`: Chroma returns `distance = 1 - dot(a, b)`, so
25
+ * `score = 1 - distance` yields the raw dot product — only equal to
26
+ * cosine similarity if both vectors are unit-normalized.
27
+ * - `"l2"`: Chroma returns squared Euclidean distance, range
28
+ * `[0, Infinity)`, which has no exact inverse into a cosine-similarity
29
+ * scale. It is converted via `score = 1 / (1 + distance)`, a
30
+ * monotonically decreasing-in-distance map into `(0, 1]`. This preserves
31
+ * result ordering but is NOT numerically comparable to cosine scores.
32
+ *
33
+ * Passing a `distanceMetric` that does not match the collection's actual
34
+ * `hnsw:space` will silently produce meaningless scores (Chroma has no way
35
+ * to report its configured space back to the client), so callers must keep
36
+ * the two in sync.
37
+ *
38
+ * ## Metadata round-tripping
39
+ *
40
+ * Chroma metadata values must be strings, numbers, or booleans. Values that
41
+ * are `undefined` are dropped (Chroma rejects `undefined`); any other
42
+ * non-primitive value (object, array, `null`) is `JSON.stringify`-ed on
43
+ * write. This is lossy: such values come back as JSON-encoded strings on
44
+ * read, not their original type. Store only primitive metadata values if
45
+ * you need exact round-tripping.
46
+ *
47
+ * ## indexName -> collection mapping
48
+ *
49
+ * `indexName` is used verbatim as the Chroma collection name (1:1,
50
+ * lazily created via `getOrCreateCollection` and cached for the lifetime of
51
+ * this adapter instance).
52
+ */
53
+ // Structural Chroma client/collection types (`ChromaClientLike`,
54
+ // `ChromaCollectionLike`, `ChromaUpsertParams`, etc.) live in
55
+ // `src/lib/types/vectorStoreChroma.ts` per this repo's canonical-type-
56
+ // location rule (see CLAUDE.md Critical Rules 2 & 12) and are imported from
57
+ // the types barrel above. This file only re-exports runtime values
58
+ // (`ChromaVectorStore`, `translateMetadataFilter`) — consumers who need the
59
+ // structural types should import them from `@juspay/neurolink` (root) or
60
+ // the types barrel directly, per Critical Rule 13.
61
+ // ---------------------------------------------------------------------------
62
+ // MetadataFilter -> Chroma `where` translation
63
+ // ---------------------------------------------------------------------------
64
+ const CHROMA_SUPPORTED_FIELD_OPS = new Set([
65
+ "$eq",
66
+ "$ne",
67
+ "$gt",
68
+ "$gte",
69
+ "$lt",
70
+ "$lte",
71
+ "$in",
72
+ "$nin",
73
+ ]);
74
+ const CHROMA_UNSUPPORTED_FIELD_OP_REASONS = {
75
+ $exists: 'Chroma metadata filtering has no existence check on the "where" clause.',
76
+ $contains: 'Chroma metadata filtering has no substring-match operator on the "where" clause (Chroma\'s substring matching only applies to document text via "where_document", not metadata field values).',
77
+ $regex: 'Chroma metadata filtering has no regular-expression operator on the "where" clause.',
78
+ $size: 'Chroma metadata filtering has no array-length operator on the "where" clause.',
79
+ };
80
+ function unsupportedFieldOpError(opKey, field) {
81
+ const reason = CHROMA_UNSUPPORTED_FIELD_OP_REASONS[opKey] ??
82
+ `Chroma metadata filtering does not support the "${opKey}" operator on the "where" clause.`;
83
+ return new Error(`Unsupported operator "${opKey}" (field "${field}"): ${reason}`);
84
+ }
85
+ /**
86
+ * Translates a field-level filter value (either a direct-equality primitive
87
+ * or an operator bag) into a Chroma `where` clause fragment for that field.
88
+ *
89
+ * Returns `undefined` when the filter value carries no enforceable
90
+ * constraint (an array, or an operator bag with no recognized keys) — this
91
+ * mirrors `InMemoryVectorStore.matchesFilter`, which treats any non-null
92
+ * object value as an "operator bag" and silently applies none of its checks
93
+ * when no recognized `$`-key is present, i.e. the field matches
94
+ * unconditionally. Omitting the field from the Chroma `where` clause
95
+ * reproduces that same "always matches" behavior.
96
+ */
97
+ function translateFieldFilter(field, value) {
98
+ if (Array.isArray(value)) {
99
+ return undefined;
100
+ }
101
+ if (value === null || typeof value !== "object") {
102
+ return { [field]: { $eq: value } };
103
+ }
104
+ const ops = value;
105
+ const opKeys = Object.keys(ops);
106
+ if (opKeys.length === 0) {
107
+ return undefined;
108
+ }
109
+ const chromaOps = {};
110
+ for (const opKey of opKeys) {
111
+ if (!CHROMA_SUPPORTED_FIELD_OPS.has(opKey)) {
112
+ throw unsupportedFieldOpError(opKey, field);
113
+ }
114
+ chromaOps[opKey] = ops[opKey];
115
+ }
116
+ return { [field]: chromaOps };
117
+ }
118
+ function translateLogicalOperator(key, value) {
119
+ switch (key) {
120
+ case "$and":
121
+ return {
122
+ $and: value.map(translateMetadataFilter),
123
+ };
124
+ case "$or":
125
+ return {
126
+ $or: value.map(translateMetadataFilter),
127
+ };
128
+ case "$not":
129
+ throw new Error('Chroma metadata filtering has no negation operator on the "where" clause; "$not" cannot be translated. Rewrite the filter using $ne/$nin instead.');
130
+ case "$nor":
131
+ throw new Error('"$nor" is declared on MetadataFilter but is not implemented by InMemoryVectorStore either, and Chroma has no negation operator on the "where" clause. Rewrite using $and of $ne/$nin clauses.');
132
+ default:
133
+ throw new Error(`Unknown logical operator "${key}" in MetadataFilter.`);
134
+ }
135
+ }
136
+ /**
137
+ * Translates a `MetadataFilter` into a Chroma `where` clause.
138
+ *
139
+ * Throws a clear error for any operator Chroma's metadata `where` clause
140
+ * cannot express ($not, $nor, $exists, $contains, $regex, $size) rather than
141
+ * silently dropping the constraint and mis-filtering results.
142
+ */
143
+ export function translateMetadataFilter(filter) {
144
+ const clauses = [];
145
+ for (const [key, value] of Object.entries(filter)) {
146
+ const clause = key.startsWith("$")
147
+ ? translateLogicalOperator(key, value)
148
+ : translateFieldFilter(key, value);
149
+ if (clause !== undefined) {
150
+ clauses.push(clause);
151
+ }
152
+ }
153
+ if (clauses.length === 0) {
154
+ return {};
155
+ }
156
+ if (clauses.length === 1) {
157
+ return clauses[0];
158
+ }
159
+ // Chroma requires a single top-level operator; combine multiple field/
160
+ // logical clauses with an explicit $and (mirrors matchesFilter ANDing
161
+ // every top-level entry).
162
+ return { $and: clauses };
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // Metadata <-> Chroma metadata conversion
166
+ // ---------------------------------------------------------------------------
167
+ function toChromaMetadataValue(value) {
168
+ if (typeof value === "string" ||
169
+ typeof value === "number" ||
170
+ typeof value === "boolean") {
171
+ return value;
172
+ }
173
+ // Chroma metadata values must be string | number | boolean. Non-primitive
174
+ // values (object, array, null) are JSON-stringified — lossy on read back,
175
+ // see module doc comment.
176
+ return JSON.stringify(value);
177
+ }
178
+ function toChromaMetadata(metadata) {
179
+ const result = {};
180
+ for (const [key, value] of Object.entries(metadata)) {
181
+ if (value === undefined) {
182
+ // Chroma rejects `undefined` metadata values outright.
183
+ continue;
184
+ }
185
+ result[key] = toChromaMetadataValue(value);
186
+ }
187
+ return result;
188
+ }
189
+ function distanceToScore(distance, metric) {
190
+ switch (metric) {
191
+ case "cosine":
192
+ case "ip":
193
+ return 1 - distance;
194
+ case "l2":
195
+ return 1 / (1 + distance);
196
+ default: {
197
+ const exhaustive = metric;
198
+ throw new Error(`Unknown Chroma distance metric: ${String(exhaustive)}`);
199
+ }
200
+ }
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // ChromaVectorStore
204
+ // ---------------------------------------------------------------------------
205
+ export class ChromaVectorStore {
206
+ client;
207
+ collections = new Map();
208
+ distanceMetric;
209
+ constructor(client, options = {}) {
210
+ this.client = client;
211
+ this.distanceMetric = options.distanceMetric ?? "cosine";
212
+ }
213
+ getCollection(indexName) {
214
+ let collection = this.collections.get(indexName);
215
+ if (!collection) {
216
+ collection = this.client.getOrCreateCollection({ name: indexName });
217
+ this.collections.set(indexName, collection);
218
+ }
219
+ return collection;
220
+ }
221
+ /**
222
+ * Add or update vectors in an index (Chroma collection).
223
+ */
224
+ async upsert(indexName, items) {
225
+ if (items.length === 0) {
226
+ return;
227
+ }
228
+ const collection = await this.getCollection(indexName);
229
+ await collection.upsert({
230
+ ids: items.map((item) => item.id),
231
+ embeddings: items.map((item) => item.vector),
232
+ metadatas: items.map((item) => toChromaMetadata(item.metadata ?? {})),
233
+ });
234
+ }
235
+ /**
236
+ * Query vectors by similarity.
237
+ */
238
+ async query(params) {
239
+ const { indexName, queryVector, topK = 10, filter, includeVectors = false, } = params;
240
+ const collection = await this.getCollection(indexName);
241
+ const include = ["metadatas", "distances"];
242
+ if (includeVectors) {
243
+ include.push("embeddings");
244
+ }
245
+ const response = await collection.query({
246
+ queryEmbeddings: [queryVector],
247
+ nResults: topK,
248
+ ...(filter ? { where: translateMetadataFilter(filter) } : {}),
249
+ include,
250
+ });
251
+ const ids = response.ids[0] ?? [];
252
+ const metadatasRow = response.metadatas?.[0] ?? [];
253
+ const distancesRow = response.distances?.[0] ?? [];
254
+ const embeddingsRow = response.embeddings?.[0] ?? [];
255
+ return ids.map((id, i) => {
256
+ const metadata = (metadatasRow[i] ?? {});
257
+ const distance = distancesRow[i];
258
+ const score = distance === null || distance === undefined
259
+ ? undefined
260
+ : distanceToScore(distance, this.distanceMetric);
261
+ return {
262
+ id,
263
+ score,
264
+ text: metadata.text,
265
+ metadata,
266
+ ...(includeVectors ? { vector: embeddingsRow[i] ?? [] } : {}),
267
+ };
268
+ });
269
+ }
270
+ /**
271
+ * Delete vectors from an index (Chroma collection) by id.
272
+ */
273
+ async delete(indexName, ids) {
274
+ if (ids.length === 0) {
275
+ return;
276
+ }
277
+ const collection = await this.getCollection(indexName);
278
+ await collection.delete({ ids });
279
+ }
280
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Vector Store Adapters
3
+ *
4
+ * Client-injection adapters implementing the `VectorStore` contract
5
+ * (see `src/lib/types/rag.ts`) against real vector database backends.
6
+ * Each adapter accepts an already-constructed vendor client typed as a
7
+ * minimal structural interface — no vendor SDK is a runtime dependency
8
+ * of this package; callers bring their own client instance.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { PineconeVectorStore } from '@juspay/neurolink/rag';
13
+ * import { Pinecone } from '@pinecone-database/pinecone';
14
+ *
15
+ * const client = new Pinecone({ apiKey: '...' });
16
+ * const store = new PineconeVectorStore(client.index('my-index'));
17
+ * ```
18
+ */
19
+ export * from "./pinecone.js";
20
+ export * from "./pgvector.js";
21
+ export * from "./chroma.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Vector Store Adapters
3
+ *
4
+ * Client-injection adapters implementing the `VectorStore` contract
5
+ * (see `src/lib/types/rag.ts`) against real vector database backends.
6
+ * Each adapter accepts an already-constructed vendor client typed as a
7
+ * minimal structural interface — no vendor SDK is a runtime dependency
8
+ * of this package; callers bring their own client instance.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { PineconeVectorStore } from '@juspay/neurolink/rag';
13
+ * import { Pinecone } from '@pinecone-database/pinecone';
14
+ *
15
+ * const client = new Pinecone({ apiKey: '...' });
16
+ * const store = new PineconeVectorStore(client.index('my-index'));
17
+ * ```
18
+ */
19
+ export * from "./pinecone.js";
20
+ export * from "./pgvector.js";
21
+ export * from "./chroma.js";
@@ -0,0 +1,95 @@
1
+ /**
2
+ * pgvector Vector Store Adapter
3
+ *
4
+ * Client-injection `VectorStore` implementation backed by Postgres +
5
+ * the `pgvector` extension. Callers construct and own the Postgres client
6
+ * (e.g. a `pg.Pool`, or an in-process `@electric-sql/pglite` instance) and
7
+ * pass it in — this module adds zero vendor SDKs as runtime dependencies.
8
+ *
9
+ * Storage model: one table per `indexName`, created lazily on first
10
+ * `upsert()` (`CREATE TABLE IF NOT EXISTS`, so it is safe to call
11
+ * repeatedly). Schema:
12
+ * id text PRIMARY KEY
13
+ * embedding vector(n) -- n fixed to the dimension of the first upsert
14
+ * metadata jsonb NOT NULL DEFAULT '{}'::jsonb
15
+ *
16
+ * Scoring: matches `InMemoryVectorStore`'s cosine-similarity convention
17
+ * (higher is better, 1.0 = identical direction) by converting pgvector's
18
+ * `<=>` cosine *distance* operator (0 = identical) via `score = 1 - distance`.
19
+ *
20
+ * `MetadataFilter` -> SQL: every value that flows into a query is a bound
21
+ * parameter (`$1`, `$2`, ...), including metadata field *names* — nothing
22
+ * from the filter is ever string-concatenated into SQL text. The one thing
23
+ * that IS textually embedded is the derived table name, and only after it
24
+ * passes a strict identifier allow-list (see `IDENTIFIER_RE`), because
25
+ * Postgres has no way to bind an identifier as a query parameter.
26
+ *
27
+ * Operators not expressible without ambiguity are rejected loudly instead of
28
+ * silently no-op'ing:
29
+ * - `$nor` (top-level logical operator) and `$size` (field-level operator)
30
+ * are declared on `MetadataFilter` but (per the reference
31
+ * `InMemoryVectorStore.matchesFilter`) are not actually implemented
32
+ * there either — rather than guess at semantics that would silently
33
+ * diverge from any caller's expectations, this adapter throws.
34
+ * - Any other unrecognized `$foo` key, top-level or field-level, throws
35
+ * for the same reason.
36
+ *
37
+ * Known, documented behavioral differences from `InMemoryVectorStore`:
38
+ * - `$eq` / bare-value equality compares via Postgres jsonb structural
39
+ * equality, whereas the in-memory reference uses JS `!==` (so it can
40
+ * never match on array/object metadata values — two distinct array
41
+ * instances are never `===`). This adapter is strictly more capable
42
+ * for those value types.
43
+ * - `$regex` runs Postgres POSIX regex (`~`) rather than a JS `RegExp`.
44
+ * For non-string or missing fields the in-memory store tests the
45
+ * pattern against `""` (so e.g. `^$` or `.*` can still match); this
46
+ * adapter instead requires the field to be a JSON string, so those
47
+ * edge-case patterns will not match a missing/non-string field here.
48
+ */
49
+ import type { MetadataFilter, PgClientLike, PgVectorStoreOptions, VectorQueryResult, VectorStore } from "../../types/index.js";
50
+ export declare class PgVectorStore implements VectorStore {
51
+ private readonly client;
52
+ private readonly tablePrefix;
53
+ /** Tables we've already run `CREATE TABLE IF NOT EXISTS` for, this process. */
54
+ private readonly ensuredTables;
55
+ constructor(client: PgClientLike, options?: PgVectorStoreOptions);
56
+ /**
57
+ * Derive and validate the backing table name for an index. Throws on
58
+ * anything that isn't a safe bare SQL identifier — this is the injection
59
+ * guard for `indexName`.
60
+ */
61
+ private tableName;
62
+ /**
63
+ * Ensure the `vector` extension and the index's table exist. Safe to call
64
+ * repeatedly (`IF NOT EXISTS` throughout). `dimension` is fixed by the
65
+ * first upsert into a fresh index; it is a validated positive integer
66
+ * embedded directly in DDL (Postgres cannot bind type modifiers either).
67
+ */
68
+ private ensureTable;
69
+ /**
70
+ * Upsert vectors into an index, creating its table on first use.
71
+ * Mirrors `InMemoryVectorStore.upsert` (metadata defaults to `{}`).
72
+ */
73
+ upsert(indexName: string, items: Array<{
74
+ id: string;
75
+ vector: number[];
76
+ metadata?: Record<string, unknown>;
77
+ }>): Promise<void>;
78
+ /**
79
+ * Query by cosine similarity. Returns `[]` if the index's table doesn't
80
+ * exist yet (mirrors `InMemoryVectorStore.query` on an unknown index)
81
+ * rather than throwing.
82
+ */
83
+ query(params: {
84
+ indexName: string;
85
+ queryVector: number[];
86
+ topK?: number;
87
+ filter?: MetadataFilter;
88
+ includeVectors?: boolean;
89
+ }): Promise<VectorQueryResult[]>;
90
+ /**
91
+ * Delete vectors by id. No-op if the index's table doesn't exist yet
92
+ * (mirrors `InMemoryVectorStore.delete`).
93
+ */
94
+ delete(indexName: string, ids: string[]): Promise<void>;
95
+ }