feat: 完善 Agent 标准交互与安全运行时
- 接入 AG-UI 运行投影、Turn 时间线和审批隔离 - 增加 Agent Skill 冻结绑定与运行时消费闭环 - 增加受控工作区、内置工具和私有 Artifact 生命周期
This commit is contained in:
@@ -39,6 +39,7 @@ public class ActionLogReporterProperties {
|
||||
"/images/**",
|
||||
"/favicon.ico",
|
||||
"/api/v1/agent/media/**",
|
||||
"/api/v1/agent/artifacts/*/content",
|
||||
"/actuator/**",
|
||||
"*.js",
|
||||
"*.css",
|
||||
|
||||
@@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
@@ -26,6 +27,8 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
|
||||
)
|
||||
public class ResponseCachingFilter implements Filter {
|
||||
|
||||
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
|
||||
|
||||
private final ActionLogReporterProperties logProperties;
|
||||
|
||||
/**
|
||||
@@ -121,18 +124,10 @@ public class ResponseCachingFilter implements Filter {
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean match(String path, String pattern) {
|
||||
if (pattern.equals("/**")) {
|
||||
return true;
|
||||
if (pattern.startsWith("/")) {
|
||||
return PATH_MATCHER.match(pattern, path);
|
||||
}
|
||||
if (pattern.endsWith("/**")) {
|
||||
String prefix = pattern.substring(0, pattern.length() - 3);
|
||||
return path.startsWith(prefix);
|
||||
}
|
||||
if (pattern.endsWith("*")) {
|
||||
String prefix = pattern.substring(0, pattern.length() - 1);
|
||||
return path.startsWith(prefix);
|
||||
}
|
||||
if (pattern.contains("*") && !pattern.contains("/**")) {
|
||||
if (pattern.contains("*")) {
|
||||
// 支持 *.js, *.css
|
||||
String p = pattern.replace("*", "").replace(".", "\\.");
|
||||
return path.matches(".*" + p + ".*");
|
||||
|
||||
@@ -4,7 +4,10 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.Test;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -32,4 +35,63 @@ public class ResponseCachingFilterTest {
|
||||
|
||||
verify(chain).doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Artifact 正文下载保持原始响应,避免异步正文被空缓存提前完成。
|
||||
*
|
||||
* @throws Exception Filter 执行失败
|
||||
*/
|
||||
@Test
|
||||
public void agentArtifactContentShouldBypassResponseCaching() throws Exception {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(request.getRequestURI()).thenReturn("/api/v1/agent/artifacts/artifact-1/content");
|
||||
|
||||
ResponseCachingFilter filter = new ResponseCachingFilter(new ActionLogReporterProperties());
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(chain).doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Artifact 元数据接口仍经过日志正文包装。
|
||||
*
|
||||
* @throws Exception Filter 执行失败
|
||||
*/
|
||||
@Test
|
||||
public void agentArtifactMetadataShouldRemainCacheable() throws Exception {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(request.getRequestURI()).thenReturn("/api/v1/agent/artifacts/artifact-1");
|
||||
|
||||
ResponseCachingFilter filter = new ResponseCachingFilter(new ActionLogReporterProperties());
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(chain).doFilter(
|
||||
isA(ContentCachingRequestWrapper.class), isA(ContentCachingResponseWrapper.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证近似路径不会误命中 Artifact 正文下载排除规则。
|
||||
*
|
||||
* @throws Exception Filter 执行失败
|
||||
*/
|
||||
@Test
|
||||
public void artifactContentChildPathShouldRemainCacheable() throws Exception {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(request.getRequestURI()).thenReturn("/api/v1/agent/artifacts/artifact-1/content/preview");
|
||||
|
||||
ResponseCachingFilter filter = new ResponseCachingFilter(new ActionLogReporterProperties());
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(chain).doFilter(
|
||||
isA(ContentCachingRequestWrapper.class), isA(ContentCachingResponseWrapper.class));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user