@salesforce/mcp 0.18.0 → 0.19.1-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +0 -2
- package/lib/main-server-provider.d.ts +1 -5
- package/lib/main-server-provider.js +0 -10
- package/lib/registry.js +0 -2
- package/package.json +2 -4
- package/lib/scripts/build-index.d.ts +0 -1
- package/lib/scripts/build-index.js +0 -124
- package/lib/scripts/create-embedding-text.d.ts +0 -29
- package/lib/scripts/create-embedding-text.js +0 -468
- package/lib/tools/sf-suggest-cli-command.d.ts +0 -26
- package/lib/tools/sf-suggest-cli-command.js +0 -120
- package/lib/utils/assets.d.ts +0 -21
- package/lib/utils/assets.js +0 -116
package/lib/index.js
CHANGED
|
@@ -20,7 +20,6 @@ import { Command, Flags, ux } from '@oclif/core';
|
|
|
20
20
|
import Cache from './utils/cache.js';
|
|
21
21
|
import { Telemetry } from './telemetry.js';
|
|
22
22
|
import { SfMcpServer } from './sf-mcp-server.js';
|
|
23
|
-
import { maybeBuildIndex } from './utils/assets.js';
|
|
24
23
|
import { registerToolsets } from './utils/registry-utils.js';
|
|
25
24
|
import { Services } from './services.js';
|
|
26
25
|
/**
|
|
@@ -140,7 +139,6 @@ You can also use special values to control access to orgs:
|
|
|
140
139
|
}, {
|
|
141
140
|
telemetry: this.telemetry,
|
|
142
141
|
});
|
|
143
|
-
await maybeBuildIndex(this.config.dataDir);
|
|
144
142
|
const services = new Services({ telemetry: this.telemetry, dataDir: this.config.dataDir });
|
|
145
143
|
await registerToolsets(flags.toolsets ?? ['all'], flags['dynamic-tools'] ?? false, flags['allow-non-ga-tools'] ?? false, server, services);
|
|
146
144
|
const transport = new StdioServerTransport();
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpTool } from '@salesforce/mcp-provider-api';
|
|
2
2
|
import { SfMcpServer } from './sf-mcp-server.js';
|
|
3
3
|
export declare function createDynamicServerTools(server: SfMcpServer): McpTool[];
|
|
4
|
-
export declare class MainServerProvider extends McpProvider {
|
|
5
|
-
getName(): string;
|
|
6
|
-
provideTools(services: Services): Promise<McpTool[]>;
|
|
7
|
-
}
|
|
@@ -13,19 +13,9 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { McpProvider } from '@salesforce/mcp-provider-api';
|
|
17
16
|
import { EnableToolsMcpTool } from './tools/sf-enable-tools.js';
|
|
18
17
|
import { ListToolsMcpTool } from './tools/sf-list-tools.js';
|
|
19
|
-
import { SuggestCliCommandMcpTool } from './tools/sf-suggest-cli-command.js';
|
|
20
18
|
export function createDynamicServerTools(server) {
|
|
21
19
|
return [new EnableToolsMcpTool(server), new ListToolsMcpTool()];
|
|
22
20
|
}
|
|
23
|
-
export class MainServerProvider extends McpProvider {
|
|
24
|
-
getName() {
|
|
25
|
-
return 'MainServerProvider';
|
|
26
|
-
}
|
|
27
|
-
async provideTools(services) {
|
|
28
|
-
return Promise.resolve([new SuggestCliCommandMcpTool(services)]);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
21
|
//# sourceMappingURL=main-server-provider.js.map
|
package/lib/registry.js
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { DxCoreMcpProvider } from '@salesforce/mcp-provider-dx-core';
|
|
17
17
|
import { CodeAnalyzerMcpProvider } from '@salesforce/mcp-provider-code-analyzer';
|
|
18
|
-
import { MainServerProvider } from './main-server-provider.js';
|
|
19
18
|
/** -------- ADD McpProvider INSTANCES HERE ------------------------------------------------------------------------- */
|
|
20
19
|
export const MCP_PROVIDER_REGISTRY = [
|
|
21
|
-
new MainServerProvider(),
|
|
22
20
|
new DxCoreMcpProvider(),
|
|
23
21
|
new CodeAnalyzerMcpProvider(),
|
|
24
22
|
// Add new instances here
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1-dev.0",
|
|
4
4
|
"description": "MCP Server for interacting with Salesforce instances",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sf-mcp-server": "bin/run.js"
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"package.json"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@huggingface/transformers": "^3.7.0",
|
|
46
45
|
"@modelcontextprotocol/sdk": "^1.16.0",
|
|
47
46
|
"@oclif/core": "^4.5.1",
|
|
48
47
|
"@salesforce/agents": "^0.15.4",
|
|
@@ -50,13 +49,12 @@
|
|
|
50
49
|
"@salesforce/core": "^8.18.0",
|
|
51
50
|
"@salesforce/kit": "^3.1.6",
|
|
52
51
|
"@salesforce/mcp-provider-api": "0.2.0",
|
|
53
|
-
"@salesforce/mcp-provider-dx-core": "0.2.
|
|
52
|
+
"@salesforce/mcp-provider-dx-core": "0.2.2",
|
|
54
53
|
"@salesforce/mcp-provider-code-analyzer": "0.0.2",
|
|
55
54
|
"@salesforce/source-deploy-retrieve": "^12.22.0",
|
|
56
55
|
"@salesforce/source-tracking": "^7.4.8",
|
|
57
56
|
"@salesforce/telemetry": "^6.1.0",
|
|
58
57
|
"@salesforce/ts-types": "^2.0.11",
|
|
59
|
-
"faiss-node": "^0.5.1",
|
|
60
58
|
"zod": "^3.25.76"
|
|
61
59
|
},
|
|
62
60
|
"devDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2025, Salesforce, Inc.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import { execSync } from 'node:child_process';
|
|
17
|
-
import fs from 'node:fs';
|
|
18
|
-
import path from 'node:path';
|
|
19
|
-
import { Args, Parser, ux } from '@oclif/core';
|
|
20
|
-
import { pipeline } from '@huggingface/transformers';
|
|
21
|
-
import faiss from 'faiss-node';
|
|
22
|
-
import { createWeightedEmbeddingText } from './create-embedding-text.js';
|
|
23
|
-
const normalizeCommandName = (command) => (command ?? '').split(':').join(' ');
|
|
24
|
-
const main = async () => {
|
|
25
|
-
const { args: { outputDir }, } = await Parser.parse(process.argv.slice(2), {
|
|
26
|
-
args: {
|
|
27
|
-
outputDir: Args.string({
|
|
28
|
-
description: 'Directory to save the output files',
|
|
29
|
-
required: true,
|
|
30
|
-
}),
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
if (!outputDir) {
|
|
34
|
-
ux.stderr('Output directory not specified. Please provide a path as the first argument.');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
// Define the output file paths
|
|
38
|
-
const commandsPath = path.join(outputDir, 'sf-commands.json');
|
|
39
|
-
const faissIndexPath = path.join(outputDir, 'commands-index.bin');
|
|
40
|
-
ux.stderr('Starting offline data preparation...');
|
|
41
|
-
// 1. Ensure output directory exists
|
|
42
|
-
if (!fs.existsSync(outputDir)) {
|
|
43
|
-
fs.mkdirSync(outputDir);
|
|
44
|
-
}
|
|
45
|
-
// 2. Get Command Data from Salesforce CLI
|
|
46
|
-
ux.stderr('Fetching commands from sf CLI...');
|
|
47
|
-
const rawCommandsJson = execSync('sf commands --json').toString();
|
|
48
|
-
const rawCommands = JSON.parse(rawCommandsJson);
|
|
49
|
-
// 3. Process and Clean the Data
|
|
50
|
-
ux.stderr('Processing and cleaning command data...');
|
|
51
|
-
const commandsData = rawCommands.map((cmd, index) => ({
|
|
52
|
-
id: index, // Use our own sequential ID for FAISS
|
|
53
|
-
command: normalizeCommandName(cmd.id),
|
|
54
|
-
summary: cmd.summary ?? 'No summary available.',
|
|
55
|
-
description: cmd.description ?? 'No description available.',
|
|
56
|
-
examples: cmd.examples ?? [],
|
|
57
|
-
flags: Object.values(cmd.flags ?? {})
|
|
58
|
-
.filter((flag) => !flag.hidden)
|
|
59
|
-
.map((flag) => ({
|
|
60
|
-
name: flag.name,
|
|
61
|
-
summary: flag.summary ?? 'No summary available.',
|
|
62
|
-
description: flag.description ?? 'No description available.',
|
|
63
|
-
type: flag.type ?? 'string',
|
|
64
|
-
required: flag.required ?? false,
|
|
65
|
-
// @ts-expect-error because options doesn't exist on boolean flags
|
|
66
|
-
options: (flag.options ?? []),
|
|
67
|
-
// @ts-expect-error because multiple doesn't exist on boolean flags
|
|
68
|
-
multiple: !!flag.multiple,
|
|
69
|
-
dependsOn: flag.dependsOn,
|
|
70
|
-
exclusive: flag.exclusive,
|
|
71
|
-
atLeastOne: flag.atLeastOne,
|
|
72
|
-
exactlyOne: flag.exactlyOne,
|
|
73
|
-
relationships: flag.relationships,
|
|
74
|
-
default: flag.default,
|
|
75
|
-
})),
|
|
76
|
-
// Create a more descriptive text for better embedding quality
|
|
77
|
-
// This will be stripped from the final output sent to the LLM to save token count
|
|
78
|
-
embeddingText: createWeightedEmbeddingText({
|
|
79
|
-
command: normalizeCommandName(cmd.id),
|
|
80
|
-
summary: cmd.summary ?? '',
|
|
81
|
-
description: cmd.description ?? '',
|
|
82
|
-
examples: cmd.examples ?? [],
|
|
83
|
-
}),
|
|
84
|
-
}));
|
|
85
|
-
if (commandsData.length === 0) {
|
|
86
|
-
ux.stderr('No command data could be processed. Is `sf` CLI installed and working?');
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
ux.stderr(`Processed ${commandsData.length} commands.`);
|
|
90
|
-
// 4. Generate Embeddings
|
|
91
|
-
ux.stderr('Loading embedding model... (This may take a moment)');
|
|
92
|
-
const embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
|
|
93
|
-
dtype: 'fp32',
|
|
94
|
-
});
|
|
95
|
-
ux.stderr('Generating embeddings for all commands...');
|
|
96
|
-
const embeddings = await Promise.all(commandsData.map((cmd) => embedder(cmd.embeddingText, { pooling: 'mean', normalize: true })));
|
|
97
|
-
// The output tensor needs to be converted to a flat Float32Array for FAISS
|
|
98
|
-
const embeddingDimension = embeddings[0].dims[1];
|
|
99
|
-
const flattenedEmbeddings = new Float32Array(commandsData.length * embeddingDimension);
|
|
100
|
-
embeddings.forEach((tensor, i) => {
|
|
101
|
-
flattenedEmbeddings.set(tensor.data, i * embeddingDimension);
|
|
102
|
-
});
|
|
103
|
-
ux.stderr(`Generated embeddings with dimension: ${embeddingDimension}`);
|
|
104
|
-
// 5. Build and Save the FAISS Index
|
|
105
|
-
ux.stderr('Building FAISS index...');
|
|
106
|
-
const index = new faiss.IndexFlatL2(embeddingDimension);
|
|
107
|
-
// Convert Float32Array to regular array for faiss-node
|
|
108
|
-
const embeddingsArray = Array.from(flattenedEmbeddings);
|
|
109
|
-
index.add(embeddingsArray);
|
|
110
|
-
const vectorCount = index.ntotal();
|
|
111
|
-
ux.stderr(`FAISS index built with ${String(vectorCount)} vectors.`);
|
|
112
|
-
// Use the correct method name for faiss-node
|
|
113
|
-
index.write(faissIndexPath);
|
|
114
|
-
ux.stderr(`FAISS index saved to: ${faissIndexPath}`);
|
|
115
|
-
// 6. Save the Processed Command Data
|
|
116
|
-
fs.writeFileSync(commandsPath, JSON.stringify(commandsData, null, 2));
|
|
117
|
-
ux.stderr(`Command data saved to: ${commandsPath}`);
|
|
118
|
-
ux.stderr('Offline preparation complete!');
|
|
119
|
-
};
|
|
120
|
-
main().catch((error) => {
|
|
121
|
-
// eslint-disable-next-line no-console
|
|
122
|
-
console.error(error);
|
|
123
|
-
});
|
|
124
|
-
//# sourceMappingURL=build-index.js.map
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Action words - verbs that describe what a command does
|
|
3
|
-
* These should get the highest weight (3x) in embedding text
|
|
4
|
-
*/
|
|
5
|
-
export declare const SF_ACTION_WORDS: readonly string[];
|
|
6
|
-
/**
|
|
7
|
-
* Domain words - nouns that represent Salesforce concepts and objects
|
|
8
|
-
* These should get medium weight (2x) in embedding text
|
|
9
|
-
*/
|
|
10
|
-
export declare const SF_DOMAIN_WORDS: readonly string[];
|
|
11
|
-
/**
|
|
12
|
-
* Modifier words - adjectives and adverbs that modify actions
|
|
13
|
-
* These should get light weight (1.5x) in embedding text
|
|
14
|
-
*/
|
|
15
|
-
export declare const SF_MODIFIER_WORDS: readonly string[];
|
|
16
|
-
/**
|
|
17
|
-
* Context words - structural terms that provide context
|
|
18
|
-
* These should get normal weight (1x) in embedding text
|
|
19
|
-
*/
|
|
20
|
-
export declare const SF_CONTEXT_WORDS: readonly string[];
|
|
21
|
-
/**
|
|
22
|
-
* Create weighted embedding text for a command
|
|
23
|
-
*/
|
|
24
|
-
export declare function createWeightedEmbeddingText({ command, summary, description, examples, }: {
|
|
25
|
-
command: string;
|
|
26
|
-
summary: string;
|
|
27
|
-
description: string;
|
|
28
|
-
examples: string[];
|
|
29
|
-
}): string;
|
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2025, Salesforce, Inc.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Action words - verbs that describe what a command does
|
|
18
|
-
* These should get the highest weight (3x) in embedding text
|
|
19
|
-
*/
|
|
20
|
-
export const SF_ACTION_WORDS = [
|
|
21
|
-
// Core CRUD operations
|
|
22
|
-
'create',
|
|
23
|
-
'generate',
|
|
24
|
-
'delete',
|
|
25
|
-
'update',
|
|
26
|
-
'upsert',
|
|
27
|
-
'get',
|
|
28
|
-
'set',
|
|
29
|
-
'unset',
|
|
30
|
-
// Deployment & sync operations
|
|
31
|
-
'deploy',
|
|
32
|
-
'retrieve',
|
|
33
|
-
'push',
|
|
34
|
-
'pull',
|
|
35
|
-
'sync',
|
|
36
|
-
'convert',
|
|
37
|
-
'validate',
|
|
38
|
-
'preview',
|
|
39
|
-
// Execution operations
|
|
40
|
-
'run',
|
|
41
|
-
'execute',
|
|
42
|
-
'test',
|
|
43
|
-
'start',
|
|
44
|
-
'stop',
|
|
45
|
-
'resume',
|
|
46
|
-
'cancel',
|
|
47
|
-
'quick',
|
|
48
|
-
// Information operations
|
|
49
|
-
'list',
|
|
50
|
-
'display',
|
|
51
|
-
'show',
|
|
52
|
-
'describe',
|
|
53
|
-
'query',
|
|
54
|
-
'search',
|
|
55
|
-
'audit',
|
|
56
|
-
'check',
|
|
57
|
-
// Management operations
|
|
58
|
-
'assign',
|
|
59
|
-
'open',
|
|
60
|
-
'login',
|
|
61
|
-
'logout',
|
|
62
|
-
'install',
|
|
63
|
-
'uninstall',
|
|
64
|
-
'enable',
|
|
65
|
-
'disable',
|
|
66
|
-
'publish',
|
|
67
|
-
'report',
|
|
68
|
-
// Data operations
|
|
69
|
-
'import',
|
|
70
|
-
'export',
|
|
71
|
-
'bulk',
|
|
72
|
-
'tree',
|
|
73
|
-
'results',
|
|
74
|
-
// Package operations
|
|
75
|
-
'promote',
|
|
76
|
-
'demote',
|
|
77
|
-
'version',
|
|
78
|
-
];
|
|
79
|
-
/**
|
|
80
|
-
* Domain words - nouns that represent Salesforce concepts and objects
|
|
81
|
-
* These should get medium weight (2x) in embedding text
|
|
82
|
-
*/
|
|
83
|
-
export const SF_DOMAIN_WORDS = [
|
|
84
|
-
// Core Salesforce concepts
|
|
85
|
-
'org',
|
|
86
|
-
'metadata',
|
|
87
|
-
'project',
|
|
88
|
-
'package',
|
|
89
|
-
'source',
|
|
90
|
-
'data',
|
|
91
|
-
'user',
|
|
92
|
-
'permission',
|
|
93
|
-
'permset',
|
|
94
|
-
'permsetlicense',
|
|
95
|
-
// Development concepts
|
|
96
|
-
'apex',
|
|
97
|
-
'flow',
|
|
98
|
-
'trigger',
|
|
99
|
-
'class',
|
|
100
|
-
'component',
|
|
101
|
-
'lwc',
|
|
102
|
-
'aura',
|
|
103
|
-
'lightning',
|
|
104
|
-
'visualforce',
|
|
105
|
-
'app',
|
|
106
|
-
'tab',
|
|
107
|
-
'field',
|
|
108
|
-
'object',
|
|
109
|
-
'record',
|
|
110
|
-
'sobject',
|
|
111
|
-
// Org types
|
|
112
|
-
'scratch',
|
|
113
|
-
'sandbox',
|
|
114
|
-
'production',
|
|
115
|
-
'devhub',
|
|
116
|
-
'shape',
|
|
117
|
-
'snapshot',
|
|
118
|
-
// Metadata types
|
|
119
|
-
'layout',
|
|
120
|
-
'workflow',
|
|
121
|
-
'validation',
|
|
122
|
-
'rule',
|
|
123
|
-
'profile',
|
|
124
|
-
'role',
|
|
125
|
-
'queue',
|
|
126
|
-
'group',
|
|
127
|
-
'territory',
|
|
128
|
-
'sharing',
|
|
129
|
-
// Testing & analysis
|
|
130
|
-
'test',
|
|
131
|
-
'coverage',
|
|
132
|
-
'log',
|
|
133
|
-
'debug',
|
|
134
|
-
'trace',
|
|
135
|
-
'analyzer',
|
|
136
|
-
'doctor',
|
|
137
|
-
// AI & Agents
|
|
138
|
-
'agent',
|
|
139
|
-
'bot',
|
|
140
|
-
'template',
|
|
141
|
-
'spec',
|
|
142
|
-
'topic',
|
|
143
|
-
'action',
|
|
144
|
-
'evaluation',
|
|
145
|
-
// Data management
|
|
146
|
-
'bulk',
|
|
147
|
-
'tree',
|
|
148
|
-
'query',
|
|
149
|
-
'soql',
|
|
150
|
-
'sosl',
|
|
151
|
-
'csv',
|
|
152
|
-
'json',
|
|
153
|
-
'xml',
|
|
154
|
-
// Package management
|
|
155
|
-
'managed',
|
|
156
|
-
'unlocked',
|
|
157
|
-
'unmanaged',
|
|
158
|
-
'installed',
|
|
159
|
-
'subscriber',
|
|
160
|
-
// Community & Experience
|
|
161
|
-
'community',
|
|
162
|
-
'experience',
|
|
163
|
-
'site',
|
|
164
|
-
'portal',
|
|
165
|
-
// Custom metadata
|
|
166
|
-
'cmdt',
|
|
167
|
-
'custom',
|
|
168
|
-
'standard',
|
|
169
|
-
// Development tools
|
|
170
|
-
'plugin',
|
|
171
|
-
'command',
|
|
172
|
-
'flag',
|
|
173
|
-
'config',
|
|
174
|
-
'alias',
|
|
175
|
-
'autocomplete',
|
|
176
|
-
'help',
|
|
177
|
-
'interactive',
|
|
178
|
-
// API & Integration
|
|
179
|
-
'api',
|
|
180
|
-
'rest',
|
|
181
|
-
'graphql',
|
|
182
|
-
'soap',
|
|
183
|
-
'request',
|
|
184
|
-
'response',
|
|
185
|
-
'endpoint',
|
|
186
|
-
// Analytics
|
|
187
|
-
'analytics',
|
|
188
|
-
'dashboard',
|
|
189
|
-
'report',
|
|
190
|
-
'dataset',
|
|
191
|
-
// DevOps & Pipeline
|
|
192
|
-
'pipeline',
|
|
193
|
-
'devops',
|
|
194
|
-
'branch',
|
|
195
|
-
'git',
|
|
196
|
-
'repository',
|
|
197
|
-
'manifest',
|
|
198
|
-
// File types
|
|
199
|
-
'file',
|
|
200
|
-
'directory',
|
|
201
|
-
'path',
|
|
202
|
-
'zip',
|
|
203
|
-
'archive',
|
|
204
|
-
];
|
|
205
|
-
/**
|
|
206
|
-
* Modifier words - adjectives and adverbs that modify actions
|
|
207
|
-
* These should get light weight (1.5x) in embedding text
|
|
208
|
-
*/
|
|
209
|
-
export const SF_MODIFIER_WORDS = [
|
|
210
|
-
// Action modifiers
|
|
211
|
-
'quick',
|
|
212
|
-
'async',
|
|
213
|
-
'sync',
|
|
214
|
-
'force',
|
|
215
|
-
'dry',
|
|
216
|
-
'preview',
|
|
217
|
-
'validate',
|
|
218
|
-
'check',
|
|
219
|
-
'watch',
|
|
220
|
-
'tail',
|
|
221
|
-
'poll',
|
|
222
|
-
// Scope modifiers
|
|
223
|
-
'all',
|
|
224
|
-
'local',
|
|
225
|
-
'remote',
|
|
226
|
-
'global',
|
|
227
|
-
'default',
|
|
228
|
-
'target',
|
|
229
|
-
'source',
|
|
230
|
-
'current',
|
|
231
|
-
'recent',
|
|
232
|
-
'latest',
|
|
233
|
-
'specific',
|
|
234
|
-
// State modifiers
|
|
235
|
-
'active',
|
|
236
|
-
'inactive',
|
|
237
|
-
'enabled',
|
|
238
|
-
'disabled',
|
|
239
|
-
'tracked',
|
|
240
|
-
'untracked',
|
|
241
|
-
'deployed',
|
|
242
|
-
'pending',
|
|
243
|
-
'failed',
|
|
244
|
-
'successful',
|
|
245
|
-
// Type modifiers
|
|
246
|
-
'managed',
|
|
247
|
-
'unmanaged',
|
|
248
|
-
'unlocked',
|
|
249
|
-
'locked',
|
|
250
|
-
'protected',
|
|
251
|
-
'public',
|
|
252
|
-
'private',
|
|
253
|
-
'shared',
|
|
254
|
-
// Format modifiers
|
|
255
|
-
'formatted',
|
|
256
|
-
'raw',
|
|
257
|
-
'pretty',
|
|
258
|
-
'compact',
|
|
259
|
-
'verbose',
|
|
260
|
-
'concise',
|
|
261
|
-
'detailed',
|
|
262
|
-
'summary',
|
|
263
|
-
];
|
|
264
|
-
/**
|
|
265
|
-
* Context words - structural terms that provide context
|
|
266
|
-
* These should get normal weight (1x) in embedding text
|
|
267
|
-
*/
|
|
268
|
-
export const SF_CONTEXT_WORDS = [
|
|
269
|
-
// Structural
|
|
270
|
-
'force',
|
|
271
|
-
'sf',
|
|
272
|
-
'sfdx',
|
|
273
|
-
'cli',
|
|
274
|
-
'salesforce',
|
|
275
|
-
// Directories
|
|
276
|
-
'app',
|
|
277
|
-
'main',
|
|
278
|
-
'default',
|
|
279
|
-
'classes',
|
|
280
|
-
'objects',
|
|
281
|
-
'layouts',
|
|
282
|
-
'tabs',
|
|
283
|
-
'flows',
|
|
284
|
-
'triggers',
|
|
285
|
-
'components',
|
|
286
|
-
'static',
|
|
287
|
-
'resources',
|
|
288
|
-
// File extensions (without dots)
|
|
289
|
-
'cls',
|
|
290
|
-
'trigger',
|
|
291
|
-
'page',
|
|
292
|
-
'component',
|
|
293
|
-
'app',
|
|
294
|
-
'evt',
|
|
295
|
-
'intf',
|
|
296
|
-
'cmp',
|
|
297
|
-
'design',
|
|
298
|
-
'svg',
|
|
299
|
-
'css',
|
|
300
|
-
'js',
|
|
301
|
-
'xml',
|
|
302
|
-
'meta',
|
|
303
|
-
// Common patterns
|
|
304
|
-
'scratch',
|
|
305
|
-
'def',
|
|
306
|
-
'definition',
|
|
307
|
-
'configuration',
|
|
308
|
-
'settings',
|
|
309
|
-
'preferences',
|
|
310
|
-
'options',
|
|
311
|
-
'parameters',
|
|
312
|
-
'arguments',
|
|
313
|
-
'flags',
|
|
314
|
-
'values',
|
|
315
|
-
];
|
|
316
|
-
/**
|
|
317
|
-
* Synonym mappings for expanding keywords with related terms
|
|
318
|
-
* This helps match user queries that use different but related terminology
|
|
319
|
-
*/
|
|
320
|
-
const SYNONYM_MAP = {
|
|
321
|
-
// Action synonyms
|
|
322
|
-
deploy: ['deployment', 'deploying', 'push', 'upload', 'send'],
|
|
323
|
-
retrieve: ['pull', 'download', 'fetch', 'get', 'sync'],
|
|
324
|
-
list: ['show', 'display', 'enumerate', 'ls', 'view'],
|
|
325
|
-
create: ['make', 'generate', 'new', 'add', 'build'],
|
|
326
|
-
delete: ['remove', 'destroy', 'rm', 'drop', 'erase'],
|
|
327
|
-
update: ['modify', 'change', 'edit', 'alter', 'refresh'],
|
|
328
|
-
run: ['execute', 'start', 'launch', 'invoke'],
|
|
329
|
-
query: ['search', 'find', 'select', 'lookup'],
|
|
330
|
-
open: ['launch', 'start', 'view', 'access'],
|
|
331
|
-
login: ['authenticate', 'auth', 'signin', 'connect'],
|
|
332
|
-
logout: ['disconnect', 'signout', 'unauthenticate'],
|
|
333
|
-
install: ['add', 'setup', 'configure'],
|
|
334
|
-
uninstall: ['remove', 'delete', 'unsetup'],
|
|
335
|
-
convert: ['transform', 'migrate', 'change', 'translate'],
|
|
336
|
-
validate: ['verify', 'check', 'confirm', 'test'],
|
|
337
|
-
preview: ['view', 'show', 'display', 'check'],
|
|
338
|
-
report: ['status', 'info', 'summary', 'details'],
|
|
339
|
-
resume: ['continue', 'restart', 'proceed'],
|
|
340
|
-
cancel: ['stop', 'abort', 'terminate', 'halt'],
|
|
341
|
-
// Domain synonyms
|
|
342
|
-
org: ['organization', 'environment', 'instance', 'tenant'],
|
|
343
|
-
metadata: ['meta', 'components', 'definitions', 'config'],
|
|
344
|
-
scratch: ['dev', 'development', 'temp', 'temporary', 'trial'],
|
|
345
|
-
sandbox: ['test', 'staging', 'non-prod', 'development'],
|
|
346
|
-
production: ['prod', 'live', 'main', 'release'],
|
|
347
|
-
apex: ['code', 'classes', 'triggers', 'programming'],
|
|
348
|
-
flow: ['workflow', 'process', 'automation'],
|
|
349
|
-
component: ['comp', 'lwc', 'aura', 'element'],
|
|
350
|
-
lightning: ['lwc', 'aura', 'web-component'],
|
|
351
|
-
object: ['sobject', 'entity', 'table', 'record-type'],
|
|
352
|
-
field: ['column', 'attribute', 'property'],
|
|
353
|
-
record: ['row', 'data', 'entry', 'item'],
|
|
354
|
-
user: ['person', 'account', 'profile', 'identity'],
|
|
355
|
-
permission: ['access', 'rights', 'privileges', 'security'],
|
|
356
|
-
permset: ['permission-set', 'permissions', 'access-set'],
|
|
357
|
-
package: ['app', 'bundle', 'module', 'extension'],
|
|
358
|
-
source: ['code', 'files', 'project-files'],
|
|
359
|
-
data: ['records', 'information', 'content'],
|
|
360
|
-
test: ['testing', 'tests', 'verification', 'validation'],
|
|
361
|
-
log: ['logs', 'debug', 'trace', 'output'],
|
|
362
|
-
agent: ['bot', 'assistant', 'ai', 'chatbot'],
|
|
363
|
-
template: ['spec', 'blueprint', 'pattern', 'example'],
|
|
364
|
-
manifest: ['package-xml', 'config', 'definition'],
|
|
365
|
-
bulk: ['batch', 'mass', 'multiple', 'many'],
|
|
366
|
-
api: ['service', 'endpoint', 'interface', 'web-service'],
|
|
367
|
-
devhub: ['dev-hub', 'development-hub', 'hub'],
|
|
368
|
-
// Format synonyms
|
|
369
|
-
json: ['javascript-object-notation', 'data'],
|
|
370
|
-
xml: ['markup', 'config', 'meta'],
|
|
371
|
-
csv: ['comma-separated', 'spreadsheet', 'data'],
|
|
372
|
-
// Common user terms
|
|
373
|
-
setup: ['configure', 'install', 'create', 'initialize'],
|
|
374
|
-
config: ['configuration', 'settings', 'preferences'],
|
|
375
|
-
info: ['information', 'details', 'data', 'summary'],
|
|
376
|
-
help: ['assistance', 'documentation', 'guide', 'support'],
|
|
377
|
-
};
|
|
378
|
-
/**
|
|
379
|
-
* Expand a list of keywords with their synonyms
|
|
380
|
-
*/
|
|
381
|
-
function expandWithSynonyms(keywords) {
|
|
382
|
-
const expanded = new Set(keywords);
|
|
383
|
-
keywords.forEach((keyword) => {
|
|
384
|
-
const synonyms = SYNONYM_MAP[keyword];
|
|
385
|
-
if (synonyms) {
|
|
386
|
-
synonyms.forEach((synonym) => expanded.add(synonym));
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
return Array.from(expanded);
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Extract keywords from a command string and categorize them
|
|
393
|
-
*/
|
|
394
|
-
function extractKeywords(command) {
|
|
395
|
-
const words = command
|
|
396
|
-
.toLowerCase()
|
|
397
|
-
.split(/[\s:-]+/)
|
|
398
|
-
.filter((word) => word.length > 0);
|
|
399
|
-
return {
|
|
400
|
-
actions: words.filter((word) => SF_ACTION_WORDS.includes(word)),
|
|
401
|
-
domains: words.filter((word) => SF_DOMAIN_WORDS.includes(word)),
|
|
402
|
-
modifiers: words.filter((word) => SF_MODIFIER_WORDS.includes(word)),
|
|
403
|
-
context: words.filter((word) => SF_CONTEXT_WORDS.includes(word)),
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Create weighted embedding text for a command
|
|
408
|
-
*/
|
|
409
|
-
export function createWeightedEmbeddingText({ command, summary, description, examples, }) {
|
|
410
|
-
const keywords = extractKeywords(command);
|
|
411
|
-
// Expand keywords with synonyms for better matching
|
|
412
|
-
const expandedActions = expandWithSynonyms(keywords.actions);
|
|
413
|
-
const expandedDomains = expandWithSynonyms(keywords.domains);
|
|
414
|
-
const expandedModifiers = expandWithSynonyms(keywords.modifiers);
|
|
415
|
-
// Build weighted sections with cleaner formatting
|
|
416
|
-
const weightedSections = [];
|
|
417
|
-
// Primary actions (3x weight) - space-separated repetitions
|
|
418
|
-
if (keywords.actions.length > 0) {
|
|
419
|
-
const actionWords = keywords.actions.join(' ');
|
|
420
|
-
weightedSections.push(`${actionWords} ${actionWords} ${actionWords}`);
|
|
421
|
-
// Add synonyms (2x weight)
|
|
422
|
-
const synonyms = expandedActions.filter((word) => !keywords.actions.includes(word));
|
|
423
|
-
if (synonyms.length > 0) {
|
|
424
|
-
const synonymText = synonyms.join(' ');
|
|
425
|
-
weightedSections.push(`${synonymText} ${synonymText}`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// Domain objects (2x weight) - space-separated repetitions
|
|
429
|
-
if (keywords.domains.length > 0) {
|
|
430
|
-
const domainWords = keywords.domains.join(' ');
|
|
431
|
-
weightedSections.push(`${domainWords} ${domainWords}`);
|
|
432
|
-
// Add synonyms (1x weight)
|
|
433
|
-
const synonyms = expandedDomains.filter((word) => !keywords.domains.includes(word));
|
|
434
|
-
if (synonyms.length > 0) {
|
|
435
|
-
weightedSections.push(synonyms.join(' '));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// Modifiers (1.5x weight - approximate with 1.5 repetitions)
|
|
439
|
-
if (keywords.modifiers.length > 0) {
|
|
440
|
-
const modifierWords = keywords.modifiers.join(' ');
|
|
441
|
-
weightedSections.push(`${modifierWords} ${modifierWords}`);
|
|
442
|
-
// Add modifier synonyms
|
|
443
|
-
const synonyms = expandedModifiers.filter((word) => !keywords.modifiers.includes(word));
|
|
444
|
-
if (synonyms.length > 0) {
|
|
445
|
-
weightedSections.push(synonyms.join(' '));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// Natural language expansion
|
|
449
|
-
if (keywords.actions.length > 0 && keywords.domains.length > 0) {
|
|
450
|
-
const primaryAction = keywords.actions[0];
|
|
451
|
-
const primaryDomain = keywords.domains[0];
|
|
452
|
-
// Core action-domain pairs
|
|
453
|
-
weightedSections.push(`${primaryAction} ${primaryDomain}`);
|
|
454
|
-
weightedSections.push(`how to ${primaryAction} ${primaryDomain}`);
|
|
455
|
-
// Add top synonym variations for better natural language matching
|
|
456
|
-
const actionSynonyms = SYNONYM_MAP[primaryAction];
|
|
457
|
-
const domainSynonyms = SYNONYM_MAP[primaryDomain];
|
|
458
|
-
if (actionSynonyms && actionSynonyms.length > 0) {
|
|
459
|
-
weightedSections.push(`${actionSynonyms[0]} ${primaryDomain}`);
|
|
460
|
-
}
|
|
461
|
-
if (domainSynonyms && domainSynonyms.length > 0) {
|
|
462
|
-
weightedSections.push(`${primaryAction} ${domainSynonyms[0]}`);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// Include natural content for semantic understanding
|
|
466
|
-
return `${weightedSections.join(' ')}\n\nCommand: ${command}\nSummary: ${summary}\n\nDescription: ${description}\n\nExamples:\n${examples?.join('\n') ?? 'No examples available'}`;
|
|
467
|
-
}
|
|
468
|
-
//# sourceMappingURL=create-embedding-text.js.map
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { McpTool, McpToolConfig, Toolset, Services, ReleaseState } from '@salesforce/mcp-provider-api';
|
|
3
|
-
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Suggest a Salesforce CLI (sf) command based on user input.
|
|
6
|
-
*/
|
|
7
|
-
declare const suggestCliCommandParamsSchema: z.ZodObject<{
|
|
8
|
-
query: z.ZodString;
|
|
9
|
-
}, "strip", z.ZodTypeAny, {
|
|
10
|
-
query: string;
|
|
11
|
-
}, {
|
|
12
|
-
query: string;
|
|
13
|
-
}>;
|
|
14
|
-
type InputArgs = z.infer<typeof suggestCliCommandParamsSchema>;
|
|
15
|
-
type InputArgsShape = typeof suggestCliCommandParamsSchema.shape;
|
|
16
|
-
type OutputArgsShape = z.ZodRawShape;
|
|
17
|
-
export declare class SuggestCliCommandMcpTool extends McpTool<InputArgsShape, OutputArgsShape> {
|
|
18
|
-
private readonly services;
|
|
19
|
-
constructor(services: Services);
|
|
20
|
-
getReleaseState(): ReleaseState;
|
|
21
|
-
getToolsets(): Toolset[];
|
|
22
|
-
getName(): string;
|
|
23
|
-
getConfig(): McpToolConfig<InputArgsShape, OutputArgsShape>;
|
|
24
|
-
exec(input: InputArgs): Promise<CallToolResult>;
|
|
25
|
-
}
|
|
26
|
-
export {};
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2025, Salesforce, Inc.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import { McpTool, Toolset, ReleaseState } from '@salesforce/mcp-provider-api';
|
|
18
|
-
import { getAssets } from '../utils/assets.js';
|
|
19
|
-
/**
|
|
20
|
-
* Suggest a Salesforce CLI (sf) command based on user input.
|
|
21
|
-
*/
|
|
22
|
-
const suggestCliCommandParamsSchema = z.object({
|
|
23
|
-
query: z.string().describe('The natural language query to suggest an `sf` command'),
|
|
24
|
-
});
|
|
25
|
-
export class SuggestCliCommandMcpTool extends McpTool {
|
|
26
|
-
services;
|
|
27
|
-
constructor(services) {
|
|
28
|
-
super();
|
|
29
|
-
this.services = services;
|
|
30
|
-
}
|
|
31
|
-
getReleaseState() {
|
|
32
|
-
return ReleaseState.GA;
|
|
33
|
-
}
|
|
34
|
-
getToolsets() {
|
|
35
|
-
return [Toolset.CORE];
|
|
36
|
-
}
|
|
37
|
-
getName() {
|
|
38
|
-
return 'sf-suggest-cli-command';
|
|
39
|
-
}
|
|
40
|
-
getConfig() {
|
|
41
|
-
return {
|
|
42
|
-
title: 'Suggest CLI Command',
|
|
43
|
-
description: `Suggests an \`sf\` CLI command based on a natural language query. It finds relevant commands from a local index and uses an LLM to construct the final, precise command to fulfill the user's request.
|
|
44
|
-
|
|
45
|
-
AGENT INSTRUCTIONS:
|
|
46
|
-
Use this tool whenever a user:
|
|
47
|
-
- asks for guidance on how to use the Salesforce CLI (sf or sfdx)
|
|
48
|
-
- needs help with Salesforce CLI (sf or sfdx) command syntax
|
|
49
|
-
- wants to know what Salesforce CLI (sf or sfdx) command to run for a specific task
|
|
50
|
-
- asks questions like 'how do I...', 'what command...', or 'how to...' related to Salesforce development operations.
|
|
51
|
-
NEVER use this tool for enabling Salesforce MCP tools (use sf-enable-tools instead).
|
|
52
|
-
NEVER use this tool for listing available Salesforce MCP tools (use sf-list-tools instead).
|
|
53
|
-
NEVER use this tool for understanding the Salesforce MCP server's capabilities.
|
|
54
|
-
NEVER use this tool for understanding the input schema of a Salesforce MCP tool.`,
|
|
55
|
-
inputSchema: suggestCliCommandParamsSchema.shape,
|
|
56
|
-
outputSchema: undefined,
|
|
57
|
-
annotations: {
|
|
58
|
-
readOnlyHint: true,
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
async exec(input) {
|
|
63
|
-
const assets = await getAssets(this.services.getConfigService().getDataDir(), 'sf-commands.json', 'commands-index.bin');
|
|
64
|
-
// Embed the user query
|
|
65
|
-
const queryEmbedding = await assets.embedder(input.query, {
|
|
66
|
-
pooling: 'mean',
|
|
67
|
-
normalize: true,
|
|
68
|
-
});
|
|
69
|
-
// Perform Semantic Search (FAISS)
|
|
70
|
-
const searchResults = assets.index.search(
|
|
71
|
-
// Convert the embedding tensor data to a flat array of numbers
|
|
72
|
-
Array.from(queryEmbedding.data), 5);
|
|
73
|
-
const topCandidateIds = searchResults.labels.slice(0, 5);
|
|
74
|
-
const contextCommands = topCandidateIds.map((id) => {
|
|
75
|
-
const command = assets.data.find((c) => c.id === id);
|
|
76
|
-
// Remove the embedding text to avoid sending it to the LLM
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
78
|
-
const { embeddingText, ...commandWithoutEmbeddingText } = command;
|
|
79
|
-
return commandWithoutEmbeddingText;
|
|
80
|
-
});
|
|
81
|
-
contextCommands.forEach((command, index) => {
|
|
82
|
-
// eslint-disable-next-line no-console
|
|
83
|
-
console.error(`Command: ${command.command}, Score: ${searchResults.distances[index]}`);
|
|
84
|
-
});
|
|
85
|
-
const prompt = `System: You are a precise expert on the Salesforce CLI (sf). Your sole purpose is to construct a single, valid sf command based on the user's request and the Command Reference provided.
|
|
86
|
-
- Base your answer STRICTLY on the user's request and the Command Reference.
|
|
87
|
-
- If there is no command that matches the user's request, tell the user that you cannot find a command.
|
|
88
|
-
- Do not use any flags or commands not listed in the reference.
|
|
89
|
-
|
|
90
|
-
User Request:
|
|
91
|
-
"${input.query}"
|
|
92
|
-
|
|
93
|
-
Command Reference:
|
|
94
|
-
${JSON.stringify(contextCommands, null, 2)}
|
|
95
|
-
|
|
96
|
-
Notes about Flag Properties:
|
|
97
|
-
- multiple: Flags that support multiple values should be specified with the '--flag value1 --flag value2' syntax.
|
|
98
|
-
- dependsOn: If a flag depends on another flag, ensure that the dependent flag is included in the command.
|
|
99
|
-
- atLeastOne: If a flag requires at least one of a set of flags, ensure that at least one of those flags is included in the command.
|
|
100
|
-
- exactlyOne: If a flag requires exactly one of a set of flags, ensure that exactly one of those flags is included in the command.
|
|
101
|
-
- exclusive: If a flag is exclusive with another flag, ensure that only one of those flags is included in the command.
|
|
102
|
-
- required: If a flag is required, ensure that it is included in the command unless it has a default value.
|
|
103
|
-
- relationships: If a flag has relationships with other flags, ensure that those relationships are respected in the command.
|
|
104
|
-
- options: If a flag has options, ensure that one of those options is used
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Synthesize the single "sf" command that best fulfills the user's request.
|
|
108
|
-
`;
|
|
109
|
-
return {
|
|
110
|
-
isError: false,
|
|
111
|
-
content: [
|
|
112
|
-
{
|
|
113
|
-
type: 'text',
|
|
114
|
-
text: prompt,
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
//# sourceMappingURL=sf-suggest-cli-command.js.map
|
package/lib/utils/assets.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { FeatureExtractionPipeline } from '@huggingface/transformers';
|
|
2
|
-
import faiss from 'faiss-node';
|
|
3
|
-
type Assets<T> = {
|
|
4
|
-
data: T;
|
|
5
|
-
embedder: FeatureExtractionPipeline;
|
|
6
|
-
index: faiss.IndexFlatL2;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Conditionally builds or rebuilds a FAISS index based on its existence and age.
|
|
10
|
-
*
|
|
11
|
-
* This function checks if a FAISS index file exists in the specified output directory.
|
|
12
|
-
* If the index exists but is older than one week, it triggers a rebuild. If the index
|
|
13
|
-
* doesn't exist, it initiates the initial build process. The build process can run as a
|
|
14
|
-
* detached child process or in the same process depending on the detached parameter.
|
|
15
|
-
*
|
|
16
|
-
* @param indexPath - The path to the FAISS index file.
|
|
17
|
-
* @param detached - Whether to run the build process detached (default: true)
|
|
18
|
-
*/
|
|
19
|
-
export declare function maybeBuildIndex(indexPath: string, detached?: boolean): Promise<void>;
|
|
20
|
-
export declare function getAssets<T>(dataDir: string, dataPath: string, indexPath: string): Promise<Assets<T>>;
|
|
21
|
-
export {};
|
package/lib/utils/assets.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2025, Salesforce, Inc.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
import fs from 'node:fs';
|
|
17
|
-
import { resolve, join } from 'node:path';
|
|
18
|
-
import { spawn } from 'node:child_process';
|
|
19
|
-
import faiss from 'faiss-node';
|
|
20
|
-
import { pipeline } from '@huggingface/transformers';
|
|
21
|
-
import { ux } from '@oclif/core';
|
|
22
|
-
/**
|
|
23
|
-
* Conditionally builds or rebuilds a FAISS index based on its existence and age.
|
|
24
|
-
*
|
|
25
|
-
* This function checks if a FAISS index file exists in the specified output directory.
|
|
26
|
-
* If the index exists but is older than one week, it triggers a rebuild. If the index
|
|
27
|
-
* doesn't exist, it initiates the initial build process. The build process can run as a
|
|
28
|
-
* detached child process or in the same process depending on the detached parameter.
|
|
29
|
-
*
|
|
30
|
-
* @param indexPath - The path to the FAISS index file.
|
|
31
|
-
* @param detached - Whether to run the build process detached (default: true)
|
|
32
|
-
*/
|
|
33
|
-
export async function maybeBuildIndex(indexPath, detached = true) {
|
|
34
|
-
try {
|
|
35
|
-
const stats = fs.statSync(indexPath);
|
|
36
|
-
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
37
|
-
ux.stderr(`Checking FAISS index in ${indexPath}...`);
|
|
38
|
-
ux.stderr(`Last modified: ${stats.mtime.toString()}`);
|
|
39
|
-
if (stats.mtime < oneWeekAgo) {
|
|
40
|
-
ux.stderr(`FAISS index is more than 1 week old - rebuilding in ${indexPath}...`);
|
|
41
|
-
await spawnBuildScript(indexPath, detached);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
ux.stderr(`FAISS index is up to date in ${indexPath}. No rebuild needed.`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
// File doesn't exist, so build the index
|
|
49
|
-
ux.stderr(`Building FAISS index in ${indexPath}...`);
|
|
50
|
-
await spawnBuildScript(indexPath, detached);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function spawnBuildScript(outputDir, detached) {
|
|
54
|
-
const scriptPath = resolve(import.meta.dirname, '..', 'scripts', 'build-index.js');
|
|
55
|
-
const args = [scriptPath, outputDir];
|
|
56
|
-
if (detached) {
|
|
57
|
-
spawn('node', args, {
|
|
58
|
-
detached: true,
|
|
59
|
-
stdio: 'ignore',
|
|
60
|
-
}).unref();
|
|
61
|
-
return Promise.resolve();
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
return new Promise((res, reject) => {
|
|
65
|
-
const childProcess = spawn('node', args, {
|
|
66
|
-
stdio: 'inherit',
|
|
67
|
-
});
|
|
68
|
-
childProcess.on('close', (code) => {
|
|
69
|
-
if (code === 0) {
|
|
70
|
-
res();
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
reject(new Error(`Build script exited with code ${code ?? 'UNKNOWN'}`));
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
childProcess.on('error', (error) => {
|
|
77
|
-
reject(error);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
export async function getAssets(dataDir, dataPath, indexPath) {
|
|
83
|
-
const fullDataPath = join(dataDir, dataPath);
|
|
84
|
-
const fullIndexPath = join(dataDir, indexPath);
|
|
85
|
-
// Ensure the index is built or rebuilt if necessary
|
|
86
|
-
await maybeBuildIndex(fullIndexPath, false);
|
|
87
|
-
try {
|
|
88
|
-
await fs.promises.access(fullDataPath);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
throw new Error(`Commands file not found at ${fullDataPath}. Please run maybeBuildIndex to build the index.`);
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
await fs.promises.access(fullIndexPath);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
throw new Error(`FAISS index not found at ${fullIndexPath}. Please run maybeBuildIndex to build the index.`);
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
const dataRaw = await fs.promises.readFile(fullDataPath, 'utf-8');
|
|
101
|
-
const data = JSON.parse(dataRaw);
|
|
102
|
-
const index = faiss.IndexFlatL2.read(fullIndexPath);
|
|
103
|
-
const embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
|
|
104
|
-
dtype: 'fp32',
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
data,
|
|
108
|
-
index,
|
|
109
|
-
embedder,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
throw new Error(`Failed to load assets: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=assets.js.map
|