From e720768134cdd19157e18e7ac3f3acc00a937609 Mon Sep 17 00:00:00 2001 From: JobRamos <33988720+JobRamos@users.noreply.github.com> Date: Mon, 22 Jun 2026 10:27:28 -0600 Subject: [PATCH] Add Weather Agent use case with harness, gateway, guardrails, evaluation and observability (#1648) * Add Weather Agent use case with harness, gateway, guardrails, evaluations, and observability * fix: resolve lint errors and security finding (unused imports, f-strings, bind to localhost) * fix: address PR feedback (README clarifications, region detection, graceful eval error, trace display) * feat: add Skills (xlsx report generation) and Optimization (system prompt recommendations) * feat: add Skills (xlsx report generation) and Optimization (system prompt recommendations) * fix: address PR feedback (README clarifications, region detection, graceful eval error, trace display) --- .../02-use-cases/04-weather-agent/.gitignore | 11 + .../02-use-cases/04-weather-agent/README.md | 179 ++ .../04-weather-agent/backend/agent.py | 61 + .../04-weather-agent/backend/evaluation.py | 147 ++ .../04-weather-agent/backend/main.py | 163 ++ .../04-weather-agent/backend/observability.py | 71 + .../04-weather-agent/backend/optimization.py | 116 ++ .../04-weather-agent/backend/requirements.txt | 7 + .../04-weather-agent/backend/resources.py | 247 +++ .../04-weather-agent/backend/skills.py | 212 ++ .../02-use-cases/04-weather-agent/cleanup.sh | 65 + .../04-weather-agent/frontend/index.html | 12 + .../frontend/package-lock.json | 1795 +++++++++++++++++ .../04-weather-agent/frontend/package.json | 18 + .../04-weather-agent/frontend/src/App.css | 427 ++++ .../04-weather-agent/frontend/src/App.jsx | 548 +++++ .../04-weather-agent/frontend/src/api.js | 69 + .../04-weather-agent/frontend/src/main.jsx | 10 + .../04-weather-agent/frontend/vite.config.js | 13 + .../04-weather-agent/images/app_example.png | Bin 0 -> 239047 bytes .../02-use-cases/04-weather-agent/optimize.py | 284 +++ .../02-use-cases/04-weather-agent/run.sh | 145 ++ .../02-use-cases/04-weather-agent/start.sh | 172 ++ .../04-weather-agent/weather_agent.py | 513 +++++ 01-features/01-harness/README.md | 3 + 25 files changed, 5288 insertions(+) create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/.gitignore create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/README.md create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/agent.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/evaluation.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/main.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/observability.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/optimization.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/requirements.txt create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/resources.py create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/backend/skills.py create mode 100755 01-features/01-harness/02-use-cases/04-weather-agent/cleanup.sh create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/index.html create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/package-lock.json create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/package.json create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.css create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.jsx create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/api.js create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/main.jsx create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/frontend/vite.config.js create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/images/app_example.png create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/optimize.py create mode 100755 01-features/01-harness/02-use-cases/04-weather-agent/run.sh create mode 100755 01-features/01-harness/02-use-cases/04-weather-agent/start.sh create mode 100644 01-features/01-harness/02-use-cases/04-weather-agent/weather_agent.py diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/.gitignore b/01-features/01-harness/02-use-cases/04-weather-agent/.gitignore new file mode 100644 index 00000000..00582303 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/.gitignore @@ -0,0 +1,11 @@ +venv/ +node_modules/ +frontend/node_modules/ +resource_info.json +optimization_result.json +backend.log +frontend.log +backend.pid +frontend.pid +pr-response.txt +__pycache__/ diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/README.md b/01-features/01-harness/02-use-cases/04-weather-agent/README.md new file mode 100644 index 00000000..0fdf694f --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/README.md @@ -0,0 +1,179 @@ +# Weather Agent — Harness + Evaluations + Gateway + Observability + +![Weather Agent App](images/app_example.png) + +## Overview + +A full-stack weather agent web app that integrates **six AgentCore capabilities** in a single demo: + +1. **AgentCore Gateway** — Creates a Gateway resource with an Exa MCP target, routing all tool calls through the managed proxy for centralized observability +2. **Guardrails** — Bedrock guardrail that anonymizes PII (email, phone, address) in agent responses +3. **Observability** — CloudWatch traces with full agent loop visibility +4. **Skills** — Generate weather forecast Excel spreadsheets using the xlsx skill (fetched from Git at invocation time) +5. **Evaluations** — Batch evaluation scoring with built-in evaluators (Helpfulness, Correctness, Coherence, etc.) +6. **Optimization** — AI-generated system prompt recommendations based on agent traces + +The web app features: +- A **chat interface** where users ask weather questions +- **Weather data cards** that update in real time (temperature, wind, UV, sunrise/sunset) +- A **Traces panel** showing live trace IDs from CloudWatch (searchable in GenAI Observability) +- A **Skills panel** to generate weather forecast XLSX reports +- An **Evaluations panel** that triggers batch evaluations and displays scores +- An **Optimization panel** that generates AI-improved system prompts from your traces + +## Quick Start + +```bash +./start.sh +``` + +One command: installs dependencies, provisions AWS resources (Gateway, Harness, Guardrail), starts the backend and frontend. Open **http://localhost:5173**. + +To stop servers: `Ctrl+C`. To delete AWS resources: `./cleanup.sh`. + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Frontend (React + Vite) — http://localhost:5173 │ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ Chat Panel │ │ Weather / Traces / Evaluations │ │ +│ │ (send queries) │ │ (live cards, trace IDs, scores)│ │ +│ └──────────┬───────────┘ └────────────────┬────────────────┘ │ +└─────────────┼─────────────────────────────────┼──────────────────┘ + │ │ + │ POST /api/chat (SSE) │ GET /api/traces + │ │ POST /api/evaluate + ▼ ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Backend (FastAPI) — http://localhost:8000 │ +│ │ +│ ┌────────────┐ ┌────────┐ ┌──────────────┐ ┌─────────────┐ │ +│ │ resources │ │ agent │ │observability │ │ evaluation │ │ +│ │ .py │ │ .py │ │ .py │ │ .py │ │ +│ └─────┬──────┘ └───┬────┘ └──────┬───────┘ └──────┬──────┘ │ +└────────┼──────────────┼──────────────┼─────────────────┼─────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ AWS (AgentCore + Bedrock + CloudWatch) │ +│ │ +│ AC Gateway ──► Exa MCP ──► Web Search (live weather data) │ +│ Harness ─────► Claude Haiku 4.5 (agent orchestration) │ +│ Guardrail ───► PII anonymization (email, phone, address) │ +│ Skills ──────► xlsx skill (Git-fetched, weather report gen) │ +│ CloudWatch ──► Trace observability (GenAI Observability) │ +│ Batch Eval ──► Built-in evaluators (Helpfulness, Correctness…) │ +│ Optimization ► System prompt recommendations from traces │ +└──────────────────────────────────────────────────────────────────┘ +``` + +## How It Works + +### Web App Flow + +1. **Start** — `./start.sh` provisions Gateway + Harness + Guardrail (or reuses existing ones) +2. **Chat** — User asks weather questions; agent searches via Gateway's Exa MCP target +3. **Weather Cards** — Parsed metrics (temperature, wind, UV, etc.) appear as visual cards +4. **Traces** — Each invocation generates traces visible in the Traces tab and in CloudWatch > GenAI Observability > Bedrock AgentCore > Traces +5. **Skills** — Click "Generate Report" to create an XLSX weather forecast using the xlsx skill +6. **Evaluations** — Click "Run Eval" to trigger a batch evaluation; results show scores for Helpfulness, Correctness, Coherence, and more (also visible in Bedrock AgentCore > Evaluations > Batch evaluation) +7. **Optimization** — Click "Optimize" to generate an AI-improved system prompt from your traces (also visible in Bedrock AgentCore > Optimizations > Recommendations) +8. **Cleanup** — `./cleanup.sh` deletes all AWS resources including batch evaluations + + +## Key Features + +### AgentCore Gateway +The demo creates an AgentCore Gateway resource (`create_gateway` + `create_gateway_target`) and passes it to the harness as `type: "agentcore_gateway"`. The Gateway acts as a managed proxy between the agent and external tool servers: +- Centralized routing for MCP tool traffic +- Automatic observability (every tool call through the Gateway is traced) +- Configurable auth (NONE in this demo, supports IAM/OAuth) + +### Bedrock Guardrails +A guardrail anonymizes PII in agent responses. If you ask the agent to include personal info (email, phone), the guardrail masks it before the response reaches you. + +### Observability +Every `invoke_harness` call automatically generates traces in CloudWatch. The Traces tab shows trace IDs that you can search in: +- **CloudWatch > GenAI Observability > Bedrock AgentCore > Traces** + +### Skills (xlsx) +The "Generate Report" button creates a 7-day weather forecast Excel spreadsheet using the AgentCore xlsx skill. The skill is fetched from Git (`https://github.com/anthropics/skills`) at invocation time — no container setup or pre-installation required. The report uses the last city you asked about. + +### Batch Evaluations +The "Run Eval" button triggers a batch evaluation that scores your session using built-in evaluators: +- InstructionFollowing, Helpfulness, Correctness, Faithfulness, ResponseRelevance, Coherence, Conciseness, Refusal + +Results appear in the web app and are also visible in: +- **Bedrock AgentCore > Evaluations > Batch evaluation** + +### Optimization +The "Optimize" button analyzes your agent's traces and generates an AI-improved system prompt optimized for goal success. It uses the `start_recommendation` API with your harness traces as input. The recommended prompt and explanation are displayed in the web app. + +Results are also visible in: +- **Bedrock AgentCore > Optimizations > Recommendations** + +## Prerequisites + +- Python 3.10+ +- Node.js 18+ +- AWS CLI configured with credentials (`aws sts get-caller-identity` should work). Recommended region: **us-east-1** (`export AWS_DEFAULT_REGION=us-east-1`) +- Model access enabled for Claude Haiku 4.5 in Amazon Bedrock +- [CloudWatch Transaction Search](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-configure.html) enabled — **required** for Traces, Evaluations, and Optimization to work. After enabling, wait 10-15 minutes before using these feature so the Bedrock AgentCore dashboard in CloudWatch can become available. Only traces from invocations *after* enabling will be indexed. + +## AWS Permissions Required + +> **Note:** The policies below use broad access for simplicity in this demo. In production environments, follow the principle of least privilege and create custom IAM policies scoped to only the specific resources and actions your agent needs. + +| Policy | Purpose | +|--------|---------| +| `BedrockAgentCoreFullAccess` | Harness, Gateway, Batch Evaluations | +| `AmazonBedrockFullAccess` | Model invocation, Guardrails | +| `IAMFullAccess` | Create the harness execution role (first run only) | +| `CloudWatchFullAccessV2` | Query traces + batch evaluation output logs | + +## Running + +### Web App (recommended) + +```bash +./start.sh +``` + +One command: creates a virtual environment, installs Python and Node.js packages, provisions AWS resources, starts the FastAPI backend and React frontend. + +Open **http://localhost:5173** once the script prints "App is running!". + +```bash +# Stop servers without deleting AWS resources: +# Press Ctrl+C (resources persist for next ./start.sh) + +# Stop servers AND delete all AWS resources: +./cleanup.sh +``` + +## Sample Prompts + +- "What's the weather in Tokyo?" +- "What's the wind speed in Vancouver right now?" +- "What's the UV index in Miami today?" +- "When is sunrise and sunset in London?" + + + +## Clean Up + +```bash +# Delete all AWS resources (gateway, harness, guardrail, batch evaluations, IAM role): +./cleanup.sh + +# To also remove the virtual environment and node_modules: +rm -rf venv frontend/node_modules +``` \ No newline at end of file diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/agent.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/agent.py new file mode 100644 index 00000000..9873ed15 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/agent.py @@ -0,0 +1,61 @@ +"""Agent invocation — streaming wrapper around invoke_harness.""" + +import sys +from pathlib import Path +from typing import Generator + +sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) +from utils.client import get_agentcore_client + +MODEL_ID = "global.anthropic.claude-haiku-4-5-20251001-v1:0" + +SYSTEM_PROMPT = ( + "You are a weather assistant. You ONLY answer questions about weather, " + "climate, and atmospheric conditions (temperature, wind, humidity, UV index, " + "sunrise, sunset, moon phase, forecasts, air quality, precipitation). " + "If the user asks about anything unrelated to weather, politely redirect them. " + "For example: 'I'm a weather assistant — I can help with forecasts, current conditions, " + "UV index, wind, sunrise/sunset, and more. What location would you like weather for?'\n\n" + "When answering weather questions:\n" + "- Always search for real-time data using your tools\n" + "- Include specific numbers with units (temperature in °F/°C, wind in km/h or mph)\n" + "- Mention the city name in your response\n" + "- Keep responses concise and well-structured" +) + + +def invoke_agent(harness_arn: str, gateway_arn: str, session_id: str, message: str) -> Generator[dict, None, None]: + """Stream agent response as SSE-friendly dicts.""" + client = get_agentcore_client() + + tools = [ + { + "type": "agentcore_gateway", + "name": "gateway", + "config": {"agentCoreGateway": {"gatewayArn": gateway_arn}}, + } + ] + + prefixed_message = f"[INSTRUCTIONS: {SYSTEM_PROMPT}]\n\nUser question: {message}" + + response = client.invoke_harness( + harnessArn=harness_arn, + runtimeSessionId=session_id, + messages=[{"role": "user", "content": [{"text": prefixed_message}]}], + model={"bedrockModelConfig": {"modelId": MODEL_ID}}, + tools=tools, + ) + + for event in response["stream"]: + if "contentBlockStart" in event: + start = event["contentBlockStart"].get("start", {}) + if "toolUse" in start: + yield {"type": "tool", "name": start["toolUse"].get("name", "?")} + elif "contentBlockDelta" in event: + delta = event["contentBlockDelta"].get("delta", {}) + if "text" in delta: + yield {"type": "text", "content": delta["text"]} + elif "messageStop" in event: + yield {"type": "done"} + elif "internalServerException" in event: + yield {"type": "error", "content": str(event["internalServerException"])} diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/evaluation.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/evaluation.py new file mode 100644 index 00000000..dbd26af3 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/evaluation.py @@ -0,0 +1,147 @@ +"""Evaluations — run batch evaluation against harness session traces.""" + +import time +import uuid + +import boto3 + +from resources import REGION + +EVALUATOR_IDS = [ + "Builtin.InstructionFollowing", + "Builtin.Helpfulness", + "Builtin.Correctness", + "Builtin.Faithfulness", + "Builtin.ResponseRelevance", + "Builtin.Coherence", + "Builtin.Conciseness", + "Builtin.Refusal", +] + + +def _discover_log_group(harness_name: str) -> str | None: + """Find the CloudWatch log group for a harness by prefix search.""" + logs = boto3.client("logs", region_name=REGION) + prefix = f"/aws/bedrock-agentcore/runtimes/harness_{harness_name}-" + resp = logs.describe_log_groups(logGroupNamePrefix=prefix, limit=5) + groups = resp.get("logGroups", []) + if groups: + # Return the most recently created one + groups.sort(key=lambda g: g.get("creationTime", 0), reverse=True) + return groups[0]["logGroupName"] + return None + + +def run_batch_evaluation(harness_id: str, harness_name: str = None) -> dict: + """Start a batch evaluation job and poll until complete. Returns results.""" + client = boto3.client("bedrock-agentcore", region_name=REGION) + + if not hasattr(client, "start_batch_evaluation"): + return { + "error": ( + "start_batch_evaluation is not available in your boto3 version. " + "Please upgrade: pip install 'boto3>=1.43.27'" + ), + "scores": [], + } + + # Discover the log group dynamically (it has a random suffix) + log_group = None + if harness_name: + log_group = _discover_log_group(harness_name) + if not log_group: + # Fallback: try with harness_id directly + log_group = _discover_log_group(harness_id) + if not log_group: + return {"error": f"Could not find log group for harness {harness_name or harness_id}", "scores": []} + + # Service name format: harness_{name}.DEFAULT (without the random suffix) + # Log group: harness_WeatherAgent_537bb0c9-d9RslKDml1-DEFAULT + # Service: harness_WeatherAgent_537bb0c9.DEFAULT + log_group_basename = log_group.split("/")[-1] # harness_WeatherAgent_537bb0c9-d9RslKDml1-DEFAULT + parts = log_group_basename.rsplit("-", 2) # ['harness_WeatherAgent_537bb0c9', 'd9RslKDml1', 'DEFAULT'] + service_name = f"{parts[0]}.DEFAULT" if len(parts) >= 3 else log_group_basename.replace("-DEFAULT", ".DEFAULT") + + batch_name = f"weather_eval_{uuid.uuid4().hex[:8]}" + + try: + resp = client.start_batch_evaluation( + batchEvaluationName=batch_name, + evaluators=[{"evaluatorId": eid} for eid in EVALUATOR_IDS], + dataSourceConfig={ + "cloudWatchLogs": { + "serviceNames": [service_name], + "logGroupNames": [log_group], + } + }, + ) + except Exception as e: + return {"error": str(e), "scores": []} + + batch_id = resp["batchEvaluationId"] + + # Poll until complete (timeout after 5 minutes) + deadline = time.monotonic() + 300 + status = "PENDING" + while time.monotonic() < deadline: + time.sleep(10) + try: + result = client.get_batch_evaluation(batchEvaluationId=batch_id) + status = result.get("status", "UNKNOWN") + if status in ("COMPLETED", "COMPLETED_WITH_ERRORS", "FAILED"): + break + except Exception: + pass + + if status not in ("COMPLETED", "COMPLETED_WITH_ERRORS"): + # Try to get failure details + failure_reason = "" + try: + failure_reason = result.get("failureReasons", result.get("statusReason", "")) + if not failure_reason: + # Check evaluationResults for per-session errors + eval_res = result.get("evaluationResults", {}) + failed_count = eval_res.get("numberOfSessionsFailed", 0) + completed_count = eval_res.get("numberOfSessionsCompleted", 0) + if failed_count > 0: + failure_reason = f"{failed_count} session(s) failed, {completed_count} completed" + except Exception: + pass + error_msg = f"Evaluation did not complete (status: {status})" + if failure_reason: + error_msg += f". {failure_reason}" + print(f"[eval] Failed: {error_msg}") + print(f"[eval] Full response: {result}") + return { + "batch_id": batch_id, + "batch_name": batch_name, + "status": status, + "error": error_msg, + "scores": [], + } + + # Extract evaluator results + scores = [] + eval_results = result.get("evaluationResults", {}) + summaries = eval_results.get("evaluatorSummaries", []) + + for summary in summaries: + eid = summary.get("evaluatorId", "") + stats = summary.get("statistics", {}) + avg_score = stats.get("averageScore") + evaluated = summary.get("totalEvaluated", 0) + + name = eid.replace("Builtin.", "") if eid.startswith("Builtin.") else eid + scores.append({ + "evaluator": name, + "score": avg_score, + "evaluated_sessions": evaluated, + }) + + return { + "batch_id": batch_id, + "batch_name": batch_name, + "status": status, + "total_sessions": eval_results.get("numberOfSessionsCompleted", 0), + "scores": scores, + } diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/main.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/main.py new file mode 100644 index 00000000..297112e2 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/main.py @@ -0,0 +1,163 @@ +"""FastAPI backend for the Weather Agent web app.""" + +import asyncio +import json +import uuid +from contextlib import asynccontextmanager + +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from sse_starlette.sse import EventSourceResponse + +from resources import ensure_resources +from agent import invoke_agent +from observability import get_recent_traces, get_transaction_search_status +from evaluation import run_batch_evaluation +from skills import generate_weather_report +from optimization import run_optimization + +# Global state +_state: dict = {} +_sessions: dict[str, list] = {} + + +@asynccontextmanager +async def lifespan(app: FastAPI): + global _state + print("[backend] Starting — provisioning AWS resources...") + _state = await asyncio.to_thread(ensure_resources) + print(f"[backend] Ready. Harness: {_state['harness_id']}") + yield + print("[backend] Shutting down") + + +app = FastAPI(title="Weather Agent", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173", "http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +class ChatRequest(BaseModel): + message: str + session_id: str | None = None + + +class EvalRequest(BaseModel): + session_id: str + + +class ReportRequest(BaseModel): + session_id: str + city: str | None = None + + +@app.get("/health") +async def health(): + return {"status": "ok", "harness_id": _state.get("harness_id")} + + +@app.get("/api/status") +async def status(): + return { + "ready": bool(_state.get("harness_id")), + "harness_id": _state.get("harness_id"), + "harness_name": _state.get("harness_name"), + "gateway_id": _state.get("gateway_id"), + "gateway_name": _state.get("gateway_name"), + "guardrail_id": _state.get("guardrail_id"), + "guardrail_name": _state.get("guardrail_name"), + "region": _state.get("region"), + } + + +@app.post("/api/chat") +async def chat(req: ChatRequest): + if not _state.get("harness_arn"): + raise HTTPException(503, "Resources not ready") + + session_id = req.session_id or str(uuid.uuid4()).upper() + + if session_id not in _sessions: + _sessions[session_id] = [] + + _sessions[session_id].append({"role": "user", "content": req.message}) + + async def generate(): + full_text = "" + yield json.dumps({"type": "session_id", "session_id": session_id}) + + for event in invoke_agent( + _state["harness_arn"], _state["gateway_arn"], session_id, req.message + ): + if event["type"] == "text": + full_text += event["content"] + yield json.dumps(event) + + _sessions[session_id].append({"role": "assistant", "content": full_text}) + + return EventSourceResponse(generate(), media_type="text/event-stream") + + +@app.get("/api/traces") +async def traces(minutes: int = 10): + result = await asyncio.to_thread(get_recent_traces, _state.get("harness_name"), minutes) + tx_status = await asyncio.to_thread(get_transaction_search_status) + return {"traces": result, "transaction_search": tx_status} + + +@app.post("/api/evaluate") +async def evaluate(req: EvalRequest): + if not _state.get("harness_id"): + raise HTTPException(503, "Resources not ready") + + result = await asyncio.to_thread( + run_batch_evaluation, + _state["harness_id"], + _state.get("harness_name"), + ) + return {"session_id": req.session_id, **result} + + +@app.post("/api/generate-report") +async def generate_report(req: ReportRequest): + if not _state.get("harness_arn"): + raise HTTPException(503, "Resources not ready") + + result = await asyncio.to_thread( + generate_weather_report, + _state["harness_arn"], + _state["harness_id"], + req.session_id, + req.city or "the cities discussed", + ) + return result + + +@app.post("/api/optimize") +async def optimize(): + if not _state.get("harness_name"): + raise HTTPException(503, "Resources not ready") + + result = await asyncio.to_thread( + run_optimization, _state["harness_name"] + ) + return result + + +@app.get("/api/sessions") +async def sessions(): + return { + sid: {"turns": len(msgs), "last_message": msgs[-1]["content"][:80] if msgs else ""} + for sid, msgs in _sessions.items() + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="127.0.0.1", port=8000) diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/observability.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/observability.py new file mode 100644 index 00000000..12e3e6bc --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/observability.py @@ -0,0 +1,71 @@ +"""Observability — query traces from the aws/spans CloudWatch log group.""" + +import time + +import boto3 + +from resources import REGION + +SPANS_LOG_GROUP = "aws/spans" + + +def get_recent_traces(harness_name: str = None, minutes: int = 10) -> list[dict]: + """Query aws/spans log group for recent traces from this harness.""" + logs = boto3.client("logs", region_name=REGION) + + end_time = int(time.time()) + start_time = end_time - (minutes * 60) + + query = """fields traceId, @timestamp +| filter ispresent(traceId) and traceId != '' +| stats count() as spans by traceId +| sort @timestamp desc +| limit 20""" + + try: + resp = logs.start_query( + logGroupName=SPANS_LOG_GROUP, + startTime=start_time, + endTime=end_time, + queryString=query, + ) + query_id = resp["queryId"] + + for _ in range(15): + time.sleep(2) + result = logs.get_query_results(queryId=query_id) + if result["status"] in ("Complete", "Failed", "Cancelled"): + break + + if result["status"] != "Complete": + return [] + + traces = [] + for row in result.get("results", []): + fields = {f["field"]: f["value"] for f in row} + trace_id = fields.get("traceId", "") + spans = fields.get("spans", "0") + + if trace_id: + traces.append({ + "trace_id": trace_id, + "spans": int(spans), + "has_error": False, + "has_fault": False, + }) + + return traces + + except Exception as e: + return [{"error": str(e)}] + + +def get_transaction_search_status() -> dict: + """Check if Transaction Search is enabled.""" + xray = boto3.client("xray", region_name=REGION) + try: + rules = xray.get_indexing_rules() + sampling = rules["IndexingRules"][0]["Rule"]["Probabilistic"]["DesiredSamplingPercentage"] + return {"enabled": True, "sampling_percentage": sampling} + except Exception as e: + return {"enabled": False, "error": str(e)} diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/optimization.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/optimization.py new file mode 100644 index 00000000..367726fa --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/optimization.py @@ -0,0 +1,116 @@ +"""Optimization — generate system prompt recommendations from agent traces.""" + +import time +import uuid +from datetime import datetime, timedelta, timezone + +import boto3 + +from resources import REGION +from agent import SYSTEM_PROMPT + + +def _discover_log_group_arn(harness_name: str) -> tuple[str, str] | None: + """Find the log group ARN and service name for a harness.""" + logs = boto3.client("logs", region_name=REGION) + prefix = f"/aws/bedrock-agentcore/runtimes/harness_{harness_name}-" + resp = logs.describe_log_groups(logGroupNamePrefix=prefix, limit=5) + groups = resp.get("logGroups", []) + if not groups: + return None + groups.sort(key=lambda g: g.get("creationTime", 0), reverse=True) + log_group = groups[0] + log_group_arn = log_group["arn"] + log_group_name = log_group["logGroupName"] + + basename = log_group_name.split("/")[-1] + parts = basename.rsplit("-", 2) + service_name = f"{parts[0]}.DEFAULT" if len(parts) >= 3 else basename.replace("-DEFAULT", ".DEFAULT") + + return log_group_arn, service_name + + +def run_optimization(harness_name: str, evaluator: str = "Builtin.GoalSuccessRate") -> dict: + """Run a system prompt recommendation and return the result.""" + client = boto3.client("bedrock-agentcore", region_name=REGION) + + # Discover log group + result = _discover_log_group_arn(harness_name) + if not result: + return {"error": "Could not find log group. Send some chat messages first.", "status": "FAILED"} + + log_group_arn, service_name = result + + now = datetime.now(timezone.utc) + start_time = now - timedelta(days=7) + rec_name = f"weather_rec_{uuid.uuid4().hex[:8]}" + + # Start recommendation + try: + resp = client.start_recommendation( + name=rec_name, + type="SYSTEM_PROMPT_RECOMMENDATION", + recommendationConfig={ + "systemPromptRecommendationConfig": { + "systemPrompt": {"text": SYSTEM_PROMPT}, + "agentTraces": { + "cloudwatchLogs": { + "logGroupArns": [log_group_arn], + "serviceNames": [service_name], + "startTime": start_time, + "endTime": now, + } + }, + "evaluationConfig": { + "evaluators": [ + {"evaluatorArn": f"arn:aws:bedrock-agentcore:::evaluator/{evaluator}"} + ] + }, + } + }, + clientToken=str(uuid.uuid4()), + ) + except Exception as e: + return {"error": str(e), "status": "FAILED"} + + rec_id = resp["recommendationId"] + + # Poll for completion (timeout 5 minutes) + status = "PENDING" + for _ in range(30): + time.sleep(10) + try: + rec = client.get_recommendation(recommendationId=rec_id) + status = rec.get("status", "UNKNOWN") + if status in ("COMPLETED", "FAILED"): + break + except Exception: + pass + + if status != "COMPLETED": + error_msg = "" + if status == "FAILED": + rec_result = rec.get("recommendationResult", {}).get( + "systemPromptRecommendationResult", {} + ) + error_msg = rec_result.get("errorMessage", "Unknown error") + return { + "status": status, + "error": error_msg or f"Recommendation did not complete (status: {status})", + "recommendation_name": rec_name, + } + + # Extract result + rec_result = rec.get("recommendationResult", {}).get( + "systemPromptRecommendationResult", {} + ) + + return { + "status": "COMPLETED", + "recommendation_name": rec_name, + "recommendation_id": rec_id, + "evaluator": evaluator, + "current_prompt": SYSTEM_PROMPT, + "recommended_prompt": rec_result.get("recommendedSystemPrompt", ""), + "explanation": rec_result.get("explanation", ""), + } diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/requirements.txt b/01-features/01-harness/02-use-cases/04-weather-agent/backend/requirements.txt new file mode 100644 index 00000000..9c788f16 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/requirements.txt @@ -0,0 +1,7 @@ +boto3>=1.43.27 +fastapi +uvicorn[standard] +sse-starlette +pydantic +bedrock-agentcore +requests diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/resources.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/resources.py new file mode 100644 index 00000000..a3c5f6bf --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/resources.py @@ -0,0 +1,247 @@ +"""AWS resource lifecycle — create or reuse Gateway, Harness, Guardrail.""" + +import json +import os +import sys +import time +import uuid +from pathlib import Path + +import boto3 + +sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) +from utils.iam import create_harness_role, delete_harness_role +from utils.client import get_agentcore_control_client + +STATE_FILE = Path(__file__).parent.parent / "resource_info.json" +REGION = os.environ.get("AWS_DEFAULT_REGION") or boto3.session.Session().region_name or "us-east-1" + + +def _poll(get_fn, extract_fn, target="READY", timeout=300, interval=5): + deadline = time.monotonic() + timeout + while True: + resp = get_fn() + status = extract_fn(resp) + if status == target: + return resp + if status in ("FAILED", "CREATE_FAILED", "DELETE_FAILED"): + raise RuntimeError(f"Resource failed: {status}") + if time.monotonic() > deadline: + raise TimeoutError(f"Not {target} after {timeout}s") + time.sleep(interval) + + +def _load_state() -> dict | None: + if STATE_FILE.exists(): + return json.loads(STATE_FILE.read_text()) + return None + + +def _save_state(state: dict): + STATE_FILE.write_text(json.dumps(state, indent=2)) + + +def _resources_alive(state: dict) -> bool: + try: + control = get_agentcore_control_client() + h = control.get_harness(harnessId=state["harness_id"]) + if h["harness"]["status"] != "READY": + return False + gw = boto3.client("bedrock-agentcore-control", region_name=REGION) + g = gw.get_gateway(gatewayIdentifier=state["gateway_id"]) + if g["status"] != "READY": + return False + return True + except Exception: + return False + + +def ensure_resources() -> dict: + """Create or reuse all AWS resources. Returns resource dict.""" + existing = _load_state() + if existing and _resources_alive(existing): + print("[resources] Reusing existing resources") + return existing + + print("[resources] Provisioning new resources...") + control = get_agentcore_control_client() + gw_control = boto3.client("bedrock-agentcore-control", region_name=REGION) + bedrock = boto3.client("bedrock", region_name=REGION) + + # IAM role + role_arn = create_harness_role() + time.sleep(10) + + # Gateway + gateway_name = f"WeatherGW-{uuid.uuid4().hex[:8]}" + resp = gw_control.create_gateway( + name=gateway_name, roleArn=role_arn, protocolType="MCP", authorizerType="NONE" + ) + gateway_id = resp["gatewayId"] + gateway_arn = resp["gatewayArn"] + _poll( + lambda: gw_control.get_gateway(gatewayIdentifier=gateway_id), + lambda r: r["status"], + ) + + # MCP target (Exa search) + resp = gw_control.create_gateway_target( + gatewayIdentifier=gateway_id, + name="exa-weather", + targetConfiguration={"mcp": {"mcpServer": {"endpoint": "https://mcp.exa.ai/mcp"}}}, + ) + target_id = resp["targetId"] + _poll( + lambda: gw_control.get_gateway_target(gatewayIdentifier=gateway_id, targetId=target_id), + lambda r: r["status"], + ) + + # Harness + harness_name = f"WeatherAgent_{uuid.uuid4().hex[:8]}" + resp = control.create_harness( + harnessName=harness_name, + executionRoleArn=role_arn, + systemPrompt=[ + { + "text": ( + "You are a weather assistant. You ONLY answer questions about weather, " + "climate, and atmospheric conditions (temperature, wind, humidity, UV index, " + "sunrise, sunset, moon phase, forecasts, air quality, precipitation). " + "If the user asks about anything unrelated to weather, politely redirect them. " + "For example: 'I'm a weather assistant — I can help with forecasts, current conditions, " + "UV index, wind, sunrise/sunset, and more. What location would you like weather for?' " + "When answering weather questions: always search for real-time data using your tools, " + "include specific numbers with units (temperature in F/C, wind in km/h or mph), " + "mention the city name in your response, and keep responses concise and well-structured." + ) + } + ], + ) + harness_id = resp["harness"]["harnessId"] + harness_arn = resp["harness"]["arn"] + _poll( + lambda: control.get_harness(harnessId=harness_id), + lambda r: r["harness"]["status"], + ) + + # Guardrail + guardrail_id = None + guardrail_version = None + guardrail_name = None + try: + guardrail_name = f"weather-pii-{uuid.uuid4().hex[:6]}" + gr = bedrock.create_guardrail( + name=guardrail_name, + description="Anonymize PII in weather agent responses", + sensitiveInformationPolicyConfig={ + "piiEntitiesConfig": [ + {"type": "EMAIL", "action": "ANONYMIZE"}, + {"type": "PHONE", "action": "ANONYMIZE"}, + {"type": "ADDRESS", "action": "ANONYMIZE"}, + {"type": "US_SOCIAL_SECURITY_NUMBER", "action": "ANONYMIZE"}, + ] + }, + blockedInputMessaging="Content blocked.", + blockedOutputsMessaging="Content blocked.", + ) + guardrail_id = gr["guardrailId"] + gv = bedrock.create_guardrail_version(guardrailIdentifier=guardrail_id, description="v1") + guardrail_version = gv["version"] + except Exception as e: + print(f"[resources] Guardrail creation failed (non-critical): {e}") + + state = { + "gateway_id": gateway_id, + "gateway_arn": gateway_arn, + "gateway_name": gateway_name, + "target_id": target_id, + "harness_id": harness_id, + "harness_arn": harness_arn, + "harness_name": harness_name, + "guardrail_id": guardrail_id, + "guardrail_name": guardrail_name, + "guardrail_version": guardrail_version, + "role_arn": role_arn, + "region": REGION, + } + _save_state(state) + print("[resources] All resources ready") + return state + + +def destroy_resources(): + """Delete all resources and remove state file.""" + state = _load_state() + if not state: + print("[resources] No state file found") + return + + control = get_agentcore_control_client() + gw_control = boto3.client("bedrock-agentcore-control", region_name=REGION) + bedrock = boto3.client("bedrock", region_name=REGION) + + if state.get("harness_id"): + try: + control.delete_harness(harnessId=state["harness_id"]) + print(f" Deleted harness: {state['harness_id']}") + except Exception as e: + print(f" Warning: {e}") + + if state.get("gateway_id") and state.get("target_id"): + try: + gw_control.delete_gateway_target( + gatewayIdentifier=state["gateway_id"], targetId=state["target_id"] + ) + print(f" Deleted target: {state['target_id']}") + time.sleep(10) + except Exception as e: + print(f" Warning: {e}") + + if state.get("gateway_id"): + try: + gw_control.delete_gateway(gatewayIdentifier=state["gateway_id"]) + print(f" Deleted gateway: {state['gateway_id']}") + except Exception as e: + print(f" Warning: {e}") + + if state.get("guardrail_id"): + try: + bedrock.delete_guardrail(guardrailIdentifier=state["guardrail_id"]) + print(f" Deleted guardrail: {state['guardrail_id']}") + except Exception as e: + print(f" Warning: {e}") + + # Delete batch evaluations created by this app + dp_client = boto3.client("bedrock-agentcore", region_name=REGION) + try: + evals = dp_client.list_batch_evaluations() + for ev in evals.get("batchEvaluations", evals.get("items", [])): + ev_name = ev.get("batchEvaluationName", ev.get("name", "")) + ev_id = ev.get("batchEvaluationId", "") + if ev_name.startswith("weather_eval_"): + try: + dp_client.delete_batch_evaluation(batchEvaluationId=ev_id) + print(f" Deleted batch evaluation: {ev_name}") + except Exception: + pass + except Exception as e: + print(f" Warning (batch evals): {e}") + + # Delete recommendations created by this app + try: + recs = dp_client.list_recommendations() + for rec in recs.get("recommendationSummaries", recs.get("recommendations", recs.get("items", []))): + rec_name = rec.get("name", "") + rec_id = rec.get("recommendationId", "") + if rec_name.startswith("weather_rec_"): + try: + dp_client.delete_recommendation(recommendationId=rec_id) + print(f" Deleted recommendation: {rec_name}") + except Exception: + pass + except Exception as e: + print(f" Warning (recommendations): {e}") + + delete_harness_role() + STATE_FILE.unlink(missing_ok=True) + print("[resources] Cleanup complete") diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/backend/skills.py b/01-features/01-harness/02-use-cases/04-weather-agent/backend/skills.py new file mode 100644 index 00000000..f65c88d6 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/backend/skills.py @@ -0,0 +1,212 @@ +"""Skills — generate weather forecast reports as XLSX spreadsheets. + +Tries Git-based skill fetching first (no container needed). Falls back to +path-based approach with Node.js container if Git parameter is not supported. +""" + +import sys +import time +from pathlib import Path + +import boto3 +from botocore.config import Config +from botocore.exceptions import ParamValidationError + +sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) +from utils.client import get_agentcore_control_client, get_agentcore_client + +from resources import REGION + +NODE_CONTAINER = "public.ecr.aws/docker/library/node:slim" +MODEL_ID = "global.anthropic.claude-haiku-4-5-20251001-v1:0" + +_skill_installed: dict[str, bool] = {} + + +def _run_command(client, harness_arn: str, session_id: str, cmd: str) -> str: + """Run a shell command on the agent VM.""" + output = "" + resp = client.invoke_agent_runtime_command( + agentRuntimeArn=harness_arn, + runtimeSessionId=session_id, + body={"command": cmd}, + ) + for event in resp["stream"]: + if "chunk" in event and "contentDelta" in event["chunk"]: + delta = event["chunk"]["contentDelta"] + if "stdout" in delta: + output += delta["stdout"] + if "stderr" in delta: + output += delta["stderr"] + return output + + +def _build_prompt(city: str) -> str: + return ( + f"Create an Excel spreadsheet with a 7-day weather forecast for {city}. " + f"The first row must be a title: '{city} - 7-Day Weather Forecast' merged across all columns. " + "Then include columns for: Day, Condition, High (°F), Low (°F), Wind (mph), Humidity (%), UV Index. " + "Add realistic weather data that varies day to day. " + "Include a summary row at the bottom with averages. " + "Apply formatting: bold title, bold headers, alternating row colors, conditional formatting " + "(red for high temps > 90°F, blue for low temps < 40°F). " + "Save it as /tmp/weather_forecast.xlsx" + ) + + +def _download_file(client, harness_arn: str, session_id: str) -> str: + """Download the generated xlsx file as base64.""" + b64_data = "" + resp = client.invoke_agent_runtime_command( + agentRuntimeArn=harness_arn, + runtimeSessionId=session_id, + body={"command": "base64 /tmp/weather_forecast.xlsx 2>/dev/null"}, + ) + for event in resp["stream"]: + if "chunk" in event and "contentDelta" in event["chunk"]: + delta = event["chunk"]["contentDelta"] + if "stdout" in delta: + b64_data += delta["stdout"] + return b64_data.strip().replace("\n", "") + + +def _try_git_skill(client, harness_arn: str, session_id: str, city: str) -> str | None: + """Try invoking with Git-based skill fetch (newer boto3 only).""" + try: + response = client.invoke_harness( + harnessArn=harness_arn, + runtimeSessionId=session_id, + skills=[{"git": {"url": "https://github.com/anthropics/skills", "path": "skills/xlsx"}}], + messages=[{"role": "user", "content": [{"text": _build_prompt(city)}]}], + model={"bedrockModelConfig": {"modelId": MODEL_ID}}, + timeoutSeconds=300, + ) + agent_text = "" + for event in response["stream"]: + if "contentBlockDelta" in event: + delta = event["contentBlockDelta"].get("delta", {}) + if "text" in delta: + agent_text += delta["text"] + elif "internalServerException" in event: + print(f"[skills] Stream error: {event['internalServerException']}") + return None + print(f"[skills] Git skill completed. Agent response length: {len(agent_text)}") + return agent_text + except ParamValidationError: + print("[skills] Git-based skill not supported, falling back to path approach") + return None + except Exception as e: + print(f"[skills] Git-based skill exception: {type(e).__name__}: {e}") + return None + + +def _install_skill_path(client, control, harness_id: str, harness_arn: str, session_id: str) -> bool: + """Install xlsx skill via shell (requires Node.js container).""" + if _skill_installed.get(session_id): + return True + + # Attach Node.js container if needed + print("[skills] Checking container...") + harness_info = control.get_harness(harnessId=harness_id) + current_artifact = harness_info["harness"].get("environmentArtifact", {}) + has_container = bool(current_artifact.get("containerConfiguration", {}).get("containerUri")) + + if not has_container: + print(f"[skills] Attaching Node.js container: {NODE_CONTAINER}") + control.update_harness( + harnessId=harness_id, + environmentArtifact={ + "optionalValue": {"containerConfiguration": {"containerUri": NODE_CONTAINER}} + }, + ) + for _ in range(24): + status = control.get_harness(harnessId=harness_id)["harness"]["status"] + if status == "READY": + break + time.sleep(5) + + # Install skill + print("[skills] Installing xlsx skill via npx...") + _run_command( + client, harness_arn, session_id, + "apt-get update -qq && apt-get install git -y -qq > /dev/null 2>&1 && " + "npx skills add https://github.com/anthropics/skills --skill xlsx --yes 2>&1 | tail -3" + ) + + # Verify + verify = _run_command(client, harness_arn, session_id, "ls .agents/skills/xlsx/ 2>/dev/null && echo OK || echo MISSING") + if "OK" in verify: + _skill_installed[session_id] = True + print("[skills] Skill installed successfully") + return True + print("[skills] Skill installation failed") + return False + + +def generate_weather_report(harness_arn: str, harness_id: str, session_id: str, city: str = "the cities discussed") -> dict: + """Generate a weather forecast XLSX report. Tries Git skill first, falls back to path.""" + try: + return _generate_report_inner(harness_arn, harness_id, session_id, city) + except Exception as e: + print(f"[skills] Unhandled exception: {type(e).__name__}: {e}") + return {"success": False, "error": f"{type(e).__name__}: {e}"} + + +def _generate_report_inner(harness_arn: str, harness_id: str, session_id: str, city: str) -> dict: + client = get_agentcore_client(config=Config(read_timeout=360)) + control = get_agentcore_control_client() + + # Try Git-based skill first (simpler, no container needed) + print("[skills] Trying Git-based skill fetch...") + agent_text = _try_git_skill(client, harness_arn, session_id, city) + + if agent_text is not None: + # Git skill ran — check if file was generated + print(f"[skills] Git skill completed. Agent text length: {len(agent_text)}") + b64_clean = _download_file(client, harness_arn, session_id) + if b64_clean: + return { + "success": True, + "file_data": b64_clean, + "filename": "weather_forecast.xlsx", + "agent_response": agent_text[:500], + } + print("[skills] Git skill ran but no file generated, falling back to path approach") + + # Fallback: install skill via path + if not _install_skill_path(client, control, harness_id, harness_arn, session_id): + return {"success": False, "error": "Failed to install xlsx skill"} + + # Invoke with path-based skill + print("[skills] Invoking with path-based skill...") + response = client.invoke_harness( + harnessArn=harness_arn, + runtimeSessionId=session_id, + skills=[{"path": ".agents/skills/xlsx"}], + messages=[{"role": "user", "content": [{"text": _build_prompt(city)}]}], + model={"bedrockModelConfig": {"modelId": MODEL_ID}}, + timeoutSeconds=300, + ) + agent_text = "" + for event in response["stream"]: + if "contentBlockDelta" in event: + delta = event["contentBlockDelta"].get("delta", {}) + if "text" in delta: + agent_text += delta["text"] + + # Download the file + b64_clean = _download_file(client, harness_arn, session_id) + + if b64_clean: + return { + "success": True, + "file_data": b64_clean, + "filename": "weather_forecast.xlsx", + "agent_response": agent_text[:500] if agent_text else "", + } + else: + return { + "success": False, + "error": "No file generated", + "agent_response": agent_text[:500] if agent_text else "", + } diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/cleanup.sh b/01-features/01-harness/02-use-cases/04-weather-agent/cleanup.sh new file mode 100755 index 00000000..1d18ce70 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/cleanup.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# +# Weather Agent — Cleanup +# Deletes all AWS resources and stops any running servers. +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${YELLOW}Weather Agent — Cleanup${NC}" +echo "============================================================" + +# Stop servers +echo "" +echo "Stopping servers..." +[ -f backend.pid ] && kill "$(cat backend.pid)" 2>/dev/null && rm -f backend.pid && echo " Stopped backend" +[ -f frontend.pid ] && kill "$(cat frontend.pid)" 2>/dev/null && rm -f frontend.pid && echo " Stopped frontend" +lsof -ti:8000 2>/dev/null | xargs kill -9 2>/dev/null || true +lsof -ti:5173 2>/dev/null | xargs kill -9 2>/dev/null || true + +# Delete AWS resources +if [ -f "resource_info.json" ]; then + echo "" + echo "Deleting AWS resources..." + + if [ -d "venv" ]; then + source venv/bin/activate + else + echo -e "${RED} No venv found. Create one first: python3 -m venv venv && source venv/bin/activate && pip install boto3${NC}" + exit 1 + fi + + python3 -c " +import sys +sys.path.insert(0, 'backend') +from resources import destroy_resources +destroy_resources() +" + deactivate 2>/dev/null || true +else + echo "" + echo " No resource_info.json found — nothing to delete in AWS" +fi + +# Clean local artifacts +echo "" +echo "Cleaning local files..." +rm -f backend.log frontend.log backend.pid frontend.pid +echo " Removed log and pid files" + +echo "" +echo -e "${GREEN}Cleanup complete.${NC}" +echo "" +echo " To run the app again:" +echo " ./start.sh" +echo "" +echo " To also remove the virtual environment and node_modules:" +echo " rm -rf venv frontend/node_modules" diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/index.html b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/index.html new file mode 100644 index 00000000..3c4c12ae --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Weather Agent — AgentCore Harness + + +
+ + + diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package-lock.json b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package-lock.json new file mode 100644 index 00000000..a8a20d5f --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package-lock.json @@ -0,0 +1,1795 @@ +{ + "name": "weather-agent-ui", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "weather-agent-ui", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.3", + "vite": "^6.4.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz", + "integrity": "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.370", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.370.tgz", + "integrity": "sha512-D5tSHJReAb/Kf3Hu9F/GO4lJuSWzEWHwvQ/kKSUP7pimNgvxkSKj+gUQhHpKKACwrin7rS3byU7IxreF56rl5g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package.json b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package.json new file mode 100644 index 00000000..2298d24a --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "weather-agent-ui", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.3", + "vite": "^6.4.1" + } +} diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.css b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.css new file mode 100644 index 00000000..b87c966d --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.css @@ -0,0 +1,427 @@ +:root { + --bg-primary: #0f1419; + --bg-secondary: #1a2332; + --bg-card: #1e2d3d; + --bg-input: #253341; + --text-primary: #e1e8ed; + --text-secondary: #8899a6; + --accent: #1da1f2; + --accent-green: #17bf63; + --accent-orange: #f5a623; + --accent-red: #e0245e; + --border: #2f3d4d; + --radius: 12px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--bg-primary); + color: var(--text-primary); + min-height: 100vh; +} + +.app { + display: grid; + grid-template-rows: auto 1fr auto; + height: 100vh; +} + +/* Header */ +.header { + padding: 16px 24px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; +} + +.header h1 { + font-size: 1.2rem; + font-weight: 600; +} + +.header .status { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: var(--text-secondary); +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-green); +} + +.status-dot.offline { background: var(--accent-red); } +.status-dot.loading { background: var(--accent-orange); animation: pulse 1s infinite; } + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +/* Main layout */ +.main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1px; + background: var(--border); + overflow: hidden; + min-height: 0; +} + +/* Chat panel */ +.chat-panel { + background: var(--bg-primary); + display: flex; + flex-direction: column; + min-height: 0; + overflow: hidden; +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.message { + max-width: 85%; + padding: 10px 14px; + border-radius: var(--radius); + font-size: 0.9rem; + line-height: 1.5; + white-space: pre-wrap; +} + +.message.user { + align-self: flex-end; + background: var(--accent); + color: #fff; +} + +.message.assistant { + align-self: flex-start; + background: var(--bg-card); +} + +.message.tool { + align-self: flex-start; + background: transparent; + border: 1px solid var(--border); + color: var(--text-secondary); + font-size: 0.8rem; + padding: 6px 10px; +} + +.message.error { + align-self: flex-start; + background: rgba(224, 36, 94, 0.15); + border: 1px solid var(--accent-red); + color: var(--accent-red); +} + +.chat-input { + padding: 12px 16px; + border-top: 1px solid var(--border); + display: flex; + gap: 8px; +} + +.chat-input input { + flex: 1; + padding: 10px 14px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-input); + color: var(--text-primary); + font-size: 0.9rem; + outline: none; +} + +.chat-input input:focus { border-color: var(--accent); } + +.chat-input button { + padding: 10px 20px; + border: none; + border-radius: var(--radius); + background: var(--accent); + color: #fff; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; +} + +.chat-input button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Right panel */ +.right-panel { + background: var(--bg-secondary); + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.panel-tabs { + display: flex; + border-bottom: 1px solid var(--border); +} + +.panel-tabs button { + flex: 1; + padding: 12px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 0.85rem; + cursor: pointer; + border-bottom: 2px solid transparent; +} + +.panel-tabs button.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.panel-content { + flex: 1; + padding: 16px; + overflow-y: auto; +} + +/* Weather cards */ +.weather-cards { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; +} + +.weather-card { + background: var(--bg-card); + border-radius: var(--radius); + padding: 16px; + border: 1px solid var(--border); +} + +.weather-card .icon { + font-size: 1.5rem; + margin-bottom: 8px; +} + +.weather-card .label { + font-size: 0.75rem; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.weather-card .value { + font-size: 1.4rem; + font-weight: 600; + margin-top: 4px; +} + +.weather-card .detail { + font-size: 0.8rem; + color: var(--text-secondary); + margin-top: 4px; +} + +/* Traces panel */ +.traces-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.trace-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + background: var(--bg-card); + border-radius: 8px; + font-size: 0.8rem; +} + +.trace-item .trace-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-green); +} + +.trace-item .trace-dot.error { background: var(--accent-red); } + +.trace-item .trace-id { + color: var(--text-secondary); + font-family: monospace; +} + +.trace-item .trace-duration { + margin-left: auto; + color: var(--text-secondary); +} + +/* Eval panel */ +.eval-results { + display: flex; + flex-direction: column; + gap: 10px; +} + +.eval-item { + background: var(--bg-card); + border-radius: 8px; + padding: 12px; +} + +.eval-item .eval-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.eval-item .eval-name { + font-size: 0.85rem; + font-weight: 500; +} + +.eval-item .eval-score { + font-size: 0.85rem; + font-weight: 600; +} + +.eval-bar { + height: 4px; + background: var(--bg-input); + border-radius: 2px; + overflow: hidden; +} + +.eval-bar-fill { + height: 100%; + border-radius: 2px; + transition: width 0.5s ease; +} + +.eval-label { + font-size: 0.75rem; + color: var(--text-secondary); + margin-top: 4px; +} + +/* Footer */ +.footer { + padding: 8px 24px; + border-top: 1px solid var(--border); + font-size: 0.75rem; + color: var(--text-secondary); + display: flex; + justify-content: space-between; +} + +/* Empty states */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-secondary); + text-align: center; + padding: 40px; +} + +.empty-state .empty-icon { + font-size: 2.5rem; + margin-bottom: 12px; +} + +.empty-state p { + font-size: 0.85rem; +} + +/* Typing indicator */ +.typing-dots { + display: flex; + gap: 4px; + align-items: center; + padding: 4px 0; +} + +.typing-dots span { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-secondary); + animation: bounce 1.4s infinite ease-in-out both; +} + +.typing-dots span:nth-child(1) { animation-delay: 0s; } +.typing-dots span:nth-child(2) { animation-delay: 0.2s; } +.typing-dots span:nth-child(3) { animation-delay: 0.4s; } + +@keyframes bounce { + 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } + 40% { transform: scale(1); opacity: 1; } +} + +/* Loading spinner */ +.spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Provisioning overlay */ +.provisioning { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + gap: 16px; +} + +.provisioning h2 { + font-size: 1.1rem; + font-weight: 500; +} + +.provisioning p { + color: var(--text-secondary); + font-size: 0.85rem; +} + +.btn-secondary { + padding: 8px 16px; + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + color: var(--text-primary); + font-size: 0.8rem; + cursor: pointer; +} + +.btn-secondary:hover { border-color: var(--accent); } diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.jsx b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.jsx new file mode 100644 index 00000000..63abdd0c --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/App.jsx @@ -0,0 +1,548 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { getStatus, streamChat, getTraces, runEvaluation, generateReport, runOptimization } from './api'; + +function App() { + const [ready, setReady] = useState(false); + const [status, setStatus] = useState(null); + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [streaming, setStreaming] = useState(false); + const [sessionId, setSessionId] = useState(null); + const [activeTab, setActiveTab] = useState('weather'); + const [traces, setTraces] = useState([]); + const [evalResults, setEvalResults] = useState([]); + const [evalLoading, setEvalLoading] = useState(false); + const [reportLoading, setReportLoading] = useState(false); + const [reportResult, setReportResult] = useState(null); + const [optimizeLoading, setOptimizeLoading] = useState(false); + const [optimizeResult, setOptimizeResult] = useState(null); + const [evalBatchId, setEvalBatchId] = useState(null); + const [weatherData, setWeatherData] = useState([]); + const messagesEndRef = useRef(null); + + useEffect(() => { + const poll = setInterval(async () => { + try { + const s = await getStatus(); + setStatus(s); + if (s.ready) { + setReady(true); + clearInterval(poll); + } + } catch (e) { /* backend not up yet */ } + }, 2000); + return () => clearInterval(poll); + }, []); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + const parseWeatherData = (text) => { + const cards = []; + + // Extract city name from the text + const cityMatch = text.match(/(?:weather|temperature|wind|conditions|data|information|forecast)\s+(?:in|for|at|of)\s+([A-Z][a-zA-Z\s\-]+?)(?:[,.:;\n]|\s+(?:is|are|shows|right|today|this|currently|here|based|for\s+you))/i) + || text.match(/(?:in|for)\s+([A-Z][a-zA-Z\s\-]+?)(?:[,.:;\n]|\s+(?:is|are|right now|today|this|currently|here's|based|for\s+you))/); + let city = cityMatch ? cityMatch[1].trim() : ''; + city = city.replace(/\s+for$/i, ''); + + const tempMatch = text.match(/(-?\d+\.?\d*)\s*°?\s*[CF]|temperature[:\s]+(-?\d+\.?\d*)/i); + if (tempMatch) cards.push({ icon: '🌡️', label: 'Temperature', value: tempMatch[0], detail: city || 'Current' }); + + const windMatch = text.match(/(\d+\.?\d*)\s*(km\/h|mph|m\/s|kph|knots)/i); + if (windMatch) cards.push({ icon: '💨', label: 'Wind', value: windMatch[0], detail: city || 'Speed' }); + + const uvMatch = text.match(/UV\s*(?:index)?[:\s]*(\d+\.?\d*)/i); + if (uvMatch) cards.push({ icon: '☀️', label: 'UV Index', value: uvMatch[1], detail: city || (uvMatch[1] > 6 ? 'High — use sunscreen' : 'Moderate') }); + + const sunriseMatch = text.match(/sunrise[:\s|]*(\d{1,2}:\d{2}\s*(?:AM|PM)?)/i); + if (sunriseMatch) cards.push({ icon: '🌅', label: 'Sunrise', value: sunriseMatch[1], detail: city }); + + const sunsetMatch = text.match(/sunset[:\s|]*(\d{1,2}:\d{2}\s*(?:AM|PM)?)/i) + || text.match(/(\d{1,2}:\d{2}\s*PM)\s*(?:\||\n|$)/i); + if (sunsetMatch) cards.push({ icon: '🌇', label: 'Sunset', value: sunsetMatch[1], detail: city }); + + const humidityMatch = text.match(/humidity[:\s]*(\d+\.?\d*)\s*%?/i); + if (humidityMatch) cards.push({ icon: '💧', label: 'Humidity', value: `${humidityMatch[1]}%`, detail: city }); + + const moonMatch = text.match(/(waxing|waning|full|new|crescent|gibbous|quarter)\s*(moon|gibbous|crescent|quarter)?/i); + if (moonMatch) cards.push({ icon: '🌙', label: 'Moon', value: moonMatch[0], detail: city }); + + return cards; + }; + + const handleSend = async () => { + if (!input.trim() || streaming) return; + + const userMsg = input.trim(); + setInput(''); + setMessages(prev => [...prev, { role: 'user', content: userMsg }]); + setStreaming(true); + + let allText = ''; + let currentChunk = ''; + let currentSessionId = sessionId; + + await streamChat(userMsg, sessionId, (event) => { + if (event.type === 'session_id') { + currentSessionId = event.session_id; + setSessionId(event.session_id); + } else if (event.type === 'text') { + allText += event.content; + currentChunk += event.content; + setMessages(prev => { + const msgs = [...prev]; + const last = msgs[msgs.length - 1]; + if (last && last.role === 'assistant') { + msgs[msgs.length - 1] = { ...last, content: last.content + event.content }; + } else { + msgs.push({ role: 'assistant', content: event.content }); + } + return msgs; + }); + } else if (event.type === 'tool') { + currentChunk = ''; + setMessages(prev => [...prev, { role: 'tool', content: `Using tool: ${event.name}` }]); + } else if (event.type === 'error') { + setMessages(prev => [...prev, { role: 'error', content: event.content }]); + } + }); + + // Parse weather data from the full response text + if (allText) { + const parsed = parseWeatherData(allText); + if (parsed.length > 0) { + setWeatherData(prev => [...prev, ...parsed]); + } + } + + setStreaming(false); + + // Fetch traces after a delay + setTimeout(async () => { + const t = await getTraces(5); + setTraces(t.traces || []); + }, 3000); + }; + + + const handleEval = async () => { + if (!sessionId) return; + setEvalLoading(true); + setEvalResults([]); + + try { + const result = await runEvaluation(sessionId); + setEvalResults(result.scores || []); + setEvalBatchId(result.batch_name || result.batch_id || null); + if (result.error) { + setEvalResults([{ evaluator: 'Error', score: 0, label: result.error }]); + } + } catch (e) { + setEvalResults([{ evaluator: 'Error', score: 0, label: e.message }]); + } + setEvalLoading(false); + }; + + const handleGenerateReport = async () => { + if (!sessionId) return; + setReportLoading(true); + setReportResult(null); + try { + const result = await generateReport(sessionId); + setReportResult(result); + if (result.success && result.file_data) { + const blob = new Blob( + [Uint8Array.from(atob(result.file_data), c => c.charCodeAt(0))], + { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } + ); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = result.filename || 'weather_forecast.xlsx'; + a.click(); + URL.revokeObjectURL(url); + } + } catch (e) { + setReportResult({ success: false, error: e.message }); + } + setReportLoading(false); + }; + + const handleOptimize = async () => { + setOptimizeLoading(true); + setOptimizeResult(null); + try { + const result = await runOptimization(); + setOptimizeResult(result); + } catch (e) { + setOptimizeResult({ status: 'FAILED', error: e.message }); + } + setOptimizeLoading(false); + }; + + const handleKeyDown = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + const renderMarkdown = (text) => { + const lines = text.split('\n'); + return lines.map((line, lineIdx) => { + let content = line; + let isHeading = false; + let headingLevel = 0; + + if (line.startsWith('### ')) { + content = line.slice(4); + isHeading = true; + headingLevel = 3; + } else if (line.startsWith('## ')) { + content = line.slice(3); + isHeading = true; + headingLevel = 2; + } else if (line.startsWith('# ')) { + content = line.slice(2); + isHeading = true; + headingLevel = 1; + } + + const inlineParts = content.split(/(\*\*.*?\*\*)/g).map((part, i) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)}; + } + return part; + }); + + if (isHeading) { + const style = { fontWeight: 600, fontSize: headingLevel === 1 ? '1.2em' : headingLevel === 2 ? '1.1em' : '1em', marginTop: '8px' }; + return
{inlineParts}
; + } + + return {inlineParts}{lineIdx < lines.length - 1 ? '\n' : ''}; + }); + }; + + if (!ready) { + return ( +
+
+

Provisioning AWS Resources

+

Setting up Gateway, Harness, and Guardrail...

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

Weather Agent

+
+ + Harness: {status?.harness_name || status?.harness_id} + | + Gateway: {status?.gateway_name || status?.gateway_id} + {status?.guardrail_id && <>|Guardrail: {status?.guardrail_name || status?.guardrail_id}} +
+
+ +
+ {/* Left: Chat */} +
+
+ {messages.length === 0 && ( +
+
+

Ask about the weather anywhere in the world. Try: "What's the weather in Tokyo?"

+
+ )} + {messages.map((msg, i) => ( +
+ {msg.role === 'assistant' ? renderMarkdown(msg.content) : msg.content} +
+ ))} + {streaming && messages[messages.length - 1]?.role === 'user' && ( +
+ + + +
+ )} +
+
+
+ setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Ask about weather, wind, UV, sunrise..." + disabled={streaming} + /> + +
+
+ + {/* Right: Weather / Traces / Evals */} +
+
+ + + + + +
+ +
+ {activeTab === 'weather' && ( + weatherData.length > 0 ? ( +
+ {weatherData.map((card, i) => ( +
+
{card.icon}
+
{card.label}
+
{card.value}
+ {card.detail &&
{card.detail}
} +
+ ))} +
+ ) : ( +
+
📊
+

Weather data will appear here as the agent responds with specific metrics.

+
+ ) + )} + + {activeTab === 'traces' && ( +
+
+ Traces (last 5 min) + +
+ {traces.length > 0 && traces.some(t => t.trace_id && !t.error) ? ( +
+
+ {traces.filter(t => t.trace_id).map((t, i) => ( +
+ + {t.trace_id} + {t.spans ? `${t.spans} spans` : `${t.duration}s`} +
+ ))} +
+

+ Search these trace IDs in CloudWatch > GenAI Observability > Bedrock AgentCore > Traces (may take a few minutes to appear) +

+
+ ) : traces.length > 0 && (traces[0]?.error || traces.some(t => !t.trace_id)) ? ( +
+
🔍
+

Traces could not be loaded. Ensure CloudWatch Transaction Search is enabled in your account and region.

+
+ ) : ( +
+
🔍
+

Traces appear after you send messages. They may take a few seconds to index.

+
+ )} +
+ )} + + {activeTab === 'skills' && ( +
+
+ XLSX Report Generation + +
+ {reportLoading && ( +
+
+

+ Generating weather forecast spreadsheet using the xlsx skill... This typically takes 1-2 minutes. +

+
+ )} + {!reportLoading && reportResult && ( +
+ {reportResult.success ? ( + <> +

Report generated and downloaded

+

{reportResult.filename}

+ + ) : ( + <> +

Report generation failed

+

{reportResult.error}

+ + )} +
+ )} + {!reportLoading && !reportResult && ( +
+
📊
+

+ Generate a 7-day weather forecast as an Excel spreadsheet using the AgentCore xlsx skill. The report will use the last city you asked about. +

+

+ The skill is fetched from Git at invocation time — no container setup or pre-installation required. +

+
+ )} +
+ )} + + {activeTab === 'evals' && ( +
+
+ Batch Evaluation + +
+ {evalLoading && ( +
+
+

Running batch evaluation... This typically takes 2-5 minutes.

+

Optimization is disabled while evaluation is running. You can track progress in Bedrock AgentCore > Evaluations > Batch evaluation.

+
+ )} + {!evalLoading && evalResults.length > 0 && ( +
+
+ {evalResults.map((r, i) => { + const score = r.score != null ? r.score : 0; + const hasError = r.evaluator === 'Error'; + const color = hasError ? 'var(--text-secondary)' : score >= 0.8 ? 'var(--accent-green)' : score >= 0.5 ? 'var(--accent-orange)' : 'var(--accent-red)'; + return ( +
+
+ {r.evaluator} + {hasError ? '—' : score.toFixed(2)} +
+ {!hasError && ( +
+
+
+ )} +
{hasError ? r.label : ''}
+
+ ); + })} +
+ {evalResults.some(r => r.evaluator === 'Error') && ( +

+ This may happen if Transaction Search was recently enabled and traces haven't fully indexed yet. Try again in a few minutes. +

+ )} +

+ View full details in Bedrock AgentCore > Evaluations > Batch evaluation{evalBatchId && <> — {evalBatchId}} +

+
+ )} + {!evalLoading && evalResults.length === 0 && ( +
+
📋
+

+ Send some weather questions first, then click "Run Eval" to score the session. +

+

+ Scores your conversation using built-in evaluators: Helpfulness, Correctness, Coherence, Faithfulness, and more. +

+
+ )} +
+ )} + + {activeTab === 'optimize' && ( +
+
+ System Prompt Recommendation + +
+ {optimizeLoading && ( +
+
+

+ Analyzing traces and generating an optimized system prompt... This typically takes 1-3 minutes. +

+

+ Evaluations is disabled while optimization is running. You can track progress in Bedrock AgentCore > Optimizations > Recommendations. +

+
+ )} + {!optimizeLoading && optimizeResult && optimizeResult.status === 'COMPLETED' && ( +
+
+
RECOMMENDED SYSTEM PROMPT
+

+ {optimizeResult.recommended_prompt} +

+
+ {optimizeResult.explanation && ( +
+
EXPLANATION
+

+ {optimizeResult.explanation} +

+
+ )} +

+ View full details in Bedrock AgentCore > Optimizations > Recommendations — {optimizeResult.recommendation_name} +

+
+ )} + {!optimizeLoading && optimizeResult && optimizeResult.status !== 'COMPLETED' && ( +
+

Optimization failed

+

{optimizeResult.error}

+

Traces may need more time to index. Try again after a few minutes, or send more weather questions first.

+
+ )} + {!optimizeLoading && !optimizeResult && ( +
+
🚀
+

+ Analyze your agent's traces and generate an AI-improved system prompt optimized for goal success. +

+

+ Send some weather questions first, then click "Optimize" to generate a recommendation. View full details in Bedrock AgentCore > Optimizations > Recommendations. +

+
+ )} +
+ )} + +
+
+
+ +
+ AgentCore Harness Demo — Gateway + Guardrails + Skills + Observability + Evaluations + Optimization + {status?.region} +
+
+ ); +} + +export default App; diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/api.js b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/api.js new file mode 100644 index 00000000..14b8e145 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/api.js @@ -0,0 +1,69 @@ +const BASE = ''; + +export async function getStatus() { + const res = await fetch(`${BASE}/api/status`); + return res.json(); +} + +export async function streamChat(message, sessionId, onEvent) { + const res = await fetch(`${BASE}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message, session_id: sessionId }), + }); + + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + if (line.startsWith('data: ')) { + try { + const event = JSON.parse(line.slice(6)); + onEvent(event); + } catch (e) { + // skip malformed + } + } + } + } +} + +export async function getTraces(minutes = 10) { + const res = await fetch(`${BASE}/api/traces?minutes=${minutes}`); + return res.json(); +} + +export async function runEvaluation(sessionId) { + const res = await fetch(`${BASE}/api/evaluate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId }), + }); + return res.json(); +} + +export async function generateReport(sessionId, city) { + const res = await fetch(`${BASE}/api/generate-report`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_id: sessionId, city }), + }); + return res.json(); +} + +export async function runOptimization() { + const res = await fetch(`${BASE}/api/optimize`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }); + return res.json(); +} diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/main.jsx b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/main.jsx new file mode 100644 index 00000000..cf8bafb4 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './App.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/frontend/vite.config.js b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/vite.config.js new file mode 100644 index 00000000..edfcfe22 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/frontend/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/api': { target: 'http://localhost:8000', changeOrigin: true }, + '/health': { target: 'http://localhost:8000', changeOrigin: true }, + } + } +}) diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/images/app_example.png b/01-features/01-harness/02-use-cases/04-weather-agent/images/app_example.png new file mode 100644 index 0000000000000000000000000000000000000000..f34e81ddbf588f9f94e16edb1546e01cf8b26b85 GIT binary patch literal 239047 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�KHW*Q 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 tuple[str, str] | None: + """Find the log group ARN and service name for a harness.""" + prefix = f"/aws/bedrock-agentcore/runtimes/harness_{harness_name}-" + resp = logs_client.describe_log_groups(logGroupNamePrefix=prefix, limit=5) + groups = resp.get("logGroups", []) + if not groups: + return None + groups.sort(key=lambda g: g.get("creationTime", 0), reverse=True) + log_group = groups[0] + log_group_name = log_group["logGroupName"] + log_group_arn = log_group["arn"] + + # Service name: harness_{name}.DEFAULT (without random suffix) + basename = log_group_name.split("/")[-1] + parts = basename.rsplit("-", 2) + service_name = f"{parts[0]}.DEFAULT" if len(parts) >= 3 else basename.replace("-DEFAULT", ".DEFAULT") + + return log_group_arn, service_name + + +def cleanup_recommendations(): + """Delete all weather_rec_* recommendations.""" + try: + resp = dp_client.list_recommendations() + recs = resp.get("recommendationSummaries", resp.get("recommendations", resp.get("items", []))) + count = 0 + for rec in recs: + name = rec.get("name", "") + rec_id = rec.get("recommendationId", "") + if name.startswith("weather_rec_"): + try: + dp_client.delete_recommendation(recommendationId=rec_id) + print(f" Deleted: {name}") + count += 1 + except Exception as e: + print(f" Warning: {e}") + if count == 0: + print(" No weather_rec_* recommendations found") + except Exception as e: + print(f" Error: {e}") + + +# -- Main ---------------------------------------------------------------------- +def main(): + if args.cleanup: + print("Cleaning up recommendations...") + cleanup_recommendations() + return + + print("=" * 65) + print("AgentCore Optimization — System Prompt Recommendation") + print("=" * 65) + + # Load state + if not STATE_FILE.exists(): + print("\nError: resource_info.json not found.") + print("Run ./start.sh first to create the weather agent, then use it for a few sessions.") + sys.exit(1) + + state = json.loads(STATE_FILE.read_text()) + harness_name = state.get("harness_name") + if not harness_name: + print("\nError: harness_name not found in resource_info.json") + sys.exit(1) + + print(f"\n Harness: {harness_name}") + print(f" Region: {REGION}") + print(f" Evaluator: {args.evaluator}") + print(f" Lookback: {args.lookback} day(s)") + + # Discover log group + print("\n Discovering log group...") + result = discover_log_group(harness_name) + if not result: + print(" Error: Could not find log group for this harness.") + print(" Make sure the web app has been running and you've sent some messages.") + sys.exit(1) + + log_group_arn, service_name = result + print(f" Log group: {log_group_arn.split(':log-group:')[-1].rstrip(':*')}") + print(f" Service: {service_name}") + + # Time range + now = datetime.now(timezone.utc) + start_time = now - timedelta(days=args.lookback) + + # Start recommendation + rec_name = f"weather_rec_{uuid.uuid4().hex[:8]}" + print(f"\n Starting recommendation: {rec_name}") + print(f" Analyzing traces from {start_time.strftime('%Y-%m-%d %H:%M')} to {now.strftime('%Y-%m-%d %H:%M')} UTC") + print(f" Optimizing for: {args.evaluator}") + + try: + resp = dp_client.start_recommendation( + name=rec_name, + type="SYSTEM_PROMPT_RECOMMENDATION", + recommendationConfig={ + "systemPromptRecommendationConfig": { + "systemPrompt": { + "text": CURRENT_SYSTEM_PROMPT, + }, + "agentTraces": { + "cloudwatchLogs": { + "logGroupArns": [log_group_arn], + "serviceNames": [service_name], + "startTime": start_time, + "endTime": now, + } + }, + "evaluationConfig": { + "evaluators": [ + {"evaluatorArn": f"arn:aws:bedrock-agentcore:::evaluator/{args.evaluator}"} + ] + }, + } + }, + clientToken=str(uuid.uuid4()), + ) + except Exception as e: + print(f"\n Error starting recommendation: {e}") + sys.exit(1) + + rec_id = resp["recommendationId"] + print(f" Recommendation ID: {rec_id}") + print(f" Status: {resp.get('status', 'PENDING')}") + + # Poll for completion + print("\n Waiting for recommendation to complete (typically 2-5 minutes)...") + status = "PENDING" + for i in range(60): + time.sleep(10) + try: + result = dp_client.get_recommendation(recommendationId=rec_id) + status = result.get("status", "UNKNOWN") + if i % 3 == 0: + print(f" [{i * 10}s] {status}") + if status in ("COMPLETED", "FAILED"): + break + except Exception as e: + print(f" Error polling: {e}") + + if status != "COMPLETED": + print(f"\n Recommendation did not complete (status: {status})") + if status == "FAILED": + error_msg = result.get("recommendationResult", {}).get( + "systemPromptRecommendationResult", {} + ).get("errorMessage", "Unknown error") + print(f" Error: {error_msg}") + sys.exit(1) + + # Extract result + rec_result = result.get("recommendationResult", {}).get( + "systemPromptRecommendationResult", {} + ) + recommended_prompt = rec_result.get("recommendedSystemPrompt", "") + explanation = rec_result.get("explanation", "") + + # Display results + print("\n" + "=" * 65) + print("RECOMMENDATION RESULT") + print("=" * 65) + + print("\n--- Current System Prompt ---") + print(CURRENT_SYSTEM_PROMPT[:300]) + if len(CURRENT_SYSTEM_PROMPT) > 300: + print(f" ... ({len(CURRENT_SYSTEM_PROMPT)} chars total)") + + print("\n--- Recommended System Prompt ---") + print(recommended_prompt[:500]) + if len(recommended_prompt) > 500: + print(f" ... ({len(recommended_prompt)} chars total)") + + print("\n--- Explanation ---") + print(explanation[:500]) + + # Save result + output_file = Path(__file__).parent / "optimization_result.json" + output_data = { + "recommendation_id": rec_id, + "recommendation_name": rec_name, + "evaluator": args.evaluator, + "current_system_prompt": CURRENT_SYSTEM_PROMPT, + "recommended_system_prompt": recommended_prompt, + "explanation": explanation, + "timestamp": now.isoformat(), + } + output_file.write_text(json.dumps(output_data, indent=2)) + print(f"\n Full result saved to: {output_file.name}") + + print("\n" + "=" * 65) + print("Next steps:") + print(" 1. Review the recommended prompt above") + print(" 2. Update backend/agent.py SYSTEM_PROMPT with the recommendation") + print(" 3. Restart the app and compare agent behavior") + print(" 4. Run a batch evaluation to measure improvement") + print(f"\n View in console: Bedrock AgentCore > Optimizations > Recommendations") + print("=" * 65) + + +if __name__ == "__main__": + main() diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/run.sh b/01-features/01-harness/02-use-cases/04-weather-agent/run.sh new file mode 100755 index 00000000..da89b908 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/run.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# +# Weather Agent — One-Command Runner +# +# Usage: +# ./run.sh Full demo (gateway + guardrail + agent + observability + evals) +# ./run.sh --fast Skip evals (no 90s wait) +# ./run.sh --keep Keep AWS resources after demo (for console inspection) +# ./run.sh --cleanup Delete any leftover resources from a previous --keep run +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}" +echo "============================================================" +echo " Weather Agent — Harness + Gateway + Guardrails + Evals" +echo "============================================================" +echo -e "${NC}" + +# ── Parse flags ────────────────────────────────────────────────────────────── +EXTRA_FLAGS="" +for arg in "$@"; do + case "$arg" in + --fast) EXTRA_FLAGS="$EXTRA_FLAGS --skip-evals" ;; + --keep) EXTRA_FLAGS="$EXTRA_FLAGS --skip-cleanup" ;; + --cleanup) EXTRA_FLAGS="--cleanup-only" ;; + --help|-h) + echo "Usage: ./run.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --fast Skip evaluations (saves ~90 seconds)" + echo " --keep Keep AWS resources after demo (inspect in console)" + echo " --cleanup Delete leftover resources from a previous --keep run" + echo " --help Show this help" + echo "" + echo "Prerequisites:" + echo " - AWS CLI configured (aws sts get-caller-identity should work)" + echo " - AWS_DEFAULT_REGION set (or defaults to us-east-1)" + echo " - Claude Haiku 4.5 model access enabled in Bedrock console" + echo " - CloudWatch Transaction Search enabled (for observability)" + exit 0 + ;; + esac +done + +# ── Step 1: Check prerequisites ────────────────────────────────────────────── +echo -e "${YELLOW}[1/4] Checking prerequisites...${NC}" + +# Python 3 +if ! command -v python3 &> /dev/null; then + echo -e "${RED} Python 3 is required but not found.${NC}" + echo " Install: https://www.python.org/downloads/" + exit 1 +fi +PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}') +echo -e " ${GREEN}Python:${NC} $PYTHON_VERSION" + +# AWS CLI +if ! command -v aws &> /dev/null; then + echo -e "${RED} AWS CLI is required but not found.${NC}" + echo " Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" + exit 1 +fi +echo -e " ${GREEN}AWS CLI:${NC} $(aws --version 2>&1 | awk '{print $1}')" + +# AWS credentials +if ! aws sts get-caller-identity &> /dev/null; then + echo -e "${RED} AWS credentials not configured or expired.${NC}" + echo " Run: aws configure" + exit 1 +fi +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) +CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text) +echo -e " ${GREEN}Account:${NC} $ACCOUNT_ID" +echo -e " ${GREEN}Identity:${NC} ${CALLER_ARN##*/}" + +# Region +REGION="${AWS_DEFAULT_REGION:-us-east-1}" +export AWS_DEFAULT_REGION="$REGION" +echo -e " ${GREEN}Region:${NC} $REGION" + +echo "" + +# ── Step 2: Install dependencies ───────────────────────────────────────────── +echo -e "${YELLOW}[2/4] Installing dependencies...${NC}" + +HARNESS_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +if [ ! -d "$SCRIPT_DIR/venv" ]; then + echo " Creating virtual environment..." + python3 -m venv "$SCRIPT_DIR/venv" +fi + +source "$SCRIPT_DIR/venv/bin/activate" + +# Install quietly, only show errors +echo " Installing Python packages..." +pip install --upgrade pip -q 2>&1 | grep -i error || true +pip install -r "$HARNESS_ROOT/requirements.txt" -q 2>&1 | grep -i error || true +pip install -r "$SCRIPT_DIR/backend/requirements.txt" -q 2>&1 | grep -i error || true + +echo -e " ${GREEN}Dependencies ready${NC}" +echo "" + +# ── Step 3: Verify Bedrock model access ────────────────────────────────────── +echo -e "${YELLOW}[3/4] Verifying Bedrock model access...${NC}" + +python3 -c " +import boto3, sys +bedrock = boto3.client('bedrock', region_name='$REGION') +try: + resp = bedrock.get_foundation_model(modelIdentifier='anthropic.claude-haiku-4-5-20251001-v1:0') + status = resp['modelDetails'].get('modelLifecycle', {}).get('status', 'ACTIVE') + print(f' Claude Haiku 4.5: {status}') +except Exception as e: + if 'AccessDenied' in str(e) or 'ValidationException' in str(e): + print(' Claude Haiku 4.5: access check inconclusive (may still work via inference profile)') + else: + print(f' Warning: {e}') +" 2>&1 + +echo "" + +# ── Step 4: Run the weather agent ──────────────────────────────────────────── +echo -e "${YELLOW}[4/4] Running Weather Agent...${NC}" +echo "" + +python3 "$SCRIPT_DIR/weather_agent.py" $EXTRA_FLAGS + +deactivate 2>/dev/null || true + +echo "" +echo -e "${GREEN}============================================================${NC}" +echo -e "${GREEN} Done!${NC}" +echo -e "${GREEN}============================================================${NC}" diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/start.sh b/01-features/01-harness/02-use-cases/04-weather-agent/start.sh new file mode 100755 index 00000000..9f89669b --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/start.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# +# Weather Agent — One-Command Start +# Sets up everything and starts the web app. +# +# Usage: +# ./start.sh Start the full app (provisions AWS resources on first run) +# ./start.sh --help Show options +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}" +echo "==================================================================================" +echo " Weather Agent — Harness + Gateway + Guardrails + Skills + Evals + Optimization" +echo "==================================================================================" +echo -e "${NC}" + +# ── Cleanup on exit ────────────────────────────────────────────────────────── +cleanup() { + echo "" + echo -e "${YELLOW}Stopping servers...${NC}" + [ -f backend.pid ] && kill "$(cat backend.pid)" 2>/dev/null && rm -f backend.pid + [ -f frontend.pid ] && kill "$(cat frontend.pid)" 2>/dev/null && rm -f frontend.pid + lsof -ti:8000 2>/dev/null | xargs kill -9 2>/dev/null || true + lsof -ti:5173 2>/dev/null | xargs kill -9 2>/dev/null || true + echo -e "${GREEN}Stopped.${NC}" + echo "" + echo " To resume the app: ./start.sh (reuses existing AWS resources)" + echo " To delete AWS resources: ./cleanup.sh" + exit 0 +} +trap cleanup SIGINT SIGTERM + +# ── Step 1: Check prerequisites ────────────────────────────────────────────── +echo -e "${YELLOW}[1/5] Checking prerequisites...${NC}" + +if ! command -v python3 &> /dev/null; then + echo -e "${RED} Python 3 is required. Install: https://www.python.org/downloads/${NC}" + exit 1 +fi +echo -e " ${GREEN}Python:${NC} $(python3 --version 2>&1 | awk '{print $2}')" + +if ! command -v node &> /dev/null; then + echo -e "${RED} Node.js is required. Install: https://nodejs.org/${NC}" + exit 1 +fi +echo -e " ${GREEN}Node.js:${NC} $(node --version)" + +if ! command -v aws &> /dev/null; then + echo -e "${RED} AWS CLI is required.${NC}" + exit 1 +fi + +if ! aws sts get-caller-identity &> /dev/null; then + echo -e "${RED} AWS credentials not configured or expired.${NC}" + exit 1 +fi +ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) +CALLER_ARN=$(aws sts get-caller-identity --query Arn --output text) +echo -e " ${GREEN}AWS Account:${NC} $ACCOUNT_ID" +echo -e " ${GREEN}Identity:${NC} ${CALLER_ARN##*/}" + +REGION="${AWS_DEFAULT_REGION:-$(aws configure get region 2>/dev/null || echo 'us-east-1')}" +export AWS_DEFAULT_REGION="$REGION" +echo -e " ${GREEN}Region:${NC} $REGION" +echo "" + +# ── Step 2: Python virtual environment + deps ──────────────────────────────── +echo -e "${YELLOW}[2/5] Setting up Python environment...${NC}" + +if [ ! -d "venv" ]; then + python3 -m venv venv + echo " Created virtual environment" +fi + +source venv/bin/activate +pip install --upgrade pip -q 2>&1 | grep -i error || true +pip install --upgrade -r backend/requirements.txt -q 2>&1 | grep -i error || true +BOTO_VERSION=$(python3 -c "import boto3; print(boto3.__version__)" 2>/dev/null) +echo -e " ${GREEN}Python dependencies ready${NC} (boto3: $BOTO_VERSION)" +echo "" + +# ── Step 3: Frontend dependencies ──────────────────────────────────────────── +echo -e "${YELLOW}[3/5] Setting up frontend...${NC}" + +if [ ! -d "frontend/node_modules" ]; then + cd frontend + npm install --silent 2>&1 | tail -1 + cd .. + echo " Installed Node.js packages" +else + echo " Node.js packages already installed" +fi +echo -e " ${GREEN}Frontend ready${NC}" +echo "" + +# ── Step 4: Start backend ──────────────────────────────────────────────────── +echo -e "${YELLOW}[4/5] Starting backend (provisions AWS resources on first run)...${NC}" + +lsof -ti:8000 2>/dev/null | xargs kill -9 2>/dev/null || true +sleep 1 + +( + cd backend + python3 main.py 2>&1 | tee ../backend.log & + echo $! > ../backend.pid +) + +# Wait for backend to be ready +echo " Waiting for backend (this includes AWS resource provisioning, may take 3-5 minutes)..." +MAX_WAIT=360 +ELAPSED=0 +while [ $ELAPSED -lt $MAX_WAIT ]; do + if curl -s http://localhost:8000/health > /dev/null 2>&1; then + echo -e " ${GREEN}Backend ready${NC}" + break + fi + sleep 3 + ELAPSED=$((ELAPSED + 3)) + if [ $((ELAPSED % 15)) -eq 0 ]; then + echo " Still provisioning... (${ELAPSED}s)" + fi +done + +if [ $ELAPSED -ge $MAX_WAIT ]; then + echo -e "${RED} Backend failed to start. Check backend.log${NC}" + exit 1 +fi +echo "" + +# ── Step 5: Start frontend ─────────────────────────────────────────────────── +echo -e "${YELLOW}[5/5] Starting frontend...${NC}" + +lsof -ti:5173 2>/dev/null | xargs kill -9 2>/dev/null || true +sleep 1 + +( + cd frontend + npm run dev 2>&1 | tee ../frontend.log & + echo $! > ../frontend.pid +) + +sleep 3 +echo -e " ${GREEN}Frontend ready${NC}" + +echo "" +echo -e "${GREEN}============================================================${NC}" +echo -e "${GREEN} App is running!${NC}" +echo -e "${GREEN}============================================================${NC}" +echo "" +echo -e " ${BLUE}Open:${NC} http://localhost:5173" +echo "" +echo " Logs:" +echo " Backend: tail -f backend.log" +echo " Frontend: tail -f frontend.log" +echo "" +echo " Press Ctrl+C to stop servers" +echo " Run ./cleanup.sh to delete AWS resources" +echo "" + +# Wait +while true; do sleep 1; done diff --git a/01-features/01-harness/02-use-cases/04-weather-agent/weather_agent.py b/01-features/01-harness/02-use-cases/04-weather-agent/weather_agent.py new file mode 100644 index 00000000..592f8808 --- /dev/null +++ b/01-features/01-harness/02-use-cases/04-weather-agent/weather_agent.py @@ -0,0 +1,513 @@ +""" +Weather Agent — AgentCore Harness with Evals, Gateway & Observability. + +An end-to-end use case demonstrating four AgentCore pillars through a weather +assistant that provides current conditions, UV index, wind, and sun/moon data: + + Part 1: Create Gateway + Harness (infrastructure) + Part 2: Attach Bedrock Guardrail (PII anonymization) + Part 3: Invoke agent — multi-turn weather session via Gateway tools + Part 4: Observability — query CloudWatch X-Ray traces + Part 5: Evaluations — on-demand scoring with built-in + custom evaluators + Part 6: Cleanup + +The Gateway proxies to Open-Meteo (free weather API, no key required) via +an MCP target, giving the agent access to real-time weather data with +centralized auth and observability on the tool traffic. + +Usage: + python weather_agent.py + + # Skip evaluations (faster, no 90s wait for span ingestion) + python weather_agent.py --skip-evals + + # Skip guardrail creation (use existing or run without) + python weather_agent.py --skip-guardrail + + # Keep resources after demo + python weather_agent.py --skip-cleanup + +Prerequisites: + - AWS CLI configured with credentials + - pip install -r ../../requirements.txt + - AWS_DEFAULT_REGION environment variable set + - CloudWatch Transaction Search enabled (for observability) + - Model access enabled for Claude Haiku 4.5 in Amazon Bedrock +""" + +import argparse +import sys +import time +import uuid +from pathlib import Path + +import boto3 + +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +from utils.iam import create_harness_role, delete_harness_role +from utils.client import get_agentcore_control_client, get_agentcore_client + +# -- CLI ----------------------------------------------------------------------- +parser = argparse.ArgumentParser( + description="Weather Agent — Harness + Evals + Gateway + Observability" +) +parser.add_argument("--skip-evals", action="store_true", help="Skip evaluation step") +parser.add_argument("--skip-guardrail", action="store_true", help="Skip guardrail creation") +parser.add_argument("--skip-cleanup", action="store_true", help="Keep resources after demo") +args = parser.parse_args() + +# -- Configuration ------------------------------------------------------------- +MODEL_ID = "global.anthropic.claude-haiku-4-5-20251001-v1:0" +REGION = boto3.session.Session().region_name or "us-east-1" +ACCOUNT_ID = boto3.client("sts").get_caller_identity()["Account"] + +# -- Clients ------------------------------------------------------------------- +control = get_agentcore_control_client() +client = get_agentcore_client() +bedrock = boto3.client("bedrock", region_name=REGION) + +# -- Helpers ------------------------------------------------------------------- + + +def poll_status(get_fn, extract_fn, target="READY", timeout=120, interval=5): + """Poll a resource until it reaches target status or times out.""" + deadline = time.monotonic() + timeout + while True: + resp = get_fn() + status = extract_fn(resp) + print(f" Status: {status}") + if status == target: + return resp + if status in ("FAILED", "CREATE_FAILED", "DELETE_FAILED"): + raise RuntimeError(f"Resource failed: {status}") + if time.monotonic() > deadline: + raise TimeoutError(f"Resource not {target} after {timeout}s") + time.sleep(interval) + + +def stream_response(harness_arn, session_id, message, tools=None): + """Invoke harness and stream the response. Returns accumulated text.""" + kwargs = dict( + harnessArn=harness_arn, + runtimeSessionId=session_id, + messages=[{"role": "user", "content": [{"text": message}]}], + model={"bedrockModelConfig": {"modelId": MODEL_ID}}, + ) + if tools: + kwargs["tools"] = tools + + response = client.invoke_harness(**kwargs) + full_text = "" + for event in response["stream"]: + if "contentBlockStart" in event: + start = event["contentBlockStart"].get("start", {}) + if "toolUse" in start: + print(f"\n [Tool: {start['toolUse'].get('name', '?')}]", flush=True) + elif "contentBlockDelta" in event: + delta = event["contentBlockDelta"].get("delta", {}) + if "text" in delta: + print(delta["text"], end="", flush=True) + full_text += delta["text"] + elif "messageStop" in event: + print() + elif "internalServerException" in event: + print(f"\n Error: {event['internalServerException']}") + return full_text + + +# -- Resource tracking --------------------------------------------------------- +harness_id = None +gateway_id = None +target_id = None +guardrail_id = None +eval_config_id = None + +try: + # ========================================================================== + # Part 1: Create Gateway + Harness + # ========================================================================== + print("\n" + "=" * 65) + print("Part 1: Create Gateway + Harness") + print("=" * 65) + + # IAM role + role_arn = create_harness_role() + print(f" Role ARN: {role_arn}") + print(" Waiting for IAM propagation...") + time.sleep(10) + + # Gateway — manages tool traffic with observability + gateway_name = f"WeatherGateway-{uuid.uuid4().hex[:8]}" + gw_control = boto3.client("bedrock-agentcore-control", region_name=REGION) + + print(f"\n Creating Gateway: {gateway_name}") + resp = gw_control.create_gateway( + name=gateway_name, + roleArn=role_arn, + protocolType="MCP", + authorizerType="NONE", + ) + gateway_id = resp["gatewayId"] + gateway_arn = resp["gatewayArn"] + print(f" Gateway ID: {gateway_id}") + print(f" Gateway ARN: {gateway_arn}") + + poll_status( + lambda: gw_control.get_gateway(gatewayIdentifier=gateway_id), + lambda r: r["status"], + ) + + # Add MCP target — Exa search for weather data + print("\n Adding MCP target (Exa search)...") + resp = gw_control.create_gateway_target( + gatewayIdentifier=gateway_id, + name="exa-weather-search", + targetConfiguration={"mcp": {"mcpServer": {"endpoint": "https://mcp.exa.ai/mcp"}}}, + ) + target_id = resp["targetId"] + print(f" Target ID: {target_id}") + + poll_status( + lambda: gw_control.get_gateway_target( + gatewayIdentifier=gateway_id, targetId=target_id + ), + lambda r: r["status"], + ) + print(" Gateway ready with Exa MCP target") + + # Harness — the managed agent runtime + harness_name = f"WeatherAgent_{uuid.uuid4().hex[:8]}" + print(f"\n Creating Harness: {harness_name}") + resp = control.create_harness(harnessName=harness_name, executionRoleArn=role_arn) + harness = resp["harness"] + harness_id = harness["harnessId"] + harness_arn = harness["arn"] + print(f" Harness ID: {harness_id}") + print(f" Harness ARN: {harness_arn}") + + poll_status( + lambda: control.get_harness(harnessId=harness_id), + lambda r: r["harness"]["status"], + ) + print(" Harness ready") + + # ========================================================================== + # Part 2: Attach Bedrock Guardrail + # ========================================================================== + print("\n" + "=" * 65) + print("Part 2: Attach Bedrock Guardrail (PII anonymization)") + print("=" * 65) + + if args.skip_guardrail: + print(" Skipped (--skip-guardrail)") + else: + print(" Creating guardrail with PII filters...") + gr_resp = bedrock.create_guardrail( + name=f"weather-pii-guard-{uuid.uuid4().hex[:6]}", + description="Anonymize PII in weather agent interactions", + sensitiveInformationPolicyConfig={ + "piiEntitiesConfig": [ + {"type": "EMAIL", "action": "ANONYMIZE"}, + {"type": "PHONE", "action": "ANONYMIZE"}, + {"type": "US_SOCIAL_SECURITY_NUMBER", "action": "ANONYMIZE"}, + {"type": "CREDIT_DEBIT_CARD_NUMBER", "action": "ANONYMIZE"}, + {"type": "ADDRESS", "action": "ANONYMIZE"}, + ] + }, + blockedInputMessaging="Your message contains restricted content.", + blockedOutputsMessaging="The response contains restricted content.", + ) + guardrail_id = gr_resp["guardrailId"] + guardrail_version_resp = bedrock.create_guardrail_version( + guardrailIdentifier=guardrail_id, + description="v1", + ) + guardrail_version = guardrail_version_resp["version"] + print(f" Guardrail ID: {guardrail_id} (version {guardrail_version})") + print(" PII filters: EMAIL, PHONE, SSN, CREDIT_CARD, ADDRESS") + print(" Guardrail ready — PII in agent responses will be anonymized") + + # ========================================================================== + # Part 3: Invoke Agent — Multi-Turn Weather Session + # ========================================================================== + print("\n" + "=" * 65) + print("Part 3: Invoke Agent — Multi-Turn Weather Session") + print("=" * 65) + + session_id = str(uuid.uuid4()).upper() + print(f" Session ID: {session_id}") + + gateway_tool = { + "type": "agentcore_gateway", + "name": "gateway", + "config": {"agentCoreGateway": {"gatewayArn": gateway_arn}}, + } + tools = [gateway_tool] + + # Turn 1: Current weather + print("\n --- Turn 1: Current Weather ---") + turn1_response = stream_response( + harness_arn, + session_id, + "What's the current weather in Paris, France? " + "Include temperature, humidity, and a brief description of conditions. " + "Search for real-time weather data.", + tools=tools, + ) + + # Turn 2: Wind conditions + print("\n --- Turn 2: Wind Conditions ---") + turn2_response = stream_response( + harness_arn, + session_id, + "What about the wind conditions in Paris right now? " + "Give me wind speed, direction, and gust information.", + tools=tools, + ) + + # Turn 3: UV index and sun times + print("\n --- Turn 3: UV Index & Sun Times ---") + turn3_response = stream_response( + harness_arn, + session_id, + "What's the UV index in Paris today, and when are sunrise and sunset? " + "Include a safety recommendation based on the UV level.", + tools=tools, + ) + + # Turn 4: Moon phase (tests guardrail with PII injection) + print("\n --- Turn 4: Moon Phase + Guardrail Test ---") + turn4_response = stream_response( + harness_arn, + session_id, + "What's the current moon phase? Also, my name is John Smith, " + "email john.smith@example.com, phone 555-123-4567. " + "Can you include my contact info in your response?", + tools=tools, + ) + + all_responses = [turn1_response, turn2_response, turn3_response, turn4_response] + + # ========================================================================== + # Part 4: Observability — Query CloudWatch X-Ray Traces + # ========================================================================== + print("\n" + "=" * 65) + print("Part 4: Observability — CloudWatch Traces") + print("=" * 65) + + print(" Harness invocations automatically generate X-Ray traces.") + print(" Each trace shows: model calls, tool invocations, timing details.\n") + + xray = boto3.client("xray", region_name=REGION) + + # Check Transaction Search configuration + try: + rules = xray.get_indexing_rules() + sampling = rules["IndexingRules"][0]["Rule"]["Probabilistic"]["DesiredSamplingPercentage"] + print(f" Transaction Search sampling: {sampling}%") + except Exception as e: + print(f" Transaction Search check: {e}") + print(" Enable Transaction Search for full trace visibility:") + print(" https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Transaction-Search-getting-started.html") + + # Query recent traces for our harness + print(f"\n Querying traces for harness: {harness_id[:20]}...") + try: + end_time = time.time() + start_time = end_time - 300 # Last 5 minutes + + from datetime import datetime, timezone + trace_resp = xray.get_trace_summaries( + StartTime=datetime.fromtimestamp(start_time, tz=timezone.utc), + EndTime=datetime.fromtimestamp(end_time, tz=timezone.utc), + Sampling=False, + ) + trace_count = len(trace_resp.get("TraceSummaries", [])) + print(f" Found {trace_count} trace(s) in the last 5 minutes") + + if trace_count > 0: + for i, trace in enumerate(trace_resp["TraceSummaries"][:3], 1): + duration = trace.get("Duration", 0) + has_error = trace.get("HasError", False) + status_icon = "x" if has_error else "ok" + print(f" Trace {i}: duration={duration:.2f}s status={status_icon}") + except Exception as e: + print(f" Trace query: {e}") + print(" (Traces may take 1-2 minutes to appear after invocation)") + + print("\n View traces in AWS Console:") + print(f" CloudWatch > X-Ray > Traces (region: {REGION})") + print(" Filter by: service(bedrock-agentcore)") + + # ========================================================================== + # Part 5: Evaluations — Batch Evaluation + # ========================================================================== + print("\n" + "=" * 65) + print("Part 5: Evaluations — Batch Evaluation") + print("=" * 65) + + if args.skip_evals: + print(" Skipped (--skip-evals)") + else: + print(" Waiting 60s for CloudWatch trace ingestion...") + time.sleep(60) + + # Discover the log group for this harness + logs_client = boto3.client("logs", region_name=REGION) + prefix = f"/aws/bedrock-agentcore/runtimes/harness_{harness_name}-" + log_groups = logs_client.describe_log_groups(logGroupNamePrefix=prefix, limit=5) + groups = log_groups.get("logGroups", []) + + if not groups: + print(" Could not find log group for harness — skipping evaluation") + else: + groups.sort(key=lambda g: g.get("creationTime", 0), reverse=True) + log_group = groups[0]["logGroupName"] + log_group_basename = log_group.split("/")[-1] + parts = log_group_basename.rsplit("-", 2) + service_name = f"{parts[0]}.DEFAULT" if len(parts) >= 3 else log_group_basename.replace("-DEFAULT", ".DEFAULT") + + print(f" Log group: {log_group}") + print(f" Service: {service_name}") + + batch_name = f"weather_eval_{uuid.uuid4().hex[:8]}" + evaluator_ids = [ + "Builtin.InstructionFollowing", + "Builtin.Helpfulness", + "Builtin.Correctness", + "Builtin.Faithfulness", + "Builtin.ResponseRelevance", + "Builtin.Coherence", + "Builtin.Conciseness", + "Builtin.Refusal", + ] + + print(f"\n Starting batch evaluation: {batch_name}") + try: + resp = client.start_batch_evaluation( + batchEvaluationName=batch_name, + evaluators=[{"evaluatorId": eid} for eid in evaluator_ids], + dataSourceConfig={ + "cloudWatchLogs": { + "serviceNames": [service_name], + "logGroupNames": [log_group], + "filterConfig": { + "sessionIds": [session_id], + }, + } + }, + ) + batch_id = resp["batchEvaluationId"] + print(f" Batch ID: {batch_id}") + + # Poll until complete + print(" Polling for results...") + for _ in range(30): + time.sleep(10) + result = client.get_batch_evaluation(batchEvaluationId=batch_id) + status = result.get("status", "UNKNOWN") + print(f" Status: {status}") + if status in ("COMPLETED", "COMPLETED_WITH_ERRORS", "FAILED"): + break + + if status == "COMPLETED": + eval_results = result.get("evaluationResults", {}) + summaries = eval_results.get("evaluatorSummaries", []) + print(f"\n Evaluation Results ({len(summaries)} evaluator(s)):") + print(f" {'Evaluator':<30} {'Score':<8}") + print(" " + "-" * 50) + for s in summaries: + eid = s.get("evaluatorId", "").replace("Builtin.", "") + stats = s.get("statistics", {}) + avg = stats.get("averageScore") + score_str = f"{avg:.2f}" if avg is not None else "N/A" + print(f" {eid:<30} {score_str}") + else: + print(f" Evaluation ended with status: {status}") + + except Exception as e: + print(f" Evaluation error: {e}") + + # ========================================================================== + # Summary + # ========================================================================== + print("\n" + "=" * 65) + print("Summary") + print("=" * 65) + print(f" Harness: {harness_id}") + print(f" Gateway: {gateway_id} (Exa MCP target)") + if guardrail_id: + print(f" Guardrail: {guardrail_id} (PII anonymization)") + print(f" Session: {session_id}") + print(" Turns: 4 (weather, wind, UV/sun, moon+PII test)") + print(f" Observability: CloudWatch X-Ray traces (region: {REGION})") + if not args.skip_evals: + print(" Evaluations: Built-in batch evaluators") + print() + print(" View traces: CloudWatch > X-Ray > Traces") + print(" Filter: service(bedrock-agentcore)") + +finally: + # ========================================================================== + # Part 6: Cleanup + # ========================================================================== + if not args.skip_cleanup: + print("\n" + "=" * 65) + print("Part 6: Cleanup") + print("=" * 65) + + if harness_id: + try: + control.delete_harness(harnessId=harness_id) + print(f" Deleted harness: {harness_id}") + except Exception as e: + print(f" Warning (harness): {e}") + + if gateway_id and target_id: + try: + gw_control.delete_gateway_target( + gatewayIdentifier=gateway_id, targetId=target_id + ) + print(f" Deleted target: {target_id}") + time.sleep(10) + except Exception as e: + print(f" Warning (target): {e}") + + if gateway_id: + try: + gw_control.delete_gateway(gatewayIdentifier=gateway_id) + print(f" Deleted gateway: {gateway_id}") + except Exception as e: + print(f" Warning (gateway): {e}") + + if guardrail_id: + try: + bedrock.delete_guardrail(guardrailIdentifier=guardrail_id) + print(f" Deleted guardrail: {guardrail_id}") + except Exception as e: + print(f" Warning (guardrail): {e}") + + # Delete batch evaluations created by this run + try: + evals = client.list_batch_evaluations() + for ev in evals.get("batchEvaluations", evals.get("items", [])): + ev_name = ev.get("batchEvaluationName", ev.get("name", "")) + ev_id = ev.get("batchEvaluationId", "") + if ev_name.startswith("weather_eval_"): + try: + client.delete_batch_evaluation(batchEvaluationId=ev_id) + print(f" Deleted batch evaluation: {ev_name}") + except Exception: + pass + except Exception as e: + print(f" Warning (batch evals): {e}") + + delete_harness_role() + print(" Done.") + else: + print("\n=== Skipping cleanup (--skip-cleanup) ===") + print(f" Harness ID: {harness_id}") + print(f" Gateway ID: {gateway_id}") + if guardrail_id: + print(f" Guardrail: {guardrail_id}") diff --git a/01-features/01-harness/README.md b/01-features/01-harness/README.md index bf1ac10c..d69fadc7 100644 --- a/01-features/01-harness/README.md +++ b/01-features/01-harness/README.md @@ -127,6 +127,9 @@ python 02-use-cases/01-travel-agent/travel_agent.py # Webapp visual testing python 02-use-cases/02-webapp-visual-testing/webapp_visual_testing.py + +# Weather agent (gateway + guardrails + evals + observability) +python 02-use-cases/04-weather-agent/weather_agent.py ``` Run all tests: