@fastino-ai/pioneer-cli 0.2.10 → 0.3.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/src/chat/index.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Chat module exports
3
- */
4
-
5
- export { ChatApp } from "./ChatApp.js";
6
- export type { ChatAppProps } from "./ChatApp.js";
7
-
@@ -1,175 +0,0 @@
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
- }
@@ -1,379 +0,0 @@
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, getWsUrl } 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[], sessionId?: string) => 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 wsUrl: 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
- this.wsUrl = getWsUrl();
69
- this.apiKey = getApiKey();
70
- }
71
-
72
- async connect(): Promise<void> {
73
- if (this.state === "connected") return;
74
- if (this.state === "connecting") {
75
- // Wait for existing connection attempt
76
- return new Promise((resolve) => {
77
- const checkState = setInterval(() => {
78
- if (this.state === "connected") {
79
- clearInterval(checkState);
80
- resolve();
81
- }
82
- }, 100);
83
- });
84
- }
85
-
86
- this.state = "connecting";
87
-
88
- // Build WebSocket options with X-API-Key header for authentication
89
- const wsOptions: WebSocket.ClientOptions = {};
90
- if (this.apiKey) {
91
- wsOptions.headers = {
92
- "X-API-Key": this.apiKey,
93
- };
94
- }
95
-
96
- return new Promise((resolve, reject) => {
97
- this.ws = new WebSocket(this.wsUrl, wsOptions);
98
-
99
- this.ws.on("open", () => {
100
- this.state = "connected";
101
- // Send auth explicitly as a defensive fallback when header auth is not
102
- // propagated by proxy layers (some environments only support
103
- // first-message auth for WS).
104
- if (this.apiKey) {
105
- this.ws?.send(JSON.stringify({ type: "auth", api_key: this.apiKey }));
106
- }
107
- resolve();
108
- });
109
-
110
- this.ws.on("error", (err: Error | { message?: string; error?: Error }) => {
111
- this.state = "disconnected";
112
- // Extract meaningful error message from ErrorEvent or Error
113
- const errorMessage = err instanceof Error
114
- ? err.message
115
- : (err as { message?: string; error?: Error }).message
116
- || (err as { error?: Error }).error?.message
117
- || "WebSocket connection failed";
118
- reject(new Error(errorMessage));
119
- });
120
-
121
- this.ws.on("close", () => {
122
- this.state = "disconnected";
123
- // Only call onDone if not already called (prevents duplicate)
124
- if (!this.doneCalled) {
125
- this.doneCalled = true;
126
- this.callbacks.onDone?.(undefined, undefined);
127
- }
128
- if (this.doneResolver) {
129
- this.doneResolver();
130
- this.doneResolver = null;
131
- }
132
- });
133
-
134
- this.ws.on("message", async (data: WebSocket.RawData) => {
135
- try {
136
- const message = JSON.parse(data.toString()) as WebSocketMessage;
137
- await this.handleMessage(message);
138
- } catch (err: unknown) {
139
- console.error("Failed to parse WebSocket message:", err);
140
- }
141
- });
142
- });
143
- }
144
-
145
- private async handleMessage(message: WebSocketMessage): Promise<void> {
146
- switch (message.type) {
147
- case "auth_required":
148
- if (this.apiKey) {
149
- this.send({ type: "auth", api_key: this.apiKey });
150
- }
151
- break;
152
-
153
- case "auth_success":
154
- break;
155
-
156
- case "stream":
157
- this.callbacks.onStream?.(message.content as string);
158
- break;
159
-
160
- case "tool_start":
161
- this.callbacks.onToolStart?.(
162
- message.name as string,
163
- message.call_id as string,
164
- message.args as Record<string, unknown>
165
- );
166
- break;
167
-
168
- case "tool_call":
169
- // Server is asking client to execute a tool locally
170
- if (this.callbacks.onToolCall) {
171
- const call: ToolCallRequest = {
172
- call_id: message.call_id as string,
173
- tool: message.tool as string,
174
- args: message.args as Record<string, unknown>,
175
- };
176
-
177
- try {
178
- const result = await this.callbacks.onToolCall(call);
179
- this.sendToolResult(call.call_id, result);
180
- } catch (error) {
181
- const errorMsg = error instanceof Error ? error.message : String(error);
182
- this.sendToolResult(call.call_id, null, errorMsg);
183
- }
184
- } else {
185
- // No tool executor configured, send error
186
- this.sendToolResult(
187
- message.call_id as string,
188
- null,
189
- "No tool executor configured on client"
190
- );
191
- }
192
- break;
193
-
194
- case "tool_result":
195
- this.callbacks.onToolResult?.(
196
- message.name as string,
197
- message.call_id as string,
198
- message.result
199
- );
200
- break;
201
-
202
- case "tool_error":
203
- this.callbacks.onToolError?.(
204
- message.name as string,
205
- message.call_id as string,
206
- message.error as string
207
- );
208
- break;
209
-
210
- case "assistant_message":
211
- this.callbacks.onAssistantMessage?.(message.content as string);
212
- break;
213
-
214
- case "error":
215
- this.callbacks.onError?.(new Error(message.message as string));
216
- break;
217
-
218
- case "done":
219
- // Only call onDone once (prevents duplicate with ws.onclose)
220
- if (!this.doneCalled) {
221
- this.doneCalled = true;
222
- // Pass the message history if provided by the backend
223
- const messages = message.messages as HistoryMessage[] | undefined;
224
- const sessionId = typeof message.session_id === "string" ? message.session_id : undefined;
225
- console.log(
226
- "[DEBUG] Done event received, messages:",
227
- messages?.length,
228
- "roles:",
229
- messages?.map((m) => m.role),
230
- "sessionId:",
231
- sessionId
232
- );
233
- this.callbacks.onDone?.(messages, sessionId);
234
- }
235
- if (this.doneResolver) {
236
- this.doneResolver();
237
- this.doneResolver = null;
238
- }
239
- break;
240
-
241
- case "ping":
242
- this.send({ type: "pong" });
243
- break;
244
-
245
- case "budget_warning":
246
- // Could add a callback for this if needed
247
- console.warn("Budget warning:", message.warnings);
248
- break;
249
- }
250
- }
251
-
252
- private send(message: WebSocketMessage): void {
253
- if (this.ws?.readyState === WebSocket.OPEN) {
254
- this.ws.send(JSON.stringify(message));
255
- }
256
- }
257
-
258
- private sendToolResult(callId: string, result: string | null, error?: string): void {
259
- this.send({
260
- type: "tool_result",
261
- call_id: callId,
262
- result: result || "",
263
- ...(error && { error }),
264
- });
265
- }
266
-
267
- /**
268
- * Send a chat message and wait for completion.
269
- * The onToolCall callback will be invoked for client-side tool execution.
270
- */
271
- async chat(
272
- message: string,
273
- callbacks: WebSocketClientCallbacks,
274
- options: ChatOptions = {}
275
- ): Promise<void> {
276
- if (this.state !== "connected") {
277
- await this.connect();
278
- }
279
-
280
- this.callbacks = callbacks;
281
- this.doneCalled = false; // Reset for new chat
282
-
283
- // Create promise that resolves when "done" is received
284
- this.donePromise = new Promise((resolve) => {
285
- this.doneResolver = resolve;
286
- });
287
-
288
- // Send chat message
289
- this.send({
290
- type: "chat",
291
- message,
292
- client_type: "cli",
293
- history: options.history || [],
294
- file_references: options.fileReferences || [],
295
- config: options.config,
296
- });
297
-
298
- // Wait for completion
299
- await this.donePromise;
300
- }
301
-
302
- /**
303
- * Disconnect from the WebSocket server.
304
- */
305
- disconnect(): void {
306
- if (this.ws) {
307
- this.ws.close();
308
- this.ws = null;
309
- }
310
- this.state = "disconnected";
311
- }
312
-
313
- /**
314
- * Check if currently connected.
315
- */
316
- isConnected(): boolean {
317
- return this.state === "connected";
318
- }
319
-
320
- /**
321
- * Get the REST API base URL (for display purposes).
322
- */
323
- getRestApiUrl(): string {
324
- return getBaseUrl();
325
- }
326
-
327
- /**
328
- * Get the WebSocket URL (for display purposes).
329
- */
330
- getWebSocketUrl(): string {
331
- return this.wsUrl;
332
- }
333
-
334
- /**
335
- * Check server health via HTTP (WebSocket doesn't have health check).
336
- */
337
- async health(): Promise<boolean> {
338
- try {
339
- const httpUrl = getBaseUrl();
340
-
341
- const headers: Record<string, string> = {
342
- "Content-Type": "application/json",
343
- };
344
-
345
- if (this.apiKey) {
346
- headers["X-API-Key"] = this.apiKey;
347
- }
348
-
349
- const response = await fetch(`${httpUrl}/health`, { headers });
350
- return response.ok;
351
- } catch {
352
- return false;
353
- }
354
- }
355
-
356
- /**
357
- * List available tools from the server.
358
- */
359
- async listTools(): Promise<Array<{ name: string; description: string }>> {
360
- const httpUrl = getBaseUrl();
361
- const url = `${httpUrl}/mle-agent/tools?client_type=cli`;
362
-
363
- const headers: Record<string, string> = {
364
- "Content-Type": "application/json",
365
- };
366
-
367
- if (this.apiKey) {
368
- headers["X-API-Key"] = this.apiKey;
369
- }
370
-
371
- const response = await fetch(url, { headers });
372
- if (!response.ok) {
373
- throw new Error(`Failed to list tools: ${response.status}`);
374
- }
375
-
376
- const data = await response.json();
377
- return data.tools || [];
378
- }
379
- }
@@ -1,2 +0,0 @@
1
- export { WebSocketClient } from "./WebSocketClient.js";
2
- export { ToolExecutor } from "./ToolExecutor.js";