diff --git a/apps/java-spring-ai-agents/aiagent/pom.xml b/apps/java-spring-ai-agents/aiagent/pom.xml
index fe6b0b2b..ea004173 100644
--- a/apps/java-spring-ai-agents/aiagent/pom.xml
+++ b/apps/java-spring-ai-agents/aiagent/pom.xml
@@ -53,14 +53,14 @@
org.springaicommunity
- spring-ai-bedrock-agentcore-codeinterpreter
- 1.0.0-RC6
+ spring-ai-agentcore-code-interpreter
+ 1.0.0-RC8
org.springaicommunity
- spring-ai-bedrock-agentcore-browser
- 1.0.0-RC6
+ spring-ai-agentcore-browser
+ 1.0.0-RC8
@@ -79,14 +79,14 @@
org.springaicommunity
- spring-ai-bedrock-agentcore-memory
- 1.0.0-RC6
+ spring-ai-agentcore-memory
+ 1.0.0-RC8
org.springaicommunity
- spring-ai-bedrock-agentcore-runtime-starter
- 1.0.0-RC6
+ spring-ai-agentcore-runtime-starter
+ 1.0.0-RC8
org.springframework.boot
diff --git a/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ChatService.java b/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ChatService.java
index 16f88064..19f218a4 100644
--- a/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ChatService.java
+++ b/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ChatService.java
@@ -1,43 +1,36 @@
package com.example.agent;
-import java.util.ArrayList;
-import java.util.List;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.agentcore.annotation.AgentCoreInvocation;
+import org.springaicommunity.agentcore.artifacts.ArtifactStore;
+import org.springaicommunity.agentcore.artifacts.GeneratedFile;
+import org.springaicommunity.agentcore.artifacts.SessionConstants;
+import org.springaicommunity.agentcore.browser.BrowserArtifacts;
import org.springaicommunity.agentcore.context.AgentCoreContext;
-import org.springaicommunity.agentcore.context.AgentCoreHeaders;
-import org.springaicommunity.agentcore.browser.BrowserScreenshot;
-import org.springaicommunity.agentcore.browser.BrowserScreenshotStore;
-import org.springaicommunity.agentcore.browser.BrowserTools;
-import org.springaicommunity.agentcore.codeinterpreter.CodeInterpreterFileStore;
-import org.springaicommunity.agentcore.codeinterpreter.CodeInterpreterTools;
-import org.springaicommunity.agentcore.codeinterpreter.GeneratedFile;
-import org.springframework.ai.tool.ToolCallbackProvider;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.annotation.Value;
+import org.springaicommunity.agentcore.memory.longterm.AgentCoreMemory;
import org.springframework.ai.chat.client.ChatClient;
-import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
-import org.springframework.ai.chat.memory.ChatMemory;
-import org.springframework.ai.chat.memory.ChatMemoryRepository;
-import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
-import org.springframework.ai.vectorstore.VectorStore;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import reactor.core.publisher.Flux;
-import tools.jackson.databind.JsonNode;
-import tools.jackson.databind.json.JsonMapper;
+import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
+import org.springframework.ai.tool.ToolCallbackProvider;
+import org.springframework.ai.vectorstore.VectorStore;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
+import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
+import reactor.core.publisher.Flux;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.ArrayList;
import java.util.Base64;
+import java.util.List;
record ChatRequest(String prompt, String fileBase64, String fileName) {
public boolean hasFile() {
@@ -57,43 +50,32 @@ public class ChatService {
private final JsonMapper jsonMapper = JsonMapper.builder().build();
- private final BrowserScreenshotStore screenshotStore;
- private final CodeInterpreterFileStore fileStore;
+ private final ArtifactStore browserArtifactStore;
+ private final ArtifactStore codeInterpreterArtifactStore;
private static final String SYSTEM_PROMPT = """
You are a helpful AI agent for travel and expense management.
Be friendly, helpful, and concise in your responses.
""";
- public ChatService(@Autowired(required = false) ChatMemoryRepository memoryRepository,
- @Autowired(required = false) List ltmAdvisors,
- @Autowired(required = false) VectorStore kbVectorStore,
- @Autowired(required = false) WebGroundingTools webGroundingTools,
- ContextAdvisor contextAdvisor,
- @Autowired(required = false) @Qualifier("browserToolCallbackProvider") ToolCallbackProvider browserTools,
- @Autowired(required = false) BrowserScreenshotStore browserScreenshotStore,
- @Autowired(required = false) @Qualifier("codeInterpreterToolCallbackProvider") ToolCallbackProvider codeInterpreterTools,
- @Autowired(required = false) CodeInterpreterFileStore codeInterpreterFileStore,
- @Autowired(required = false) @Qualifier("mcpToolCallbacks") ToolCallbackProvider mcpTools,
- ChatModel chatModel,
- @Value("${app.ai.document.model:global.anthropic.claude-opus-4-5-20251101-v1:0}") String documentModel,
- ChatClient.Builder chatClientBuilder) {
+ public ChatService(AgentCoreMemory agentCoreMemory,
+ VectorStore kbVectorStore,
+ WebGroundingTools webGroundingTools,
+ ContextAdvisor contextAdvisor,
+ @Qualifier("browserToolCallbackProvider") ToolCallbackProvider browserTools,
+ @Qualifier("codeInterpreterToolCallbackProvider") ToolCallbackProvider codeInterpreterTools,
+ @Qualifier("browserArtifactStore") ArtifactStore browserArtifactStore,
+ @Qualifier("codeInterpreterArtifactStore") ArtifactStore codeInterpreterArtifactStore,
+ @Qualifier("mcpToolCallbacks") ToolCallbackProvider mcpTools,
+ ChatModel chatModel,
+ @Value("${app.ai.document.model:global.anthropic.claude-opus-4-5-20251101-v1:0}") String documentModel,
+ ChatClient.Builder chatClientBuilder) {
List advisors = new ArrayList<>();
- // Short-Term Memory (STM)
- if (memoryRepository != null) {
- ChatMemory chatMemory = MessageWindowChatMemory.builder()
- .chatMemoryRepository(memoryRepository)
- .build();
- advisors.add(MessageChatMemoryAdvisor.builder(chatMemory).build());
- logger.info("Memory enabled");
- }
-
- // Long-Term Memory (LTM)
- if (ltmAdvisors != null && !ltmAdvisors.isEmpty()) {
- advisors.addAll(ltmAdvisors);
- logger.info("Advisors enabled: {} advisors", ltmAdvisors.size());
+ if (advisors.size() > 0) {
+ advisors.addAll(agentCoreMemory.advisors);
+ logger.info("Advisors enabled: {} advisors", agentCoreMemory.advisors);
}
// Knowledge Base (RAG)
@@ -113,19 +95,15 @@ public ChatService(@Autowired(required = false) ChatMemoryRepository memoryRepos
logger.info("Web Grounding enabled");
}
- // Browser
- this.screenshotStore = browserScreenshotStore;
-
// Tool Callback Providers
+ this.browserArtifactStore = browserArtifactStore;
List toolCallbackProviders = new ArrayList<>();
if (browserTools != null) {
toolCallbackProviders.add(browserTools);
logger.info("Browser enabled");
}
- // Code Interpreter
- this.fileStore = codeInterpreterFileStore;
-
+ this.codeInterpreterArtifactStore = codeInterpreterArtifactStore;
if (codeInterpreterTools != null) {
toolCallbackProviders.add(codeInterpreterTools);
logger.info("Code Interpreter enabled");
@@ -169,38 +147,29 @@ private Flux chat(String prompt, String sessionId) {
.stream().content()
.concatWith(Flux.defer(() -> appendGeneratedFiles(sessionId)))
.concatWith(Flux.defer(() -> appendScreenshots(sessionId)))
- .contextWrite(ctx -> ctx.put(CodeInterpreterTools.SESSION_ID_CONTEXT_KEY, sessionId))
- .contextWrite(ctx -> ctx.put(BrowserTools.SESSION_ID_CONTEXT_KEY, sessionId));
+ .contextWrite(ctx -> ctx.put(SessionConstants.SESSION_ID_KEY, sessionId));
}
private String getSessionId(AgentCoreContext context) {
- String authHeader = context.getHeader(AgentCoreHeaders.AUTHORIZATION);
- if (authHeader != null && authHeader.startsWith("Bearer ")) {
- String jwt = authHeader.replace("Bearer ", "");
- String payload = new String(Base64.getUrlDecoder().decode(jwt.split("\\.")[1]));
- JsonNode claims = jsonMapper.readTree(payload);
- String sub = claims.get("sub").asString().replace("-", "").substring(0, 25);
- return sub + ":" + claims.get("auth_time").asString();
- }
- return (authHeader != null && !authHeader.isBlank()) ? authHeader : "default-user";
+ return ConversationIdResolver.resolve(context);
}
private Flux appendScreenshots(String sessionId) {
- if (screenshotStore == null) {
+ if (browserArtifactStore == null) {
return Flux.empty();
}
- List screenshots = screenshotStore.retrieve(sessionId);
+ List screenshots = browserArtifactStore.retrieve(sessionId);
if (screenshots == null || screenshots.isEmpty()) {
return Flux.empty();
}
return Flux.just(formatScreenshotsAsMarkdown(screenshots));
}
- private String formatScreenshotsAsMarkdown(List screenshots) {
+ private String formatScreenshotsAsMarkdown(List screenshots) {
StringBuilder sb = new StringBuilder();
- for (BrowserScreenshot screenshot : screenshots) {
+ for (GeneratedFile screenshot : screenshots) {
sb.append("\n\n
.append(screenshot.toDataUrl())
.append(")");
@@ -209,10 +178,10 @@ private String formatScreenshotsAsMarkdown(List screenshots)
}
private Flux appendGeneratedFiles(String sessionId) {
- if (fileStore == null) {
+ if (codeInterpreterArtifactStore == null) {
return Flux.empty();
}
- List files = fileStore.retrieve(sessionId);
+ List files = codeInterpreterArtifactStore.retrieve(sessionId);
if (files == null || files.isEmpty()) {
return Flux.empty();
}
diff --git a/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ConversationIdResolver.java b/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ConversationIdResolver.java
new file mode 100644
index 00000000..a53427a6
--- /dev/null
+++ b/apps/java-spring-ai-agents/aiagent/src/main/java/com/example/agent/ConversationIdResolver.java
@@ -0,0 +1,45 @@
+package com.example.agent;
+
+import org.springaicommunity.agentcore.context.AgentCoreContext;
+import org.springaicommunity.agentcore.context.AgentCoreHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.Base64;
+import java.util.UUID;
+
+/**
+ * Utility for extracting conversation ID from AgentCore context.
+ * Format: userId:sessionId (authenticated) or sessionId (anonymous)
+ */
+public final class ConversationIdResolver {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConversationIdResolver.class);
+ private static final JsonMapper jsonMapper = JsonMapper.builder().build();
+
+ private ConversationIdResolver() {}
+
+ public static String resolve(AgentCoreContext context) {
+ String sessionId = context.getHeader(AgentCoreHeaders.SESSION_ID);
+ if (sessionId == null || sessionId.isBlank()) {
+ sessionId = UUID.randomUUID().toString();
+ }
+
+ String authHeader = context.getHeader(AgentCoreHeaders.AUTHORIZATION);
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ try {
+ String jwt = authHeader.substring(7);
+ String payload = new String(Base64.getUrlDecoder().decode(jwt.split("\\.")[1]));
+ JsonNode claims = jsonMapper.readTree(payload);
+ String userId = claims.get("sub").asString();
+ return userId + ":" + sessionId;
+ } catch (Exception e) {
+ logger.debug("JWT parsing failed, using sessionId only", e);
+ }
+ }
+
+ return sessionId;
+ }
+}
diff --git a/apps/java-spring-ai-agents/aiagent/src/main/resources/application.properties b/apps/java-spring-ai-agents/aiagent/src/main/resources/application.properties
index 1b46b843..6d3525b7 100644
--- a/apps/java-spring-ai-agents/aiagent/src/main/resources/application.properties
+++ b/apps/java-spring-ai-agents/aiagent/src/main/resources/application.properties
@@ -9,6 +9,11 @@ spring.ai.bedrock.converse.chat.timeout=120s
spring.ai.bedrock.converse.chat.options.max-tokens=4096
spring.ai.bedrock.converse.chat.options.model=global.anthropic.claude-sonnet-4-5-20250929-v1:0
spring.ai.bedrock.converse.chat.options.temperature=0.7
+# Knowledge Base
+spring.ai.vectorstore.bedrock-knowledge-base.knowledge-base-id=${BEDROCK_KNOWLEDGE_BASE_ID:your-knowledge-base-id-here}
+# AgentCore memory
+agentcore.memory.memory-id=${AGENTCORE_MEMORY_ID:your-memory-id-here}
+agentcore.memory.long-term.auto-discovery=true
# AgentCore Browser - tool descriptions
agentcore.browser.browse-url-description=Browse a web page and extract its text content. Returns the page title and body text. Use this to read and extract data from websites. For interactive sites, combine with fillForm and clickElement to navigate, then call browseUrl again to read the results.
agentcore.browser.screenshot-description=Take a screenshot of a web page for the user to see. Does NOT return page content to you. Use browseUrl to extract data first, then takeScreenshot for visual evidence.
diff --git a/apps/java-spring-ai-agents/aiagent/src/main/resources/static/auth.js b/apps/java-spring-ai-agents/aiagent/src/main/resources/static/auth.js
index 374f3afb..9cad9beb 100644
--- a/apps/java-spring-ai-agents/aiagent/src/main/resources/static/auth.js
+++ b/apps/java-spring-ai-agents/aiagent/src/main/resources/static/auth.js
@@ -1,8 +1,23 @@
// Authentication management
const AUTH_KEY = 'agentcore_auth';
+const SESSION_KEY = 'agentcore_session_id';
let currentAuth = null;
+// Session ID management
+function getSessionId() {
+ let sessionId = sessionStorage.getItem(SESSION_KEY);
+ if (!sessionId) {
+ sessionId = crypto.randomUUID();
+ sessionStorage.setItem(SESSION_KEY, sessionId);
+ }
+ return sessionId;
+}
+
+function clearSessionId() {
+ sessionStorage.removeItem(SESSION_KEY);
+}
+
function saveAuth(auth) {
localStorage.setItem(AUTH_KEY, JSON.stringify(auth));
currentAuth = auth;
@@ -19,13 +34,13 @@ function loadAuth() {
function clearAuth() {
localStorage.removeItem(AUTH_KEY);
+ clearSessionId();
currentAuth = null;
}
function isAuthenticated() {
const auth = loadAuth();
- if (!auth || !auth.expiresAt) return false;
- if (auth.authType !== 'simple' && !auth.accessToken) return false;
+ if (!auth || !auth.expiresAt || !auth.accessToken) return false;
return !isSessionExpired(auth);
}
@@ -34,25 +49,7 @@ function isSessionExpired(auth) {
}
async function authenticateUser(username, password, config) {
- if (config.authType === 'cognito') {
- return authenticateCognito(username, password, config);
- }
- return authenticateSimple(username, password);
-}
-
-async function authenticateSimple(username, password) {
- if (!username || username.trim() === '') {
- throw new Error('Username is required');
- }
-
- const auth = {
- username: username,
- expiresAt: Date.now() + (24 * 60 * 60 * 1000),
- authType: 'simple'
- };
-
- saveAuth(auth);
- return auth;
+ return authenticateCognito(username, password, config);
}
async function authenticateCognito(username, password, config) {
diff --git a/apps/java-spring-ai-agents/aiagent/src/main/resources/static/chat.js b/apps/java-spring-ai-agents/aiagent/src/main/resources/static/chat.js
index 3e366fb8..d03959c5 100644
--- a/apps/java-spring-ai-agents/aiagent/src/main/resources/static/chat.js
+++ b/apps/java-spring-ai-agents/aiagent/src/main/resources/static/chat.js
@@ -317,7 +317,8 @@ async function sendMessage(message, config, auth) {
const headers = {
'Content-Type': 'application/json',
- 'Accept': 'text/plain, text/event-stream'
+ 'Accept': 'text/plain, text/event-stream',
+ 'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id': getSessionId()
};
if (config.authType === 'cognito' || config.mode === 'aws') {
diff --git a/apps/java-spring-ai-agents/aiagent/src/test/java/com/example/agent/ConversationIdResolverTest.java b/apps/java-spring-ai-agents/aiagent/src/test/java/com/example/agent/ConversationIdResolverTest.java
new file mode 100644
index 00000000..4432d0b6
--- /dev/null
+++ b/apps/java-spring-ai-agents/aiagent/src/test/java/com/example/agent/ConversationIdResolverTest.java
@@ -0,0 +1,74 @@
+package com.example.agent;
+
+import org.junit.jupiter.api.Test;
+import org.springaicommunity.agentcore.context.AgentCoreContext;
+import org.springaicommunity.agentcore.context.AgentCoreHeaders;
+import org.springframework.http.HttpHeaders;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ConversationIdResolverTest {
+
+ @Test
+ void resolve_withJwtAndSessionHeader_returnsUserIdColonSessionId() {
+ String payload = "{\"sub\":\"user-123\",\"auth_time\":1234567890}";
+ String jwt = "header." + Base64.getUrlEncoder().encodeToString(payload.getBytes()) + ".signature";
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(AgentCoreHeaders.AUTHORIZATION, "Bearer " + jwt);
+ headers.add(AgentCoreHeaders.SESSION_ID, "session-abc");
+ AgentCoreContext context = new AgentCoreContext(headers);
+
+ String result = ConversationIdResolver.resolve(context);
+
+ assertEquals("user-123:session-abc", result);
+ }
+
+ @Test
+ void resolve_withSessionHeaderOnly_returnsSessionId() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(AgentCoreHeaders.SESSION_ID, "session-xyz");
+ AgentCoreContext context = new AgentCoreContext(headers);
+
+ String result = ConversationIdResolver.resolve(context);
+
+ assertEquals("session-xyz", result);
+ }
+
+ @Test
+ void resolve_withNoHeaders_returnsGeneratedUuid() {
+ HttpHeaders headers = new HttpHeaders();
+ AgentCoreContext context = new AgentCoreContext(headers);
+
+ String result = ConversationIdResolver.resolve(context);
+
+ assertNotNull(result);
+ assertDoesNotThrow(() -> java.util.UUID.fromString(result));
+ }
+
+ @Test
+ void resolve_withInvalidJwt_returnsSessionIdOnly() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(AgentCoreHeaders.AUTHORIZATION, "Bearer invalid-jwt");
+ headers.add(AgentCoreHeaders.SESSION_ID, "session-fallback");
+ AgentCoreContext context = new AgentCoreContext(headers);
+
+ String result = ConversationIdResolver.resolve(context);
+
+ assertEquals("session-fallback", result);
+ }
+
+ @Test
+ void resolve_withNonBearerAuth_returnsSessionIdOnly() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(AgentCoreHeaders.AUTHORIZATION, "Basic sometoken");
+ headers.add(AgentCoreHeaders.SESSION_ID, "session-123");
+ AgentCoreContext context = new AgentCoreContext(headers);
+
+ String result = ConversationIdResolver.resolve(context);
+
+ assertEquals("session-123", result);
+ }
+}