Skip to content

Commit 2c88c95

Browse files
authored
Merge branch 'main' into feat/tool-schema-support
2 parents e799b81 + 35ccc77 commit 2c88c95

40 files changed

Lines changed: 2274 additions & 94 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Detailed error messages.
2727

2828
**Environment (please complete the following information):**
2929

30-
- AgentScope-Java Version: [e.g. 1.0.8, check your pom.xml dependency version or run `mvn dependency:tree | grep agentscope-parent:pom`(only mac/linux)]
30+
- AgentScope-Java Version: [e.g. 1.0.9, check your pom.xml dependency version or run `mvn dependency:tree | grep agentscope-parent:pom`(only mac/linux)]
3131
- Java Version: [e.g. 17]
3232
- OS: [e.g. macos, windows]
3333

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## AgentScope-Java Version
22

3-
[The version of AgentScope-Java you are working on, e.g. 1.0.8, check your pom.xml dependency version or run `mvn dependency:tree | grep agentscope-parent:pom`(only mac/linux)]
3+
[The version of AgentScope-Java you are working on, e.g. 1.0.9, check your pom.xml dependency version or run `mvn dependency:tree | grep agentscope-parent:pom`(only mac/linux)]
44

55
## Description
66

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Built for enterprise deployment requirements:
7777
<dependency>
7878
<groupId>io.agentscope</groupId>
7979
<artifactId>agentscope</artifactId>
80-
<version>1.0.8</version>
80+
<version>1.0.9</version>
8181
</dependency>
8282
```
8383

README_zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ AgentScope 设计上能够与现有企业基础设施集成,无需大规模改
7777
<dependency>
7878
<groupId>io.agentscope</groupId>
7979
<artifactId>agentscope</artifactId>
80-
<version>1.0.8</version>
80+
<version>1.0.9</version>
8181
</dependency>
8282
```
8383

SKILL.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ public static void main(String[] args) {
100100
<dependency>
101101
<groupId>io.agentscope</groupId>
102102
<artifactId>agentscope</artifactId>
103-
<version>1.0.8</version>
103+
<version>1.0.9</version>
104104
</dependency>
105105
</dependencies>
106106
```
107107

108108
**For local development (if working with source code):**
109109
```xml
110110
<properties>
111-
<agentscope.version>1.0.8</agentscope.version>
111+
<agentscope.version>1.0.9</agentscope.version>
112112
<java.version>17</java.version>
113113
</properties>
114114

@@ -122,8 +122,8 @@ public static void main(String[] args) {
122122
```
123123

124124
**⚠️ IMPORTANT: Version Selection**
125-
- **Use `agentscope:1.0.8`** for production (stable, from Maven Central)
126-
- **Use `agentscope-core:1.0.8`** only if you're developing AgentScope itself
125+
- **Use `agentscope:1.0.9`** for production (stable, from Maven Central)
126+
- **Use `agentscope-core:1.0.9`** only if you're developing AgentScope itself
127127
- **NEVER use version `0.1.0-SNAPSHOT`** - this version doesn't exist
128128

129129
### ⚠️ CRITICAL: Common Dependency Mistakes
@@ -161,7 +161,7 @@ public static void main(String[] args) {
161161
<dependency>
162162
<groupId>io.agentscope</groupId>
163163
<artifactId>agentscope</artifactId>
164-
<version>1.0.8</version> <!-- ✅ CORRECT -->
164+
<version>1.0.9</version> <!-- ✅ CORRECT -->
165165
</dependency>
166166
```
167167

agentscope-core/src/main/java/io/agentscope/core/Version.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
public final class Version {
2525

2626
/** AgentScope Java version */
27-
public static final String VERSION = "1.0.9-SNAPSHOT";
27+
public static final String VERSION = "1.0.10-SNAPSHOT";
2828

2929
private Version() {
3030
// Utility class - prevent instantiation
@@ -35,7 +35,7 @@ private Version() {
3535
*
3636
* <p>Format: {@code agentscope-java/{version}; java/{java_version}; platform/{os}}
3737
*
38-
* <p>Example: {@code agentscope-java/1.0.9-SNAPSHOT; java/17.0.1; platform/Mac OS X}
38+
* <p>Example: {@code agentscope-java/1.0.10-SNAPSHOT; java/17.0.1; platform/Mac OS X}
3939
*
4040
* @return unified User-Agent string
4141
*/

agentscope-core/src/main/java/io/agentscope/core/tool/ToolMethodInvoker.java

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,8 @@ Mono<ToolResultBlock> invokeAsync(
8585
r ->
8686
converter.convert(
8787
r, extractGenericType(method)))
88-
.onErrorResume(
89-
e ->
90-
Mono.just(
91-
handleInvocationError(
92-
e instanceof Exception
93-
? (Exception) e
94-
: new RuntimeException(
95-
e)))));
88+
.onErrorResume(this::handleError))
89+
.onErrorResume(this::handleError);
9690

9791
} else if (returnType == Mono.class) {
9892
// Async method returning Mono: invoke and flatMap
@@ -108,14 +102,8 @@ r, extractGenericType(method)))
108102
.flatMap(
109103
mono ->
110104
mono.map(r -> converter.convert(r, extractGenericType(method)))
111-
.onErrorResume(
112-
e ->
113-
Mono.just(
114-
handleInvocationError(
115-
e instanceof Exception
116-
? (Exception) e
117-
: new RuntimeException(
118-
e)))));
105+
.onErrorResume(this::handleError))
106+
.onErrorResume(this::handleError);
119107

120108
} else {
121109
// Sync method: wrap in Mono.fromCallable
@@ -127,13 +115,7 @@ r, extractGenericType(method)))
127115
Object result = method.invoke(toolObject, args);
128116
return converter.convert(result, method.getGenericReturnType());
129117
})
130-
.onErrorResume(
131-
e ->
132-
Mono.just(
133-
handleInvocationError(
134-
e instanceof Exception
135-
? (Exception) e
136-
: new RuntimeException(e))));
118+
.onErrorResume(this::handleError);
137119
}
138120
}
139121

@@ -295,19 +277,23 @@ private Object convertSingleParameter(Parameter parameter, Map<String, Object> i
295277
return null;
296278
}
297279

298-
Class<?> paramType = parameter.getType();
280+
Class<?> rawType = parameter.getType();
281+
Type paramType = parameter.getParameterizedType();
299282

300-
// Direct assignment if types match
301-
if (paramType.isAssignableFrom(value.getClass())) {
283+
// Direct assignment only if:
284+
// 1. Raw types match, AND
285+
// 2. The parameter is not a parameterized type (no generic info to preserve)
286+
if (rawType.isAssignableFrom(value.getClass())
287+
&& !(paramType instanceof ParameterizedType)) {
302288
return value;
303289
}
304290

305-
// Try JsonCodec conversion first
291+
// Use JsonCodec conversion with full type information to preserve generics.
306292
try {
307293
return JsonUtils.getJsonCodec().convertValue(value, paramType);
308294
} catch (Exception e) {
309295
// Fallback to string-based conversion for primitives
310-
return convertFromString(value.toString(), paramType);
296+
return convertFromString(value.toString(), rawType);
311297
}
312298
}
313299

@@ -333,17 +319,31 @@ private Object convertFromString(String stringValue, Class<?> targetType) {
333319
return stringValue;
334320
}
335321

322+
/**
323+
* Reactive error handler for use with {@code onErrorResume}.
324+
*
325+
* <p>Delegates to {@link #handleInvocationError(Throwable)} and wraps the
326+
* result in {@code Mono.just(...)}.
327+
*
328+
* @param e the error from the reactive chain
329+
* @return Mono containing ToolResultBlock with error info
330+
* @throws ToolSuspendException if found in the exception chain
331+
*/
332+
private Mono<ToolResultBlock> handleError(Throwable e) {
333+
return Mono.just(handleInvocationError(e));
334+
}
335+
336336
/**
337337
* Handle invocation errors with informative messages.
338338
*
339339
* <p>Special handling for {@link ToolSuspendException}: if found in the exception chain,
340340
* it will be re-thrown to allow proper suspension handling by {@link ToolExecutor}.
341341
*
342-
* @param e the exception
342+
* @param e the throwable
343343
* @return ToolResultBlock with error message
344344
* @throws ToolSuspendException if found in the exception chain
345345
*/
346-
private ToolResultBlock handleInvocationError(Exception e) {
346+
private ToolResultBlock handleInvocationError(Throwable e) {
347347
// Check if the exception itself is ToolSuspendException
348348
if (e instanceof ToolSuspendException) {
349349
throw (ToolSuspendException) e;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.core.tool;
17+
18+
import com.github.victools.jsonschema.generator.FieldScope;
19+
import com.github.victools.jsonschema.generator.MemberScope;
20+
import com.github.victools.jsonschema.generator.Module;
21+
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
22+
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart;
23+
import java.util.stream.Stream;
24+
25+
/**
26+
* A victools JSON Schema {@link Module} that reads {@link ToolParam} annotations
27+
* to populate {@code "description"} and {@code "required"} in the generated schema.
28+
*
29+
* <p>By default, un-annotated fields are treated as required. Pass
30+
* {@link Option#PROPERTY_REQUIRED_FALSE_BY_DEFAULT} to invert this.
31+
*
32+
* @see ToolParam
33+
*/
34+
public class ToolSchemaModule implements Module {
35+
36+
private final boolean requiredByDefault;
37+
38+
/**
39+
* Creates a module with the given options.
40+
*
41+
* @param options pass {@link Option#PROPERTY_REQUIRED_FALSE_BY_DEFAULT}
42+
* to treat un-annotated fields as optional
43+
*/
44+
public ToolSchemaModule(Option... options) {
45+
this.requiredByDefault =
46+
Stream.of(options)
47+
.noneMatch(option -> option == Option.PROPERTY_REQUIRED_FALSE_BY_DEFAULT);
48+
}
49+
50+
@Override
51+
public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
52+
this.applyToConfigBuilder(builder.forFields());
53+
}
54+
55+
/**
56+
* Registers description resolver and required check.
57+
*/
58+
private void applyToConfigBuilder(SchemaGeneratorConfigPart<FieldScope> configPart) {
59+
configPart.withPropertyNameOverrideResolver(this::resolvePropertyName);
60+
configPart.withDescriptionResolver(this::resolveDescription);
61+
configPart.withRequiredCheck(this::checkRequired);
62+
}
63+
64+
/**
65+
* Returns the {@code @ToolParam} name, or {@code null} if absent.
66+
*
67+
* @param member the field or getter being processed
68+
* @return the property name, or {@code null}
69+
*/
70+
private String resolvePropertyName(FieldScope member) {
71+
ToolParam toolParam = member.getAnnotationConsideringFieldAndGetter(ToolParam.class);
72+
if (toolParam != null && hasText(toolParam.name())) {
73+
return toolParam.name();
74+
}
75+
return null;
76+
}
77+
78+
/**
79+
* Returns the {@code @ToolParam} description, or {@code null} if absent.
80+
*
81+
* @param member the field or getter being processed
82+
* @return the description text, or {@code null}
83+
*/
84+
private String resolveDescription(MemberScope<?, ?> member) {
85+
ToolParam toolParam = member.getAnnotationConsideringFieldAndGetter(ToolParam.class);
86+
if (toolParam != null && hasText(toolParam.description())) {
87+
return toolParam.description();
88+
}
89+
return null;
90+
}
91+
92+
/**
93+
* Returns {@code @ToolParam.required()}, falling back to {@link #requiredByDefault}.
94+
*
95+
* @param member the field or getter being processed
96+
* @return {@code true} if the field should be required
97+
*/
98+
private boolean checkRequired(MemberScope<?, ?> member) {
99+
ToolParam toolParam = member.getAnnotationConsideringFieldAndGetter(ToolParam.class);
100+
if (toolParam != null) {
101+
return toolParam.required();
102+
}
103+
return this.requiredByDefault;
104+
}
105+
106+
/**
107+
* Returns {@code true} if the string is non-null and non-blank.
108+
*/
109+
private static boolean hasText(String str) {
110+
return str != null && !str.isBlank();
111+
}
112+
113+
/**
114+
* Options for customizing module behavior.
115+
*/
116+
public enum Option {
117+
118+
/**
119+
* Treat un-annotated fields as optional instead of required.
120+
*/
121+
PROPERTY_REQUIRED_FALSE_BY_DEFAULT
122+
}
123+
}

agentscope-core/src/main/java/io/agentscope/core/tool/mcp/McpClientBuilder.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,7 @@ public Mono<McpClientWrapper> buildAsync() {
296296

297297
McpSchema.Implementation clientInfo =
298298
new McpSchema.Implementation(
299-
"agentscope-java",
300-
"AgentScope Java Framework",
301-
"1.0.9-SNAPSHOT");
299+
"agentscope-java", "AgentScope Java Framework", "1.0.10-SNAPSHOT");
302300

303301
McpSchema.ClientCapabilities clientCapabilities =
304302
McpSchema.ClientCapabilities.builder().build();
@@ -329,7 +327,7 @@ public McpClientWrapper buildSync() {
329327

330328
McpSchema.Implementation clientInfo =
331329
new McpSchema.Implementation(
332-
"agentscope-java", "AgentScope Java Framework", "1.0.9-SNAPSHOT");
330+
"agentscope-java", "AgentScope Java Framework", "1.0.10-SNAPSHOT");
333331

334332
McpSchema.ClientCapabilities clientCapabilities =
335333
McpSchema.ClientCapabilities.builder().build();

agentscope-core/src/main/java/io/agentscope/core/util/JacksonJsonCodec.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import com.fasterxml.jackson.core.JsonProcessingException;
2020
import com.fasterxml.jackson.core.type.TypeReference;
2121
import com.fasterxml.jackson.databind.DeserializationFeature;
22+
import com.fasterxml.jackson.databind.JavaType;
2223
import com.fasterxml.jackson.databind.ObjectMapper;
2324
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
25+
import java.lang.reflect.Type;
2426
import org.slf4j.Logger;
2527
import org.slf4j.LoggerFactory;
2628

@@ -137,4 +139,14 @@ public <T> T convertValue(Object from, TypeReference<T> toTypeRef) {
137139
throw new JsonException("Failed to convert value", e);
138140
}
139141
}
142+
143+
@Override
144+
public Object convertValue(Object from, Type toType) {
145+
try {
146+
JavaType javaType = objectMapper.getTypeFactory().constructType(toType);
147+
return objectMapper.convertValue(from, javaType);
148+
} catch (IllegalArgumentException e) {
149+
throw new JsonException("Failed to convert value to " + toType.getTypeName(), e);
150+
}
151+
}
140152
}

0 commit comments

Comments
 (0)