@customize-agent/knowledge 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,7 +34,7 @@ export declare class KnowledgeBaseManager {
34
34
  readonly projectId?: string;
35
35
  readonly kbPath: string;
36
36
  readonly store: IndexStateStore;
37
- private readonly chromaClient;
37
+ private readonly sqliteVecClient;
38
38
  private readonly classifier;
39
39
  private readonly scanner;
40
40
  private readonly collections;
@@ -10,7 +10,7 @@ import { HashEmbeddingProvider } from '../embedding/embedding-provider.js';
10
10
  import { ContentExtractor } from '../extraction/content-extractor.js';
11
11
  import { FederationSearch } from '../search/federation-search.js';
12
12
  import { CollectionManager } from '../vector/collection-manager.js';
13
- import { ChromaHttpClient, ChromaVectorStore } from '../vector/chroma-store.js';
13
+ import { SQLiteVecClient, SQLiteVecVectorStore } from '../vector/sqlite-vec-store.js';
14
14
  import { VectorIndexer } from '../vector/vector-indexer.js';
15
15
  import { ChangeTracker } from './change-tracker.js';
16
16
  import { KnowledgeFileScanner } from './file-scanner.js';
@@ -22,7 +22,7 @@ export class KnowledgeBaseManager {
22
22
  projectId;
23
23
  kbPath;
24
24
  store;
25
- chromaClient = new ChromaHttpClient();
25
+ sqliteVecClient;
26
26
  classifier = new FileClassifier();
27
27
  scanner = new KnowledgeFileScanner();
28
28
  collections = new CollectionManager();
@@ -47,17 +47,20 @@ export class KnowledgeBaseManager {
47
47
  this.llmProvider = options.llmProvider;
48
48
  this.onProgress = options.onProgress;
49
49
  const storageRoot = options.storageRoot ?? path.join(os.homedir(), USER_DATA_DIR);
50
+ let dbPath;
50
51
  if (this.scope === 'global') {
51
52
  this.kbPath = options.kbPath ?? path.join(storageRoot, GLOBAL_KNOWLEDGE_DIR);
52
- this.store = new IndexStateStore(path.join(storageRoot, 'global-knowledge.db'));
53
+ dbPath = path.join(storageRoot, 'global-knowledge.db');
53
54
  }
54
55
  else {
55
56
  if (!options.projectRoot || !options.projectId) {
56
57
  throw new Error('project knowledge base requires projectRoot and projectId');
57
58
  }
58
59
  this.kbPath = options.kbPath ?? getProjectKbPath(options.projectRoot);
59
- this.store = new IndexStateStore(path.join(storageRoot, 'projects', options.projectId, 'kb.db'));
60
+ dbPath = path.join(storageRoot, 'projects', options.projectId, 'kb.db');
60
61
  }
62
+ this.store = new IndexStateStore(dbPath);
63
+ this.sqliteVecClient = new SQLiteVecClient({ dbPath });
61
64
  }
62
65
  initialize() {
63
66
  if (this.scope === 'project' && this.projectRoot) {
@@ -228,7 +231,7 @@ export class KnowledgeBaseManager {
228
231
  message: vectorDeferred
229
232
  ? '解析、切片和 SQLite 入库已完成,向量入库后台执行'
230
233
  : vectorStatus.status === 'error'
231
- ? '解析和切片已完成,ChromaDB 未连接,向量待入库'
234
+ ? '解析和切片已完成,sqlite-vec 向量待入库'
232
235
  : '知识库索引完成',
233
236
  chunkCount: stats.chunkCount,
234
237
  vectorStatus,
@@ -380,7 +383,7 @@ export class KnowledgeBaseManager {
380
383
  const targetPath = this.resolveKbRelativePath(normalized);
381
384
  if (fs.existsSync(targetPath))
382
385
  fs.unlinkSync(targetPath);
383
- // 同步删除 ChromaDB 向量数据,避免孤儿向量污染搜索结果
386
+ // 同步删除 sqlite-vec 向量数据,避免孤儿向量污染搜索结果
384
387
  const record = this.store.listRecords().find(r => r.relativePath === normalized);
385
388
  if (record) {
386
389
  await this.deleteVectorFile(record.collectionName, normalized);
@@ -409,7 +412,7 @@ export class KnowledgeBaseManager {
409
412
  const chunks = this.store.listChunks(options);
410
413
  for (const collectionName of new Set(chunks.map(chunk => chunk.collectionName)))
411
414
  this.ensureVectorStore(collectionName);
412
- this.reportProgress({ stage: 'vectorizing', percent: 85, message: `正在写入 ChromaDB 向量库,共 ${chunks.length} 个切片`, chunkCount: chunks.length });
415
+ this.reportProgress({ stage: 'vectorizing', percent: 85, message: `正在写入 sqlite-vec 向量库,共 ${chunks.length} 个切片`, chunkCount: chunks.length });
413
416
  const indexer = new VectorIndexer(this.embeddingProvider, this.vectorStores);
414
417
  try {
415
418
  const results = await indexer.indexChunks(chunks);
@@ -426,7 +429,7 @@ export class KnowledgeBaseManager {
426
429
  this.store.setMetadata('vector_index_status', 'error');
427
430
  this.store.setMetadata('vector_index_error', message);
428
431
  this.store.setMetadata('last_vector_index_at', String(Date.now()));
429
- this.reportProgress({ stage: 'error', percent: 85, message: 'ChromaDB 向量入库失败', chunkCount: chunks.length, vectorStatus: this.getVectorStatus() });
432
+ this.reportProgress({ stage: 'error', percent: 85, message: 'sqlite-vec 向量入库失败', chunkCount: chunks.length, vectorStatus: this.getVectorStatus() });
430
433
  return [];
431
434
  }
432
435
  }
@@ -450,7 +453,7 @@ export class KnowledgeBaseManager {
450
453
  error: this.store.getMetadata('vector_index_error') || undefined,
451
454
  indexedChunks: Number(this.store.getMetadata('vector_indexed_chunks') ?? 0),
452
455
  lastIndexedAt: Number(this.store.getMetadata('last_vector_index_at') ?? 0),
453
- backend: `ChromaDB (${this.chromaClient.baseUrl})`,
456
+ backend: `SQLite + sqlite-vec (${this.sqliteVecClient.dbPath})`,
454
457
  };
455
458
  }
456
459
  async rewriteQueries(query) {
@@ -673,7 +676,7 @@ ${resultsText}
673
676
  ensureVectorStore(collectionName) {
674
677
  if (this.vectorStores.has(collectionName))
675
678
  return;
676
- this.vectorStores.set(collectionName, new ChromaVectorStore(this.chromaClient, collectionName));
679
+ this.vectorStores.set(collectionName, new SQLiteVecVectorStore(this.sqliteVecClient, collectionName));
677
680
  }
678
681
  async deleteVectorFile(collectionName, relativePath) {
679
682
  this.ensureVectorStore(collectionName);
@@ -492,6 +492,11 @@ export class ContentExtractor {
492
492
  }
493
493
  async extractRasterImage(file) {
494
494
  const metadata = { extractionMode: 'builtin_tesseract_ocr_isolated', vectorizable: true };
495
+ if (process.env.CUSTOMIZE_AGENT_DISABLE_OCR === '1') {
496
+ metadata.extractionMode = 'raster_image_metadata';
497
+ metadata.contentCoverage = 'metadata_filename';
498
+ return { text: this.metadataOnlyText(file), metadata, warnings: ['OCR disabled; indexed image metadata only'] };
499
+ }
495
500
  const validationError = this.validateRasterImage(file.absolutePath);
496
501
  if (validationError) {
497
502
  metadata.contentCoverage = 'invalid_image';
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export { MultiProjectManager } from './core/multi-project-manager.js';
13
13
  export { computeProjectId } from './core/project-id.js';
14
14
  export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
15
15
  export { ProjectRegistry } from './core/project-registry.js';
16
- export { ChromaHttpClient, ChromaVectorStore, type ChromaClientOptions } from './vector/chroma-store.js';
16
+ export { SQLiteVecClient, SQLiteVecVectorStore, type SQLiteVecClientOptions } from './vector/sqlite-vec-store.js';
17
17
  export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
18
18
  export type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './vector/types.js';
19
19
  export { VectorIndexer, type VectorIndexResult } from './vector/vector-indexer.js';
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ export { MultiProjectManager } from './core/multi-project-manager.js';
15
15
  export { computeProjectId } from './core/project-id.js';
16
16
  export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
17
17
  export { ProjectRegistry } from './core/project-registry.js';
18
- export { ChromaHttpClient, ChromaVectorStore } from './vector/chroma-store.js';
18
+ export { SQLiteVecClient, SQLiteVecVectorStore } from './vector/sqlite-vec-store.js';
19
19
  export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
20
20
  export { VectorIndexer } from './vector/vector-indexer.js';
21
21
  export { FederationSearch } from './search/federation-search.js';
@@ -0,0 +1,37 @@
1
+ import type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
2
+ interface QdrantSearchPoint {
3
+ id: string | number;
4
+ score?: number;
5
+ payload?: Record<string, unknown>;
6
+ }
7
+ export interface QdrantClientOptions {
8
+ baseUrl?: string;
9
+ }
10
+ export declare class QdrantHttpClient implements CollectionClient {
11
+ readonly baseUrl: string;
12
+ constructor(options?: QdrantClientOptions);
13
+ heartbeat(): Promise<boolean>;
14
+ getOrCreateCollection(name: string, metadata?: Record<string, unknown>): Promise<VectorCollectionInfo>;
15
+ listCollections(): Promise<VectorCollectionInfo[]>;
16
+ deleteCollection(name: string): Promise<void>;
17
+ upsert(collectionName: string, documents: VectorDocument[]): Promise<void>;
18
+ deleteWhere(collectionName: string, where: Record<string, string | number | boolean>): Promise<void>;
19
+ search(collectionName: string, query: VectorSearchQuery): Promise<QdrantSearchPoint[]>;
20
+ private getCollection;
21
+ private toFilter;
22
+ private pointId;
23
+ private toPayload;
24
+ private request;
25
+ }
26
+ export declare class QdrantVectorStore implements VectorStoreInterface {
27
+ private readonly client;
28
+ readonly collectionName: string;
29
+ constructor(client: QdrantHttpClient, collectionName: string);
30
+ ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
31
+ upsert(documents: VectorDocument[]): Promise<void>;
32
+ deleteByFilePath(filePath: string): Promise<void>;
33
+ search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
34
+ private payloadString;
35
+ private toMetadata;
36
+ }
37
+ export {};
@@ -0,0 +1,172 @@
1
+ import * as crypto from 'node:crypto';
2
+ export class QdrantHttpClient {
3
+ baseUrl;
4
+ constructor(options = {}) {
5
+ this.baseUrl = options.baseUrl ?? process.env.QDRANT_URL ?? process.env.QDRANT_BASE_URL ?? 'http://127.0.0.1:6333';
6
+ }
7
+ async heartbeat() {
8
+ try {
9
+ await this.request('/collections', {}, 3000);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ async getOrCreateCollection(name, metadata = {}) {
17
+ const existing = await this.getCollection(name);
18
+ if (existing)
19
+ return existing;
20
+ const size = Number(metadata.embedding_dimension ?? process.env.QDRANT_VECTOR_SIZE ?? 384);
21
+ await this.request(`/collections/${encodeURIComponent(name)}`, {
22
+ method: 'PUT',
23
+ body: JSON.stringify({
24
+ vectors: { size, distance: 'Cosine' },
25
+ on_disk_payload: true,
26
+ }),
27
+ }, 30000);
28
+ return { name, metadata };
29
+ }
30
+ async listCollections() {
31
+ const response = await this.request('/collections', {}, 10000);
32
+ return (response.result?.collections ?? []).map(collection => ({ name: collection.name }));
33
+ }
34
+ async deleteCollection(name) {
35
+ await this.request(`/collections/${encodeURIComponent(name)}`, { method: 'DELETE' }, 30000);
36
+ }
37
+ async upsert(collectionName, documents) {
38
+ if (documents.length === 0)
39
+ return;
40
+ await this.request(`/collections/${encodeURIComponent(collectionName)}/points?wait=true`, {
41
+ method: 'PUT',
42
+ body: JSON.stringify({
43
+ points: documents.map(document => ({
44
+ id: this.pointId(document.id),
45
+ vector: document.embedding,
46
+ payload: this.toPayload({
47
+ ...document.metadata,
48
+ id: document.id,
49
+ content: document.content,
50
+ }),
51
+ })),
52
+ }),
53
+ }, 60000);
54
+ }
55
+ async deleteWhere(collectionName, where) {
56
+ await this.request(`/collections/${encodeURIComponent(collectionName)}/points/delete?wait=true`, {
57
+ method: 'POST',
58
+ body: JSON.stringify({
59
+ filter: this.toFilter(where),
60
+ }),
61
+ }, 30000);
62
+ }
63
+ async search(collectionName, query) {
64
+ const response = await this.request(`/collections/${encodeURIComponent(collectionName)}/points/search`, {
65
+ method: 'POST',
66
+ body: JSON.stringify({
67
+ vector: query.queryEmbedding,
68
+ limit: query.topK,
69
+ filter: query.where ? this.toFilter(query.where) : undefined,
70
+ with_payload: true,
71
+ }),
72
+ }, 30000);
73
+ return response.result ?? [];
74
+ }
75
+ async getCollection(name) {
76
+ try {
77
+ await this.request(`/collections/${encodeURIComponent(name)}`, {}, 10000);
78
+ return { name };
79
+ }
80
+ catch (error) {
81
+ if (error instanceof Error && error.message.includes('404'))
82
+ return undefined;
83
+ throw error;
84
+ }
85
+ }
86
+ toFilter(where) {
87
+ return {
88
+ must: Object.entries(where).map(([key, value]) => ({ key, match: { value } })),
89
+ };
90
+ }
91
+ pointId(id) {
92
+ const hash = crypto.createHash('sha256').update(id).digest('hex');
93
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
94
+ }
95
+ toPayload(payload) {
96
+ const result = {};
97
+ for (const [key, value] of Object.entries(payload)) {
98
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
99
+ result[key] = value;
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+ async request(path, init = {}, timeoutMs = 3000) {
105
+ const controller = new AbortController();
106
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
107
+ let response;
108
+ try {
109
+ response = await fetch(`${this.baseUrl}${path}`, {
110
+ ...init,
111
+ signal: controller.signal,
112
+ headers: {
113
+ 'content-type': 'application/json',
114
+ ...(init.headers ?? {}),
115
+ },
116
+ });
117
+ }
118
+ finally {
119
+ clearTimeout(timeout);
120
+ }
121
+ if (!response.ok) {
122
+ const body = await response.text().catch(() => '');
123
+ throw new Error(`Qdrant request failed: ${response.status} ${response.statusText} ${body}`.trim());
124
+ }
125
+ if (response.status === 204)
126
+ return undefined;
127
+ return await response.json();
128
+ }
129
+ }
130
+ export class QdrantVectorStore {
131
+ client;
132
+ collectionName;
133
+ constructor(client, collectionName) {
134
+ this.client = client;
135
+ this.collectionName = collectionName;
136
+ }
137
+ async ensureCollection(metadata) {
138
+ await this.client.getOrCreateCollection(this.collectionName, metadata);
139
+ }
140
+ async upsert(documents) {
141
+ await this.client.upsert(this.collectionName, documents);
142
+ }
143
+ async deleteByFilePath(filePath) {
144
+ await this.client.deleteWhere(this.collectionName, { file_path: filePath });
145
+ }
146
+ async search(query) {
147
+ const points = await this.client.search(this.collectionName, query);
148
+ return points.map(point => ({
149
+ collection: this.collectionName,
150
+ score: Number(point.score ?? 0),
151
+ document: {
152
+ id: this.payloadString(point.payload?.id) ?? String(point.id),
153
+ content: this.payloadString(point.payload?.content) ?? '',
154
+ metadata: this.toMetadata(point.payload ?? {}),
155
+ },
156
+ }));
157
+ }
158
+ payloadString(value) {
159
+ return typeof value === 'string' ? value : undefined;
160
+ }
161
+ toMetadata(payload) {
162
+ const result = {};
163
+ for (const [key, value] of Object.entries(payload)) {
164
+ if (key === 'content')
165
+ continue;
166
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) {
167
+ result[key] = value;
168
+ }
169
+ }
170
+ return result;
171
+ }
172
+ }
@@ -0,0 +1,38 @@
1
+ import type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
2
+ interface SearchRow {
3
+ id: string;
4
+ content: string;
5
+ metadata_json: string | null;
6
+ distance: number;
7
+ }
8
+ export interface SQLiteVecClientOptions {
9
+ dbPath: string;
10
+ }
11
+ export declare class SQLiteVecClient implements CollectionClient {
12
+ readonly dbPath: string;
13
+ private readonly db;
14
+ constructor(options: SQLiteVecClientOptions);
15
+ getOrCreateCollection(name: string, metadata?: Record<string, unknown>): Promise<VectorCollectionInfo>;
16
+ listCollections(): Promise<VectorCollectionInfo[]>;
17
+ deleteCollection(name: string): Promise<void>;
18
+ upsert(collectionName: string, documents: VectorDocument[]): void;
19
+ deleteWhere(collectionName: string, where: Record<string, string | number | boolean>): void;
20
+ deleteByFilePath(collectionName: string, filePath: string): void;
21
+ search(collectionName: string, query: VectorSearchQuery): SearchRow[];
22
+ private getCollection;
23
+ private requireCollection;
24
+ private tableName;
25
+ private parseMetadata;
26
+ private metadataString;
27
+ }
28
+ export declare class SQLiteVecVectorStore implements VectorStoreInterface {
29
+ private readonly client;
30
+ readonly collectionName: string;
31
+ constructor(client: SQLiteVecClient, collectionName: string);
32
+ ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
33
+ upsert(documents: VectorDocument[]): Promise<void>;
34
+ deleteByFilePath(filePath: string): Promise<void>;
35
+ search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
36
+ private parseDocumentMetadata;
37
+ }
38
+ export {};
@@ -0,0 +1,203 @@
1
+ import Database from 'better-sqlite3';
2
+ import * as crypto from 'node:crypto';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import * as sqliteVec from 'sqlite-vec';
6
+ export class SQLiteVecClient {
7
+ dbPath;
8
+ db;
9
+ constructor(options) {
10
+ this.dbPath = options.dbPath;
11
+ fs.mkdirSync(path.dirname(options.dbPath), { recursive: true });
12
+ this.db = new Database(options.dbPath);
13
+ this.db.pragma('journal_mode = WAL');
14
+ sqliteVec.load(this.db);
15
+ this.db.exec(`
16
+ CREATE TABLE IF NOT EXISTS vector_collections (
17
+ name TEXT PRIMARY KEY,
18
+ table_name TEXT NOT NULL UNIQUE,
19
+ dimension INTEGER NOT NULL,
20
+ metadata_json TEXT,
21
+ created_at INTEGER NOT NULL
22
+ );
23
+ CREATE TABLE IF NOT EXISTS vector_documents (
24
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ collection_name TEXT NOT NULL,
26
+ id TEXT NOT NULL,
27
+ content TEXT NOT NULL,
28
+ metadata_json TEXT,
29
+ file_path TEXT,
30
+ vector_rowid INTEGER,
31
+ created_at INTEGER NOT NULL,
32
+ updated_at INTEGER NOT NULL,
33
+ UNIQUE(collection_name, id)
34
+ );
35
+ CREATE INDEX IF NOT EXISTS idx_vector_documents_collection_file ON vector_documents(collection_name, file_path);
36
+ `);
37
+ try {
38
+ this.db.exec('ALTER TABLE vector_documents ADD COLUMN vector_rowid INTEGER');
39
+ }
40
+ catch { /* column already exists */ }
41
+ }
42
+ async getOrCreateCollection(name, metadata = {}) {
43
+ const existing = this.getCollection(name);
44
+ if (existing)
45
+ return { name: existing.name, metadata: this.parseMetadata(existing.metadata_json) };
46
+ const dimension = Number(metadata.embedding_dimension ?? 384);
47
+ const tableName = this.tableName(name);
48
+ this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${tableName} USING vec0(embedding float[${dimension}])`);
49
+ this.db.prepare(`
50
+ INSERT INTO vector_collections (name, table_name, dimension, metadata_json, created_at)
51
+ VALUES (?, ?, ?, ?, ?)
52
+ `).run(name, tableName, dimension, JSON.stringify(metadata), Date.now());
53
+ return { name, metadata };
54
+ }
55
+ async listCollections() {
56
+ return this.db.prepare('SELECT name, metadata_json FROM vector_collections ORDER BY name').all()
57
+ .map(row => {
58
+ const record = row;
59
+ return { name: record.name, metadata: this.parseMetadata(record.metadata_json) };
60
+ });
61
+ }
62
+ async deleteCollection(name) {
63
+ const collection = this.getCollection(name);
64
+ if (!collection)
65
+ return;
66
+ this.db.exec(`DROP TABLE IF EXISTS ${collection.table_name}`);
67
+ this.db.prepare('DELETE FROM vector_documents WHERE collection_name = ?').run(name);
68
+ this.db.prepare('DELETE FROM vector_collections WHERE name = ?').run(name);
69
+ }
70
+ upsert(collectionName, documents) {
71
+ if (documents.length === 0)
72
+ return;
73
+ const collection = this.requireCollection(collectionName);
74
+ const deleteVec = this.db.prepare(`DELETE FROM ${collection.table_name} WHERE rowid = ?`);
75
+ const deleteDoc = this.db.prepare('DELETE FROM vector_documents WHERE collection_name = ? AND id = ?');
76
+ const insertDoc = this.db.prepare(`
77
+ INSERT INTO vector_documents (collection_name, id, content, metadata_json, file_path, created_at, updated_at)
78
+ VALUES (?, ?, ?, ?, ?, ?, ?)
79
+ `);
80
+ const updateVectorRowid = this.db.prepare('UPDATE vector_documents SET vector_rowid = ? WHERE rowid = ?');
81
+ const insertVec = this.db.prepare(`INSERT INTO ${collection.table_name} (embedding) VALUES (vec_f32(?))`);
82
+ const existingRow = this.db.prepare('SELECT rowid, vector_rowid FROM vector_documents WHERE collection_name = ? AND id = ?');
83
+ const transaction = this.db.transaction((items) => {
84
+ for (const document of items) {
85
+ const existing = existingRow.get(collectionName, document.id);
86
+ if (existing?.vector_rowid)
87
+ deleteVec.run(existing.vector_rowid);
88
+ deleteDoc.run(collectionName, document.id);
89
+ const now = Date.now();
90
+ const metadataJson = JSON.stringify(document.metadata);
91
+ const filePath = this.metadataString(document.metadata.file_path);
92
+ const documentResult = insertDoc.run(collectionName, document.id, document.content, metadataJson, filePath, now, now);
93
+ const vectorResult = insertVec.run(JSON.stringify(document.embedding));
94
+ updateVectorRowid.run(Number(vectorResult.lastInsertRowid), Number(documentResult.lastInsertRowid));
95
+ }
96
+ });
97
+ transaction(documents);
98
+ }
99
+ deleteWhere(collectionName, where) {
100
+ if (typeof where.file_path !== 'string')
101
+ return;
102
+ this.deleteByFilePath(collectionName, where.file_path);
103
+ }
104
+ deleteByFilePath(collectionName, filePath) {
105
+ const collection = this.getCollection(collectionName);
106
+ if (!collection)
107
+ return;
108
+ const rows = this.db.prepare('SELECT vector_rowid FROM vector_documents WHERE collection_name = ? AND file_path = ?').all(collectionName, filePath);
109
+ const deleteVec = this.db.prepare(`DELETE FROM ${collection.table_name} WHERE rowid = ?`);
110
+ const deleteDocs = this.db.prepare('DELETE FROM vector_documents WHERE collection_name = ? AND file_path = ?');
111
+ const transaction = this.db.transaction(() => {
112
+ for (const row of rows) {
113
+ if (row.vector_rowid)
114
+ deleteVec.run(row.vector_rowid);
115
+ }
116
+ deleteDocs.run(collectionName, filePath);
117
+ });
118
+ transaction();
119
+ }
120
+ search(collectionName, query) {
121
+ const collection = this.getCollection(collectionName);
122
+ if (!collection)
123
+ return [];
124
+ const filePath = query.where?.file_path;
125
+ if (typeof filePath === 'string') {
126
+ return this.db.prepare(`
127
+ SELECT d.id, d.content, d.metadata_json, v.distance
128
+ FROM (
129
+ SELECT rowid, distance
130
+ FROM ${collection.table_name}
131
+ WHERE embedding MATCH vec_f32(?) AND k = ?
132
+ ) v
133
+ JOIN vector_documents d ON d.vector_rowid = v.rowid
134
+ WHERE d.file_path = ?
135
+ ORDER BY v.distance
136
+ `).all(JSON.stringify(query.queryEmbedding), query.topK, filePath);
137
+ }
138
+ return this.db.prepare(`
139
+ SELECT d.id, d.content, d.metadata_json, v.distance
140
+ FROM (
141
+ SELECT rowid, distance
142
+ FROM ${collection.table_name}
143
+ WHERE embedding MATCH vec_f32(?) AND k = ?
144
+ ) v
145
+ JOIN vector_documents d ON d.vector_rowid = v.rowid
146
+ ORDER BY v.distance
147
+ `).all(JSON.stringify(query.queryEmbedding), query.topK);
148
+ }
149
+ getCollection(name) {
150
+ return this.db.prepare('SELECT name, table_name, dimension, metadata_json FROM vector_collections WHERE name = ?').get(name);
151
+ }
152
+ requireCollection(name) {
153
+ const collection = this.getCollection(name);
154
+ if (!collection)
155
+ throw new Error(`SQLite vec collection not found: ${name}`);
156
+ return collection;
157
+ }
158
+ tableName(collectionName) {
159
+ const hash = crypto.createHash('sha256').update(collectionName).digest('hex').slice(0, 16);
160
+ return `vec_${hash}`;
161
+ }
162
+ parseMetadata(metadataJson) {
163
+ if (!metadataJson)
164
+ return undefined;
165
+ return JSON.parse(metadataJson);
166
+ }
167
+ metadataString(value) {
168
+ return typeof value === 'string' ? value : null;
169
+ }
170
+ }
171
+ export class SQLiteVecVectorStore {
172
+ client;
173
+ collectionName;
174
+ constructor(client, collectionName) {
175
+ this.client = client;
176
+ this.collectionName = collectionName;
177
+ }
178
+ async ensureCollection(metadata) {
179
+ await this.client.getOrCreateCollection(this.collectionName, metadata);
180
+ }
181
+ async upsert(documents) {
182
+ this.client.upsert(this.collectionName, documents);
183
+ }
184
+ async deleteByFilePath(filePath) {
185
+ this.client.deleteByFilePath(this.collectionName, filePath);
186
+ }
187
+ async search(query) {
188
+ return this.client.search(this.collectionName, query).map(row => ({
189
+ collection: this.collectionName,
190
+ score: 1 / (1 + Number(row.distance ?? 0)),
191
+ document: {
192
+ id: row.id,
193
+ content: row.content,
194
+ metadata: this.parseDocumentMetadata(row.metadata_json),
195
+ },
196
+ }));
197
+ }
198
+ parseDocumentMetadata(metadataJson) {
199
+ if (!metadataJson)
200
+ return {};
201
+ return JSON.parse(metadataJson);
202
+ }
203
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customize-agent/knowledge",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "Local knowledge base infrastructure for customize-agent",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,9 +8,15 @@
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
12
13
  }
13
14
  },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "typecheck": "tsc --noEmit",
18
+ "lint": "eslint src/"
19
+ },
14
20
  "license": "MIT",
15
21
  "author": "Pan-jijian",
16
22
  "repository": {
@@ -32,6 +38,7 @@
32
38
  ],
33
39
  "dependencies": {
34
40
  "better-sqlite3": "^12.10.0",
41
+ "sqlite-vec": "^0.1.9",
35
42
  "fast-glob": "^3.3.3",
36
43
  "jszip": "^3.10.1",
37
44
  "@napi-rs/canvas": "^0.1.82",
@@ -44,10 +51,5 @@
44
51
  "devDependencies": {
45
52
  "@types/better-sqlite3": "^7.6.13",
46
53
  "@types/node": "^25.9.3"
47
- },
48
- "scripts": {
49
- "build": "tsc",
50
- "typecheck": "tsc --noEmit",
51
- "lint": "eslint src/"
52
54
  }
53
- }
55
+ }