@customize-agent/knowledge 1.0.1
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/dist/chunking/text-chunker.d.ts +24 -0
- package/dist/chunking/text-chunker.js +90 -0
- package/dist/classification/classifier.d.ts +12 -0
- package/dist/classification/classifier.js +95 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +40 -0
- package/dist/core/change-tracker.d.ts +10 -0
- package/dist/core/change-tracker.js +66 -0
- package/dist/core/file-scanner.d.ts +8 -0
- package/dist/core/file-scanner.js +32 -0
- package/dist/core/index-state-store.d.ts +103 -0
- package/dist/core/index-state-store.js +439 -0
- package/dist/core/knowledge-base-manager.d.ts +76 -0
- package/dist/core/knowledge-base-manager.js +300 -0
- package/dist/core/multi-project-manager.d.ts +30 -0
- package/dist/core/multi-project-manager.js +163 -0
- package/dist/core/project-config.d.ts +9 -0
- package/dist/core/project-config.js +73 -0
- package/dist/core/project-id.d.ts +1 -0
- package/dist/core/project-id.js +8 -0
- package/dist/core/project-registry.d.ts +10 -0
- package/dist/core/project-registry.js +70 -0
- package/dist/dedup/dedup-engine.d.ts +20 -0
- package/dist/dedup/dedup-engine.js +78 -0
- package/dist/dedup/relationship-detector.d.ts +10 -0
- package/dist/dedup/relationship-detector.js +84 -0
- package/dist/embedding/embedding-provider.d.ts +15 -0
- package/dist/embedding/embedding-provider.js +31 -0
- package/dist/extraction/content-extractor.d.ts +36 -0
- package/dist/extraction/content-extractor.js +655 -0
- package/dist/extraction/external-extractor.d.ts +63 -0
- package/dist/extraction/external-extractor.js +139 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/search/federation-search.d.ts +41 -0
- package/dist/search/federation-search.js +102 -0
- package/dist/server/dashboard-client.d.ts +2 -0
- package/dist/server/dashboard-client.js +396 -0
- package/dist/server/dashboard-i18n.d.ts +112 -0
- package/dist/server/dashboard-i18n.js +220 -0
- package/dist/server/dashboard-page.d.ts +6 -0
- package/dist/server/dashboard-page.js +138 -0
- package/dist/server/dashboard-server.d.ts +13 -0
- package/dist/server/dashboard-server.js +225 -0
- package/dist/server/dashboard-styles.d.ts +1 -0
- package/dist/server/dashboard-styles.js +152 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +1 -0
- package/dist/vector/chroma-store.d.ts +39 -0
- package/dist/vector/chroma-store.js +131 -0
- package/dist/vector/collection-manager.d.ts +16 -0
- package/dist/vector/collection-manager.js +77 -0
- package/dist/vector/types.d.ts +33 -0
- package/dist/vector/types.js +1 -0
- package/dist/vector/vector-indexer.d.ts +18 -0
- package/dist/vector/vector-indexer.js +61 -0
- package/package.json +45 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ClassifiedFile, FileCategory } from '../types.js';
|
|
2
|
+
export interface ExternalExtractionResult {
|
|
3
|
+
text: string;
|
|
4
|
+
metadata?: Record<string, unknown>;
|
|
5
|
+
warnings?: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ExternalExtractor {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly category?: FileCategory;
|
|
11
|
+
readonly formats?: string[];
|
|
12
|
+
readonly extensions?: string[];
|
|
13
|
+
readonly available: boolean;
|
|
14
|
+
supports(file: ClassifiedFile): boolean;
|
|
15
|
+
extract(file: ClassifiedFile): ExternalExtractionResult;
|
|
16
|
+
describe(): ExternalExtractorCapability;
|
|
17
|
+
}
|
|
18
|
+
export interface ExternalExtractorCapability {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
category?: FileCategory;
|
|
22
|
+
formats?: string[];
|
|
23
|
+
extensions?: string[];
|
|
24
|
+
available: boolean;
|
|
25
|
+
kind: 'command';
|
|
26
|
+
}
|
|
27
|
+
export interface CommandExternalExtractorOptions {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
command: string;
|
|
31
|
+
args?: string[];
|
|
32
|
+
category?: FileCategory;
|
|
33
|
+
formats?: string[];
|
|
34
|
+
extensions?: string[];
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class CommandExternalExtractor implements ExternalExtractor {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly category?: FileCategory;
|
|
41
|
+
readonly formats?: string[];
|
|
42
|
+
readonly extensions?: string[];
|
|
43
|
+
readonly available: boolean;
|
|
44
|
+
private readonly command;
|
|
45
|
+
private readonly args;
|
|
46
|
+
private readonly timeoutMs;
|
|
47
|
+
constructor(options: CommandExternalExtractorOptions);
|
|
48
|
+
supports(file: ClassifiedFile): boolean;
|
|
49
|
+
extract(file: ClassifiedFile): ExternalExtractionResult;
|
|
50
|
+
describe(): ExternalExtractorCapability;
|
|
51
|
+
private interpolate;
|
|
52
|
+
private checkAvailable;
|
|
53
|
+
}
|
|
54
|
+
export declare class ExternalExtractorRegistry {
|
|
55
|
+
private readonly extractors;
|
|
56
|
+
static fromEnvironment(env?: NodeJS.ProcessEnv): ExternalExtractorRegistry;
|
|
57
|
+
register(extractor: ExternalExtractor): void;
|
|
58
|
+
findAll(file: ClassifiedFile): ExternalExtractor[];
|
|
59
|
+
find(file: ClassifiedFile): ExternalExtractor | undefined;
|
|
60
|
+
listCapabilities(): ExternalExtractorCapability[];
|
|
61
|
+
private registerConfiguredOrAuto;
|
|
62
|
+
private commandExists;
|
|
63
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export class CommandExternalExtractor {
|
|
4
|
+
id;
|
|
5
|
+
name;
|
|
6
|
+
category;
|
|
7
|
+
formats;
|
|
8
|
+
extensions;
|
|
9
|
+
available;
|
|
10
|
+
command;
|
|
11
|
+
args;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.id = options.id;
|
|
15
|
+
this.name = options.name;
|
|
16
|
+
this.command = options.command;
|
|
17
|
+
this.args = options.args ?? ['{file}'];
|
|
18
|
+
this.category = options.category;
|
|
19
|
+
this.formats = options.formats;
|
|
20
|
+
this.extensions = options.extensions?.map(ext => ext.toLowerCase());
|
|
21
|
+
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
22
|
+
this.available = this.checkAvailable();
|
|
23
|
+
}
|
|
24
|
+
supports(file) {
|
|
25
|
+
if (this.category && file.category !== this.category)
|
|
26
|
+
return false;
|
|
27
|
+
if (this.formats && !this.formats.includes(file.format))
|
|
28
|
+
return false;
|
|
29
|
+
if (this.extensions && !this.extensions.includes(path.extname(file.absolutePath).toLowerCase()))
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
extract(file) {
|
|
34
|
+
const args = this.args.map(arg => this.interpolate(arg, file));
|
|
35
|
+
const result = spawnSync(this.command, args, {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
timeout: this.timeoutMs,
|
|
38
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
39
|
+
});
|
|
40
|
+
if (result.error)
|
|
41
|
+
throw result.error;
|
|
42
|
+
if (result.status !== 0) {
|
|
43
|
+
throw new Error(`${this.name} exited with ${result.status}: ${result.stderr}`);
|
|
44
|
+
}
|
|
45
|
+
const stdout = result.stdout.trim();
|
|
46
|
+
if (!stdout)
|
|
47
|
+
return { text: '', metadata: { externalExtractor: this.id } };
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(stdout);
|
|
50
|
+
return {
|
|
51
|
+
text: parsed.text ?? stdout,
|
|
52
|
+
metadata: { externalExtractor: this.id, ...(parsed.metadata ?? {}) },
|
|
53
|
+
warnings: parsed.warnings,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {
|
|
58
|
+
text: stdout,
|
|
59
|
+
metadata: { externalExtractor: this.id, externalOutput: 'text' },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
describe() {
|
|
64
|
+
return {
|
|
65
|
+
id: this.id,
|
|
66
|
+
name: this.name,
|
|
67
|
+
category: this.category,
|
|
68
|
+
formats: this.formats,
|
|
69
|
+
extensions: this.extensions,
|
|
70
|
+
available: this.available,
|
|
71
|
+
kind: 'command',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
interpolate(value, file) {
|
|
75
|
+
return value
|
|
76
|
+
.replaceAll('{file}', file.absolutePath)
|
|
77
|
+
.replaceAll('{relativePath}', file.relativePath)
|
|
78
|
+
.replaceAll('{category}', file.category)
|
|
79
|
+
.replaceAll('{format}', file.format);
|
|
80
|
+
}
|
|
81
|
+
checkAvailable() {
|
|
82
|
+
const result = spawnSync(this.command, ['--version'], {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
timeout: 3_000,
|
|
85
|
+
stdio: 'ignore',
|
|
86
|
+
});
|
|
87
|
+
return !result.error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export class ExternalExtractorRegistry {
|
|
91
|
+
extractors = [];
|
|
92
|
+
static fromEnvironment(env = process.env) {
|
|
93
|
+
const registry = new ExternalExtractorRegistry();
|
|
94
|
+
registry.registerConfiguredOrAuto('cad-dwg', 'DWG Advanced Parser', env.CUSTOMIZE_AGENT_DWG_PARSER, [{ command: 'dwg-parser' }, { command: 'oda-dwg-parser' }], { category: 'cad', formats: ['autocad'], extensions: ['.dwg', '.dwt'] });
|
|
95
|
+
registry.registerConfiguredOrAuto('cad-solidworks', 'SolidWorks Advanced Parser', env.CUSTOMIZE_AGENT_SOLIDWORKS_PARSER, [{ command: 'solidworks-parser' }, { command: 'sldworks-parser' }], { category: 'cad', formats: ['solidworks'] });
|
|
96
|
+
registry.registerConfiguredOrAuto('diagram-visio', 'Visio Advanced Parser', env.CUSTOMIZE_AGENT_VISIO_PARSER, [{ command: 'visio-parser' }, { command: 'vsdx-parser' }], { category: 'diagram', formats: ['visio'] });
|
|
97
|
+
registry.registerConfiguredOrAuto('ocr-image', 'OCR Image Parser', env.CUSTOMIZE_AGENT_OCR_PARSER, [{ command: 'tesseract', args: ['{file}', 'stdout', '-l', 'chi_sim+eng'] }], { category: 'image', formats: ['raster', 'raw'] });
|
|
98
|
+
registry.registerConfiguredOrAuto('vision-image', 'Vision Image Parser', env.CUSTOMIZE_AGENT_VISION_PARSER, [{ command: 'vision-parser' }, { command: 'llava-parser' }], { category: 'image', formats: ['raster', 'raw'] });
|
|
99
|
+
registry.registerConfiguredOrAuto('pdf-advanced', 'PDF Advanced Parser', env.CUSTOMIZE_AGENT_PDF_PARSER, [{ command: 'pdftotext', args: ['{file}', '-'] }, { command: 'pdf-parser' }], { category: 'document', formats: ['pdf'] });
|
|
100
|
+
registry.registerConfiguredOrAuto('ocr-pdf', 'OCR PDF Parser', env.CUSTOMIZE_AGENT_OCR_PARSER, [{ command: 'ocrmypdf-text' }, { command: 'tesseract', args: ['{file}', 'stdout', '-l', 'chi_sim+eng'] }], { category: 'document', formats: ['pdf'] });
|
|
101
|
+
registry.registerConfiguredOrAuto('vision-pdf', 'Vision PDF Parser', env.CUSTOMIZE_AGENT_VISION_PARSER, [{ command: 'vision-parser' }, { command: 'llava-parser' }], { category: 'document', formats: ['pdf'] });
|
|
102
|
+
registry.registerConfiguredOrAuto('office-advanced', 'Office Advanced Parser', env.CUSTOMIZE_AGENT_OFFICE_PARSER, [{ command: 'pandoc', args: ['-t', 'plain', '{file}'] }, { command: 'textutil', args: ['-convert', 'txt', '-stdout', '{file}'] }, { command: 'mammoth', args: ['{file}'] }], { category: 'document', formats: ['office', 'presentation'] });
|
|
103
|
+
registry.registerConfiguredOrAuto('spreadsheet-advanced', 'Spreadsheet Advanced Parser', env.CUSTOMIZE_AGENT_SPREADSHEET_PARSER, [{ command: 'xlsx2csv' }, { command: 'in2csv' }], { category: 'spreadsheet', formats: ['excel', 'opendoc'] });
|
|
104
|
+
return registry;
|
|
105
|
+
}
|
|
106
|
+
register(extractor) {
|
|
107
|
+
this.extractors.push(extractor);
|
|
108
|
+
}
|
|
109
|
+
findAll(file) {
|
|
110
|
+
return this.extractors.filter(extractor => extractor.available && extractor.supports(file));
|
|
111
|
+
}
|
|
112
|
+
find(file) {
|
|
113
|
+
return this.findAll(file)[0];
|
|
114
|
+
}
|
|
115
|
+
listCapabilities() {
|
|
116
|
+
return this.extractors.map(extractor => extractor.describe());
|
|
117
|
+
}
|
|
118
|
+
registerConfiguredOrAuto(id, name, configuredCommand, candidates, supports) {
|
|
119
|
+
const configured = configuredCommand ? { command: configuredCommand } : undefined;
|
|
120
|
+
const candidate = configured
|
|
121
|
+
?? candidates.find(item => this.commandExists(typeof item === 'string' ? item : item.command))
|
|
122
|
+
?? candidates[0];
|
|
123
|
+
if (!candidate)
|
|
124
|
+
return;
|
|
125
|
+
const command = typeof candidate === 'string' ? candidate : candidate.command;
|
|
126
|
+
const args = typeof candidate === 'string' ? undefined : candidate.args;
|
|
127
|
+
this.register(new CommandExternalExtractor({
|
|
128
|
+
id,
|
|
129
|
+
name,
|
|
130
|
+
command,
|
|
131
|
+
args,
|
|
132
|
+
...supports,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
commandExists(command) {
|
|
136
|
+
const result = spawnSync(command, ['--version'], { stdio: 'ignore', timeout: 2_000 });
|
|
137
|
+
return !result.error;
|
|
138
|
+
}
|
|
139
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './constants.js';
|
|
3
|
+
export { TextChunker, type ChunkConfig, type TextChunk } from './chunking/text-chunker.js';
|
|
4
|
+
export { FileClassifier } from './classification/classifier.js';
|
|
5
|
+
export { DedupEngine, type MinHashSignature, type SimilarityMatch } from './dedup/dedup-engine.js';
|
|
6
|
+
export { RelationshipDetector } from './dedup/relationship-detector.js';
|
|
7
|
+
export { HashEmbeddingProvider, type EmbeddingProvider } from './embedding/embedding-provider.js';
|
|
8
|
+
export { ContentExtractor, type ExtractionResult } from './extraction/content-extractor.js';
|
|
9
|
+
export { CommandExternalExtractor, ExternalExtractorRegistry, type CommandExternalExtractorOptions, type ExternalExtractionResult, type ExternalExtractor, type ExternalExtractorCapability } from './extraction/external-extractor.js';
|
|
10
|
+
export { ChangeTracker } from './core/change-tracker.js';
|
|
11
|
+
export { KnowledgeFileScanner, type DiskFileStat } from './core/file-scanner.js';
|
|
12
|
+
export { IndexStateStore, type ChunkSearchResult, type FileHashRecord, type FileRelationship, type StoredChunk } from './core/index-state-store.js';
|
|
13
|
+
export { KnowledgeBaseManager, type KnowledgeBaseManagerOptions } from './core/knowledge-base-manager.js';
|
|
14
|
+
export { MultiProjectManager } from './core/multi-project-manager.js';
|
|
15
|
+
export { computeProjectId } from './core/project-id.js';
|
|
16
|
+
export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
|
|
17
|
+
export { ProjectRegistry } from './core/project-registry.js';
|
|
18
|
+
export { ChromaHttpClient, ChromaVectorStore, type ChromaClientOptions } from './vector/chroma-store.js';
|
|
19
|
+
export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
|
|
20
|
+
export type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './vector/types.js';
|
|
21
|
+
export { VectorIndexer, type VectorIndexResult } from './vector/vector-indexer.js';
|
|
22
|
+
export { FederationSearch, type FederatedQuery, type FederatedResult, type FederatedSearchItem, type SearchFilters, type SearchScope } from './search/federation-search.js';
|
|
23
|
+
export { startKnowledgeDashboard, type DashboardServerHandle, type DashboardServerOptions } from './server/dashboard-server.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './constants.js';
|
|
3
|
+
export { TextChunker } from './chunking/text-chunker.js';
|
|
4
|
+
export { FileClassifier } from './classification/classifier.js';
|
|
5
|
+
export { DedupEngine } from './dedup/dedup-engine.js';
|
|
6
|
+
export { RelationshipDetector } from './dedup/relationship-detector.js';
|
|
7
|
+
export { HashEmbeddingProvider } from './embedding/embedding-provider.js';
|
|
8
|
+
export { ContentExtractor } from './extraction/content-extractor.js';
|
|
9
|
+
export { CommandExternalExtractor, ExternalExtractorRegistry } from './extraction/external-extractor.js';
|
|
10
|
+
export { ChangeTracker } from './core/change-tracker.js';
|
|
11
|
+
export { KnowledgeFileScanner } from './core/file-scanner.js';
|
|
12
|
+
export { IndexStateStore } from './core/index-state-store.js';
|
|
13
|
+
export { KnowledgeBaseManager } from './core/knowledge-base-manager.js';
|
|
14
|
+
export { MultiProjectManager } from './core/multi-project-manager.js';
|
|
15
|
+
export { computeProjectId } from './core/project-id.js';
|
|
16
|
+
export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
|
|
17
|
+
export { ProjectRegistry } from './core/project-registry.js';
|
|
18
|
+
export { ChromaHttpClient, ChromaVectorStore } from './vector/chroma-store.js';
|
|
19
|
+
export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
|
|
20
|
+
export { VectorIndexer } from './vector/vector-indexer.js';
|
|
21
|
+
export { FederationSearch } from './search/federation-search.js';
|
|
22
|
+
export { startKnowledgeDashboard } from './server/dashboard-server.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { VectorStoreInterface } from '../vector/types.js';
|
|
2
|
+
export type SearchScope = 'project' | 'global' | 'all';
|
|
3
|
+
export interface FederatedSearchItem {
|
|
4
|
+
id: string;
|
|
5
|
+
content: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
scope: 'project' | 'global';
|
|
8
|
+
collection: string;
|
|
9
|
+
score: number;
|
|
10
|
+
contentHash?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FederatedResult {
|
|
13
|
+
results: FederatedSearchItem[];
|
|
14
|
+
scopesSearched: Array<'project' | 'global'>;
|
|
15
|
+
queryTimeMs: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SearchFilters {
|
|
18
|
+
category?: string;
|
|
19
|
+
filePath?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface FederatedQuery {
|
|
22
|
+
query: string;
|
|
23
|
+
queryEmbedding: number[];
|
|
24
|
+
topK: number;
|
|
25
|
+
scope: SearchScope;
|
|
26
|
+
projectId?: string;
|
|
27
|
+
collections?: string[];
|
|
28
|
+
filters?: SearchFilters;
|
|
29
|
+
}
|
|
30
|
+
export declare class FederationSearch {
|
|
31
|
+
private readonly vectorStores;
|
|
32
|
+
static readonly SCOPE_WEIGHTS: Record<'project' | 'global', number>;
|
|
33
|
+
constructor(vectorStores?: Map<string, VectorStoreInterface>);
|
|
34
|
+
search(query: FederatedQuery): Promise<FederatedResult>;
|
|
35
|
+
merge(results: FederatedSearchItem[], topK: number, scope?: SearchScope): FederatedResult;
|
|
36
|
+
private resolveCollectionsForQuery;
|
|
37
|
+
private buildFilter;
|
|
38
|
+
private toFederatedItem;
|
|
39
|
+
private resolveScopes;
|
|
40
|
+
private crossScopeDedup;
|
|
41
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ALL_CATEGORIES } from '../constants.js';
|
|
2
|
+
import { globalCollectionName, projectCollectionName } from '../vector/collection-manager.js';
|
|
3
|
+
export class FederationSearch {
|
|
4
|
+
vectorStores;
|
|
5
|
+
static SCOPE_WEIGHTS = {
|
|
6
|
+
project: 1.0,
|
|
7
|
+
global: 0.7,
|
|
8
|
+
};
|
|
9
|
+
constructor(vectorStores = new Map()) {
|
|
10
|
+
this.vectorStores = vectorStores;
|
|
11
|
+
}
|
|
12
|
+
async search(query) {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
const collectionNames = this.resolveCollectionsForQuery(query);
|
|
15
|
+
const where = this.buildFilter(query.filters);
|
|
16
|
+
const perCollection = await Promise.all(collectionNames.map(async (collectionName) => {
|
|
17
|
+
const store = this.vectorStores.get(collectionName);
|
|
18
|
+
if (!store)
|
|
19
|
+
return [];
|
|
20
|
+
const results = await store.search({
|
|
21
|
+
queryEmbedding: query.queryEmbedding,
|
|
22
|
+
topK: query.topK * 3,
|
|
23
|
+
where,
|
|
24
|
+
});
|
|
25
|
+
const scope = collectionName.startsWith('proj_') ? 'project' : 'global';
|
|
26
|
+
return results.map(result => this.toFederatedItem(result, scope));
|
|
27
|
+
}));
|
|
28
|
+
const merged = this.merge(perCollection.flat(), query.topK, query.scope);
|
|
29
|
+
return { ...merged, queryTimeMs: Date.now() - start };
|
|
30
|
+
}
|
|
31
|
+
merge(results, topK, scope = 'all') {
|
|
32
|
+
const start = Date.now();
|
|
33
|
+
const allowedScopes = this.resolveScopes(scope);
|
|
34
|
+
const weighted = results
|
|
35
|
+
.filter(result => allowedScopes.includes(result.scope))
|
|
36
|
+
.map(result => ({
|
|
37
|
+
...result,
|
|
38
|
+
score: result.score * FederationSearch.SCOPE_WEIGHTS[result.scope],
|
|
39
|
+
}));
|
|
40
|
+
const deduped = this.crossScopeDedup(weighted);
|
|
41
|
+
return {
|
|
42
|
+
results: deduped.sort((a, b) => b.score - a.score).slice(0, topK),
|
|
43
|
+
scopesSearched: allowedScopes,
|
|
44
|
+
queryTimeMs: Date.now() - start,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
resolveCollectionsForQuery(query) {
|
|
48
|
+
const names = [];
|
|
49
|
+
if (query.scope === 'project' || query.scope === 'all') {
|
|
50
|
+
if (!query.projectId)
|
|
51
|
+
throw new Error('project scope requires projectId');
|
|
52
|
+
for (const category of ALL_CATEGORIES) {
|
|
53
|
+
names.push(projectCollectionName(query.projectId, category));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (query.scope === 'global' || query.scope === 'all') {
|
|
57
|
+
for (const category of ALL_CATEGORIES) {
|
|
58
|
+
names.push(globalCollectionName(category));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return query.collections ? names.filter(name => query.collections?.includes(name)) : names;
|
|
62
|
+
}
|
|
63
|
+
buildFilter(filters) {
|
|
64
|
+
if (!filters)
|
|
65
|
+
return undefined;
|
|
66
|
+
const where = {};
|
|
67
|
+
if (filters.category)
|
|
68
|
+
where.category = filters.category;
|
|
69
|
+
if (filters.filePath)
|
|
70
|
+
where.file_path = filters.filePath;
|
|
71
|
+
return Object.keys(where).length > 0 ? where : undefined;
|
|
72
|
+
}
|
|
73
|
+
toFederatedItem(result, scope) {
|
|
74
|
+
return {
|
|
75
|
+
id: result.document.id,
|
|
76
|
+
content: result.document.content,
|
|
77
|
+
filePath: String(result.document.metadata.file_path ?? ''),
|
|
78
|
+
scope,
|
|
79
|
+
collection: result.collection,
|
|
80
|
+
score: result.score,
|
|
81
|
+
contentHash: typeof result.document.metadata.content_hash === 'string' ? result.document.metadata.content_hash : undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
resolveScopes(scope) {
|
|
85
|
+
if (scope === 'project')
|
|
86
|
+
return ['project'];
|
|
87
|
+
if (scope === 'global')
|
|
88
|
+
return ['global'];
|
|
89
|
+
return ['project', 'global'];
|
|
90
|
+
}
|
|
91
|
+
crossScopeDedup(results) {
|
|
92
|
+
const byKey = new Map();
|
|
93
|
+
for (const result of results) {
|
|
94
|
+
const key = result.contentHash ?? result.filePath;
|
|
95
|
+
const existing = byKey.get(key);
|
|
96
|
+
if (!existing || (existing.scope === 'global' && result.scope === 'project')) {
|
|
97
|
+
byKey.set(key, result);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return [...byKey.values()];
|
|
101
|
+
}
|
|
102
|
+
}
|