File size: 2,156 Bytes
ed89324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { spawn } from "child_process";
import fs from "fs";

let toolSpec;
export function listTools() {
  if (!toolSpec) {
    toolSpec = JSON.parse(fs.readFileSync("tools.json", "utf-8"));
  }
  return toolSpec;
}

/**
 * Stream a child process over SSE.
 * send(eventType, payload) writes to the SSE connection.
 */
function streamProcess(cmd, args, options, send) {
  const child = spawn(cmd, args, { shell: false, ...options });

  child.stdout.on("data", (chunk) => {
    send("output", { data: chunk.toString() });
  });

  child.stderr.on("data", (chunk) => {
    send("error", { data: chunk.toString() });
  });

  child.on("close", (code) => {
    send("done", { code });
  });

  child.on("error", (err) => {
    send("error", { data: String(err) });
    send("done", { code: -1 });
  });

  return child;
}

/**
 * Execute a tool call and stream results.
 * tool: "shell" | "python"
 * args: { command } | { code }
 */
export function executeTool(tool, args, send) {
  if (tool === "shell") {
    if (!args?.command || typeof args.command !== "string") {
      send("error", { data: "Missing 'command' string." });
      return send("done", { code: 2 });
    }
    // Use /bin/bash -lc to enable pipes, redirects, env, etc.
    return streamProcess("/bin/bash", ["-lc", args.command], {}, send);
  }

  if (tool === "python") {
    if (!args?.code || typeof args.code !== "string") {
      send("error", { data: "Missing 'code' string." });
      return send("done", { code: 2 });
    }
    // Run python inline, escape newlines safely by passing as stdin
    const child = spawn("python3", ["-"], { stdio: ["pipe", "pipe", "pipe"] });
    child.stdin.write(args.code);
    child.stdin.end();

    child.stdout.on("data", (chunk) => send("output", { data: chunk.toString() }));
    child.stderr.on("data", (chunk) => send("error", { data: chunk.toString() }));
    child.on("close", (code) => send("done", { code }));
    child.on("error", (err) => {
      send("error", { data: String(err) });
      send("done", { code: -1 });
    });
    return child;
  }

  send("error", { data: `Unknown tool '${tool}'.` });
  send("done", { code: 2 });
}