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
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,15 @@ private Mono<ToolResultBlock> executeCore(ToolCallParam param) {
// Create emitter for streaming
ToolEmitter toolEmitter = new DefaultToolEmitter(toolCall, getEffectiveChunkCallback());

// Merge preset parameters with input
// Merge input with preset parameters. Preset values win so framework-controlled
// parameters remain immutable from the caller/LLM perspective.
Map<String, Object> mergedInput = new HashMap<>();
if (registered != null) {
mergedInput.putAll(registered.getPresetParameters());
}
if (param.getInput() != null && !param.getInput().isEmpty()) {
if (!param.getInput().isEmpty()) {
mergedInput.putAll(param.getInput());
} else if (toolCall.getInput() != null) {
} else if (!toolCall.getInput().isEmpty()) {
mergedInput.putAll(toolCall.getInput());
}
mergedInput.putAll(registered.getPresetParameters());

// Build final execution param
ToolCallParam executionParam =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,82 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
assertEquals(2, successCount, "Exactly two calls should succeed");
}

@Test
@DisplayName("Should apply preset parameters after explicit ToolCallParam input")
void shouldApplyPresetParametersAfterExplicitInput() {
class OverrideTool {
@Tool(description = "Test preset precedence with explicit ToolCallParam input")
public ToolResultBlock testOverride(
@ToolParam(name = "param1") String param1,
@ToolParam(name = "param2") String param2) {
return ToolResultBlock.text(
String.format("param1: %s, param2: %s", param1, param2));
}
}

toolkit.registration()
.tool(new OverrideTool())
.presetParameters(
Map.of(
"testOverride",
Map.of("param1", "preset_value1", "param2", "preset_value2")))
.apply();

Map<String, Object> explicitInput = Map.of("param1", "agent_value1");
ToolUseBlock toolCall =
ToolUseBlock.builder()
.id("call-override")
.name("testOverride")
.input(Map.of())
.content("{}")
.build();

ToolResultBlock result =
toolkit.callTool(
ToolCallParam.builder()
.toolUseBlock(toolCall)
.input(explicitInput)
.build())
.block(TIMEOUT);

assertNotNull(result, "Result should not be null");
String resultText = extractFirstText(result);
assertTrue(
resultText.contains("param1: preset_value1"),
"Preset value should override explicit ToolCallParam input");
assertTrue(resultText.contains("param2: preset_value2"), "Preset value should be used");
}

@Test
@DisplayName("Should use only preset parameters when both input sources are absent")
void shouldUseOnlyPresetParametersWhenInputsAbsent() {
class PresetOnlyTool {
@Tool(description = "Test preset usage when no explicit inputs are present")
public ToolResultBlock presetOnly(@ToolParam(name = "param1") String param1) {
return ToolResultBlock.text("param1: " + param1);
}
}

toolkit.registration()
.tool(new PresetOnlyTool())
.presetParameters(Map.of("presetOnly", Map.of("param1", "preset_value1")))
.apply();

ToolUseBlock toolCall =
ToolUseBlock.builder()
.id("call-preset-only")
.name("presetOnly")
.content("{}")
.build();

ToolResultBlock result =
toolkit.callTool(ToolCallParam.builder().toolUseBlock(toolCall).build())
.block(TIMEOUT);

assertNotNull(result, "Result should not be null");
assertEquals("param1: preset_value1", extractFirstText(result));
}

@Test
@DisplayName("Should format all error messages consistently")
void testErrorMessageFormat() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ public ToolResultBlock testWithContext(
}

@Test
@DisplayName("Should allow agent parameters to override preset parameters")
@DisplayName("Should keep preset parameters authoritative over agent parameters")
void testPresetParametersOverride() {
class OverrideTool {
@Tool(description = "Test tool for parameter override")
Expand All @@ -692,7 +692,7 @@ public ToolResultBlock testOverride(
Map.of("param1", "preset_value1", "param2", "preset_value2"));
toolkit.registration().tool(new OverrideTool()).presetParameters(presetParams).apply();

// Call with agent providing param1 (should override preset)
// Call with agent providing param1 (preset should still win)
Map<String, Object> overrideInput = Map.of("param1", "agent_value1");
ToolUseBlock toolCall =
ToolUseBlock.builder()
Expand All @@ -705,9 +705,10 @@ public ToolResultBlock testOverride(
toolkit.callTool(ToolCallParam.builder().toolUseBlock(toolCall).build()).block();
String resultText = getResultText(result);

// param1 should be overridden by agent, param2 should use preset
// Preset values should not be overridden by agent input
assertTrue(
resultText.contains("param1: agent_value1"), "Agent value should override preset");
resultText.contains("param1: preset_value1"),
"Preset value should override agent input");
assertTrue(resultText.contains("param2: preset_value2"), "Preset value should be used");
}

Expand Down
Loading