Skip to content

Commit 60ebe8f

Browse files
committed
added a2ui support
1 parent 329c6e9 commit 60ebe8f

File tree

19 files changed

+827
-18
lines changed

19 files changed

+827
-18
lines changed

src/main/java/io/github/vishalmysore/a2a/client/A2AAgent.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public CommonClientResponse remoteMethodCall(String query) {
7171
JsonRpcRequest request = createRequest("tasks/send", params);
7272

7373
// A2A v1.0: Add protocol version header and optional extension headers
74-
RestTemplate restTemplate = new RestTemplate();
74+
RestTemplate restTemplate = createRestTemplateWithTimeout();
7575
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
7676
headers.set("A2A-Version", "1.0");
7777

@@ -104,6 +104,17 @@ public CommonClientResponse remoteMethodCall(String query) {
104104
private JsonRpcRequest createRequest(String method, Object params) {
105105
return new JsonRpcRequest("2.0", method, params, UUID.randomUUID().toString());
106106
}
107+
108+
private RestTemplate createRestTemplateWithTimeout() {
109+
RestTemplate template = new RestTemplate();
110+
// Set timeout to 5 minutes for OpenAI API calls which can take time
111+
org.springframework.http.client.SimpleClientHttpRequestFactory factory =
112+
new org.springframework.http.client.SimpleClientHttpRequestFactory();
113+
factory.setConnectTimeout(60000); // 60 seconds connect timeout
114+
factory.setReadTimeout(300000); // 5 minutes read timeout
115+
template.setRequestFactory(factory);
116+
return template;
117+
}
107118

108119
@Override
109120
public CommonClientResponse remoteMethodCall(String remoteMethodName, String query) {

src/main/java/io/github/vishalmysore/a2a/client/A2ATaskClient.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,22 @@ public A2ATaskClient() {
3737

3838
public A2ATaskClient(String baseUrl) {
3939
this.baseUrl = baseUrl;
40-
this.restTemplate = new RestTemplate();
40+
this.restTemplate = createRestTemplateWithTimeout();
4141
this.objectMapper = new ObjectMapper();
4242
this.pendingTasks = Collections.synchronizedList(new ArrayList<>());
4343
this.completedTasks = Collections.synchronizedList(new ArrayList<>());
4444
}
45+
46+
private RestTemplate createRestTemplateWithTimeout() {
47+
RestTemplate template = new RestTemplate();
48+
// Set timeout to 5 minutes for OpenAI API calls which can take time
49+
org.springframework.http.client.SimpleClientHttpRequestFactory factory =
50+
new org.springframework.http.client.SimpleClientHttpRequestFactory();
51+
factory.setConnectTimeout(60000); // 60 seconds connect timeout
52+
factory.setReadTimeout(300000); // 5 minutes read timeout
53+
template.setRequestFactory(factory);
54+
return template;
55+
}
4556

4657
private JsonRpcRequest createRequest(String method, Object params) {
4758
return new JsonRpcRequest("2.0", method, params, UUID.randomUUID().toString());

src/main/java/io/github/vishalmysore/a2a/domain/DataPart.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.vishalmysore.a2a.domain;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
45
import lombok.ToString;
56

67
import java.util.HashMap;
@@ -9,6 +10,7 @@
910
/**
1011
* A2A v1.0: DataPart serializes as {"data": {...}}
1112
* No "kind" or "type" field in JSON.
13+
* However, accepts "kind" during deserialization for compatibility with a2a-js SDK.
1214
* For A2UI support, set metadata mimeType to "application/json+a2ui"
1315
*/
1416
@ToString
@@ -18,6 +20,8 @@ public class DataPart extends Part {
1820
private String id;
1921
@JsonIgnore
2022
private String type = "data";
23+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
24+
private String kind; // Accept during deserialization, ignore during serialization
2125

2226

2327
private Map<String, Object> data;

src/main/java/io/github/vishalmysore/a2a/domain/FilePart.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package io.github.vishalmysore.a2a.domain;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
45
import lombok.ToString;
56

67
import java.util.Map;
78

89
/**
910
* A2A v1.0: FilePart serializes as {"file": {...}}
1011
* No "kind" or "type" field in JSON.
12+
* However, accepts "kind" during deserialization for compatibility with a2a-js SDK.
1113
*/
1214
@ToString
1315
public class FilePart extends Part {
@@ -16,6 +18,8 @@ public class FilePart extends Part {
1618
private String id;
1719
@JsonIgnore
1820
private String type = "file";
21+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
22+
private String kind; // Accept during deserialization, ignore during serialization
1923

2024
private FileContent file;
2125

src/main/java/io/github/vishalmysore/a2a/domain/Part.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
* - TextPart: {"text": "..."}
1313
* - FilePart: {"file": {...}}
1414
* - DataPart: {"data": {...}}
15+
*
16+
* Jackson uses DEDUCTION to determine which Part type based on presence of specific fields.
1517
*/
1618
@JsonTypeInfo(
17-
use = JsonTypeInfo.Id.NAME,
18-
include = JsonTypeInfo.As.WRAPPER_OBJECT,
19-
property = "type"
19+
use = JsonTypeInfo.Id.DEDUCTION
2020
)
2121
@JsonSubTypes({
22-
@JsonSubTypes.Type(value = TextPart.class, name = "text"),
23-
@JsonSubTypes.Type(value = FilePart.class, name = "file"),
24-
@JsonSubTypes.Type(value = DataPart.class, name = "data")
22+
@JsonSubTypes.Type(value = TextPart.class),
23+
@JsonSubTypes.Type(value = FilePart.class),
24+
@JsonSubTypes.Type(value = DataPart.class)
2525
})
2626
public abstract class Part {
2727

src/main/java/io/github/vishalmysore/a2a/domain/TextPart.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.vishalmysore.a2a.domain;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
45
import lombok.ToString;
56

67
import java.util.HashMap;
@@ -9,6 +10,7 @@
910
/**
1011
* A2A v1.0: TextPart serializes as {"text": "..."}
1112
* No "kind" or "type" field in JSON.
13+
* However, accepts "kind" during deserialization for compatibility with a2a-js SDK.
1214
*/
1315
@ToString
1416
public class TextPart extends Part {
@@ -17,6 +19,8 @@ public class TextPart extends Part {
1719
private String id;
1820
@JsonIgnore
1921
private String type = "text";
22+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
23+
private String kind; // Accept during deserialization, ignore during serialization
2024
private String text;
2125

2226

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.github.vishalmysore.a2a.server;
22

33
import io.github.vishalmysore.a2a.domain.JsonRpcRequest;
4+
import jakarta.servlet.http.HttpServletRequest;
45
import org.springframework.web.bind.annotation.RequestBody;
56

67
public interface A2ARPCController {
78
default void preProcessing(String method, Object params) {};
89
default void postProcessing(String method, Object params) {};
910
public Object handleRpc(JsonRpcRequest request);
11+
public Object handleRpc(JsonRpcRequest request, HttpServletRequest httpRequest);
1012
public A2ATaskController getTaskController();
1113
}

src/main/java/io/github/vishalmysore/a2a/server/DyanamicTaskContoller.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
import com.t4a.JsonUtils;
55
import com.t4a.detect.ActionCallback;
66
import com.t4a.predict.PredictionLoader;
7-
import com.t4a.processor.AIProcessingException;
8-
import com.t4a.processor.AIProcessor;
9-
import com.t4a.processor.GeminiV2ActionProcessor;
10-
import com.t4a.processor.OpenAiActionProcessor;
7+
import com.t4a.processor.*;
118
import com.t4a.processor.scripts.BaseScriptProcessor;
129
import com.t4a.processor.scripts.ScriptProcessor;
1310
import com.t4a.processor.scripts.ScriptResult;
@@ -16,6 +13,7 @@
1613
import com.t4a.transform.PromptTransformer;
1714
import io.github.vishalmysore.a2a.domain.*;
1815

16+
import io.github.vishalmysore.common.CallBackType;
1917
import lombok.Getter;
2018
import lombok.extern.java.Log;
2119

@@ -157,6 +155,12 @@ private void processParts(TaskSendParams taskSendParams, Task task, String taskI
157155
}
158156
}
159157

158+
public DataPart createA2uiDataPart(Map<String, Object> a2uiData) {
159+
// Create metadata map with the A2UI MIME type
160+
161+
// Return new DataPart with the data and metadata
162+
return DataPart.createA2UIPart(a2uiData);
163+
}
160164
private void processTextPart(TextPart textPart, Task task, ActionCallback actionCallback) throws AIProcessingException {
161165
if (!"text".equals(textPart.getType())) {
162166
return;
@@ -172,7 +176,14 @@ private void processTextPart(TextPart textPart, Task task, ActionCallback action
172176

173177
private void processWithCallback(String text, Task task, ActionCallback actionCallback) throws AIProcessingException {
174178
actionCallback.setContext(task);
175-
getBaseProcessor().processSingleAction(text, actionCallback);
179+
if(actionCallback.getType().equals(CallBackType.A2UI.name())) {
180+
Object obj = getBaseProcessor().processSingleAction(text,null, new LoggingHumanDecision(),new LogginggExplainDecision(),actionCallback);
181+
TaskStatus status = task.getStatus();
182+
status.setState(TaskState.COMPLETED);
183+
status.getMessage().getParts().add(createA2uiDataPart((Map<String, Object>) obj));
184+
} else {
185+
getBaseProcessor().processSingleAction(text, actionCallback);
186+
}
176187
}
177188

178189
private void processWithoutCallback(String text, Task task) throws AIProcessingException {

src/main/java/io/github/vishalmysore/common/A2AActionCallBack.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
import com.t4a.detect.ActionCallback;
44
import com.t4a.detect.ActionState;
5+
import io.github.vishalmysore.a2a.domain.DataPart;
6+
import io.github.vishalmysore.a2a.domain.Message;
57
import io.github.vishalmysore.a2a.domain.Task;
68
import io.github.vishalmysore.a2a.domain.TaskState;
9+
import io.github.vishalmysore.a2a.domain.TaskStatus;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.Map;
714

815
/**
916
* A2AActionCallBack is an interface that extends ActionCallback.
@@ -43,4 +50,42 @@ public String getType() {
4350
public void sendtStatus(String status, ActionState state) {
4451
((Task) getContext()).setDetailedAndMessage(TaskState.forValue(state.getValue()), status );
4552
}
53+
54+
/**
55+
* Add A2UI content to the A2A Task response.
56+
* This allows A2A agents to return UI components to clients that support A2UI extension.
57+
* The A2UI content is added to the task's status message as a DataPart.
58+
*
59+
* @param a2uiMessage A2UI message structure (surfaceUpdate, beginRendering, etc.)
60+
* @see <a href="https://a2ui.org/specification/v0.8-a2ui/">A2UI Specification</a>
61+
*/
62+
public void addA2UIContent(Map<String, Object> a2uiMessage) {
63+
Task task = (Task) getContext();
64+
TaskStatus taskStatus = task.getStatus();
65+
66+
// Get or create the status message
67+
Message message;
68+
if (taskStatus != null && taskStatus.getMessage() != null) {
69+
message = taskStatus.getMessage();
70+
} else {
71+
message = new Message();
72+
message.setRole("agent");
73+
if (taskStatus == null) {
74+
taskStatus = new TaskStatus();
75+
task.setStatus(taskStatus);
76+
}
77+
taskStatus.setMessage(message);
78+
}
79+
80+
// Get or create parts list
81+
List parts = message.getParts();
82+
if (parts == null) {
83+
parts = new ArrayList<>();
84+
message.setParts(parts);
85+
}
86+
87+
// Create and add A2UI DataPart
88+
DataPart dataPart = DataPart.createA2UIPart(a2uiMessage);
89+
parts.add(dataPart);
90+
}
4691
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.github.vishalmysore.common;
2+
3+
public class A2AUICallback extends A2AActionCallBack {
4+
@Override
5+
public String getType() {
6+
return CallBackType.A2UI.name();
7+
}
8+
}

0 commit comments

Comments
 (0)