@fastino-ai/pioneer-cli 0.1.0 → 0.2.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.
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Modal.com integration for serverless ML workloads
3
+ */
4
+
5
+ import { spawn } from "child_process";
6
+ import type { Tool, ToolResult } from "../agent/types.js";
7
+
8
+ export interface ModalConfig {
9
+ tokenId?: string;
10
+ tokenSecret?: string;
11
+ timeout?: number;
12
+ }
13
+
14
+ // Check if Modal CLI is available
15
+ export async function isModalAvailable(): Promise<boolean> {
16
+ return new Promise((resolve) => {
17
+ const proc = spawn("modal", ["--version"], { stdio: "pipe" });
18
+ proc.on("close", (code) => resolve(code === 0));
19
+ proc.on("error", () => resolve(false));
20
+ });
21
+ }
22
+
23
+ // Run a Modal function
24
+ async function runModal(
25
+ args: string[],
26
+ options: ModalConfig = {}
27
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
28
+ const timeout = options.timeout || 300000; // 5 min default
29
+
30
+ return new Promise((resolve) => {
31
+ let stdout = "";
32
+ let stderr = "";
33
+ let timedOut = false;
34
+
35
+ const env: NodeJS.ProcessEnv = { ...process.env };
36
+ if (options.tokenId) env.MODAL_TOKEN_ID = options.tokenId;
37
+ if (options.tokenSecret) env.MODAL_TOKEN_SECRET = options.tokenSecret;
38
+
39
+ const proc = spawn("modal", args, {
40
+ stdio: ["pipe", "pipe", "pipe"],
41
+ env,
42
+ });
43
+
44
+ const timer = setTimeout(() => {
45
+ timedOut = true;
46
+ proc.kill("SIGTERM");
47
+ }, timeout);
48
+
49
+ proc.stdout.on("data", (data: Buffer) => {
50
+ stdout += data.toString();
51
+ });
52
+
53
+ proc.stderr.on("data", (data: Buffer) => {
54
+ stderr += data.toString();
55
+ });
56
+
57
+ proc.on("close", (code) => {
58
+ clearTimeout(timer);
59
+ resolve({
60
+ stdout,
61
+ stderr,
62
+ exitCode: timedOut ? -1 : (code ?? -1),
63
+ });
64
+ });
65
+
66
+ proc.on("error", (err) => {
67
+ clearTimeout(timer);
68
+ resolve({
69
+ stdout: "",
70
+ stderr: err.message,
71
+ exitCode: -1,
72
+ });
73
+ });
74
+ });
75
+ }
76
+
77
+ export function createModalRunTool(config: ModalConfig = {}): Tool {
78
+ return {
79
+ name: "modal_run",
80
+ description: "Run a Python function on Modal.com serverless GPU/CPU infrastructure. Useful for ML inference, training, and compute-intensive tasks.",
81
+ parameters: [
82
+ {
83
+ name: "code",
84
+ type: "string",
85
+ description: "Python code with Modal app definition. Must include @app.function decorator.",
86
+ required: true,
87
+ },
88
+ {
89
+ name: "function_name",
90
+ type: "string",
91
+ description: "Name of the Modal function to run (the one with @app.local_entrypoint or to call)",
92
+ required: true,
93
+ },
94
+ {
95
+ name: "args",
96
+ type: "string",
97
+ description: "Arguments to pass to the function (as JSON string)",
98
+ required: false,
99
+ },
100
+ ],
101
+
102
+ async execute(args: Record<string, unknown>): Promise<ToolResult> {
103
+ const code = args.code as string;
104
+ const functionName = args.function_name as string;
105
+ const funcArgs = args.args as string;
106
+
107
+ if (!code || !functionName) {
108
+ return {
109
+ toolCallId: "",
110
+ output: "",
111
+ success: false,
112
+ error: "code and function_name are required",
113
+ };
114
+ }
115
+
116
+ // Check if Modal is available
117
+ const available = await isModalAvailable();
118
+ if (!available) {
119
+ return {
120
+ toolCallId: "",
121
+ output: "",
122
+ success: false,
123
+ error: "Modal CLI not installed. Run: pip install modal",
124
+ };
125
+ }
126
+
127
+ // Create temp file with the code
128
+ const fs = await import("fs");
129
+ const path = await import("path");
130
+ const os = await import("os");
131
+
132
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "modal-"));
133
+ const tempFile = path.join(tempDir, "app.py");
134
+
135
+ // Ensure the code has proper Modal imports
136
+ let fullCode = code;
137
+ if (!code.includes("import modal")) {
138
+ fullCode = "import modal\n\n" + code;
139
+ }
140
+
141
+ fs.writeFileSync(tempFile, fullCode);
142
+
143
+ try {
144
+ // Run the Modal function
145
+ const runArgs = ["run", tempFile];
146
+ if (functionName) {
147
+ runArgs.push("::" + functionName);
148
+ }
149
+ if (funcArgs) {
150
+ try {
151
+ const parsed = JSON.parse(funcArgs);
152
+ for (const [key, value] of Object.entries(parsed)) {
153
+ runArgs.push(`--${key}`, String(value));
154
+ }
155
+ } catch {
156
+ // Ignore JSON parse errors
157
+ }
158
+ }
159
+
160
+ const result = await runModal(runArgs, config);
161
+
162
+ let output = result.stdout;
163
+ if (result.stderr) {
164
+ output += (output ? "\n\nStderr:\n" : "") + result.stderr;
165
+ }
166
+
167
+ return {
168
+ toolCallId: "",
169
+ output: output || "(no output)",
170
+ success: result.exitCode === 0,
171
+ error: result.exitCode !== 0 ? `Exit code: ${result.exitCode}` : undefined,
172
+ };
173
+ } finally {
174
+ // Cleanup
175
+ try {
176
+ fs.rmSync(tempDir, { recursive: true, force: true });
177
+ } catch {
178
+ // Ignore cleanup errors
179
+ }
180
+ }
181
+ },
182
+ };
183
+ }
184
+
185
+ export function createModalDeployTool(config: ModalConfig = {}): Tool {
186
+ return {
187
+ name: "modal_deploy",
188
+ description: "Deploy a Modal app for persistent endpoints or scheduled jobs.",
189
+ parameters: [
190
+ {
191
+ name: "code",
192
+ type: "string",
193
+ description: "Python code with Modal app definition",
194
+ required: true,
195
+ },
196
+ {
197
+ name: "app_name",
198
+ type: "string",
199
+ description: "Name for the deployed app",
200
+ required: true,
201
+ },
202
+ ],
203
+
204
+ async execute(args: Record<string, unknown>): Promise<ToolResult> {
205
+ const code = args.code as string;
206
+ const appName = args.app_name as string;
207
+
208
+ if (!code || !appName) {
209
+ return {
210
+ toolCallId: "",
211
+ output: "",
212
+ success: false,
213
+ error: "code and app_name are required",
214
+ };
215
+ }
216
+
217
+ const available = await isModalAvailable();
218
+ if (!available) {
219
+ return {
220
+ toolCallId: "",
221
+ output: "",
222
+ success: false,
223
+ error: "Modal CLI not installed. Run: pip install modal",
224
+ };
225
+ }
226
+
227
+ const fs = await import("fs");
228
+ const path = await import("path");
229
+ const os = await import("os");
230
+
231
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "modal-deploy-"));
232
+ const tempFile = path.join(tempDir, "app.py");
233
+
234
+ let fullCode = code;
235
+ if (!code.includes("import modal")) {
236
+ fullCode = "import modal\n\n" + code;
237
+ }
238
+
239
+ fs.writeFileSync(tempFile, fullCode);
240
+
241
+ try {
242
+ const result = await runModal(["deploy", tempFile, "--name", appName], config);
243
+
244
+ let output = result.stdout;
245
+ if (result.stderr) {
246
+ output += (output ? "\n\nStderr:\n" : "") + result.stderr;
247
+ }
248
+
249
+ return {
250
+ toolCallId: "",
251
+ output: output || "(no output)",
252
+ success: result.exitCode === 0,
253
+ error: result.exitCode !== 0 ? `Exit code: ${result.exitCode}` : undefined,
254
+ };
255
+ } finally {
256
+ try {
257
+ fs.rmSync(tempDir, { recursive: true, force: true });
258
+ } catch {
259
+ // Ignore
260
+ }
261
+ }
262
+ },
263
+ };
264
+ }
265
+
266
+ export function createModalTools(config: ModalConfig = {}): Tool[] {
267
+ return [createModalRunTool(config), createModalDeployTool(config)];
268
+ }
269
+
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Code Sandbox - Isolated execution environments
3
+ * Uses temporary directories with timeout and resource limits
4
+ */
5
+
6
+ import { spawn, execSync } from "child_process";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ import type { Tool, ToolResult } from "../agent/types.js";
11
+
12
+ export interface SandboxOptions {
13
+ timeout?: number; // ms
14
+ memoryLimit?: string; // e.g., "512m"
15
+ cpuLimit?: number; // cores
16
+ useDocker?: boolean;
17
+ dockerImage?: string;
18
+ }
19
+
20
+ export interface SandboxResult {
21
+ stdout: string;
22
+ stderr: string;
23
+ exitCode: number;
24
+ timedOut: boolean;
25
+ duration: number;
26
+ }
27
+
28
+ // Language configurations
29
+ const LANGUAGE_CONFIG: Record<
30
+ string,
31
+ { extension: string; command: (file: string) => string[]; setup?: string }
32
+ > = {
33
+ python: {
34
+ extension: ".py",
35
+ command: (file) => ["python3", file],
36
+ setup: "pip install -q",
37
+ },
38
+ javascript: {
39
+ extension: ".js",
40
+ command: (file) => ["node", file],
41
+ },
42
+ typescript: {
43
+ extension: ".ts",
44
+ command: (file) => ["bun", file],
45
+ },
46
+ bash: {
47
+ extension: ".sh",
48
+ command: (file) => ["bash", file],
49
+ },
50
+ ruby: {
51
+ extension: ".rb",
52
+ command: (file) => ["ruby", file],
53
+ },
54
+ go: {
55
+ extension: ".go",
56
+ command: (file) => ["go", "run", file],
57
+ },
58
+ };
59
+
60
+ export class Sandbox {
61
+ private tempDir: string;
62
+ private options: SandboxOptions;
63
+
64
+ constructor(options: SandboxOptions = {}) {
65
+ this.options = {
66
+ timeout: options.timeout || 30000,
67
+ memoryLimit: options.memoryLimit || "512m",
68
+ cpuLimit: options.cpuLimit || 1,
69
+ useDocker: options.useDocker || false,
70
+ dockerImage: options.dockerImage || "python:3.11-slim",
71
+ };
72
+ this.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sandbox-"));
73
+ }
74
+
75
+ async execute(code: string, language: string): Promise<SandboxResult> {
76
+ const config = LANGUAGE_CONFIG[language];
77
+ if (!config) {
78
+ return {
79
+ stdout: "",
80
+ stderr: `Unsupported language: ${language}. Supported: ${Object.keys(LANGUAGE_CONFIG).join(", ")}`,
81
+ exitCode: 1,
82
+ timedOut: false,
83
+ duration: 0,
84
+ };
85
+ }
86
+
87
+ const filename = `code${config.extension}`;
88
+ const filepath = path.join(this.tempDir, filename);
89
+ fs.writeFileSync(filepath, code);
90
+
91
+ if (this.options.useDocker) {
92
+ return this.executeInDocker(filepath, config.command(filename));
93
+ } else {
94
+ return this.executeLocal(filepath, config.command(filepath));
95
+ }
96
+ }
97
+
98
+ private async executeLocal(
99
+ filepath: string,
100
+ command: string[]
101
+ ): Promise<SandboxResult> {
102
+ const startTime = Date.now();
103
+ let stdout = "";
104
+ let stderr = "";
105
+ let timedOut = false;
106
+
107
+ return new Promise((resolve) => {
108
+ const proc = spawn(command[0], command.slice(1), {
109
+ cwd: this.tempDir,
110
+ env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1" },
111
+ stdio: ["pipe", "pipe", "pipe"],
112
+ });
113
+
114
+ const timer = setTimeout(() => {
115
+ timedOut = true;
116
+ proc.kill("SIGTERM");
117
+ setTimeout(() => proc.kill("SIGKILL"), 5000);
118
+ }, this.options.timeout);
119
+
120
+ proc.stdout.on("data", (data: Buffer) => {
121
+ stdout += data.toString();
122
+ });
123
+
124
+ proc.stderr.on("data", (data: Buffer) => {
125
+ stderr += data.toString();
126
+ });
127
+
128
+ proc.on("close", (code) => {
129
+ clearTimeout(timer);
130
+ const duration = Date.now() - startTime;
131
+
132
+ resolve({
133
+ stdout: stdout.slice(0, 50000), // Limit output
134
+ stderr: stderr.slice(0, 50000),
135
+ exitCode: timedOut ? -1 : (code ?? -1),
136
+ timedOut,
137
+ duration,
138
+ });
139
+ });
140
+
141
+ proc.on("error", (err) => {
142
+ clearTimeout(timer);
143
+ resolve({
144
+ stdout: "",
145
+ stderr: err.message,
146
+ exitCode: -1,
147
+ timedOut: false,
148
+ duration: Date.now() - startTime,
149
+ });
150
+ });
151
+ });
152
+ }
153
+
154
+ private async executeInDocker(
155
+ filepath: string,
156
+ command: string[]
157
+ ): Promise<SandboxResult> {
158
+ const startTime = Date.now();
159
+ const timeoutSeconds = Math.ceil((this.options.timeout || 30000) / 1000);
160
+
161
+ const dockerArgs = [
162
+ "run",
163
+ "--rm",
164
+ "-v",
165
+ `${this.tempDir}:/sandbox:ro`,
166
+ "-w",
167
+ "/sandbox",
168
+ "--memory",
169
+ this.options.memoryLimit || "512m",
170
+ "--cpus",
171
+ String(this.options.cpuLimit || 1),
172
+ "--network",
173
+ "none",
174
+ "--pids-limit",
175
+ "100",
176
+ this.options.dockerImage || "python:3.11-slim",
177
+ "timeout",
178
+ String(timeoutSeconds),
179
+ ...command,
180
+ ];
181
+
182
+ return new Promise((resolve) => {
183
+ const proc = spawn("docker", dockerArgs, {
184
+ stdio: ["pipe", "pipe", "pipe"],
185
+ });
186
+
187
+ let stdout = "";
188
+ let stderr = "";
189
+ let timedOut = false;
190
+
191
+ const timer = setTimeout(() => {
192
+ timedOut = true;
193
+ proc.kill("SIGTERM");
194
+ }, (this.options.timeout || 30000) + 5000);
195
+
196
+ proc.stdout.on("data", (data: Buffer) => {
197
+ stdout += data.toString();
198
+ });
199
+
200
+ proc.stderr.on("data", (data: Buffer) => {
201
+ stderr += data.toString();
202
+ });
203
+
204
+ proc.on("close", (code) => {
205
+ clearTimeout(timer);
206
+ const duration = Date.now() - startTime;
207
+
208
+ // Docker timeout returns code 124
209
+ if (code === 124) timedOut = true;
210
+
211
+ resolve({
212
+ stdout: stdout.slice(0, 50000),
213
+ stderr: stderr.slice(0, 50000),
214
+ exitCode: timedOut ? -1 : (code ?? -1),
215
+ timedOut,
216
+ duration,
217
+ });
218
+ });
219
+
220
+ proc.on("error", (err) => {
221
+ clearTimeout(timer);
222
+ resolve({
223
+ stdout: "",
224
+ stderr: `Docker error: ${err.message}`,
225
+ exitCode: -1,
226
+ timedOut: false,
227
+ duration: Date.now() - startTime,
228
+ });
229
+ });
230
+ });
231
+ }
232
+
233
+ cleanup(): void {
234
+ try {
235
+ fs.rmSync(this.tempDir, { recursive: true, force: true });
236
+ } catch {
237
+ // Ignore cleanup errors
238
+ }
239
+ }
240
+ }
241
+
242
+ export function createSandboxTool(options: SandboxOptions = {}): Tool {
243
+ return {
244
+ name: "execute_code",
245
+ description: "Execute code in an isolated sandbox environment. Supports Python, JavaScript, TypeScript, Bash, Ruby, and Go.",
246
+ parameters: [
247
+ {
248
+ name: "code",
249
+ type: "string",
250
+ description: "The code to execute",
251
+ required: true,
252
+ },
253
+ {
254
+ name: "language",
255
+ type: "string",
256
+ description: "Programming language: python, javascript, typescript, bash, ruby, go",
257
+ required: true,
258
+ },
259
+ ],
260
+
261
+ async execute(args: Record<string, unknown>): Promise<ToolResult> {
262
+ const code = args.code as string;
263
+ const language = (args.language as string)?.toLowerCase();
264
+
265
+ if (!code) {
266
+ return { toolCallId: "", output: "", success: false, error: "Code is required" };
267
+ }
268
+
269
+ if (!language || !LANGUAGE_CONFIG[language]) {
270
+ return {
271
+ toolCallId: "",
272
+ output: "",
273
+ success: false,
274
+ error: `Invalid language. Supported: ${Object.keys(LANGUAGE_CONFIG).join(", ")}`,
275
+ };
276
+ }
277
+
278
+ const sandbox = new Sandbox(options);
279
+
280
+ try {
281
+ const result = await sandbox.execute(code, language);
282
+
283
+ let output = "";
284
+ if (result.stdout) output += result.stdout;
285
+ if (result.stderr) output += (output ? "\n\nStderr:\n" : "") + result.stderr;
286
+ if (result.timedOut) output += `\n\n(Execution timed out after ${options.timeout || 30000}ms)`;
287
+
288
+ return {
289
+ toolCallId: "",
290
+ output: output || "(no output)",
291
+ success: result.exitCode === 0 && !result.timedOut,
292
+ error: result.exitCode !== 0 ? `Exit code: ${result.exitCode}` : undefined,
293
+ };
294
+ } finally {
295
+ sandbox.cleanup();
296
+ }
297
+ },
298
+ };
299
+ }
300
+
301
+ // Check if Docker is available
302
+ export function isDockerAvailable(): boolean {
303
+ try {
304
+ execSync("docker --version", { stdio: "ignore" });
305
+ return true;
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+