@wrongstack/vector-memory 0.308.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.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/errors.d.ts +19 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +729 -0
- package/dist/schema.d.ts +21 -0
- package/dist/store.d.ts +43 -0
- package/dist/tools.d.ts +14 -0
- package/dist/transformers-provider.d.ts +67 -0
- package/dist/types.d.ts +95 -0
- package/dist/vector-codec.d.ts +11 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @wrongstack/vector-memory
|
|
2
|
+
|
|
3
|
+
An **additional vector-search memory** that sits alongside the existing
|
|
4
|
+
SAGE lexical memory system. Embeddings are computed locally via
|
|
5
|
+
[@huggingface/transformers](https://github.com/huggingface/transformers.js)
|
|
6
|
+
using the `Xenova/all-MiniLM-L6-v2` ONNX model (384 dimensions, ~25 MB
|
|
7
|
+
quantized). No project text leaves the machine.
|
|
8
|
+
|
|
9
|
+
The store is deliberately separate from SAGE's SQLite database — its own
|
|
10
|
+
file under `.wrongstack/vector-memory/vector-memory.db` — so the two
|
|
11
|
+
stores cannot contend on the same file lock. A built-in `syncFromSage`
|
|
12
|
+
bridge indexes active SAGE memories into the vector store, giving
|
|
13
|
+
semantic search over your existing knowledge.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
`@huggingface/transformers` is listed as an **optional** dependency so
|
|
18
|
+
the package installs and typechecks even without it. In this monorepo
|
|
19
|
+
it's installed by default; in downstream packages, ensure it's present:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm add @huggingface/transformers
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package itself is a workspace member; no extra install step is
|
|
26
|
+
needed inside the monorepo.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
TransformersEmbeddingProvider,
|
|
33
|
+
VectorMemoryStore,
|
|
34
|
+
createVectorMemoryTools,
|
|
35
|
+
} from '@wrongstack/vector-memory';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
|
|
38
|
+
const provider = new TransformersEmbeddingProvider({
|
|
39
|
+
// Optional overrides (these are the defaults):
|
|
40
|
+
modelId: 'Xenova/all-MiniLM-L6-v2',
|
|
41
|
+
cacheDir: path.join(projectRoot, '.wrongstack/vector-memory/models'),
|
|
42
|
+
dtype: 'q8',
|
|
43
|
+
device: 'cpu',
|
|
44
|
+
allowRemoteModels: true, // set false to require a pre-cached model
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const store = new VectorMemoryStore({ provider, projectRoot });
|
|
48
|
+
|
|
49
|
+
await store.remember({ text: 'pnpm is the package manager', tags: ['build'] });
|
|
50
|
+
|
|
51
|
+
const hits = await store.search('how do we install dependencies', { limit: 5 });
|
|
52
|
+
// → hits[i].entry.text, hits[i].score (cosine similarity in [0, 1])
|
|
53
|
+
|
|
54
|
+
store.close();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tools
|
|
58
|
+
|
|
59
|
+
`createVectorMemoryTools(store)` returns four `@wrongstack/core` `Tool`
|
|
60
|
+
definitions that surface the store to agents:
|
|
61
|
+
|
|
62
|
+
| Tool | Permission | Mutating | Purpose |
|
|
63
|
+
|------|------------|----------|---------|
|
|
64
|
+
| `vector_memory_remember` | `confirm` | yes | Store text + embed it |
|
|
65
|
+
| `vector_memory_search` | `auto` | no | Semantic top-k search |
|
|
66
|
+
| `vector_memory_stats` | `auto` | no | Entry/vector/provider counts |
|
|
67
|
+
| `vector_memory_forget` | `confirm` | yes | Hard-delete an entry by id |
|
|
68
|
+
|
|
69
|
+
Wire them alongside `createSageTools` if you want agents to have both
|
|
70
|
+
the lexical and the semantic memory surface.
|
|
71
|
+
|
|
72
|
+
## Graceful fallback
|
|
73
|
+
|
|
74
|
+
`TransformersEmbeddingProvider` is lazy — nothing is imported until the
|
|
75
|
+
first `embed()` call. If `@huggingface/transformers` is not installed
|
|
76
|
+
or the model fails to load (offline, corrupt cache, etc.):
|
|
77
|
+
|
|
78
|
+
- `isAvailable()` returns `false`.
|
|
79
|
+
- `embed()` throws `VectorMemoryProviderUnavailableError` (or a wrapped
|
|
80
|
+
underlying error from the pipeline).
|
|
81
|
+
- The store's `remember()` **swallows** the embedding failure and
|
|
82
|
+
persists the entry without a vector. Search simply skips entries
|
|
83
|
+
without a matching vector row. Writes never disappear.
|
|
84
|
+
- `search()` returns `[]` when embedding fails, so callers can fall
|
|
85
|
+
back to lexical search at a higher layer.
|
|
86
|
+
|
|
87
|
+
For an offline-only store, wire the sage `HashingEmbeddingProvider`
|
|
88
|
+
instead — it's deterministic, zero-dependency, and satisfies the same
|
|
89
|
+
`EmbeddingProvider` contract:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { HashingEmbeddingProvider, type EmbeddingProvider } from '@wrongstack/sage';
|
|
93
|
+
|
|
94
|
+
const provider: EmbeddingProvider = new HashingEmbeddingProvider({ dimensions: 384 });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The store accepts any `EmbeddingProvider`; you can mix and match
|
|
98
|
+
between providers across instances or for testing.
|
|
99
|
+
|
|
100
|
+
## Syncing with SAGE
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const sagePort = /* a MemoryPort or any object satisfying SageSyncSource */;
|
|
104
|
+
const report = await store.syncFromSage({
|
|
105
|
+
listActiveMemories: async ({ limit }) => {
|
|
106
|
+
const page = await sagePort.listSagePage({ statuses: ['active'], limit });
|
|
107
|
+
return (page.memories ?? []).map((m) => ({
|
|
108
|
+
id: m.id,
|
|
109
|
+
text: m.text,
|
|
110
|
+
tags: m.tags,
|
|
111
|
+
}));
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
// { scanned, indexed, skipped, failed, errors }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The bridge deduplicates on `contentHash` (SHA-256 of NFKC-normalized
|
|
118
|
+
text) so repeated calls are idempotent.
|
|
119
|
+
|
|
120
|
+
## Storage layout
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
<projectRoot>/.wrongstack/vector-memory/
|
|
124
|
+
├── vector-memory.db — SQLite database (WAL mode)
|
|
125
|
+
├── vector-memory.db-wal — WAL frame file
|
|
126
|
+
└── models/ — transformers.js model cache (when configured)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The model cache defaults to `.wrongstack/vector-memory/models` under
|
|
130
|
+
the project root; override via `TransformersEmbeddingProvider({ cacheDir })`.
|
|
131
|
+
Set `allowRemoteModels: false` to refuse downloads and require a
|
|
132
|
+
pre-populated cache (useful in CI / air-gapped runs).
|
|
133
|
+
|
|
134
|
+
## Schema
|
|
135
|
+
|
|
136
|
+
- `entries` — text, summary, metadata JSON, tags JSON, scope, kind,
|
|
137
|
+
content_hash, timestamps. Indexed on scope/kind/hash/updated_at.
|
|
138
|
+
- `vectors` — `(entry_id, provider_id)` PK, dimensions, raw float32
|
|
139
|
+
BLOB, timestamp. Foreign-key cascades on entry delete. Provider-id
|
|
140
|
+
keying means a model change triggers reindexing rather than mixed
|
|
141
|
+
vectors.
|
|
142
|
+
- `entries_fts` — FTS5 mirror of `text` + `tags` for the optional
|
|
143
|
+
lexical fallback path. Kept in sync via triggers.
|
|
144
|
+
- `schema_meta` — active provider id and dimensions.
|
|
145
|
+
|
|
146
|
+
## API reference
|
|
147
|
+
|
|
148
|
+
- `VectorMemoryStore` — constructor `({ provider, projectRoot, directory?, filename? })`.
|
|
149
|
+
- `remember(input)` → `VectorEntryWithVector`
|
|
150
|
+
- `get(id)` → `VectorEntryWithVector | undefined`
|
|
151
|
+
- `forget(id)` → `boolean`
|
|
152
|
+
- `search(query, opts?)` → `VectorSearchHit[]`
|
|
153
|
+
- `list(opts?)` → `VectorEntry[]`
|
|
154
|
+
- `stats()` → `VectorStoreStats`
|
|
155
|
+
- `reindexAll()` → `{ processed, errors }`
|
|
156
|
+
- `syncFromSage(source)` → `SageSyncReport`
|
|
157
|
+
- `activeProviderId` → `string`
|
|
158
|
+
- `close()`
|
|
159
|
+
|
|
160
|
+
See `src/types.ts` for the full type surface.
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
Tests use a deterministic `FakeEmbeddingProvider` — no network, no
|
|
165
|
+
model download. Run:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
pnpm --filter @wrongstack/vector-memory test
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The integration path that exercises the real transformers.js pipeline
|
|
172
|
+
is not included in the default test suite because it requires model
|
|
173
|
+
download. To exercise it manually:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { TransformersEmbeddingProvider } from '@wrongstack/vector-memory';
|
|
177
|
+
const p = new TransformersEmbeddingProvider();
|
|
178
|
+
console.log(await p.isAvailable()); // true when @huggingface/transformers is installed
|
|
179
|
+
const [vec] = await p.embed(['hello world']);
|
|
180
|
+
console.log(vec.length); // 384
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Related
|
|
184
|
+
|
|
185
|
+
- `@wrongstack/sage` — lexical/FTS/graph memory. `EmbeddingProvider` and
|
|
186
|
+
`cosineSimilarity` are exported from sage so any package can implement
|
|
187
|
+
or compose with them.
|
|
188
|
+
- `docs/competitive-roadmap-2026-2027/13-semantic-sage-retrieval.md` —
|
|
189
|
+
the roadmap doc this package implements a subset of.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the vector memory package.
|
|
3
|
+
*
|
|
4
|
+
* Kept in their own module so callers can `instanceof`-check without
|
|
5
|
+
* pulling the full store implementation.
|
|
6
|
+
*/
|
|
7
|
+
export declare class VectorMemoryError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Raised when the configured embedding provider is unavailable — typically
|
|
12
|
+
* because `@huggingface/transformers` was not installed or the model
|
|
13
|
+
* failed to load. The original cause (if any) is attached for diagnostics.
|
|
14
|
+
*/
|
|
15
|
+
export declare class VectorMemoryProviderUnavailableError extends VectorMemoryError {
|
|
16
|
+
readonly cause: unknown;
|
|
17
|
+
constructor(message: string, cause?: unknown);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wrongstack/vector-memory — public exports.
|
|
3
|
+
*/
|
|
4
|
+
export { DEFAULT_VECTOR_DIMENSIONS, DEFAULT_VECTOR_DTYPE, DEFAULT_VECTOR_MODEL_ID, TransformersEmbeddingProvider, type TransformersEmbeddingProviderOptions, } from './transformers-provider.js';
|
|
5
|
+
export { VECTOR_DIMENSIONS_KEY, VECTOR_PROVIDER_KEY, VECTOR_SCHEMA_VERSION, decodeVector, encodeVector, initVectorSchema, } from './schema.js';
|
|
6
|
+
export { VectorMemoryStore, fallbackHashingProvider, type SageSyncSource, } from './store.js';
|
|
7
|
+
export { createVectorMemoryTools } from './tools.js';
|
|
8
|
+
export type { SageSyncReport, VectorEntry, VectorEntryInput, VectorEntryWithVector, VectorKind, VectorMemoryStoreOptions, VectorScope, VectorSearchHit, VectorSearchOptions, VectorStoreStats, } from './types.js';
|
|
9
|
+
export { VectorMemoryError, VectorMemoryProviderUnavailableError, } from './errors.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var VectorMemoryError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "VectorMemoryError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var VectorMemoryProviderUnavailableError = class extends VectorMemoryError {
|
|
9
|
+
cause;
|
|
10
|
+
constructor(message, cause) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "VectorMemoryProviderUnavailableError";
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/transformers-provider.ts
|
|
18
|
+
var DEFAULT_VECTOR_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
19
|
+
var DEFAULT_VECTOR_DIMENSIONS = 384;
|
|
20
|
+
var DEFAULT_VECTOR_DTYPE = "q8";
|
|
21
|
+
var TransformersEmbeddingProvider = class {
|
|
22
|
+
id;
|
|
23
|
+
dimensions;
|
|
24
|
+
modelId;
|
|
25
|
+
cacheDir;
|
|
26
|
+
dtype;
|
|
27
|
+
device;
|
|
28
|
+
batchSize;
|
|
29
|
+
maxChars;
|
|
30
|
+
allowRemote;
|
|
31
|
+
extractor;
|
|
32
|
+
loadPromise;
|
|
33
|
+
constructor(opts = {}) {
|
|
34
|
+
this.modelId = opts.modelId ?? DEFAULT_VECTOR_MODEL_ID;
|
|
35
|
+
this.cacheDir = opts.cacheDir;
|
|
36
|
+
this.dtype = opts.dtype ?? DEFAULT_VECTOR_DTYPE;
|
|
37
|
+
this.device = opts.device ?? "cpu";
|
|
38
|
+
this.batchSize = opts.batchSize ?? 16;
|
|
39
|
+
this.maxChars = opts.maxChars ?? 2048;
|
|
40
|
+
this.allowRemote = opts.allowRemoteModels ?? true;
|
|
41
|
+
this.dimensions = DEFAULT_VECTOR_DIMENSIONS;
|
|
42
|
+
this.id = `transformers-js:${this.modelId}:${this.dtype}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Synchronous capability check. Returns false when the optional
|
|
46
|
+
* `@huggingface/transformers` dependency is not installed.
|
|
47
|
+
*
|
|
48
|
+
* NOTE: this probes via dynamic import and caches the result, but does
|
|
49
|
+
* NOT load the model itself — model loading is deferred to `embed()`.
|
|
50
|
+
*/
|
|
51
|
+
async isAvailable() {
|
|
52
|
+
try {
|
|
53
|
+
await this.loadModule();
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async embed(texts) {
|
|
60
|
+
if (texts.length === 0) return [];
|
|
61
|
+
const extractor = await this.getExtractor();
|
|
62
|
+
const prepared = texts.map((t) => this.prepare(t));
|
|
63
|
+
const batches = [];
|
|
64
|
+
for (let i = 0; i < prepared.length; i += this.batchSize) {
|
|
65
|
+
batches.push(prepared.slice(i, i + this.batchSize));
|
|
66
|
+
}
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const batch of batches) {
|
|
69
|
+
const out = await extractor(batch, { pooling: "mean", normalize: true });
|
|
70
|
+
results.push(...this.tensorToVectors(out, batch.length));
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
/** Truncate + normalize text before embedding. */
|
|
75
|
+
prepare(text) {
|
|
76
|
+
if (!text) return "";
|
|
77
|
+
const normalized = text.normalize("NFKC").trim();
|
|
78
|
+
return normalized.length > this.maxChars ? normalized.slice(0, this.maxChars) : normalized;
|
|
79
|
+
}
|
|
80
|
+
tensorToVectors(out, batchSize) {
|
|
81
|
+
if (typeof out.tolist === "function") {
|
|
82
|
+
const nested = out.tolist();
|
|
83
|
+
if (Array.isArray(nested) && Array.isArray(nested[0])) {
|
|
84
|
+
return nested.map((row) => Float32Array.from(row));
|
|
85
|
+
}
|
|
86
|
+
return [Float32Array.from(nested)];
|
|
87
|
+
}
|
|
88
|
+
const flat = out.data;
|
|
89
|
+
if (flat instanceof Float32Array) {
|
|
90
|
+
if (batchSize === 1) return [flat];
|
|
91
|
+
const dim = flat.length / batchSize;
|
|
92
|
+
const vectors = [];
|
|
93
|
+
for (let i = 0; i < batchSize; i++) {
|
|
94
|
+
vectors.push(Float32Array.from(flat.subarray(i * dim, (i + 1) * dim)));
|
|
95
|
+
}
|
|
96
|
+
return vectors;
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(flat) && Array.isArray(flat[0])) {
|
|
99
|
+
return flat.map((row) => Float32Array.from(row));
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(flat)) {
|
|
102
|
+
return [Float32Array.from(flat)];
|
|
103
|
+
}
|
|
104
|
+
throw new Error("TransformersEmbeddingProvider: unexpected pipeline output shape");
|
|
105
|
+
}
|
|
106
|
+
async getExtractor() {
|
|
107
|
+
if (this.extractor) return this.extractor;
|
|
108
|
+
if (!this.loadPromise) this.loadPromise = this.loadExtractor();
|
|
109
|
+
this.extractor = await this.loadPromise;
|
|
110
|
+
return this.extractor;
|
|
111
|
+
}
|
|
112
|
+
async loadExtractor() {
|
|
113
|
+
const mod = await this.loadModule();
|
|
114
|
+
if (this.cacheDir) mod.env.cacheDir = this.cacheDir;
|
|
115
|
+
mod.env.allowRemoteModels = this.allowRemote;
|
|
116
|
+
if (!this.allowRemote) mod.env.localModelPath = this.cacheDir ?? "";
|
|
117
|
+
const pipe = await mod.pipeline("feature-extraction", this.modelId, {
|
|
118
|
+
dtype: this.dtype,
|
|
119
|
+
device: this.device
|
|
120
|
+
});
|
|
121
|
+
return pipe;
|
|
122
|
+
}
|
|
123
|
+
async loadModule() {
|
|
124
|
+
try {
|
|
125
|
+
return await import("@huggingface/transformers");
|
|
126
|
+
} catch (err) {
|
|
127
|
+
throw new VectorMemoryProviderUnavailableError(
|
|
128
|
+
"@huggingface/transformers is not installed. Install it (pnpm add @huggingface/transformers) or wire a fallback EmbeddingProvider.",
|
|
129
|
+
err
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/schema.ts
|
|
136
|
+
var VECTOR_SCHEMA_VERSION = 1;
|
|
137
|
+
var VECTOR_PROVIDER_KEY = "active_provider_id";
|
|
138
|
+
var VECTOR_DIMENSIONS_KEY = "active_provider_dimensions";
|
|
139
|
+
function initVectorSchema(db) {
|
|
140
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
141
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
142
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
143
|
+
db.exec("PRAGMA temp_store = MEMORY");
|
|
144
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
145
|
+
db.exec(`
|
|
146
|
+
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
147
|
+
key TEXT PRIMARY KEY,
|
|
148
|
+
value TEXT NOT NULL
|
|
149
|
+
);
|
|
150
|
+
`);
|
|
151
|
+
db.exec(`
|
|
152
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
text TEXT NOT NULL,
|
|
155
|
+
summary TEXT,
|
|
156
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
157
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
158
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
159
|
+
kind TEXT NOT NULL DEFAULT 'note',
|
|
160
|
+
content_hash TEXT NOT NULL,
|
|
161
|
+
created_at TEXT NOT NULL,
|
|
162
|
+
updated_at TEXT NOT NULL
|
|
163
|
+
);
|
|
164
|
+
`);
|
|
165
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_entries_scope ON entries(scope)");
|
|
166
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_entries_kind ON entries(kind)");
|
|
167
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_entries_hash ON entries(content_hash)");
|
|
168
|
+
db.exec(
|
|
169
|
+
"CREATE INDEX IF NOT EXISTS idx_entries_updated ON entries(updated_at DESC)"
|
|
170
|
+
);
|
|
171
|
+
db.exec(`
|
|
172
|
+
CREATE TABLE IF NOT EXISTS vectors (
|
|
173
|
+
entry_id TEXT NOT NULL,
|
|
174
|
+
provider_id TEXT NOT NULL,
|
|
175
|
+
dimensions INTEGER NOT NULL,
|
|
176
|
+
vector BLOB NOT NULL,
|
|
177
|
+
created_at TEXT NOT NULL,
|
|
178
|
+
PRIMARY KEY (entry_id, provider_id),
|
|
179
|
+
FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE
|
|
180
|
+
);
|
|
181
|
+
`);
|
|
182
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_vectors_provider ON vectors(provider_id)");
|
|
183
|
+
db.exec(`
|
|
184
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(
|
|
185
|
+
id UNINDEXED, text, tags, content='entries', content_rowid='rowid'
|
|
186
|
+
);
|
|
187
|
+
`);
|
|
188
|
+
db.exec(`
|
|
189
|
+
CREATE TRIGGER IF NOT EXISTS entries_ai AFTER INSERT ON entries BEGIN
|
|
190
|
+
INSERT INTO entries_fts(rowid, text, tags)
|
|
191
|
+
VALUES (new.rowid, new.text, new.tags);
|
|
192
|
+
END;
|
|
193
|
+
`);
|
|
194
|
+
db.exec(`
|
|
195
|
+
CREATE TRIGGER IF NOT EXISTS entries_ad AFTER DELETE ON entries BEGIN
|
|
196
|
+
INSERT INTO entries_fts(entries_fts, rowid, text, tags)
|
|
197
|
+
VALUES('delete', old.rowid, old.text, old.tags);
|
|
198
|
+
END;
|
|
199
|
+
`);
|
|
200
|
+
db.exec(`
|
|
201
|
+
CREATE TRIGGER IF NOT EXISTS entries_au AFTER UPDATE ON entries BEGIN
|
|
202
|
+
INSERT INTO entries_fts(entries_fts, rowid, text, tags)
|
|
203
|
+
VALUES('delete', old.rowid, old.text, old.tags);
|
|
204
|
+
INSERT INTO entries_fts(rowid, text, tags)
|
|
205
|
+
VALUES (new.rowid, new.text, new.tags);
|
|
206
|
+
END;
|
|
207
|
+
`);
|
|
208
|
+
}
|
|
209
|
+
function encodeVector(vec) {
|
|
210
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
211
|
+
}
|
|
212
|
+
function decodeVector(buf) {
|
|
213
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
214
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
215
|
+
for (let i = 0; i < copy.length; i++) {
|
|
216
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
217
|
+
}
|
|
218
|
+
return copy;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/store.ts
|
|
222
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
223
|
+
import * as fs from "node:fs";
|
|
224
|
+
import * as path from "node:path";
|
|
225
|
+
import { DatabaseSync } from "node:sqlite";
|
|
226
|
+
import { HashingEmbeddingProvider, cosineSimilarity } from "@wrongstack/sage";
|
|
227
|
+
var DEFAULT_DIRECTORY = ".wrongstack/vector-memory";
|
|
228
|
+
var DEFAULT_FILENAME = "vector-memory.db";
|
|
229
|
+
var VectorMemoryStore = class _VectorMemoryStore {
|
|
230
|
+
db;
|
|
231
|
+
dbPath;
|
|
232
|
+
provider;
|
|
233
|
+
closed = false;
|
|
234
|
+
constructor(opts) {
|
|
235
|
+
if (!opts.provider) throw new Error("VectorMemoryStore: provider is required");
|
|
236
|
+
if (!opts.projectRoot) throw new Error("VectorMemoryStore: projectRoot is required");
|
|
237
|
+
this.provider = opts.provider;
|
|
238
|
+
const dir = opts.directory ?? DEFAULT_DIRECTORY;
|
|
239
|
+
const filename = opts.filename ?? DEFAULT_FILENAME;
|
|
240
|
+
if (path.isAbsolute(dir)) {
|
|
241
|
+
throw new Error("Vector memory directory must be project-relative.");
|
|
242
|
+
}
|
|
243
|
+
const rootDir = path.resolve(opts.projectRoot, dir);
|
|
244
|
+
const rel = path.relative(path.resolve(opts.projectRoot), rootDir);
|
|
245
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
246
|
+
throw new Error("Vector memory directory must stay inside the project root.");
|
|
247
|
+
}
|
|
248
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
249
|
+
this.dbPath = path.join(rootDir, filename);
|
|
250
|
+
this.db = new DatabaseSync(this.dbPath);
|
|
251
|
+
initVectorSchema(this.db);
|
|
252
|
+
this.recordActiveProvider();
|
|
253
|
+
}
|
|
254
|
+
get activeProviderId() {
|
|
255
|
+
const row = this.db.prepare("SELECT value FROM schema_meta WHERE key = ?").get(VECTOR_PROVIDER_KEY);
|
|
256
|
+
return row?.value ?? this.provider.id;
|
|
257
|
+
}
|
|
258
|
+
recordActiveProvider() {
|
|
259
|
+
this.db.exec("BEGIN");
|
|
260
|
+
try {
|
|
261
|
+
this.db.prepare(
|
|
262
|
+
`INSERT INTO schema_meta (key, value) VALUES (?, ?)
|
|
263
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
264
|
+
).run(VECTOR_PROVIDER_KEY, this.provider.id);
|
|
265
|
+
this.db.prepare(
|
|
266
|
+
`INSERT INTO schema_meta (key, value) VALUES (?, ?)
|
|
267
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
268
|
+
).run(VECTOR_DIMENSIONS_KEY, String(this.provider.dimensions));
|
|
269
|
+
this.db.exec("COMMIT");
|
|
270
|
+
} catch (e) {
|
|
271
|
+
this.db.exec("ROLLBACK");
|
|
272
|
+
throw e;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
static contentHash(text) {
|
|
276
|
+
return createHash("sha256").update(text.normalize("NFKC").trim()).digest("hex");
|
|
277
|
+
}
|
|
278
|
+
async remember(input) {
|
|
279
|
+
this.assertOpen();
|
|
280
|
+
if (!input.text || input.text.trim().length === 0) {
|
|
281
|
+
throw new Error("VectorMemoryStore.remember: text must be non-empty");
|
|
282
|
+
}
|
|
283
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
284
|
+
const id = randomUUID();
|
|
285
|
+
const contentHash = _VectorMemoryStore.contentHash(input.text);
|
|
286
|
+
const metadata = input.metadata ?? {};
|
|
287
|
+
const tags = input.tags ?? [];
|
|
288
|
+
const scope = input.scope ?? "project";
|
|
289
|
+
const kind = input.kind ?? "note";
|
|
290
|
+
let vector;
|
|
291
|
+
let providerId;
|
|
292
|
+
try {
|
|
293
|
+
const result2 = await this.provider.embed([input.text]);
|
|
294
|
+
vector = result2[0];
|
|
295
|
+
providerId = this.provider.id;
|
|
296
|
+
} catch {
|
|
297
|
+
providerId = void 0;
|
|
298
|
+
vector = void 0;
|
|
299
|
+
}
|
|
300
|
+
this.db.exec("BEGIN");
|
|
301
|
+
try {
|
|
302
|
+
this.db.prepare(
|
|
303
|
+
`INSERT INTO entries
|
|
304
|
+
(id, text, summary, metadata, tags, scope, kind, content_hash, created_at, updated_at)
|
|
305
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
306
|
+
).run(
|
|
307
|
+
id,
|
|
308
|
+
input.text,
|
|
309
|
+
input.summary ?? null,
|
|
310
|
+
JSON.stringify(metadata),
|
|
311
|
+
JSON.stringify(tags),
|
|
312
|
+
scope,
|
|
313
|
+
kind,
|
|
314
|
+
contentHash,
|
|
315
|
+
now,
|
|
316
|
+
now
|
|
317
|
+
);
|
|
318
|
+
if (vector && providerId) {
|
|
319
|
+
this.db.prepare(
|
|
320
|
+
`INSERT INTO vectors (entry_id, provider_id, dimensions, vector, created_at)
|
|
321
|
+
VALUES (?, ?, ?, ?, ?)
|
|
322
|
+
ON CONFLICT(entry_id, provider_id) DO UPDATE SET
|
|
323
|
+
vector = excluded.vector,
|
|
324
|
+
dimensions = excluded.dimensions,
|
|
325
|
+
created_at = excluded.created_at`
|
|
326
|
+
).run(id, providerId, vector.length, encodeVector(vector), now);
|
|
327
|
+
}
|
|
328
|
+
this.db.exec("COMMIT");
|
|
329
|
+
} catch (e) {
|
|
330
|
+
this.db.exec("ROLLBACK");
|
|
331
|
+
throw e;
|
|
332
|
+
}
|
|
333
|
+
const result = {
|
|
334
|
+
id,
|
|
335
|
+
text: input.text,
|
|
336
|
+
summary: input.summary ?? void 0,
|
|
337
|
+
metadata,
|
|
338
|
+
tags,
|
|
339
|
+
scope,
|
|
340
|
+
kind,
|
|
341
|
+
contentHash,
|
|
342
|
+
createdAt: now,
|
|
343
|
+
updatedAt: now,
|
|
344
|
+
providerId: providerId ?? "",
|
|
345
|
+
dimensions: vector?.length ?? 0
|
|
346
|
+
};
|
|
347
|
+
if (vector) result.vector = vector;
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
get(id) {
|
|
351
|
+
this.assertOpen();
|
|
352
|
+
const row = this.db.prepare("SELECT * FROM entries WHERE id = ?").get(id);
|
|
353
|
+
if (!row) return void 0;
|
|
354
|
+
const vectorRow = this.db.prepare("SELECT * FROM vectors WHERE entry_id = ?").get(id);
|
|
355
|
+
return this.rowToEntry(row, vectorRow);
|
|
356
|
+
}
|
|
357
|
+
forget(id) {
|
|
358
|
+
this.assertOpen();
|
|
359
|
+
this.db.exec("BEGIN");
|
|
360
|
+
try {
|
|
361
|
+
const info = this.db.prepare("DELETE FROM entries WHERE id = ?").run(id);
|
|
362
|
+
this.db.exec("COMMIT");
|
|
363
|
+
return info.changes > 0;
|
|
364
|
+
} catch (e) {
|
|
365
|
+
this.db.exec("ROLLBACK");
|
|
366
|
+
throw e;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async search(query, opts = {}) {
|
|
370
|
+
this.assertOpen();
|
|
371
|
+
const limit = opts.limit ?? 10;
|
|
372
|
+
const threshold = opts.threshold ?? 0;
|
|
373
|
+
if (typeof query !== "string" || query.trim().length === 0) return [];
|
|
374
|
+
let queryVec;
|
|
375
|
+
try {
|
|
376
|
+
const result = await this.provider.embed([query]);
|
|
377
|
+
if (!result[0]) return [];
|
|
378
|
+
queryVec = result[0];
|
|
379
|
+
} catch {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
if (!queryVec || queryVec.length === 0) return [];
|
|
383
|
+
const providerId = this.provider.id;
|
|
384
|
+
const dimensions = this.provider.dimensions;
|
|
385
|
+
const filters = ["v.provider_id = ?", "v.dimensions = ?"];
|
|
386
|
+
const params = [providerId, dimensions];
|
|
387
|
+
if (opts.scope !== void 0) {
|
|
388
|
+
filters.push("e.scope = ?");
|
|
389
|
+
params.push(opts.scope);
|
|
390
|
+
}
|
|
391
|
+
if (opts.kind !== void 0) {
|
|
392
|
+
filters.push("e.kind = ?");
|
|
393
|
+
params.push(opts.kind);
|
|
394
|
+
}
|
|
395
|
+
const rows = this.db.prepare(
|
|
396
|
+
`SELECT e.id, e.text, e.summary, e.metadata, e.tags, e.scope, e.kind,
|
|
397
|
+
e.content_hash, e.created_at, e.updated_at,
|
|
398
|
+
v.vector AS vec_blob
|
|
399
|
+
FROM entries e
|
|
400
|
+
JOIN vectors v ON v.entry_id = e.id
|
|
401
|
+
WHERE ${filters.join(" AND ")}`
|
|
402
|
+
).all(...params);
|
|
403
|
+
const scored = [];
|
|
404
|
+
for (const row of rows) {
|
|
405
|
+
const blob = row.vec_blob;
|
|
406
|
+
const vec = decodeVector(blob);
|
|
407
|
+
const raw = cosineSimilarity(queryVec, vec);
|
|
408
|
+
const score = Math.max(0, Math.min(1, raw));
|
|
409
|
+
if (score < threshold) continue;
|
|
410
|
+
const entry = this.rowToEntry(row);
|
|
411
|
+
scored.push({ entry, score, providerId });
|
|
412
|
+
}
|
|
413
|
+
scored.sort((a, b) => b.score - a.score);
|
|
414
|
+
return scored.slice(0, limit);
|
|
415
|
+
}
|
|
416
|
+
list(opts = {}) {
|
|
417
|
+
this.assertOpen();
|
|
418
|
+
const where = [];
|
|
419
|
+
const params = [];
|
|
420
|
+
if (opts.scope !== void 0) {
|
|
421
|
+
where.push("scope = ?");
|
|
422
|
+
params.push(opts.scope);
|
|
423
|
+
}
|
|
424
|
+
if (opts.kind !== void 0) {
|
|
425
|
+
where.push("kind = ?");
|
|
426
|
+
params.push(opts.kind);
|
|
427
|
+
}
|
|
428
|
+
const sql = `SELECT * FROM entries ${where.length ? `WHERE ${where.join(" AND ")}` : ""}
|
|
429
|
+
ORDER BY updated_at DESC LIMIT ?`;
|
|
430
|
+
params.push(opts.limit ?? 100);
|
|
431
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
432
|
+
return rows.map((r) => this.rowToEntry(r));
|
|
433
|
+
}
|
|
434
|
+
async reindexAll() {
|
|
435
|
+
this.assertOpen();
|
|
436
|
+
const rows = this.db.prepare("SELECT id, text FROM entries").all();
|
|
437
|
+
let processed = 0;
|
|
438
|
+
let errors = 0;
|
|
439
|
+
for (const row of rows) {
|
|
440
|
+
try {
|
|
441
|
+
const result = await this.provider.embed([row.text]);
|
|
442
|
+
const v = result[0];
|
|
443
|
+
if (!v) {
|
|
444
|
+
errors++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
this.db.prepare(
|
|
448
|
+
`INSERT INTO vectors (entry_id, provider_id, dimensions, vector, created_at)
|
|
449
|
+
VALUES (?, ?, ?, ?, ?)
|
|
450
|
+
ON CONFLICT(entry_id, provider_id) DO UPDATE SET
|
|
451
|
+
vector = excluded.vector,
|
|
452
|
+
dimensions = excluded.dimensions,
|
|
453
|
+
created_at = excluded.created_at`
|
|
454
|
+
).run(
|
|
455
|
+
row.id,
|
|
456
|
+
this.provider.id,
|
|
457
|
+
v.length,
|
|
458
|
+
encodeVector(v),
|
|
459
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
460
|
+
);
|
|
461
|
+
processed++;
|
|
462
|
+
} catch {
|
|
463
|
+
errors++;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { processed, errors };
|
|
467
|
+
}
|
|
468
|
+
stats() {
|
|
469
|
+
this.assertOpen();
|
|
470
|
+
const entryCount = this.db.prepare("SELECT COUNT(*) AS n FROM entries").get().n;
|
|
471
|
+
const vectorCount = this.db.prepare("SELECT COUNT(*) AS n FROM vectors").get().n;
|
|
472
|
+
const providerRows = this.db.prepare("SELECT DISTINCT provider_id FROM vectors").all();
|
|
473
|
+
return {
|
|
474
|
+
entries: entryCount,
|
|
475
|
+
vectors: vectorCount,
|
|
476
|
+
providers: providerRows.map((r) => r.provider_id),
|
|
477
|
+
modelAvailable: true,
|
|
478
|
+
modelId: this.provider.id,
|
|
479
|
+
dimensions: this.provider.dimensions
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
close() {
|
|
483
|
+
if (this.closed) return;
|
|
484
|
+
this.closed = true;
|
|
485
|
+
this.db.close();
|
|
486
|
+
}
|
|
487
|
+
assertOpen() {
|
|
488
|
+
if (this.closed) throw new Error("VectorMemoryStore is closed");
|
|
489
|
+
}
|
|
490
|
+
rowToEntry(row, vectorRow) {
|
|
491
|
+
const summaryValue = row.summary;
|
|
492
|
+
const entry = {
|
|
493
|
+
id: row.id,
|
|
494
|
+
text: row.text,
|
|
495
|
+
summary: summaryValue ?? void 0,
|
|
496
|
+
metadata: safeParseJson(row.metadata, {}),
|
|
497
|
+
tags: safeParseJson(row.tags, []),
|
|
498
|
+
scope: row.scope,
|
|
499
|
+
kind: row.kind,
|
|
500
|
+
contentHash: row.content_hash,
|
|
501
|
+
createdAt: row.created_at,
|
|
502
|
+
updatedAt: row.updated_at,
|
|
503
|
+
providerId: vectorRow?.provider_id ?? "",
|
|
504
|
+
dimensions: vectorRow?.dimensions ?? 0
|
|
505
|
+
};
|
|
506
|
+
if (vectorRow?.vector) {
|
|
507
|
+
entry.vector = decodeVector(vectorRow.vector);
|
|
508
|
+
}
|
|
509
|
+
return entry;
|
|
510
|
+
}
|
|
511
|
+
async syncFromSage(sage) {
|
|
512
|
+
this.assertOpen();
|
|
513
|
+
const memories = await sage.listActiveMemories({ limit: 5e3 });
|
|
514
|
+
let indexed = 0;
|
|
515
|
+
let skipped = 0;
|
|
516
|
+
let failed = 0;
|
|
517
|
+
const errors = [];
|
|
518
|
+
for (const memory of memories) {
|
|
519
|
+
try {
|
|
520
|
+
const existing = this.db.prepare("SELECT id FROM entries WHERE content_hash = ? LIMIT 1").get(_VectorMemoryStore.contentHash(memory.text));
|
|
521
|
+
if (existing) {
|
|
522
|
+
skipped++;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
await this.remember({
|
|
526
|
+
text: memory.text,
|
|
527
|
+
summary: memory.summary ?? void 0,
|
|
528
|
+
metadata: { source: "sage", sageId: memory.id, ...memory.metadata ?? {} },
|
|
529
|
+
tags: memory.tags ?? [],
|
|
530
|
+
scope: "project",
|
|
531
|
+
kind: "note"
|
|
532
|
+
});
|
|
533
|
+
indexed++;
|
|
534
|
+
} catch (err) {
|
|
535
|
+
failed++;
|
|
536
|
+
errors.push({ memoryId: memory.id, message: errMsg(err) });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { scanned: memories.length, indexed, skipped, failed, errors };
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
function fallbackHashingProvider(dimensions) {
|
|
543
|
+
return new HashingEmbeddingProvider({ dimensions });
|
|
544
|
+
}
|
|
545
|
+
function safeParseJson(value, fallback) {
|
|
546
|
+
if (typeof value !== "string") return fallback;
|
|
547
|
+
try {
|
|
548
|
+
return JSON.parse(value);
|
|
549
|
+
} catch {
|
|
550
|
+
return fallback;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function errMsg(err) {
|
|
554
|
+
return err instanceof Error ? err.message : String(err);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/tools.ts
|
|
558
|
+
function createVectorMemoryTools(store) {
|
|
559
|
+
return [
|
|
560
|
+
vectorMemoryRememberTool(store),
|
|
561
|
+
vectorMemorySearchTool(store),
|
|
562
|
+
vectorMemoryStatsTool(store),
|
|
563
|
+
vectorMemoryForgetTool(store)
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
function vectorMemoryRememberTool(store) {
|
|
567
|
+
return {
|
|
568
|
+
name: "vector_memory_remember",
|
|
569
|
+
category: "Memory",
|
|
570
|
+
description: "Persist a piece of knowledge into the local vector memory store. The text is embedded with the active embedding provider (transformers.js when available, otherwise the sage hashing provider). Returns the new entry id and whether an embedding was stored.",
|
|
571
|
+
usageHint: "Store text you want to find later by *meaning*, not just exact keywords. Embeddings happen locally \u2014 no project text leaves the machine.",
|
|
572
|
+
permission: "confirm",
|
|
573
|
+
mutating: true,
|
|
574
|
+
riskTier: "standard",
|
|
575
|
+
timeoutMs: 5e3,
|
|
576
|
+
capabilities: ["memory.write"],
|
|
577
|
+
icon: "settings",
|
|
578
|
+
inputSchema: {
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
text: { type: "string", minLength: 1, description: "The text to embed and store." },
|
|
582
|
+
summary: { type: "string", description: "Optional short label." },
|
|
583
|
+
tags: {
|
|
584
|
+
type: "array",
|
|
585
|
+
items: { type: "string" },
|
|
586
|
+
description: "Optional tags for later filtering."
|
|
587
|
+
},
|
|
588
|
+
scope: {
|
|
589
|
+
type: "string",
|
|
590
|
+
enum: ["project", "user", "session"],
|
|
591
|
+
description: "Visibility scope. Defaults to `project`."
|
|
592
|
+
},
|
|
593
|
+
kind: {
|
|
594
|
+
type: "string",
|
|
595
|
+
enum: ["note", "fact", "summary", "snippet", "link"],
|
|
596
|
+
description: "Entry kind. Defaults to `note`."
|
|
597
|
+
},
|
|
598
|
+
metadata: {
|
|
599
|
+
type: "object",
|
|
600
|
+
additionalProperties: true,
|
|
601
|
+
description: "Free-form metadata persisted as JSON."
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
required: ["text"],
|
|
605
|
+
additionalProperties: false
|
|
606
|
+
},
|
|
607
|
+
execute: async (input) => {
|
|
608
|
+
const entry = await store.remember(input);
|
|
609
|
+
return { id: entry.id, hasVector: entry.vector !== void 0 };
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function vectorMemorySearchTool(store) {
|
|
614
|
+
return {
|
|
615
|
+
name: "vector_memory_search",
|
|
616
|
+
category: "Memory",
|
|
617
|
+
description: "Semantic search over the vector memory store. Embeds the query with the active provider and returns the top-k entries ranked by cosine similarity. Returns an empty list when the embedding provider is unavailable \u2014 callers should fall back to lexical search.",
|
|
618
|
+
usageHint: "Use when you want results ranked by meaning. Pairs well with sage `memory_search` for keyword precision.",
|
|
619
|
+
permission: "auto",
|
|
620
|
+
mutating: false,
|
|
621
|
+
riskTier: "safe",
|
|
622
|
+
timeoutMs: 5e3,
|
|
623
|
+
capabilities: ["memory.read"],
|
|
624
|
+
icon: "search",
|
|
625
|
+
inputSchema: {
|
|
626
|
+
type: "object",
|
|
627
|
+
properties: {
|
|
628
|
+
query: { type: "string", minLength: 1, description: "The natural-language query." },
|
|
629
|
+
limit: { type: "number", minimum: 1, maximum: 100, description: "Max results (default 10)." },
|
|
630
|
+
threshold: {
|
|
631
|
+
type: "number",
|
|
632
|
+
minimum: 0,
|
|
633
|
+
maximum: 1,
|
|
634
|
+
description: "Minimum cosine similarity. Results below the floor are dropped."
|
|
635
|
+
},
|
|
636
|
+
scope: {
|
|
637
|
+
type: "string",
|
|
638
|
+
enum: ["project", "user", "session"],
|
|
639
|
+
description: "Restrict to a scope."
|
|
640
|
+
},
|
|
641
|
+
kind: {
|
|
642
|
+
type: "string",
|
|
643
|
+
enum: ["note", "fact", "summary", "snippet", "link"],
|
|
644
|
+
description: "Restrict to a kind."
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
required: ["query"],
|
|
648
|
+
additionalProperties: false
|
|
649
|
+
},
|
|
650
|
+
execute: async (input) => {
|
|
651
|
+
const hits = await store.search(input.query, {
|
|
652
|
+
limit: input.limit !== void 0 ? input.limit : void 0,
|
|
653
|
+
threshold: input.threshold !== void 0 ? input.threshold : void 0,
|
|
654
|
+
scope: input.scope,
|
|
655
|
+
kind: input.kind
|
|
656
|
+
});
|
|
657
|
+
return {
|
|
658
|
+
hits: hits.map((h) => ({
|
|
659
|
+
id: h.entry.id,
|
|
660
|
+
score: h.score,
|
|
661
|
+
text: h.entry.text,
|
|
662
|
+
summary: h.entry.summary ?? void 0,
|
|
663
|
+
tags: h.entry.tags
|
|
664
|
+
}))
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function vectorMemoryStatsTool(store) {
|
|
670
|
+
return {
|
|
671
|
+
name: "vector_memory_stats",
|
|
672
|
+
category: "Memory",
|
|
673
|
+
description: "Return counts, providers, and dimensions for the local vector memory store.",
|
|
674
|
+
usageHint: "Cheap diagnostic \u2014 safe to call any time.",
|
|
675
|
+
permission: "auto",
|
|
676
|
+
mutating: false,
|
|
677
|
+
riskTier: "safe",
|
|
678
|
+
timeoutMs: 1e3,
|
|
679
|
+
capabilities: ["memory.read"],
|
|
680
|
+
icon: "search",
|
|
681
|
+
inputSchema: {
|
|
682
|
+
type: "object",
|
|
683
|
+
properties: {},
|
|
684
|
+
additionalProperties: false
|
|
685
|
+
},
|
|
686
|
+
execute: async () => store.stats()
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function vectorMemoryForgetTool(store) {
|
|
690
|
+
return {
|
|
691
|
+
name: "vector_memory_forget",
|
|
692
|
+
category: "Memory",
|
|
693
|
+
description: "Remove an entry (and its vector) from the vector memory store.",
|
|
694
|
+
usageHint: "Hard delete \u2014 no soft-delete tombstone. Use `vector_memory_search` to find the id first if you only have text.",
|
|
695
|
+
permission: "confirm",
|
|
696
|
+
mutating: true,
|
|
697
|
+
riskTier: "standard",
|
|
698
|
+
timeoutMs: 1e3,
|
|
699
|
+
capabilities: ["memory.write"],
|
|
700
|
+
icon: "settings",
|
|
701
|
+
inputSchema: {
|
|
702
|
+
type: "object",
|
|
703
|
+
properties: {
|
|
704
|
+
id: { type: "string", minLength: 1, description: "Entry id returned by `vector_memory_remember`." }
|
|
705
|
+
},
|
|
706
|
+
required: ["id"],
|
|
707
|
+
additionalProperties: false
|
|
708
|
+
},
|
|
709
|
+
execute: async (input) => ({ removed: store.forget(input.id) })
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
export {
|
|
713
|
+
DEFAULT_VECTOR_DIMENSIONS,
|
|
714
|
+
DEFAULT_VECTOR_DTYPE,
|
|
715
|
+
DEFAULT_VECTOR_MODEL_ID,
|
|
716
|
+
TransformersEmbeddingProvider,
|
|
717
|
+
VECTOR_DIMENSIONS_KEY,
|
|
718
|
+
VECTOR_PROVIDER_KEY,
|
|
719
|
+
VECTOR_SCHEMA_VERSION,
|
|
720
|
+
VectorMemoryError,
|
|
721
|
+
VectorMemoryProviderUnavailableError,
|
|
722
|
+
VectorMemoryStore,
|
|
723
|
+
createVectorMemoryTools,
|
|
724
|
+
decodeVector,
|
|
725
|
+
encodeVector,
|
|
726
|
+
fallbackHashingProvider,
|
|
727
|
+
initVectorSchema
|
|
728
|
+
};
|
|
729
|
+
//# sourceMappingURL=index.js.map
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite schema for the vector memory store.
|
|
3
|
+
*
|
|
4
|
+
* Two tables:
|
|
5
|
+
* - `entries` — text + metadata, no embedding column
|
|
6
|
+
* - `vectors` — (entry_id, provider_id) PK, raw float32 blob
|
|
7
|
+
*
|
|
8
|
+
* The vectors table is keyed by (entry_id, provider_id) so a model swap
|
|
9
|
+
* invalidates only the old provider rows on insert — no mixed-vector search.
|
|
10
|
+
* A companion `schema_meta` table records the active provider id and dims.
|
|
11
|
+
*/
|
|
12
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
13
|
+
export declare const VECTOR_SCHEMA_VERSION = 1;
|
|
14
|
+
export declare const VECTOR_PROVIDER_KEY = "active_provider_id";
|
|
15
|
+
export declare const VECTOR_DIMENSIONS_KEY = "active_provider_dimensions";
|
|
16
|
+
export declare function initVectorSchema(db: DatabaseSync): void;
|
|
17
|
+
/** Encode a Float32Array to a SQLite BLOB (Buffer). */
|
|
18
|
+
export declare function encodeVector(vec: Float32Array): Buffer;
|
|
19
|
+
/** Decode a SQLite BLOB (Buffer or Uint8Array) back to a Float32Array. */
|
|
20
|
+
export declare function decodeVector(buf: Buffer | Uint8Array): Float32Array;
|
|
21
|
+
//# sourceMappingURL=schema.d.ts.map
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { HashingEmbeddingProvider } from '@wrongstack/sage';
|
|
2
|
+
import type { SageSyncReport, VectorEntry, VectorEntryInput, VectorEntryWithVector, VectorKind, VectorMemoryStoreOptions, VectorScope, VectorSearchHit, VectorSearchOptions, VectorStoreStats } from './types.js';
|
|
3
|
+
export declare class VectorMemoryStore {
|
|
4
|
+
private readonly db;
|
|
5
|
+
private readonly dbPath;
|
|
6
|
+
private readonly provider;
|
|
7
|
+
private closed;
|
|
8
|
+
constructor(opts: VectorMemoryStoreOptions);
|
|
9
|
+
get activeProviderId(): string;
|
|
10
|
+
private recordActiveProvider;
|
|
11
|
+
static contentHash(text: string): string;
|
|
12
|
+
remember(input: VectorEntryInput): Promise<VectorEntryWithVector>;
|
|
13
|
+
get(id: string): VectorEntryWithVector | undefined;
|
|
14
|
+
forget(id: string): boolean;
|
|
15
|
+
search(query: string, opts?: VectorSearchOptions): Promise<VectorSearchHit[]>;
|
|
16
|
+
list(opts?: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
scope?: VectorScope;
|
|
19
|
+
kind?: VectorKind;
|
|
20
|
+
}): VectorEntry[];
|
|
21
|
+
reindexAll(): Promise<{
|
|
22
|
+
processed: number;
|
|
23
|
+
errors: number;
|
|
24
|
+
}>;
|
|
25
|
+
stats(): VectorStoreStats;
|
|
26
|
+
close(): void;
|
|
27
|
+
private assertOpen;
|
|
28
|
+
private rowToEntry;
|
|
29
|
+
syncFromSage(sage: SageSyncSource): Promise<SageSyncReport>;
|
|
30
|
+
}
|
|
31
|
+
export interface SageSyncSource {
|
|
32
|
+
listActiveMemories(opts: {
|
|
33
|
+
limit: number;
|
|
34
|
+
}): Promise<Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
text: string;
|
|
37
|
+
summary?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
}>>;
|
|
41
|
+
}
|
|
42
|
+
export declare function fallbackHashingProvider(dimensions: number): HashingEmbeddingProvider;
|
|
43
|
+
//# sourceMappingURL=store.d.ts.map
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector memory tools — agent-facing surface for the vector store.
|
|
3
|
+
*
|
|
4
|
+
* The tools mirror sage's tool patterns:
|
|
5
|
+
* - reads (`vector_memory_search`, `vector_memory_stats`) → `permission: 'auto'`
|
|
6
|
+
* - writes (`vector_memory_remember`, `vector_memory_forget`) → `permission: 'confirm'`
|
|
7
|
+
*
|
|
8
|
+
* All tools accept the store directly so hosts can wire them however they
|
|
9
|
+
* like — there's no implicit dependency on the sage runtime surface.
|
|
10
|
+
*/
|
|
11
|
+
import type { Tool } from '@wrongstack/core/types';
|
|
12
|
+
import type { VectorMemoryStore } from './store.js';
|
|
13
|
+
export declare function createVectorMemoryTools(store: VectorMemoryStore): Tool[];
|
|
14
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransformersEmbeddingProvider — wraps `@huggingface/transformers`'s
|
|
3
|
+
* feature-extraction pipeline as a sage `EmbeddingProvider`.
|
|
4
|
+
*
|
|
5
|
+
* The provider is lazy: nothing is imported until `embed()` is first called.
|
|
6
|
+
* If `@huggingface/transformers` is not installed (it's listed as an
|
|
7
|
+
* optionalDependency), `isAvailable()` returns false and `embed()` throws
|
|
8
|
+
* a descriptive `VectorMemoryProviderUnavailableError` so callers can
|
|
9
|
+
* fall back to `HashingEmbeddingProvider`.
|
|
10
|
+
*
|
|
11
|
+
* Default model: `Xenova/all-MiniLM-L6-v2` (384 dims, ~25MB quantized).
|
|
12
|
+
* On first use the model is fetched from the Hugging Face Hub and cached
|
|
13
|
+
* under `cacheDir` (default: `.wrongstack/vector-memory/models`).
|
|
14
|
+
*/
|
|
15
|
+
import type { EmbeddingProvider } from '@wrongstack/sage';
|
|
16
|
+
export declare const DEFAULT_VECTOR_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
17
|
+
export declare const DEFAULT_VECTOR_DIMENSIONS = 384;
|
|
18
|
+
export declare const DEFAULT_VECTOR_DTYPE = "q8";
|
|
19
|
+
export interface TransformersEmbeddingProviderOptions {
|
|
20
|
+
/** Hugging Face Hub model id (default `Xenova/all-MiniLM-L6-v2`). */
|
|
21
|
+
modelId?: string;
|
|
22
|
+
/** Local cache directory for downloaded model files. */
|
|
23
|
+
cacheDir?: string;
|
|
24
|
+
/** Inference dtype — `q8` is ~4x smaller and ~2x faster than `fp32`. */
|
|
25
|
+
dtype?: 'q8' | 'fp16' | 'fp32' | 'q4';
|
|
26
|
+
/** Device selector — Node builds default to `cpu` via onnxruntime-node. */
|
|
27
|
+
device?: 'cpu' | 'wasm' | 'webgpu';
|
|
28
|
+
/** Maximum texts per batch when calling the pipeline. */
|
|
29
|
+
batchSize?: number;
|
|
30
|
+
/** Maximum characters per input before the provider truncates. */
|
|
31
|
+
maxChars?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Disable remote model downloads. Useful in offline / air-gapped runs
|
|
34
|
+
* — the provider will surface a clear error when the model isn't cached.
|
|
35
|
+
*/
|
|
36
|
+
allowRemoteModels?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare class TransformersEmbeddingProvider implements EmbeddingProvider {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly dimensions: number;
|
|
41
|
+
private readonly modelId;
|
|
42
|
+
private readonly cacheDir;
|
|
43
|
+
private readonly dtype;
|
|
44
|
+
private readonly device;
|
|
45
|
+
private readonly batchSize;
|
|
46
|
+
private readonly maxChars;
|
|
47
|
+
private readonly allowRemote;
|
|
48
|
+
private extractor;
|
|
49
|
+
private loadPromise;
|
|
50
|
+
constructor(opts?: TransformersEmbeddingProviderOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Synchronous capability check. Returns false when the optional
|
|
53
|
+
* `@huggingface/transformers` dependency is not installed.
|
|
54
|
+
*
|
|
55
|
+
* NOTE: this probes via dynamic import and caches the result, but does
|
|
56
|
+
* NOT load the model itself — model loading is deferred to `embed()`.
|
|
57
|
+
*/
|
|
58
|
+
isAvailable(): Promise<boolean>;
|
|
59
|
+
embed(texts: string[]): Promise<Float32Array[]>;
|
|
60
|
+
/** Truncate + normalize text before embedding. */
|
|
61
|
+
private prepare;
|
|
62
|
+
private tensorToVectors;
|
|
63
|
+
private getExtractor;
|
|
64
|
+
private loadExtractor;
|
|
65
|
+
private loadModule;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=transformers-provider.d.ts.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Memory — public type surface.
|
|
3
|
+
*
|
|
4
|
+
* The store persists entries (text + metadata) alongside their vector
|
|
5
|
+
* embeddings, keyed by provider id so a model change triggers re-indexing.
|
|
6
|
+
*/
|
|
7
|
+
import type { EmbeddingProvider } from '@wrongstack/sage';
|
|
8
|
+
export type VectorScope = 'project' | 'user' | 'session';
|
|
9
|
+
export type VectorKind = 'note' | 'fact' | 'summary' | 'snippet' | 'link';
|
|
10
|
+
export interface VectorEntryInput {
|
|
11
|
+
text: string;
|
|
12
|
+
/** Optional short label — stored as-is, returned in search results. */
|
|
13
|
+
summary?: string | undefined;
|
|
14
|
+
/** Free-form metadata persisted as JSON. Use for source URLs, anchors, etc. */
|
|
15
|
+
metadata?: Record<string, unknown> | undefined;
|
|
16
|
+
tags?: string[] | undefined;
|
|
17
|
+
scope?: VectorScope | undefined;
|
|
18
|
+
kind?: VectorKind | undefined;
|
|
19
|
+
/** Embedding provider id override for this write. Defaults to the store's provider. */
|
|
20
|
+
providerId?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
export interface VectorEntry {
|
|
23
|
+
id: string;
|
|
24
|
+
text: string;
|
|
25
|
+
summary?: string | undefined;
|
|
26
|
+
metadata: Record<string, unknown>;
|
|
27
|
+
tags: string[];
|
|
28
|
+
scope: VectorScope;
|
|
29
|
+
kind: VectorKind;
|
|
30
|
+
/** Stable hash of the entry text — used to detect duplicate re-writes. */
|
|
31
|
+
contentHash: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export interface VectorEntryWithVector extends VectorEntry {
|
|
36
|
+
/** Provider id that produced the stored vector. */
|
|
37
|
+
providerId: string;
|
|
38
|
+
/** Vector dimensions (matches `EmbeddingProvider.dimensions`). */
|
|
39
|
+
dimensions: number;
|
|
40
|
+
/** Decoded vector; absent when no embedding has been computed yet. */
|
|
41
|
+
vector?: Float32Array | undefined;
|
|
42
|
+
}
|
|
43
|
+
export interface VectorSearchOptions {
|
|
44
|
+
limit?: number | undefined;
|
|
45
|
+
/** Minimum cosine similarity [0, 1]. Results below the floor are dropped. */
|
|
46
|
+
threshold?: number | undefined;
|
|
47
|
+
scope?: VectorScope | undefined;
|
|
48
|
+
kind?: VectorKind | undefined;
|
|
49
|
+
/** Provider id override for the query embedding. Defaults to the store's provider. */
|
|
50
|
+
providerId?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
export interface VectorSearchHit {
|
|
53
|
+
entry: VectorEntry;
|
|
54
|
+
/** Cosine similarity in [-1, 1]. The store clamps to [0, 1] before returning. */
|
|
55
|
+
score: number;
|
|
56
|
+
providerId: string;
|
|
57
|
+
}
|
|
58
|
+
export interface VectorStoreStats {
|
|
59
|
+
entries: number;
|
|
60
|
+
vectors: number;
|
|
61
|
+
providers: string[];
|
|
62
|
+
modelAvailable: boolean;
|
|
63
|
+
modelId: string;
|
|
64
|
+
dimensions: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The store accepts any `EmbeddingProvider` from `@wrongstack/sage`. In
|
|
68
|
+
* production the caller wires a `TransformersEmbeddingProvider`; tests and
|
|
69
|
+
* offline runs can substitute a `HashingEmbeddingProvider` or a fake.
|
|
70
|
+
*/
|
|
71
|
+
export interface VectorMemoryStoreOptions {
|
|
72
|
+
/** Embedding provider used for both write-side and query-side embedding. */
|
|
73
|
+
provider: EmbeddingProvider;
|
|
74
|
+
/** Absolute project root whose `.wrongstack/vector-memory/` directory owns the SQLite db. */
|
|
75
|
+
projectRoot: string;
|
|
76
|
+
/** Override the storage subdirectory (default `.wrongstack/vector-memory`). */
|
|
77
|
+
directory?: string;
|
|
78
|
+
/** Override the SQLite file name (default `vector-memory.db`). */
|
|
79
|
+
filename?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returned by `syncFromSage` — surfaces what was indexed, what was skipped,
|
|
83
|
+
* and any failures so the caller can log or retry.
|
|
84
|
+
*/
|
|
85
|
+
export interface SageSyncReport {
|
|
86
|
+
scanned: number;
|
|
87
|
+
indexed: number;
|
|
88
|
+
skipped: number;
|
|
89
|
+
failed: number;
|
|
90
|
+
errors: Array<{
|
|
91
|
+
memoryId: string;
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Float32Array <-> SQLite BLOB codec.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the pattern used by `packages/tools/src/codebase-index/vector-search.ts`
|
|
5
|
+
* so that the on-disk byte layout is portable between the codebase-index
|
|
6
|
+
* and vector-memory stores. node:sqlite returns `Uint8Array` for BLOB columns,
|
|
7
|
+
* so we read through `DataView` regardless of the runtime type.
|
|
8
|
+
*/
|
|
9
|
+
export declare function encodeVector(vec: Float32Array): Buffer;
|
|
10
|
+
export declare function decodeVector(buf: Buffer | Uint8Array): Float32Array;
|
|
11
|
+
//# sourceMappingURL=vector-codec.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wrongstack/vector-memory",
|
|
3
|
+
"version": "0.308.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "WrongStack Vector Memory — an additional vector-search memory store powered by @huggingface/transformers (local ONNX embeddings), alongside the SAGE lexical memory system.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/WrongStack/WrongStack.git",
|
|
9
|
+
"directory": "packages/vector-memory"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/WrongStack/WrongStack#readme",
|
|
12
|
+
"bugs": "https://github.com/WrongStack/WrongStack/issues",
|
|
13
|
+
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"!dist/**/*.map",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@wrongstack/core": "0.308.0",
|
|
31
|
+
"@wrongstack/sage": "0.308.0"
|
|
32
|
+
},
|
|
33
|
+
"optionalDependencies": {
|
|
34
|
+
"@huggingface/transformers": "^4.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^26.2.0",
|
|
38
|
+
"typescript": "^7.0.2",
|
|
39
|
+
"vitest": "^4.1.10"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "node ../../scripts/build-package.mjs",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:integration": "WRONGSTACK_VECTOR_INTEGRATION=1 vitest run tests/integration.test.ts",
|
|
50
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
51
|
+
}
|
|
52
|
+
}
|