Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,25 @@ private Object convertFromString(String stringValue, Class<?> targetType) {
/**
* Handle invocation errors with informative messages.
*
* <p>Special handling for {@link ToolSuspendException}: if found in the exception chain,
* it will be re-thrown to allow proper suspension handling by {@link ToolExecutor}.
*
* @param e the exception
* @return ToolResultBlock with error message
* @throws ToolSuspendException if found in the exception chain
*/
private ToolResultBlock handleInvocationError(Exception e) {
// Check if the exception itself is ToolSuspendException
if (e instanceof ToolSuspendException) {
throw (ToolSuspendException) e;
}

Throwable cause = e.getCause();
// Check for ToolSuspendException in the exception chain
if (cause instanceof ToolSuspendException) {
throw (ToolSuspendException) cause;
}

String errorMsg =
cause != null
? ExceptionUtils.getErrorMessage(cause)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.agentscope.core.tool;

import static org.junit.Assert.assertThrows;

import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.message.ToolUseBlock;
import io.agentscope.core.tool.test.ToolTestUtils;
Expand Down Expand Up @@ -121,6 +123,24 @@ public int parsableIntString(
@ToolParam(name = "value", description = "value") String value) {
return Integer.parseInt(value);
}

public String suspendTool(
@ToolParam(name = "reason", description = "reason") String reason) {
throw new ToolSuspendException(reason);
}

public java.util.concurrent.CompletableFuture<String> suspendToolAsync(
@ToolParam(name = "reason", description = "reason") String reason) {
return java.util.concurrent.CompletableFuture.supplyAsync(
() -> {
throw new ToolSuspendException(reason);
});
}

public reactor.core.publisher.Mono<String> suspendToolMono(
@ToolParam(name = "reason", description = "reason") String reason) {
return reactor.core.publisher.Mono.error(new ToolSuspendException(reason));
}
}

@Test
Expand Down Expand Up @@ -493,6 +513,54 @@ void testConvertFromString_LargeNumbers() throws Exception {
String.valueOf(Double.MAX_VALUE), ToolTestUtils.extractContent(response2));
}

@Test
void testToolSuspendException_SyncMethod() throws Exception {
TestTools tools = new TestTools();
Method method = TestTools.class.getMethod("suspendTool", String.class);

Map<String, Object> input = new HashMap<>();
input.put("reason", "Waiting for external API");

assertThrows(
ToolSuspendException.class,
() -> {
invokeWithParam(tools, method, input);
});
}

@Test
void testToolSuspendException_CompletableFuture() throws Exception {
TestTools tools = new TestTools();
Method method = TestTools.class.getMethod("suspendToolAsync", String.class);

Map<String, Object> input = new HashMap<>();
input.put("reason", "Async suspension required");

assertThrows(
ToolSuspendException.class,
() -> {
invokeWithParam(tools, method, input);
});
}

@Test
void testToolSuspendException_Mono() throws Exception {
TestTools tools = new TestTools();
Method method = TestTools.class.getMethod("suspendToolMono", String.class);

Map<String, Object> input = new HashMap<>();
input.put("reason", "Reactive suspension needed");

try {
ToolResultBlock response = invokeWithParam(tools, method, input);
Assertions.fail("Should throw ToolSuspendException");
} catch (ToolSuspendException e) {
Assertions.assertEquals("Reactive suspension needed", e.getReason());
} catch (Exception e) {
Assertions.fail("Unexpected exception: " + e.getMessage());
}
}

@Test
void testConvertFromString_NegativeNumbers() throws Exception {
TestTools tools = new TestTools();
Expand Down
Loading