@fastino-ai/pioneer-cli 0.2.2 → 0.2.3
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/.claude/settings.local.json +7 -1
- package/.cursor/rules/api-documentation.mdc +14 -0
- package/.cursor/rules/backend-location-rule.mdc +5 -0
- package/Medical_NER_Dataset_1.jsonl +50 -0
- package/README.md +4 -1
- package/bun.lock +52 -0
- package/package.json +5 -2
- package/src/api.ts +551 -22
- package/src/chat/ChatApp.tsx +548 -263
- package/src/client/ToolExecutor.ts +175 -0
- package/src/client/WebSocketClient.ts +333 -0
- package/src/client/index.ts +2 -0
- package/src/config.ts +49 -139
- package/src/index.tsx +796 -106
- package/src/telemetry.ts +173 -0
- package/src/tests/config.test.ts +19 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/filesystem.ts +1 -1
- package/src/tools/index.ts +2 -9
- package/src/tools/sandbox.ts +1 -1
- package/src/tools/types.ts +25 -0
- package/src/utils/index.ts +6 -0
- package/fastino-ai-pioneer-cli-0.2.0.tgz +0 -0
- package/ner_dataset.json +0 -111
- package/src/agent/Agent.ts +0 -342
- package/src/agent/BudgetManager.ts +0 -167
- package/src/agent/LLMClient.ts +0 -435
- package/src/agent/ToolRegistry.ts +0 -97
- package/src/agent/index.ts +0 -15
- package/src/agent/types.ts +0 -84
- package/src/evolution/EvalRunner.ts +0 -301
- package/src/evolution/EvolutionEngine.ts +0 -319
- package/src/evolution/FeedbackCollector.ts +0 -197
- package/src/evolution/ModelTrainer.ts +0 -371
- package/src/evolution/index.ts +0 -18
- package/src/evolution/types.ts +0 -110
- package/src/tools/modal.ts +0 -269
- package/src/tools/training.ts +0 -443
- package/src/tools/wandb.ts +0 -348
- /package/src/{agent → utils}/FileResolver.ts +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExecutor - Executes CLI tools locally
|
|
3
|
+
*
|
|
4
|
+
* When the MLE Agent calls a CLI tool (bash, read_file, etc.), this executor
|
|
5
|
+
* runs it on the user's machine and returns the result.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createBashTool } from "../tools/bash.js";
|
|
9
|
+
import {
|
|
10
|
+
createReadFileTool,
|
|
11
|
+
createWriteFileTool,
|
|
12
|
+
createEditFileTool,
|
|
13
|
+
createListDirTool,
|
|
14
|
+
createSearchFilesTool,
|
|
15
|
+
} from "../tools/filesystem.js";
|
|
16
|
+
import type { Tool, ToolResult } from "../tools/types.js";
|
|
17
|
+
|
|
18
|
+
export interface ToolExecutorOptions {
|
|
19
|
+
workingDirectory?: string;
|
|
20
|
+
allowedPaths?: string[];
|
|
21
|
+
maxFileSize?: number;
|
|
22
|
+
bashTimeout?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ToolExecutor {
|
|
26
|
+
private tools: Map<string, Tool> = new Map();
|
|
27
|
+
private workingDirectory: string;
|
|
28
|
+
|
|
29
|
+
constructor(options: ToolExecutorOptions = {}) {
|
|
30
|
+
this.workingDirectory = options.workingDirectory || process.cwd();
|
|
31
|
+
|
|
32
|
+
const fsOptions = {
|
|
33
|
+
basePath: this.workingDirectory,
|
|
34
|
+
allowedPaths: options.allowedPaths,
|
|
35
|
+
maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB default
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const bashOptions = {
|
|
39
|
+
cwd: this.workingDirectory,
|
|
40
|
+
timeout: options.bashTimeout || 60000, // 60s default
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Register all CLI tools
|
|
44
|
+
this.registerTool(createBashTool(bashOptions));
|
|
45
|
+
this.registerTool(createReadFileTool(fsOptions));
|
|
46
|
+
this.registerTool(createWriteFileTool(fsOptions));
|
|
47
|
+
this.registerTool(createEditFileTool(fsOptions));
|
|
48
|
+
this.registerTool(createListDirTool(fsOptions));
|
|
49
|
+
this.registerTool(createSearchFilesTool(fsOptions));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private registerTool(tool: Tool): void {
|
|
53
|
+
this.tools.set(tool.name, tool);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Execute a tool by name with the given arguments.
|
|
58
|
+
* Returns the output string on success, throws on error.
|
|
59
|
+
*/
|
|
60
|
+
async execute(toolName: string, args: Record<string, unknown>): Promise<string> {
|
|
61
|
+
const tool = this.tools.get(toolName);
|
|
62
|
+
|
|
63
|
+
if (!tool) {
|
|
64
|
+
throw new Error(`Unknown tool: ${toolName}. Available tools: ${this.getToolNames().join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Map argument names if needed (server uses different names)
|
|
68
|
+
const mappedArgs = this.mapArguments(toolName, args);
|
|
69
|
+
|
|
70
|
+
const result = await tool.execute(mappedArgs);
|
|
71
|
+
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
throw new Error(result.error || `Tool ${toolName} failed`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result.output;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Map server argument names to local tool argument names.
|
|
81
|
+
* The server uses slightly different naming conventions.
|
|
82
|
+
*/
|
|
83
|
+
private mapArguments(toolName: string, args: Record<string, unknown>): Record<string, unknown> {
|
|
84
|
+
const mapped = { ...args };
|
|
85
|
+
|
|
86
|
+
switch (toolName) {
|
|
87
|
+
case "read_file":
|
|
88
|
+
// Server: file_path, start_line, end_line
|
|
89
|
+
// Local: path, start_line, end_line
|
|
90
|
+
if ("file_path" in args) {
|
|
91
|
+
mapped.path = args.file_path;
|
|
92
|
+
delete mapped.file_path;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case "write_file":
|
|
97
|
+
// Server: file_path, content
|
|
98
|
+
// Local: path, content
|
|
99
|
+
if ("file_path" in args) {
|
|
100
|
+
mapped.path = args.file_path;
|
|
101
|
+
delete mapped.file_path;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case "edit_file":
|
|
106
|
+
// Server: file_path, old_text, new_text
|
|
107
|
+
// Local: path, old_text, new_text
|
|
108
|
+
if ("file_path" in args) {
|
|
109
|
+
mapped.path = args.file_path;
|
|
110
|
+
delete mapped.file_path;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case "list_directory":
|
|
115
|
+
// Both use: path, recursive, max_depth
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case "search_files":
|
|
119
|
+
// Server: path, pattern, search_content, max_results
|
|
120
|
+
// Local: path, pattern, type
|
|
121
|
+
if (args.search_content) {
|
|
122
|
+
mapped.type = "content";
|
|
123
|
+
} else {
|
|
124
|
+
mapped.type = "filename";
|
|
125
|
+
}
|
|
126
|
+
delete mapped.search_content;
|
|
127
|
+
delete mapped.max_results;
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case "bash":
|
|
131
|
+
// Both use: command, workdir
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return mapped;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get list of available tool names.
|
|
140
|
+
*/
|
|
141
|
+
getToolNames(): string[] {
|
|
142
|
+
return Array.from(this.tools.keys());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get a tool by name.
|
|
147
|
+
*/
|
|
148
|
+
getTool(name: string): Tool | undefined {
|
|
149
|
+
return this.tools.get(name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if a tool exists.
|
|
154
|
+
*/
|
|
155
|
+
hasTool(name: string): boolean {
|
|
156
|
+
return this.tools.has(name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get working directory.
|
|
161
|
+
*/
|
|
162
|
+
getWorkingDirectory(): string {
|
|
163
|
+
return this.workingDirectory;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Set working directory for bash commands.
|
|
168
|
+
*/
|
|
169
|
+
setWorkingDirectory(dir: string): void {
|
|
170
|
+
this.workingDirectory = dir;
|
|
171
|
+
|
|
172
|
+
// Recreate bash tool with new working directory
|
|
173
|
+
this.registerTool(createBashTool({ cwd: dir }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket client for Pioneer MLE Agent
|
|
3
|
+
*
|
|
4
|
+
* Connects to the /mle-agent/ws endpoint for bidirectional communication.
|
|
5
|
+
* Handles tool_call messages by executing tools locally and sending results back.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import WebSocket from "ws";
|
|
9
|
+
import { getApiKey, getBaseUrl } from "../config.js";
|
|
10
|
+
|
|
11
|
+
export interface WebSocketMessage {
|
|
12
|
+
type: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToolCallRequest {
|
|
17
|
+
call_id: string;
|
|
18
|
+
tool: string;
|
|
19
|
+
args: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Message format for conversation history.
|
|
24
|
+
* Matches the format sent by the backend on done event.
|
|
25
|
+
*/
|
|
26
|
+
export interface HistoryMessage {
|
|
27
|
+
role: "user" | "assistant" | "tool";
|
|
28
|
+
content: string;
|
|
29
|
+
tool_calls?: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
args: Record<string, unknown>;
|
|
33
|
+
}>;
|
|
34
|
+
tool_call_id?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WebSocketClientCallbacks {
|
|
38
|
+
onStream?: (content: string) => void;
|
|
39
|
+
onToolStart?: (name: string, callId: string, args: Record<string, unknown>) => void;
|
|
40
|
+
onToolResult?: (name: string, callId: string, result: unknown) => void;
|
|
41
|
+
onToolError?: (name: string, callId: string, error: string) => void;
|
|
42
|
+
onToolCall?: (call: ToolCallRequest) => Promise<string>;
|
|
43
|
+
onAssistantMessage?: (content: string) => void;
|
|
44
|
+
onError?: (error: Error) => void;
|
|
45
|
+
onDone?: (messages?: HistoryMessage[]) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ChatOptions {
|
|
49
|
+
history?: HistoryMessage[];
|
|
50
|
+
fileReferences?: string[];
|
|
51
|
+
config?: { model?: string };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type ConnectionState = "disconnected" | "connecting" | "connected";
|
|
55
|
+
|
|
56
|
+
export class WebSocketClient {
|
|
57
|
+
private ws: WebSocket | null = null;
|
|
58
|
+
private baseUrl: string;
|
|
59
|
+
private apiKey: string | undefined;
|
|
60
|
+
private callbacks: WebSocketClientCallbacks = {};
|
|
61
|
+
private state: ConnectionState = "disconnected";
|
|
62
|
+
private messageResolvers: Map<string, (value: void) => void> = new Map();
|
|
63
|
+
private donePromise: Promise<void> | null = null;
|
|
64
|
+
private doneResolver: (() => void) | null = null;
|
|
65
|
+
private doneCalled: boolean = false; // Prevent duplicate onDone calls
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
// Convert HTTP URL to WebSocket URL
|
|
69
|
+
this.baseUrl = getBaseUrl()
|
|
70
|
+
.replace(/^https:/, "wss:")
|
|
71
|
+
.replace(/^http:/, "ws:")
|
|
72
|
+
.replace(/\/$/, "");
|
|
73
|
+
this.apiKey = getApiKey();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async connect(): Promise<void> {
|
|
77
|
+
if (this.state === "connected") return;
|
|
78
|
+
if (this.state === "connecting") {
|
|
79
|
+
// Wait for existing connection attempt
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const checkState = setInterval(() => {
|
|
82
|
+
if (this.state === "connected") {
|
|
83
|
+
clearInterval(checkState);
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
}, 100);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.state = "connecting";
|
|
91
|
+
|
|
92
|
+
// Build URL with optional auth token
|
|
93
|
+
let url = `${this.baseUrl}/mle-agent/ws`;
|
|
94
|
+
if (this.apiKey) {
|
|
95
|
+
url += `?token=${encodeURIComponent(this.apiKey)}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
this.ws = new WebSocket(url);
|
|
100
|
+
|
|
101
|
+
this.ws.on("open", () => {
|
|
102
|
+
this.state = "connected";
|
|
103
|
+
resolve();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.ws.on("error", (err: Error) => {
|
|
107
|
+
this.state = "disconnected";
|
|
108
|
+
reject(err);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.ws.on("close", () => {
|
|
112
|
+
this.state = "disconnected";
|
|
113
|
+
// Only call onDone if not already called (prevents duplicate)
|
|
114
|
+
if (!this.doneCalled) {
|
|
115
|
+
this.doneCalled = true;
|
|
116
|
+
this.callbacks.onDone?.();
|
|
117
|
+
}
|
|
118
|
+
if (this.doneResolver) {
|
|
119
|
+
this.doneResolver();
|
|
120
|
+
this.doneResolver = null;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.ws.on("message", async (data: WebSocket.RawData) => {
|
|
125
|
+
try {
|
|
126
|
+
const message = JSON.parse(data.toString()) as WebSocketMessage;
|
|
127
|
+
await this.handleMessage(message);
|
|
128
|
+
} catch (err: unknown) {
|
|
129
|
+
console.error("Failed to parse WebSocket message:", err);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async handleMessage(message: WebSocketMessage): Promise<void> {
|
|
136
|
+
switch (message.type) {
|
|
137
|
+
case "stream":
|
|
138
|
+
this.callbacks.onStream?.(message.content as string);
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case "tool_start":
|
|
142
|
+
this.callbacks.onToolStart?.(
|
|
143
|
+
message.name as string,
|
|
144
|
+
message.call_id as string,
|
|
145
|
+
message.args as Record<string, unknown>
|
|
146
|
+
);
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case "tool_call":
|
|
150
|
+
// Server is asking client to execute a tool locally
|
|
151
|
+
if (this.callbacks.onToolCall) {
|
|
152
|
+
const call: ToolCallRequest = {
|
|
153
|
+
call_id: message.call_id as string,
|
|
154
|
+
tool: message.tool as string,
|
|
155
|
+
args: message.args as Record<string, unknown>,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const result = await this.callbacks.onToolCall(call);
|
|
160
|
+
this.sendToolResult(call.call_id, result);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
163
|
+
this.sendToolResult(call.call_id, null, errorMsg);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// No tool executor configured, send error
|
|
167
|
+
this.sendToolResult(
|
|
168
|
+
message.call_id as string,
|
|
169
|
+
null,
|
|
170
|
+
"No tool executor configured on client"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
|
|
175
|
+
case "tool_result":
|
|
176
|
+
this.callbacks.onToolResult?.(
|
|
177
|
+
message.name as string,
|
|
178
|
+
message.call_id as string,
|
|
179
|
+
message.result
|
|
180
|
+
);
|
|
181
|
+
break;
|
|
182
|
+
|
|
183
|
+
case "tool_error":
|
|
184
|
+
this.callbacks.onToolError?.(
|
|
185
|
+
message.name as string,
|
|
186
|
+
message.call_id as string,
|
|
187
|
+
message.error as string
|
|
188
|
+
);
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case "assistant_message":
|
|
192
|
+
this.callbacks.onAssistantMessage?.(message.content as string);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case "error":
|
|
196
|
+
this.callbacks.onError?.(new Error(message.message as string));
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case "done":
|
|
200
|
+
// Only call onDone once (prevents duplicate with ws.onclose)
|
|
201
|
+
if (!this.doneCalled) {
|
|
202
|
+
this.doneCalled = true;
|
|
203
|
+
// Pass the message history if provided by the backend
|
|
204
|
+
const messages = message.messages as HistoryMessage[] | undefined;
|
|
205
|
+
console.log("[DEBUG] Done event received, messages:", messages?.length, "roles:", messages?.map(m => m.role));
|
|
206
|
+
this.callbacks.onDone?.(messages);
|
|
207
|
+
}
|
|
208
|
+
if (this.doneResolver) {
|
|
209
|
+
this.doneResolver();
|
|
210
|
+
this.doneResolver = null;
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case "ping":
|
|
215
|
+
this.send({ type: "pong" });
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case "budget_warning":
|
|
219
|
+
// Could add a callback for this if needed
|
|
220
|
+
console.warn("Budget warning:", message.warnings);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private send(message: WebSocketMessage): void {
|
|
226
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
227
|
+
this.ws.send(JSON.stringify(message));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private sendToolResult(callId: string, result: string | null, error?: string): void {
|
|
232
|
+
this.send({
|
|
233
|
+
type: "tool_result",
|
|
234
|
+
call_id: callId,
|
|
235
|
+
result: result || "",
|
|
236
|
+
...(error && { error }),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Send a chat message and wait for completion.
|
|
242
|
+
* The onToolCall callback will be invoked for client-side tool execution.
|
|
243
|
+
*/
|
|
244
|
+
async chat(
|
|
245
|
+
message: string,
|
|
246
|
+
callbacks: WebSocketClientCallbacks,
|
|
247
|
+
options: ChatOptions = {}
|
|
248
|
+
): Promise<void> {
|
|
249
|
+
if (this.state !== "connected") {
|
|
250
|
+
await this.connect();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.callbacks = callbacks;
|
|
254
|
+
this.doneCalled = false; // Reset for new chat
|
|
255
|
+
|
|
256
|
+
// Create promise that resolves when "done" is received
|
|
257
|
+
this.donePromise = new Promise((resolve) => {
|
|
258
|
+
this.doneResolver = resolve;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Send chat message
|
|
262
|
+
this.send({
|
|
263
|
+
type: "chat",
|
|
264
|
+
message,
|
|
265
|
+
client_type: "cli",
|
|
266
|
+
history: options.history || [],
|
|
267
|
+
file_references: options.fileReferences || [],
|
|
268
|
+
config: options.config,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Wait for completion
|
|
272
|
+
await this.donePromise;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Disconnect from the WebSocket server.
|
|
277
|
+
*/
|
|
278
|
+
disconnect(): void {
|
|
279
|
+
if (this.ws) {
|
|
280
|
+
this.ws.close();
|
|
281
|
+
this.ws = null;
|
|
282
|
+
}
|
|
283
|
+
this.state = "disconnected";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Check if currently connected.
|
|
288
|
+
*/
|
|
289
|
+
isConnected(): boolean {
|
|
290
|
+
return this.state === "connected";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check server health via HTTP (WebSocket doesn't have health check).
|
|
295
|
+
*/
|
|
296
|
+
async health(): Promise<boolean> {
|
|
297
|
+
try {
|
|
298
|
+
const httpUrl = this.baseUrl
|
|
299
|
+
.replace(/^wss:/, "https:")
|
|
300
|
+
.replace(/^ws:/, "http:");
|
|
301
|
+
const response = await fetch(`${httpUrl}/health`);
|
|
302
|
+
return response.ok;
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* List available tools from the server.
|
|
310
|
+
*/
|
|
311
|
+
async listTools(): Promise<Array<{ name: string; description: string }>> {
|
|
312
|
+
const httpUrl = this.baseUrl
|
|
313
|
+
.replace(/^wss:/, "https:")
|
|
314
|
+
.replace(/^ws:/, "http:");
|
|
315
|
+
const url = `${httpUrl}/mle-agent/tools?client_type=cli`;
|
|
316
|
+
|
|
317
|
+
const headers: Record<string, string> = {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
if (this.apiKey) {
|
|
322
|
+
headers["X-API-Key"] = this.apiKey;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const response = await fetch(url, { headers });
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error(`Failed to list tools: ${response.status}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const data = await response.json();
|
|
331
|
+
return data.tools || [];
|
|
332
|
+
}
|
|
333
|
+
}
|