@frontmcp/plugins 0.4.1 → 0.5.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/package.json +8 -3
- package/src/cache/cache.plugin.js +27 -25
- package/src/cache/cache.plugin.js.map +1 -1
- package/src/cache/providers/cache-memory.provider.js +2 -1
- package/src/cache/providers/cache-memory.provider.js.map +1 -1
- package/src/cache/providers/cache-redis.provider.js +1 -0
- package/src/cache/providers/cache-redis.provider.js.map +1 -1
- package/src/codecall/README.md +999 -0
- package/src/codecall/codecall.plugin.d.ts +41 -0
- package/src/codecall/codecall.plugin.js +152 -0
- package/src/codecall/codecall.plugin.js.map +1 -0
- package/src/codecall/codecall.symbol.d.ts +106 -0
- package/src/codecall/codecall.symbol.js +4 -0
- package/src/codecall/codecall.symbol.js.map +1 -0
- package/src/codecall/codecall.types.d.ts +289 -0
- package/src/codecall/codecall.types.js +258 -0
- package/src/codecall/codecall.types.js.map +1 -0
- package/src/codecall/errors/index.d.ts +1 -0
- package/src/codecall/errors/index.js +6 -0
- package/src/codecall/errors/index.js.map +1 -0
- package/src/codecall/errors/tool-call.errors.d.ts +79 -0
- package/src/codecall/errors/tool-call.errors.js +119 -0
- package/src/codecall/errors/tool-call.errors.js.map +1 -0
- package/src/codecall/index.d.ts +2 -0
- package/src/codecall/index.js +8 -0
- package/src/codecall/index.js.map +1 -0
- package/src/codecall/providers/code-call.config.d.ts +29 -0
- package/src/codecall/providers/code-call.config.js +120 -0
- package/src/codecall/providers/code-call.config.js.map +1 -0
- package/src/codecall/security/index.d.ts +2 -0
- package/src/codecall/security/index.js +7 -0
- package/src/codecall/security/index.js.map +1 -0
- package/src/codecall/security/self-reference-guard.d.ts +32 -0
- package/src/codecall/security/self-reference-guard.js +70 -0
- package/src/codecall/security/self-reference-guard.js.map +1 -0
- package/src/codecall/security/tool-access-control.service.d.ts +104 -0
- package/src/codecall/security/tool-access-control.service.js +170 -0
- package/src/codecall/security/tool-access-control.service.js.map +1 -0
- package/src/codecall/services/audit-logger.service.d.ts +186 -0
- package/src/codecall/services/audit-logger.service.js +322 -0
- package/src/codecall/services/audit-logger.service.js.map +1 -0
- package/src/codecall/services/enclave.service.d.ts +62 -0
- package/src/codecall/services/enclave.service.js +214 -0
- package/src/codecall/services/enclave.service.js.map +1 -0
- package/src/codecall/services/error-enrichment.service.d.ts +94 -0
- package/src/codecall/services/error-enrichment.service.js +387 -0
- package/src/codecall/services/error-enrichment.service.js.map +1 -0
- package/src/codecall/services/index.d.ts +6 -0
- package/src/codecall/services/index.js +13 -0
- package/src/codecall/services/index.js.map +1 -0
- package/src/codecall/services/output-sanitizer.d.ts +86 -0
- package/src/codecall/services/output-sanitizer.js +260 -0
- package/src/codecall/services/output-sanitizer.js.map +1 -0
- package/src/codecall/services/synonym-expansion.service.d.ts +66 -0
- package/src/codecall/services/synonym-expansion.service.js +374 -0
- package/src/codecall/services/synonym-expansion.service.js.map +1 -0
- package/src/codecall/services/tool-search.service.d.ts +175 -0
- package/src/codecall/services/tool-search.service.js +587 -0
- package/src/codecall/services/tool-search.service.js.map +1 -0
- package/src/codecall/tools/describe.schema.d.ts +28 -0
- package/src/codecall/tools/describe.schema.js +67 -0
- package/src/codecall/tools/describe.schema.js.map +1 -0
- package/src/codecall/tools/describe.tool.d.ts +35 -0
- package/src/codecall/tools/describe.tool.js +207 -0
- package/src/codecall/tools/describe.tool.js.map +1 -0
- package/src/codecall/tools/execute.schema.d.ts +115 -0
- package/src/codecall/tools/execute.schema.js +116 -0
- package/src/codecall/tools/execute.schema.js.map +1 -0
- package/src/codecall/tools/execute.tool.d.ts +5 -0
- package/src/codecall/tools/execute.tool.js +238 -0
- package/src/codecall/tools/execute.tool.js.map +1 -0
- package/src/codecall/tools/index.d.ts +4 -0
- package/src/codecall/tools/index.js +13 -0
- package/src/codecall/tools/index.js.map +1 -0
- package/src/codecall/tools/invoke.schema.d.ts +99 -0
- package/src/codecall/tools/invoke.schema.js +27 -0
- package/src/codecall/tools/invoke.schema.js.map +1 -0
- package/src/codecall/tools/invoke.tool.d.ts +13 -0
- package/src/codecall/tools/invoke.tool.js +70 -0
- package/src/codecall/tools/invoke.tool.js.map +1 -0
- package/src/codecall/tools/search.schema.d.ts +30 -0
- package/src/codecall/tools/search.schema.js +60 -0
- package/src/codecall/tools/search.schema.js.map +1 -0
- package/src/codecall/tools/search.tool.d.ts +5 -0
- package/src/codecall/tools/search.tool.js +108 -0
- package/src/codecall/tools/search.tool.js.map +1 -0
- package/src/codecall/utils/describe.utils.d.ts +86 -0
- package/src/codecall/utils/describe.utils.js +531 -0
- package/src/codecall/utils/describe.utils.js.map +1 -0
- package/src/codecall/utils/index.d.ts +2 -0
- package/src/codecall/utils/index.js +7 -0
- package/src/codecall/utils/index.js.map +1 -0
- package/src/codecall/utils/mcp-result.d.ts +6 -0
- package/src/codecall/utils/mcp-result.js +36 -0
- package/src/codecall/utils/mcp-result.js.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// file: libs/plugins/src/codecall/services/tool-search.service.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ToolSearchService = void 0;
|
|
5
|
+
const vectoriadb_1 = require("vectoriadb");
|
|
6
|
+
const synonym_expansion_service_1 = require("./synonym-expansion.service");
|
|
7
|
+
/**
|
|
8
|
+
* Universal Intent Mapping & Query Normalization
|
|
9
|
+
* - This module defines the semantic knowledge base for the MCP tool search engine.
|
|
10
|
+
* It is designed to bridge the gap between natural language user intents
|
|
11
|
+
* (e.g., "fix," "chat," "buy") and technical tool definitions (e.g., "update," "post," "create").
|
|
12
|
+
* - Key Parts:
|
|
13
|
+
* 1. DEFAULT_SYNONYM_GROUPS: A domain-agnostic mapping of bidirectional synonyms covering
|
|
14
|
+
* CRUD, DevOps, Financial, Social, and Lifecycle operations. This ensures that a
|
|
15
|
+
* query for "Show me the bill" matches a tool named "get_invoice".
|
|
16
|
+
* 2. STOP_WORDS: A curated exclusion list strictly optimized for Command-Line/Chat
|
|
17
|
+
* interfaces. Unlike standard NLP stop lists, this PRESERVES action verbs
|
|
18
|
+
* ("find", "start", "make") as they are critical signals of user intent in a
|
|
19
|
+
* tool-execution context.
|
|
20
|
+
*/
|
|
21
|
+
const STOP_WORDS = new Set([
|
|
22
|
+
// Articles & Determiners
|
|
23
|
+
'the',
|
|
24
|
+
'a',
|
|
25
|
+
'an',
|
|
26
|
+
'this',
|
|
27
|
+
'that',
|
|
28
|
+
'these',
|
|
29
|
+
'those',
|
|
30
|
+
// Prepositions
|
|
31
|
+
'in',
|
|
32
|
+
'on',
|
|
33
|
+
'at',
|
|
34
|
+
'to',
|
|
35
|
+
'for',
|
|
36
|
+
'of',
|
|
37
|
+
'with',
|
|
38
|
+
'by',
|
|
39
|
+
'from',
|
|
40
|
+
'into',
|
|
41
|
+
'over',
|
|
42
|
+
'after',
|
|
43
|
+
'before',
|
|
44
|
+
'between',
|
|
45
|
+
'under',
|
|
46
|
+
'about',
|
|
47
|
+
'against',
|
|
48
|
+
'during',
|
|
49
|
+
'through',
|
|
50
|
+
// Conjunctions
|
|
51
|
+
'and',
|
|
52
|
+
'or',
|
|
53
|
+
'but',
|
|
54
|
+
'nor',
|
|
55
|
+
'so',
|
|
56
|
+
'yet',
|
|
57
|
+
'as',
|
|
58
|
+
'than',
|
|
59
|
+
'if',
|
|
60
|
+
'because',
|
|
61
|
+
'while',
|
|
62
|
+
'when',
|
|
63
|
+
'where',
|
|
64
|
+
'unless',
|
|
65
|
+
// Pronouns (Subject/Object/Possessive)
|
|
66
|
+
'i',
|
|
67
|
+
'me',
|
|
68
|
+
'my',
|
|
69
|
+
'mine',
|
|
70
|
+
'myself',
|
|
71
|
+
'you',
|
|
72
|
+
'your',
|
|
73
|
+
'yours',
|
|
74
|
+
'yourself',
|
|
75
|
+
'he',
|
|
76
|
+
'him',
|
|
77
|
+
'his',
|
|
78
|
+
'himself',
|
|
79
|
+
'she',
|
|
80
|
+
'her',
|
|
81
|
+
'hers',
|
|
82
|
+
'herself',
|
|
83
|
+
'it',
|
|
84
|
+
'its',
|
|
85
|
+
'itself',
|
|
86
|
+
'we',
|
|
87
|
+
'us',
|
|
88
|
+
'our',
|
|
89
|
+
'ours',
|
|
90
|
+
'ourselves',
|
|
91
|
+
'they',
|
|
92
|
+
'them',
|
|
93
|
+
'their',
|
|
94
|
+
'theirs',
|
|
95
|
+
'themselves',
|
|
96
|
+
'who',
|
|
97
|
+
'whom',
|
|
98
|
+
'whose',
|
|
99
|
+
'which',
|
|
100
|
+
'what',
|
|
101
|
+
// Auxiliary/Linking Verbs (State of being is usually noise, Action is signal)
|
|
102
|
+
'is',
|
|
103
|
+
'was',
|
|
104
|
+
'are',
|
|
105
|
+
'were',
|
|
106
|
+
'been',
|
|
107
|
+
'be',
|
|
108
|
+
'being',
|
|
109
|
+
'have',
|
|
110
|
+
'has',
|
|
111
|
+
'had',
|
|
112
|
+
'having',
|
|
113
|
+
'do',
|
|
114
|
+
'does',
|
|
115
|
+
'did',
|
|
116
|
+
'doing', // "do" is usually auxiliary ("do you have..."). "run" or "execute" is better.
|
|
117
|
+
'will',
|
|
118
|
+
'would',
|
|
119
|
+
'shall',
|
|
120
|
+
'should',
|
|
121
|
+
'can',
|
|
122
|
+
'could',
|
|
123
|
+
'may',
|
|
124
|
+
'might',
|
|
125
|
+
'must',
|
|
126
|
+
// Quantifiers / Adverbs of degree
|
|
127
|
+
'all',
|
|
128
|
+
'any',
|
|
129
|
+
'both',
|
|
130
|
+
'each',
|
|
131
|
+
'few',
|
|
132
|
+
'more',
|
|
133
|
+
'most',
|
|
134
|
+
'other',
|
|
135
|
+
'some',
|
|
136
|
+
'such',
|
|
137
|
+
'no',
|
|
138
|
+
'nor',
|
|
139
|
+
'not',
|
|
140
|
+
'only',
|
|
141
|
+
'own',
|
|
142
|
+
'same',
|
|
143
|
+
'too',
|
|
144
|
+
'very',
|
|
145
|
+
'just',
|
|
146
|
+
'even',
|
|
147
|
+
// Conversational / Chat Fillers (Common in LLM prompts)
|
|
148
|
+
'please',
|
|
149
|
+
'pls',
|
|
150
|
+
'plz',
|
|
151
|
+
'thanks',
|
|
152
|
+
'thank',
|
|
153
|
+
'thx',
|
|
154
|
+
'hello',
|
|
155
|
+
'hi',
|
|
156
|
+
'hey',
|
|
157
|
+
'ok',
|
|
158
|
+
'okay',
|
|
159
|
+
'yes',
|
|
160
|
+
'no',
|
|
161
|
+
'actually',
|
|
162
|
+
'basically',
|
|
163
|
+
'literally',
|
|
164
|
+
'maybe',
|
|
165
|
+
'perhaps',
|
|
166
|
+
'now',
|
|
167
|
+
'then',
|
|
168
|
+
'here',
|
|
169
|
+
'there',
|
|
170
|
+
'again',
|
|
171
|
+
'once',
|
|
172
|
+
'back', // "back" can be tricky, but usually implies direction not action
|
|
173
|
+
// Meta/Structural words
|
|
174
|
+
'example',
|
|
175
|
+
'context',
|
|
176
|
+
'optionally',
|
|
177
|
+
'optional', // Users rarely search for "optional", they search for the thing itself.
|
|
178
|
+
'etc',
|
|
179
|
+
'ie',
|
|
180
|
+
'eg',
|
|
181
|
+
]);
|
|
182
|
+
/**
|
|
183
|
+
* Service that maintains a searchable index of tools from the ToolRegistry
|
|
184
|
+
* Supports both TF-IDF (lightweight, synchronous) and ML-based (semantic) embeddings
|
|
185
|
+
* Implements the ToolSearch interface for dependency injection
|
|
186
|
+
*/
|
|
187
|
+
class ToolSearchService {
|
|
188
|
+
static MAX_SUBSCRIPTION_RETRIES = 100;
|
|
189
|
+
vectorDB;
|
|
190
|
+
strategy;
|
|
191
|
+
initialized = false;
|
|
192
|
+
mlInitialized = false;
|
|
193
|
+
subscriptionRetries = 0;
|
|
194
|
+
config;
|
|
195
|
+
scope;
|
|
196
|
+
unsubscribe;
|
|
197
|
+
synonymService = null;
|
|
198
|
+
constructor(config = {}, scope) {
|
|
199
|
+
this.scope = scope;
|
|
200
|
+
const embeddingOptions = config.embeddingOptions || {
|
|
201
|
+
strategy: 'tfidf',
|
|
202
|
+
modelName: 'Xenova/all-MiniLM-L6-v2',
|
|
203
|
+
cacheDir: './.cache/transformers',
|
|
204
|
+
useHNSW: false,
|
|
205
|
+
synonymExpansion: { enabled: true, replaceDefaults: false, maxExpansionsPerTerm: 5 },
|
|
206
|
+
};
|
|
207
|
+
this.strategy = config.strategy || embeddingOptions.strategy || 'tfidf';
|
|
208
|
+
this.config = {
|
|
209
|
+
strategy: this.strategy,
|
|
210
|
+
embeddingOptions,
|
|
211
|
+
defaultTopK: config.defaultTopK ?? 8,
|
|
212
|
+
defaultSimilarityThreshold: config.defaultSimilarityThreshold ?? 0.0,
|
|
213
|
+
mode: config.mode ?? 'codecall_only',
|
|
214
|
+
includeTools: config.includeTools,
|
|
215
|
+
};
|
|
216
|
+
// Validate mode parameter at runtime
|
|
217
|
+
const validModes = ['codecall_only', 'codecall_opt_in', 'metadata_driven'];
|
|
218
|
+
if (!validModes.includes(this.config.mode)) {
|
|
219
|
+
throw new Error(`Invalid CodeCall mode: ${this.config.mode}. Valid modes: ${validModes.join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
// Initialize the appropriate vector database
|
|
222
|
+
if (this.strategy === 'ml') {
|
|
223
|
+
this.vectorDB = new vectoriadb_1.VectoriaDB({
|
|
224
|
+
modelName: embeddingOptions.modelName || 'Xenova/all-MiniLM-L6-v2',
|
|
225
|
+
cacheDir: embeddingOptions.cacheDir || './.cache/transformers',
|
|
226
|
+
defaultTopK: this.config.defaultTopK,
|
|
227
|
+
defaultSimilarityThreshold: this.config.defaultSimilarityThreshold,
|
|
228
|
+
useHNSW: embeddingOptions.useHNSW || false,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.vectorDB = new vectoriadb_1.TFIDFVectoria({
|
|
233
|
+
defaultTopK: this.config.defaultTopK,
|
|
234
|
+
defaultSimilarityThreshold: this.config.defaultSimilarityThreshold,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// Initialize synonym expansion for TF-IDF strategy (ML already handles semantic similarity)
|
|
238
|
+
if (config.synonymExpansion === false) {
|
|
239
|
+
this.synonymService = null;
|
|
240
|
+
}
|
|
241
|
+
else if (this.strategy === 'tfidf') {
|
|
242
|
+
const synonymConfig = typeof config.synonymExpansion === 'object' ? config.synonymExpansion : {};
|
|
243
|
+
// Only enable if not explicitly disabled
|
|
244
|
+
if (synonymConfig.enabled !== false) {
|
|
245
|
+
this.synonymService = new synonym_expansion_service_1.SynonymExpansionService(synonymConfig);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Defer subscription until scope.tools is available
|
|
249
|
+
// During plugin initialization, scope.tools may not exist yet
|
|
250
|
+
this.setupSubscription();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Sets up subscription to tool changes. Handles the case where scope.tools
|
|
254
|
+
* may not be available yet during plugin initialization.
|
|
255
|
+
*/
|
|
256
|
+
setupSubscription() {
|
|
257
|
+
// If tools registry is not yet available, retry after a microtask
|
|
258
|
+
if (!this.scope.tools) {
|
|
259
|
+
if (this.subscriptionRetries++ >= ToolSearchService.MAX_SUBSCRIPTION_RETRIES) {
|
|
260
|
+
console.warn('ToolSearchService: scope.tools not available after max retries');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Use queueMicrotask to defer until after current initialization
|
|
264
|
+
queueMicrotask(() => this.setupSubscription());
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Reset retry counter on success
|
|
268
|
+
this.subscriptionRetries = 0;
|
|
269
|
+
// Subscribe to tool changes with immediate=true to get current snapshot
|
|
270
|
+
// This ensures tools are indexed as they become available, regardless of loading order
|
|
271
|
+
this.unsubscribe = this.scope.tools.subscribe({ immediate: true }, (event) => {
|
|
272
|
+
// Handle tool change event - reindex all tools from the snapshot
|
|
273
|
+
this.handleToolChange(event.snapshot);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Handles tool change events by reindexing all tools from the snapshot
|
|
278
|
+
*/
|
|
279
|
+
async handleToolChange(tools) {
|
|
280
|
+
// Clear and rebuild index
|
|
281
|
+
this.vectorDB.clear();
|
|
282
|
+
if (tools.length === 0) {
|
|
283
|
+
this.initialized = true;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Initialize ML model if needed (first time only, and only when we have tools)
|
|
287
|
+
// Deferred initialization avoids async operations when there's nothing to index
|
|
288
|
+
if (!this.mlInitialized && this.strategy === 'ml' && this.vectorDB instanceof vectoriadb_1.VectoriaDB) {
|
|
289
|
+
await this.vectorDB.initialize();
|
|
290
|
+
this.mlInitialized = true;
|
|
291
|
+
}
|
|
292
|
+
// Filter tools based on CodeCall config and per-tool metadata
|
|
293
|
+
const filteredTools = tools.filter((tool) => this.shouldIndexTool(tool));
|
|
294
|
+
if (filteredTools.length === 0) {
|
|
295
|
+
this.initialized = true;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const documents = filteredTools.map((tool) => {
|
|
299
|
+
const searchableText = this.extractSearchableText(tool);
|
|
300
|
+
const appId = this.extractAppId(tool);
|
|
301
|
+
const toolName = tool.name;
|
|
302
|
+
const qualifiedName = tool.fullName || toolName;
|
|
303
|
+
return {
|
|
304
|
+
id: toolName,
|
|
305
|
+
text: searchableText,
|
|
306
|
+
metadata: {
|
|
307
|
+
id: toolName,
|
|
308
|
+
toolName,
|
|
309
|
+
qualifiedName,
|
|
310
|
+
appId,
|
|
311
|
+
toolInstance: tool,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
if (this.strategy === 'ml' && this.vectorDB instanceof vectoriadb_1.VectoriaDB) {
|
|
316
|
+
await this.vectorDB.addMany(documents);
|
|
317
|
+
}
|
|
318
|
+
else if (this.vectorDB instanceof vectoriadb_1.TFIDFVectoria) {
|
|
319
|
+
this.vectorDB.addDocuments(documents);
|
|
320
|
+
this.vectorDB.reindex();
|
|
321
|
+
}
|
|
322
|
+
this.initialized = true;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Determines if a tool should be indexed in the search database.
|
|
326
|
+
* Filters based on:
|
|
327
|
+
* - Excludes codecall:* meta-tools (they should not be searchable)
|
|
328
|
+
* - Mode-based filtering (codecall_only, codecall_opt_in, metadata_driven)
|
|
329
|
+
* - Per-tool metadata.codecall.enabledInCodeCall
|
|
330
|
+
* - Custom includeTools filter function
|
|
331
|
+
*/
|
|
332
|
+
shouldIndexTool(tool) {
|
|
333
|
+
const toolName = tool.name || tool.fullName;
|
|
334
|
+
// Never index codecall:* meta-tools - they are for orchestration, not for search
|
|
335
|
+
if (toolName.startsWith('codecall:')) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
// Get CodeCall-specific metadata from the tool
|
|
339
|
+
const codecallMeta = this.getCodeCallMetadata(tool);
|
|
340
|
+
// Apply mode-based filtering
|
|
341
|
+
switch (this.config.mode) {
|
|
342
|
+
case 'codecall_only':
|
|
343
|
+
// In codecall_only mode, all non-codecall tools are searchable
|
|
344
|
+
// unless explicitly disabled via metadata
|
|
345
|
+
if (codecallMeta?.enabledInCodeCall === false) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
case 'codecall_opt_in':
|
|
350
|
+
// In opt_in mode, tools must explicitly opt-in via metadata
|
|
351
|
+
if (codecallMeta?.enabledInCodeCall !== true) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
case 'metadata_driven':
|
|
356
|
+
// In metadata_driven mode, default to enabled unless explicitly disabled
|
|
357
|
+
if (codecallMeta?.enabledInCodeCall === false) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
default:
|
|
362
|
+
// This should never happen due to constructor validation
|
|
363
|
+
// but provides defense-in-depth and satisfies exhaustive checking
|
|
364
|
+
throw new Error(`Unknown CodeCall mode: ${this.config.mode}`);
|
|
365
|
+
}
|
|
366
|
+
// Apply custom includeTools filter if provided
|
|
367
|
+
if (this.config.includeTools) {
|
|
368
|
+
const appId = this.extractAppId(tool);
|
|
369
|
+
const filterInfo = {
|
|
370
|
+
name: toolName,
|
|
371
|
+
appId,
|
|
372
|
+
source: codecallMeta?.source,
|
|
373
|
+
description: tool.metadata.description,
|
|
374
|
+
tags: codecallMeta?.tags || tool.metadata.tags,
|
|
375
|
+
};
|
|
376
|
+
if (!this.config.includeTools(filterInfo)) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Extract CodeCall-specific metadata from a tool.
|
|
384
|
+
*/
|
|
385
|
+
getCodeCallMetadata(tool) {
|
|
386
|
+
// NOTE: `any` cast is intentional - ToolMetadata has constrained generics
|
|
387
|
+
return tool.metadata?.codecall;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Initializes the search service by indexing all tools from the registry.
|
|
391
|
+
* NOTE: This method is now a no-op. Initialization is handled reactively
|
|
392
|
+
* via subscription to tool change events in the constructor.
|
|
393
|
+
* This method exists for interface compatibility.
|
|
394
|
+
*/
|
|
395
|
+
async initialize() {
|
|
396
|
+
// Initialization is now handled reactively via subscription
|
|
397
|
+
// The subscription with immediate=true in the constructor ensures tools are indexed
|
|
398
|
+
// This method exists for interface compatibility
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Cleanup subscription when service is destroyed
|
|
402
|
+
*/
|
|
403
|
+
dispose() {
|
|
404
|
+
if (this.unsubscribe) {
|
|
405
|
+
this.unsubscribe();
|
|
406
|
+
this.unsubscribe = undefined;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Extracts searchable text from a tool instance.
|
|
411
|
+
* Uses term weighting to improve relevance:
|
|
412
|
+
* - Description terms are heavily weighted (most important for semantic matching)
|
|
413
|
+
* - Tool name parts are tokenized and weighted
|
|
414
|
+
* - Tags provide additional context
|
|
415
|
+
*/
|
|
416
|
+
extractSearchableText(tool) {
|
|
417
|
+
const parts = [];
|
|
418
|
+
// Extract and weight tool name parts
|
|
419
|
+
// Split on common delimiters (: - _ .) to get meaningful tokens
|
|
420
|
+
if (tool.name) {
|
|
421
|
+
const nameParts = tool.name.split(/[:\-_.]/).filter(Boolean);
|
|
422
|
+
// Add each part twice for moderate weighting
|
|
423
|
+
for (const part of nameParts) {
|
|
424
|
+
parts.push(part, part);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Description is the most important for semantic matching
|
|
428
|
+
// Weight it heavily by repeating 3x
|
|
429
|
+
if (tool.metadata.description) {
|
|
430
|
+
const description = tool.metadata.description;
|
|
431
|
+
parts.push(description, description, description);
|
|
432
|
+
// Also extract key terms from description (words 4+ chars) for extra weight
|
|
433
|
+
const keyTerms = description
|
|
434
|
+
.toLowerCase()
|
|
435
|
+
.split(/\s+/)
|
|
436
|
+
.filter((word) => word.length >= 4 && !this.isStopWord(word));
|
|
437
|
+
parts.push(...keyTerms);
|
|
438
|
+
}
|
|
439
|
+
// Add tags with moderate weight (2x)
|
|
440
|
+
if (tool.metadata.tags && tool.metadata.tags.length > 0) {
|
|
441
|
+
for (const tag of tool.metadata.tags) {
|
|
442
|
+
parts.push(tag, tag);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Add input schema property names (useful for parameter-based searches)
|
|
446
|
+
if (tool.rawInputSchema && typeof tool.rawInputSchema === 'object') {
|
|
447
|
+
const schema = tool.rawInputSchema;
|
|
448
|
+
if (schema.properties) {
|
|
449
|
+
parts.push(...Object.keys(schema.properties));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Add example descriptions and input values to searchable text
|
|
453
|
+
// Examples help users find tools by use-case descriptions
|
|
454
|
+
const examples = tool.metadata?.examples;
|
|
455
|
+
if (examples && Array.isArray(examples)) {
|
|
456
|
+
for (const ex of examples) {
|
|
457
|
+
// Add example description (2x weight for relevance)
|
|
458
|
+
if (ex.description) {
|
|
459
|
+
parts.push(ex.description, ex.description);
|
|
460
|
+
}
|
|
461
|
+
// Add example input keys and string values
|
|
462
|
+
if (ex.input && typeof ex.input === 'object') {
|
|
463
|
+
for (const [key, value] of Object.entries(ex.input)) {
|
|
464
|
+
parts.push(key);
|
|
465
|
+
if (typeof value === 'string') {
|
|
466
|
+
parts.push(value);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return parts.join(' ');
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Checks if a word is a common stop word that should not receive extra weighting.
|
|
476
|
+
* Uses module-level STOP_WORDS constant to avoid recreating the Set on each call.
|
|
477
|
+
*/
|
|
478
|
+
isStopWord(word) {
|
|
479
|
+
return STOP_WORDS.has(word);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Extracts app ID from tool's owner lineage
|
|
483
|
+
*/
|
|
484
|
+
extractAppId(tool) {
|
|
485
|
+
if (!tool.owner)
|
|
486
|
+
return undefined;
|
|
487
|
+
// The owner structure has kind and id
|
|
488
|
+
if (tool.owner.kind === 'app') {
|
|
489
|
+
return tool.owner.id;
|
|
490
|
+
}
|
|
491
|
+
// If the tool is owned by a plugin, we need to look at its parent scope
|
|
492
|
+
// For now, we'll return undefined and rely on the lineage if needed
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Searches for tools matching the query
|
|
497
|
+
* Implements the ToolSearch interface
|
|
498
|
+
*/
|
|
499
|
+
async search(query, options = {}) {
|
|
500
|
+
const { topK = this.config.defaultTopK, appIds, excludeToolNames = [] } = options;
|
|
501
|
+
const minScore = this.config.defaultSimilarityThreshold;
|
|
502
|
+
// Build filter function
|
|
503
|
+
const filter = (metadata) => {
|
|
504
|
+
// Exclude tools
|
|
505
|
+
if (excludeToolNames.includes(metadata.toolName)) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
// Filter by appId if specified
|
|
509
|
+
if (appIds && appIds.length > 0) {
|
|
510
|
+
if (!metadata.appId || !appIds.includes(metadata.appId)) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return true;
|
|
515
|
+
};
|
|
516
|
+
// Expand query with synonyms for TF-IDF strategy to improve relevance
|
|
517
|
+
// For example: "add user" -> "add create new insert make user account member profile"
|
|
518
|
+
const effectiveQuery = this.synonymService ? this.synonymService.expandQuery(query) : query;
|
|
519
|
+
// Search using vectoriadb
|
|
520
|
+
const results = await this.vectorDB.search(effectiveQuery, {
|
|
521
|
+
topK,
|
|
522
|
+
threshold: minScore,
|
|
523
|
+
filter,
|
|
524
|
+
});
|
|
525
|
+
// Transform results to match the ToolSearch interface
|
|
526
|
+
return results.map((result) => ({
|
|
527
|
+
toolName: result.metadata.toolName,
|
|
528
|
+
appId: result.metadata.appId,
|
|
529
|
+
description: result.metadata.toolInstance.metadata.description || '',
|
|
530
|
+
relevanceScore: result.score,
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Gets all indexed tool names
|
|
535
|
+
*/
|
|
536
|
+
getAllToolNames() {
|
|
537
|
+
if (this.vectorDB instanceof vectoriadb_1.VectoriaDB) {
|
|
538
|
+
return this.vectorDB.getAll().map((doc) => doc.id);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
return this.vectorDB.getAllDocumentIds();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Gets the total number of indexed tools
|
|
546
|
+
*/
|
|
547
|
+
getTotalCount() {
|
|
548
|
+
if (this.vectorDB instanceof vectoriadb_1.VectoriaDB) {
|
|
549
|
+
return this.vectorDB.size();
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
return this.vectorDB.getDocumentCount();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Checks if a tool exists in the index
|
|
557
|
+
*/
|
|
558
|
+
hasTool(toolName) {
|
|
559
|
+
if (this.vectorDB instanceof vectoriadb_1.VectoriaDB) {
|
|
560
|
+
return this.vectorDB.has(toolName);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
return this.vectorDB.hasDocument(toolName);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Clears the entire index
|
|
568
|
+
*/
|
|
569
|
+
clear() {
|
|
570
|
+
this.vectorDB.clear();
|
|
571
|
+
this.initialized = false;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get the current embedding strategy
|
|
575
|
+
*/
|
|
576
|
+
getStrategy() {
|
|
577
|
+
return this.strategy;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Check if the service is initialized
|
|
581
|
+
*/
|
|
582
|
+
isInitialized() {
|
|
583
|
+
return this.initialized;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
exports.ToolSearchService = ToolSearchService;
|
|
587
|
+
//# sourceMappingURL=tool-search.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-search.service.js","sourceRoot":"","sources":["../../../../src/codecall/services/tool-search.service.ts"],"names":[],"mappings":"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","sourcesContent":["// file: libs/plugins/src/codecall/services/tool-search.service.ts\n\nimport { ToolEntry, ScopeEntry } from '@frontmcp/sdk';\nimport { TFIDFVectoria, VectoriaDB, DocumentMetadata } from 'vectoriadb';\nimport type {\n EmbeddingStrategy,\n CodeCallEmbeddingOptions,\n CodeCallMode,\n CodeCallToolMetadata,\n} from '../codecall.types';\nimport type {\n ToolSearch,\n ToolSearchResult as SymbolToolSearchResult,\n ToolSearchOptions as SymbolToolSearchOptions,\n} from '../codecall.symbol';\nimport { SynonymExpansionService, SynonymExpansionConfig } from './synonym-expansion.service';\n\n/**\n * Universal Intent Mapping & Query Normalization\n * - This module defines the semantic knowledge base for the MCP tool search engine.\n * It is designed to bridge the gap between natural language user intents\n * (e.g., \"fix,\" \"chat,\" \"buy\") and technical tool definitions (e.g., \"update,\" \"post,\" \"create\").\n * - Key Parts:\n * 1. DEFAULT_SYNONYM_GROUPS: A domain-agnostic mapping of bidirectional synonyms covering\n * CRUD, DevOps, Financial, Social, and Lifecycle operations. This ensures that a\n * query for \"Show me the bill\" matches a tool named \"get_invoice\".\n * 2. STOP_WORDS: A curated exclusion list strictly optimized for Command-Line/Chat\n * interfaces. Unlike standard NLP stop lists, this PRESERVES action verbs\n * (\"find\", \"start\", \"make\") as they are critical signals of user intent in a\n * tool-execution context.\n */\nconst STOP_WORDS: ReadonlySet<string> = new Set([\n // Articles & Determiners\n 'the',\n 'a',\n 'an',\n 'this',\n 'that',\n 'these',\n 'those',\n\n // Prepositions\n 'in',\n 'on',\n 'at',\n 'to',\n 'for',\n 'of',\n 'with',\n 'by',\n 'from',\n 'into',\n 'over',\n 'after',\n 'before',\n 'between',\n 'under',\n 'about',\n 'against',\n 'during',\n 'through',\n\n // Conjunctions\n 'and',\n 'or',\n 'but',\n 'nor',\n 'so',\n 'yet',\n 'as',\n 'than',\n 'if',\n 'because',\n 'while',\n 'when',\n 'where',\n 'unless',\n\n // Pronouns (Subject/Object/Possessive)\n 'i',\n 'me',\n 'my',\n 'mine',\n 'myself',\n 'you',\n 'your',\n 'yours',\n 'yourself',\n 'he',\n 'him',\n 'his',\n 'himself',\n 'she',\n 'her',\n 'hers',\n 'herself',\n 'it',\n 'its',\n 'itself',\n 'we',\n 'us',\n 'our',\n 'ours',\n 'ourselves',\n 'they',\n 'them',\n 'their',\n 'theirs',\n 'themselves',\n 'who',\n 'whom',\n 'whose',\n 'which',\n 'what',\n\n // Auxiliary/Linking Verbs (State of being is usually noise, Action is signal)\n 'is',\n 'was',\n 'are',\n 'were',\n 'been',\n 'be',\n 'being',\n 'have',\n 'has',\n 'had',\n 'having',\n 'do',\n 'does',\n 'did',\n 'doing', // \"do\" is usually auxiliary (\"do you have...\"). \"run\" or \"execute\" is better.\n 'will',\n 'would',\n 'shall',\n 'should',\n 'can',\n 'could',\n 'may',\n 'might',\n 'must',\n\n // Quantifiers / Adverbs of degree\n 'all',\n 'any',\n 'both',\n 'each',\n 'few',\n 'more',\n 'most',\n 'other',\n 'some',\n 'such',\n 'no',\n 'nor',\n 'not',\n 'only',\n 'own',\n 'same',\n 'too',\n 'very',\n 'just',\n 'even',\n\n // Conversational / Chat Fillers (Common in LLM prompts)\n 'please',\n 'pls',\n 'plz',\n 'thanks',\n 'thank',\n 'thx',\n 'hello',\n 'hi',\n 'hey',\n 'ok',\n 'okay',\n 'yes',\n 'no',\n 'actually',\n 'basically',\n 'literally',\n 'maybe',\n 'perhaps',\n 'now',\n 'then',\n 'here',\n 'there',\n 'again',\n 'once',\n 'back', // \"back\" can be tricky, but usually implies direction not action\n\n // Meta/Structural words\n 'example',\n 'context',\n 'optionally',\n 'optional', // Users rarely search for \"optional\", they search for the thing itself.\n 'etc',\n 'ie',\n 'eg',\n]);\n/**\n * Metadata structure for tool documents in the vector database\n */\n// NOTE: `any` is intentional - ToolEntry has constrained generics that don't work with `unknown`\ninterface ToolMetadata extends DocumentMetadata {\n id: string;\n toolName: string;\n qualifiedName: string;\n appId?: string;\n toolInstance: ToolEntry<any, any>;\n}\n\n/**\n * Search result for tool search\n */\n// NOTE: `any` is intentional - ToolEntry has constrained generics that don't work with `unknown`\nexport interface SearchResult {\n tool: ToolEntry<any, any>;\n score: number;\n toolName: string;\n qualifiedName: string;\n appId?: string;\n}\n\n/**\n * Search options for tool search\n */\nexport interface SearchOptions {\n topK?: number;\n appIds?: string[];\n excludeToolNames?: string[];\n minScore?: number;\n}\n\n/**\n * Filter function type for including tools\n */\nexport type IncludeToolsFilter = (info: {\n name: string;\n appId?: string;\n source?: string;\n description?: string;\n tags?: string[];\n}) => boolean;\n\n/**\n * Configuration for tool search service\n */\nexport interface ToolSearchServiceConfig {\n /**\n * Embedding strategy to use\n * @default 'tfidf'\n */\n strategy?: EmbeddingStrategy;\n\n /**\n * Full embedding options (alternative to just strategy)\n */\n embeddingOptions?: CodeCallEmbeddingOptions;\n\n /**\n * Default number of results to return\n * @default 8\n */\n defaultTopK?: number;\n\n /**\n * Default similarity threshold\n * @default 0.0\n */\n defaultSimilarityThreshold?: number;\n\n /**\n * CodeCall mode for filtering tools\n * @default 'codecall_only'\n */\n mode?: CodeCallMode;\n\n /**\n * Optional filter function for including tools in the search index\n */\n includeTools?: IncludeToolsFilter;\n\n /**\n * Synonym expansion configuration.\n * When enabled, queries are expanded with synonyms to improve search relevance.\n * For example, \"add user\" will also match tools containing \"create user\".\n * Only applies when strategy is 'tfidf' (ML already handles semantic similarity).\n *\n * Set to false to disable, or provide a config object to customize.\n * @default { enabled: true } when strategy is 'tfidf'\n */\n synonymExpansion?: false | (SynonymExpansionConfig & { enabled?: boolean });\n}\n\n/**\n * Service that maintains a searchable index of tools from the ToolRegistry\n * Supports both TF-IDF (lightweight, synchronous) and ML-based (semantic) embeddings\n * Implements the ToolSearch interface for dependency injection\n */\nexport class ToolSearchService implements ToolSearch {\n private static readonly MAX_SUBSCRIPTION_RETRIES = 100;\n private vectorDB: TFIDFVectoria<ToolMetadata> | VectoriaDB<ToolMetadata>;\n private strategy: EmbeddingStrategy;\n private initialized = false;\n private mlInitialized = false;\n private subscriptionRetries = 0;\n private config: Required<Omit<ToolSearchServiceConfig, 'includeTools' | 'mode' | 'synonymExpansion'>> & {\n mode: CodeCallMode;\n includeTools?: IncludeToolsFilter;\n };\n private scope: ScopeEntry;\n private unsubscribe?: () => void;\n private synonymService: SynonymExpansionService | null = null;\n\n constructor(config: ToolSearchServiceConfig = {}, scope: ScopeEntry) {\n this.scope = scope;\n const embeddingOptions: CodeCallEmbeddingOptions = config.embeddingOptions || {\n strategy: 'tfidf',\n modelName: 'Xenova/all-MiniLM-L6-v2',\n cacheDir: './.cache/transformers',\n useHNSW: false,\n synonymExpansion: { enabled: true, replaceDefaults: false, maxExpansionsPerTerm: 5 },\n };\n this.strategy = config.strategy || embeddingOptions.strategy || 'tfidf';\n\n this.config = {\n strategy: this.strategy,\n embeddingOptions,\n defaultTopK: config.defaultTopK ?? 8,\n defaultSimilarityThreshold: config.defaultSimilarityThreshold ?? 0.0,\n mode: config.mode ?? 'codecall_only',\n includeTools: config.includeTools,\n };\n\n // Validate mode parameter at runtime\n const validModes = ['codecall_only', 'codecall_opt_in', 'metadata_driven'] as const;\n if (!validModes.includes(this.config.mode as (typeof validModes)[number])) {\n throw new Error(`Invalid CodeCall mode: ${this.config.mode}. Valid modes: ${validModes.join(', ')}`);\n }\n\n // Initialize the appropriate vector database\n if (this.strategy === 'ml') {\n this.vectorDB = new VectoriaDB<ToolMetadata>({\n modelName: embeddingOptions.modelName || 'Xenova/all-MiniLM-L6-v2',\n cacheDir: embeddingOptions.cacheDir || './.cache/transformers',\n defaultTopK: this.config.defaultTopK,\n defaultSimilarityThreshold: this.config.defaultSimilarityThreshold,\n useHNSW: embeddingOptions.useHNSW || false,\n });\n } else {\n this.vectorDB = new TFIDFVectoria<ToolMetadata>({\n defaultTopK: this.config.defaultTopK,\n defaultSimilarityThreshold: this.config.defaultSimilarityThreshold,\n });\n }\n\n // Initialize synonym expansion for TF-IDF strategy (ML already handles semantic similarity)\n if (config.synonymExpansion === false) {\n this.synonymService = null;\n } else if (this.strategy === 'tfidf') {\n const synonymConfig = typeof config.synonymExpansion === 'object' ? config.synonymExpansion : {};\n // Only enable if not explicitly disabled\n if (synonymConfig.enabled !== false) {\n this.synonymService = new SynonymExpansionService(synonymConfig);\n }\n }\n\n // Defer subscription until scope.tools is available\n // During plugin initialization, scope.tools may not exist yet\n this.setupSubscription();\n }\n\n /**\n * Sets up subscription to tool changes. Handles the case where scope.tools\n * may not be available yet during plugin initialization.\n */\n private setupSubscription(): void {\n // If tools registry is not yet available, retry after a microtask\n if (!this.scope.tools) {\n if (this.subscriptionRetries++ >= ToolSearchService.MAX_SUBSCRIPTION_RETRIES) {\n console.warn('ToolSearchService: scope.tools not available after max retries');\n return;\n }\n // Use queueMicrotask to defer until after current initialization\n queueMicrotask(() => this.setupSubscription());\n return;\n }\n // Reset retry counter on success\n this.subscriptionRetries = 0;\n\n // Subscribe to tool changes with immediate=true to get current snapshot\n // This ensures tools are indexed as they become available, regardless of loading order\n this.unsubscribe = this.scope.tools.subscribe({ immediate: true }, (event) => {\n // Handle tool change event - reindex all tools from the snapshot\n this.handleToolChange(event.snapshot as unknown as ToolEntry<any, any>[]);\n });\n }\n\n /**\n * Handles tool change events by reindexing all tools from the snapshot\n */\n private async handleToolChange(tools: ToolEntry<any, any>[]): Promise<void> {\n // Clear and rebuild index\n this.vectorDB.clear();\n\n if (tools.length === 0) {\n this.initialized = true;\n return;\n }\n\n // Initialize ML model if needed (first time only, and only when we have tools)\n // Deferred initialization avoids async operations when there's nothing to index\n if (!this.mlInitialized && this.strategy === 'ml' && this.vectorDB instanceof VectoriaDB) {\n await this.vectorDB.initialize();\n this.mlInitialized = true;\n }\n\n // Filter tools based on CodeCall config and per-tool metadata\n const filteredTools = tools.filter((tool) => this.shouldIndexTool(tool));\n\n if (filteredTools.length === 0) {\n this.initialized = true;\n return;\n }\n\n const documents = filteredTools.map((tool) => {\n const searchableText = this.extractSearchableText(tool);\n const appId = this.extractAppId(tool);\n const toolName = tool.name;\n const qualifiedName = tool.fullName || toolName;\n\n return {\n id: toolName,\n text: searchableText,\n metadata: {\n id: toolName,\n toolName,\n qualifiedName,\n appId,\n toolInstance: tool,\n },\n };\n });\n\n if (this.strategy === 'ml' && this.vectorDB instanceof VectoriaDB) {\n await this.vectorDB.addMany(documents);\n } else if (this.vectorDB instanceof TFIDFVectoria) {\n this.vectorDB.addDocuments(documents);\n this.vectorDB.reindex();\n }\n\n this.initialized = true;\n }\n\n /**\n * Determines if a tool should be indexed in the search database.\n * Filters based on:\n * - Excludes codecall:* meta-tools (they should not be searchable)\n * - Mode-based filtering (codecall_only, codecall_opt_in, metadata_driven)\n * - Per-tool metadata.codecall.enabledInCodeCall\n * - Custom includeTools filter function\n */\n private shouldIndexTool(tool: ToolEntry<any, any>): boolean {\n const toolName = tool.name || tool.fullName;\n\n // Never index codecall:* meta-tools - they are for orchestration, not for search\n if (toolName.startsWith('codecall:')) {\n return false;\n }\n\n // Get CodeCall-specific metadata from the tool\n const codecallMeta = this.getCodeCallMetadata(tool);\n\n // Apply mode-based filtering\n switch (this.config.mode) {\n case 'codecall_only':\n // In codecall_only mode, all non-codecall tools are searchable\n // unless explicitly disabled via metadata\n if (codecallMeta?.enabledInCodeCall === false) {\n return false;\n }\n break;\n\n case 'codecall_opt_in':\n // In opt_in mode, tools must explicitly opt-in via metadata\n if (codecallMeta?.enabledInCodeCall !== true) {\n return false;\n }\n break;\n\n case 'metadata_driven':\n // In metadata_driven mode, default to enabled unless explicitly disabled\n if (codecallMeta?.enabledInCodeCall === false) {\n return false;\n }\n break;\n\n default:\n // This should never happen due to constructor validation\n // but provides defense-in-depth and satisfies exhaustive checking\n throw new Error(`Unknown CodeCall mode: ${this.config.mode}`);\n }\n\n // Apply custom includeTools filter if provided\n if (this.config.includeTools) {\n const appId = this.extractAppId(tool);\n const filterInfo = {\n name: toolName,\n appId,\n source: codecallMeta?.source,\n description: tool.metadata.description,\n tags: codecallMeta?.tags || tool.metadata.tags,\n };\n\n if (!this.config.includeTools(filterInfo)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Extract CodeCall-specific metadata from a tool.\n */\n private getCodeCallMetadata(tool: ToolEntry<any, any>): CodeCallToolMetadata | undefined {\n // NOTE: `any` cast is intentional - ToolMetadata has constrained generics\n return (tool.metadata as any)?.codecall as CodeCallToolMetadata | undefined;\n }\n\n /**\n * Initializes the search service by indexing all tools from the registry.\n * NOTE: This method is now a no-op. Initialization is handled reactively\n * via subscription to tool change events in the constructor.\n * This method exists for interface compatibility.\n */\n async initialize(): Promise<void> {\n // Initialization is now handled reactively via subscription\n // The subscription with immediate=true in the constructor ensures tools are indexed\n // This method exists for interface compatibility\n }\n\n /**\n * Cleanup subscription when service is destroyed\n */\n dispose(): void {\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = undefined;\n }\n }\n\n /**\n * Extracts searchable text from a tool instance.\n * Uses term weighting to improve relevance:\n * - Description terms are heavily weighted (most important for semantic matching)\n * - Tool name parts are tokenized and weighted\n * - Tags provide additional context\n */\n private extractSearchableText(tool: ToolEntry<any, any>): string {\n const parts: string[] = [];\n\n // Extract and weight tool name parts\n // Split on common delimiters (: - _ .) to get meaningful tokens\n if (tool.name) {\n const nameParts = tool.name.split(/[:\\-_.]/).filter(Boolean);\n // Add each part twice for moderate weighting\n for (const part of nameParts) {\n parts.push(part, part);\n }\n }\n\n // Description is the most important for semantic matching\n // Weight it heavily by repeating 3x\n if (tool.metadata.description) {\n const description = tool.metadata.description;\n parts.push(description, description, description);\n\n // Also extract key terms from description (words 4+ chars) for extra weight\n const keyTerms = description\n .toLowerCase()\n .split(/\\s+/)\n .filter((word) => word.length >= 4 && !this.isStopWord(word));\n parts.push(...keyTerms);\n }\n\n // Add tags with moderate weight (2x)\n if (tool.metadata.tags && tool.metadata.tags.length > 0) {\n for (const tag of tool.metadata.tags) {\n parts.push(tag, tag);\n }\n }\n\n // Add input schema property names (useful for parameter-based searches)\n if (tool.rawInputSchema && typeof tool.rawInputSchema === 'object') {\n const schema = tool.rawInputSchema as any;\n if (schema.properties) {\n parts.push(...Object.keys(schema.properties));\n }\n }\n\n // Add example descriptions and input values to searchable text\n // Examples help users find tools by use-case descriptions\n const examples = tool.metadata?.examples;\n if (examples && Array.isArray(examples)) {\n for (const ex of examples) {\n // Add example description (2x weight for relevance)\n if (ex.description) {\n parts.push(ex.description, ex.description);\n }\n // Add example input keys and string values\n if (ex.input && typeof ex.input === 'object') {\n for (const [key, value] of Object.entries(ex.input as Record<string, unknown>)) {\n parts.push(key);\n if (typeof value === 'string') {\n parts.push(value);\n }\n }\n }\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Checks if a word is a common stop word that should not receive extra weighting.\n * Uses module-level STOP_WORDS constant to avoid recreating the Set on each call.\n */\n private isStopWord(word: string): boolean {\n return STOP_WORDS.has(word);\n }\n\n /**\n * Extracts app ID from tool's owner lineage\n */\n private extractAppId(tool: ToolEntry<any, any>): string | undefined {\n if (!tool.owner) return undefined;\n\n // The owner structure has kind and id\n if (tool.owner.kind === 'app') {\n return tool.owner.id;\n }\n\n // If the tool is owned by a plugin, we need to look at its parent scope\n // For now, we'll return undefined and rely on the lineage if needed\n return undefined;\n }\n\n /**\n * Searches for tools matching the query\n * Implements the ToolSearch interface\n */\n async search(query: string, options: SymbolToolSearchOptions = {}): Promise<SymbolToolSearchResult[]> {\n const { topK = this.config.defaultTopK, appIds, excludeToolNames = [] } = options;\n const minScore = this.config.defaultSimilarityThreshold;\n\n // Build filter function\n const filter = (metadata: ToolMetadata): boolean => {\n // Exclude tools\n if (excludeToolNames.includes(metadata.toolName)) {\n return false;\n }\n\n // Filter by appId if specified\n if (appIds && appIds.length > 0) {\n if (!metadata.appId || !appIds.includes(metadata.appId)) {\n return false;\n }\n }\n\n return true;\n };\n\n // Expand query with synonyms for TF-IDF strategy to improve relevance\n // For example: \"add user\" -> \"add create new insert make user account member profile\"\n const effectiveQuery = this.synonymService ? this.synonymService.expandQuery(query) : query;\n\n // Search using vectoriadb\n const results = await this.vectorDB.search(effectiveQuery, {\n topK,\n threshold: minScore,\n filter,\n });\n\n // Transform results to match the ToolSearch interface\n return results.map((result) => ({\n toolName: result.metadata.toolName,\n appId: result.metadata.appId,\n description: result.metadata.toolInstance.metadata.description || '',\n relevanceScore: result.score,\n }));\n }\n\n /**\n * Gets all indexed tool names\n */\n getAllToolNames(): string[] {\n if (this.vectorDB instanceof VectoriaDB) {\n return this.vectorDB.getAll().map((doc) => doc.id);\n } else {\n return this.vectorDB.getAllDocumentIds();\n }\n }\n\n /**\n * Gets the total number of indexed tools\n */\n getTotalCount(): number {\n if (this.vectorDB instanceof VectoriaDB) {\n return this.vectorDB.size();\n } else {\n return this.vectorDB.getDocumentCount();\n }\n }\n\n /**\n * Checks if a tool exists in the index\n */\n hasTool(toolName: string): boolean {\n if (this.vectorDB instanceof VectoriaDB) {\n return this.vectorDB.has(toolName);\n } else {\n return this.vectorDB.hasDocument(toolName);\n }\n }\n\n /**\n * Clears the entire index\n */\n clear(): void {\n this.vectorDB.clear();\n this.initialized = false;\n }\n\n /**\n * Get the current embedding strategy\n */\n getStrategy(): EmbeddingStrategy {\n return this.strategy;\n }\n\n /**\n * Check if the service is initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n}\n"]}
|