feat: 标准化 Agent AG-UI 与审批运行时
- 新增 AG-UI 事件投影与协议编码模块 - 支持 Turn 级审批作用域和受信任动态审批策略
This commit is contained in:
36
easy-agents-agui/pom.xml
Normal file
36
easy-agents-agui/pom.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<name>easy-agents-agui</name>
|
||||
<artifactId>easy-agents-agui</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.easyagents</groupId>
|
||||
<artifactId>easy-agents-agent-runtime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.agentscope</groupId>
|
||||
<artifactId>agentscope-extensions-agui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.easyagents.agui;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.agentscope.core.agui.model.AguiMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* AgentScope 1.0.12 尚未提供的 AG-UI 标准线级事件。
|
||||
*
|
||||
* <p>该补充层只覆盖当前官方扩展缺失的标准事件,不复制 AG-UI 事件枚举。</p>
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = AguiExtendedEvent.RunError.class, name = "RUN_ERROR"),
|
||||
@JsonSubTypes.Type(value = AguiExtendedEvent.MessagesSnapshot.class, name = "MESSAGES_SNAPSHOT")
|
||||
})
|
||||
public sealed interface AguiExtendedEvent
|
||||
permits AguiExtendedEvent.RunError, AguiExtendedEvent.MessagesSnapshot {
|
||||
|
||||
/**
|
||||
* 运行失败事件。
|
||||
*
|
||||
* @param threadId AG-UI 线程 ID
|
||||
* @param runId AG-UI 运行 ID
|
||||
* @param message 可安全展示的错误消息
|
||||
* @param code 稳定错误码
|
||||
*/
|
||||
record RunError(String threadId, String runId, String message, String code)
|
||||
implements AguiExtendedEvent {
|
||||
|
||||
/**
|
||||
* 创建运行失败事件。
|
||||
*
|
||||
* @param threadId AG-UI 线程 ID
|
||||
* @param runId AG-UI 运行 ID
|
||||
* @param message 可安全展示的错误消息
|
||||
* @param code 稳定错误码
|
||||
*/
|
||||
@JsonCreator
|
||||
public RunError(
|
||||
@JsonProperty("threadId") String threadId,
|
||||
@JsonProperty("runId") String runId,
|
||||
@JsonProperty("message") String message,
|
||||
@JsonProperty("code") String code) {
|
||||
this.threadId = Objects.requireNonNull(threadId, "threadId cannot be null");
|
||||
this.runId = Objects.requireNonNull(runId, "runId cannot be null");
|
||||
this.message = Objects.requireNonNull(message, "message cannot be null");
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息全量快照事件。
|
||||
*
|
||||
* @param threadId AG-UI 线程 ID
|
||||
* @param runId AG-UI 运行 ID
|
||||
* @param messages 消息快照
|
||||
*/
|
||||
record MessagesSnapshot(String threadId, String runId, List<AguiMessage> messages)
|
||||
implements AguiExtendedEvent {
|
||||
|
||||
/**
|
||||
* 创建消息全量快照事件。
|
||||
*
|
||||
* @param threadId AG-UI 线程 ID
|
||||
* @param runId AG-UI 运行 ID
|
||||
* @param messages 消息快照
|
||||
*/
|
||||
@JsonCreator
|
||||
public MessagesSnapshot(
|
||||
@JsonProperty("threadId") String threadId,
|
||||
@JsonProperty("runId") String runId,
|
||||
@JsonProperty("messages") List<AguiMessage> messages) {
|
||||
this.threadId = Objects.requireNonNull(threadId, "threadId cannot be null");
|
||||
this.runId = Objects.requireNonNull(runId, "runId cannot be null");
|
||||
this.messages = messages == null ? List.of() : List.copyOf(messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.easyagents.agui;
|
||||
|
||||
import io.agentscope.core.agui.AguiException;
|
||||
import io.agentscope.core.util.JsonException;
|
||||
import io.agentscope.core.util.JsonUtils;
|
||||
|
||||
/**
|
||||
* 将 AG-UI 事件编码为 JSON 或 SSE 数据帧。
|
||||
*
|
||||
* <p>编码器无可变状态,可安全跨请求复用。</p>
|
||||
*/
|
||||
public final class AguiProtocolEventEncoder {
|
||||
|
||||
/**
|
||||
* 编码为 JSON。
|
||||
*
|
||||
* @param event AG-UI 官方事件或补充标准事件
|
||||
* @return JSON 文本
|
||||
* @throws AguiException.EncodingException 序列化失败时抛出
|
||||
*/
|
||||
public String encodeToJson(Object event) {
|
||||
try {
|
||||
return JsonUtils.getJsonCodec().toJson(event);
|
||||
} catch (JsonException exception) {
|
||||
throw new AguiException.EncodingException("Failed to encode AG-UI event", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为 SSE data 帧。
|
||||
*
|
||||
* @param event AG-UI 官方事件或补充标准事件
|
||||
* @return 完整 SSE data 帧
|
||||
* @throws AguiException.EncodingException 序列化失败时抛出
|
||||
*/
|
||||
public String encode(Object event) {
|
||||
return "data: " + encodeToJson(event) + "\n\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package com.easyagents.agui;
|
||||
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import io.agentscope.core.agui.event.AguiEvent;
|
||||
import io.agentscope.core.util.JsonException;
|
||||
import io.agentscope.core.util.JsonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 将 Easy-Agents 中立运行时事件有序投影为 AG-UI 事件。
|
||||
*
|
||||
* <p>实例绑定单个 run 且非线程安全。调用方应按运行顺序串行调用 {@link #project(AgentRuntimeEvent)}。</p>
|
||||
*/
|
||||
public final class AguiRuntimeEventProjector {
|
||||
|
||||
private static final String DEFAULT_ERROR_MESSAGE = "Agent runtime failed.";
|
||||
|
||||
/** AG-UI thread ID。 */
|
||||
private final String threadId;
|
||||
/** AG-UI run ID。 */
|
||||
private final String runId;
|
||||
/** 已开始但尚未收到结果的工具调用。 */
|
||||
private final Set<String> knownToolCallIds = new LinkedHashSet<>();
|
||||
|
||||
private boolean runStarted;
|
||||
private boolean terminated;
|
||||
private String openMessageId;
|
||||
private String openReasoningMessageId;
|
||||
private long generatedMessageSequence;
|
||||
|
||||
/**
|
||||
* 创建不含业务 Custom Event 的投影器。
|
||||
*
|
||||
* @param threadId AG-UI thread ID
|
||||
* @param runId AG-UI run ID
|
||||
*/
|
||||
public AguiRuntimeEventProjector(String threadId, String runId) {
|
||||
this.threadId = requireText(threadId, "threadId");
|
||||
this.runId = requireText(runId, "runId");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按输入顺序投影一条运行时事件。
|
||||
*
|
||||
* @param event Easy-Agents 运行时事件
|
||||
* @return 零到多条 AG-UI 官方或补充标准事件
|
||||
*/
|
||||
public List<Object> project(AgentRuntimeEvent event) {
|
||||
if (event == null || event.getEventType() == null || terminated) {
|
||||
return List.of();
|
||||
}
|
||||
List<Object> output = new ArrayList<>();
|
||||
switch (event.getEventType()) {
|
||||
case STARTED -> startRun(output);
|
||||
case MESSAGE_DELTA -> projectMessageDelta(event, output);
|
||||
case REASONING_STARTED -> startReasoning(event, output);
|
||||
case REASONING_DELTA -> projectReasoningDelta(event, output);
|
||||
case REASONING_COMPLETED -> closeReasoning(output);
|
||||
case TOOL_CALL -> projectToolCall(event, output);
|
||||
case TOOL_RESULT -> projectToolResult(event, output);
|
||||
case COMPLETED -> finishSuccessfully(output);
|
||||
case FAILED -> finishWithError(event, "AGENT_RUNTIME_FAILED", output);
|
||||
case CANCELLED -> finishWithError(event, "RUN_CANCELLED", output);
|
||||
default -> {
|
||||
// EasyFlow 等上层业务扩展由各自协议边界映射为 CUSTOM,通用模块保持业务无关。
|
||||
}
|
||||
}
|
||||
return List.copyOf(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前投影是否已经产生协议终态。
|
||||
*
|
||||
* @return 已产生 RUN_FINISHED 或 RUN_ERROR 时为 true
|
||||
*/
|
||||
public boolean isTerminated() {
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private void startRun(List<Object> output) {
|
||||
if (!runStarted) {
|
||||
output.add(new AguiEvent.RunStarted(threadId, runId));
|
||||
runStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void projectMessageDelta(AgentRuntimeEvent event, List<Object> output) {
|
||||
startRun(output);
|
||||
String messageId = eventMessageId(event, "assistant");
|
||||
if (!Objects.equals(openMessageId, messageId)) {
|
||||
closeMessage(output);
|
||||
output.add(new AguiEvent.TextMessageStart(threadId, runId, messageId, "assistant"));
|
||||
openMessageId = messageId;
|
||||
}
|
||||
String delta = stringValue(event.getPayload(), "text");
|
||||
if (!delta.isEmpty()) {
|
||||
output.add(new AguiEvent.TextMessageContent(threadId, runId, messageId, delta));
|
||||
}
|
||||
}
|
||||
|
||||
private void startReasoning(AgentRuntimeEvent event, List<Object> output) {
|
||||
startRun(output);
|
||||
if (openReasoningMessageId != null) {
|
||||
return;
|
||||
}
|
||||
openReasoningMessageId = eventMessageId(event, "reasoning");
|
||||
output.add(new AguiEvent.ReasoningMessageStart(
|
||||
threadId, runId, openReasoningMessageId, "reasoning"));
|
||||
}
|
||||
|
||||
private void projectReasoningDelta(AgentRuntimeEvent event, List<Object> output) {
|
||||
startReasoning(event, output);
|
||||
String delta = stringValue(event.getPayload(), "reasoning");
|
||||
if (!delta.isEmpty()) {
|
||||
output.add(new AguiEvent.ReasoningMessageContent(
|
||||
threadId, runId, openReasoningMessageId, delta));
|
||||
}
|
||||
}
|
||||
|
||||
private void projectToolCall(AgentRuntimeEvent event, List<Object> output) {
|
||||
startRun(output);
|
||||
String toolCallId = firstText(event.getToolCallId(), stringValue(event.getPayload(), "toolCallId"));
|
||||
if (toolCallId == null || !knownToolCallIds.add(toolCallId)) {
|
||||
return;
|
||||
}
|
||||
String toolName = firstText(
|
||||
stringValue(event.getPayload(), "toolName"),
|
||||
stringValue(event.getPayload(), "name"),
|
||||
"tool");
|
||||
output.add(new AguiEvent.ToolCallStart(threadId, runId, toolCallId, toolName));
|
||||
output.add(new AguiEvent.ToolCallArgs(
|
||||
threadId, runId, toolCallId, jsonValue(event.getPayload().get("input"))));
|
||||
output.add(new AguiEvent.ToolCallEnd(threadId, runId, toolCallId));
|
||||
}
|
||||
|
||||
private void projectToolResult(AgentRuntimeEvent event, List<Object> output) {
|
||||
startRun(output);
|
||||
String toolCallId = firstText(event.getToolCallId(), stringValue(event.getPayload(), "toolCallId"));
|
||||
if (toolCallId == null || !knownToolCallIds.contains(toolCallId)) {
|
||||
return;
|
||||
}
|
||||
String messageId = eventMessageId(event, "tool-" + toolCallId);
|
||||
String content = nullToEmpty(firstText(
|
||||
stringValue(event.getPayload(), "text"),
|
||||
event.getPayload().containsKey("result")
|
||||
? jsonValue(event.getPayload().get("result"))
|
||||
: null));
|
||||
output.add(new AguiEvent.ToolCallResult(
|
||||
threadId, runId, toolCallId, content, "tool", messageId));
|
||||
}
|
||||
|
||||
private void finishSuccessfully(List<Object> output) {
|
||||
startRun(output);
|
||||
closeOpenFragments(output);
|
||||
output.add(new AguiEvent.RunFinished(threadId, runId));
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
private void finishWithError(AgentRuntimeEvent event, String code, List<Object> output) {
|
||||
startRun(output);
|
||||
closeOpenFragments(output);
|
||||
String message = firstText(
|
||||
stringValue(event.getPayload(), "message"),
|
||||
stringValue(event.getPayload(), "reason"),
|
||||
DEFAULT_ERROR_MESSAGE);
|
||||
output.add(new AguiExtendedEvent.RunError(threadId, runId, message, code));
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
private void closeOpenFragments(List<Object> output) {
|
||||
closeReasoning(output);
|
||||
closeMessage(output);
|
||||
}
|
||||
|
||||
private void closeMessage(List<Object> output) {
|
||||
if (openMessageId != null) {
|
||||
output.add(new AguiEvent.TextMessageEnd(threadId, runId, openMessageId));
|
||||
openMessageId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void closeReasoning(List<Object> output) {
|
||||
if (openReasoningMessageId != null) {
|
||||
output.add(new AguiEvent.ReasoningMessageEnd(
|
||||
threadId, runId, openReasoningMessageId));
|
||||
openReasoningMessageId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String eventMessageId(AgentRuntimeEvent event, String suffix) {
|
||||
String messageId = firstText(
|
||||
event.getMessageId(),
|
||||
event.getMessage() == null ? null : event.getMessage().getMessageId());
|
||||
if (messageId != null) {
|
||||
return messageId;
|
||||
}
|
||||
generatedMessageSequence++;
|
||||
return runId + "-" + suffix + "-" + generatedMessageSequence;
|
||||
}
|
||||
|
||||
private static String stringValue(Map<String, Object> payload, String key) {
|
||||
if (payload == null) {
|
||||
return "";
|
||||
}
|
||||
Object value = payload.get(key);
|
||||
return value instanceof String text ? text : "";
|
||||
}
|
||||
|
||||
private static String jsonValue(Object value) {
|
||||
if (value == null) {
|
||||
return "{}";
|
||||
}
|
||||
if (value instanceof String text) {
|
||||
return text;
|
||||
}
|
||||
try {
|
||||
return JsonUtils.getJsonCodec().toJson(value);
|
||||
} catch (JsonException exception) {
|
||||
throw new IllegalArgumentException("Failed to encode AG-UI payload", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static String firstText(String... values) {
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isBlank()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String requireText(String value, String name) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(name + " cannot be blank");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.easyagents.agui;
|
||||
|
||||
import io.agentscope.core.agui.event.AguiEvent;
|
||||
import io.agentscope.core.agui.model.AguiMessage;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link AguiProtocolEventEncoder} 的线级协议测试。
|
||||
*/
|
||||
public class AguiProtocolEventEncoderTest {
|
||||
|
||||
private final AguiProtocolEventEncoder encoder = new AguiProtocolEventEncoder();
|
||||
|
||||
/**
|
||||
* 验证 AgentScope 官方事件保留 AG-UI type 字段。
|
||||
*/
|
||||
@Test
|
||||
public void shouldEncodeOfficialEvent() {
|
||||
String json = encoder.encodeToJson(new AguiEvent.RunStarted("thread-1", "run-1"));
|
||||
|
||||
Assert.assertTrue(json.contains("\"type\":\"RUN_STARTED\""));
|
||||
Assert.assertTrue(json.contains("\"threadId\":\"thread-1\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证补充的失败和消息快照事件使用现代 AG-UI 标准事件名。
|
||||
*/
|
||||
@Test
|
||||
public void shouldEncodeExtendedStandardEvents() {
|
||||
String error = encoder.encodeToJson(
|
||||
new AguiExtendedEvent.RunError("thread-1", "run-1", "boom", "FAILED"));
|
||||
String snapshot = encoder.encodeToJson(new AguiExtendedEvent.MessagesSnapshot(
|
||||
"thread-1", "run-1", List.of(AguiMessage.userMessage("message-1", "hello"))));
|
||||
|
||||
Assert.assertTrue(error.contains("\"type\":\"RUN_ERROR\""));
|
||||
Assert.assertTrue(error.contains("\"code\":\"FAILED\""));
|
||||
Assert.assertTrue(snapshot.contains("\"type\":\"MESSAGES_SNAPSHOT\""));
|
||||
Assert.assertTrue(snapshot.contains("\"role\":\"user\""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.easyagents.agui;
|
||||
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import io.agentscope.core.agui.event.AguiEvent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link AguiRuntimeEventProjector} 的协议顺序与终态测试。
|
||||
*/
|
||||
public class AguiRuntimeEventProjectorTest {
|
||||
|
||||
/**
|
||||
* 验证文本、推理、工具和成功终态按 AG-UI 顺序投影。
|
||||
*/
|
||||
@Test
|
||||
public void shouldProjectSuccessfulRunInProtocolOrder() {
|
||||
AguiRuntimeEventProjector projector = new AguiRuntimeEventProjector("thread-1", "run-1");
|
||||
List<Object> events = new ArrayList<>();
|
||||
|
||||
events.addAll(projector.project(event(AgentRuntimeEventType.STARTED, null, null, Map.of())));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.REASONING_STARTED, "reasoning-1", null, Map.of())));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.REASONING_DELTA, "reasoning-1", null, Map.of("reasoning", "分析"))));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.REASONING_COMPLETED, "reasoning-1", null, Map.of())));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.MESSAGE_DELTA, "message-1", null, Map.of("text", "你好"))));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.TOOL_CALL, null, "tool-1",
|
||||
Map.of("toolName", "search", "input", Map.of("q", "AG-UI")))));
|
||||
events.addAll(projector.project(event(
|
||||
AgentRuntimeEventType.TOOL_RESULT, null, "tool-1", Map.of("text", "done"))));
|
||||
events.addAll(projector.project(event(AgentRuntimeEventType.COMPLETED, null, null, Map.of())));
|
||||
|
||||
Assert.assertEquals(List.of(
|
||||
"RunStarted",
|
||||
"ReasoningMessageStart",
|
||||
"ReasoningMessageContent",
|
||||
"ReasoningMessageEnd",
|
||||
"TextMessageStart",
|
||||
"TextMessageContent",
|
||||
"ToolCallStart",
|
||||
"ToolCallArgs",
|
||||
"ToolCallEnd",
|
||||
"ToolCallResult",
|
||||
"TextMessageEnd",
|
||||
"RunFinished"), events.stream().map(value -> value.getClass().getSimpleName()).toList());
|
||||
Assert.assertEquals(
|
||||
"reasoning", ((AguiEvent.ReasoningMessageStart) events.get(1)).role());
|
||||
Assert.assertTrue(projector.isTerminated());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证失败终态不会追加成功事件,终态后的迟到事件会被丢弃。
|
||||
*/
|
||||
@Test
|
||||
public void shouldEmitSingleErrorTerminalAndIgnoreLateEvents() {
|
||||
AguiRuntimeEventProjector projector = new AguiRuntimeEventProjector("thread-1", "run-1");
|
||||
|
||||
List<Object> failed = projector.project(event(
|
||||
AgentRuntimeEventType.FAILED, null, null, Map.of("message", "boom")));
|
||||
List<Object> late = projector.project(event(AgentRuntimeEventType.COMPLETED, null, null, Map.of()));
|
||||
|
||||
Assert.assertEquals(2, failed.size());
|
||||
Assert.assertTrue(failed.get(0) instanceof AguiEvent.RunStarted);
|
||||
Assert.assertEquals(
|
||||
new AguiExtendedEvent.RunError("thread-1", "run-1", "boom", "AGENT_RUNTIME_FAILED"),
|
||||
failed.get(1));
|
||||
Assert.assertTrue(late.isEmpty());
|
||||
}
|
||||
|
||||
private static AgentRuntimeEvent event(
|
||||
AgentRuntimeEventType type,
|
||||
String messageId,
|
||||
String toolCallId,
|
||||
Map<String, Object> payload) {
|
||||
AgentRuntimeEvent event = AgentRuntimeEvent.of(type);
|
||||
event.setMessageId(messageId);
|
||||
event.setToolCallId(toolCallId);
|
||||
event.setPayload(payload);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user