Skip to content

Commit e93bf78

Browse files
committed
V1: Enhancements to ToolManager, LocalShell, and RunningJVM
1 parent 0e01d01 commit e93bf78

6 files changed

Lines changed: 60 additions & 13 deletions

File tree

src/main/java/uno/anahata/ai/Chat.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,10 @@ private boolean processAndReloopForFunctionCalls(ChatMessage modelMessageWithCal
411411
contextManager.add(message);
412412
}
413413

414-
return !executedCalls.isEmpty();
414+
boolean anyKilled = processingResult.getOutcomes().stream()
415+
.anyMatch(outcome -> outcome.getStatus() == ToolCallStatus.KILLED);
416+
417+
return !executedCalls.isEmpty() && !anyKilled;
415418
}
416419

417420
private GenerateContentResponse sendToModelWithRetry(List<Content> context) {

src/main/java/uno/anahata/ai/tools/ToolCallOutcome.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public String toFeedbackString(boolean dialogShown) {
6060
case CANCELLED:
6161
statusLabel = "NOT_EXECUTED (Dialog-cancelled)";
6262
break;
63+
case ERROR:
64+
statusLabel = "ERROR";
65+
break;
66+
case KILLED:
67+
statusLabel = "KILLED";
68+
break;
6369
default:
6470
statusLabel = "NOT_EXECUTED";
6571
}

src/main/java/uno/anahata/ai/tools/ToolCallStatus.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,11 @@ public enum ToolCallStatus {
2929
CANCELLED,
3030

3131
/** The model attempted to call a tool while function calling was disabled by the user. */
32-
DISABLED;
33-
}
32+
DISABLED,
33+
34+
/** The tool was approved for execution, but the execution failed with an exception. */
35+
ERROR,
36+
37+
/** The tool execution was explicitly killed/interrupted by the user. */
38+
KILLED;
39+
}

src/main/java/uno/anahata/ai/tools/ToolManager.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,15 @@ public FunctionProcessingResult processFunctionCalls(ChatMessage modelResponseMe
398398
executedCalls.add(new ExecutedToolCall(idc.getSourcePart(), fr, rawResult));
399399

400400
} catch (Exception e) {
401-
log.error("Error executing tool call: {}", toolName, e);
402-
failureTracker.recordFailure(idc.getCall(), e);
401+
if (Thread.interrupted() || ExceptionUtils.indexOfThrowable(e, InterruptedException.class) != -1) {
402+
log.info("Tool call '{}' was interrupted/killed.", toolName);
403+
status = ToolCallStatus.KILLED;
404+
} else {
405+
log.error("Error executing tool call: {}", toolName, e);
406+
failureTracker.recordFailure(idc.getCall(), e);
407+
status = ToolCallStatus.ERROR;
408+
}
409+
403410
Map<String, Object> errorMap = new HashMap<>();
404411
errorMap.put("error", ExceptionUtils.getStackTrace(e));
405412
FunctionResponse errorResponse = FunctionResponse.builder()

src/main/java/uno/anahata/ai/tools/spi/LocalShell.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010
import java.util.Map;
1111
import java.util.concurrent.ExecutorService;
1212
import java.util.concurrent.Future;
13+
import lombok.extern.slf4j.Slf4j;
1314
import uno.anahata.ai.tools.AIToolMethod;
1415
import uno.anahata.ai.tools.AIToolParam;
1516

1617
/**
1718
* A tool provider that allows the AI model to execute commands in the local
18-
* bash shell.
19+
* shell (bash on Unix, cmd.exe on Windows).
1920
* <p>
2021
* This tool is powerful and should be used with caution. It captures standard
2122
* output, standard error, and execution metadata.
2223
* </p>
2324
*/
25+
@Slf4j
2426
public class LocalShell {
2527

2628
/**
27-
* Executes a shell command using {@code bash -c}.
29+
* Executes a shell command using the appropriate system shell.
2830
*
2931
* @param command The shell command to execute.
3032
* @return A map containing execution results:
@@ -40,8 +42,8 @@ public class LocalShell {
4042
* </ul>
4143
* @throws Exception if the command fails to start or execution is interrupted.
4244
*/
43-
@AIToolMethod("Runs a shell command with bash -c: <command> and returns a map with the following values:\n"
44-
+ " \n\tthreadId (it of the thread that executed the shell command, "
45+
@AIToolMethod("Runs a shell command using the system's default shell (cmd.exe on Windows, bash on Unix) and returns a map with the following values:\n"
46+
+ " \n\tthreadId (id of the thread that executed the shell command, "
4547
+ " \n\tpid, process id"
4648
+ " \n\tstartTime, process start time as an ISO-8601 string"
4749
+ " \n\tendTime, process end time as an ISO-8601 string"
@@ -51,10 +53,17 @@ public class LocalShell {
5153
+ " \n\tstderr, process standard error"
5254
)
5355
public static Map<String, Object> runShell(@AIToolParam("The command to run") String command) throws Exception {
54-
System.out.println("executeShellCommand: " + command);
56+
log.info("executeShellCommand: {}", command);
5557
Map<String, Object> result = new HashMap<>();
5658

57-
ProcessBuilder pb = new ProcessBuilder("bash", "-c", command);
59+
String os = System.getProperty("os.name").toLowerCase();
60+
ProcessBuilder pb;
61+
if (os.contains("win")) {
62+
pb = new ProcessBuilder("cmd.exe", "/c", command);
63+
} else {
64+
pb = new ProcessBuilder("bash", "-c", command);
65+
}
66+
5867
pb.redirectErrorStream(false);
5968

6069
Instant startTime = Instant.now();
@@ -110,4 +119,4 @@ public String call() throws IOException {
110119
return sb.toString();
111120
}
112121
}
113-
}
122+
}

src/main/java/uno/anahata/ai/tools/spi/RunningJVM.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@
3838
* test, and run code dynamically. It uses an in-memory file manager and a
3939
* custom classloader to ensure that newly compiled classes take precedence.
4040
* </p>
41+
*
42+
* <h3>The Hot-Reload Mechanism:</h3>
43+
* <p>
44+
* When code is compiled via {@link #compileJava(String, String, String, String[])}, the resulting
45+
* bytecode is stored in memory. A custom {@link URLClassLoader} is then created with a
46+
* <b>Child-First</b> strategy:
47+
* </p>
48+
* <ol>
49+
* <li>It first checks if the class is the one just compiled in memory.</li>
50+
* <li>If not, it attempts to load the class from the {@code extraClassPath} (e.g., a project's {@code target/classes}).</li>
51+
* <li>Only if the class is not found in either location does it delegate to the parent classloader (e.g. the IDE's classpath or the host application's classpath).</li>
52+
* </ol>
53+
* <p>
54+
* This ensures that any changes you make to the code are reflected immediately, even if a version
55+
* of that class already exists in the IDE's main classpath.
56+
* </p>
4157
*
4258
* @author anahata
4359
*/
@@ -339,4 +355,4 @@ private static Object ensureJsonSerializable(Object value) {
339355

340356
return value.toString();
341357
}
342-
}
358+
}

0 commit comments

Comments
 (0)