@customize-agent/knowledge 4.0.1 → 4.0.2

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 (33) hide show
  1. package/dist/chunking/bge-tokenizer.d.ts +10 -0
  2. package/dist/chunking/bge-tokenizer.js +68 -0
  3. package/dist/chunking/text-chunker.d.ts +10 -0
  4. package/dist/chunking/text-chunker.js +158 -53
  5. package/dist/classification/classifier.js +0 -1
  6. package/dist/core/index-state-store.d.ts +47 -0
  7. package/dist/core/index-state-store.js +184 -50
  8. package/dist/core/knowledge-base-manager.d.ts +24 -2
  9. package/dist/core/knowledge-base-manager.js +195 -51
  10. package/dist/core/multi-project-manager.d.ts +2 -0
  11. package/dist/core/multi-project-manager.js +13 -2
  12. package/dist/embedding/embedding-provider.d.ts +22 -0
  13. package/dist/embedding/embedding-provider.js +116 -2
  14. package/dist/extraction/content-extractor.d.ts +30 -2
  15. package/dist/extraction/content-extractor.js +514 -115
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +2 -2
  18. package/dist/search/federation-search.d.ts +1 -0
  19. package/dist/search/federation-search.js +1 -0
  20. package/dist/vector/hnsw-vector-store.d.ts +20 -0
  21. package/dist/vector/hnsw-vector-store.js +107 -0
  22. package/dist/vector/types.d.ts +2 -0
  23. package/dist/vector/vector-indexer.d.ts +2 -0
  24. package/dist/vector/vector-indexer.js +13 -1
  25. package/models/bge-small-zh-v1.5/config.json +31 -0
  26. package/models/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  27. package/models/bge-small-zh-v1.5/special_tokens_map.json +7 -0
  28. package/models/bge-small-zh-v1.5/tokenizer.json +21278 -0
  29. package/models/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  30. package/package.json +11 -4
  31. package/scripts/install-hnsw.cjs +47 -0
  32. package/dist/vector/sqlite-vec-store.d.ts +0 -38
  33. package/dist/vector/sqlite-vec-store.js +0 -203
@@ -0,0 +1,10 @@
1
+ export declare class BgeTokenizer {
2
+ private readonly vocab;
3
+ private readonly unkToken;
4
+ constructor(tokenizerPath?: string | undefined);
5
+ countTokens(text: string): number;
6
+ encode(text: string): string[];
7
+ private preTokenize;
8
+ private wordPiece;
9
+ private static resolveTokenizerPath;
10
+ }
@@ -0,0 +1,68 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ export class BgeTokenizer {
5
+ vocab;
6
+ unkToken;
7
+ constructor(tokenizerPath = BgeTokenizer.resolveTokenizerPath()) {
8
+ if (!tokenizerPath)
9
+ throw new Error('BGE tokenizer.json 缺失,无法执行真实 Token 计数');
10
+ const raw = JSON.parse(fs.readFileSync(tokenizerPath, 'utf8'));
11
+ this.vocab = new Set(Object.keys(raw.model?.vocab ?? {}));
12
+ this.unkToken = raw.model?.unk_token ?? '[UNK]';
13
+ if (this.vocab.size === 0)
14
+ throw new Error('BGE tokenizer vocab 为空,无法执行真实 Token 计数');
15
+ }
16
+ countTokens(text) {
17
+ return this.encode(text).length;
18
+ }
19
+ encode(text) {
20
+ const tokens = [];
21
+ for (const token of this.preTokenize(text))
22
+ tokens.push(...this.wordPiece(token));
23
+ return tokens;
24
+ }
25
+ preTokenize(text) {
26
+ const normalized = Array.from(text, char => {
27
+ const code = char.charCodeAt(0);
28
+ return code < 32 || code === 127 ? ' ' : char;
29
+ }).join('')
30
+ .replace(/([\p{Script=Han}])/gu, ' $1 ')
31
+ .normalize('NFKC');
32
+ return normalized.match(/[\p{Script=Han}]|[\p{Letter}\p{Number}_]+|[^\s\p{Letter}\p{Number}_]/gu) ?? [];
33
+ }
34
+ wordPiece(token) {
35
+ if (this.vocab.has(token))
36
+ return [token];
37
+ const pieces = [];
38
+ let start = 0;
39
+ while (start < token.length) {
40
+ let end = token.length;
41
+ let current;
42
+ while (start < end) {
43
+ const piece = `${start > 0 ? '##' : ''}${token.slice(start, end)}`;
44
+ if (this.vocab.has(piece)) {
45
+ current = piece;
46
+ break;
47
+ }
48
+ end -= 1;
49
+ }
50
+ if (!current)
51
+ return Array.from(token).map(() => this.unkToken);
52
+ pieces.push(current);
53
+ start = end;
54
+ }
55
+ return pieces;
56
+ }
57
+ static resolveTokenizerPath() {
58
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
59
+ const candidates = [
60
+ process.env.CUSTOMIZE_BGE_TOKENIZER_PATH,
61
+ process.env.KB_BGE_TOKENIZER_PATH,
62
+ path.resolve(process.cwd(), 'packages', 'knowledge', 'models', 'bge-small-zh-v1.5', 'tokenizer.json'),
63
+ path.resolve(process.cwd(), 'models', 'bge-small-zh-v1.5', 'tokenizer.json'),
64
+ path.resolve(currentDir, '..', '..', 'models', 'bge-small-zh-v1.5', 'tokenizer.json'),
65
+ ].filter(Boolean);
66
+ return candidates.find(candidate => fs.existsSync(candidate));
67
+ }
68
+ }
@@ -14,17 +14,27 @@ export interface ChunkConfig {
14
14
  headerInjection: boolean;
15
15
  }
16
16
  export declare class TextChunker {
17
+ private readonly tokenizer;
17
18
  chunk(text: string, file: ClassifiedFile, metadata?: Record<string, unknown>): TextChunk[];
18
19
  private createCandidates;
19
20
  private createTextCandidates;
20
21
  private createTableCandidates;
21
22
  private createDataCandidates;
22
23
  private createCodeCandidates;
24
+ private normalizeCodeLanguage;
25
+ private splitCodeByLanguage;
26
+ private collectIndentSensitiveBlocks;
27
+ private collectBraceBalancedBlocks;
28
+ private splitCodeByStructuralFallback;
23
29
  private splitIntoSections;
24
30
  private mergeLeadingHeader;
25
31
  private recursiveSplit;
26
32
  private mergeParts;
27
33
  private splitByWindow;
34
+ private splitBySentenceBoundary;
35
+ private isMarkdownTable;
36
+ private extractMarkdownTableRowRange;
37
+ private splitMarkdownTable;
28
38
  private enforceCandidateLimit;
29
39
  private withHeader;
30
40
  private createChunk;
@@ -1,3 +1,4 @@
1
+ import { BgeTokenizer } from './bge-tokenizer.js';
1
2
  const DEFAULT_CONFIGS = {
2
3
  document: { maxChunkSize: 800, overlap: 100, headerInjection: true },
3
4
  spreadsheet: { maxChunkSize: 1000, overlap: 120, headerInjection: true },
@@ -18,7 +19,18 @@ const RECURSIVE_SEPARATORS = [
18
19
  /(?<=[,、])\s*/u,
19
20
  /\s+/u,
20
21
  ];
22
+ const LANGUAGE_ROUTER = {
23
+ typescript: { delimiters: /\n(?=(?:export\s+)?(?:async\s+)?(?:class|function|interface|type|const|let)\s)/u, blockStart: /\{\s*$/u, indentSensitive: false },
24
+ javascript: { delimiters: /\n(?=(?:export\s+)?(?:async\s+)?(?:class|function|const|let)\s)/u, blockStart: /\{\s*$/u, indentSensitive: false },
25
+ python: { delimiters: /\n(?=(?:class|def)\s)/u, blockStart: /:\s*$/u, indentSensitive: true },
26
+ go: { delimiters: /\n(?=(?:func|type|struct|interface)\s)/u, blockStart: /\{\s*$/u, indentSensitive: false },
27
+ java: { delimiters: /\n(?=(?:public|protected|private|static|final|abstract|\s)*(?:class|interface|enum|(?:\w|<|>|\[|\])+\s+\w+\s*\())/u, blockStart: /\{\s*$/u, indentSensitive: false },
28
+ csharp: { delimiters: /\n(?=(?:public|protected|private|internal|static|sealed|abstract|\s)*(?:class|interface|enum|(?:\w|<|>|\[|\])+\s+\w+\s*\())/u, blockStart: /\{\s*$/u, indentSensitive: false },
29
+ cpp: { delimiters: /\n(?=(?:class|struct|namespace|template)\s|[\w:*&<>]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
30
+ c: { delimiters: /\n(?=(?:struct|enum)\s|[\w*]+\s+\w+\s*\()/u, blockStart: /\{\s*$/u, indentSensitive: false },
31
+ };
21
32
  export class TextChunker {
33
+ tokenizer = new BgeTokenizer();
22
34
  chunk(text, file, metadata = {}) {
23
35
  const source = text.trim();
24
36
  if (source.length === 0)
@@ -34,7 +46,7 @@ export class TextChunker {
34
46
  if (file.category === 'data')
35
47
  return this.createDataCandidates(text, config);
36
48
  if (file.category === 'code')
37
- return this.createCodeCandidates(text, config);
49
+ return this.createCodeCandidates(text, file, config);
38
50
  return this.createTextCandidates(text, file.category, config);
39
51
  }
40
52
  createTextCandidates(text, category, config) {
@@ -62,43 +74,23 @@ export class TextChunker {
62
74
  return candidates;
63
75
  }
64
76
  createTableCandidates(text, config) {
65
- const lines = text.split(/\r?\n/u).map(line => line.trim()).filter(Boolean);
66
- const headerLines = lines.filter(line => /^(文件|类型|Sheet|表格|列|Columns?)[::]/iu.test(line));
67
- const dataLines = lines.filter(line => !headerLines.includes(line));
68
- if (dataLines.length === 0)
69
- return this.createTextCandidates(text, 'spreadsheet', config);
70
- const header = headerLines.join('\n');
71
- const candidates = [];
72
- let rowStart = 0;
73
- let parentIndex = 0;
74
- while (rowStart < dataLines.length) {
75
- const rows = [];
76
- while (rowStart + rows.length < dataLines.length) {
77
- const nextLine = dataLines[rowStart + rows.length];
78
- if (!nextLine)
79
- break;
80
- const nextRows = [...rows, nextLine];
81
- const candidate = [header, `行范围: ${rowStart + 1}-${rowStart + nextRows.length}`, ...nextRows].filter(Boolean).join('\n');
82
- if (rows.length > 0 && this.estimateTokens(candidate) > config.maxChunkSize)
83
- break;
84
- rows.push(nextLine);
85
- }
86
- const textChunk = [header, `行范围: ${rowStart + 1}-${rowStart + rows.length}`, ...rows].filter(Boolean).join('\n');
87
- candidates.push({
88
- text: textChunk,
89
- startChar: text.indexOf(rows[0] ?? ''),
90
- endChar: text.indexOf(rows.at(-1) ?? '') + (rows.at(-1)?.length ?? 0),
91
- sectionTitle: '表格数据',
92
- kind: 'table',
93
- parentId: `table-${parentIndex}`,
94
- parentIndex,
95
- childIndex: 0,
96
- rowRange: `${rowStart + 1}-${rowStart + rows.length}`,
77
+ if (this.isMarkdownTable(text)) {
78
+ return this.splitMarkdownTable(text, config.maxChunkSize).map((part, index) => {
79
+ const startChar = Math.max(0, text.indexOf(part.slice(0, 40)));
80
+ return {
81
+ text: part,
82
+ startChar,
83
+ endChar: startChar + part.length,
84
+ sectionTitle: this.extractSectionTitle(part) ?? '表格数据',
85
+ kind: 'table',
86
+ parentId: `table-${index}`,
87
+ parentIndex: index,
88
+ childIndex: 0,
89
+ rowRange: this.extractMarkdownTableRowRange(part),
90
+ };
97
91
  });
98
- rowStart += Math.max(1, rows.length);
99
- parentIndex += 1;
100
92
  }
101
- return candidates;
93
+ return this.createTextCandidates(text, 'spreadsheet', config);
102
94
  }
103
95
  createDataCandidates(text, config) {
104
96
  const sections = text.split(/\n(?=[\w.[\]-]+[::]\s)|\n{2,}/u).map(part => part.trim()).filter(Boolean);
@@ -114,19 +106,89 @@ export class TextChunker {
114
106
  childIndex: 0,
115
107
  }));
116
108
  }
117
- createCodeCandidates(text, config) {
118
- const blocks = text.split(/\n(?=(?:export\s+)?(?:async\s+)?(?:function|class|interface|type|const|let|var)\s)/u).map(part => part.trim()).filter(Boolean);
109
+ createCodeCandidates(text, file, config) {
110
+ const language = this.normalizeCodeLanguage(file.format);
111
+ const languageConfig = LANGUAGE_ROUTER[language];
112
+ const blocks = languageConfig
113
+ ? this.splitCodeByLanguage(text, languageConfig)
114
+ : this.splitCodeByStructuralFallback(text);
119
115
  const parts = blocks.length > 1 ? blocks : this.recursiveSplit(text, config.maxChunkSize);
120
- return this.mergeParts(parts, config.maxChunkSize, config.overlap).map((part, index) => ({
121
- text: part,
122
- startChar: Math.max(0, text.indexOf(part.slice(0, 40))),
123
- endChar: Math.max(0, text.indexOf(part.slice(0, 40))) + part.length,
124
- sectionTitle: this.extractSectionTitle(part),
125
- kind: 'code',
126
- parentId: `code-${index}`,
127
- parentIndex: index,
128
- childIndex: 0,
129
- }));
116
+ return this.mergeParts(parts, config.maxChunkSize, config.overlap).map((part, index) => {
117
+ const startChar = Math.max(0, text.indexOf(part.slice(0, 40)));
118
+ return {
119
+ text: part,
120
+ startChar,
121
+ endChar: startChar + part.length,
122
+ sectionTitle: this.extractSectionTitle(part),
123
+ kind: 'code',
124
+ parentId: `code-${language}-${index}`,
125
+ parentIndex: index,
126
+ childIndex: 0,
127
+ };
128
+ });
129
+ }
130
+ normalizeCodeLanguage(format) {
131
+ const aliases = {
132
+ ts: 'typescript', tsx: 'typescript', typescript: 'typescript',
133
+ js: 'javascript', jsx: 'javascript', javascript: 'javascript',
134
+ py: 'python', python: 'python',
135
+ golang: 'go', go: 'go',
136
+ java: 'java', cs: 'csharp', csharp: 'csharp',
137
+ cpp: 'cpp', cxx: 'cpp', cc: 'cpp', hpp: 'cpp',
138
+ c: 'c', h: 'c',
139
+ };
140
+ return aliases[format.toLowerCase()] ?? format.toLowerCase();
141
+ }
142
+ splitCodeByLanguage(text, config) {
143
+ const raw = text.split(config.delimiters).map(part => part.trim()).filter(Boolean);
144
+ if (raw.length <= 1)
145
+ return raw;
146
+ return raw.flatMap(block => config.indentSensitive ? this.collectIndentSensitiveBlocks(block, config) : this.collectBraceBalancedBlocks(block, config));
147
+ }
148
+ collectIndentSensitiveBlocks(block, config) {
149
+ const lines = block.split(/\r?\n/u);
150
+ const result = [];
151
+ let current = [];
152
+ let baseIndent;
153
+ for (const line of lines) {
154
+ const indent = line.match(/^\s*/u)?.[0].length ?? 0;
155
+ if (current.length > 0 && baseIndent != null && indent <= baseIndent && config.blockStart.test(current[0] ?? '') && line.trim()) {
156
+ result.push(current.join('\n').trim());
157
+ current = [];
158
+ baseIndent = undefined;
159
+ }
160
+ if (current.length === 0)
161
+ baseIndent = indent;
162
+ current.push(line);
163
+ }
164
+ if (current.length > 0)
165
+ result.push(current.join('\n').trim());
166
+ return result.filter(Boolean);
167
+ }
168
+ collectBraceBalancedBlocks(block, _config) {
169
+ const lines = block.split(/\r?\n/u);
170
+ const result = [];
171
+ let current = [];
172
+ let depth = 0;
173
+ for (const line of lines) {
174
+ current.push(line);
175
+ depth += (line.match(/\{/gu) ?? []).length;
176
+ depth -= (line.match(/\}/gu) ?? []).length;
177
+ if (current.length > 1 && depth <= 0) {
178
+ result.push(current.join('\n').trim());
179
+ current = [];
180
+ depth = 0;
181
+ }
182
+ }
183
+ if (current.length > 0)
184
+ result.push(current.join('\n').trim());
185
+ return result.filter(Boolean);
186
+ }
187
+ splitCodeByStructuralFallback(text) {
188
+ const blocks = this.collectBraceBalancedBlocks(text, { delimiters: /\n/u, blockStart: /\{\s*$/u, indentSensitive: false });
189
+ if (blocks.length > 1)
190
+ return blocks;
191
+ return text.split(/\n(?=\S)/u).map(part => part.trim()).filter(Boolean);
130
192
  }
131
193
  splitIntoSections(text, category) {
132
194
  const pattern = category === 'cad' || category === 'diagram'
@@ -154,11 +216,13 @@ export class TextChunker {
154
216
  recursiveSplit(text, maxTokens, separatorIndex = 0) {
155
217
  if (this.estimateTokens(text) <= maxTokens)
156
218
  return [text.trim()].filter(Boolean);
219
+ if (this.isMarkdownTable(text))
220
+ return this.splitMarkdownTable(text, maxTokens);
157
221
  if (separatorIndex >= RECURSIVE_SEPARATORS.length)
158
- return this.splitByWindow(text, maxTokens);
222
+ return this.splitBySentenceBoundary(text, maxTokens);
159
223
  const separator = RECURSIVE_SEPARATORS[separatorIndex];
160
224
  if (!separator)
161
- return this.splitByWindow(text, maxTokens);
225
+ return this.splitBySentenceBoundary(text, maxTokens);
162
226
  const parts = text.split(separator).map(part => part.trim()).filter(Boolean);
163
227
  if (parts.length <= 1)
164
228
  return this.recursiveSplit(text, maxTokens, separatorIndex + 1);
@@ -183,8 +247,11 @@ export class TextChunker {
183
247
  return chunks.flatMap(chunk => this.estimateTokens(chunk) > maxTokens ? this.splitByWindow(chunk, maxTokens, overlapTokens) : [chunk]);
184
248
  }
185
249
  splitByWindow(text, maxTokens, overlapTokens = 0) {
186
- const maxChars = Math.max(200, maxTokens * 4);
187
- const overlapChars = Math.max(0, overlapTokens * 4);
250
+ const tokens = this.tokenizer.encode(text);
251
+ if (tokens.length <= maxTokens)
252
+ return [text.trim()].filter(Boolean);
253
+ const maxChars = Math.max(200, Math.ceil(text.length * (maxTokens / Math.max(1, tokens.length))));
254
+ const overlapChars = Math.max(0, Math.ceil(text.length * (overlapTokens / Math.max(1, tokens.length))));
188
255
  const step = Math.max(1, maxChars - overlapChars);
189
256
  const chunks = [];
190
257
  for (let start = 0; start < text.length; start += step) {
@@ -194,6 +261,44 @@ export class TextChunker {
194
261
  }
195
262
  return chunks.filter(Boolean);
196
263
  }
264
+ splitBySentenceBoundary(text, maxTokens) {
265
+ const units = text.split(/(?<=[。?!;;.!?])\s+|(?<=[。?!;;.!?])/u).map(part => part.trim()).filter(Boolean);
266
+ if (units.length <= 1)
267
+ return this.splitByWindow(text, maxTokens);
268
+ return this.mergeParts(units, maxTokens, 0);
269
+ }
270
+ isMarkdownTable(text) {
271
+ const lines = text.trim().split(/\r?\n/u);
272
+ return lines.length >= 3 && lines.some(line => /^\s*\|?\s*:?-{3,}:?\s*\|/u.test(line));
273
+ }
274
+ extractMarkdownTableRowRange(text) {
275
+ const lines = text.trim().split(/\r?\n/u).filter(line => /^\s*\|/u.test(line));
276
+ const rowCount = Math.max(0, lines.length - 2);
277
+ return rowCount > 0 ? `1-${rowCount}` : undefined;
278
+ }
279
+ splitMarkdownTable(text, maxTokens) {
280
+ const lines = text.trim().split(/\r?\n/u).filter(Boolean);
281
+ const separatorIndex = lines.findIndex(line => /^\s*\|?\s*:?-{3,}:?\s*\|/u.test(line));
282
+ if (separatorIndex <= 0)
283
+ return this.splitBySentenceBoundary(text, maxTokens);
284
+ const header = lines.slice(0, separatorIndex + 1);
285
+ const rows = lines.slice(separatorIndex + 1);
286
+ const chunks = [];
287
+ let current = [];
288
+ for (const row of rows) {
289
+ const candidate = [...header, ...current, row].join('\n');
290
+ if (current.length > 0 && this.estimateTokens(candidate) > maxTokens) {
291
+ chunks.push([...header, ...current].join('\n'));
292
+ current = [row];
293
+ }
294
+ else {
295
+ current.push(row);
296
+ }
297
+ }
298
+ if (current.length > 0)
299
+ chunks.push([...header, ...current].join('\n'));
300
+ return chunks.flatMap(chunk => this.estimateTokens(chunk) > maxTokens ? this.splitByWindow(chunk, maxTokens) : [chunk]);
301
+ }
197
302
  enforceCandidateLimit(candidates, config) {
198
303
  return candidates.flatMap(candidate => {
199
304
  if (this.estimateTokens(candidate.text) <= config.maxChunkSize)
@@ -263,6 +368,6 @@ export class TextChunker {
263
368
  return text.slice(Math.max(0, text.length - chars));
264
369
  }
265
370
  estimateTokens(text) {
266
- return Math.max(1, Math.ceil(text.length / 4));
371
+ return Math.max(1, this.tokenizer.countTokens(text));
267
372
  }
268
373
  }
@@ -76,7 +76,6 @@ export class FileClassifier {
76
76
  ['.html', 'web', 'html'], ['.htm', 'web', 'html'], ['.xhtml', 'web', 'html'], ['.css', 'web', 'stylesheet'], ['.scss', 'web', 'stylesheet'], ['.sass', 'web', 'stylesheet'], ['.less', 'web', 'stylesheet'],
77
77
  ['.hbs', 'web', 'template'], ['.ejs', 'web', 'template'], ['.pug', 'web', 'template'], ['.j2', 'web', 'template'], ['.jinja2', 'web', 'template'],
78
78
  ['.drawio', 'diagram', 'drawio'], ['.dio', 'diagram', 'drawio'], ['.vsdx', 'diagram', 'visio'], ['.vdx', 'diagram', 'visio'], ['.puml', 'diagram', 'plantuml'], ['.plantuml', 'diagram', 'plantuml'], ['.mmd', 'diagram', 'mermaid'], ['.mermaid', 'diagram', 'mermaid'], ['.excalidraw', 'diagram', 'excalidraw'],
79
- ['.zip', 'archive', 'zip'], ['.jar', 'archive', 'zip'], ['.war', 'archive', 'zip'], ['.apk', 'archive', 'zip'], ['.tar', 'archive', 'tar'], ['.gz', 'archive', 'other'], ['.tgz', 'archive', 'tar'], ['.bz2', 'archive', 'other'], ['.rar', 'archive', 'other'], ['.7z', 'archive', 'other'],
80
79
  ];
81
80
  return new Map(entries.map(([ext, category, format]) => [ext, [category, format]]));
82
81
  }
@@ -1,6 +1,18 @@
1
1
  import type { TextChunk } from '../chunking/text-chunker.js';
2
2
  import type { FileCategory, IndexStateRecord } from '../types.js';
3
+ export type KnowledgeJobStatus = 'PENDING' | 'PARSING' | 'CHUNKING' | 'INDEXING' | 'SUCCESS' | 'ERROR';
4
+ export interface KnowledgeIndexJob {
5
+ id: string;
6
+ relativePath: string;
7
+ status: KnowledgeJobStatus;
8
+ percent: number;
9
+ message: string;
10
+ errorMessage?: string;
11
+ createdAt: number;
12
+ updatedAt: number;
13
+ }
3
14
  export interface StoredChunk {
15
+ rowid: number;
4
16
  id: string;
5
17
  relativePath: string;
6
18
  chunkIndex: number;
@@ -34,6 +46,18 @@ export interface StoredParentChunk {
34
46
  metadataJson?: string;
35
47
  createdAt: number;
36
48
  }
49
+ export interface StoredDocumentChunk {
50
+ id: string;
51
+ relativePath: string;
52
+ content: string;
53
+ category: FileCategory;
54
+ format: string;
55
+ collectionName: string;
56
+ parentCount: number;
57
+ chunkCount: number;
58
+ metadataJson?: string;
59
+ createdAt: number;
60
+ }
37
61
  export interface FileHashRecord {
38
62
  contentHash: string;
39
63
  filePath: string;
@@ -68,6 +92,22 @@ export declare class IndexStateStore {
68
92
  upsertRecord(record: IndexStateRecord): void;
69
93
  updateVerified(relativePath: string, mtime: number): void;
70
94
  listRecords(): IndexStateRecord[];
95
+ enqueueIndexJob(job: {
96
+ id: string;
97
+ relativePath: string;
98
+ message?: string;
99
+ }): KnowledgeIndexJob;
100
+ updateIndexJob(id: string, patch: {
101
+ status?: KnowledgeJobStatus;
102
+ percent?: number;
103
+ message?: string;
104
+ errorMessage?: string;
105
+ }): void;
106
+ getIndexJob(id: string): KnowledgeIndexJob | undefined;
107
+ listPendingIndexJobs(limit?: number): KnowledgeIndexJob[];
108
+ countPendingIndexJobs(): number;
109
+ listActiveIndexJobsByPath(relativePath: string): KnowledgeIndexJob[];
110
+ listIndexJobsByPrefix(prefix: string): KnowledgeIndexJob[];
71
111
  replaceChunks(relativePath: string, chunks: TextChunk[], file: {
72
112
  category: FileCategory;
73
113
  format: string;
@@ -78,9 +118,12 @@ export declare class IndexStateStore {
78
118
  relativePath?: string;
79
119
  limit?: number;
80
120
  }): StoredChunk[];
121
+ getChunkByRowid(rowid: number): StoredChunk | undefined;
122
+ getChunksByRowids(rowids: number[]): StoredChunk[];
81
123
  getContextChunks(relativePath: string, chunkIndex: number, window?: number): StoredChunk[];
82
124
  listParentChunks(relativePath: string): StoredParentChunk[];
83
125
  getParentChunk(relativePath: string, parentId: string): StoredParentChunk | undefined;
126
+ getDocumentChunk(relativePath: string): StoredDocumentChunk | undefined;
84
127
  getChunksByParent(relativePath: string, parentId: string, limit?: number): StoredChunk[];
85
128
  searchChunks(query: string, limit?: number): ChunkSearchResult[];
86
129
  private searchChunksFts;
@@ -127,11 +170,15 @@ export declare class IndexStateStore {
127
170
  private rowToFileHash;
128
171
  private rowToRelationship;
129
172
  private rowToParentChunk;
173
+ private rowToDocumentChunk;
130
174
  private splitParentGroups;
175
+ private rowToJob;
131
176
  private metadataString;
132
177
  private rowToChunk;
133
178
  private expandSearchTerms;
134
179
  private toFtsQuery;
180
+ private mergeKeywordResults;
181
+ private chineseNgrams;
135
182
  private bm25ToPositiveScore;
136
183
  private scoreChunkDetailed;
137
184
  private countOccurrences;