Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 29 additions & 19 deletions tools/run_command.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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()}
49 changes: 48 additions & 1 deletion tools/run_command.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down