001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.ai.adk.tool.builtin; 019 020import static dev.enola.common.SuccessOrError.error; 021import static dev.enola.common.SuccessOrError.success; 022 023import com.google.adk.tools.Annotations; 024import com.google.adk.tools.BaseTool; 025import com.google.adk.tools.FunctionTool; 026 027import dev.enola.ai.adk.tool.Tools; 028import dev.enola.common.SuccessOrError; 029 030import java.io.BufferedReader; 031import java.io.IOException; 032import java.io.InputStreamReader; 033import java.util.Map; 034import java.util.concurrent.TimeUnit; 035 036public class ExecTool { 037 038 // TODO Make allowed commands configurable 039 040 // TODO Use stuff from package dev.enola.common.exec 041 042 public BaseTool createTool() { 043 return FunctionTool.create(this, "executeCommand"); 044 } 045 046 @Annotations.Schema( 047 description = 048 "Executes a shell command and captures its standard output and standard error.") 049 public Map<String, ?> executeCommand( 050 @Annotations.Schema(description = "The command to execute (e.g., 'ls -l').") 051 String command) { 052 return Tools.toMap(executeCommandHelper(command)); 053 } 054 055 private SuccessOrError<String> executeCommandHelper(String command) { 056 try { 057 ProcessBuilder pb = new ProcessBuilder("bash", "-c", command); 058 pb.redirectErrorStream(true); // Combine stdout and stderr 059 Process process = pb.start(); 060 061 StringBuilder output = new StringBuilder(); 062 try (BufferedReader reader = 063 new BufferedReader(new InputStreamReader(process.getInputStream()))) { 064 String line; 065 while ((line = reader.readLine()) != null) { 066 output.append(line).append("\n"); 067 } 068 } 069 070 if (!process.waitFor(10, TimeUnit.SECONDS)) { 071 process.destroyForcibly(); 072 return error("Command timed out after 10 seconds."); 073 } 074 075 int exitCode = process.exitValue(); 076 return success( 077 String.format( 078 "Exit Code: %d%nOutput:%n%s", exitCode, output.toString().trim())); 079 } catch (IOException | InterruptedException e) { 080 return error("Failed to execute command: " + e.getMessage()); 081 } 082 } 083}