From 0ac0a499dd6cfa29e25d551d523e09241369eca4 Mon Sep 17 00:00:00 2001 From: Anil kumar Gurrala Date: Tue, 30 Sep 2025 22:00:18 -0400 Subject: [PATCH 1/6] agentcore memory browser --- .../03-memory-browser/.env.example | 12 + .../03-memory-browser/.gitignore | 78 + .../03-memory-browser/README.md | 237 +++ .../03-memory-browser/backend/.env.example | 18 + .../03-memory-browser/backend/app.py | 1290 +++++++++++++++++ .../backend/requirements.txt | 7 + .../03-memory-browser/package.json | 49 + .../03-memory-browser/public/index.html | 18 + .../scripts/get-aws-credentials.js | 79 + .../03-memory-browser/src/App.js | 571 ++++++++ .../src/components/LongTermMemoryForm.js | 448 ++++++ .../src/components/ShortTermMemoryForm.js | 237 +++ .../03-memory-browser/src/index.css | 1117 ++++++++++++++ .../03-memory-browser/src/index.js | 11 + .../03-memory-browser/start-backend.sh | 48 + 15 files changed, 4220 insertions(+) create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.env.example create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.gitignore create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/.env.example create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/requirements.txt create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/package.json create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/public/index.html create mode 100755 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/App.js create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/ShortTermMemoryForm.js create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.css create mode 100644 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.js create mode 100755 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/start-backend.sh diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.env.example b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.env.example new file mode 100644 index 00000000..52093dbf --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.env.example @@ -0,0 +1,12 @@ +# AgentCore Memory Dashboard - Frontend Configuration + +# Backend API URL +REACT_APP_BACKEND_URL=http://localhost:8000 + +# Dashboard Settings +REACT_APP_MAX_MEMORY_ENTRIES=50 +REACT_APP_REFRESH_INTERVAL=5000 +REACT_APP_DEBUG_MODE=true + +# Note: Memory ID, Actor ID, and Session ID are entered by users through the UI +# No need to configure them here as environment variables \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.gitignore b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.gitignore new file mode 100644 index 00000000..12e1f4ce --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/.gitignore @@ -0,0 +1,78 @@ +# Dependencies +node_modules/ +backend/venv/ +backend/.venv/ +.venv/ + +# Environment variables with sensitive/personal data +# backend/.env - now cleaned and safe to share + +# Keep both .env files as they only contain non-sensitive config +# .env (frontend) - safe to share +# backend/.env - safe to share (cleaned of personal AWS profile) + +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so + +# Build outputs +build/ +dist/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.local +.env.development.local +.env.test.local +.env.production.local + +# AWS credentials (if accidentally placed in project) +.aws/ +credentials +config \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md new file mode 100644 index 00000000..311ebab3 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md @@ -0,0 +1,237 @@ +# AgentCore Memory Dashboard + +A lightweight React + FastAPI dashboard for browsing AWS Bedrock AgentCore Memory data. + +**đŸ“Ļ Repository Size**: ~2MB (dependencies excluded - see setup instructions below) + +## ✨ Key Features + +- **Dynamic Configuration**: Memory ID, Actor ID, and Session ID entered through UI +- **Short-Term Memory**: Query conversation events and turns +- **Long-Term Memory**: Browse facts, preferences, and summaries +- **Real-time Search**: Content filtering with live results + + + +## 📋 Prerequisites + +- **Node.js** 16+ +- **Python** 3.8+ +- **AWS CLI** configured with credentials +- **AWS Bedrock AgentCore Memory** access + +## 🔑 AWS Credentials Setup + +### Step 1: Install AWS CLI +```bash +# macOS +brew install awscli + +# Linux +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +sudo ./aws/install + +# Windows +# Download and run the AWS CLI MSI installer from AWS website +``` + +### Step 2: Configure AWS Credentials +Choose one of these methods: + +#### Option A: AWS Configure (Recommended) +```bash +aws configure +``` +Enter your: +- AWS Access Key ID +- AWS Secret Access Key +- Default region (e.g., `us-east-1`) +- Default output format (e.g., `json`) + +#### Option B: Environment Variables +```bash +export AWS_ACCESS_KEY_ID=your-access-key-id +export AWS_SECRET_ACCESS_KEY=your-secret-access-key +export AWS_DEFAULT_REGION=us-east-1 +``` + +#### Option C: AWS Credentials File +Create `~/.aws/credentials`: +```ini +[default] +aws_access_key_id = your-access-key-id +aws_secret_access_key = your-secret-access-key +``` + +Create `~/.aws/config`: +```ini +[default] +region = us-east-1 +output = json +``` + +### Step 3: Verify AWS Access +```bash +# Test AWS connection +aws sts get-caller-identity + +# Test Bedrock access +aws bedrock list-foundation-models --region us-east-1 +``` + +### Step 4: Required IAM Permissions +Your AWS user/role needs these permissions: +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:ListMemoryRecords", + "bedrock-agentcore:ListEvents", + "bedrock-agentcore:GetLastKTurns", + "bedrock-agentcore:RetrieveMemories", + "bedrock-agentcore:GetMemoryStrategies" + ], + "Resource": "*" + } + ] +} +``` + +## 🚀 Quick Start Guide + +### Step 1: Clone and Setup +```bash +# Clone the repository +git clone +cd agentcore-memory-dashboard + +# Install frontend dependencies (this will download ~200MB of packages) +npm install +``` + +**Note**: +- đŸ“Ļ **Dependencies not included**: `node_modules` and `backend/venv` are excluded from the repository +- 🔧 **First-time setup**: Run `npm install` to download all frontend dependencies +- ✅ **Frontend `.env`**: Already configured with default settings +- ❌ **Backend `.env`**: Needs to be created (see Step 2) + +### Step 2: Configure Environment Variables + +#### Backend Configuration +Copy the example file and customize: +```bash +# Copy the example file +cp backend/.env.example backend/.env + +# Edit backend/.env and set your AWS profile (if needed) +# AWS_PROFILE=your-profile-name +``` + +The `backend/.env` file should contain: +```env +# AWS Configuration (region will be auto-detected from AWS CLI/profile if not set) +# AWS_REGION=us-east-1 + +# Server Configuration +HOST=0.0.0.0 +PORT=8000 +DEBUG=true + +# CORS Configuration +ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + +# Optional: AWS Profile (if using multiple profiles) +# AWS_PROFILE=your-profile-name +``` + +**Note**: AWS region is automatically detected from your AWS CLI configuration. Only set `AWS_REGION` if you need to override the default. + +#### Frontend Configuration +The frontend `.env` file is already configured with default values. You can modify it if needed: +```env +# Backend API URL +REACT_APP_BACKEND_URL=http://localhost:8000 + +# Dashboard Settings +REACT_APP_MAX_MEMORY_ENTRIES=50 +REACT_APP_REFRESH_INTERVAL=5000 +``` + +### Step 3: Install Backend Dependencies +```bash +# Navigate to backend directory +cd backend + +# Create Python virtual environment (isolated Python packages) +python3 -m venv venv + +# Activate virtual environment +# On macOS/Linux: +source venv/bin/activate +# On Windows: +# venv\Scripts\activate + +# Install Python dependencies (~50MB of packages) +pip install -r requirements.txt + +# Return to project root +cd .. +``` + +**Note**: The virtual environment (`backend/venv/`) is excluded from the repository to keep it lightweight. + +### Step 4: Start the Application + +#### Option A: Start Both Services Together (Recommended) +```bash +# From project root directory +npm run dev +``` +This will start both the backend (FastAPI) and frontend (React) simultaneously. + +#### Option B: Start Services Separately +```bash +# Terminal 1: Start backend +npm run start-backend + +# Terminal 2: Start frontend +npm start +``` + + + +### Step 5: Access the Dashboard +- **Frontend**: http://localhost:3000 +- **Backend API**: http://localhost:8000 +- **API Documentation**: http://localhost:8000/docs + +### Step 6: Configure Memory Access +1. Open the dashboard at http://localhost:3000 +2. Enter your **Memory ID** and **Actor ID** in the header +3. Click **Configure** to validate access +4. Start querying your AgentCore Memory data! + +## 📊 Dashboard Features + +### Short-Term Memory +- Query conversation events and turns +- Filter by content, event type, and role + +### Long-Term Memory +- **User Input Required**: Memory ID and Namespace (entered via UI) +- Namespace-based querying with content filtering +- Browse facts, preferences, and summaries + +## 🔧 Troubleshooting + +### Common Issues +- **Backend won't start**: Check Python virtual environment is activated +- **Frontend can't connect**: Verify backend is running on port 8000 +- **AWS permission errors**: Run `aws sts get-caller-identity` to verify credentials +- **Memory ID not found**: Check Memory ID exists and you have proper permissions + +--- diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/.env.example b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/.env.example new file mode 100644 index 00000000..44415bf5 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/.env.example @@ -0,0 +1,18 @@ +# AgentCore Memory Backend Configuration + +# AWS Configuration (region will be auto-detected from AWS CLI/profile if not set) +# AWS_REGION=us-east-1 + +# Optional: AWS Profile (if using multiple profiles) +# AWS_PROFILE=your-profile-name + +# Server Configuration +HOST=0.0.0.0 +PORT=8000 +DEBUG=true + +# CORS Configuration +ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 + +# Note: Memory ID, Actor ID, and Session ID are entered by users through the dashboard UI +# No need to configure them here as environment variables \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py new file mode 100644 index 00000000..324fbf37 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py @@ -0,0 +1,1290 @@ +#!/usr/bin/env python3 +""" +AgentCore Memory Dashboard Backend - List Memory Records Only +Simple approach to just list all memory records without semantic search +""" + +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, field_validator +from typing import Optional, List, Dict, Any +import os +import logging +import sys +import re +from datetime import datetime +from botocore.exceptions import ClientError +from bedrock_agentcore.memory import MemoryClient +from bedrock_agentcore.memory.constants import StrategyType +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +def get_namespaces(mem_client: MemoryClient, memory_id: str) -> Dict: + """Get namespace mapping for memory strategies.""" + strategies = mem_client.get_memory_strategies(memory_id) + return {i["type"]: i["namespaces"][0] for i in strategies} + +def clean_aws_error_message(error_message: str) -> str: + """Clean up AWS error messages by removing ARNs and sensitive information.""" + import re + + # Remove ARN patterns (arn:aws:...) + error_message = re.sub(r'arn:aws:[^:\s]+:[^:\s]*:[^:\s]*:[^\s]+', '[AWS Resource]', error_message) + + # Remove account IDs (12-digit numbers) + error_message = re.sub(r'\b\d{12}\b', '[Account]', error_message) + + # Clean up common AWS error patterns + if 'AccessDeniedException' in error_message: + if 'bedrock-agentcore:GetMemory' in error_message: + return "Access denied: Missing required permission 'bedrock-agentcore:GetMemory'. Please check your IAM permissions." + elif 'bedrock-agentcore' in error_message: + return "Access denied: Missing required Bedrock AgentCore permissions. Please check your IAM permissions." + else: + return "Access denied: Insufficient permissions. Please check your AWS credentials and IAM permissions." + + if 'ResourceNotFoundException' in error_message or 'not found' in error_message.lower(): + return "Resource not found. Please verify the Memory ID exists and is accessible." + + if 'ValidationException' in error_message: + return "Invalid request parameters. Please check your Memory ID format." + + # Return cleaned message + return error_message + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app = FastAPI( + title="AgentCore Memory Dashboard API - List Only", + description="Simple backend to list all memory records", + version="1.0.0" +) + +# Configure CORS for React frontend +ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000").split(",") +app.add_middleware( + CORSMiddleware, + allow_origins=ALLOWED_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Configuration +MEMORY_ID = os.getenv("AGENTCORE_MEMORY_ID") # No default - must be provided by user + +# AWS Region detection - try multiple sources +def get_aws_region(): + """Get AWS region from environment, AWS CLI config, or default""" + # 1. Check environment variable first + region = os.getenv("AWS_REGION") + if region: + logger.info(f"Using AWS region from environment: {region}") + return region + + # 2. Try to get from AWS CLI configuration + try: + import subprocess + result = subprocess.run(['aws', 'configure', 'get', 'region'], + capture_output=True, text=True, timeout=5) + if result.returncode == 0 and result.stdout.strip(): + region = result.stdout.strip() + logger.info(f"Using AWS region from CLI config: {region}") + return region + except Exception as e: + logger.debug(f"Could not get region from AWS CLI: {e}") + + # 3. Try to get from boto3 session (respects AWS_DEFAULT_REGION, profiles, etc.) + try: + import boto3 + session = boto3.Session() + region = session.region_name + if region: + logger.info(f"Using AWS region from boto3 session: {region}") + return region + except Exception as e: + logger.debug(f"Could not get region from boto3 session: {e}") + + # 4. Fall back to default + logger.warning("No AWS region configured, using default: us-east-1") + return "us-east-1" + +AWS_REGION = get_aws_region() + +# Initialize AgentCore Memory client +try: + memory_client = MemoryClient() + logger.info("✅ AgentCore Memory client initialized") +except Exception as e: + logger.error(f"❌ Failed to initialize AgentCore Memory client: {e}") + memory_client = None + +class MemoryQuery(BaseModel): + namespace: Optional[str] = None + max_results: Optional[int] = 50 + memory_id: Optional[str] = None + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return { + "status": "healthy", + "memory_client": memory_client is not None, + "default_memory_id": MEMORY_ID, + "region": AWS_REGION, + "region_source": "auto-detected from AWS configuration", + "requires_memory_id": MEMORY_ID is None, + "timestamp": datetime.now().isoformat() + "Z" + } + +class ShortTermMemoryQuery(BaseModel): + actor_id: str + session_id: str + max_results: Optional[int] = 20 + memory_id: Optional[str] = None + # Existing filters + event_type: Optional[str] = "all" + role_filter: Optional[str] = "all" + sort_by: Optional[str] = "timestamp" + sort_order: Optional[str] = "desc" + # Essential filters only + content_search: Optional[str] = None + +class LongTermMemoryQuery(BaseModel): + namespace: str # Required field + max_results: Optional[int] = 20 + memory_id: Optional[str] = None + content_type: Optional[str] = "all" + sort_by: Optional[str] = "timestamp" + sort_order: Optional[str] = "desc" + + @field_validator('namespace') + @classmethod + def namespace_must_not_be_empty(cls, v): + if not v or not v.strip(): + raise ValueError('Namespace cannot be empty') + return v.strip() + +def apply_short_term_filters(memories: List[Dict[str, Any]], query: ShortTermMemoryQuery) -> List[Dict[str, Any]]: + """Apply client-side filters to short-term memory results""" + filtered_memories = memories.copy() + + # Content search filtering + if query.content_search and query.content_search.strip(): + search_term = query.content_search.strip().lower() + filtered_memories = [ + m for m in filtered_memories + if search_term in m.get('content', '').lower() + ] + + + + # Role filtering (existing) + if query.role_filter != "all": + filtered_memories = [ + m for m in filtered_memories + if m.get('role', '').upper() == query.role_filter.upper() + ] + + # Event type filtering (existing) + if query.event_type != "all": + filtered_memories = [ + m for m in filtered_memories + if m.get('type', '') == query.event_type + ] + + # Simple sorting with consistent string conversion + reverse_order = query.sort_order == "desc" + + if query.sort_by == "timestamp": + # Convert all timestamps to strings for consistent sorting + filtered_memories.sort( + key=lambda x: str(x.get('timestamp', '')), + reverse=reverse_order + ) + elif query.sort_by == "size": + filtered_memories.sort( + key=lambda x: int(x.get('size', 0)), + reverse=reverse_order + ) + + return filtered_memories + +@app.post("/api/agentcore/getShortTermMemory") +async def get_short_term_memory(query: ShortTermMemoryQuery): + """Get short-term memory (events and conversation turns) from AgentCore Memory""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + # Use provided memory_id or fall back to environment default + memory_id = query.memory_id or MEMORY_ID + + if not memory_id: + raise HTTPException( + status_code=400, + detail="Memory ID is required. Please provide memory_id in request or set AGENTCORE_MEMORY_ID environment variable." + ) + + short_term_memories = [] + + logger.info(f"īŋŊ Fextching short-term memory for actor_id='{query.actor_id}', session_id='{query.session_id}'") + logger.info(f"📋 Memory ID: {memory_id}") + logger.info(f"📋 Max results: {query.max_results}") + + # Method 1: Try ListEvents API + try: + logger.info("📞 Using ListEvents API") + events = memory_client.list_events( + memory_id=memory_id, + actor_id=query.actor_id, + session_id=query.session_id, + max_results=query.max_results + ) + + if events: + logger.info(f"✅ Found {len(events)} events") + + for event_idx, event in enumerate(events): + payload = event.get('payload', {}) + content_text = "" + + # Handle the actual payload structure from MemoryClient.list_events + if isinstance(payload, list) and len(payload) > 0: + # Payload is a list, get first item + first_item = payload[0] + if isinstance(first_item, dict): + # Look for conversational content + if 'conversational' in first_item: + conversational = first_item['conversational'] + if isinstance(conversational, dict): + content = conversational.get('content', {}) + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(conversational) + else: + # Fallback to any content field + if 'content' in first_item: + content = first_item['content'] + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(content) + else: + content_text = str(first_item) + else: + content_text = str(first_item) + elif isinstance(payload, dict): + # Handle dict payload + if 'content' in payload: + content = payload['content'] + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(content) + elif 'message' in payload: + content_text = str(payload['message']) + else: + content_text = str(payload) + else: + content_text = str(payload) + + memory_entry = { + "id": f"event-{event_idx}", + "content": content_text, + "type": "event", + "memory_type": "SHORT_TERM", + "actor_id": query.actor_id, + "session_id": query.session_id, + "event_id": event.get('eventId', f"event-{event_idx}"), + "event_type": event.get('eventType', 'unknown'), + "timestamp": str(event.get('eventTimestamp', datetime.now().isoformat() + "Z")), + "size": len(content_text) + } + short_term_memories.append(memory_entry) + + except Exception as e: + error_msg = str(e).lower() + logger.warning(f"ListEvents failed for {query.actor_id}/{query.session_id}: {e}") + logger.warning(f"ListEvents error type: {type(e).__name__}") + + # Clean the error message to remove ARNs and sensitive info + clean_error = clean_aws_error_message(str(e)) + + # Check for specific Memory ID not found errors + if any(keyword in error_msg for keyword in ['not found', 'does not exist', 'invalid memory', 'memory id', 'resourcenotfoundexception']): + logger.error(f"❌ Memory ID '{memory_id}' not found or inaccessible") + raise HTTPException( + status_code=404, + detail=f"Memory ID '{memory_id}' not found. Please verify the Memory ID exists and you have access permissions." + ) + elif any(keyword in error_msg for keyword in ['access denied', 'unauthorized', 'permission', 'accessdeniedexception']): + logger.error(f"❌ Access denied for Memory ID '{memory_id}'") + raise HTTPException( + status_code=403, + detail=clean_error + ) + + # Method 2: Try get_last_k_turns + try: + logger.info("🔄 Using get_last_k_turns API") + recent_turns = memory_client.get_last_k_turns( + memory_id=memory_id, + actor_id=query.actor_id, + session_id=query.session_id, + k=query.max_results or 10 + ) + + if recent_turns: + logger.info(f"✅ Found {len(recent_turns)} conversation turns") + + for turn_idx, turn in enumerate(recent_turns): + for message_idx, message in enumerate(turn): + content = message.get('content', {}) + if isinstance(content, dict): + content_text = content.get('text', str(content)) + else: + content_text = str(content) + + memory_entry = { + "id": f"turn-{turn_idx}-{message_idx}", + "content": content_text, + "type": "conversation", + "memory_type": "SHORT_TERM", + "actor_id": query.actor_id, + "session_id": query.session_id, + "role": message.get('role', 'unknown'), + "turn_index": turn_idx, + "message_index": message_idx, + "timestamp": datetime.now().isoformat() + "Z", + "size": len(content_text) + } + short_term_memories.append(memory_entry) + + except Exception as e: + error_msg = str(e).lower() + logger.warning(f"get_last_k_turns failed for {query.actor_id}/{query.session_id}: {e}") + logger.warning(f"get_last_k_turns error type: {type(e).__name__}") + + # Clean the error message to remove ARNs and sensitive info + clean_error = clean_aws_error_message(str(e)) + + # Check for specific Memory ID not found errors + if any(keyword in error_msg for keyword in ['not found', 'does not exist', 'invalid memory', 'memory id', 'resourcenotfoundexception']): + logger.error(f"❌ Memory ID '{memory_id}' not found or inaccessible") + raise HTTPException( + status_code=404, + detail=f"Memory ID '{memory_id}' not found. Please verify the Memory ID exists and you have access permissions." + ) + elif any(keyword in error_msg for keyword in ['access denied', 'unauthorized', 'permission', 'accessdeniedexception']): + logger.error(f"❌ Access denied for Memory ID '{memory_id}'") + raise HTTPException( + status_code=403, + detail=clean_error + ) + + logger.info(f"✅ Total short-term memories found: {len(short_term_memories)}") + + # Apply filters + filtered_memories = apply_short_term_filters(short_term_memories, query) + logger.info(f"🔍 After filtering: {len(filtered_memories)} memories remain") + + return { + "memories": filtered_memories, + "total_count": len(filtered_memories), + "raw_count": len(short_term_memories), + "source": "short_term_memory", + "actor_id": query.actor_id, + "session_id": query.session_id, + "memory_id": memory_id, + "filters_applied": { + "content_search": bool(query.content_search), + "role_filter": query.role_filter, + "event_type": query.event_type + } + } + + except Exception as e: + logger.error(f"Error getting short-term memory: {e}") + raise HTTPException(status_code=500, detail=f"Failed to get short-term memory: {str(e)}") + +class EventQuery(BaseModel): + event_id: str + memory_id: Optional[str] = None + # Optional: if you have these, retrieval is more efficient + actor_id: Optional[str] = None + session_id: Optional[str] = None + +class EventSearchQuery(BaseModel): + event_id: str + memory_id: Optional[str] = None + # Search parameters to find the event + search_all_sessions: bool = False + known_actor_ids: Optional[List[str]] = None + +@app.post("/api/agentcore/searchEventById") +async def search_event_by_id(query: EventSearchQuery): + """Search for an event by ID across multiple sessions""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + memory_id = query.memory_id or MEMORY_ID + + logger.info(f"🔍 Searching for event ID: {query.event_id}") + logger.info(f"📋 Memory ID: {memory_id}") + + # Common actor IDs to try (only if provided by user) + actor_ids_to_try = query.known_actor_ids or [] + + # Session IDs must be provided by user - no hardcoded defaults + session_ids_to_try = [] + + for actor_id in actor_ids_to_try: + for session_id in session_ids_to_try: + try: + logger.info(f"🔍 Searching in actor_id={actor_id}, session_id={session_id}") + + events = memory_client.list_events( + memory_id=memory_id, + actor_id=actor_id, + session_id=session_id, + max_results=100 # Search more events + ) + + # Look for the specific event ID + for event in events: + if event.get('eventId') == query.event_id: + logger.info(f"✅ Found event {query.event_id} in {actor_id}/{session_id}") + + # Process the event (same logic as before) + payload = event.get('payload', {}) + content_text = str(payload) # Simplified for now + + event_data = { + "id": query.event_id, + "content": content_text, + "type": "event", + "memory_type": "SHORT_TERM", + "event_id": event.get('eventId', query.event_id), + "event_type": event.get('eventType', 'unknown'), + "actor_id": actor_id, + "session_id": session_id, + "timestamp": str(event.get('eventTimestamp', datetime.now().isoformat() + "Z")), + "size": len(content_text), + "found_in": f"{actor_id}/{session_id}" + } + + return { + "event": event_data, + "found": True, + "memory_id": memory_id, + "event_id": query.event_id, + "search_location": f"{actor_id}/{session_id}" + } + + except Exception as e: + logger.warning(f"Search failed for {actor_id}/{session_id}: {e}") + continue + + return { + "event": None, + "found": False, + "memory_id": memory_id, + "event_id": query.event_id, + "error": f"Event {query.event_id} not found in searched sessions", + "searched_combinations": len(actor_ids_to_try) * len(session_ids_to_try) + } + + except Exception as e: + logger.error(f"Error searching for event: {e}") + raise HTTPException(status_code=500, detail=f"Failed to search for event: {str(e)}") + +@app.post("/api/agentcore/getEventById") +async def get_event_by_id(query: EventQuery): + """Get a specific event by event ID""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + memory_id = query.memory_id or MEMORY_ID + + logger.info(f"🔍 Fetching event by ID: {query.event_id}") + logger.info(f"📋 Memory ID: {memory_id}") + + # Try to get the specific event + try: + # Note: This assumes there's a get_event method in the memory client + # You may need to check the actual AgentCore Memory client API + event = memory_client.get_event( + memory_id=memory_id, + event_id=query.event_id + ) + + if event: + payload = event.get('payload', {}) + content_text = "" + + # Handle payload structure (same logic as in list_events) + if isinstance(payload, list) and len(payload) > 0: + first_item = payload[0] + if isinstance(first_item, dict): + if 'conversational' in first_item: + conversational = first_item['conversational'] + if isinstance(conversational, dict): + content = conversational.get('content', {}) + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(conversational) + else: + if 'content' in first_item: + content = first_item['content'] + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(content) + else: + content_text = str(first_item) + else: + content_text = str(first_item) + elif isinstance(payload, dict): + if 'content' in payload: + content = payload['content'] + if isinstance(content, dict) and 'text' in content: + content_text = content['text'] + else: + content_text = str(content) + elif 'message' in payload: + content_text = str(payload['message']) + else: + content_text = str(payload) + else: + content_text = str(payload) + + event_data = { + "id": query.event_id, + "content": content_text, + "type": "event", + "memory_type": "SHORT_TERM", + "event_id": event.get('eventId', query.event_id), + "event_type": event.get('eventType', 'unknown'), + "actor_id": event.get('actorId', 'unknown'), + "session_id": event.get('sessionId', 'unknown'), + "timestamp": str(event.get('eventTimestamp', datetime.now().isoformat() + "Z")), + "size": len(content_text), + "raw_event": event # Include full event data for debugging + } + + return { + "event": event_data, + "found": True, + "memory_id": memory_id, + "event_id": query.event_id + } + else: + return { + "event": None, + "found": False, + "memory_id": memory_id, + "event_id": query.event_id, + "error": "Event not found" + } + + except AttributeError: + # If get_event method doesn't exist, try alternative approach + logger.warning("get_event method not available, trying alternative approach") + + # Alternative: Search through all events to find the specific one + # This is less efficient but works if direct event retrieval isn't available + try: + # We'd need actor_id and session_id for this approach + # This is a limitation of the current API + return { + "event": None, + "found": False, + "memory_id": memory_id, + "event_id": query.event_id, + "error": "Direct event retrieval not supported. Need actor_id and session_id to search events." + } + except Exception as e: + logger.error(f"Alternative event search failed: {e}") + raise HTTPException(status_code=500, detail=f"Failed to retrieve event: {str(e)}") + + except Exception as e: + logger.error(f"Error getting event by ID: {e}") + raise HTTPException(status_code=500, detail=f"Failed to get event: {str(e)}") + +@app.post("/api/agentcore/listNamespaces") +async def list_namespaces(query: MemoryQuery): + """List available namespaces from AgentCore Memory strategies""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + # Use provided memory_id or fall back to environment default + memory_id = query.memory_id or MEMORY_ID + + if not memory_id: + raise HTTPException( + status_code=400, + detail="Memory ID is required. Please provide memory_id in request or set AGENTCORE_MEMORY_ID environment variable." + ) + + logger.info(f"🔍 Listing namespaces for memory ID: {memory_id}") + + # Get memory strategies to discover namespaces + try: + strategies = memory_client.get_memory_strategies(memory_id) + logger.info(f"✅ Found {len(strategies)} memory strategies") + + namespaces = [] + for strategy in strategies: + strategy_namespaces = strategy.get('namespaces', []) + strategy_type = strategy.get('type', 'UNKNOWN') + + for namespace in strategy_namespaces: + namespaces.append({ + 'namespace': namespace, + 'type': strategy_type, + 'count': 0, # We could add a count query here if needed + 'sample_content': '' # We could add sample content if needed + }) + + logger.info(f"✅ Discovered {len(namespaces)} namespaces") + + return { + "namespaces": namespaces, + "total_count": len(namespaces), + "memory_id": memory_id + } + + except Exception as e: + error_msg = str(e).lower() + logger.error(f"Failed to get memory strategies: {e}") + + # Clean the error message to remove ARNs and sensitive info + clean_error = clean_aws_error_message(str(e)) + + # Check for specific Memory ID not found errors + if any(keyword in error_msg for keyword in ['not found', 'does not exist', 'invalid memory', 'memory id', 'resourcenotfoundexception']): + logger.error(f"❌ Memory ID '{memory_id}' not found or inaccessible") + raise HTTPException( + status_code=404, + detail=f"Memory ID '{memory_id}' not found. Please verify the Memory ID exists and you have access permissions." + ) + elif any(keyword in error_msg for keyword in ['access denied', 'unauthorized', 'permission', 'accessdeniedexception']): + logger.error(f"❌ Access denied for Memory ID '{memory_id}'") + raise HTTPException( + status_code=403, + detail=clean_error + ) + else: + raise HTTPException(status_code=500, detail=f"Failed to get memory strategies: {clean_error}") + + except Exception as e: + logger.error(f"Error listing namespaces: {e}") + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + +@app.post("/api/agentcore/getLongTermMemory") +async def get_long_term_memory(query: LongTermMemoryQuery): + """Get long-term memory (facts, preferences, summaries) from AgentCore Memory""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + # Use provided memory_id or fall back to environment default + memory_id = query.memory_id or MEMORY_ID + + if not memory_id: + raise HTTPException( + status_code=400, + detail="Memory ID is required. Please provide memory_id in request or set AGENTCORE_MEMORY_ID environment variable." + ) + + long_term_memories = [] + + logger.info(f"īŋŊ Fetching loong-term memory with namespace='{query.namespace}', max_results={query.max_results}") + logger.info(f"📋 Memory ID: {memory_id}") + logger.info(f"📋 Filters: content_type={query.content_type}, sort_by={query.sort_by}, sort_order={query.sort_order}") + + # Use retrieve_memories to get long-term memory directly from AgentCore + try: + logger.info("📚 Using retrieve_memories API") + + # Use retrieve_memories for semantic search + memories = memory_client.retrieve_memories( + memory_id=memory_id, + namespace=query.namespace, + query="*", # Get all content - could be made configurable + top_k=query.max_results + ) + + if isinstance(memories, dict) and 'memoryRecordSummaries' in memories: + memory_records = memories['memoryRecordSummaries'] + else: + memory_records = memories if isinstance(memories, list) else [] + + if memory_records: + logger.info(f"✅ Found {len(memory_records)} memory records") + + for memory_idx, memory in enumerate(memory_records): + # Debug: log the raw memory structure + logger.info(f"📋 Raw memory record {memory_idx}: {memory}") + + content = memory.get('content', {}) + if isinstance(content, dict): + content_text = content.get('text', str(content)) + else: + content_text = str(content) + + # Apply content type filter + memory_namespaces = memory.get('namespaces', []) + namespace_str = memory_namespaces[0] if memory_namespaces else query.namespace + + if query.content_type != 'all': + if query.content_type == 'facts' and 'facts' not in namespace_str: + continue + elif query.content_type == 'preferences' and 'preferences' not in namespace_str: + continue + elif query.content_type == 'summaries' and not any(word in content_text.lower() for word in ['summary', 'topic', 'conversation']): + continue + elif query.content_type == 'context' and 'context' not in namespace_str: + continue + + memory_entry = { + "id": memory.get('memoryRecordId', f"memory-{memory_idx}"), + "content": content_text, + "type": "record", + "memory_type": "LONG_TERM", + "namespace": namespace_str, + "strategyId": memory.get('memoryStrategyId', ''), + "score": memory.get('score', 0), + "timestamp": str(memory.get('createdAt', datetime.now().isoformat() + "Z")), + "size": len(content_text) + } + long_term_memories.append(memory_entry) + else: + logger.info("❌ No memory records found") + + except Exception as e: + error_msg = str(e).lower() + logger.error(f"retrieve_memories failed: {e}") + + # Clean the error message to remove ARNs and sensitive info + clean_error = clean_aws_error_message(str(e)) + + # Check for specific Memory ID not found errors + if any(keyword in error_msg for keyword in ['not found', 'does not exist', 'invalid memory', 'memory id', 'resourcenotfoundexception']): + logger.error(f"❌ Memory ID '{memory_id}' not found or inaccessible") + raise HTTPException( + status_code=404, + detail=f"Memory ID '{memory_id}' not found. Please verify the Memory ID exists and you have access permissions." + ) + elif any(keyword in error_msg for keyword in ['access denied', 'unauthorized', 'permission', 'accessdeniedexception']): + logger.error(f"❌ Access denied for Memory ID '{memory_id}'") + raise HTTPException( + status_code=403, + detail=clean_error + ) + elif 'namespace' in error_msg and ('not found' in error_msg or 'invalid' in error_msg): + logger.error(f"❌ Namespace '{query.namespace}' not found in Memory ID '{memory_id}'") + raise HTTPException( + status_code=404, + detail=f"Namespace '{query.namespace}' not found in Memory ID '{memory_id}'. Please verify the namespace exists." + ) + else: + raise HTTPException(status_code=500, detail=f"Failed to retrieve memories from AgentCore: {clean_error}") + + # Apply sorting + if query.sort_by == 'timestamp': + long_term_memories.sort(key=lambda x: x['timestamp'], reverse=(query.sort_order == 'desc')) + elif query.sort_by == 'namespace': + long_term_memories.sort(key=lambda x: x['namespace'], reverse=(query.sort_order == 'desc')) + elif query.sort_by == 'size': + long_term_memories.sort(key=lambda x: x['size'], reverse=(query.sort_order == 'desc')) + + logger.info(f"✅ Total long-term memories found: {len(long_term_memories)}") + + return { + "memories": long_term_memories, + "total_count": len(long_term_memories), + "source": "long_term_memory", + "namespace": query.namespace, + "memory_id": memory_id + } + + except Exception as e: + logger.error(f"Error getting long-term memory: {e}") + raise HTTPException(status_code=500, detail=f"Failed to get long-term memory: {str(e)}") + +@app.post("/api/agentcore/getMemoryEntries") +async def get_memory_entries(query: MemoryQuery): + """List all memory records from AgentCore Memory""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + # Use provided memory_id or fall back to environment default + memory_id = query.memory_id or MEMORY_ID + + if not memory_id: + raise HTTPException( + status_code=400, + detail="Memory ID is required. Please provide memory_id in request or set AGENTCORE_MEMORY_ID environment variable." + ) + + all_memories = [] + + # Use ListMemoryRecords operation to browse all records without semantic search + logger.info(f"🔍 Listing memory records using ListMemoryRecords operation") + logger.info(f"📋 Memory ID: {memory_id}") + + # AgentCore Memory typically requires a namespace for efficient queries + # If no namespace provided, return helpful message + if not query.namespace: + logger.info("📋 No namespace provided - AgentCore Memory requires namespace for queries") + return { + "memories": [], + "total_count": 0, + "source": "list_memory_records", + "memory_id": memory_id, + "message": "No namespace provided. AgentCore Memory requires a namespace to query data efficiently. Please provide a namespace in your request." + } + + # Try listing memory records with provided namespace + try: + logger.info(f"📋 Listing memory records from namespace: {query.namespace}") + + memories = memory_client.list_memory_records( + memoryId=memory_id, + namespace=query.namespace, + maxResults=query.max_results or 50 + ) + + # Handle the actual response structure + if isinstance(memories, dict) and 'memoryRecordSummaries' in memories: + memory_records = memories['memoryRecordSummaries'] + logger.info(f"📋 Processing {len(memory_records)} actual memory records") + else: + memory_records = memories if isinstance(memories, list) else [memories] + + logger.info(f"✅ Found {len(memory_records)} memory records") + + # Convert memories to our format + for memory in memory_records: + # Handle both dict and string formats + if isinstance(memory, dict): + content = memory.get('content', {}) + if isinstance(content, dict): + content_text = content.get('text', str(content)) + else: + content_text = str(content) + + memory_entry = { + "id": memory.get('memoryId', f"memory-{len(all_memories)}"), + "content": content_text, + "memory_type": "LONG_TERM_MEMORY", + "namespace": query.namespace, + "score": memory.get('score', 0), + "timestamp": memory.get('createdAt', datetime.now().isoformat() + "Z"), + "size": len(content_text) + } + else: + # Handle string format + content_text = str(memory) + memory_entry = { + "id": f"memory-{len(all_memories)}", + "content": content_text, + "memory_type": "LONG_TERM_MEMORY", + "namespace": query.namespace, + "score": 0, + "timestamp": datetime.now().isoformat() + "Z", + "size": len(content_text) + } + + all_memories.append(memory_entry) + + except Exception as e: + logger.warning(f"Could not list memory records from namespace '{query.namespace}': {e}") + return { + "memories": [], + "total_count": 0, + "source": "list_memory_records", + "memory_id": memory_id, + "error": f"Failed to access namespace '{query.namespace}': {str(e)}" + } + + + logger.info(f"✅ Total memory records found: {len(all_memories)}") + + return { + "memories": all_memories, + "total_count": len(all_memories), + "source": "list_memory_records", + "memory_id": memory_id + } + + except Exception as e: + logger.error(f"Error listing memory records: {e}") + raise HTTPException(status_code=500, detail=f"Failed to list memory records: {str(e)}") + +class MemoryIdValidationQuery(BaseModel): + memory_id: str + +class ListNamespacesQuery(BaseModel): + memory_id: str + max_results: Optional[int] = 100 + +@app.post("/api/agentcore/listNamespaces") +async def list_namespaces(query: ListNamespacesQuery): + """List available namespaces for a given memory ID""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + memory_id = query.memory_id or MEMORY_ID + if not memory_id: + raise HTTPException(status_code=400, detail="Memory ID is required") + + logger.info(f"🔍 Listing namespaces for memory ID: {memory_id}") + + # Use the correct AgentCore Memory SDK approach to get namespaces + try: + logger.info("📋 Getting memory strategies to discover namespaces...") + + # Get memory strategies which contain namespace information + strategies = memory_client.get_memory_strategies(memory_id) + logger.info(f"✅ Found {len(strategies)} memory strategies") + + found_namespaces = [] + + # Extract namespaces from strategies + for strategy in strategies: + strategy_type = strategy.get("type", "unknown") + namespaces = strategy.get("namespaces", []) + + logger.info(f"📋 Strategy '{strategy_type}' has namespaces: {namespaces}") + + for namespace in namespaces: + # Try to get a sample of records from this namespace to count them + try: + # Use retrieve_memories to get sample content + sample_memories = memory_client.retrieve_memories( + memory_id=memory_id, + namespace=namespace, + query="*", # Generic query to get any content + top_k=3 # Get a few samples + ) + + sample_content = "" + if sample_memories and len(sample_memories) > 0: + first_memory = sample_memories[0] + content = first_memory.get('content', {}) + if isinstance(content, dict): + sample_content = content.get('text', str(content))[:100] + "..." + else: + sample_content = str(content)[:100] + "..." + + found_namespaces.append({ + "namespace": namespace, + "type": strategy_type, + "count": len(sample_memories) if sample_memories else 0, + "sample_content": sample_content + }) + + logger.info(f"✅ Found namespace: {namespace} (type: {strategy_type}) with {len(sample_memories) if sample_memories else 0} sample records") + + except Exception as e: + # Still add the namespace even if we can't get samples + found_namespaces.append({ + "namespace": namespace, + "type": strategy_type, + "count": 0, + "sample_content": f"Unable to retrieve sample: {str(e)}" + }) + logger.warning(f"âš ī¸ Found namespace: {namespace} (type: {strategy_type}) but couldn't retrieve samples: {e}") + + # Remove duplicates based on namespace + unique_namespaces = [] + seen_namespaces = set() + for ns in found_namespaces: + if ns["namespace"] not in seen_namespaces: + unique_namespaces.append(ns) + seen_namespaces.add(ns["namespace"]) + + return { + "memory_id": memory_id, + "namespaces": unique_namespaces, + "total_found": len(unique_namespaces), + "strategies_found": len(strategies), + "message": f"Found {len(unique_namespaces)} namespaces from {len(strategies)} memory strategies" if unique_namespaces else "No namespaces found in memory strategies" + } + + except Exception as e: + logger.warning(f"Failed to get memory strategies: {e}") + + # Fallback to the old pattern-based approach + logger.info("🔄 Falling back to pattern-based namespace discovery...") + + try: + # Common namespace patterns for AgentCore Memory + namespace_patterns = [ + "support/user/DEFAULT", + "support/user/DEFAULT/facts", + "support/user/DEFAULT/preferences", + "support/user/DEFAULT/context", + "support/user/DEFAULT/summaries", + "facts", + "preferences", + "context", + "summaries" + ] + + found_namespaces = [] + + for pattern in namespace_patterns: + try: + # Use list_memory_records to test namespace existence + memories = memory_client.list_memory_records( + memoryId=memory_id, + namespace=pattern, + maxResults=1 + ) + + if memories and len(memories) > 0: + sample_content = "" + if memories[0].get('content'): + content = memories[0]['content'] + if isinstance(content, dict): + sample_content = content.get('text', str(content))[:100] + "..." + else: + sample_content = str(content)[:100] + "..." + + found_namespaces.append({ + "namespace": pattern, + "type": "unknown", + "count": 1, # We only queried for 1 record + "sample_content": sample_content + }) + logger.info(f"✅ Found namespace: {pattern} (fallback method)") + + except Exception as e2: + logger.debug(f"❌ Namespace {pattern} not accessible: {e2}") + continue + + return { + "memory_id": memory_id, + "namespaces": found_namespaces, + "total_found": len(found_namespaces), + "method": "fallback_pattern_based", + "message": f"Found {len(found_namespaces)} namespaces using fallback method (get_memory_strategies failed: {str(e)})" + } + + except Exception as e2: + logger.warning(f"Fallback method also failed: {e2}") + return { + "memory_id": memory_id, + "namespaces": [], + "total_found": 0, + "error": f"Both get_memory_strategies and fallback failed: {str(e)} / {str(e2)}" + } + + except Exception as e: + logger.error(f"Error listing namespaces: {e}") + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + +@app.post("/api/agentcore/validateMemoryId") +async def validate_memory_id(query: MemoryIdValidationQuery): + """Validate if a memory ID is accessible""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + logger.info(f"🔍 Validating memory ID: {query.memory_id}") + + # Try to list memory records to validate the memory ID + try: + memories = memory_client.list_memory_records( + memoryId=query.memory_id, + maxResults=1 # Just check if we can access it + ) + + return { + "valid": True, + "memory_id": query.memory_id, + "accessible": True, + "message": "Memory ID is valid and accessible" + } + + except Exception as e: + logger.warning(f"Memory ID validation failed: {e}") + return { + "valid": False, + "memory_id": query.memory_id, + "accessible": False, + "message": f"Memory ID validation failed: {str(e)}" + } + + except Exception as e: + logger.error(f"Error validating memory ID: {e}") + raise HTTPException(status_code=500, detail=f"Failed to validate memory ID: {str(e)}") + +class AddMemoryEntryQuery(BaseModel): + session_id: str + memory_type: str + content: str + +class DeleteMemoryEntriesQuery(BaseModel): + session_id: str + memory_type: Optional[str] = None + +class SearchMemoryEntriesQuery(BaseModel): + query: str + session_id: Optional[str] = None + memory_type: Optional[str] = None + max_results: Optional[int] = 50 + +@app.post("/api/agentcore/addMemoryEntry") +async def add_memory_entry(query: AddMemoryEntryQuery): + """Add a memory entry (not implemented for AgentCore Memory)""" + return { + "success": False, + "message": "Adding memory entries is not supported in this dashboard. AgentCore Memory entries are created by your application." + } + +@app.post("/api/agentcore/deleteMemoryEntries") +async def delete_memory_entries(query: DeleteMemoryEntriesQuery): + """Delete memory entries (not implemented for AgentCore Memory)""" + return { + "success": False, + "message": "Deleting memory entries is not supported in this dashboard. AgentCore Memory manages its own lifecycle." + } + +@app.post("/api/agentcore/searchMemoryEntries") +async def search_memory_entries(query: SearchMemoryEntriesQuery): + """Search memory entries (simplified implementation)""" + try: + # This is a simplified search - in a real implementation you might use + # semantic search or other AgentCore Memory search capabilities + return { + "memories": [], + "total_count": 0, + "message": "Search functionality not fully implemented. Use the Long-Term Memory tab with specific namespaces for better results." + } + except Exception as e: + logger.error(f"Error searching memory entries: {e}") + raise HTTPException(status_code=500, detail=f"Failed to search memory entries: {str(e)}") + +@app.post("/api/agentcore/listNamespaces") +async def list_namespaces(request: dict): + """List available namespaces for long-term memory""" + try: + if not memory_client: + raise HTTPException(status_code=503, detail="AgentCore Memory client not available") + + memory_id = request.get("memory_id") or MEMORY_ID + max_results = request.get("max_results", 100) + + if not memory_id: + raise HTTPException( + status_code=400, + detail="Memory ID is required. Please provide memory_id in request or set AGENTCORE_MEMORY_ID environment variable." + ) + + logger.info(f"🔍 Listing namespaces for memory_id: {memory_id}") + + try: + # Get memory strategies to discover namespaces + strategies = memory_client.get_memory_strategies(memory_id) + logger.info(f"✅ Found {len(strategies)} memory strategies") + + namespaces = [] + for strategy in strategies: + strategy_type = strategy.get("type", "UNKNOWN") + strategy_namespaces = strategy.get("namespaces", []) + + for namespace in strategy_namespaces: + # Try to get a count of records in this namespace + try: + # Sample a few records to get count and sample content + sample_memories = memory_client.retrieve_memories( + memory_id=memory_id, + namespace=namespace, + query="*", + top_k=5 + ) + + if isinstance(sample_memories, dict) and 'memoryRecordSummaries' in sample_memories: + memory_records = sample_memories['memoryRecordSummaries'] + else: + memory_records = sample_memories if isinstance(sample_memories, list) else [] + + count = len(memory_records) + sample_content = "" + if memory_records: + first_record = memory_records[0] + content = first_record.get('content', {}) + if isinstance(content, dict): + sample_content = content.get('text', str(content)) + else: + sample_content = str(content) + + except Exception as e: + logger.warning(f"Failed to get count for namespace {namespace}: {e}") + count = 0 + sample_content = "" + + namespace_info = { + "namespace": namespace, + "type": strategy_type, + "count": count, + "sample_content": sample_content[:200] if sample_content else "" + } + namespaces.append(namespace_info) + + logger.info(f"✅ Found {len(namespaces)} total namespaces") + + return { + "namespaces": namespaces, + "total_count": len(namespaces), + "memory_id": memory_id, + "strategies_count": len(strategies) + } + + except Exception as e: + logger.error(f"Failed to get memory strategies: {e}") + raise HTTPException(status_code=500, detail=f"Failed to get namespaces: {str(e)}") + + except Exception as e: + logger.error(f"Error listing namespaces: {e}") + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + +@app.get("/api/agentcore/listSessions") +async def list_sessions(): + """List available sessions (simplified)""" + try: + return { + "sessions": [ + {"session_id": "memory-records", "type": "MEMORY_RECORDS", "active": True} + ], + "total_sessions": 1, + "source": "list_records_focus" + } + + except Exception as e: + logger.error(f"Error listing sessions: {e}") + return { + "sessions": [], + "total_sessions": 0, + "error": str(e) + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/requirements.txt b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/requirements.txt new file mode 100644 index 00000000..0a640d14 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi>=0.110.0 +uvicorn[standard]>=0.27.0 +pydantic>=2.6.0 +boto3>=1.34.0 +python-dotenv==1.0.0 +requests>=2.31.0 +bedrock-agentcore>=0.1.0 \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/package.json b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/package.json new file mode 100644 index 00000000..e519baf7 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/package.json @@ -0,0 +1,49 @@ +{ + "name": "agentcore-memory-dashboard", + "version": "1.0.0", + "private": true, + "dependencies": { + "@aws-sdk/client-bedrock-agentcore": "^3.887.0", + "@aws-sdk/credential-providers": "^3.887.0", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^13.5.0", + "axios": "^1.4.0", + "date-fns": "^2.30.0", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "recharts": "^2.8.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "start-backend": "./start-backend.sh", + "dev": "concurrently \"npm run start-backend\" \"npm start\"", + "get-credentials": "node scripts/get-aws-credentials.js" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "concurrently": "^9.2.1" + } +} diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/public/index.html b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/public/index.html new file mode 100644 index 00000000..d7c27e23 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/public/index.html @@ -0,0 +1,18 @@ + + + + + + + + + AgentCore Memory Dashboard + + + +
+ + \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js new file mode 100755 index 00000000..cb2d0d53 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js @@ -0,0 +1,79 @@ +#!/usr/bin/env node + +/** + * Helper script to get temporary AWS credentials for the React app + * This script uses the AWS CLI configuration to get temporary credentials + * that can be used in the browser environment. + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +async function getTemporaryCredentials() { + try { + console.log('🔑 Setting up AWS configuration for the dashboard...'); + + // First, check if we can access AWS + try { + const identity = execSync('aws sts get-caller-identity --output json', { encoding: 'utf8' }); + const identityData = JSON.parse(identity); + console.log(`✅ AWS Identity confirmed: ${identityData.Arn}`); + } catch (error) { + throw new Error('AWS CLI not configured or no valid credentials found'); + } + + // Get current AWS region + let region = 'us-east-1'; + try { + const configOutput = execSync('aws configure get region', { encoding: 'utf8' }); + region = configOutput.trim() || 'us-east-1'; + } catch (error) { + console.log('â„šī¸ Using default region: us-east-1'); + } + + // Create environment variables content for frontend configuration + const envContent = ` +# AgentCore Memory Dashboard - Frontend Configuration +# Generated on: ${new Date().toISOString()} +# +# Note: This dashboard uses a backend proxy approach for AWS credentials +# since browser applications cannot directly use AWS CLI credentials for security reasons. + +# Backend API URL +REACT_APP_BACKEND_URL=http://localhost:8000 + +# Dashboard Settings +REACT_APP_MAX_MEMORY_ENTRIES=50 +REACT_APP_REFRESH_INTERVAL=5000 +REACT_APP_DEBUG_MODE=true + +# Note: Memory ID, Actor ID, and Session ID are entered by users through the UI +# No hardcoded values needed here +`.trim(); + + // Write to .env file + const envPath = path.join(__dirname, '..', '.env'); + fs.writeFileSync(envPath, envContent); + + console.log('✅ Frontend configuration saved to .env file'); + console.log('🔧 Dashboard configured to use backend proxy for AWS credentials'); + console.log('🚀 You can now run: npm run dev'); + console.log(''); + console.log('📝 Note: Memory ID, Actor ID, and Session ID will be entered through the UI'); + console.log(' No hardcoded values are stored in configuration files.'); + + } catch (error) { + console.error('❌ Error setting up configuration:'); + console.error(error.message); + console.error('\n💡 Troubleshooting:'); + console.error('1. Run: aws configure list'); + console.error('2. Run: aws sts get-caller-identity'); + console.error('3. Make sure you have AWS CLI installed and configured'); + console.error('4. Ensure your AWS credentials have Bedrock AgentCore permissions'); + process.exit(1); + } +} + +// Run the script +getTemporaryCredentials(); \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/App.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/App.js new file mode 100644 index 00000000..8e4bdea1 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/App.js @@ -0,0 +1,571 @@ +import React, { useState, useEffect } from 'react'; +import { + Clock, + Database, + RefreshCw, + MessageSquare, + Search, + CheckCircle, + Loader +} from 'lucide-react'; +import ShortTermMemoryForm from './components/ShortTermMemoryForm'; +import LongTermMemoryForm from './components/LongTermMemoryForm'; + +function App() { + const [activeTab, setActiveTab] = useState('shortterm'); + const [lastUpdated, setLastUpdated] = useState(new Date()); + const [shortTermMemories, setShortTermMemories] = useState([]); + const [longTermMemories, setLongTermMemories] = useState([]); + // Global configuration for both memory types + const [globalConfig, setGlobalConfig] = useState({ memory_id: '', actor_id: '' }); + const [availableNamespaces, setAvailableNamespaces] = useState([]); + const [isConfigured, setIsConfigured] = useState(false); + const [hasBeenConfigured, setHasBeenConfigured] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + // Short-term memory filter states + const [eventTypeFilter, setEventTypeFilter] = useState('all'); + const [roleFilter, setRoleFilter] = useState('all'); + const [sortBy, setSortBy] = useState('timestamp'); + const [sortOrder, setSortOrder] = useState('desc'); + const [contentSearch, setContentSearch] = useState(''); + + // Long-term memory filter states + const [ltSearchQuery, setLtSearchQuery] = useState(''); + const [ltSortOrder, setLtSortOrder] = useState('desc'); + + const refreshData = () => { + setLoading(true); + setLastUpdated(new Date()); + setTimeout(() => setLoading(false), 1000); // Simulate refresh + }; + + const handleShortTermMemoryFetch = (memories) => { + setShortTermMemories(memories); + console.log('Short-term memories fetched:', memories); + console.log('Current filters:', { eventTypeFilter, roleFilter, contentSearch }); + + // Debug: Show what types and roles are actually in the data + const types = [...new Set(memories.map(m => m.type))]; + const roles = [...new Set(memories.map(m => m.role))]; + console.log('Available types in data:', types); + console.log('Available roles in data:', roles); + }; + + const handleLongTermMemoryFetch = (memories) => { + setLongTermMemories(memories); + console.log('Long-term memories fetched:', memories); + }; + + const handleGlobalConfigUpdate = async (config) => { + setLoading(true); + setError(''); + console.log('Global memory configuration updated:', config); + + // Discover namespaces for long-term memory to validate the Memory ID + try { + const response = await fetch('http://localhost:8000/api/agentcore/listNamespaces', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + memory_id: config.memory_id, + max_results: 100 + }) + }); + + const textResponse = await response.text(); + let data; + + try { + data = JSON.parse(textResponse); + } catch (parseError) { + console.error('Non-JSON response from backend:', textResponse); + throw new Error(`Backend returned non-JSON response (status ${response.status}). Check backend logs.`); + } + + if (!response.ok) { + const errorMessage = data.detail || `Request failed with status ${response.status}`; + throw new Error(errorMessage); + } + + // Configuration is valid, update state + setGlobalConfig(config); + setIsConfigured(true); + setHasBeenConfigured(true); + + if (data.namespaces && data.namespaces.length > 0) { + setAvailableNamespaces(data.namespaces); + console.log('Available namespaces discovered:', data.namespaces); + } + + } catch (err) { + console.error('❌ Memory configuration error:', err); + + // Parse specific error messages from backend + let errorMessage = 'Failed to validate memory configuration'; + + // Handle fetch API errors (not axios) + if (err.message && err.message.includes('Failed to fetch')) { + errorMessage = 'Unable to connect to backend server. Please ensure the backend is running.'; + } else if (err.message) { + errorMessage = err.message; + } + + setError(errorMessage); + + // Don't update configuration if validation failed + setIsConfigured(false); + setHasBeenConfigured(false); + } finally { + setLoading(false); + } + }; + + const handleNamespacesFound = (namespaces) => { + setAvailableNamespaces(namespaces); + console.log('Available namespaces:', namespaces); + }; + + const tabs = [ + { id: 'shortterm', label: 'Short-Term Memory', icon: }, + { id: 'longterm', label: 'Long-Term Memory', icon: } + ]; + + const renderResultsContent = () => { + switch (activeTab) { + case 'shortterm': + return shortTermMemories.length > 0 ? ( +
+
+
+

Short-Term Memory Results

+
+ + {shortTermMemories.filter(memory => { + if (contentSearch && (!memory.content || !memory.content.toLowerCase().includes(contentSearch.toLowerCase()))) { + return false; + } + if (eventTypeFilter !== 'all' && memory.type !== eventTypeFilter) { + return false; + } + if (roleFilter !== 'all' && memory.role !== roleFilter) { + return false; + } + return true; + }).length} + + of {shortTermMemories.length} entries +
+
+
+ + + + {/* Search and Filter Controls */} +
+
+ + setContentSearch(e.target.value)} + className="search-input" + /> +
+ + + +
+ +
+ {(() => { + const filtered = shortTermMemories.filter(memory => { + if (contentSearch && (!memory.content || !memory.content.toLowerCase().includes(contentSearch.toLowerCase()))) { + return false; + } + if (eventTypeFilter !== 'all' && memory.type !== eventTypeFilter) { + return false; + } + if (roleFilter !== 'all') { + const memoryRole = (memory.role || '').toLowerCase(); + if (roleFilter === 'user' && memoryRole !== 'user') { + return false; + } + if (roleFilter === 'assistant' && memoryRole !== 'assistant') { + return false; + } + } + return true; + }); + + console.log(`Filtering: ${shortTermMemories.length} → ${filtered.length} results`); + console.log('Filter breakdown:', { + contentSearch: contentSearch || 'none', + eventTypeFilter, + roleFilter, + totalMemories: shortTermMemories.length, + filteredMemories: filtered.length + }); + + return filtered; + })() + .sort((a, b) => { + if (sortBy === 'timestamp') { + const aTime = new Date(a.timestamp || 0); + const bTime = new Date(b.timestamp || 0); + return sortOrder === 'desc' ? bTime - aTime : aTime - bTime; + } else if (sortBy === 'size') { + const aSize = a.size || 0; + const bSize = b.size || 0; + return sortOrder === 'desc' ? bSize - aSize : aSize - bSize; + } + return 0; + }) + .map((memory, index) => ( +
+
+
+ + {memory.type || 'Event'} + + {memory.event_type && ( + + {memory.event_type} + + )} + {memory.role && ( + + {memory.role} + + )} +
+ + {new Date(memory.timestamp).toLocaleString()} + +
+ +
+ {memory.content} +
+ +
+ {memory.actor_id && Actor: {memory.actor_id}} + {memory.session_id && Session: {memory.session_id}} + {memory.event_id && Event ID: {memory.event_id}} + Size: {memory.size} chars +
+
+ ))} +
+
+ ) : ( +
+ +

No Records Found

+

No short-term memory data found for this session. Try different parameters.

+
+ ); + + case 'longterm': + return longTermMemories.length > 0 ? ( +
+
+
+

Long-Term Memory Results

+
+ + {longTermMemories.filter(memory => { + if (ltSearchQuery && (!memory.content || !memory.content.toLowerCase().includes(ltSearchQuery.toLowerCase()))) { + return false; + } + return true; + }).length} + + of {longTermMemories.length} entries +
+
+
+ + {/* Search and Sort Controls */} +
+
+ + setLtSearchQuery(e.target.value)} + className="search-input" + /> +
+ +
+ +
+ {longTermMemories + .filter(memory => { + if (ltSearchQuery && (!memory.content || !memory.content.toLowerCase().includes(ltSearchQuery.toLowerCase()))) { + return false; + } + return true; + }) + .sort((a, b) => { + const aTime = new Date(a.timestamp || 0); + const bTime = new Date(b.timestamp || 0); + return ltSortOrder === 'desc' ? bTime - aTime : aTime - bTime; + }) + .map((memory, index) => ( +
+
+
+ + {memory.type || 'Record'} + + {memory.namespace && ( + + {memory.namespace} + + )} +
+ + {new Date(memory.timestamp).toLocaleString()} + +
+ +
+ {memory.content} +
+ +
+ {memory.namespace && Namespace: {memory.namespace}} + {memory.score && Score: {memory.score}} + Size: {memory.size} chars +
+
+ ))} +
+
+ ) : ( +
+ +

No Data Found

+

No long-term memory records found for this namespace. Try different filters.

+
+ ); + + default: + return ( +
+ +

Select Memory Type

+

Choose Short-Term or Long-Term memory from the sidebar to get started.

+
+ ); + } + }; + + return ( +
+
+
+
+
+ +
+
+

AgentCore Memory

+ Real-time monitoring dashboard +
+
+ + {/* Memory Configuration in Header */} +
+
+ + setGlobalConfig(prev => ({ ...prev, memory_id: e.target.value }))} + placeholder="your-memory-id-here" + className="header-input" + required + /> +
+
+ + setGlobalConfig(prev => ({ ...prev, actor_id: e.target.value }))} + placeholder="DEFAULT" + className="header-input" + required + /> +
+ + +
+ +
+
+
+ Live +
+ +
+ + {lastUpdated.toLocaleTimeString()} +
+ + +
+ + {/* Error Display */} + {error && ( +
+
âš ī¸
+ {error} +
+ )} +
+ + <> + {/* Sidebar Layout */} + {(globalConfig.memory_id.trim() && globalConfig.actor_id.trim()) && ( +
+ {/* Sidebar */} +
+ {/* Memory Type Selection */} +
+
+ +

Memory Type

+
+ +
+ {tabs.map((tab) => ( + + ))} +
+
+ + {/* Query Parameters */} +
+
+ +

Query Parameters

+
+ + {activeTab === 'shortterm' && ( + + )} + + {activeTab === 'longterm' && ( + + )} +
+
+ + {/* Main Content Area */} +
+ {(globalConfig.memory_id.trim() && globalConfig.actor_id.trim()) ? ( +
+ {renderResultsContent()} +
+ ) : ( +
+ +

Welcome to AgentCore Memory Dashboard

+

Configure your Memory ID and Actor ID in the sidebar to get started.

+
+ )} +
+
+ )} + +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js new file mode 100644 index 00000000..e6c1bfe7 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js @@ -0,0 +1,448 @@ +import { useState, useEffect } from 'react'; +import { Database, Search, AlertCircle, CheckCircle, Loader, Layers, User, MessageCircle, X } from 'lucide-react'; + +const LongTermMemoryForm = ({ onMemoryFetch, memoryConfig, availableNamespaces }) => { + const [formData, setFormData] = useState({ + namespace: '', + max_results: 20 + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + // Modal state for collecting missing values + const [showModal, setShowModal] = useState(false); + const [modalData, setModalData] = useState({ + originalNamespace: '', + missingValues: {}, + resolvedNamespace: '' + }); + + // Helper function to detect placeholders in namespace + const detectPlaceholders = (namespace) => { + const placeholderPattern = /\{(\w+)\}/g; + const placeholders = []; + let match; + + while ((match = placeholderPattern.exec(namespace)) !== null) { + placeholders.push(match[1]); + } + + return placeholders; + }; + + // Helper function to resolve namespace with available values + const resolveNamespace = (namespace, values = {}) => { + let resolved = namespace; + + // Use provided values or fall back to memoryConfig + const allValues = { + actorId: values.actorId || memoryConfig.actor_id, + sessionId: values.sessionId || memoryConfig.session_id, + ...values + }; + + // Replace all placeholders + Object.entries(allValues).forEach(([key, value]) => { + if (value && value.trim()) { + resolved = resolved.replace(new RegExp(`\\{${key}\\}`, 'g'), value); + } + }); + + return resolved; + }; + + // Helper function to get missing values + const getMissingValues = (namespace) => { + const placeholders = detectPlaceholders(namespace); + const missing = {}; + + placeholders.forEach(placeholder => { + const configKey = placeholder === 'actorId' ? 'actor_id' : + placeholder === 'sessionId' ? 'session_id' : placeholder; + + if (!memoryConfig[configKey] || !memoryConfig[configKey].trim()) { + missing[placeholder] = ''; + } + }); + + return missing; + }; + + const handleNamespaceSelection = (originalNamespace) => { + const missingValues = getMissingValues(originalNamespace); + + if (Object.keys(missingValues).length > 0) { + // Show modal to collect missing values + setModalData({ + originalNamespace, + missingValues, + resolvedNamespace: resolveNamespace(originalNamespace) + }); + setShowModal(true); + } else { + // No missing values, proceed directly + const resolvedNamespace = resolveNamespace(originalNamespace); + setFormData(prev => ({ ...prev, namespace: resolvedNamespace })); + handleAutoFetch({ ...formData, namespace: resolvedNamespace }); + } + }; + + const handleInputChange = (field, value) => { + setFormData(prev => ({ + ...prev, + [field]: value + })); + setError(''); + setSuccess(''); + + // Auto-fetch when namespace is selected (for manual input) + if (field === 'namespace' && value.trim()) { + const updatedFormData = { ...formData, [field]: value }; + handleAutoFetch(updatedFormData); + } + }; + + const handleModalValueChange = (key, value) => { + setModalData(prev => ({ + ...prev, + missingValues: { + ...prev.missingValues, + [key]: value + } + })); + }; + + const handleModalSubmit = () => { + const resolvedNamespace = resolveNamespace(modalData.originalNamespace, modalData.missingValues); + setFormData(prev => ({ ...prev, namespace: resolvedNamespace })); + setShowModal(false); + handleAutoFetch({ ...formData, namespace: resolvedNamespace }); + }; + + const handleModalCancel = () => { + setShowModal(false); + setModalData({ + originalNamespace: '', + missingValues: {}, + resolvedNamespace: '' + }); + }; + + const handleAutoFetch = async (currentFormData) => { + if (!currentFormData.namespace.trim()) return; + + setLoading(true); + setError(''); + setSuccess(''); + + const requestPayload = { + ...currentFormData, + memory_id: memoryConfig.memory_id, + content_type: 'all', + sort_by: 'timestamp', + sort_order: 'desc' + }; + + console.log('🚀 Auto-fetching long-term memory:', requestPayload); + + try { + const response = await fetch('http://localhost:8000/api/agentcore/getLongTermMemory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestPayload) + }); + + console.log('📡 Response status:', response.status); + + const textResponse = await response.text(); + let data; + + try { + data = JSON.parse(textResponse); + } catch (parseError) { + console.error('Non-JSON response from backend:', textResponse); + throw new Error(`Backend returned non-JSON response (status ${response.status}). Check backend logs.`); + } + + if (!response.ok) { + console.error('❌ Error response:', data); + const errorMessage = data.detail || `Request failed with status ${response.status}`; + throw new Error(errorMessage); + } + console.log('✅ Response data:', data); + + if (data.memories && data.memories.length > 0) { + setSuccess(`Found ${data.memories.length} long-term memory entries!`); + onMemoryFetch(data.memories); + } else { + setSuccess('Query completed successfully.'); + onMemoryFetch([]); // Pass empty array to show empty state in main area + } + + } catch (err) { + console.error('❌ Long-term memory fetch error:', err); + + // Parse specific error messages from backend + let errorMessage = 'Failed to fetch long-term memory'; + + if (err.response?.status === 404) { + errorMessage = err.response.data?.detail || 'Memory ID or namespace not found. Please verify they exist and you have access permissions.'; + } else if (err.response?.status === 403) { + errorMessage = err.response.data?.detail || 'Access denied. Please check your AWS credentials and permissions.'; + } else if (err.response?.data?.detail) { + errorMessage = err.response.data.detail; + } else if (err.message) { + errorMessage = err.message; + } + + setError(errorMessage); + } finally { + setLoading(false); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!formData.namespace.trim()) { + setError('Namespace is required.'); + return; + } + + setLoading(true); + setError(''); + setSuccess(''); + + const requestPayload = { + ...formData, + memory_id: memoryConfig.memory_id, + content_type: 'all', + sort_by: 'timestamp', + sort_order: 'desc' + }; + + console.log('🚀 Sending long-term memory request:', requestPayload); + + try { + const response = await fetch('http://localhost:8000/api/agentcore/getLongTermMemory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestPayload) + }); + + console.log('📡 Response status:', response.status); + + const textResponse = await response.text(); + let data; + + try { + data = JSON.parse(textResponse); + } catch (parseError) { + console.error('Non-JSON response from backend:', textResponse); + throw new Error(`Backend returned non-JSON response (status ${response.status}). Check backend logs.`); + } + + if (!response.ok) { + console.error('❌ Error response:', data); + const errorMessage = data.detail || `Request failed with status ${response.status}`; + throw new Error(errorMessage); + } + console.log('✅ Response data:', data); + + if (data.memories && data.memories.length > 0) { + setSuccess(`Found ${data.memories.length} long-term memory entries!`); + onMemoryFetch(data.memories); + } else { + setSuccess('Query completed successfully.'); + onMemoryFetch([]); // Pass empty array to show empty state in main area + } + + } catch (err) { + console.error('❌ Long-term memory submit error:', err); + + // Parse specific error messages from backend + let errorMessage = 'Failed to fetch long-term memory'; + + if (err.response?.status === 404) { + errorMessage = err.response.data?.detail || 'Memory ID or namespace not found. Please verify they exist and you have access permissions.'; + } else if (err.response?.status === 403) { + errorMessage = err.response.data?.detail || 'Access denied. Please check your AWS credentials and permissions.'; + } else if (err.response?.data?.detail) { + errorMessage = err.response.data.detail; + } else if (err.message) { + errorMessage = err.message; + } + + setError(errorMessage); + } finally { + setLoading(false); + } + }; + + console.log('🔍 LongTermMemoryForm render:', { + availableNamespaces, + availableNamespacesLength: availableNamespaces.length, + memoryConfig + }); + + return ( +
+ + +
+
+
+ + {availableNamespaces.length > 0 ? ( +
+ {availableNamespaces.map((ns, index) => { + // Check if this namespace has missing values + const missingValues = getMissingValues(ns.namespace); + const hasMissingValues = Object.keys(missingValues).length > 0; + + // For display, show resolved namespace only if no values are missing + const displayNamespace = hasMissingValues ? ns.namespace : resolveNamespace(ns.namespace); + + const isSelected = formData.namespace === displayNamespace || + (!hasMissingValues && formData.namespace === resolveNamespace(ns.namespace)); + + return ( +
handleNamespaceSelection(ns.namespace)} + > +
+ + {ns.type === 'SEMANTIC' ? 'Facts' : + ns.type === 'USER_PREFERENCE' ? 'Preferences' : + ns.type === 'SUMMARIZATION' ? 'Summaries' : ns.type} + +
+
+ {displayNamespace.split('/').slice(0, -1).join('/') || displayNamespace} + {hasMissingValues && ( + (requires values) + )} +
+
+ ); + })} +
+ ) : ( + handleInputChange('namespace', e.target.value)} + placeholder="e.g., your-namespace/facts, company/user/preferences" + className="form-input" + required + /> + )} +
+ {availableNamespaces.length > 0 + ? `Select from ${availableNamespaces.length} available namespaces discovered from your memory strategies` + : 'Specify the exact namespace to query (e.g., your-namespace/facts, company/user/preferences)' + } +
+
+ + + + {loading && ( +
+ + Loading memory data... +
+ )} +
+ + {/* Status Messages */} + {error && ( +
+ + {error} +
+ )} + + {success && ( +
+ + {success} +
+ )} +
+ + {/* Modal for collecting missing values */} + {showModal && ( +
+
+
+

Complete Namespace Configuration

+ +
+ +
+

This namespace requires additional values:

+
+ Namespace: {modalData.originalNamespace} +
+ +
+ {Object.entries(modalData.missingValues).map(([key, value]) => ( +
+ + handleModalValueChange(key, e.target.value)} + placeholder={key === 'actorId' ? 'e.g., DEFAULT, user123' : + key === 'sessionId' ? 'e.g., session-abc123' : `Enter ${key}`} + className="form-input" + /> +
+ ))} +
+ +
+ Resolved namespace: + {resolveNamespace(modalData.originalNamespace, modalData.missingValues)} +
+
+ +
+ + +
+
+
+ )} +
+ ); +}; + +export default LongTermMemoryForm; \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/ShortTermMemoryForm.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/ShortTermMemoryForm.js new file mode 100644 index 00000000..7a645ce9 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/ShortTermMemoryForm.js @@ -0,0 +1,237 @@ +import React, { useState, useEffect } from 'react'; +import { User, MessageCircle, Search, AlertCircle, CheckCircle, Loader, HelpCircle, Clock, ChevronDown, Database } from 'lucide-react'; + +const ShortTermMemoryForm = ({ onMemoryFetch, memoryConfig }) => { + const [formData, setFormData] = useState({ + session_id: '', + max_results: 20 + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const [searchHistory, setSearchHistory] = useState([]); + const [showActorHistory, setShowActorHistory] = useState(false); + const [showSessionHistory, setShowSessionHistory] = useState(false); + + // Load search history from localStorage on component mount + useEffect(() => { + const savedHistory = localStorage.getItem('agentcore-search-history'); + if (savedHistory) { + try { + setSearchHistory(JSON.parse(savedHistory)); + } catch (e) { + console.error('Failed to parse search history:', e); + } + } + }, []); + + // No need for useEffect since we get memoryConfig as prop + + // Save search to history + const saveToHistory = (actorId, sessionId) => { + const newEntry = { + actor_id: actorId, + session_id: sessionId, + timestamp: new Date().toISOString(), + id: Date.now() + }; + + const updatedHistory = [ + newEntry, + ...searchHistory.filter(item => + !(item.actor_id === actorId && item.session_id === sessionId) + ) + ].slice(0, 10); // Keep only last 10 searches + + setSearchHistory(updatedHistory); + localStorage.setItem('agentcore-search-history', JSON.stringify(updatedHistory)); + }; + + const handleInputChange = (field, value) => { + setFormData(prev => ({ + ...prev, + [field]: value + })); + setError(''); + setSuccess(''); + }; + + const handleHistorySelect = (historyItem) => { + setFormData(prev => ({ + ...prev, + actor_id: historyItem.actor_id, + session_id: historyItem.session_id + })); + setShowActorHistory(false); + setShowSessionHistory(false); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!memoryConfig.actor_id || !memoryConfig.actor_id.trim()) { + setError('Actor ID is required in configuration'); + return; + } + + if (!formData.session_id.trim()) { + setError('Session ID is required'); + return; + } + + setLoading(true); + setError(''); + setSuccess(''); + + try { + const response = await fetch('http://localhost:8000/api/agentcore/getShortTermMemory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...formData, + memory_id: memoryConfig.memory_id, + actor_id: memoryConfig.actor_id + }) + }); + + const textResponse = await response.text(); + let data; + + try { + data = JSON.parse(textResponse); + } catch (parseError) { + console.error('Non-JSON response from backend:', textResponse); + throw new Error(`Backend returned non-JSON response (status ${response.status}). Check backend logs.`); + } + + if (!response.ok) { + const errorMessage = data.detail || `Request failed with status ${response.status}`; + throw new Error(errorMessage); + } + + // data is already parsed above + + if (data.memories && data.memories.length > 0) { + setSuccess(`Found ${data.memories.length} short-term memory entries!`); + onMemoryFetch(data.memories); + // Save successful search to history + saveToHistory(formData.actor_id, formData.session_id); + } else { + setSuccess('Query completed successfully.'); + onMemoryFetch([]); // Pass empty array to show empty state in main area + } + + } catch (err) { + console.error('❌ Short-term memory fetch error:', err); + + // Parse specific error messages from backend + let errorMessage = 'Failed to fetch short-term memory'; + + if (err.response?.status === 404) { + errorMessage = err.response.data?.detail || 'Memory ID not found. Please verify the Memory ID exists and you have access permissions.'; + } else if (err.response?.status === 403) { + errorMessage = err.response.data?.detail || 'Access denied. Please check your AWS credentials and permissions.'; + } else if (err.response?.data?.detail) { + errorMessage = err.response.data.detail; + } else if (err.message) { + errorMessage = err.message; + } + + setError(errorMessage); + } finally { + setLoading(false); + } + }; + + const clearHistory = () => { + setSearchHistory([]); + localStorage.removeItem('agentcore-search-history'); + }; + + const getUniqueActorIds = () => { + const actors = [...new Set(searchHistory.map(item => item.actor_id))]; + return actors.slice(0, 5); + }; + + const getUniqueSessionIds = () => { + const sessions = [...new Set(searchHistory.map(item => item.session_id))]; + return sessions.slice(0, 5); + }; + + return ( +
+ + +
+
+
+ + handleInputChange('session_id', e.target.value)} + placeholder="e.g., session-abc123, conv-456def" + className="form-input" + required + /> +
+ Specify the exact session identifier to query (e.g., session-abc123, conv-456def) +
+
+ + + + + +
+ + +
 
+
+
+ + {/* Status Messages */} + {error && ( +
+ + {error} +
+ )} + + {success && ( +
+ + {success} +
+ )} + + + +
+
+ ); +}; + +export default ShortTermMemoryForm; \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.css b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.css new file mode 100644 index 00000000..134e8849 --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.css @@ -0,0 +1,1117 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + +/* Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #f8fafc; + color: #1e293b; + line-height: 1.6; +} + +/* Layout */ +.dashboard { + min-height: 100vh; + background: linear-gradient(135deg, #f8fafc 0%, #ffffff 50%, #f1f5f9 100%); +} + +.container { + max-width: 95%; + margin: 0 auto; + padding: 24px; + min-width: 1200px; +} + +/* Responsive container for different screen sizes */ +@media (min-width: 1920px) { + .container { + max-width: 1800px; + } +} + +@media (min-width: 2560px) { + .container { + max-width: 2200px; + } +} + +/* Header */ +.modern-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + margin-bottom: 32px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(12px); + border-radius: 16px; + border: 1px solid rgba(226, 232, 240, 0.6); + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.04); + gap: 24px; +} + +.header-brand { + display: flex; + align-items: center; + gap: 16px; +} + +.brand-icon { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + background: linear-gradient(135deg, #3b82f6, #2563eb); + border-radius: 12px; + color: white; +} + +.brand-text h1 { + font-size: 1.5rem; + font-weight: 700; + color: #1e293b; + margin: 0; +} + +.brand-subtitle { + font-size: 0.875rem; + color: #64748b; + font-weight: 500; +} + +/* Header Configuration */ +.header-config { + display: flex; + align-items: center; + gap: 16px; +} + +/* Responsive header for larger screens */ +@media (min-width: 1920px) { + .header-config { + gap: 24px; + } + + .header-input { + min-width: 180px; + } + + .config-field-inline:first-child .header-input { + min-width: 260px; + } +} + +@media (min-width: 2560px) { + .header-config { + gap: 32px; + } + + .header-input { + min-width: 200px; + padding: 10px 16px; + font-size: 1rem; + } + + .config-field-inline:first-child .header-input { + min-width: 300px; + } +} + +.config-field-inline { + display: flex; + flex-direction: column; + gap: 4px; +} + +.config-field-inline label { + font-size: 0.75rem; + font-weight: 600; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.header-input { + padding: 8px 12px; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 0.875rem; + background: rgba(255, 255, 255, 0.9); + color: #1e293b; + transition: all 0.2s ease; + min-width: 160px; +} + +/* Specific width for Memory ID field */ +.config-field-inline:first-child .header-input { + min-width: 220px; +} + +.header-input:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.header-update-btn { + padding: 8px 16px; + background: #3b82f6; + color: white; + border: none; + border-radius: 6px; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + margin-top: 18px; +} + +.header-update-btn:hover:not(:disabled) { + background: #2563eb; + transform: translateY(-1px); +} + +.header-update-btn:disabled { + background: #9ca3af; + cursor: not-allowed; + transform: none; +} + +/* Header Actions */ +.header-actions { + display: flex; + align-items: center; + gap: 16px; +} + +.status-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.2); + border-radius: 20px; + color: #16a34a; + font-size: 0.875rem; + font-weight: 600; +} + +.status-dot { + width: 8px; + height: 8px; + background: #16a34a; + border-radius: 50%; + animation: pulse 2s infinite; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} + +.last-updated-compact { + display: flex; + align-items: center; + gap: 6px; + color: #64748b; + font-size: 0.875rem; +} + +.refresh-button-modern { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.9); + border: 1px solid #cbd5e1; + border-radius: 8px; + color: #64748b; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.refresh-button-modern:hover:not(:disabled) { + background: #f8fafc; + border-color: #94a3b8; + color: #475569; +} + +.refresh-button-modern:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Dashboard Layout */ +.dashboard-layout { + display: flex; + gap: 24px; + height: calc(100vh - 200px); + max-height: calc(100vh - 200px); + min-height: 600px; +} + +/* Sidebar */ +.sidebar { + width: 350px; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 20px; +} + +/* Responsive sidebar for larger screens */ +@media (min-width: 1920px) { + .sidebar { + width: 400px; + } +} + +@media (min-width: 2560px) { + .sidebar { + width: 450px; + } +} + +.sidebar-section { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 12px; + padding: 20px; + backdrop-filter: blur(10px); +} + +.sidebar-section-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid #e2e8f0; +} + +.sidebar-section-header h3 { + font-size: 1rem; + font-weight: 600; + color: #1e293b; + margin: 0; +} + +/* Memory Type Selector */ +.memory-type-selector { + display: flex; + flex-direction: column; + gap: 8px; +} + +.memory-type-btn { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: transparent; + border: 1px solid #e2e8f0; + border-radius: 8px; + color: #64748b; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; + font-weight: 500; +} + +.memory-type-btn:hover { + border-color: #cbd5e1; + background: rgba(248, 250, 252, 0.8); +} + +.memory-type-btn.active { + background: linear-gradient(135deg, #3b82f6, #2563eb); + border-color: #3b82f6; + color: white; + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); +} + +.tab-icon { + display: flex; + align-items: center; +} + +.tab-label { + font-size: 0.9rem; +} + +/* Main Area */ +.main-area { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + +.results-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + +/* Welcome Screen */ +.welcome-screen { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; + color: #64748b; + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 12px; + backdrop-filter: blur(10px); + padding: 60px 20px; +} + +.welcome-icon { + color: #3b82f6; + margin-bottom: 24px; +} + +.welcome-screen h2 { + font-size: 1.5rem; + font-weight: 600; + color: #1e293b; + margin-bottom: 12px; +} + +.welcome-screen p { + font-size: 1rem; + line-height: 1.5; + max-width: 400px; +} + +/* Results Section */ +.results-section { + display: flex; + flex-direction: column; + height: 100%; + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 12px; + backdrop-filter: blur(10px); + overflow: hidden; + min-height: 0; +} + +.results-header { + padding: 20px; + border-bottom: 1px solid #e2e8f0; + flex-shrink: 0; +} + +.results-title { + display: flex; + align-items: center; + justify-content: space-between; +} + +.results-title h2 { + font-size: 1.25rem; + font-weight: 600; + color: #1e293b; + margin: 0; +} + +.results-count { + display: flex; + align-items: center; + gap: 8px; +} + +.count-badge { + background: #3b82f6; + color: white; + padding: 4px 12px; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 600; +} + +.count-text { + color: #64748b; + font-size: 0.875rem; +} + +/* Results Controls */ +.results-controls { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: rgba(248, 250, 252, 0.8); + border-bottom: 1px solid #e2e8f0; + flex-shrink: 0; +} + +.search-control { + position: relative; + flex: 1; + max-width: 400px; +} + +.search-icon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #9ca3af; +} + +.search-input { + width: 100%; + padding: 8px 12px 8px 40px; + border: 1px solid #d1d5db; + border-radius: 6px; + font-size: 0.875rem; + background: white; + transition: all 0.2s ease; +} + +.search-input:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.filter-select, +.sort-select { + padding: 8px 12px; + border: 1px solid #d1d5db; + border-radius: 6px; + font-size: 0.875rem; + background: white; + color: #374151; + cursor: pointer; + transition: all 0.2s ease; +} + +.filter-select:focus, +.sort-select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +/* Memory List - The key scrollable container */ +.memory-list { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 16px 20px; + display: flex; + flex-direction: column; + gap: 16px; + min-height: 0; +} + +/* Responsive memory list for larger screens */ +@media (min-width: 1920px) { + .memory-list { + padding: 20px 32px; + gap: 20px; + } +} + +@media (min-width: 2560px) { + .memory-list { + padding: 24px 40px; + gap: 24px; + } +} + +/* Memory Items */ +.memory-item { + background: rgba(255, 255, 255, 0.9); + border: 1px solid #e5e7eb; + border-radius: 8px; + padding: 16px; + transition: all 0.2s ease; +} + +.memory-item:hover { + border-color: #d1d5db; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.memory-item.shortterm { + border-left: 4px solid #3b82f6; +} + +.memory-item.longterm { + border-left: 4px solid #3b82f6; +} + +.memory-item-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.memory-badges { + display: flex; + align-items: center; + gap: 8px; +} + +.memory-type, +.event-type-badge, +.role-badge, +.namespace-badge { + padding: 4px 8px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; +} + +.memory-type.conversation { + background: #dbeafe; + color: #1d4ed8; +} + +.memory-type.event { + background: #fef3c7; + color: #d97706; +} + +.memory-type.record { + background: #dbeafe; + color: #1d4ed8; +} + +.event-type-badge { + background: #f3f4f6; + color: #6b7280; +} + +.role-badge { + background: #ede9fe; + color: #7c3aed; +} + +.namespace-badge { + background: #dbeafe; + color: #1d4ed8; +} + +.memory-timestamp { + color: #6b7280; + font-size: 0.875rem; +} + +.memory-content { + color: #374151; + line-height: 1.6; + margin-bottom: 12px; + word-wrap: break-word; +} + +/* Responsive memory content for larger screens */ +@media (min-width: 1920px) { + .memory-content { + font-size: 1rem; + line-height: 1.7; + margin-bottom: 16px; + } +} + +@media (min-width: 2560px) { + .memory-content { + font-size: 1.1rem; + line-height: 1.8; + margin-bottom: 20px; + } +} + +.memory-metadata { + display: flex; + align-items: center; + gap: 16px; + color: #6b7280; + font-size: 0.75rem; + padding-top: 12px; + border-top: 1px solid #f3f4f6; +} + +.memory-metadata span { + background: #f9fafb; + padding: 2px 6px; + border-radius: 4px; +} + +/* Empty State */ +.empty-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 12px; + backdrop-filter: blur(10px); +} + +.empty-icon { + color: #d1d5db; + margin-bottom: 16px; +} + +.empty-state h3 { + font-size: 1.125rem; + font-weight: 600; + color: #374151; + margin-bottom: 8px; +} + +.empty-state p { + color: #6b7280; + font-size: 0.875rem; +} + +/* Forms */ +.memory-form { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + backdrop-filter: blur(10px); +} + +.form-grid { + display: flex; + flex-direction: column; + gap: 20px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.form-group label { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9rem; + font-weight: 600; + color: #374151; +} + +.form-input { + width: 100%; + padding: 10px 15px; + background: rgba(248, 250, 252, 0.8); + border: 1px solid #cbd5e1; + border-radius: 8px; + color: #1e293b; + font-size: 0.9rem; + transition: all 0.2s ease; +} + +.form-input:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.form-help { + font-size: 0.8rem; + color: #64748b; + line-height: 1.4; +} + +.submit-button-inline { + padding: 10px 20px; + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: white; + border: none; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; + height: fit-content; +} + +.submit-button-inline:hover:not(:disabled) { + background: linear-gradient(135deg, #2563eb, #1d4ed8); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); +} + +.submit-button-inline:disabled { + background: #9ca3af; + cursor: not-allowed; + transform: none; +} + +.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +/* Status Messages */ +.status-message { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 500; + margin-top: 15px; +} + +.status-message.error { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + color: #dc2626; +} + +.status-message.success { + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.3); + color: #16a34a; +} + +/* Error Banner for Header */ +.error-banner-modern { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: 8px; + color: #dc2626; + font-size: 0.9rem; + font-weight: 500; + margin-top: 16px; + backdrop-filter: blur(10px); +} + +.error-banner-modern .error-icon { + font-size: 1.1rem; +} + +.error-banner-modern span { + flex: 1; + line-height: 1.4; +} + +/* Namespace Selector */ +.namespace-selector { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 300px; + overflow-y: auto; +} + +.namespace-option { + padding: 12px; + border: 1px solid #e2e8f0; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + background: rgba(255, 255, 255, 0.9); +} + +.namespace-option:hover { + border-color: #cbd5e1; + background: rgba(248, 250, 252, 0.9); +} + +.namespace-option.selected { + border-color: #3b82f6; + background: rgba(59, 130, 246, 0.05); +} + +.namespace-type { + margin-bottom: 8px; +} + +.type-badge { + padding: 4px 8px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; +} + +.type-badge.semantic { + background: #dbeafe; + color: #1d4ed8; +} + +.type-badge.user_preference { + background: #ede9fe; + color: #7c3aed; +} + +.type-badge.summarization { + background: #dbeafe; + color: #1d4ed8; +} + +.namespace-path { + font-weight: 600; + color: #1e293b; + margin-bottom: 4px; +} + +.namespace-full { + font-size: 0.875rem; + color: #64748b; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; +} + +.missing-values-indicator { + color: #f59e0b; + font-size: 12px; + font-style: italic; + margin-left: 8px; +} + +.namespace-option:hover .missing-values-indicator { + color: #d97706; +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: white; + border-radius: 8px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px 16px; + border-bottom: 1px solid #e5e7eb; +} + +.modal-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #111827; +} + +.modal-close { + background: none; + border: none; + cursor: pointer; + padding: 4px; + border-radius: 4px; + color: #6b7280; + transition: all 0.2s; +} + +.modal-close:hover { + background: #f3f4f6; + color: #374151; +} + +.modal-body { + padding: 20px 24px; +} + +.modal-body p { + margin: 0 0 16px; + color: #6b7280; +} + +.namespace-preview { + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 6px; + padding: 12px; + margin: 16px 0; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-size: 14px; +} + +.missing-values-form { + margin: 20px 0; +} + +.missing-values-form .form-group { + margin-bottom: 16px; +} + +.missing-values-form .form-group:last-child { + margin-bottom: 0; +} + +.resolved-preview { + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 6px; + padding: 12px; + margin-top: 16px; +} + +.resolved-preview code { + display: block; + margin-top: 8px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-size: 14px; + color: #1e40af; + background: none; + padding: 0; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 12px; + padding: 16px 24px 20px; + border-top: 1px solid #e5e7eb; +} + +.modal-btn { + padding: 8px 16px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: 1px solid transparent; +} + +.modal-btn.cancel { + background: white; + color: #6b7280; + border-color: #d1d5db; +} + +.modal-btn.cancel:hover { + background: #f9fafb; + color: #374151; +} + +.modal-btn.submit { + background: #3b82f6; + color: white; +} + +.modal-btn.submit:hover:not(:disabled) { + background: #2563eb; +} + +.modal-btn.submit:disabled { + background: #9ca3af; + cursor: not-allowed; +} + +/* S +hort-term Memory Form Specific Layout */ +.short-term-memory-form .form-grid { + display: flex; + flex-direction: column; + gap: 20px; +} + +.short-term-memory-form .form-group { + width: 100%; +} + +.short-term-memory-form .submit-button-inline { + width: 100%; + justify-content: center; + margin-top: 10px; + padding: 12px 20px; +} + +/ * Red asterisks for required fields */ .config-field-inline label { + position: relative; +} + +.config-field-inline label:after { + content: ''; +} + +/* Target labels that contain asterisk */ +.config-field-inline label:contains('*') { + color: #64748b; +} + +/* Make asterisks red */ +.header-config label { + color: #64748b; +} + +.header-config label:after { + content: ''; +} + +/* Red asterisk styling */ +.required-asterisk { + color: #ef4444; + font-weight: bold; +} \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.js new file mode 100644 index 00000000..1675893a --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/start-backend.sh b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/start-backend.sh new file mode 100755 index 00000000..73e8eb4c --- /dev/null +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/start-backend.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Start AgentCore Memory Dashboard Backend +echo "🚀 Starting AgentCore Memory Dashboard Backend..." + +# Check if we're in the right directory +if [ ! -f "backend/app.py" ]; then + echo "❌ Error: backend/app.py not found. Please run this script from the agentcore-memory-dashboard directory." + exit 1 +fi + +# Create virtual environment if it doesn't exist +if [ ! -d "backend/venv" ]; then + echo "đŸ“Ļ Creating Python virtual environment..." + cd backend + python3 -m venv venv + cd .. +fi + +# Activate virtual environment +echo "🔧 Activating virtual environment..." +source backend/venv/bin/activate + +# Install dependencies +echo "đŸ“Ļ Installing Python dependencies..." +cd backend +pip install -r requirements.txt + +# Check if bedrock-agentcore is available +echo "🔍 Checking AgentCore Memory SDK..." +python -c " +try: + from bedrock_agentcore.memory import MemoryClient + print('✅ bedrock-agentcore SDK is available') +except ImportError: + print('âš ī¸ bedrock-agentcore SDK not found') + print(' The backend will use mock data for development') + print(' To install: pip install bedrock-agentcore') +" + +# Start the backend server +echo "🚀 Starting FastAPI backend server..." +echo "📍 Backend will be available at: http://localhost:8000" +echo "📖 API documentation at: http://localhost:8000/docs" +echo "" +echo "Press Ctrl+C to stop the server" + +uvicorn app:app --host 0.0.0.0 --port 8000 --reload \ No newline at end of file From ea4a386f0eb0639d35caab5784da61acc3a26968 Mon Sep 17 00:00:00 2001 From: Anil Gurrala <136643863+visitani@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:08:52 -0400 Subject: [PATCH 2/6] Update README.md Signed-off-by: Anil Gurrala <136643863+visitani@users.noreply.github.com> --- .../03-advanced-patterns/03-memory-browser/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md index 311ebab3..38a37499 100644 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/README.md @@ -107,7 +107,7 @@ Your AWS user/role needs these permissions: ```bash # Clone the repository git clone -cd agentcore-memory-dashboard +cd 01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser # Install frontend dependencies (this will download ~200MB of packages) npm install From 68ab58cfa5368bff685b8ad9bf7050ac47d1a674 Mon Sep 17 00:00:00 2001 From: Anil Gurrala Date: Tue, 7 Oct 2025 22:23:15 -0400 Subject: [PATCH 3/6] resolved comments --- .vscode/settings.json | 3 ++ .../03-memory-browser/backend/app.py | 50 ++++++++++++------- .../scripts/get-aws-credentials.js | 9 ++-- .../src/components/LongTermMemoryForm.js | 28 ++++++++--- 4 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5480842b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "kiroAgent.configureMCP": "Disabled" +} \ No newline at end of file diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py index 324fbf37..90730069 100644 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py @@ -411,7 +411,8 @@ async def get_short_term_memory(query: ShortTermMemoryQuery): except Exception as e: logger.error(f"Error getting short-term memory: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get short-term memory: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to get short-term memory: {clean_error}") class EventQuery(BaseModel): event_id: str @@ -503,7 +504,8 @@ async def search_event_by_id(query: EventSearchQuery): except Exception as e: logger.error(f"Error searching for event: {e}") - raise HTTPException(status_code=500, detail=f"Failed to search for event: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to search for event: {clean_error}") @app.post("/api/agentcore/getEventById") async def get_event_by_id(query: EventQuery): @@ -614,11 +616,13 @@ async def get_event_by_id(query: EventQuery): } except Exception as e: logger.error(f"Alternative event search failed: {e}") - raise HTTPException(status_code=500, detail=f"Failed to retrieve event: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to retrieve event: {clean_error}") except Exception as e: logger.error(f"Error getting event by ID: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get event: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to get event: {clean_error}") @app.post("/api/agentcore/listNamespaces") async def list_namespaces(query: MemoryQuery): @@ -689,7 +693,8 @@ async def list_namespaces(query: MemoryQuery): except Exception as e: logger.error(f"Error listing namespaces: {e}") - raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {clean_error}") @app.post("/api/agentcore/getLongTermMemory") async def get_long_term_memory(query: LongTermMemoryQuery): @@ -821,7 +826,8 @@ async def get_long_term_memory(query: LongTermMemoryQuery): except Exception as e: logger.error(f"Error getting long-term memory: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get long-term memory: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to get long-term memory: {clean_error}") @app.post("/api/agentcore/getMemoryEntries") async def get_memory_entries(query: MemoryQuery): @@ -912,12 +918,14 @@ async def get_memory_entries(query: MemoryQuery): except Exception as e: logger.warning(f"Could not list memory records from namespace '{query.namespace}': {e}") + # Clean error message to avoid exposing internal details + clean_error = clean_aws_error_message(str(e)) return { "memories": [], "total_count": 0, "source": "list_memory_records", "memory_id": memory_id, - "error": f"Failed to access namespace '{query.namespace}': {str(e)}" + "error": f"Failed to access namespace '{query.namespace}': {clean_error}" } @@ -932,7 +940,8 @@ async def get_memory_entries(query: MemoryQuery): except Exception as e: logger.error(f"Error listing memory records: {e}") - raise HTTPException(status_code=500, detail=f"Failed to list memory records: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to list memory records: {clean_error}") class MemoryIdValidationQuery(BaseModel): memory_id: str @@ -1006,7 +1015,7 @@ async def list_namespaces(query: ListNamespacesQuery): "namespace": namespace, "type": strategy_type, "count": 0, - "sample_content": f"Unable to retrieve sample: {str(e)}" + "sample_content": f"Unable to retrieve sample: {clean_aws_error_message(str(e))}" }) logger.warning(f"âš ī¸ Found namespace: {namespace} (type: {strategy_type}) but couldn't retrieve samples: {e}") @@ -1083,7 +1092,7 @@ async def list_namespaces(query: ListNamespacesQuery): "namespaces": found_namespaces, "total_found": len(found_namespaces), "method": "fallback_pattern_based", - "message": f"Found {len(found_namespaces)} namespaces using fallback method (get_memory_strategies failed: {str(e)})" + "message": f"Found {len(found_namespaces)} namespaces using fallback method (get_memory_strategies failed: {clean_aws_error_message(str(e))})" } except Exception as e2: @@ -1092,12 +1101,13 @@ async def list_namespaces(query: ListNamespacesQuery): "memory_id": memory_id, "namespaces": [], "total_found": 0, - "error": f"Both get_memory_strategies and fallback failed: {str(e)} / {str(e2)}" + "error": f"Both get_memory_strategies and fallback failed: {clean_aws_error_message(str(e))} / {clean_aws_error_message(str(e2))}" } except Exception as e: logger.error(f"Error listing namespaces: {e}") - raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {clean_error}") @app.post("/api/agentcore/validateMemoryId") async def validate_memory_id(query: MemoryIdValidationQuery): @@ -1128,12 +1138,13 @@ async def validate_memory_id(query: MemoryIdValidationQuery): "valid": False, "memory_id": query.memory_id, "accessible": False, - "message": f"Memory ID validation failed: {str(e)}" + "message": f"Memory ID validation failed: {clean_aws_error_message(str(e))}" } except Exception as e: logger.error(f"Error validating memory ID: {e}") - raise HTTPException(status_code=500, detail=f"Failed to validate memory ID: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to validate memory ID: {clean_error}") class AddMemoryEntryQuery(BaseModel): session_id: str @@ -1179,7 +1190,8 @@ async def search_memory_entries(query: SearchMemoryEntriesQuery): } except Exception as e: logger.error(f"Error searching memory entries: {e}") - raise HTTPException(status_code=500, detail=f"Failed to search memory entries: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to search memory entries: {clean_error}") @app.post("/api/agentcore/listNamespaces") async def list_namespaces(request: dict): @@ -1259,11 +1271,13 @@ async def list_namespaces(request: dict): except Exception as e: logger.error(f"Failed to get memory strategies: {e}") - raise HTTPException(status_code=500, detail=f"Failed to get namespaces: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to get namespaces: {clean_error}") except Exception as e: logger.error(f"Error listing namespaces: {e}") - raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {str(e)}") + clean_error = clean_aws_error_message(str(e)) + raise HTTPException(status_code=500, detail=f"Failed to list namespaces: {clean_error}") @app.get("/api/agentcore/listSessions") async def list_sessions(): @@ -1282,7 +1296,7 @@ async def list_sessions(): return { "sessions": [], "total_sessions": 0, - "error": str(e) + "error": clean_aws_error_message(str(e)) } if __name__ == "__main__": diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js index cb2d0d53..97fd5c2d 100755 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/scripts/get-aws-credentials.js @@ -24,12 +24,15 @@ async function getTemporaryCredentials() { } // Get current AWS region - let region = 'us-east-1'; + let region = null; try { const configOutput = execSync('aws configure get region', { encoding: 'utf8' }); - region = configOutput.trim() || 'us-east-1'; + region = configOutput.trim(); + if (!region) { + throw new Error('No AWS region configured. Run: aws configure set region '); + } } catch (error) { - console.log('â„šī¸ Using default region: us-east-1'); + throw new Error('AWS region not configured. Run: aws configure set region '); } // Create environment variables content for frontend configuration diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js index e6c1bfe7..9b2dc9b2 100644 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/src/components/LongTermMemoryForm.js @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; -import { Database, Search, AlertCircle, CheckCircle, Loader, Layers, User, MessageCircle, X } from 'lucide-react'; +import { useState } from 'react'; +import { AlertCircle, CheckCircle, Loader, Layers, User, MessageCircle, X } from 'lucide-react'; const LongTermMemoryForm = ({ onMemoryFetch, memoryConfig, availableNamespaces }) => { const [formData, setFormData] = useState({ @@ -321,10 +321,26 @@ const LongTermMemoryForm = ({ onMemoryFetch, memoryConfig, availableNamespaces } onClick={() => handleNamespaceSelection(ns.namespace)} >
- - {ns.type === 'SEMANTIC' ? 'Facts' : - ns.type === 'USER_PREFERENCE' ? 'Preferences' : - ns.type === 'SUMMARIZATION' ? 'Summaries' : ns.type} + + {(() => { + // Standard AgentCore strategy types + const standardTypes = { + 'SEMANTIC': 'Facts', + 'USER_PREFERENCE': 'Preferences', + 'SUMMARIZATION': 'Summaries' + }; + + // If it's a standard type, use the friendly name + if (standardTypes[ns.type]) { + return standardTypes[ns.type]; + } + + // For custom types, format them nicely + return ns.type + .split('_') + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + })()}
From 2f9e15475bfa8b50167aeee290ad4da9033c9d2a Mon Sep 17 00:00:00 2001 From: Anil Gurrala Date: Tue, 14 Oct 2025 00:36:40 -0400 Subject: [PATCH 4/6] removed memory id from exception messages --- .../03-advanced-patterns/03-memory-browser/backend/app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py index 90730069..ddb4e1fa 100644 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py @@ -496,7 +496,6 @@ async def search_event_by_id(query: EventSearchQuery): return { "event": None, "found": False, - "memory_id": memory_id, "event_id": query.event_id, "error": f"Event {query.event_id} not found in searched sessions", "searched_combinations": len(actor_ids_to_try) * len(session_ids_to_try) @@ -593,7 +592,6 @@ async def get_event_by_id(query: EventQuery): return { "event": None, "found": False, - "memory_id": memory_id, "event_id": query.event_id, "error": "Event not found" } @@ -610,7 +608,6 @@ async def get_event_by_id(query: EventQuery): return { "event": None, "found": False, - "memory_id": memory_id, "event_id": query.event_id, "error": "Direct event retrieval not supported. Need actor_id and session_id to search events." } @@ -924,7 +921,6 @@ async def get_memory_entries(query: MemoryQuery): "memories": [], "total_count": 0, "source": "list_memory_records", - "memory_id": memory_id, "error": f"Failed to access namespace '{query.namespace}': {clean_error}" } @@ -1098,7 +1094,6 @@ async def list_namespaces(query: ListNamespacesQuery): except Exception as e2: logger.warning(f"Fallback method also failed: {e2}") return { - "memory_id": memory_id, "namespaces": [], "total_found": 0, "error": f"Both get_memory_strategies and fallback failed: {clean_aws_error_message(str(e))} / {clean_aws_error_message(str(e2))}" From c42af189ed2691b1cf98d96967b00205c6620f4c Mon Sep 17 00:00:00 2001 From: Anil Gurrala Date: Fri, 17 Oct 2025 13:57:58 -0400 Subject: [PATCH 5/6] removed the sys --- .../03-advanced-patterns/03-memory-browser/backend/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py index ddb4e1fa..d7d3d4c8 100644 --- a/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py +++ b/01-tutorials/04-AgentCore-memory/03-advanced-patterns/03-memory-browser/backend/app.py @@ -10,7 +10,6 @@ from typing import Optional, List, Dict, Any import os import logging -import sys import re from datetime import datetime from botocore.exceptions import ClientError From e181c9da9136dbb1cd9bd98cd18f3951273fb516 Mon Sep 17 00:00:00 2001 From: Anil Gurrala Date: Fri, 17 Oct 2025 14:14:15 -0400 Subject: [PATCH 6/6] Remove .vscode/settings.json and add to .gitignore --- .gitignore | 1 + .vscode/settings.json | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index acf76b81..70111ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -229,3 +229,4 @@ lambda.zip ### Bedrock AgentCore ### .bedrock_agentcore/ .bedrock_agentcore.yaml +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5480842b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "kiroAgent.configureMCP": "Disabled" -} \ No newline at end of file