opencode-toolbox 0.5.0 → 0.6.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/README.md +37 -5
- package/dist/index.js +499 -159
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Create `~/.config/opencode/toolbox.jsonc`:
|
|
|
63
63
|
|
|
64
64
|
## Usage
|
|
65
65
|
|
|
66
|
-
The plugin exposes
|
|
66
|
+
The plugin exposes five tools:
|
|
67
67
|
|
|
68
68
|
### toolbox_search_bm25
|
|
69
69
|
|
|
@@ -207,17 +207,49 @@ Returns a comprehensive status object:
|
|
|
207
207
|
|
|
208
208
|
### /toolbox-status Slash Command
|
|
209
209
|
|
|
210
|
-
The plugin automatically creates
|
|
210
|
+
The plugin automatically creates a `/toolbox-status` slash command on first launch:
|
|
211
211
|
|
|
212
212
|
```
|
|
213
213
|
~/.config/opencode/command/toolbox-status.md
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
- **Auto-created** on first plugin launch
|
|
217
|
-
- **Auto-updated** when plugin version changes (tracked via `toolbox_version` in frontmatter)
|
|
218
|
-
|
|
219
216
|
Use it in OpenCode by typing `/toolbox-status` to get a formatted status report.
|
|
220
217
|
|
|
218
|
+
### toolbox_perf
|
|
219
|
+
|
|
220
|
+
Get detailed performance metrics for the toolbox plugin:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
toolbox_perf({})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Returns performance data including initialization times, search latencies, and execution stats:
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"init": {
|
|
231
|
+
"duration": 1234.56,
|
|
232
|
+
"serverCount": 6,
|
|
233
|
+
"toolCount": 42
|
|
234
|
+
},
|
|
235
|
+
"timers": {
|
|
236
|
+
"search.bm25": { "count": 15, "total": 45.2, "avg": 3.01, "min": 1.2, "max": 8.5 },
|
|
237
|
+
"search.regex": { "count": 5, "total": 12.1, "avg": 2.42, "min": 1.1, "max": 4.2 },
|
|
238
|
+
"tool.execute": { "count": 10, "total": 892.3, "avg": 89.23, "min": 12.5, "max": 245.8 }
|
|
239
|
+
},
|
|
240
|
+
"indexStats": {
|
|
241
|
+
"documentCount": 42,
|
|
242
|
+
"avgDocLength": 15.3
|
|
243
|
+
},
|
|
244
|
+
"config": {
|
|
245
|
+
"initMode": "eager",
|
|
246
|
+
"connectionTimeout": 5000,
|
|
247
|
+
"requestTimeout": 30000,
|
|
248
|
+
"retryAttempts": 2
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
221
253
|
## Search Modes
|
|
222
254
|
|
|
223
255
|
### BM25 (Natural Language)
|
package/dist/index.js
CHANGED
|
@@ -19267,7 +19267,7 @@ function tool(input) {
|
|
|
19267
19267
|
}
|
|
19268
19268
|
tool.schema = exports_external;
|
|
19269
19269
|
// src/plugin.ts
|
|
19270
|
-
import { appendFile, mkdir, writeFile,
|
|
19270
|
+
import { appendFile, mkdir, writeFile, access } from "fs/promises";
|
|
19271
19271
|
|
|
19272
19272
|
// node_modules/zod/v4/classic/external.js
|
|
19273
19273
|
var exports_external2 = {};
|
|
@@ -32816,8 +32816,16 @@ var ServerConfigSchema = exports_external2.discriminatedUnion("type", [
|
|
|
32816
32816
|
LocalServerConfigSchema,
|
|
32817
32817
|
RemoteServerConfigSchema
|
|
32818
32818
|
]);
|
|
32819
|
+
var ConnectionConfigSchema = exports_external2.object({
|
|
32820
|
+
connectTimeout: exports_external2.number().min(100).max(60000).default(5000),
|
|
32821
|
+
requestTimeout: exports_external2.number().min(100).max(300000).default(30000),
|
|
32822
|
+
retryAttempts: exports_external2.number().min(0).max(10).default(2),
|
|
32823
|
+
retryDelay: exports_external2.number().min(0).max(30000).default(1000)
|
|
32824
|
+
});
|
|
32819
32825
|
var SettingsConfigSchema = exports_external2.object({
|
|
32820
|
-
defaultLimit: exports_external2.number().min(1).max(20).default(5)
|
|
32826
|
+
defaultLimit: exports_external2.number().min(1).max(20).default(5),
|
|
32827
|
+
initMode: exports_external2.enum(["eager", "lazy"]).default("eager"),
|
|
32828
|
+
connection: ConnectionConfigSchema.optional()
|
|
32821
32829
|
});
|
|
32822
32830
|
var ConfigSchema = exports_external2.object({
|
|
32823
32831
|
mcp: exports_external2.record(exports_external2.string(), ServerConfigSchema),
|
|
@@ -37627,6 +37635,9 @@ class RemoteMCPClient {
|
|
|
37627
37635
|
return this.toolsCache;
|
|
37628
37636
|
}
|
|
37629
37637
|
}
|
|
37638
|
+
// src/mcp-client/manager.ts
|
|
37639
|
+
import { EventEmitter } from "events";
|
|
37640
|
+
|
|
37630
37641
|
// src/catalog/catalog.ts
|
|
37631
37642
|
function normalizeTool(serverName, tool3) {
|
|
37632
37643
|
const id = {
|
|
@@ -37674,15 +37685,178 @@ function buildSearchableText(serverName, tool3, args) {
|
|
|
37674
37685
|
function normalizeTools(serverName, tools) {
|
|
37675
37686
|
return tools.map((tool3) => normalizeTool(serverName, tool3));
|
|
37676
37687
|
}
|
|
37688
|
+
// src/profiler/profiler.ts
|
|
37689
|
+
function percentile(sorted, p) {
|
|
37690
|
+
if (sorted.length === 0)
|
|
37691
|
+
return 0;
|
|
37692
|
+
const index = Math.ceil(p / 100 * sorted.length) - 1;
|
|
37693
|
+
return sorted[Math.max(0, index)];
|
|
37694
|
+
}
|
|
37695
|
+
function calculateStats(measurements) {
|
|
37696
|
+
if (measurements.length === 0)
|
|
37697
|
+
return null;
|
|
37698
|
+
const sorted = [...measurements].sort((a, b) => a - b);
|
|
37699
|
+
const total = sorted.reduce((sum, v) => sum + v, 0);
|
|
37700
|
+
return {
|
|
37701
|
+
count: sorted.length,
|
|
37702
|
+
min: sorted[0],
|
|
37703
|
+
max: sorted[sorted.length - 1],
|
|
37704
|
+
avg: total / sorted.length,
|
|
37705
|
+
p50: percentile(sorted, 50),
|
|
37706
|
+
p95: percentile(sorted, 95),
|
|
37707
|
+
p99: percentile(sorted, 99),
|
|
37708
|
+
total
|
|
37709
|
+
};
|
|
37710
|
+
}
|
|
37711
|
+
|
|
37712
|
+
class Profiler {
|
|
37713
|
+
marks = new Map;
|
|
37714
|
+
measures = new Map;
|
|
37715
|
+
serverMetrics = new Map;
|
|
37716
|
+
initStartTime = null;
|
|
37717
|
+
initEndTime = null;
|
|
37718
|
+
initState = "idle";
|
|
37719
|
+
indexBuildTime = null;
|
|
37720
|
+
toolCount = 0;
|
|
37721
|
+
incrementalUpdates = 0;
|
|
37722
|
+
startTime = performance.now();
|
|
37723
|
+
mark(name) {
|
|
37724
|
+
this.marks.set(name, performance.now());
|
|
37725
|
+
}
|
|
37726
|
+
measure(name, startMark) {
|
|
37727
|
+
const start = this.marks.get(startMark);
|
|
37728
|
+
if (start === undefined) {
|
|
37729
|
+
return -1;
|
|
37730
|
+
}
|
|
37731
|
+
const duration5 = performance.now() - start;
|
|
37732
|
+
const existing = this.measures.get(name) || [];
|
|
37733
|
+
existing.push(duration5);
|
|
37734
|
+
this.measures.set(name, existing);
|
|
37735
|
+
return duration5;
|
|
37736
|
+
}
|
|
37737
|
+
record(name, duration5) {
|
|
37738
|
+
const existing = this.measures.get(name) || [];
|
|
37739
|
+
existing.push(duration5);
|
|
37740
|
+
this.measures.set(name, existing);
|
|
37741
|
+
}
|
|
37742
|
+
getStats(name) {
|
|
37743
|
+
const measurements = this.measures.get(name);
|
|
37744
|
+
if (!measurements)
|
|
37745
|
+
return null;
|
|
37746
|
+
return calculateStats(measurements);
|
|
37747
|
+
}
|
|
37748
|
+
initStart() {
|
|
37749
|
+
this.initStartTime = performance.now();
|
|
37750
|
+
this.initState = "initializing";
|
|
37751
|
+
}
|
|
37752
|
+
initComplete(state) {
|
|
37753
|
+
this.initEndTime = performance.now();
|
|
37754
|
+
this.initState = state;
|
|
37755
|
+
}
|
|
37756
|
+
recordServerConnect(name, connectTime, toolCount, status, error92) {
|
|
37757
|
+
this.serverMetrics.set(name, {
|
|
37758
|
+
name,
|
|
37759
|
+
connectTime,
|
|
37760
|
+
toolCount,
|
|
37761
|
+
status,
|
|
37762
|
+
error: error92
|
|
37763
|
+
});
|
|
37764
|
+
}
|
|
37765
|
+
recordIndexBuild(duration5, toolCount) {
|
|
37766
|
+
this.indexBuildTime = duration5;
|
|
37767
|
+
this.toolCount = toolCount;
|
|
37768
|
+
}
|
|
37769
|
+
recordIncrementalUpdate(toolCount) {
|
|
37770
|
+
this.incrementalUpdates++;
|
|
37771
|
+
this.toolCount += toolCount;
|
|
37772
|
+
}
|
|
37773
|
+
getInitState() {
|
|
37774
|
+
return this.initState;
|
|
37775
|
+
}
|
|
37776
|
+
getInitDuration() {
|
|
37777
|
+
if (this.initStartTime === null)
|
|
37778
|
+
return null;
|
|
37779
|
+
if (this.initEndTime !== null) {
|
|
37780
|
+
return this.initEndTime - this.initStartTime;
|
|
37781
|
+
}
|
|
37782
|
+
return performance.now() - this.initStartTime;
|
|
37783
|
+
}
|
|
37784
|
+
export() {
|
|
37785
|
+
return {
|
|
37786
|
+
timestamp: new Date().toISOString(),
|
|
37787
|
+
uptime: performance.now() - this.startTime,
|
|
37788
|
+
initialization: {
|
|
37789
|
+
startTime: this.initStartTime || 0,
|
|
37790
|
+
endTime: this.initEndTime,
|
|
37791
|
+
duration: this.getInitDuration(),
|
|
37792
|
+
state: this.initState,
|
|
37793
|
+
servers: Array.from(this.serverMetrics.values())
|
|
37794
|
+
},
|
|
37795
|
+
indexing: {
|
|
37796
|
+
buildTime: this.indexBuildTime,
|
|
37797
|
+
toolCount: this.toolCount,
|
|
37798
|
+
incrementalUpdates: this.incrementalUpdates
|
|
37799
|
+
},
|
|
37800
|
+
searches: {
|
|
37801
|
+
bm25: this.getStats("search.bm25"),
|
|
37802
|
+
regex: this.getStats("search.regex")
|
|
37803
|
+
},
|
|
37804
|
+
executions: this.getStats("tool.execute")
|
|
37805
|
+
};
|
|
37806
|
+
}
|
|
37807
|
+
reset() {
|
|
37808
|
+
this.marks.clear();
|
|
37809
|
+
this.measures.clear();
|
|
37810
|
+
this.serverMetrics.clear();
|
|
37811
|
+
this.initStartTime = null;
|
|
37812
|
+
this.initEndTime = null;
|
|
37813
|
+
this.initState = "idle";
|
|
37814
|
+
this.indexBuildTime = null;
|
|
37815
|
+
this.toolCount = 0;
|
|
37816
|
+
this.incrementalUpdates = 0;
|
|
37817
|
+
}
|
|
37818
|
+
startTimer(name) {
|
|
37819
|
+
const start = performance.now();
|
|
37820
|
+
return () => {
|
|
37821
|
+
const duration5 = performance.now() - start;
|
|
37822
|
+
this.record(name, duration5);
|
|
37823
|
+
return duration5;
|
|
37824
|
+
};
|
|
37825
|
+
}
|
|
37826
|
+
}
|
|
37827
|
+
var globalProfiler = new Profiler;
|
|
37677
37828
|
// src/mcp-client/manager.ts
|
|
37678
|
-
|
|
37829
|
+
var DEFAULT_CONNECTION_CONFIG = {
|
|
37830
|
+
connectTimeout: 5000,
|
|
37831
|
+
requestTimeout: 30000,
|
|
37832
|
+
retryAttempts: 2,
|
|
37833
|
+
retryDelay: 1000
|
|
37834
|
+
};
|
|
37835
|
+
function sleep(ms) {
|
|
37836
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37837
|
+
}
|
|
37838
|
+
function withTimeout(promise4, ms, errorMessage) {
|
|
37839
|
+
return Promise.race([
|
|
37840
|
+
promise4,
|
|
37841
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(errorMessage)), ms))
|
|
37842
|
+
]);
|
|
37843
|
+
}
|
|
37844
|
+
|
|
37845
|
+
class MCPManager extends EventEmitter {
|
|
37679
37846
|
servers;
|
|
37680
37847
|
clients;
|
|
37681
37848
|
clientFactory;
|
|
37849
|
+
connectionConfig;
|
|
37850
|
+
initState = "idle";
|
|
37851
|
+
initPromise = null;
|
|
37852
|
+
serversPending = 0;
|
|
37853
|
+
serversCompleted = 0;
|
|
37682
37854
|
constructor(options) {
|
|
37855
|
+
super();
|
|
37683
37856
|
this.servers = new Map;
|
|
37684
37857
|
this.clients = new Map;
|
|
37685
37858
|
this.clientFactory = options?.clientFactory || this.defaultClientFactory.bind(this);
|
|
37859
|
+
this.connectionConfig = { ...DEFAULT_CONNECTION_CONFIG, ...options?.connectionConfig };
|
|
37686
37860
|
}
|
|
37687
37861
|
defaultClientFactory(name, config3) {
|
|
37688
37862
|
if (config3.type === "local") {
|
|
@@ -37694,39 +37868,134 @@ class MCPManager {
|
|
|
37694
37868
|
}
|
|
37695
37869
|
}
|
|
37696
37870
|
async initialize(servers) {
|
|
37871
|
+
if (this.initState !== "idle") {
|
|
37872
|
+
return this.initPromise || Promise.resolve();
|
|
37873
|
+
}
|
|
37874
|
+
globalProfiler.initStart();
|
|
37875
|
+
this.initState = "initializing";
|
|
37876
|
+
this.serversPending = Object.keys(servers).length;
|
|
37877
|
+
this.serversCompleted = 0;
|
|
37697
37878
|
const promises = Object.entries(servers).map(async ([name, config3]) => {
|
|
37698
|
-
return this.
|
|
37879
|
+
return this.connectServerWithRetry(name, config3);
|
|
37699
37880
|
});
|
|
37700
|
-
|
|
37881
|
+
this.initPromise = Promise.all(promises).then(() => {
|
|
37882
|
+
this.finalizeInit();
|
|
37883
|
+
});
|
|
37884
|
+
return this.initPromise;
|
|
37885
|
+
}
|
|
37886
|
+
initializeBackground(servers) {
|
|
37887
|
+
this.initialize(servers).catch(() => {});
|
|
37888
|
+
}
|
|
37889
|
+
finalizeInit() {
|
|
37890
|
+
const allServers = Array.from(this.servers.values());
|
|
37891
|
+
const connected = allServers.filter((s) => s.status === "connected");
|
|
37892
|
+
const failed = allServers.filter((s) => s.status === "error");
|
|
37893
|
+
if (connected.length === allServers.length) {
|
|
37894
|
+
this.initState = "ready";
|
|
37895
|
+
} else if (connected.length > 0) {
|
|
37896
|
+
this.initState = "degraded";
|
|
37897
|
+
} else {
|
|
37898
|
+
this.initState = "degraded";
|
|
37899
|
+
}
|
|
37900
|
+
globalProfiler.initComplete(this.initState);
|
|
37901
|
+
this.emit("init:complete", this.initState);
|
|
37902
|
+
}
|
|
37903
|
+
async connectServerWithRetry(name, config3) {
|
|
37904
|
+
const maxAttempts = this.connectionConfig.retryAttempts + 1;
|
|
37905
|
+
let lastError = null;
|
|
37906
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
37907
|
+
try {
|
|
37908
|
+
await this.connectServer(name, config3);
|
|
37909
|
+
return;
|
|
37910
|
+
} catch (error92) {
|
|
37911
|
+
lastError = error92 instanceof Error ? error92 : new Error(String(error92));
|
|
37912
|
+
if (attempt < maxAttempts) {
|
|
37913
|
+
await sleep(this.connectionConfig.retryDelay);
|
|
37914
|
+
}
|
|
37915
|
+
}
|
|
37916
|
+
}
|
|
37917
|
+
this.servers.set(name, {
|
|
37918
|
+
name,
|
|
37919
|
+
config: config3,
|
|
37920
|
+
tools: [],
|
|
37921
|
+
status: "error",
|
|
37922
|
+
error: lastError?.message || "Connection failed after retries"
|
|
37923
|
+
});
|
|
37924
|
+
globalProfiler.recordServerConnect(name, -1, 0, "error", lastError?.message);
|
|
37925
|
+
this.emit("server:error", name, lastError?.message || "Unknown error");
|
|
37926
|
+
this.checkPartialReady();
|
|
37701
37927
|
}
|
|
37702
37928
|
async connectServer(name, config3) {
|
|
37929
|
+
const startTime = performance.now();
|
|
37703
37930
|
this.servers.set(name, {
|
|
37704
37931
|
name,
|
|
37705
37932
|
config: config3,
|
|
37706
37933
|
tools: [],
|
|
37707
37934
|
status: "connecting"
|
|
37708
37935
|
});
|
|
37709
|
-
|
|
37710
|
-
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37715
|
-
|
|
37716
|
-
|
|
37717
|
-
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
|
|
37723
|
-
|
|
37724
|
-
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
|
|
37728
|
-
|
|
37936
|
+
const client = this.clientFactory(name, config3);
|
|
37937
|
+
await withTimeout(client.connect(), this.connectionConfig.connectTimeout, `Connection to ${name} timed out after ${this.connectionConfig.connectTimeout}ms`);
|
|
37938
|
+
const tools = await withTimeout(client.listTools(), this.connectionConfig.requestTimeout, `Listing tools from ${name} timed out after ${this.connectionConfig.requestTimeout}ms`);
|
|
37939
|
+
const catalogTools = normalizeTools(name, tools);
|
|
37940
|
+
const connectTime = performance.now() - startTime;
|
|
37941
|
+
this.servers.set(name, {
|
|
37942
|
+
name,
|
|
37943
|
+
config: config3,
|
|
37944
|
+
tools: catalogTools,
|
|
37945
|
+
status: "connected"
|
|
37946
|
+
});
|
|
37947
|
+
this.clients.set(name, client);
|
|
37948
|
+
globalProfiler.recordServerConnect(name, connectTime, catalogTools.length, "connected");
|
|
37949
|
+
this.emit("server:connected", name, catalogTools);
|
|
37950
|
+
this.checkPartialReady();
|
|
37951
|
+
}
|
|
37952
|
+
checkPartialReady() {
|
|
37953
|
+
this.serversCompleted++;
|
|
37954
|
+
if (this.initState === "initializing") {
|
|
37955
|
+
const connected = Array.from(this.servers.values()).filter((s) => s.status === "connected");
|
|
37956
|
+
if (connected.length === 1 && this.serversCompleted < this.serversPending) {
|
|
37957
|
+
this.initState = "partial";
|
|
37958
|
+
this.emit("init:partial");
|
|
37959
|
+
}
|
|
37960
|
+
}
|
|
37961
|
+
}
|
|
37962
|
+
isReady() {
|
|
37963
|
+
return this.initState === "ready" || this.initState === "partial" || this.initState === "degraded";
|
|
37964
|
+
}
|
|
37965
|
+
isComplete() {
|
|
37966
|
+
return this.initState === "ready" || this.initState === "degraded";
|
|
37967
|
+
}
|
|
37968
|
+
getInitState() {
|
|
37969
|
+
return this.initState;
|
|
37970
|
+
}
|
|
37971
|
+
async waitForReady() {
|
|
37972
|
+
if (this.isComplete()) {
|
|
37973
|
+
return this.initState;
|
|
37729
37974
|
}
|
|
37975
|
+
if (this.initPromise) {
|
|
37976
|
+
await this.initPromise;
|
|
37977
|
+
return this.initState;
|
|
37978
|
+
}
|
|
37979
|
+
return this.initState;
|
|
37980
|
+
}
|
|
37981
|
+
waitForPartial() {
|
|
37982
|
+
if (this.isReady()) {
|
|
37983
|
+
return Promise.resolve();
|
|
37984
|
+
}
|
|
37985
|
+
return new Promise((resolve) => {
|
|
37986
|
+
const onPartial = () => {
|
|
37987
|
+
this.off("init:partial", onPartial);
|
|
37988
|
+
this.off("init:complete", onComplete);
|
|
37989
|
+
resolve();
|
|
37990
|
+
};
|
|
37991
|
+
const onComplete = () => {
|
|
37992
|
+
this.off("init:partial", onPartial);
|
|
37993
|
+
this.off("init:complete", onComplete);
|
|
37994
|
+
resolve();
|
|
37995
|
+
};
|
|
37996
|
+
this.on("init:partial", onPartial);
|
|
37997
|
+
this.on("init:complete", onComplete);
|
|
37998
|
+
});
|
|
37730
37999
|
}
|
|
37731
38000
|
getAllTools() {
|
|
37732
38001
|
const allTools = [];
|
|
@@ -37751,7 +38020,7 @@ class MCPManager {
|
|
|
37751
38020
|
if (!client) {
|
|
37752
38021
|
throw new Error(`MCP client not found for server: ${serverName}`);
|
|
37753
38022
|
}
|
|
37754
|
-
return client.callTool(toolName, args);
|
|
38023
|
+
return withTimeout(client.callTool(toolName, args), this.connectionConfig.requestTimeout, `Tool execution timed out after ${this.connectionConfig.requestTimeout}ms`);
|
|
37755
38024
|
}
|
|
37756
38025
|
getServer(name) {
|
|
37757
38026
|
return this.servers.get(name);
|
|
@@ -37764,12 +38033,17 @@ class MCPManager {
|
|
|
37764
38033
|
await Promise.all(promises);
|
|
37765
38034
|
this.servers.clear();
|
|
37766
38035
|
this.clients.clear();
|
|
38036
|
+
this.initState = "idle";
|
|
38037
|
+
this.initPromise = null;
|
|
37767
38038
|
}
|
|
37768
38039
|
}
|
|
37769
38040
|
// src/search/bm25.ts
|
|
37770
38041
|
function tokenize(text) {
|
|
37771
38042
|
return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 0);
|
|
37772
38043
|
}
|
|
38044
|
+
function yieldToEventLoop() {
|
|
38045
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
38046
|
+
}
|
|
37773
38047
|
|
|
37774
38048
|
class BM25Index {
|
|
37775
38049
|
documents;
|
|
@@ -37777,6 +38051,7 @@ class BM25Index {
|
|
|
37777
38051
|
docLengths;
|
|
37778
38052
|
avgDocLength = 0;
|
|
37779
38053
|
totalDocs = 0;
|
|
38054
|
+
totalTokens = 0;
|
|
37780
38055
|
k1 = 1.2;
|
|
37781
38056
|
b = 0.75;
|
|
37782
38057
|
constructor() {
|
|
@@ -37785,20 +38060,82 @@ class BM25Index {
|
|
|
37785
38060
|
this.docLengths = new Map;
|
|
37786
38061
|
}
|
|
37787
38062
|
indexTools(tools) {
|
|
37788
|
-
this.
|
|
37789
|
-
this.
|
|
37790
|
-
|
|
38063
|
+
this.clear();
|
|
38064
|
+
this.addToolsBatch(tools);
|
|
38065
|
+
}
|
|
38066
|
+
async indexToolsAsync(tools, chunkSize = 50) {
|
|
38067
|
+
this.clear();
|
|
38068
|
+
await this.addToolsAsync(tools, chunkSize);
|
|
38069
|
+
}
|
|
38070
|
+
addToolsBatch(tools) {
|
|
37791
38071
|
for (const tool3 of tools) {
|
|
37792
|
-
|
|
37793
|
-
|
|
37794
|
-
|
|
37795
|
-
|
|
37796
|
-
|
|
37797
|
-
|
|
38072
|
+
this.addToolInternal(tool3);
|
|
38073
|
+
}
|
|
38074
|
+
this.recalculateAvgDocLength();
|
|
38075
|
+
}
|
|
38076
|
+
async addToolsAsync(tools, chunkSize = 50) {
|
|
38077
|
+
for (let i = 0;i < tools.length; i += chunkSize) {
|
|
38078
|
+
const chunk = tools.slice(i, i + chunkSize);
|
|
38079
|
+
for (const tool3 of chunk) {
|
|
38080
|
+
this.addToolInternal(tool3);
|
|
38081
|
+
}
|
|
38082
|
+
if (i + chunkSize < tools.length) {
|
|
38083
|
+
await yieldToEventLoop();
|
|
37798
38084
|
}
|
|
37799
38085
|
}
|
|
37800
|
-
this.
|
|
37801
|
-
|
|
38086
|
+
this.recalculateAvgDocLength();
|
|
38087
|
+
}
|
|
38088
|
+
addTool(tool3) {
|
|
38089
|
+
this.addToolInternal(tool3);
|
|
38090
|
+
this.recalculateAvgDocLengthIncremental();
|
|
38091
|
+
}
|
|
38092
|
+
addToolInternal(tool3) {
|
|
38093
|
+
if (this.documents.has(tool3.idString)) {
|
|
38094
|
+
return;
|
|
38095
|
+
}
|
|
38096
|
+
const tokens = tokenize(tool3.searchableText);
|
|
38097
|
+
this.documents.set(tool3.idString, { tokens, tool: tool3 });
|
|
38098
|
+
this.docLengths.set(tool3.idString, tokens.length);
|
|
38099
|
+
this.totalTokens += tokens.length;
|
|
38100
|
+
this.totalDocs++;
|
|
38101
|
+
const uniqueTokens = new Set(tokens);
|
|
38102
|
+
for (const token of uniqueTokens) {
|
|
38103
|
+
this.docFreqs.set(token, (this.docFreqs.get(token) || 0) + 1);
|
|
38104
|
+
}
|
|
38105
|
+
}
|
|
38106
|
+
removeTool(idString) {
|
|
38107
|
+
const doc3 = this.documents.get(idString);
|
|
38108
|
+
if (!doc3)
|
|
38109
|
+
return false;
|
|
38110
|
+
const uniqueTokens = new Set(doc3.tokens);
|
|
38111
|
+
for (const token of uniqueTokens) {
|
|
38112
|
+
const freq = this.docFreqs.get(token) || 0;
|
|
38113
|
+
if (freq <= 1) {
|
|
38114
|
+
this.docFreqs.delete(token);
|
|
38115
|
+
} else {
|
|
38116
|
+
this.docFreqs.set(token, freq - 1);
|
|
38117
|
+
}
|
|
38118
|
+
}
|
|
38119
|
+
this.totalTokens -= doc3.tokens.length;
|
|
38120
|
+
this.documents.delete(idString);
|
|
38121
|
+
this.docLengths.delete(idString);
|
|
38122
|
+
this.totalDocs--;
|
|
38123
|
+
this.recalculateAvgDocLengthIncremental();
|
|
38124
|
+
return true;
|
|
38125
|
+
}
|
|
38126
|
+
recalculateAvgDocLength() {
|
|
38127
|
+
if (this.totalDocs === 0) {
|
|
38128
|
+
this.avgDocLength = 0;
|
|
38129
|
+
return;
|
|
38130
|
+
}
|
|
38131
|
+
this.avgDocLength = this.totalTokens / this.totalDocs;
|
|
38132
|
+
}
|
|
38133
|
+
recalculateAvgDocLengthIncremental() {
|
|
38134
|
+
if (this.totalDocs === 0) {
|
|
38135
|
+
this.avgDocLength = 0;
|
|
38136
|
+
return;
|
|
38137
|
+
}
|
|
38138
|
+
this.avgDocLength = this.totalTokens / this.totalDocs;
|
|
37802
38139
|
}
|
|
37803
38140
|
search(query, limit = 5) {
|
|
37804
38141
|
const queryTokens = tokenize(query);
|
|
@@ -37854,10 +38191,21 @@ class BM25Index {
|
|
|
37854
38191
|
this.docLengths.clear();
|
|
37855
38192
|
this.avgDocLength = 0;
|
|
37856
38193
|
this.totalDocs = 0;
|
|
38194
|
+
this.totalTokens = 0;
|
|
37857
38195
|
}
|
|
37858
38196
|
get size() {
|
|
37859
38197
|
return this.totalDocs;
|
|
37860
38198
|
}
|
|
38199
|
+
has(idString) {
|
|
38200
|
+
return this.documents.has(idString);
|
|
38201
|
+
}
|
|
38202
|
+
getStats() {
|
|
38203
|
+
return {
|
|
38204
|
+
docCount: this.totalDocs,
|
|
38205
|
+
termCount: this.docFreqs.size,
|
|
38206
|
+
avgDocLength: this.avgDocLength
|
|
38207
|
+
};
|
|
38208
|
+
}
|
|
37861
38209
|
}
|
|
37862
38210
|
// src/search/regex.ts
|
|
37863
38211
|
var MAX_REGEX_LENGTH = 200;
|
|
@@ -37911,69 +38259,18 @@ function generateSignature(tool3) {
|
|
|
37911
38259
|
}).join(", ");
|
|
37912
38260
|
return `${tool3.id.name}(${argList})`;
|
|
37913
38261
|
}
|
|
37914
|
-
// package.json
|
|
37915
|
-
var package_default = {
|
|
37916
|
-
name: "opencode-toolbox",
|
|
37917
|
-
version: "0.5.0",
|
|
37918
|
-
description: "Tool Search Tool Plugin for OpenCode - search and execute tools from MCP servers on-demand",
|
|
37919
|
-
main: "dist/index.js",
|
|
37920
|
-
module: "dist/index.js",
|
|
37921
|
-
type: "module",
|
|
37922
|
-
exports: {
|
|
37923
|
-
".": {
|
|
37924
|
-
import: "./dist/index.js",
|
|
37925
|
-
types: "./dist/index.d.ts"
|
|
37926
|
-
}
|
|
37927
|
-
},
|
|
37928
|
-
scripts: {
|
|
37929
|
-
build: "bun build src/index.ts --outdir dist --target bun",
|
|
37930
|
-
dev: "bun --hot src/index.ts",
|
|
37931
|
-
test: "bun test",
|
|
37932
|
-
"test:coverage": "bun test --coverage",
|
|
37933
|
-
typecheck: "tsc --noEmit"
|
|
37934
|
-
},
|
|
37935
|
-
files: [
|
|
37936
|
-
"dist",
|
|
37937
|
-
"README.md"
|
|
37938
|
-
],
|
|
37939
|
-
keywords: [
|
|
37940
|
-
"opencode",
|
|
37941
|
-
"plugin",
|
|
37942
|
-
"tool-search",
|
|
37943
|
-
"mcp"
|
|
37944
|
-
],
|
|
37945
|
-
license: "MIT",
|
|
37946
|
-
devDependencies: {
|
|
37947
|
-
"@types/bun": "latest",
|
|
37948
|
-
typescript: "^5.9.3"
|
|
37949
|
-
},
|
|
37950
|
-
peerDependencies: {
|
|
37951
|
-
typescript: "^5"
|
|
37952
|
-
},
|
|
37953
|
-
dependencies: {
|
|
37954
|
-
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
37955
|
-
"@opencode-ai/plugin": "latest",
|
|
37956
|
-
"jsonc-parser": "^3.3.1",
|
|
37957
|
-
zod: "^4.3.5"
|
|
37958
|
-
}
|
|
37959
|
-
};
|
|
37960
|
-
|
|
37961
38262
|
// src/plugin.ts
|
|
37962
|
-
var PLUGIN_VERSION = package_default.version;
|
|
37963
38263
|
var DEFAULT_CONFIG_PATH = `${process.env.HOME}/.config/opencode/toolbox.jsonc`;
|
|
37964
38264
|
var LOG_FILE_PATH = `${process.env.HOME}/.local/share/opencode/toolbox.log`;
|
|
37965
38265
|
var LOG_DIR = `${process.env.HOME}/.local/share/opencode`;
|
|
37966
38266
|
var COMMAND_DIR = `${process.env.HOME}/.config/opencode/command`;
|
|
37967
38267
|
var COMMAND_FILE_PATH = `${COMMAND_DIR}/toolbox-status.md`;
|
|
37968
|
-
|
|
37969
|
-
return `---
|
|
38268
|
+
var COMMAND_CONTENT = `---
|
|
37970
38269
|
description: Check toolbox plugin status and server health
|
|
37971
|
-
toolbox_version: ${PLUGIN_VERSION}
|
|
37972
38270
|
---
|
|
37973
|
-
Run toolbox_status({}) and show me the results in a readable format.
|
|
38271
|
+
Run toolbox_status({}) tool and show me the results in a readable format.
|
|
37974
38272
|
Highlight any failed servers or issues.
|
|
37975
38273
|
`;
|
|
37976
|
-
}
|
|
37977
38274
|
function parseToolName(fullName) {
|
|
37978
38275
|
const underscoreIndex = fullName.indexOf("_");
|
|
37979
38276
|
if (underscoreIndex === -1) {
|
|
@@ -38015,7 +38312,13 @@ Pass arguments as JSON string matching the tool's schema.`;
|
|
|
38015
38312
|
var STATUS_DESC = `Get toolbox status including plugin initialization, MCP server connections, and tool counts.
|
|
38016
38313
|
|
|
38017
38314
|
Shows success/total metrics to highlight failures. Use to check if toolbox is working correctly.`;
|
|
38315
|
+
var PERF_DESC = `Get detailed performance metrics for the toolbox plugin.
|
|
38316
|
+
|
|
38317
|
+
Shows initialization times, search latencies, execution stats, and per-server metrics.`;
|
|
38318
|
+
var isTestEnv = !!process.env.BUN_TEST;
|
|
38018
38319
|
function log(level, message, extra) {
|
|
38320
|
+
if (isTestEnv)
|
|
38321
|
+
return;
|
|
38019
38322
|
const timestamp = new Date().toISOString();
|
|
38020
38323
|
const extraStr = extra ? ` ${JSON.stringify(extra)}` : "";
|
|
38021
38324
|
const line = `${timestamp} [${level.toUpperCase()}] ${message}${extraStr}
|
|
@@ -38023,27 +38326,20 @@ function log(level, message, extra) {
|
|
|
38023
38326
|
mkdir(LOG_DIR, { recursive: true }).then(() => appendFile(LOG_FILE_PATH, line)).catch(() => {});
|
|
38024
38327
|
}
|
|
38025
38328
|
function ensureCommandFile() {
|
|
38026
|
-
|
|
38027
|
-
|
|
38028
|
-
|
|
38029
|
-
|
|
38030
|
-
if (existingVersion !== PLUGIN_VERSION) {
|
|
38031
|
-
return writeFile(COMMAND_FILE_PATH, content).then(() => {
|
|
38032
|
-
log("info", `Updated /toolbox-status command (${existingVersion} -> ${PLUGIN_VERSION})`);
|
|
38033
|
-
});
|
|
38034
|
-
}
|
|
38035
|
-
}).catch(() => {
|
|
38036
|
-
mkdir(COMMAND_DIR, { recursive: true }).then(() => writeFile(COMMAND_FILE_PATH, content)).then(() => log("info", `Created /toolbox-status command (v${PLUGIN_VERSION})`)).catch(() => {});
|
|
38329
|
+
if (isTestEnv)
|
|
38330
|
+
return;
|
|
38331
|
+
access(COMMAND_FILE_PATH).catch(() => {
|
|
38332
|
+
mkdir(COMMAND_DIR, { recursive: true }).then(() => writeFile(COMMAND_FILE_PATH, COMMAND_CONTENT)).then(() => log("info", "Created /toolbox-status command file")).catch(() => {});
|
|
38037
38333
|
});
|
|
38038
38334
|
}
|
|
38039
38335
|
var SYSTEM_PROMPT_BASE = `# Extended Toolbox
|
|
38040
38336
|
|
|
38041
38337
|
You have access to an extended toolbox with additional capabilities (web search, time utilities, code search, etc.).
|
|
38042
38338
|
|
|
38043
|
-
##
|
|
38044
|
-
ALWAYS
|
|
38045
|
-
|
|
38046
|
-
|
|
38339
|
+
## Rules
|
|
38340
|
+
1. ALWAYS toolbox_search_* before saying "I cannot do that" or "I don't have access to."
|
|
38341
|
+
2. ALWAYS toolbox_search_* if you think that user wants you to use some tools
|
|
38342
|
+
3. ALWAYS toolbox_search_* if you think that user may refer specific tool name which is not exist in the context
|
|
38047
38343
|
|
|
38048
38344
|
## Workflow
|
|
38049
38345
|
1. Search: toolbox_search_bm25({ text: "what you need" }) or toolbox_search_regex({ pattern: "prefix_.*" })
|
|
@@ -38064,13 +38360,12 @@ function generateSystemPrompt(mcpManager) {
|
|
|
38064
38360
|
}
|
|
38065
38361
|
return `${SYSTEM_PROMPT_BASE}
|
|
38066
38362
|
|
|
38067
|
-
##
|
|
38068
|
-
|
|
38069
|
-
|
|
38070
|
-
${JSON.stringify(toolboxSchema, null, 2)}
|
|
38071
|
-
\`\`\``;
|
|
38363
|
+
## Registered MCP Servers
|
|
38364
|
+
${Object.entries(toolboxSchema).map(([server, tools]) => `- ${server}: ${tools.map((t) => t.split("_").slice(1).join("_")).join(", ")}`).join(`
|
|
38365
|
+
`)}`;
|
|
38072
38366
|
}
|
|
38073
38367
|
var ToolboxPlugin = async (ctx) => {
|
|
38368
|
+
const pluginLoadStart = performance.now();
|
|
38074
38369
|
const { client } = ctx;
|
|
38075
38370
|
const configPath = process.env.OPENCODE_TOOLBOX_CONFIG || DEFAULT_CONFIG_PATH;
|
|
38076
38371
|
const configResult = await loadConfig(configPath);
|
|
@@ -38080,53 +38375,64 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38080
38375
|
return {};
|
|
38081
38376
|
}
|
|
38082
38377
|
const config3 = configResult.data;
|
|
38083
|
-
const
|
|
38378
|
+
const initMode = config3.settings?.initMode || "eager";
|
|
38379
|
+
const connectionConfig = {
|
|
38380
|
+
connectTimeout: config3.settings?.connection?.connectTimeout || 5000,
|
|
38381
|
+
requestTimeout: config3.settings?.connection?.requestTimeout || 30000,
|
|
38382
|
+
retryAttempts: config3.settings?.connection?.retryAttempts || 2,
|
|
38383
|
+
retryDelay: config3.settings?.connection?.retryDelay || 1000
|
|
38384
|
+
};
|
|
38385
|
+
const mcpManager = new MCPManager({ connectionConfig });
|
|
38084
38386
|
const bm25Index = new BM25Index;
|
|
38085
|
-
let initialized = false;
|
|
38086
38387
|
let searchCount = 0;
|
|
38087
38388
|
let executionCount = 0;
|
|
38088
38389
|
let executionSuccessCount = 0;
|
|
38089
38390
|
ensureCommandFile();
|
|
38090
38391
|
const serverNames = Object.keys(config3.mcp);
|
|
38392
|
+
const pluginLoadDuration = performance.now() - pluginLoadStart;
|
|
38091
38393
|
log("info", `Toolbox plugin loaded successfully`, {
|
|
38092
38394
|
configPath,
|
|
38395
|
+
logPath: LOG_FILE_PATH,
|
|
38093
38396
|
serverCount: serverNames.length,
|
|
38094
|
-
servers: serverNames
|
|
38397
|
+
servers: serverNames,
|
|
38398
|
+
initMode,
|
|
38399
|
+
loadDurationMs: Math.round(pluginLoadDuration * 100) / 100
|
|
38400
|
+
});
|
|
38401
|
+
mcpManager.on("server:connected", (serverName, tools) => {
|
|
38402
|
+
const startTime = performance.now();
|
|
38403
|
+
bm25Index.addToolsBatch(tools);
|
|
38404
|
+
const indexTime = performance.now() - startTime;
|
|
38405
|
+
globalProfiler.recordIncrementalUpdate(tools.length);
|
|
38406
|
+
log("info", `Server ${serverName} connected, indexed ${tools.length} tools in ${indexTime.toFixed(2)}ms`);
|
|
38407
|
+
});
|
|
38408
|
+
mcpManager.on("server:error", (serverName, error92) => {
|
|
38409
|
+
log("warn", `Server ${serverName} failed: ${error92}`);
|
|
38410
|
+
});
|
|
38411
|
+
mcpManager.on("init:complete", (state) => {
|
|
38412
|
+
const duration5 = globalProfiler.getInitDuration();
|
|
38413
|
+
const servers = mcpManager.getAllServers();
|
|
38414
|
+
const connectedServers = servers.filter((s) => s.status === "connected");
|
|
38415
|
+
log("info", `Initialization complete in ${duration5?.toFixed(2)}ms: ${connectedServers.length}/${servers.length} servers, ${bm25Index.size} tools indexed`, {
|
|
38416
|
+
state,
|
|
38417
|
+
totalServers: servers.length,
|
|
38418
|
+
connectedServers: connectedServers.length,
|
|
38419
|
+
totalTools: bm25Index.size
|
|
38420
|
+
});
|
|
38095
38421
|
});
|
|
38422
|
+
if (initMode === "eager") {
|
|
38423
|
+
mcpManager.initializeBackground(config3.mcp);
|
|
38424
|
+
log("info", "Started eager background initialization");
|
|
38425
|
+
}
|
|
38096
38426
|
async function ensureInitialized() {
|
|
38097
|
-
if (
|
|
38427
|
+
if (mcpManager.isReady()) {
|
|
38098
38428
|
return;
|
|
38099
|
-
|
|
38100
|
-
|
|
38429
|
+
}
|
|
38430
|
+
if (initMode === "lazy" && mcpManager.getInitState() === "idle") {
|
|
38431
|
+
log("info", "Starting lazy initialization on first use");
|
|
38101
38432
|
await mcpManager.initialize(config3.mcp);
|
|
38102
|
-
|
|
38103
|
-
bm25Index.indexTools(allTools);
|
|
38104
|
-
initialized = true;
|
|
38105
|
-
const servers = mcpManager.getAllServers();
|
|
38106
|
-
const connectedServers = servers.filter((s) => s.status === "connected");
|
|
38107
|
-
const failedServers = servers.filter((s) => s.status === "error");
|
|
38108
|
-
const initMsg = `Initialization complete: ${connectedServers.length}/${servers.length} servers connected, ${allTools.length} tools indexed`;
|
|
38109
|
-
log("info", initMsg, {
|
|
38110
|
-
totalServers: servers.length,
|
|
38111
|
-
connectedServers: connectedServers.length,
|
|
38112
|
-
failedServers: failedServers.length,
|
|
38113
|
-
totalTools: allTools.length,
|
|
38114
|
-
servers: servers.map((s) => ({
|
|
38115
|
-
name: s.name,
|
|
38116
|
-
status: s.status,
|
|
38117
|
-
toolCount: s.tools.length,
|
|
38118
|
-
error: s.error || null
|
|
38119
|
-
}))
|
|
38120
|
-
});
|
|
38121
|
-
if (failedServers.length > 0) {
|
|
38122
|
-
const warnMsg = `${failedServers.length} server(s) failed to connect: ${failedServers.map((s) => s.name).join(", ")}`;
|
|
38123
|
-
log("warn", warnMsg);
|
|
38124
|
-
}
|
|
38125
|
-
} catch (error92) {
|
|
38126
|
-
const errorMsg = `Failed to initialize MCP servers: ${error92 instanceof Error ? error92.message : String(error92)}`;
|
|
38127
|
-
log("error", errorMsg);
|
|
38128
|
-
throw error92;
|
|
38433
|
+
return;
|
|
38129
38434
|
}
|
|
38435
|
+
await mcpManager.waitForPartial();
|
|
38130
38436
|
}
|
|
38131
38437
|
return {
|
|
38132
38438
|
tool: {
|
|
@@ -38137,9 +38443,11 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38137
38443
|
limit: tool.schema.number().optional().describe("Maximum number of results to return (default: 5)")
|
|
38138
38444
|
},
|
|
38139
38445
|
async execute(args) {
|
|
38446
|
+
const timer = globalProfiler.startTimer("search.bm25");
|
|
38140
38447
|
try {
|
|
38141
38448
|
await ensureInitialized();
|
|
38142
38449
|
} catch (error92) {
|
|
38450
|
+
timer();
|
|
38143
38451
|
return JSON.stringify({
|
|
38144
38452
|
success: false,
|
|
38145
38453
|
error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
|
|
@@ -38149,11 +38457,13 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38149
38457
|
const searchLimit = args.limit || config3.settings?.defaultLimit || 5;
|
|
38150
38458
|
const allTools = mcpManager.getAllCatalogTools();
|
|
38151
38459
|
const results = bm25Index.search(args.text, searchLimit);
|
|
38152
|
-
|
|
38460
|
+
const duration5 = timer();
|
|
38461
|
+
log("info", `BM25 search completed: "${args.text}" -> ${results.length} results in ${duration5.toFixed(2)}ms`, {
|
|
38153
38462
|
searchType: "bm25",
|
|
38154
38463
|
query: args.text,
|
|
38155
38464
|
resultsCount: results.length,
|
|
38156
|
-
limit: searchLimit
|
|
38465
|
+
limit: searchLimit,
|
|
38466
|
+
durationMs: duration5
|
|
38157
38467
|
});
|
|
38158
38468
|
return formatSearchResults(results, allTools);
|
|
38159
38469
|
}
|
|
@@ -38165,9 +38475,11 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38165
38475
|
limit: tool.schema.number().optional().describe("Maximum number of results to return (default: 5)")
|
|
38166
38476
|
},
|
|
38167
38477
|
async execute(args) {
|
|
38478
|
+
const timer = globalProfiler.startTimer("search.regex");
|
|
38168
38479
|
try {
|
|
38169
38480
|
await ensureInitialized();
|
|
38170
38481
|
} catch (error92) {
|
|
38482
|
+
timer();
|
|
38171
38483
|
return JSON.stringify({
|
|
38172
38484
|
success: false,
|
|
38173
38485
|
error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
|
|
@@ -38177,6 +38489,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38177
38489
|
const searchLimit = args.limit || config3.settings?.defaultLimit || 5;
|
|
38178
38490
|
const allTools = mcpManager.getAllCatalogTools();
|
|
38179
38491
|
const result = searchWithRegex(allTools, args.pattern, searchLimit);
|
|
38492
|
+
const duration5 = timer();
|
|
38180
38493
|
if ("error" in result) {
|
|
38181
38494
|
log("warn", `Regex search failed: "${args.pattern}" -> ${result.error}`);
|
|
38182
38495
|
return JSON.stringify({
|
|
@@ -38184,11 +38497,12 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38184
38497
|
error: result.error
|
|
38185
38498
|
});
|
|
38186
38499
|
}
|
|
38187
|
-
log("info", `Regex search completed: "${args.pattern}" -> ${result.length} results`, {
|
|
38500
|
+
log("info", `Regex search completed: "${args.pattern}" -> ${result.length} results in ${duration5.toFixed(2)}ms`, {
|
|
38188
38501
|
searchType: "regex",
|
|
38189
38502
|
pattern: args.pattern,
|
|
38190
38503
|
resultsCount: result.length,
|
|
38191
|
-
limit: searchLimit
|
|
38504
|
+
limit: searchLimit,
|
|
38505
|
+
durationMs: duration5
|
|
38192
38506
|
});
|
|
38193
38507
|
return formatSearchResults(result, allTools);
|
|
38194
38508
|
}
|
|
@@ -38200,9 +38514,11 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38200
38514
|
arguments: tool.schema.string().optional().describe("JSON-encoded arguments for the tool, matching its schema. Use '{}' or omit for tools with no required arguments.")
|
|
38201
38515
|
},
|
|
38202
38516
|
async execute(args) {
|
|
38517
|
+
const timer = globalProfiler.startTimer("tool.execute");
|
|
38203
38518
|
try {
|
|
38204
38519
|
await ensureInitialized();
|
|
38205
38520
|
} catch (error92) {
|
|
38521
|
+
timer();
|
|
38206
38522
|
return JSON.stringify({
|
|
38207
38523
|
success: false,
|
|
38208
38524
|
error: `Failed to initialize: ${error92 instanceof Error ? error92.message : String(error92)}`
|
|
@@ -38210,6 +38526,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38210
38526
|
}
|
|
38211
38527
|
const parsed = parseToolName(args.name);
|
|
38212
38528
|
if (!parsed) {
|
|
38529
|
+
timer();
|
|
38213
38530
|
log("warn", `Invalid tool name format: ${args.name}`, {
|
|
38214
38531
|
toolName: args.name
|
|
38215
38532
|
});
|
|
@@ -38223,6 +38540,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38223
38540
|
try {
|
|
38224
38541
|
toolArgs = JSON.parse(args.arguments);
|
|
38225
38542
|
} catch (error92) {
|
|
38543
|
+
timer();
|
|
38226
38544
|
log("warn", `Failed to parse arguments as JSON for ${args.name}`, {
|
|
38227
38545
|
toolName: args.name,
|
|
38228
38546
|
arguments: args.arguments
|
|
@@ -38235,11 +38553,10 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38235
38553
|
}
|
|
38236
38554
|
executionCount++;
|
|
38237
38555
|
try {
|
|
38238
|
-
const startTime = Date.now();
|
|
38239
38556
|
const result = await mcpManager.callTool(parsed.serverName, parsed.toolName, toolArgs);
|
|
38240
|
-
const duration5 =
|
|
38557
|
+
const duration5 = timer();
|
|
38241
38558
|
executionSuccessCount++;
|
|
38242
|
-
log("info", `Tool executed successfully: ${args.name}`, {
|
|
38559
|
+
log("info", `Tool executed successfully: ${args.name} in ${duration5.toFixed(2)}ms`, {
|
|
38243
38560
|
server: parsed.serverName,
|
|
38244
38561
|
tool: parsed.toolName,
|
|
38245
38562
|
durationMs: duration5
|
|
@@ -38249,11 +38566,13 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38249
38566
|
result
|
|
38250
38567
|
});
|
|
38251
38568
|
} catch (error92) {
|
|
38569
|
+
const duration5 = timer();
|
|
38252
38570
|
const errorMsg = `Tool execution failed: ${error92 instanceof Error ? error92.message : String(error92)}`;
|
|
38253
38571
|
log("error", errorMsg, {
|
|
38254
38572
|
server: parsed.serverName,
|
|
38255
38573
|
tool: parsed.toolName,
|
|
38256
|
-
error: errorMsg
|
|
38574
|
+
error: errorMsg,
|
|
38575
|
+
durationMs: duration5
|
|
38257
38576
|
});
|
|
38258
38577
|
return JSON.stringify({
|
|
38259
38578
|
success: false,
|
|
@@ -38266,7 +38585,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38266
38585
|
description: STATUS_DESC,
|
|
38267
38586
|
args: {},
|
|
38268
38587
|
async execute() {
|
|
38269
|
-
if (!
|
|
38588
|
+
if (!mcpManager.isReady()) {
|
|
38270
38589
|
try {
|
|
38271
38590
|
await ensureInitialized();
|
|
38272
38591
|
} catch (error92) {
|
|
@@ -38282,9 +38601,13 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38282
38601
|
const failedServers = servers.filter((s) => s.status === "error");
|
|
38283
38602
|
const connectingServers = servers.filter((s) => s.status === "connecting");
|
|
38284
38603
|
const totalTools = mcpManager.getAllCatalogTools().length;
|
|
38604
|
+
const initDuration = globalProfiler.getInitDuration();
|
|
38285
38605
|
const status = {
|
|
38286
38606
|
plugin: {
|
|
38287
|
-
initialized:
|
|
38607
|
+
initialized: mcpManager.isComplete(),
|
|
38608
|
+
initState: mcpManager.getInitState(),
|
|
38609
|
+
initMode,
|
|
38610
|
+
initDurationMs: initDuration ? Math.round(initDuration) : null,
|
|
38288
38611
|
configPath,
|
|
38289
38612
|
uptime: process.uptime(),
|
|
38290
38613
|
searches: searchCount,
|
|
@@ -38308,7 +38631,7 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38308
38631
|
},
|
|
38309
38632
|
tools: {
|
|
38310
38633
|
total: totalTools,
|
|
38311
|
-
|
|
38634
|
+
indexed: bm25Index.size,
|
|
38312
38635
|
serversWithTools: servers.filter((s) => s.tools.length > 0).length
|
|
38313
38636
|
},
|
|
38314
38637
|
health: {
|
|
@@ -38323,15 +38646,32 @@ var ToolboxPlugin = async (ctx) => {
|
|
|
38323
38646
|
});
|
|
38324
38647
|
return JSON.stringify(status, null, 2);
|
|
38325
38648
|
}
|
|
38649
|
+
}),
|
|
38650
|
+
toolbox_perf: tool({
|
|
38651
|
+
description: PERF_DESC,
|
|
38652
|
+
args: {},
|
|
38653
|
+
async execute() {
|
|
38654
|
+
const report = globalProfiler.export();
|
|
38655
|
+
return JSON.stringify({
|
|
38656
|
+
...report,
|
|
38657
|
+
indexStats: bm25Index.getStats(),
|
|
38658
|
+
config: {
|
|
38659
|
+
initMode,
|
|
38660
|
+
connectionTimeout: connectionConfig.connectTimeout,
|
|
38661
|
+
requestTimeout: connectionConfig.requestTimeout,
|
|
38662
|
+
retryAttempts: connectionConfig.retryAttempts
|
|
38663
|
+
}
|
|
38664
|
+
}, null, 2);
|
|
38665
|
+
}
|
|
38326
38666
|
})
|
|
38327
38667
|
},
|
|
38328
38668
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
38329
|
-
|
|
38330
|
-
|
|
38331
|
-
|
|
38332
|
-
|
|
38333
|
-
output.system.push(SYSTEM_PROMPT_BASE);
|
|
38669
|
+
if (!mcpManager.isReady() && initMode === "eager") {
|
|
38670
|
+
try {
|
|
38671
|
+
await mcpManager.waitForPartial();
|
|
38672
|
+
} catch {}
|
|
38334
38673
|
}
|
|
38674
|
+
output.system.push(generateSystemPrompt(mcpManager));
|
|
38335
38675
|
}
|
|
38336
38676
|
};
|
|
38337
38677
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-toolbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Tool Search Tool Plugin for OpenCode - search and execute tools from MCP servers on-demand",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"dev": "bun --hot src/index.ts",
|
|
17
17
|
"test": "bun test",
|
|
18
18
|
"test:coverage": "bun test --coverage",
|
|
19
|
-
"typecheck": "tsc --noEmit"
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"bench": "bun run bench:search && bun run bench:init && bun run bench:concurrent",
|
|
21
|
+
"bench:search": "bun run bench/search.bench.ts",
|
|
22
|
+
"bench:init": "bun run bench/init.bench.ts",
|
|
23
|
+
"bench:concurrent": "bun run bench/concurrent.bench.ts"
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
22
26
|
"dist",
|