diff --git a/tools/run_command.py b/tools/run_command.py index fa068a0..fcb78c6 100644 --- a/tools/run_command.py +++ b/tools/run_command.py @@ -1,12 +1,10 @@ -import uuid +import json from collections.abc import Generator from typing import Any from dify_plugin import Tool from dify_plugin.entities.tool import ToolInvokeMessage -from daytona import SessionExecuteRequest - from _client import build_client, get_sandbox @@ -17,37 +15,49 @@ def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessag sandbox_id = tool_parameters.get("sandbox_id", "") ephemeral = not sandbox_id + command = tool_parameters["command"] + cwd = tool_parameters.get("cwd") or None + env_vars = self._parse_env_vars(tool_parameters.get("env_vars")) + + timeout = tool_parameters.get("timeout") + if timeout in (None, ""): + timeout = None + else: + timeout = int(timeout) + if sandbox_id: sandbox = get_sandbox(daytona, sandbox_id) else: sandbox = daytona.create() - session_id = f"dify-{uuid.uuid4().hex[:12]}" - session_created = False - try: - sandbox.process.create_session(session_id) - session_created = True - - response = sandbox.process.execute_session_command( - session_id, - SessionExecuteRequest(command=tool_parameters["command"]), + response = sandbox.process.exec( + command, cwd=cwd, env=env_vars, timeout=timeout ) + yield self.create_text_message(response.result or "(no output)") yield self.create_json_message({ "exit_code": response.exit_code, - "stdout": response.stdout or "", - "stderr": response.stderr or "", + "output": response.result or "", "sandbox_id": sandbox.id, }) finally: - if session_created and not ephemeral: - try: - sandbox.process.delete_session(session_id) - except Exception: - pass if ephemeral: try: sandbox.delete() except Exception: pass + + @staticmethod + def _parse_env_vars(raw: Any) -> dict[str, str] | None: + if not raw: + return None + if isinstance(raw, dict): + return {str(k): str(v) for k, v in raw.items()} + try: + parsed = json.loads(raw) + except (ValueError, TypeError) as e: + raise ValueError(f"env_vars must be a JSON object string: {e}") + if not isinstance(parsed, dict): + raise ValueError("env_vars must be a JSON object") + return {str(k): str(v) for k, v in parsed.items()} diff --git a/tools/run_command.yaml b/tools/run_command.yaml index f4270d3..c620056 100644 --- a/tools/run_command.yaml +++ b/tools/run_command.yaml @@ -12,7 +12,7 @@ description: zh_Hans: Run a shell command in a Daytona sandbox and return the output. ja_JP: Run a shell command in a Daytona sandbox and return the output. pt_BR: Run a shell command in a Daytona sandbox and return the output. - llm: Execute a shell command in a Daytona sandbox. Useful for installing packages, running scripts, file operations, and any Linux command. If no sandbox_id is provided, a new ephemeral sandbox is created automatically. Returns stdout, stderr, and exit code. + llm: Execute a shell command in a Daytona sandbox. Useful for installing packages, running scripts, file operations, and any Linux command. Supports optional working directory (cwd) and environment variables. If no sandbox_id is provided, a new ephemeral sandbox is created automatically. Returns combined output (stdout and stderr) and exit code. parameters: - name: command type: string @@ -29,6 +29,53 @@ parameters: pt_BR: The shell command to execute in the sandbox. llm_description: The shell command to execute in the Daytona sandbox. This runs in a standard Linux environment. form: llm + - name: cwd + type: string + required: false + label: + en_US: Working Directory + zh_Hans: Working Directory + ja_JP: Working Directory + pt_BR: Working Directory + human_description: + en_US: Working directory for command execution (e.g. /home/daytona/project). Defaults to sandbox working directory. + zh_Hans: Working directory for command execution (e.g. /home/daytona/project). Defaults to sandbox working directory. + ja_JP: Working directory for command execution (e.g. /home/daytona/project). Defaults to sandbox working directory. + pt_BR: Working directory for command execution (e.g. /home/daytona/project). Defaults to sandbox working directory. + llm_description: Working directory for command execution (e.g. /home/daytona/project). Defaults to sandbox working directory. + form: llm + default: "" + - name: env_vars + type: string + required: false + label: + en_US: Environment Variables (JSON) + zh_Hans: Environment Variables (JSON) + ja_JP: Environment Variables (JSON) + pt_BR: Environment Variables (JSON) + human_description: + en_US: "Environment variables for the command, as a JSON object string. Example: {\"NODE_ENV\": \"production\"}" + zh_Hans: "Environment variables for the command, as a JSON object string. Example: {\"NODE_ENV\": \"production\"}" + ja_JP: "Environment variables for the command, as a JSON object string. Example: {\"NODE_ENV\": \"production\"}" + pt_BR: "Environment variables for the command, as a JSON object string. Example: {\"NODE_ENV\": \"production\"}" + llm_description: 'Environment variables for the command, as a JSON object string. Example: {"NODE_ENV": "production"}' + form: llm + default: "" + - name: timeout + type: number + required: false + label: + en_US: Timeout (seconds) + zh_Hans: Timeout (seconds) + ja_JP: Timeout (seconds) + pt_BR: Timeout (segundos) + human_description: + en_US: Maximum execution time in seconds. Leave empty for no timeout. + zh_Hans: Maximum execution time in seconds. Leave empty for no timeout. + ja_JP: Maximum execution time in seconds. Leave empty for no timeout. + pt_BR: Tempo maximo de execucao em segundos. Deixar vazio para sem limite. + llm_description: 'Optional timeout in seconds. Use a higher value for long operations like pip install or pytest.' + form: llm - name: sandbox_id type: string required: false