diff --git a/README.md b/README.md index 7dd098e..35057c6 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ While [FAST](https://github.com/awslabs/fullstack-solution-template-for-agentcor | [CopilotKit Generative UI](#copilotkit-generative-ui) | Generative UI, shared state, and human-in-the-loop interactions via CopilotKit | | [LLM Council](#llm-council) | An implementation of "Council of LLMs" pattern on AWS. Builds consensus among multiple diverse LLMs.| | [Dual Monitoring System](#dual-monitoring-system) | Dual-layer monitoring for agentic solutions using AgentCore Evaluations and AWS DevOps Agent | +| [AgentCore AWS Specialist Agent](#agentcore-aws-specialist-agent) | AWS specialist chat agent with Gateway MCP tools, long-term memory, web search, Skills on Runtime, and a NAT-free VPC | @@ -70,6 +71,17 @@ While [FAST](https://github.com/awslabs/fullstack-solution-template-for-agentcor ![Architecture Diagram](samples/dual-monitoring-system/docs/architecture-diagram/Dual-monitoring-20260407.jpg) +### [AgentCore AWS Specialist Agent](samples/aws-specialist-agent/) +**Description**: An AWS specialist chat agent showcased at the AgentCore booth at AWS Summit Japan 2026. The agent reasons about AWS, calls AWS APIs and managed tools through AgentCore Gateway, searches the web, executes code, and remembers facts across sessions. + +**Built on FAST**: v0.4.1 + +**Key Differences from FAST**: Adds selectable models (Claude and OpenAI GPT on Bedrock via a CDK model registry), AgentCore Memory long-term memory with an LTM-listing MCP server, the Amazon-managed Web Search connector, a chat-history sidebar (API Gateway + Lambda + DynamoDB), a fully closed NAT-free VPC using only VPC endpoints, AWS Skills mounted from S3 Files at `/mnt/skills`, speculative pre-warming to cut cold-start latency, and multi-MCP-server management gated per user department by Cedar ABAC. + +**Use Case**: Building production-oriented specialist agents that need fine-grained per-user tool authorization, private networking, long-term memory, and multiple MCP tool sources on AgentCore. + +![AgentCore AWS Specialist Agent UI](samples/aws-specialist-agent/docs/img/screenshot.png) + >SDK: config_id, execution_status + SDK-->>You: Config created ✓ + + loop Every sampled session + AC->>CW: Read agent runtime logs + AC->>AC: Run LLM-as-judge evaluators + AC->>CW: Write evaluation results + end + + You->>SDK: list_online_configs() + SDK->>AC: ListOnlineEvaluationConfigs + AC-->>You: configs[] +``` + +### オンライン評価設定の作成 + +特定の環境でエージェントの品質を継続的にモニタリングしたい場合に使用します。 + +> **補足:** `auto_create_execution_role=True` が IAM ロールの自動作成を発動させます。SDK は `AgentCoreEvalsSDK-{region}-{hash}` という名前のロールを作成し、エージェントの CloudWatch ログを読み取り、LLM-as-judge スコアリングのために Bedrock モデルを呼び出す権限を付与します。 + +```python +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +# agent_id はランタイム ARN の最後のセグメント +# 例: "arn:aws:bedrock-agentcore:us-east-1:123456789:runtime/my-agent-abc123" の "my-agent-abc123" +agent_id = "my-agent-abc123" # 実際のエージェント ID に置き換えてください + +response = eval_client.create_online_config( + agent_id=agent_id, + config_name="production_eval", + sampling_rate=10.0, # セッションの 10% を評価 + evaluator_list=[ + "Builtin.Helpfulness", + "Builtin.Correctness", + "Builtin.GoalSuccessRate", + ], + config_description="Production evaluation with core metrics", + auto_create_execution_role=True, # IAM ロールを自動作成 + enable_on_create=True, # 即座に評価を開始 +) + +config_id = response["onlineEvaluationConfigId"] # メトリクスを照会する際に必要なので保存しておく +print(f"Created config: {config_id}") +print(f"Status: {response['executionStatus']}") +``` + +> **タイミング:** 設定の作成は通常 10〜30 秒で完了します。エージェントが次のセッションを処理した 2〜5 分後から、評価結果が CloudWatch に表示され始めます。 + +### オンライン評価の有効化 / 無効化 + +設定を失わずに評価を一時的に停止したい場合 (例: メンテナンスウィンドウやコスト管理時) に使用します。 + +```python +# 評価を無効化 (削除せずに一時停止) +eval_client.update_online_config( + config_id="your-config-id", # 実際の config ID に置き換えてください + execution_status="DISABLED" +) + +# 評価を再度有効化 +eval_client.update_online_config( + config_id="your-config-id", + execution_status="ENABLED" +) +``` + +### サンプリングレートの更新 + +設定を再作成せずにコストとカバレッジのトレードオフを調整したい場合に使用します。 + +```python +eval_client.update_online_config( + config_id="your-config-id", + sampling_rate=5.0 # コスト削減のために 5% に減らす +) +``` + +### すべての設定の一覧表示 + +```python +configs = eval_client.list_online_configs() +for config in configs.get("onlineEvaluationConfigs", []): + print(f"{config['onlineEvaluationConfigName']}: {config['executionStatus']} " + f"(sampling: {config.get('samplingRate', 'N/A')}%)") +``` + +### 設定の削除 + +```python +eval_client.delete_online_config(config_id="your-config-id") +``` + +--- + +## オンデマンド評価 + +オンデマンド評価では、特定のセッションを即座に評価し、結果を同期的に取得できます。SDK は AgentCore Observability からセッションのトレースを自動的に取得します。 + +```mermaid +sequenceDiagram + participant You + participant SDK as Evaluation SDK + participant Obs as Observability + participant AC as AgentCore + + You->>SDK: eval_client.run(agent_id, session_id, evaluators) + SDK->>Obs: Retrieve session traces & spans + Obs-->>SDK: trace_data + SDK->>AC: Evaluate(spans, evaluator_id) + AC->>AC: Run LLM-as-judge + AC-->>SDK: score, label, explanation + SDK-->>You: Results (synchronous) +``` + +### 単一セッションの評価 + +特定の会話をスポットチェックしたり、ユーザーから報告された問題をデバッグしたり、プロンプト変更後のエージェントの動作を検証したりする際に使用します。 + +> **タイミング:** 各評価器はセッションあたり 5〜15 秒かかります。1 つのセッションに対して 3 つの評価器を実行する場合、通常 15〜45 秒で完了します。 + +```python +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +agent_id = "my-agent-abc123" # 実際のエージェント ID に置き換えてください +session_id = "session-456" # 実際のセッション ID に置き換えてください + +# 1 つ以上の評価器をセッションに対して実行 +results = eval_client.run( + agent_id=agent_id, + session_id=session_id, + evaluators=["Builtin.Helpfulness", "Builtin.Correctness"] +) + +for result in results.results: + print(f"Evaluator: {result.evaluator_name}") + print(f" Score: {result.value:.2f}") + print(f" Label: {result.label}") + print(f" Explanation: {result.explanation}") + if hasattr(result, 'token_usage') and result.token_usage: + print(f" Tokens: {result.token_usage}") + print() +``` + +### 低レベル boto3 API による評価 + +きめ細かい制御が必要な場合 (たとえば、セッション全体ではなく特定のスパンを評価する場合や、すでにスパンを自分で取得している場合) に使用します。 + +```python +import boto3 + +bedrock_agentcore = boto3.client("bedrock-agentcore") + +response = bedrock_agentcore.evaluate( + evaluatorId="Builtin.Helpfulness", + evaluationInput={ + "sessionSpans": [ + { + "traceId": "abc123", + "spanId": "def456", + "name": "agent_response", + "startTimeUnixNano": 1708128000000000000, + "endTimeUnixNano": 1708128001000000000, + "attributes": { + "session.id": "session-456" + }, + "status": {"code": "OK"}, + "scope": {"name": "bedrock-agentcore"} + } + ] + }, + evaluationTarget={ + "traceIds": ["abc123"], # オプション: 特定のトレースに範囲を限定 + "spanIds": ["def456"] # オプション: 特定のスパンに範囲を限定 + } +) + +for result in response.get("evaluationResults", []): + print(f"Score: {result['value']}, Label: {result['label']}") + print(f"Explanation: {result['explanation']}") +``` + +### バッチ評価 (複数セッション) + +プロンプト変更やデプロイ後に一連のセッションを評価し、品質の前後比較を行う場合に使用します。 + +> **タイミング:** バッチ評価は逐次実行されます。評価器ごと、セッションごとに約 10 秒かかります。10 セッションを 2 つの評価器で評価する場合、おおよそ 3〜4 分かかります。 + +```python +agent_id = "my-agent-abc123" +session_ids = ["session-001", "session-002", "session-003"] # 実際の ID に置き換えてください +evaluators = ["Builtin.Helpfulness", "Builtin.Correctness"] + +all_results = [] +for session_id in session_ids: + try: + results = eval_client.run( + agent_id=agent_id, + session_id=session_id, + evaluators=evaluators + ) + all_results.append({ + "session_id": session_id, + "results": [ + { + "evaluator": r.evaluator_name, + "score": r.value, + "label": r.label, + "explanation": r.explanation + } + for r in results.results + ] + }) + except Exception as e: + print(f"Failed to evaluate {session_id}: {e}") + +print(f"Evaluated {len(all_results)} sessions successfully") +``` + +--- + +## 組み込み評価器リファレンス + +AgentCore は 3 つの評価レベルにまたがる 15 の組み込み評価器を提供します。 + +### 評価レベルの判断ツリー + +| 目的 | 使用するレベル | 理由 | +| -------------------------- | -------------- | ------------------------------------ | +| 全体的なタスク完了度を測定 | SESSION | 会話全体をエンドツーエンドで評価 | +| 個々の応答品質を評価 | TRACE | エージェントの各ターンを独立して評価 | +| ツール使用の正しさを検証 | TOOL_CALL | 特定のツール呼び出しを評価 | + +### 品質と関連性 (TRACE レベル) + +| 評価器 ID | 測定内容 | +| ------------------------------ | ---------------------------------------------------------------- | +| `Builtin.Helpfulness` | ユーザーの観点から見た応答の有用性と価値 | +| `Builtin.Correctness` | 応答に含まれる情報が事実として正確かどうか | +| `Builtin.Faithfulness` | 応答が提供されたコンテキストに忠実で、ハルシネーションがないか | +| `Builtin.Coherence` | 応答の論理的な流れと一貫性 | +| `Builtin.Conciseness` | 応答が不必要な情報なく適切に簡潔であるか | +| `Builtin.InstructionFollowing` | 応答がユーザー入力内のすべての明示的な指示に従っているか | +| `Builtin.ContextRelevance` | 取得されたコンテキストがユーザーのクエリにどれだけ関連しているか | +| `Builtin.ResponseRelevance` | 応答が特定の質問やリクエストにどれだけ的確に対応しているか | + +### 安全性 (TRACE レベル) + +| 評価器 ID | 測定内容 | +| ----------------------- | ------------------------------------------------------------ | +| `Builtin.Harmfulness` | 応答内に潜在的に有害または安全でないコンテンツがあるかを検出 | +| `Builtin.Maliciousness` | 悪意のある意図やユーザーを操作しようとする試みを特定 | +| `Builtin.Stereotyping` | 応答内のステレオタイプや偏った表現を検出 | +| `Builtin.Refusal` | エージェントが不適切なリクエストを適切に拒否したかを追跡 | + +### ツール使用 (TOOL_CALL レベル) + +| 評価器 ID | 測定内容 | +| ------------------------------- | ---------------------------------------------------- | +| `Builtin.ToolSelectionAccuracy` | エージェントがタスクに対して正しいツールを選択したか | +| `Builtin.ToolParameterAccuracy` | エージェントが正しいパラメータでツールを使用したか | + +### セッションレベル + +| 評価器 ID | 測定内容 | +| ------------------------- | -------------------------------------------------------------- | +| `Builtin.GoalSuccessRate` | エージェントが会話内のすべてのユーザー目標を成功裏に完了したか | + +### 評価器選択ガイド + +ユースケースに基づいて有効化する評価器を決定するためのマトリックスです。 + +| ユースケース | 推奨される評価器 | +| ------------------------------ | ------------------------------------------------------------------- | +| 汎用チャットボット | `Helpfulness`、`Correctness`、`GoalSuccessRate` | +| RAG / 知識検索 | `Faithfulness`、`ContextRelevance`、`Correctness` | +| ツールを使うエージェント | `ToolSelectionAccuracy`、`ToolParameterAccuracy`、`GoalSuccessRate` | +| 安全性が重要なアプリケーション | `Harmfulness`、`Maliciousness`、`Stereotyping`、`Refusal` | +| 指示追従タスク | `InstructionFollowing`、`Coherence`、`Conciseness` | + +### プログラムから利用可能な評価器を一覧表示 + +```python +evaluators = eval_client.list_evaluators() +for evaluator in evaluators.get("evaluatorSummaries", []): + print(f"{evaluator['evaluatorId']}: {evaluator.get('evaluatorName', '')}") +``` + +--- + +## カスタム評価器 + +組み込み評価器がドメイン固有の品質基準 (たとえば財務的正確性、医療安全性、ブランドボイスへの準拠など) をカバーしていない場合に、カスタム評価器を使用します。 + +### プレースホルダー リファレンス + +各評価レベルでは、実際のトレースデータに置き換えられる固定のプレースホルダー (シングルブレース) のセットがサポートされています。 + +| レベル | プレースホルダー | 説明 | +| --------- | ------------------- | -------------------------------------------------------------------------- | +| SESSION | `{context}` | すべてのターンにわたるユーザープロンプト、アシスタント応答、ツール呼び出し | +| SESSION | `{available_tools}` | ID、パラメータ、説明を含む利用可能なツール呼び出し | +| TRACE | `{context}` | 過去のターン + 現在のターンのユーザープロンプトとツール呼び出し | +| TRACE | `{assistant_turn}` | 現在のターンのアシスタント応答 | +| TOOL_CALL | `{context}` | 過去のターン + 現在のターンのユーザープロンプトと過去のツール呼び出し | +| TOOL_CALL | `{tool_turn}` | 評価対象のツール呼び出し | +| TOOL_CALL | `{available_tools}` | ID、パラメータ、説明を含む利用可能なツール呼び出し | + +> **重要:** ダブルブレース `{{placeholder}}` ではなく、シングルブレース `{placeholder}` を使用してください。命令文には少なくとも 1 つのプレースホルダーを含める必要があります。 + +### カスタム評価器の作成 (AWS SDK) + +`create_evaluator` API は、命令文、評価スケール、モデル設定を含む `llmAsAJudge` を持つネストされた `evaluatorConfig` 構造を使用します。 + +```python +import boto3 + +control_client = boto3.client("bedrock-agentcore-control") + +response = control_client.create_evaluator( + evaluatorName="domain_accuracy", + description="Evaluates domain-specific accuracy for financial queries", + level="TRACE", + evaluatorConfig={ + "llmAsAJudge": { + "instructions": ( + "You are evaluating the domain-specific accuracy of the assistant's response " + "in financial contexts. Consider:\n" + "1. Are financial terms used correctly?\n" + "2. Are calculations accurate?\n" + "3. Are regulatory references correct?\n\n" + "Context: {context}\n" + "Candidate Response: {assistant_turn}" + ), + "ratingScale": { + "numerical": [ + { + "value": 1.0, + "label": "Very Good", + "definition": "Completely accurate, all facts and calculations correct" + }, + { + "value": 0.75, + "label": "Good", + "definition": "Mostly accurate with minor issues" + }, + { + "value": 0.5, + "label": "OK", + "definition": "Partially correct with notable errors" + }, + { + "value": 0.25, + "label": "Poor", + "definition": "Significant errors or misconceptions" + }, + { + "value": 0.0, + "label": "Very Poor", + "definition": "Completely incorrect or irrelevant" + } + ] + }, + "modelConfig": { + "bedrockEvaluatorModelConfig": { + "modelId": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "inferenceConfig": { + "maxTokens": 500, + "temperature": 1.0 + } + } + } + } + } +) + +evaluator_arn = response["evaluatorArn"] +evaluator_id = evaluator_arn.split("/")[-1] +print(f"Created custom evaluator: {evaluator_id}") +``` + +### カスタム評価器の作成 (Starter Toolkit SDK) + +```python +import json +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +# JSON ファイルから設定を読み込む (設定フォーマットは下記参照) +with open("custom_evaluator_config.json") as f: + evaluator_config = json.load(f) + +custom_evaluator = eval_client.create_evaluator( + name="domain_accuracy", + level="TRACE", + description="Evaluates domain-specific accuracy for financial queries", + config=evaluator_config +) +``` + +### カスタム評価器の Config JSON フォーマット + +Starter Toolkit SDK または AWS CLI で利用するため、以下を `custom_evaluator_config.json` として保存します。 + +```json +{ + "llmAsAJudge": { + "modelConfig": { + "bedrockEvaluatorModelConfig": { + "modelId": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "inferenceConfig": { + "maxTokens": 500, + "temperature": 1.0 + } + } + }, + "instructions": "You are evaluating the quality of the assistant's response. Context: {context}\nCandidate Response: {assistant_turn}", + "ratingScale": { + "numerical": [ + { "value": 1.0, "label": "Very Good", "definition": "Completely accurate" }, + { "value": 0.75, "label": "Good", "definition": "Mostly accurate with minor issues" }, + { "value": 0.5, "label": "OK", "definition": "Partially correct" }, + { "value": 0.25, "label": "Poor", "definition": "Significant errors" }, + { "value": 0.0, "label": "Very Poor", "definition": "Completely incorrect" } + ] + } + } +} +``` + +### カスタム評価器をオンライン設定に追加 + +評価器を作成した後、それをオンライン評価設定に追加します。 + +```python +import boto3 + +control_client = boto3.client("bedrock-agentcore-control") + +# 現在の評価器を取得 +config = control_client.get_online_evaluation_config( + onlineEvaluationConfigId="your-config-id" +) +current_evaluators = [e["evaluatorId"] for e in config.get("evaluators", [])] + +# カスタム評価器を追加 +current_evaluators.append("domain_accuracy-XXXXXXXXXX") # create_evaluator から返された ID を使用 + +control_client.update_online_evaluation_config( + onlineEvaluationConfigId="your-config-id", + evaluators=[{"evaluatorId": eid} for eid in current_evaluators] +) +``` + +カスタム評価器は、組み込み評価器と同様に、オンライン評価とオンデマンド評価の両方で使用できます。 + +> **補足:** サービスは `reason` および `score` 出力フィールドを強制する標準化プロンプトを命令文に自動的に追加します。評価器の命令文には出力フォーマットの指示を含めないでください。 + +--- + +## 評価結果とフォーマット + +### オンライン評価の結果 (CloudWatch Logs) + +オンライン評価結果は、次の場所の CloudWatch Logs に書き込まれます。 + +``` +/aws/bedrock-agentcore/evaluations/results/{config-id} +``` + +> **補足:** ロググループ名には設定名ではなく config ID (例: `a1b2c3d4-...`) が使用されます。config ID は `create_online_config()` のレスポンスまたは `list_online_configs()` から取得できます。 + +各ログイベントには、OpenTelemetry スタイルの属性を持つ JSON オブジェクトが含まれます。 + +```json +{ + "attributes": { + "gen_ai.evaluation.name": "Builtin.Helpfulness", + "gen_ai.evaluation.score.value": 0.83, + "gen_ai.evaluation.score.label": "Very Helpful", + "gen_ai.evaluation.explanation": "The response directly addresses the user's question with relevant and actionable information..." + }, + "traceId": "abc123def456", // pragma: allowlist secret (example placeholder, not a real secret) + "spanId": "789ghi", + "sessionId": "session-456", + "timestamp": "2026-02-17T00:42:42.086Z" +} +``` + +### 主要フィールド + +| フィールド | 説明 | +| ------------------------------------------ | --------------------------------------- | +| `attributes.gen_ai.evaluation.name` | 評価器 ID (例: `Builtin.Helpfulness`) | +| `attributes.gen_ai.evaluation.score.value` | 0.0 から 1.0 の数値スコア | +| `attributes.gen_ai.evaluation.score.label` | 人間が読めるラベル (例: "Very Helpful") | +| `attributes.gen_ai.evaluation.explanation` | スコアの詳細な根拠 | +| `traceId` | 評価対象のトレース | +| `spanId` | 評価対象の特定のスパン | +| `sessionId` | エージェントのセッション ID | + +### オンデマンド評価の結果 + +オンデマンド結果は、API レスポンスとして同期的に返されます。 + +```json +{ + "evaluatorId": "Builtin.Helpfulness", + "evaluatorName": "Builtin.Helpfulness", + "value": 0.83, + "label": "Very Helpful", + "explanation": "The response directly addresses...", + "tokenUsage": { + "inputTokens": 958, + "outputTokens": 211, + "totalTokens": 1169 + } +} +``` + +--- + +## 結果のダウンロードとクエリ + +### CloudWatch から結果をクエリ (Python) + +分析、エクスポート、ダッシュボード表示のために評価結果を取得する際に使用します。 + +> **CloudWatch Logs クォータ:** `FilterLogEvents` は、リージョンごとアカウントごとに 1 秒あたり 5 リクエストに制限されています。大規模な結果セットの場合は、ページネーション間に遅延を追加するか、より高速なクエリのために CloudWatch Logs Insights を使用してください。 + +```python +import boto3 +import json +from datetime import datetime, timedelta + +cloudwatch_logs = boto3.client("logs") + +config_id = "your-config-id" # create/list で取得した実際の config ID に置き換えてください +log_group = f"/aws/bedrock-agentcore/evaluations/results/{config_id}" + +end_time = datetime.utcnow() +start_time = end_time - timedelta(days=7) + +results = [] +next_token = None + +while True: + params = { + "logGroupName": log_group, + "startTime": int(start_time.timestamp() * 1000), + "endTime": int(end_time.timestamp() * 1000), + "limit": 10000, + } + if next_token: + params["nextToken"] = next_token + + response = cloudwatch_logs.filter_log_events(**params) + + for event in response.get("events", []): + try: + log_data = json.loads(event["message"]) + results.append(log_data) + except json.JSONDecodeError: + continue + + next_token = response.get("nextToken") + if not next_token: + break + +print(f"Retrieved {len(results)} evaluation results") +``` + +### 結果を CSV にエクスポート + +結果をステークホルダーと共有したり、表計算ツールにインポートしたりする際に使用します。 + +```python +import csv + +with open("evaluation_results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["session_id", "trace_id", "evaluator", "score", "label", "explanation"]) + + for result in results: + attrs = result.get("attributes", {}) + writer.writerow([ + result.get("sessionId", ""), + result.get("traceId", ""), + attrs.get("gen_ai.evaluation.name", ""), + attrs.get("gen_ai.evaluation.score.value", ""), + attrs.get("gen_ai.evaluation.score.label", ""), + attrs.get("gen_ai.evaluation.explanation", ""), + ]) +``` + +### 結果から集計メトリクスを計算 + +ダッシュボードやレポート用のサマリー統計を構築する際に使用します。 + +```python +from collections import defaultdict + +evaluator_metrics = defaultdict(lambda: {"count": 0, "total_score": 0.0}) + +for result in results: + attrs = result.get("attributes", {}) + evaluator = attrs.get("gen_ai.evaluation.name", "unknown") + score = attrs.get("gen_ai.evaluation.score.value", 0.0) + + evaluator_metrics[evaluator]["count"] += 1 + evaluator_metrics[evaluator]["total_score"] += score + +# 評価器ごとの平均を出力 +for evaluator, metrics in evaluator_metrics.items(): + avg = metrics["total_score"] / metrics["count"] if metrics["count"] > 0 else 0 + print(f"{evaluator}: avg={avg:.2f} ({metrics['count']} evaluations)") +``` + +### スコア分布 + +スコアデータの形状を理解するために使用します。スコアの大半が高い側に集中しているのか、低スコアのロングテールがあるのかを把握できます。 + +```python +def compute_score_distribution(results): + """標準的なビンに沿ってスコア分布を計算する。""" + bins = { + "0.0-0.2": 0, + "0.2-0.4": 0, + "0.4-0.6": 0, + "0.6-0.8": 0, + "0.8-1.0": 0, + } + + for result in results: + score = result.get("attributes", {}).get("gen_ai.evaluation.score.value", 0.0) + if score < 0.2: + bins["0.0-0.2"] += 1 + elif score < 0.4: + bins["0.2-0.4"] += 1 + elif score < 0.6: + bins["0.4-0.6"] += 1 + elif score < 0.8: + bins["0.6-0.8"] += 1 + else: + bins["0.8-1.0"] += 1 + + return bins + +distribution = compute_score_distribution(results) +for range_label, count in distribution.items(): + print(f" {range_label}: {count}") +``` + +--- + +## AI 駆動の分析 + +評価結果が得られたら、Amazon Bedrock の基盤モデルを使用して、低スコア評価のパターンを自動的に分析し、システムプロンプトの改善案を生成できます。これにより、フィードバックループが完成します。 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Evaluate │────▶│ Identify │────▶│ Improve │────▶│ Deploy & │ +│ Agent │ │ Patterns │ │ Prompt │ │ Re-evaluate│ +└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ + ▲ │ + └───────────────────────────────────────────────────────────┘ + Continuous improvement loop +``` + +```mermaid +sequenceDiagram + participant You + participant CW as CloudWatch Logs + participant FM as Foundation Model (Bedrock) + + You->>CW: Query low-scoring evaluation results + CW-->>You: Evaluation logs (scores, explanations) + You->>FM: Analyze patterns in low-scoring results + FM-->>You: Patterns, summary, recommendations + You->>FM: Generate improved prompt based on patterns + FM-->>You: Improved prompt + change explanations + You->>You: Review, apply, and re-evaluate +``` + +### パターン分析 + +評価結果が蓄積され、特定の評価器でなぜ低スコアになっているのかを理解したい場合に使用します。 + +> **タイミング:** 約 50 件の評価結果に対するパターン分析は、モデルと入力サイズに応じて通常 15〜30 秒かかります。 + +```python +import boto3 +import json + +bedrock_runtime = boto3.client("bedrock-runtime") + +def analyze_evaluation_patterns(low_scoring_results: list[dict]) -> dict: + """ + 低スコア評価結果を分析し、失敗パターンを特定する。 + + Args: + low_scoring_results: CloudWatch から取得した評価結果ログ + (しきい値以下のスコア、例: <= 0.5 にフィルタリング) + + Returns: + パターン、サマリー、推奨事項を含む分析結果 + """ + # モデル用に結果をフォーマット — 評価器名、スコア、説明を含める + formatted = [] + for result in low_scoring_results[:50]: # トークン超過を避けるため制限 + attrs = result.get("attributes", {}) + formatted.append({ + "session_id": attrs.get("session.id", "unknown"), + "evaluator": attrs.get("gen_ai.evaluation.name", "unknown"), + "score": attrs.get("gen_ai.evaluation.score.value", 0.0), + "label": attrs.get("gen_ai.evaluation.score.label", ""), + "explanation": attrs.get("gen_ai.evaluation.explanation", ""), + }) + + system_prompt = """You are an expert at analyzing agent evaluation data to identify +patterns and root causes of poor performance. For each pattern you identify: +1. Describe the pattern clearly +2. Count how frequently it occurs +3. List affected session IDs +4. Provide concrete evidence from the evaluation explanations + +Return JSON: {"patterns": [...], "summary": "...", "recommendations": [...]}""" + + response = bedrock_runtime.invoke_model( + modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 4096, + "system": system_prompt, + "messages": [{"role": "user", "content": f""" +Analyze these {len(formatted)} low-scoring evaluations and identify common patterns: + +{json.dumps(formatted, indent=2)} + +Focus on: which evaluators score low consistently, common issues in explanations, +and actionable patterns across sessions."""}], + "temperature": 0.7, + }), + ) + + response_body = json.loads(response["body"].read()) + analysis_text = response_body["content"][0]["text"] + + # レスポンスから JSON を解析 (markdown のフェンスがある場合は除去) + analysis_text = analysis_text.strip().strip("`").removeprefix("json").strip() + return json.loads(analysis_text) +``` + +### システムプロンプトの改善 + +パターン分析の後、特定された問題に対処するより良いシステムプロンプトを自動的に生成する際に使用します。 + +> **タイミング:** プロンプト改善の生成は、モデルが説明とともに完全な改訂プロンプトを生成するため、通常 20〜60 秒かかります。 + +```python +def generate_prompt_improvement(current_prompt: str, analysis: dict) -> dict: + """ + 失敗パターンの分析に基づいて、改善されたシステムプロンプトを生成する。 + + Args: + current_prompt: エージェントの現在のシステムプロンプト + analysis: analyze_evaluation_patterns() の出力 + + Returns: + improvedPrompt と理由付きの変更リストを含む辞書 + """ + system_prompt = """You are an expert at improving system prompts for AI agents. +Generate specific improvements based on identified failure patterns. +For each change, explain the reasoning and expected impact. + +Return JSON: {"improvedPrompt": "...", "changes": [{"section": "...", +"reasoning": "...", "impact": "..."}]}""" + + # トークン制限内に収めるため、パターンの根拠を切り詰める + analysis_summary = { + "summary": analysis.get("summary", ""), + "patterns": [ + { + "pattern": p["pattern"], + "frequency": p["frequency"], + "evidence": p["evidence"][:500], + } + for p in analysis.get("patterns", [])[:10] + ], + "recommendations": analysis.get("recommendations", [])[:10], + } + + response = bedrock_runtime.invoke_model( + modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 8192, + "system": system_prompt, + "messages": [{"role": "user", "content": f""" +Current System Prompt: +{current_prompt} + +Performance Analysis: +{json.dumps(analysis_summary, indent=2)} + +Generate an improved prompt that addresses these issues."""}], + "temperature": 0.7, + }), + ) + + response_body = json.loads(response["body"].read()) + result_text = response_body["content"][0]["text"] + result_text = result_text.strip().strip("`").removeprefix("json").strip() + return json.loads(result_text) +``` + +### エンドツーエンドのワークフロー + +これらをすべて組み合わせる — 結果のクエリから改善されたプロンプトの生成まで: + +```python +# 1. 低スコアの評価結果をクエリ (「結果のダウンロードとクエリ」を参照) +results = query_evaluation_results("your-config-id", days=30) +low_scoring = [ + r for r in results + if r.get("attributes", {}).get("gen_ai.evaluation.score.value", 1.0) <= 0.5 +] +print(f"Found {len(low_scoring)} low-scoring evaluations") + +# 2. パターン分析 (約 15〜30 秒) +analysis = analyze_evaluation_patterns(low_scoring) +print(f"Identified {len(analysis['patterns'])} patterns") +for pattern in analysis["patterns"]: + print(f" - {pattern['pattern']} (frequency: {pattern['frequency']})") + +# 3. プロンプト改善を生成 (約 20〜60 秒) +current_prompt = "You are a helpful assistant..." # エージェントの現在のプロンプト +improvement = generate_prompt_improvement(current_prompt, analysis) +print(f"\nSuggested {len(improvement['changes'])} changes:") +for change in improvement["changes"]: + print(f" Section: {change['section']}") + print(f" Reasoning: {change['reasoning']}") + print(f" Impact: {change['impact']}\n") + +# 4. 改善されたプロンプトをレビューして適用 +print("Improved prompt:") +print(improvement["improvedPrompt"]) +``` + +--- + +## セッション、トレース、スパンの取得 (Observability) + +`bedrock-agentcore-starter-toolkit` には、AgentCore からセッションデータ (トレースとスパン) を取得する Observability クライアントが含まれています。これは、セッションエクスプローラーやトレースビューアの構築、評価へのデータ投入に役立ちます。 + +### Session / Trace / Span の階層 + +``` +Session (conversation) +├── Trace 1 (user turn) +│ ├── Span: agent_planning (root) +│ ├── Span: tool_call → search_api +│ ├── Span: tool_result ← search_api +│ └── Span: agent_response +├── Trace 2 (user turn) +│ ├── Span: agent_planning (root) +│ └── Span: agent_response +└── Trace 3 (user turn) + ├── Span: agent_planning (root) + ├── Span: tool_call → database_query + ├── Span: tool_result ← database_query + └── Span: agent_response +``` + +### Observability クライアントの初期化 + +```python +from bedrock_agentcore_starter_toolkit import Observability + +agent_id = "my-agent-abc123" # 実際のエージェント ID に置き換えてください +obs_client = Observability(agent_id=agent_id, region="us-east-1") +``` + +### セッションのトレースとスパンの一覧表示 + +特定の会話で何が起こったか — どのツールが呼び出されたか、各ステップにかかった時間、エラーが発生したかどうか — を調査するために使用します。 + +```python +trace_data = obs_client.list(session_id="session-456") # 実際のセッション ID に置き換えてください + +# trace_data.traces → dict: {trace_id: [list of spans]} +# trace_data.spans → すべてのトレースにわたるすべてのスパンのフラットなリスト +# trace_data.start_time → ナノ秒単位のセッション開始時刻 + +print(f"Traces: {len(trace_data.traces)}") +print(f"Total spans: {len(trace_data.spans)}") + +for trace_id, spans in trace_data.traces.items(): + print(f"\nTrace {trace_id}: {len(spans)} spans") + for span in sorted(spans, key=lambda s: s.start_time_unix_nano or 0): + print(f" {span.span_name} ({span.duration_ms}ms) " + f"parent={span.parent_span_id or 'root'}") +``` + +### スパンのプロパティ + +SDK が返す各スパンオブジェクトには、以下の主要なプロパティがあります。 + +| プロパティ | 型 | 説明 | +| ---------------------- | ------------- | -------------------------------------------------- | +| `span_id` | `str` | 一意のスパン識別子 | +| `trace_id` | `str` | 親トレースの識別子 | +| `parent_span_id` | `str \| None` | 親スパン ID (ルートスパンの場合は `None`) | +| `span_name` | `str` | 操作の名前 (例: `"agent_response"`、`"tool_call"`) | +| `start_time_unix_nano` | `int` | エポックからのナノ秒単位の開始時刻 | +| `end_time_unix_nano` | `int` | エポックからのナノ秒単位の終了時刻 | +| `duration_ms` | `float` | ミリ秒単位の継続時間 | +| `status_code` | `str` | ステータス (`"OK"`、`"ERROR"`、`"UNSET"`) | +| `attributes` | `dict` | OpenTelemetry 属性 (モデル ID、トークン数など) | + +### トレースデータを表示用にフォーマット + +このヘルパーを使って、SDK のトレースデータを API レスポンスや UI レンダリングに適した JSON シリアライズ可能な構造に変換します。 + +```python +from datetime import datetime + +def format_session_for_display(trace_data) -> dict: + """SDK のトレースデータを JSON シリアライズ可能な構造にフォーマットする。""" + formatted_traces = [] + + for trace_id, spans in trace_data.traces.items(): + if not spans: + continue + + spans.sort(key=lambda s: s.start_time_unix_nano or 0) + + trace_start = min(s.start_time_unix_nano for s in spans if s.start_time_unix_nano) + trace_end = max(s.end_time_unix_nano for s in spans if s.end_time_unix_nano) + + formatted_traces.append({ + "traceId": trace_id, + "startTime": datetime.fromtimestamp(trace_start / 1e9).isoformat(), + "endTime": datetime.fromtimestamp(trace_end / 1e9).isoformat(), + "durationMs": (trace_end - trace_start) / 1e6, + "spans": [ + { + "spanId": s.span_id, + "traceId": s.trace_id, + "parentSpanId": s.parent_span_id, + "name": s.span_name, + "startTime": datetime.fromtimestamp(s.start_time_unix_nano / 1e9).isoformat(), + "endTime": datetime.fromtimestamp(s.end_time_unix_nano / 1e9).isoformat(), + "durationMs": s.duration_ms, + "status": s.status_code or "UNSET", + "attributes": s.attributes or {}, + } + for s in spans + ], + }) + + formatted_traces.sort(key=lambda t: t["startTime"]) + return { + "traceCount": len(formatted_traces), + "spanCount": len(trace_data.spans), + "traces": formatted_traces, + } +``` + +--- + +## ダッシュボードの構築 + +評価結果と Observability データを利用して、エージェントのパフォーマンスをモニタリングするダッシュボードを構築できます。主要なコンポーネントとデータフローの概要を以下に示します。 + +### アーキテクチャ概要 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Dashboard UI │ +│ │ +│ ┌───────────────┐ ┌──────────────┐ ┌────────────────────┐ │ +│ │ Summary Tiles │ │ Score Dist. │ │ Per-Evaluator │ │ +│ │ • Total evals │ │ Chart │ │ Metrics │ │ +│ │ • Avg score │ │ (bar chart) │ │ (color-coded) │ │ +│ │ • Low/High │ │ │ │ │ │ +│ └───────────────┘ └──────────────┘ └────────────────────┘ │ +│ │ +│ ┌──────────────────────────┐ ┌───────────────────────────┐ │ +│ │ Session Explorer │ │ On-Demand Eval Panel │ │ +│ │ • Browse sessions │ │ • Select evaluators │ │ +│ │ • Filter by score/date │ │ • Run against session │ │ +│ │ • View trace timeline │ │ • View scores/explanations│ │ +│ └──────────────────────────┘ └───────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ AI Analysis Panel │ │ +│ │ • Trigger pattern analysis on low-scoring results │ │ +│ │ • View identified patterns and recommendations │ │ +│ │ • Generate and review prompt improvements │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Backend API │ +│ │ +│ Evaluation SDK CloudWatch Logs Observability SDK │ +│ • list_online_configs • filter_log_events • obs.list() │ +│ • run() (on-demand) • (eval results) • (traces/spans) │ +│ • create/update/delete │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 主要なダッシュボードコンポーネント + +| コンポーネント | データソース | 表示する内容 | +| -------------------------- | -------------------------- | --------------------------------------------------------------------- | +| サマリータイル | 集計された CloudWatch 結果 | 総評価数、平均スコア、低スコア数 (< 0.5)、高スコア数 (≥ 0.8) | +| スコア分布 | 集計された CloudWatch 結果 | ビン (0.0〜0.2、0.2〜0.4 など) ごとのスコアを示す棒グラフ | +| 評価器ごとのメトリクス | 集計された CloudWatch 結果 | 各評価器の平均スコアと件数を色分け表示 (緑 ≥ 0.8、黄 ≥ 0.6、赤 < 0.6) | +| セッションエクスプローラー | Observability SDK | スコア、トレース数、タイムスタンプ付きの閲覧可能なセッション一覧 | +| トレースビューア | Observability SDK | トレースとスパンのタイムライン可視化 (親子階層を表示) | +| オンデマンド評価パネル | Evaluation SDK の `run()` | 評価器を選択し、セッションに対して実行、スコアと説明を表示 | +| AI 分析パネル | Bedrock の `invoke_model` | パターン分析、推奨事項、プロンプト改善案 | + +### データフロー + +```mermaid +graph TD + A[Dashboard loads] --> B[Fetch evaluation configs] + B --> C{Configs found?} + C -->|Yes| D[Fetch metrics per config from CloudWatch] + C -->|No| E[Show setup banner] + D --> F[Aggregate across configs] + F --> G[Display summary tiles + charts + evaluator breakdown] + + H[User selects session] --> I[Fetch traces via Observability SDK] + I --> J[Display trace timeline viewer] + + K[User clicks Run Evaluation] --> L[Call eval_client.run per evaluator] + L --> M[Display scores + explanations] + + N[User triggers AI Analysis] --> O[Query low-scoring results from CloudWatch] + O --> P[Send to foundation model for pattern analysis] + P --> Q[Display patterns + recommendations] + Q --> R[Generate prompt improvement] +``` + +### 設定をまたいだメトリクスの集計 + +複数の評価設定 (たとえば、評価器セットごとに分けた設定) がある場合、統合されたダッシュボードビューのためにメトリクスを集計します。 + +```python +def aggregate_metrics(all_config_metrics: list[dict]) -> dict: + """複数の評価設定のメトリクスを統合する。""" + total_evals = 0 + weighted_score_sum = 0.0 + combined_distribution = {} + combined_evaluators = {} + + for metrics in all_config_metrics: + count = metrics["totalEvaluations"] + if count == 0: + continue + + total_evals += count + weighted_score_sum += metrics["averageScore"] * count + + for bin_key, bin_count in metrics.get("scoreDistribution", {}).items(): + combined_distribution[bin_key] = combined_distribution.get(bin_key, 0) + bin_count + + for eval_id, eval_metrics in metrics.get("evaluatorMetrics", {}).items(): + if eval_id not in combined_evaluators: + combined_evaluators[eval_id] = {"count": 0, "totalScore": 0.0} + combined_evaluators[eval_id]["count"] += eval_metrics["count"] + combined_evaluators[eval_id]["totalScore"] += eval_metrics["totalScore"] + + # 平均を計算 + for eval_id in combined_evaluators: + m = combined_evaluators[eval_id] + m["averageScore"] = m["totalScore"] / m["count"] if m["count"] > 0 else 0.0 + + return { + "totalEvaluations": total_evals, + "averageScore": weighted_score_sum / total_evals if total_evals > 0 else 0.0, + "scoreDistribution": combined_distribution, + "evaluatorMetrics": combined_evaluators, + } +``` + +--- + +## ベストプラクティス + +### コスト最適化 + +#### サンプリングレート戦略 + +| 環境 | 推奨レート | 理由 | +| ------------ | ---------- | ---------------------------- | +| 開発 | 100% | テスト中の完全な可視性 | +| ステージング | 25〜50% | QA に十分なカバレッジ | +| 本番 | 5〜10% | コスト効率の良いモニタリング | + +#### 評価器選択戦略 + +少数のコア評価器から始めて、必要に応じて拡張します。 + +1. **3 つのコア評価器から始める**: `Helpfulness`、`Correctness`、`GoalSuccessRate` +2. **本番向けに安全性評価器を追加**: `Harmfulness`、`Maliciousness` +3. **必要に応じて品質評価器を追加**: `Faithfulness`、`Coherence`、`InstructionFollowing` +4. **ツールを使用する場合はツール評価器を追加**: `ToolSelectionAccuracy`、`ToolParameterAccuracy` + +各オンライン評価設定では、最大 10 個の評価器をサポートします。 + +#### トークン使用量 + +- 組み込み評価器は効率的なプロンプトを使用します (評価あたり約 1,000 トークン) +- SESSION レベル評価器はコストが高くなります (会話全体を評価) +- TRACE レベル評価器はより細粒度です (応答ごと) +- TOOL_CALL レベル評価器が最もターゲットを絞っています (ツール呼び出しごと) + +#### CloudWatch Logs のコスト + +オンライン評価結果は CloudWatch Logs に保存され、以下のコストが発生します。 + +- **取り込み:** 取り込み 1 GB あたり $0.50 +- **ストレージ:** 月あたり 1 GB で $0.03 (デフォルトの保持期間は無期限) + +コストを抑えるため、評価ロググループに保持ポリシーを設定します。 + +```python +cloudwatch_logs.put_retention_policy( + logGroupName=f"/aws/bedrock-agentcore/evaluations/results/{config_id}", + retentionInDays=90 # 結果を 90 日間保持 +) +``` + +### 運用上のヒント + +#### 一般的な操作の所要時間の目安 + +| 操作 | 一般的な所要時間 | +| ----------------------------------------- | -------------------------------------- | +| オンライン評価設定の作成 | 10〜30 秒 | +| 最初の結果が CloudWatch に表示されるまで | 次のエージェントセッションから 2〜5 分 | +| オンデマンド評価 (1 評価器、1 セッション) | 5〜15 秒 | +| オンデマンド評価 (3 評価器、1 セッション) | 15〜45 秒 | +| バッチ評価 (10 セッション × 2 評価器) | 3〜4 分 | +| AI パターン分析 (約 50 件の結果) | 15〜30 秒 | +| AI プロンプト改善生成 | 20〜60 秒 | + +#### 命名規則 + +コードベース全体で一貫した命名を使用します。 + +| 概念 | Python (SDK) | JSON (API レスポンス) | +| ------------------ | -------------- | -------------------------- | +| エージェント識別子 | `agent_id` | `agentId` | +| 設定識別子 | `config_id` | `onlineEvaluationConfigId` | +| セッション識別子 | `session_id` | `sessionId` | +| 評価器識別子 | `evaluator_id` | `evaluatorId` | + +> **補足:** SDK はパラメータに `snake_case` を使用します。AgentCore からの API レスポンスは `camelCase` を使用します。このガイドの例では、Python 変数には `snake_case`、JSON レスポンスを表示する際には `camelCase` を使用しています。 + +#### トラブルシューティング + +| 症状 | 想定される原因 | 解決策 | +| ------------------------------------------ | ----------------------------------- | --------------------------------------------------------------------- | +| メトリクスが表示されない | 結果が表示されるまで 2〜5 分かかる | 待ってから更新する。`executionStatus` が `ENABLED` であることを確認 | +| 評価 API 呼び出しで 403 が発生 | IAM 権限が不足している | ロールに `bedrock-agentcore:*` を追加 | +| ロググループで `ResourceNotFoundException` | まだ評価が実行されていない | エージェントを呼び出してセッションを生成し、待つ | +| セッション数に対して評価数が少ない | サンプリングレートが低すぎる | `sampling_rate` を増やすか、エージェントトラフィックを増やす | +| `FilterLogEvents` がスロットリングされる | CloudWatch の 5 リクエスト/秒の制限 | ページネーション間に遅延を追加するか、CloudWatch Logs Insights を使用 | + +--- + +## 参考資料 + +- [AgentCore Evaluations Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/evaluations.html) +- [CreateOnlineEvaluationConfig API](https://docs.aws.amazon.com/bedrock-agentcore-control/latest/APIReference/API_CreateOnlineEvaluationConfig.html) +- [Evaluate API](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_Evaluate.html) +- [AgentCore Samples Repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) +- [Fullstack Solution Template for AgentCore](https://github.com/awslabs/amazon-bedrock-agentcore-samples/tree/main/fullstack-solution-template-for-agentcore) +- [CloudWatch Logs Pricing](https://aws.amazon.com/cloudwatch/pricing/) +- [Bedrock Supported Regions](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html) diff --git a/samples/aws-specialist-agent/docs-jp/AGENT_CONFIGURATION.md b/samples/aws-specialist-agent/docs-jp/AGENT_CONFIGURATION.md new file mode 100644 index 0000000..f7ef5d0 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/AGENT_CONFIGURATION.md @@ -0,0 +1,184 @@ +# エージェント設定ガイド + +FAST はコンテナ内で動作する任意のエージェントフレームワークをサポートします。このガイドでは、既存のパターンを使用する方法、独自のパターンを作成する方法、エージェントの動作を設定する方法について説明します。 + +--- + +## 既存のパターン + +### Strands Single Agent パターン + +**場所**: `agent/strands-single-agent/` + +AgentCore Memory との統合を備えた、Strands フレームワークを使用した基本的な会話エージェントです。 + +**このエージェントが行うこと**: + +- マルチターンの会話チャット +- 短期メモリでの会話履歴の維持 +- **オプションの長期メモリ**: `config.yaml` で `use_long_term_memory: true` を設定すると、エージェントは `SemanticMemoryStrategy` を使用してセッションをまたいで事実を抽出して呼び出します (Cognito ユーザー ID をキーとして使用)。詳細は [Memory Integration Guide](MEMORY_INTEGRATION.md#enabling-long-term-memory) を参照してください。 +- より良い UX のためにレスポンスをストリーミング +- Cognito で認証 (ユーザー ID はメモリで追跡) + +**主要な設定ファイル**: + +- **エージェントのロジック**: `agent/strands-single-agent/basic_agent.py` - メモリ統合、モデル設定、ストリーミングロジックを含むメインのエージェント実装 +- **Python の依存関係**: `agent/strands-single-agent/requirements.txt` - 必要な Python パッケージ (Strands、bedrock-agentcore など) +- **コンテナ設定**: `agent/strands-single-agent/Dockerfile` - Docker コンテナの定義 (`deployment_type: docker` の場合のみ使用) +- **インフラストラクチャ**: `infra-cdk/lib/backend-stack.ts` - メモリリソースとランタイムデプロイメントのための CDK 設定 + +**モデル設定** (レジストリ駆動): + +チャットモデルは `basic_agent.py` にハードコードされていません。ユーザーが UI +でモデルを選び、選択肢はレジストリで一元定義されます: + +```typescript +// infra-cdk/lib/utils/model-registry.ts +export const SELECTABLE_MODELS: readonly SelectableModel[] = [ + { + key: "opus-4.8", + label: "Claude Opus 4.8", + id: "global.anthropic.claude-opus-4-8", + provider: "anthropic", + }, + { + key: "sonnet-5", + label: "Claude Sonnet 5", + id: "global.anthropic.claude-sonnet-5", + provider: "anthropic", + }, + { + key: "sonnet-4.6", + label: "Claude Sonnet 4.6", + id: "global.anthropic.claude-sonnet-4-6", + provider: "anthropic", + default: true, + }, + // ...ここに 1 行追加して再デプロイ +] +``` + +CDK がバックエンドの allowlist (`MODEL_MAP` / `DEFAULT_MODEL_KEY` 環境変数) と +フロントエンドのピッカー選択肢 (`aws-exports.json`) をこの単一ソースから導出する +ため、両者がズレることはありません。モデルの変更・追加はこの配列を編集して再 +デプロイするだけで、`basic_agent.py` の変更は不要です。論理キーから物理モデルへの +解決は `agent/strands-single-agent/models.py` が担います。Claude は bedrock-runtime +(Converse)、OpenAI GPT は bedrock-mantle の OpenAI Responses API で、両プロバイダ +とも稼働中です。どのモデルにも `temperature` は渡しません (現行世代は拒否するため)。 + +**システムプロンプト** (`agent/strands-single-agent/basic_agent.py`): + +```python +system_prompt = """You are a helpful assistant. Answer questions clearly and concisely.""" +``` + +**変更後**: 再デプロイの手順については [Deployment Guide](DEPLOYMENT.md) を参照してください。 + +--- + +## 独自のエージェントパターンを作成する + +### Step 1: パターンディレクトリを作成する + +```bash +mkdir -p agent/my-custom-agent +cd agent/my-custom-agent +``` + +### Step 2: エージェントを実装する + +以下を行うエージェントコードを作成します: + +- AgentCore Runtime からの HTTP リクエストを受信する +- ユーザークエリを処理する +- レスポンスを返す (ストリーミングまたは非ストリーミング) +- AgentCore Memory と統合する (オプション) + +**構造の例**: + +```python +from bedrock_agentcore.runtime import BedrockAgentCoreApp, RequestContext +from utils.auth import extract_user_id_from_context + +app = BedrockAgentCoreApp() + +@app.entrypoint +async def agent_handler(payload, context: RequestContext): + """エージェントのメインエントリポイント""" + user_query = payload.get("prompt") + session_id = payload.get("runtimeSessionId") + + # 検証済みの JWT トークンからユーザー ID を安全に抽出する + # (操作される可能性のある) ペイロード本体を信頼するのではなく + user_id = extract_user_id_from_context(context) + + # ここにエージェントのロジック + # ... + + yield response + +if __name__ == "__main__": + app.run() +``` + +### Step 3: Dockerfile を作成する (Docker デプロイメントのみ) + +設定で `deployment_type: docker` を使用する場合は、Dockerfile を作成します: + +```dockerfile +FROM public.ecr.aws/docker/library/python:3.13-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["python", "your_agent.py"] +``` + +**ZIP デプロイメントの場合**: Dockerfile は不要です。ZIP パッケージャーは `agent//` ディレクトリと `agent/utils/`、`gateway/`、`tools/` ディレクトリ、および `requirements.txt` の依存関係を自動的にバンドルします。 + +### Step 4: CDK 設定を更新する + +`infra-cdk/config.yaml` で: + +```yaml +backend: + pattern: "my-custom-agent" # パターンディレクトリ名 +``` + +**エージェントが追加の AWS サービスを必要とする場合** (Knowledge Bases、DynamoDB、S3 など)、`infra-cdk/lib/` の CDK スタックを変更します: + +**例**: Knowledge Base を追加する + +```typescript +// Knowledge Base コンストラクトを作成 +const knowledgeBase = new bedrock.CfnKnowledgeBase(this, "KB", { + name: "MyKnowledgeBase", + // ... 設定 +}); + +// backend-stack.ts でエージェントの環境変数に追加 +EnvironmentVariables: { + KNOWLEDGE_BASE_ID: knowledgeBase.attrKnowledgeBaseId, + // ... その他の変数 +} +``` + +### Step 5: デプロイ + +完全なデプロイ手順については [Deployment Guide](DEPLOYMENT.md) を参照してください。 + +## Design notes + +本派生プロジェクト固有の設計判断: + +- **選択可能モデル、単一の信頼ソース**: モデルピッカーは任意の ID からの推測ではなくレジストリで駆動する。レジストリは表示名・プロバイダ・推論プロファイル/エンドポイント・機能フラグを宣言し、フロントエンドとバックエンドの双方がここから読むのでピッカーと Runtime が常に同期する。 +- **OpenAI モデルを Bedrock 経由で配信**: GPT モデルもレジストリエントリとして追加し、Bedrock が公開している OpenAI Responses API エンドポイント経由で呼ぶ。認証と可観測性の経路を一本化でき、別途 OpenAI API キーを配線する必要はない。 +- **直接呼び出しによる簡素化**: OpenAI 統合は同一リージョンのエンドポイントを直叩きする。同一リージョン版エンドポイントが提供された段階で旧クロスリージョン peering 構成は撤去し、ネットワーク構成の単純化と遅延削減を達成した。 +- **モデル構築時に temperature を渡さない**: 一部の reasoning 系エンドポイントは `temperature` を拒否するため、モデル構築経路では一切設定しない。エンドポイント依存のパラメータは各モデルのレジストリフラグで管理する。 +- **System prompt をセクション分割**: Language / Skills first / AWS guidance / Tool routing といった名前付きセクションに分け、追記が適切な場所に入り複数プロバイダ間でも保守性を保てるようにしている。 diff --git a/samples/aws-specialist-agent/docs-jp/CEDAR_POLICY_GUIDE.md b/samples/aws-specialist-agent/docs-jp/CEDAR_POLICY_GUIDE.md new file mode 100644 index 0000000..fd961d1 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/CEDAR_POLICY_GUIDE.md @@ -0,0 +1,415 @@ +# Cedar Policy ガイド + +このドキュメントでは、AgentCore Gateway 向けの Cedar policy の書き方、管理方法、拡張方法について解説します。アイデンティティ伝搬アーキテクチャやコンポーネントセットアップについては [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md) を参照してください。 + +## クレームの理解(カスタム vs 標準) + +Cedar policy は `principal.getTag("claim_name")` を介して JWT クレームを参照します。これらのクレームは 2 つのカテゴリに分かれます。 + +### カスタムクレーム(アプリケーション定義) + +カスタムクレームは V3 Pre-Token Lambda が `claimsToAddOrOverride` 経由で注入します。標準的な JWT/OIDC クレームセットの一部ではなく、アプリケーションのアクセス制御のニーズに基づいて定義されます。 + +**このデモのクレーム:** + +| クレーム | 用途 | 値の例 | +| ------------ | ---------------------------------- | ------------------------ | +| `user_id` | 認証済みユーザーのアイデンティティ | `"yourname@company.com"` | +| `department` | ユーザーの組織単位 | `"finance"` | +| `role` | ユーザーの権限レベル | `"admin"` | + +**カスタムクレームの追加例:** + +| クレーム | ユースケース | Cedar での利用 | +| ----------------- | -------------------------- | ---------------------------------------------------- | +| `tenant_id` | マルチテナント分離 | `principal.getTag("tenant_id") == "example-corp"` | +| `clearance_level` | 階層化されたデータアクセス | `principal.getTag("clearance_level") == "top-level"` | +| `region` | 地理的に制限されたアクセス | `principal.getTag("region") == "us-east-1"` | +| `runtime_env` | Runtime レベルの分離 | `principal.getTag("runtime_env") == "production"` | + +カスタムクレームの追加方法: Pre-Token Lambda の `claimsToAddOrOverride` dict にクレームを注入し、Cedar 内で `principal.getTag("claim_name")` を使って参照します。Gateway の構成変更は不要 — CUSTOM_JWT authorizer がすべての JWT クレームを Cedar タグに自動マッピングします。 + +### 標準クレーム(Cognito 管理) + +標準クレームは Cognito によって自動的にすべてのトークンに含められます。Pre-Token Lambda で上書きすることはできません。 + +| クレーム | 説明 | 変更可能? | +| --------------------- | -------------------------------------------- | --------- | +| `sub` | Subject 識別子(M2M の場合は app client ID) | 不可 | +| `iss` | トークン発行者(Cognito user pool URL) | 不可 | +| `client_id` | App client ID | 不可 | +| `token_use` | 常に `"access"` | 不可 | +| `scope` | 付与された OAuth スコープ | 不可 | +| `exp` / `iat` / `jti` | トークンのタイミングと ID | 不可 | + +標準クレームも Cedar 内で `principal.getTag()` 経由でアクセス可能ですが、ビジネスロジックよりもインフラレベルのチェックに使われるのが一般的です。 + +### クレームとして利用できないもの + +| データ | 理由 | 代替手段 | +| --------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| リクエストヘッダ / IP | Cedar に公開されていない | N/A(サポートされていません) | +| Runtime ARN | Cedar スキーマに存在しない | Pre-Token Lambda 経由で `runtime_env` を注入([Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control) 参照) | +| ツール入力パラメータ | クレームではない | Cedar 内で `context.input.` を使用 | + +## Policy ファイルの場所 + +`gateway/policies/` — 1 ファイル = 1 Cedar ステートメント(AgentCore `CreatePolicy` は 1 ポリシーにつき 1 ステートメント)です。出荷されるセットは以下のとおりです。 + +| ファイル | ツール | 許可される部門 | +| ------------------------------ | -------------------------------------------------------- | -------------------- | +| `01-sample-tool.cedar` | `sample-tool-target___text_analysis_tool` | finance, engineering | +| `02-aws-mcp-read.cedar` | AWS MCP の読み取り系ツール | finance, engineering | +| `03-aws-mcp-destructive.cedar` | `aws-mcp___aws___call_aws`、`aws-mcp___aws___run_script` | finance のみ | + +該当ファイルを編集して `cdk deploy` を実行すると変更が適用されます。Custom Resource Lambda が Policy Engine を再作成することなくポリシーをインプレースで更新します。guest(グループなし)はどの `permit` にもマッチせず、deny-by-default により拒否されます。 + +## アクション名のフォーマット + +Cedar のアクション名は `___`(アンダースコア 3 つ)のフォーマットに従います。 + +- **TargetName** は `backend-stack.ts` 内の `CfnGatewayTarget` 名から取得(例: `sample-tool-target`) +- **tool_name** は `tool_spec.json` から取得(例: `text_analysis_tool`) +- 結合: `sample-tool-target___text_analysis_tool` + +これらは大文字小文字を区別します。不一致があると、ポリシーロジックが正しく見えてもすべてのリクエストが密かに拒否されます。 + +## Deny-by-Default + +Cedar は deny-by-default です: いずれの `permit` 文もリクエストにマッチしなければ、自動的に拒否されます。アクセスをブロックするのに明示的な `forbid` 文は不要 — permit の条件から該当の部門を単に外せばよいだけです。 + +例えば、ゲストを拒否するには、permit の部門リストから `"guest"` を削除します。`forbid` 文は不要です。 + +## ツールディスカバリー vs 実行 + +AgentCore Policy Engine はツールライフサイクルの **2 つの時点** で認可を強制します。 + +### 1. ディスカバリー(`tools/list`)— ツールフィルタリング + +Runtime が Gateway に対して `tools/list` を呼び出すと、Policy Engine は `PartiallyAuthorizeActions` を使って **すべてのツール** を呼び出し元のアイデンティティに対して評価します。呼び出し元が利用を許可されていないツールは **レスポンスから削除** されます。エージェントはそれらの存在に気付きません。 + +``` +Agent → Runtime → Gateway tools/list → Policy Engine (PartiallyAuthorizeActions) + ↓ + 各ツールを principal のクレームに対して評価 + ↓ + 許可されたツールのみを返す + ↓ + Agent はフィルタリングされたツールリストを受け取る +``` + +**効果:** Version 2(ゲスト拒否)がアクティブな状態で `department=guest` のユーザーが `tools/list` を呼び出すと、`text_analysis_tool` はレスポンスに含まれません。エージェントはツールの存在を知らないため、呼び出そうとすることもありません。 + +### 2. 実行(`tools/call`)— 完全コンテキスト強制 + +エージェントが特定のツールを呼び出すと、Policy Engine はツールの入力パラメータ(`context.input`)を含む **完全なコンテキスト** でリクエストを評価します。実際のリクエストペイロードにアクセスできるため、ディスカバリーよりも厳格な評価です。 + +``` +Agent → Runtime → Gateway tools/call → Policy Engine (AuthorizeAction) + ↓ + principal クレーム + context.input を評価 + ↓ + Allow → ツール実行 + Deny → 認可エラーを返す +``` + +**なぜ両方必要か?** ツールがディスカバリーフィルタリングを通過する(ユーザーは一般的にツール利用を許可されている)ものの、入力固有の条件で実行時に拒否される場合があります。例えば: + +```cedar +// ユーザーは refund ツールをディスカバリーできる(tools/list フィルタリングを通過) +// ただし amount > 1000 の場合は実行が拒否される(tools/call チェックで失敗) +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"billing-target___process_refund", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" && + context.input.amount < 1000 +}; +``` + +この例では、finance ユーザーは `tools/list` で `process_refund` を確認できます(finance 部門だから)が、$5000 の返金を処理しようとすると、`context.input.amount < 1000` が失敗するため `tools/call` は拒否されます。 + +### CloudWatch でのディスカバリーフィルタリングの確認 + +拒否されたツールが `tools/list` からフィルタリングされていることを確認するには: + +1. Runtime と Gateway の両方でトレーシングを有効化([Verifying Policy Decisions via Tracing](IDENTITY_POLICY.md#verifying-policy-decisions-via-tracing) 参照) +2. フロントエンドからクエリをトリガー +3. CloudWatch → `aws/spans` ロググループで `PartiallyAuthorizeActions` をフィルタ +4. スパンに含まれる情報: + - `aws.agentcore.policy.allowed_tools`: エージェントに返されたツール + - `aws.agentcore.policy.denied_tools`: フィルタアウトされたツール + - `aws.agentcore.gateway.policy.mode`: `ENFORCE` と表示されるはず + +> **Runtime レベルでの確認:** エージェントコードにログ行を追加して、Cedar policy フィルタリング後にエージェントが受け取ったツールを確認します。Strands エージェントパターンの例: +> +> **Strands パターン**(`agent/strands-single-agent/basic_agent.py`)— `Agent()` 作成後に追加: +> +> ```python +> agent = Agent( +> name="strands_agent", +> tools=[gateway_client, code_tools.execute_python_securely], +> ... +> ) +> specs = agent.tool_registry.get_all_tool_specs() +> logger.info(f"[GATEWAY] Raw tool specs: {specs}") +> return agent +> ``` +> +> **確認場所:** CloudWatch → Log groups → `/aws/bedrock-agentcore/runtimes/{runtime_name}` → ログストリーム `otel-rt-logs`。`[GATEWAY] Raw tool specs` で検索。 + +### サマリー + +| ステージ | API | 評価対象 | 拒否時の挙動 | +| ------------------------------ | --------------------------- | ---------------------------------------------- | --------------------------------------------------- | +| ディスカバリー(`tools/list`) | `PartiallyAuthorizeActions` | Principal クレームのみ(入力コンテキストなし) | ツールが隠される — エージェントは存在を知らない | +| 実行(`tools/call`) | `AuthorizeAction` | Principal クレーム + `context.input` | リクエスト拒否 — エージェントは認可エラーを受け取る | + +## 新しいツールの追加 + +新しい Gateway target とツールを追加する場合: + +1. `backend-stack.ts` で新しい Lambda ツールと `CfnGatewayTarget` を作成 +2. 正しいアクション名で `gateway/policies/` に新しいポリシーファイル(1 ステートメント)を追加 +3. `cdk deploy` を実行 + +各 `create_policy` 呼び出しは 1 つの Cedar 文を含む 1 つのポリシーを作成します。Custom Resource は現状デプロイごとに単一ポリシーを作成します。複数のポリシーを追加するには(例: permit と forbid 文を別々に)、文ごとに `create_policy()` を呼び出すように Custom Resource Lambda を更新してください。 + +## Cedar スキーマ制約 + +AgentCore Gateway は、Gateway の MCP ツールマニフェストから自動生成されたスキーマに対して Cedar policy を検証します。サポートされていないフィールドを参照するポリシーは作成時に失敗し、CloudFormation がロールバックします。 + +**Cedar policy でサポートされている要素:** + +| 要素 | 参照可能なもの | 例 | +| ------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------- | +| `principal` | `AgentCore::OAuthUser` でなければならない | `principal is AgentCore::OAuthUser` | +| `principal.hasTag()` / `principal.getTag()` | CUSTOM_JWT authorizer がマッピングした任意の JWT クレーム | `principal.getTag("department")` | +| `action` | `___` フォーマットのツールアクション | `AgentCore::Action::"sample-tool-target___text_analysis_tool"` | +| `resource` | Gateway ARN | `AgentCore::Gateway::"arn:aws:..."` | +| `context.input` | MCP マニフェストで定義されたツール入力パラメータ | `context.input.query` | + +**Cedar policy でサポートされていない要素:** + +| 要素 | 理由 | +| --------------------------------------- | ----------------------------------------------------------- | +| `context.runtime.arn` | スキーマに存在しない — `context.input` のみ利用可能 | +| カスタムエンティティタイプ | `AgentCore` 名前空間外でエンティティを定義できない | +| `OAuthUser` 上のカスタム属性 | プロパティ直接アクセスではなく `hasTag()`/`getTag()` を使用 | +| リクエストメタデータ(ヘッダ、IP など) | Cedar に公開されていない | + +`context.input` で利用できない情報にアクセス制御の判断が依存する場合は、Pre-Token Lambda 経由で JWT クレームとして注入し、`principal.getTag()` でアクセスしてください。このパターンの例は [Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control) を参照してください。 + +## Cedar Policy 機能 + +Cedar は認可向けに設計された専用ポリシー言語です。このセクションでは、AgentCore Gateway の Cedar policy で表現できる内容を、各機能の実用的な例と共に解説します。 + +> **このプロジェクトで既に実演されているもの:** +> +> - アイデンティティベースのアクセス(`principal.getTag("department") == "finance"`)— [Cedar Policy ファイル](IDENTITY_POLICY.md#cedar-policy-file) の Version 1 & 2 を参照 +> - 多値 OR 条件(`department == "finance" || department == "engineering"`)— Version 1 ポリシーを参照 +> +> 以下の機能は同じインフラを使って実装できる **追加のパターン** を示します。 + +### 機能 1: 入力検証 (`context.input`) + +**シナリオ:** Finance ユーザーは返金処理ができますが、$1000 までです。$1000 を超える返金には別の承認ワークフローが必要です。 + +**仕組み:** `context.input` により Cedar はツール入力パラメータ(MCP ツールマニフェストで定義されたもの)にアクセスできます。これらの値に対して条件を書けます。ツールは finance ユーザーには `tools/list` で表示されます(ディスカバリーは principal クレームのみをチェック)が、$1000 制限は実際の入力が利用可能な `tools/call` 時に強制されます。 + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"billing-target___process_refund", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" && + context.input.amount < 1000 +}; +``` + +**結果:** + +- Finance ユーザー、amount=500 → 許可 +- Finance ユーザー、amount=5000 → 拒否(制限超過) +- Engineering ユーザー、amount=100 → 拒否(部門が違う) + +**重要:** これは [ツールディスカバリー vs 実行](#tool-discovery-vs-execution) が最も重要となる例です。ツールはディスカバリーフィルタリングを通過する(finance ユーザーは一般的に許可される)ものの、入力が条件に違反する場合は実行が拒否されます。 + +--- + +### 機能 2: 複数ツールポリシー (`action in [...]`) + +**シナリオ:** 開発者はすべての読み取り専用ツール(list、get、search)を使えますが、書き込みツール(create、update、delete)は使えません。 + +**仕組み:** ツールごとに別々の `permit` 文を書く代わりに、`action in [...]` を使って 1 つのポリシーを複数のツールに同時に適用します。 + +```cedar +permit( + principal is AgentCore::OAuthUser, + action in [ + AgentCore::Action::"data-target___list_records", + AgentCore::Action::"data-target___get_record", + AgentCore::Action::"data-target___search_records" + ], + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("role") && + principal.getTag("role") == "developer" +}; +``` + +**結果:** + +- 開発者が `list_records` を呼び出す → 許可 +- 開発者が `search_records` を呼び出す → 許可 +- 開発者が `delete_record` を呼び出す → 拒否(アクションリストに含まれない) + +> **補足:** 各ツールに対して個別の `permit` 文を書くこともできます。`action in [...]` 構文は、同じ条件下でツールをグループ化するための便利な手段です。 + +--- + +### 機能 3: 明示的拒否 (`forbid`) + +**シナリオ:** すべての部門がツールを使用できますが、特定のユーザー(例: 漏洩したアカウント)は部門に関係なく明示的にブロックする必要があります。 + +**仕組み:** `forbid` 文は `permit` 文をオーバーライドします。Cedar の競合解決は「forbid wins」です — `permit` と `forbid` の両方がマッチする場合、リクエストは拒否されます。 + +```cedar +// すべての部門を許可 +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") +}; + +// ただし特定のユーザーは明示的にブロック +forbid( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_id") && + principal.getTag("user_id") == "compromised-user@example.com" +}; +``` + +**結果:** + +- 部門を持つ任意のユーザー → 許可 +- `compromised-user@example.com` → 拒否(forbid が permit に勝つ) + +> **補足:** Cedar の deny-by-default により、ユーザーや部門を `permit` から単に省略するだけで +> アクセスを拒否するのに十分なことが多いです。`forbid` は広範な `permit` を特定のケースで +> オーバーライドするとき(漏洩ユーザーのブロック、インシデント時のツール無効化、 +> 緊急停止の実装など)に使ってください。 + +--- + +### 機能 4: ワイルドカード文字列マッチング (`like`) + +**シナリオ:** `@example.com` メールドメインのユーザーのみが内部ツールにアクセスできます。他のメールドメインの外部請負業者は拒否されます。 + +**仕組み:** 文字列クレーム値のパターンマッチングには `like` と `*` ワイルドカードを使います。 + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"internal-target___internal_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_id") && + principal.getTag("user_id") like "*@example.com" +}; +``` + +**結果:** + +- `alice@example.com` → 許可 +- `bob@example.com` → 許可 +- `contractor@external.com` → 拒否(パターンに一致しない) + +> **補足:** `like` はワイルドカードとして `*` のみをサポートし、 +> 任意の種類の文字(文字、数字、記号、ドットなど)が 0 個以上にマッチします。 +> 正規表現、単一文字ワイルドカード、文字クラス、その他のパターン構文はサポートされていません。 + +--- + +### 機能 5: 環境ベースのアクセス制御 + +**シナリオ:** 本番ツールは本番 runtime からのみアクセス可能であるべきです。Staging runtime は、ユーザーが正しい部門・役割を持っていても本番ツールを呼び出せないようにします。 + +**仕組み:** Pre-Token Lambda が Cognito の `clientId` を `runtime_env` クレームにマッピングします([Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control) 参照)。Cedar はユーザーアイデンティティと runtime 環境の両方をチェックします。 + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"prod-target___production_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("runtime_env") && + principal.getTag("runtime_env") == "production" && + principal.hasTag("department") && + principal.getTag("department") == "finance" +}; +``` + +**結果:** + +- 本番 runtime からの finance ユーザー → 許可 +- Staging runtime からの finance ユーザー → 拒否(環境が違う) +- 本番 runtime からの engineering ユーザー → 拒否(部門が違う) + +--- + +### クイックリファレンス: Cedar 演算子 + +| 演算子 | 意味 | 例 | +| -------------------- | ---------------------------- | ---------------------------------------------------- | +| `==` | 等しい | `principal.getTag("role") == "admin"` | +| `!=` | 等しくない | `principal.getTag("department") != "restricted"` | +| `&&` | AND(両方とも真) | `condition_a && condition_b` | +| `\|\|` | OR(いずれかが真) | `value == "a" \|\| value == "b"` | +| `<`, `>`, `<=`, `>=` | 数値比較 | `context.input.amount < 1000` | +| `in [...]` | アクションが集合に含まれる | `action in [Action::"a", Action::"b"]` | +| `like` | ワイルドカード文字列マッチ | `principal.getTag("email") like "*@example.com"` | +| `hasTag()` | クレームがトークンに存在する | `principal.hasTag("department")` | +| `getTag()` | クレーム値を取得 | `principal.getTag("department")` | +| `has` | フィールド/属性が存在する | `context.input has shippingAddress` | +| `.contains()` | 集合のメンバーシップ | `["US", "CA", "MX"].contains(context.input.country)` | + +### Cedar でできないこと + +| 制限事項 | 回避策 | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 正規表現 | シンプルなパターンには `like` と `*` ワイルドカードを使う | +| 算術演算(例: `a + b > c`) | Pre-Token Lambda で事前計算してクレームとして注入 | +| 外部データ参照(例: データベースクエリ) | Pre-Token Lambda で解決してクレームとして注入 | +| 時刻ベースのルール(例: 「営業時間中のみ」) | Pre-Token Lambda から `time_window` クレームを注入 | +| 配列/リストメンバーシップ(例: 「ユーザーが allowed_list に含まれる」) | ハードコードされたリストには `.contains()` を使う: `["a", "b"].contains(context.input.x)`。動的なリスト(データベースから読み込み)の場合は Pre-Token Lambda で解決し、ブール値クレームとして注入 | +| リクエストヘッダ、IP アドレス、ネットワークコンテキスト | Cedar に公開されていない — 利用不可 | + +> **アーキテクチャパターン:** Cedar が直接評価できないもの(時刻、外部データ、 +> 複雑なロジック)は Pre-Token Lambda で解決し、結果をカスタムクレームとして +> 注入してください。Cedar は事前解決済みの値をチェックします。これによりポリシーは +> シンプル、決定的、監査可能に保たれます。 + +## Design notes + +本派生プロジェクト固有の設計判断: + +- **Cedar v2 + guest 既定拒否**: 未認証呼び出しはポリシーセット側で弾き、ツール呼び出しには必ず認証済み principal を要求する。アプリ層のフィルタリング後置きには依存しない。 +- **Cognito グループによるユーザー単位 ABAC**: 部門相当のグループメンバーシップを pre-token Lambda で Cedar 属性に投影し、ポリシー本体を書き換えずにユーザー単位のツール許可/拒否を実現する。解決は Cognito 側のサーバーサイドで行うため閉域構成でも動作する。 +- **Cedar policy Lambda の権限**: Cedar policy を生成する Custom Resource は target カタログを列挙・検証するために `bedrock-agentcore:ListGatewayTargets` と `bedrock-agentcore:InvokeGateway` が必要。いずれも欠落するとデプロイ時にエラーになるため、回帰の切り分けが容易になっている。 diff --git a/samples/aws-specialist-agent/docs-jp/CONTEXT_MANAGEMENT.md b/samples/aws-specialist-agent/docs-jp/CONTEXT_MANAGEMENT.md new file mode 100644 index 0000000..c3ea5d7 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/CONTEXT_MANAGEMENT.md @@ -0,0 +1,618 @@ +# コンテキスト管理ガイド + +FAST 内における長時間実行型あるいはマルチターン型のエージェント会話で、LLM のコンテキストウィンドウを管理するための実践ガイドです。 + +エージェントが長い会話を扱うにつれ(特に大量のツール呼び出し、巨大なツール結果、反復ワークフローを伴う場合)、会話履歴はモデルのコンテキストウィンドウを超えるほど膨らんでいきます。オーバーフローに到達する前であっても、巨大なコンテキストはモデル性能の劣化、レイテンシの増加、コストの肥大化を引き起こします。コンテキスト管理戦略は、会話履歴を能動的または受動的に圧縮・トリミング・要約することで、これらの問題に対処します。 + +このガイドでは Strands と LangGraph で利用可能な組み込みオプション、それぞれの使いどころ、そして組み込みオプションがユースケースに合わない場合に完全カスタムなソリューションを実装する方法について解説します。 + +--- + +## どのような場合にコンテキスト管理が必要か + +エージェントが以下に該当する場合、コンテキスト管理が必要となる可能性があります。 + +- 時間とともにメッセージが蓄積する **マルチターン会話** を実行する +- 多数の連続したツール呼び出しを伴う **反復ワークフロー** を実行する(例: コード生成ループ、データ分析パイプライン) +- **大きなツール結果** を返す(例: ファイル内容、API レスポンス、スクリーンショット) +- 人間の介在なしに **長期間自律的に動作する** + +シンプルな単発のチャットや短いマルチターンチャットエージェントでは、デフォルトの挙動(明示的なコンテキスト管理なし)で通常は十分です。特に、200k トークン以上の大きなコンテキストウィンドウを持つ最強の LLM を使う場合はそうです。 + +--- + +## 戦略の比較 + +| 戦略 | 情報損失 | 複雑性 | 適した用途 | +| ---------------------------- | --------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| **スライディングウィンドウ** | 高 — 古いメッセージは完全に破棄される | 低 | シンプルなチャットボット、短い会話、古いメッセージを参照しない UX、または古いトピックの参照を扱う長期メモリを内蔵したアプリケーション | +| **要約** | 低 — 重要情報が圧縮された形で保持される | 中 | マルチターンアシスタント、反復ワークフロー、コンテキストウィンドウがオーバーフローした際に数秒間停止しても許容される(要約生成のため)UX | +| **能動的圧縮** | 低〜中 — オーバーフロー前にトリガー | 中 | 長時間自律稼働するエージェント、コンテキストウィンドウが 50% を超えると高品質な結果を返すのが難しい弱い LLM を使うアプリケーション | +| **カスタムフックベース** | 設定可能 — 何を残すかを完全に制御 | 高 | 外部メモリを伴う特殊な長時間稼働エージェント | + +--- + +## オプション 1: スライディングウィンドウ (Strands) + +最もシンプルなアプローチです。直近 N 件のメッセージを保持し、それより古いものを破棄します。Strands は `SlidingWindowConversationManager` を標準で提供しています。 + +### 仕組み + +- 固定ウィンドウサイズでメッセージを保持(デフォルト: 40 件) +- ウィンドウを超えると最も古いメッセージから削除 +- 不正な会話状態にならないよう tool use / result のペアを維持 +- 大きなツール結果を必要に応じて切り詰める(先頭・末尾 200 文字を保持) + +### 設定 + +```python +from strands import Agent +from strands.agent.conversation_manager import SlidingWindowConversationManager + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SlidingWindowConversationManager( + window_size=40, # 保持する最大メッセージ数(デフォルト: 40) + should_truncate_results=True, # 大きなツール結果を切り詰める(デフォルト: True) + ), +) +``` + +### 能動的圧縮 + +スライディングウィンドウマネージャは能動的圧縮もサポートしており、エラーを待たずにコンテキストウィンドウがオーバーフローする前にコンテキスト削減をトリガーできます。 + +```python +conversation_manager=SlidingWindowConversationManager( + window_size=40, + proactive_compression=True, # コンテキスト使用率 70% で圧縮(デフォルト閾値) +) + +# あるいはカスタム閾値で: +conversation_manager=SlidingWindowConversationManager( + window_size=40, + proactive_compression={"compression_threshold": 0.5}, # 50% で圧縮 +) +``` + +### ターンごとの管理 + +ループ内で多くのツール操作を行うエージェント(例: 頻繁なスクリーンショットを伴う Web ブラウジング)の場合、毎モデル呼び出し前に能動的にトリミングするようターンごとの管理を有効化します。 + +```python +conversation_manager=SlidingWindowConversationManager( + window_size=40, + per_turn=True, # 毎モデル呼び出し前に適用 + # per_turn=5, # または 5 回ごとに適用 +) +``` + +### FAST Strands パターンへの適用 + +`agent/strands-single-agent/basic_agent.py` にスライディングウィンドウ管理を追加するには、`Agent` 構築時に `conversation_manager` パラメータを渡します。 + +```python +from strands.agent.conversation_manager import SlidingWindowConversationManager + +# create_strands_agent() 内: +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=[gateway_client, code_tools.execute_python_securely], + conversation_manager=SlidingWindowConversationManager(window_size=40), + session_manager=session_manager, +) +``` + +### 長所と短所 + +| 長所 | 短所 | +| ------------------------------ | -------------------------------------------- | +| 追加の LLM 呼び出しがゼロ | 古いコンテキストが完全に失われる | +| レイテンシ増加・コスト増加なし | エージェントが過去の会話を「忘れる」 | +| 設定がシンプル | 完全な履歴が必要な長時間稼働タスクには不向き | + +ドキュメント: **Strands Docs**: [SlidingWindowConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.sliding_window_conversation_manager/) + +--- + +## オプション 2: 要約型 Conversation Manager (Strands) + +古いメッセージを完全に破棄するのではなく、LLM 呼び出しを使って要約します。これによりトークン数を削減しつつ重要情報を保持できます。 + +### 仕組み + +- コンテキストオーバーフロー(または能動的圧縮閾値到達)時に、最古の N% のメッセージを要約 +- 要約は単一のユーザーメッセージとして元のメッセージ群を置き換える +- 直近メッセージはそのまま保持 +- 別途の LLM 呼び出しで要約を生成 + +### 設定 + +```python +from strands import Agent +from strands.agent.conversation_manager import SummarizingConversationManager + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, # 最古の 30% のメッセージを要約(デフォルト) + preserve_recent_messages=10, # 直近 10 件のメッセージを常に保持(デフォルト) + proactive_compression=True, # コンテキスト使用率 70% で圧縮 + ), +) +``` + +### 専用要約エージェントの利用 + +デフォルトでは要約マネージャは親エージェント(とそのツール)を使って要約を生成します。より細かく制御したい場合は、要約専用エージェントを指定できます。 + +```python +from strands import Agent +from strands.agent.conversation_manager import SummarizingConversationManager + +# 要約専用の軽量エージェント — ツールは不要 +summarizer = Agent( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + system_prompt="You are a conversation summarizer. Create concise bullet-point summaries.", +) + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, + preserve_recent_messages=10, + summarization_agent=summarizer, + proactive_compression={"compression_threshold": 0.5}, + ), +) +``` + +### FAST Strands パターンへの適用 + +```python +from strands.agent.conversation_manager import SummarizingConversationManager + +# create_strands_agent() 内: +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=[gateway_client, code_tools.execute_python_securely], + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, + preserve_recent_messages=10, + proactive_compression=True, + ), + session_manager=session_manager, +) +``` + +### 長所と短所 + +| 長所 | 短所 | +| -------------------------------- | ------------------------------------------------- | +| 古いコンテキストの重要情報を保持 | 追加の LLM 呼び出しによりレイテンシとコストが増加 | +| 圧縮率を設定可能 | 要約品質はモデル依存 | +| 組み込み済みでカスタムコード不要 | ニュアンスや具体的な詳細が失われる可能性 | + +ドキュメント: **Strands Docs**: [SummarizingConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.summarizing_conversation_manager/) + +--- + +## オプション 3: LangGraph ミドルウェア (Trim / Summarize) + +LangGraph は `@before_model` デコレータを使ったミドルウェアベースのコンテキスト管理アプローチを提供します。 + +### メッセージのトリミング + +毎モデル呼び出し前に古いメッセージを削除し、直近のものだけを残します。 + +```python +from langchain.messages import RemoveMessage +from langgraph.graph.message import REMOVE_ALL_MESSAGES +from langchain.agents import create_agent, AgentState +from langchain.agents.middleware import before_model +from langgraph.runtime import Runtime +from typing import Any + + +@before_model +def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None: + """コンテキストウィンドウに収まるよう直近の数メッセージのみ残す。""" + messages = state["messages"] + if len(messages) <= 10: + return None + + first_msg = messages[0] + recent_messages = messages[-10:] + return { + "messages": [ + RemoveMessage(id=REMOVE_ALL_MESSAGES), + first_msg, + *recent_messages, + ] + } +``` + +### メッセージの要約 + +組み込みの `SummarizationMiddleware` を使って自動要約を行います。 + +```python +from langchain.agents import create_agent +from langchain.agents.middleware import SummarizationMiddleware +from langgraph.checkpoint.memory import InMemorySaver + +agent = create_agent( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + tools=tools, + middleware=[ + SummarizationMiddleware( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + trigger=("tokens", 4000), # トークン数が 4000 を超えたらトリガー + keep=("messages", 20), # 直近 20 メッセージはそのまま保持 + ) + ], + checkpointer=InMemorySaver(), +) +``` + +### 長所と短所 + +| 長所 | 短所 | +| ---------------------------------------- | ------------------------------------------------ | +| LangGraph とのネイティブな統合 | LangGraph 固有のパターンが必要 | +| 他のミドルウェアと組み合わせ可能 | Strands とは API が異なる | +| `SummarizationMiddleware` が複雑性を吸収 | トークンカウンティングのオーバーヘッドが少々ある | + +ドキュメント: **LangGraph Docs**: [Short-term Memory & Summarization](https://docs.langchain.com/oss/python/langchain/short-term-memory) + +--- + +## オプション 4: カスタムフックベースのコンテキスト管理 (Strands) + +高度なユースケース、特に長時間自律稼働エージェントでは、組み込みマネージャでは制御が不足する場合があります。Strands フックを使えば完全カスタムなソリューションを実装できます。 + +このアプローチは以下の場合に最適です: + +- コンテキストウィンドウ使用率の特定の **パーセント** で要約をトリガー(オーバーフロー時のみではない) +- 圧縮後に **外部メモリの再注入**(例: ログファイル、ナレッジベース、構造化された状態)を行いたい +- 要約に **生の Bedrock Converse 呼び出し** を使いたい(エージェントループやツール呼び出しを回避) +- コンテキスト管理アクティビティをフロントエンドに通知するため **カスタムストリームイベント** を発火したい +- 要約失敗時の **フォールバック戦略** を実装したい + +### アーキテクチャ + +このパターンでは 2 つのコンポーネントを協調させます: + +1. **No-op の `ConversationManager`** — Strands の組み込みコンテキスト管理を完全に無効化 +2. **`HookProvider`** — `BeforeModelCallEvent` に登録され、毎 LLM 呼び出し前に能動的なコンテキスト管理を実行 + +### 実装 + +#### ステップ 1: `strands.agent.conversation_manager.ConversationManager` を継承した No-op Conversation Manager を作成 + +```python +from strands.agent.conversation_manager import ConversationManager + + +class NoOpConversationManager(ConversationManager): + """組み込みコンテキスト管理を無効化する。 + + Strands は ConversationManager を要求するが、コンテキスト削減は + フックで自前処理する。両メソッドは意図的に空とする。 + """ + + def apply_management(self, agent, **kwargs): + """No-op: コンテキスト管理はフックで行う。""" + pass + + def reduce_context(self, agent, **kwargs): + """No-op: コンテキスト管理はフックで行う。""" + pass +``` + +#### ステップ 2: コンテキストチェックフックを作成 + +```python +import logging +import os + +import boto3 +from strands.hooks import HookProvider, HookRegistry, BeforeModelCallEvent + +logger = logging.getLogger(__name__) + +# ご利用のモデルのコンテキストウィンドウサイズに合わせて設定してください +CONTEXT_WINDOW_TOKENS = 200_000 # 例: Claude Sonnet + +SUMMARIZATION_PROMPT = """You are a conversation summarizer. Provide a concise summary. + +Format Requirements: +- Create a structured summary in bullet-point format +- Do NOT respond conversationally +- Include: key decisions, tool executions and results, current state, next steps +""" + + +class ContextCheckHook(HookProvider): + """コンテキスト使用率が閾値を超えた際に能動的に会話を要約する。 + + BeforeModelCallEvent で発火する。コンテキストが threshold_pct を超えると、 + 直接 Bedrock Converse 呼び出し(エージェントループ・ツールなし)で + 古いメッセージを要約しつつ、直近のメッセージはそのまま保持する。 + + Args: + threshold_pct: 要約をトリガーするコンテキストウィンドウのパーセント。 + preserve_recent: そのまま保持する直近メッセージの数。 + model_id: 要約呼び出しに使用するモデル ID。 + """ + + def __init__( + self, + threshold_pct: float = 50.0, + preserve_recent: int = 6, + model_id: str = "us.anthropic.claude-sonnet-4-20250514-v1:0", + ): + self.threshold_pct = threshold_pct + self.preserve_recent = preserve_recent + self._model_id = model_id + self._bedrock = None + + def register_hooks(self, registry: HookRegistry, **kwargs) -> None: + """before-model-call チェックを登録する。""" + registry.add_callback(BeforeModelCallEvent, self._check) + + def _check(self, event: BeforeModelCallEvent) -> None: + """コンテキスト使用率を確認し、閾値超過時に要約をトリガーする。""" + agent = event.agent + # 後ろから走査して、usage メタデータを持つ最新の assistant メッセージを探す + for msg in reversed(agent.messages): + if msg.get("role") == "assistant": + usage = msg.get("metadata", {}).get("usage", {}) + if usage: + input_tokens = usage.get("inputTokens", 0) + cache_tokens = usage.get("cacheReadInputTokens", 0) + pct = (input_tokens + cache_tokens) / CONTEXT_WINDOW_TOKENS * 100 + if pct >= self.threshold_pct: + logger.info("Context at %.1f%% — summarizing", pct) + self._summarize_and_replace(agent) + return # 最新の assistant メッセージだけをチェック + + def _summarize_and_replace(self, agent) -> None: + """古いメッセージを要約し、要約で置き換える。""" + messages = agent.messages + if len(messages) <= self.preserve_recent: + return + + # 分割点を探す — tool_use/tool_result のペアを破壊しないように + split = len(messages) - self.preserve_recent + while split > 0 and self._is_tool_result(messages[split]): + split -= 1 + if split <= 0: + return + + to_summarize = messages[:split] + to_keep = messages[split:] + + # 要約呼び出し用にテキストのみのフォーマットへ変換 + converse_messages = self._to_text_only(to_summarize) + converse_messages.append({ + "role": "user", + "content": [{"text": "Please summarize this conversation."}], + }) + + # Bedrock Converse の直接呼び出し — エージェントループもツールもなし + if not self._bedrock: + self._bedrock = boto3.client( + "bedrock-runtime", + region_name=os.environ.get("AWS_DEFAULT_REGION", "us-east-1"), + ) + + try: + response = self._bedrock.converse( + modelId=self._model_id, + system=[{"text": SUMMARIZATION_PROMPT}], + messages=converse_messages, + inferenceConfig={"maxTokens": 4096}, + ) + summary_text = response["output"]["message"]["content"][0]["text"] + except Exception as e: + logger.error("Summarization failed: %s — falling back to truncation", e) + summary_text = "(conversation history truncated due to context limits)" + + # エージェントのメッセージを in-place で置き換える + agent.messages[:] = [ + {"role": "user", "content": [{"text": f"## Previous Conversation Summary\n\n{summary_text}"}]}, + {"role": "assistant", "content": [{"text": "Understood. I'll continue from where we left off."}]}, + ] + to_keep + + def _is_tool_result(self, msg: dict) -> bool: + """メッセージに toolResult ブロックが含まれるかチェックする。""" + content = msg.get("content", []) + if isinstance(content, list): + return any(isinstance(b, dict) and "toolResult" in b for b in content) + return False + + def _to_text_only(self, messages: list[dict]) -> list[dict]: + """メッセージをテキストのみのフォーマットに変換する(toolUse/toolResult ブロックを除去)。 + + Bedrock Converse API では、対応するツール定義をリクエストに含めずに + toolUse ブロックを送ることはできないため、この変換が必要となる。 + """ + result = [] + for msg in messages: + role = msg.get("role") + if role not in ("user", "assistant"): + continue + content = msg.get("content", []) + if isinstance(content, str): + result.append({"role": role, "content": [{"text": content}]}) + continue + text_blocks = [] + for block in content: + if isinstance(block, dict): + if "text" in block: + text_blocks.append({"text": block["text"]}) + elif "toolUse" in block: + name = block["toolUse"].get("name", "unknown") + text_blocks.append({"text": f"[Called tool: {name}]"}) + elif "toolResult" in block: + tr_content = block["toolResult"].get("content", []) + snippet = next( + (c["text"][:200] for c in tr_content if isinstance(c, dict) and "text" in c), + "result received", + ) + text_blocks.append({"text": f"[Tool result: {snippet}]"}) + if text_blocks: + result.append({"role": role, "content": text_blocks}) + + # user / assistant が交互になるよう保証する(Bedrock の要件) + cleaned = [] + for msg in result: + if cleaned and cleaned[-1]["role"] == msg["role"]: + cleaned[-1]["content"].extend(msg["content"]) + else: + cleaned.append(msg) + if cleaned and cleaned[0]["role"] == "assistant": + cleaned.insert(0, {"role": "user", "content": [{"text": "(start of conversation)"}]}) + + return cleaned +``` + +#### ステップ 3: エージェントに組み込む + +```python +from strands import Agent + +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=tools, + hooks=[ContextCheckHook(threshold_pct=50.0, preserve_recent=6)], + conversation_manager=NoOpConversationManager(), + session_manager=session_manager, +) +``` + +### 高度な使い方: 要約後の外部メモリ再注入 + +会話外に構造化された状態を持つ長時間稼働エージェント(例: 進捗ログ、結果ファイル、ナレッジベース)では、要約後にその状態を再注入することで、圧縮で失われた可能性のある重要なコンテキストを復元できます。 + +```python +def _summarize_and_replace(self, agent) -> None: + # ... (上と同じ要約処理)... + + # メッセージ置き換え後、外部状態を再注入する + self._inject_external_state(agent) + +def _inject_external_state(self, agent) -> None: + """コンテキスト圧縮後、構造化された外部状態を再注入する。""" + state_path = os.environ.get("AGENT_STATE_FILE", "") + if not state_path: + return + try: + with open(state_path) as f: + state_content = f.read() + except FileNotFoundError: + return + + agent.messages.append({ + "role": "user", + "content": [{"text": ( + "Context was compressed. Here is the current state log — " + "use it to recall what has been done and the current status:\n\n" + + state_content + )}], + }) +``` + +このパターンは以下のエージェントで特に強力です: + +- 実行したアクションと観測された結果のログを保持しているエージェント +- 圧縮を跨いで構造化データ(テーブル、設定)を保持する必要があるエージェント +- 試行履歴が重要な反復最適化ループで動作するエージェント + +### 長所と短所 + +| 長所 | 短所 | +| -------------------------------------------------------------------- | ----------------------------------------------------------- | +| トリガータイミングと圧縮挙動を完全制御 | 書く・保守するコード量が多い | +| 外部メモリの再注入を統合可能 | Bedrock API のフォーマット制約を自前で扱う必要がある | +| 能動的 — オーバーフロー前に発火し、より多くの詳細を保持 | メッセージフォーマット内部の理解が必要 | +| Bedrock 直接呼び出しでエージェントループやツール呼び出しの問題を回避 | tool_use / tool_result ペアの分割を自前で処理する必要がある | + +--- + +## 設計上の判断とベストプラクティス + +### 閾値の選び方 + +- **70%**(組み込み能動的圧縮のデフォルト)— 大半のマルチターンチャットエージェントに適しています。モデルの応答用にヘッドルームを確保できます。 +- **50%** — 急速にコンテキストを蓄積する長時間自律稼働エージェントに適しています。早めにトリガーし、要約により多くの詳細を残せます。 +- **オーバーフロー時のみ**(受動的)— 最もシンプルですが、オーバーフローエラーで最後のリクエストが破棄され情報を失うリスクがあります。 + +総じて、閾値はテストデータがあれば定量的に決定できる指標です。例えばさまざまな閾値で実行し、ハルシネーションが発生し始める閾値を見つけ、それより低い値を設定するなどです。A/B テストとユーザーフィードバックシグナルの収集でも判断できます。 + +### ツールペアの保持 + +メッセージをトリミング・分割する際、`toolUse` ブロックとそれに対応する `toolResult` を絶対に分断しないでください。これは不正な会話状態を生み、API エラーを引き起こします。分割点からは必ず後ろ向きに走査して、クリーンな境界を見つけてください。 + +### 要約のためのテキストのみへの変換 + +要約用に別途 Bedrock Converse 呼び出しを行う場合、対応するツール定義を提供せずに `toolUse` や `toolResult` ブロックを含めることはできません。最もシンプルな解決策は、これらのブロックをテキスト記述に変換することです(例: `[Called tool: analyze_data]`、`[Tool result: 42 records processed]`)。 + +### コストの考慮 + +要約呼び出しは追加の LLM 呼び出しになります: + +- Claude Sonnet で約 50K トークンのコンテキストを要約するコストは概ね $0.15〜$0.25 です +- 要約を頻繁にトリガーするエージェントでは、要約呼び出しに小型・低コストのモデルの利用を検討してください +- スライディングウィンドウ方式は追加コストはゼロですが情報を失います + +### 会話の妥当性ルール (Bedrock Converse API) + +`agent.messages` を直接操作する際は、以下を保証してください: + +1. メッセージは `user` と `assistant` のロールを交互に持つ +2. 会話は `user` メッセージで開始する +3. すべての `toolUse` ブロックには次の user メッセージに対応する `toolResult` がある +4. 空の content ブロックがない + +--- + +## クイックリファレンス: どのオプションを選ぶか + +| ユースケース | 推奨アプローチ | +| ---------------------------------------------------- | -------------------------------------------------------------- | +| シンプルなチャットボット、短い会話 | スライディングウィンドウ(オプション 1) | +| 中程度の長さのマルチターンアシスタント | 能動的圧縮を有効化した要約マネージャ(オプション 2) | +| LangGraph ベースのエージェント | LangGraph ミドルウェア(オプション 3) | +| 長時間(数時間)自律稼働するエージェント | 外部メモリ再注入を伴うカスタムフック(オプション 4) | +| 大きなツール結果(画像、ファイル)を扱うエージェント | `per_turn=True` と切り詰めを有効化したスライディングウィンドウ | + +--- + +## 参考資料 + +- [Strands ConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.conversation_manager/) +- [Strands SlidingWindowConversationManager](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.sliding_window_conversation_manager/) +- [Strands SummarizingConversationManager](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.summarizing_conversation_manager/) +- [Strands Hooks API](https://strandsagents.com/docs/api/python/strands.hooks.events/) +- [LangGraph Short-term Memory](https://docs.langchain.com/oss/python/langchain/short-term-memory) +- [LangMem Summarization Guide](https://langchain-ai.github.io/langmem/guides/summarization/) +- [FAST Memory Integration Guide](./MEMORY_INTEGRATION.md) + +## Design notes + +本派生プロジェクト固有のチャット UX 設計判断: + +- **Lambda バックの履歴サイドバー**: 過去の会話は専用 history API 経由で一覧し、各セッションは DynamoDB 上のインデックスから復元する。DDB にはインデックスだけを保存しメッセージ本体は保存しないので書き込みが安く、Memory との重複も避けられる。 +- **ストリーミングイベントのスリム化**: AgentCore Runtime のイベントサイズ上限に当たらないように、ストリーミングペイロードを最小化している。診断用データはワイヤフォーマットから外し、クライアント側で再構築する。 +- **スティッキー auto-scroll**: チャットの自動スクロールはユーザーが最下部にいるときだけ作動する。ストリーミング中に過去メッセージを読んでいる最中に途切れさせない。 +- **Cmd+Enter で送信 (IME 安全)**: 日本語などの IME 変換イベントを検出し、変換中の Enter では送信しない。Cmd/Ctrl+Enter を送信の正規バインディングにしている。 +- **OpenAI の toolUseId 使い回しを許容**: OpenAI は同一ターン内で `toolUseId` をラウンド跨ぎで再利用するが Anthropic は再利用しない。チャット UI はツール呼び出しを ID 一意の前提を持たずに追跡する。 +- **Mermaid レンダリング**: エージェント応答中の ` ```mermaid ` フェンスをクライアント側で SVG にレンダリングし、別ツール不要でアーキテクチャ説明に図を載せられるようにしている。 diff --git a/samples/aws-specialist-agent/docs-jp/DEPLOYMENT.md b/samples/aws-specialist-agent/docs-jp/DEPLOYMENT.md new file mode 100644 index 0000000..61cebcb --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/DEPLOYMENT.md @@ -0,0 +1,476 @@ +# デプロイガイド + +このガイドでは、Fullstack AgentCore Solution Template (FAST) を AWS にデプロイする手順を説明します。 + +## 前提条件 + +デプロイを行う前に、以下を準備してください。 + +- **Node.js 20+** がインストールされていること([AWS guide for installing Node.js on EC2](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html) を参照) +- **AWS CLI** が認証情報とともに構成されていること(`aws configure`)- [AWS CLI Configuration guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) を参照 +- **AWS CDK CLI** がインストールされていること: `npm install -g aws-cdk`([CDK Getting Started guide](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) を参照) +- **Python 3.11 以上**(標準ライブラリのみ - デプロイには仮想環境は不要) +- **Docker** - すべてのデプロイで必要です。[Install Docker Engine](https://docs.docker.com/engine/install/) を参照してください。`docker ps` で確認できます。代わりに Mac では [Finch](https://github.com/runfinch/finch) を使用することもできます。non-ARM マシンを使用している場合は以下を参照してください。 +- 以下を作成するための十分な権限を持つ AWS アカウント: + - S3 buckets + - CloudFront distributions + - Cognito User Pools + - Amplify Hosting projects + - Bedrock AgentCore resources + - IAM roles and policies + +### 別のアカウント/リージョンへデプロイする場合 + +コミット済みの `config.yaml` は **us-east-1** で検証済みです。同一アカウント・同一リージョン内であれば、`vpc_cidr` を他と重複しない値にし、`admin_user_email` を実在アドレスに変えるだけでそのままデプロイできます。**別アカウントや別リージョン**へデプロイする場合は、加えて以下が必要です。 + +1. **リージョンは us-east-1(現時点)。** AgentCore Web Search が us-east-1 のみ提供のため、それ以外のリージョンでは意図的にデプロイが早期失敗します。OpenAI(GPT)モデルも同リージョンの `bedrock-mantle` エンドポイント経由で到達します。別リージョンにするには、先に Web Search ターゲットと OpenAI モデルを外す必要があります。 +2. **アカウントに合った AZ をピン留めする。** AgentCore Runtime の VPC モードは特定の AZ **ID**(us-east-1: `use1-az1` / `use1-az2` / `use1-az4`)のみ対応し、AZ 名 → ID のマッピングはアカウント固有です。既定の `availability_zones: [us-east-1b, us-east-1d]` は検証アカウントでは対応 ID にマップされますが、あなたのアカウントでは異なる可能性があります。`aws ec2 describe-availability-zones --query "AvailabilityZones[].[ZoneName,ZoneId]" --output table` で正しい AZ 名を導出し、対応 ID にマップされる 2 つの名前を `backend.availability_zones` に設定してください(外れるとサブネット作成が失敗します)。 +3. **Bedrock のモデルアクセスを有効化する。** 選択可能なモデル(Claude Fable 5 / Opus / Sonnet / Haiku と OpenAI GPT)がアカウントで有効である必要があります。Claude Fable 5 はさらに、呼び出し元リージョンで Bedrock のデータ保持モードを `provider_data_share` に設定する必要があります(後述の「Claude Fable 5 の有効化」を参照)。アクセスのないモデルは `infra-cdk/lib/utils/model-registry.ts` で `available: false` にしてください。 + +## 構成 + +### 1. 構成ファイルの更新 + +`infra-cdk/config.yaml` を編集してデプロイをカスタマイズします。 + +```yaml +stack_name_base: your-project-name # 任意のスタック名に変更してください(最大 35 文字) + +admin_user_email: null # 任意: admin@example.com(ユーザーを自動作成して認証情報をメール送信) + +backend: + pattern: strands-single-agent # 利用可能なパターン: strands-single-agent, langgraph + deployment_type: docker # 利用可能なデプロイタイプ: docker (default), zip +``` + +**重要**: + +- 競合を避けるため、`stack_name_base` をプロジェクト固有の一意な名前に変更してください +- 最大長は 35 文字です(AWS AgentCore runtime の命名規則による制約) +- コミット済みの `infra-cdk/config.yaml` では `admin_user_email` がダミー値(`your-email+fastprojectadmin@example.com`)になっています。**デプロイ前に必ず自分の実在する受信可能なアドレスに変更してください** — 管理者ユーザーの一時パスワードがこのアドレスに送信されるため、ダミーのままだとサインインできません。 +- department/role の認可はメールアドレスではなく Cognito の**グループ所属**で決まります。グループ所属は CDK 管理外の運用データなので、デプロイ後に管理者ユーザーをグループに追加してください(例: `aws cognito-idp admin-add-user-to-group --user-pool-id --username --group-name finance`)。未所属のままだと "guest" 扱いで全 Gateway ツールが拒否されます。 + +### デプロイタイプ + +FAST は AgentCore Runtime に対して 2 つのデプロイタイプをサポートしています。`infra-cdk/config.yaml` で `deployment_type` を設定します。 + +| タイプ | 説明 | +| ------------------ | ------------------------------------------------------------ | +| `docker` (default) | コンテナイメージをビルドし、ECR にプッシュします | +| `zip` | Lambda 経由でコードをパッケージ化し、S3 にアップロードします | + +**補足**: どちらのデプロイタイプでも Docker は必要です。`zip` オプションは agent runtime のパッケージ化方法にのみ影響します。スタック内の他の Lambda 関数は依然として依存関係のバンドルに Docker を使用します。 + +**Docker (デフォルト) を使用するケース:** + +- PyPI で ARM64 wheel が提供されていないネイティブ C/C++ ライブラリが必要な場合 +- デプロイパッケージが 250 MB を超える場合 +- カスタムの OS レベル依存関係が必要な場合 +- 最大限の互換性を求める場合 + +**ZIP を使用するケース:** + +- 開発時の反復速度を高めたい場合 +- 依存関係が pure Python であるか、ARM64 wheel が利用可能な場合 +- セッションスループットを高めたい場合 + +**ZIP パッケージに含まれるもの**: `agent//`、`agent/utils/`、`gateway/`、`tools/` の各ディレクトリが、`requirements.txt` の依存関係とともにバンドルされます。これは Docker デプロイの Dockerfile における `COPY` コマンドに対応しています。 + +### VPC デプロイ(プライベートネットワーク) + +デフォルトでは、AgentCore Runtime はインターネットアクセス可能な PUBLIC ネットワークモードで動作します。プライベートなネットワーク分離のために既存の VPC へ runtime をデプロイするには、`infra-cdk/config.yaml` で `network_mode: VPC` を設定し、VPC の詳細を指定します。 + +#### VPC の内側/外側で動作するもの + +VPC モードを有効にすると、**AgentCore Runtime**(エージェントコード)は VPC のプライベートサブネット内で動作します。エージェントが行うすべてのネットワーク呼び出しは VPC のネットワーキングルールに従い、AWS サービスには VPC エンドポイント経由で到達します。エージェントが直接インターネット呼び出しを行うことはありません。 + +以下のコンポーネントは VPC の **外側** にある AWS マネージドインフラストラクチャで動作します。 + +- **Gateway tool Lambdas** — エージェントは `bedrock-agentcore.gateway` VPC エンドポイント経由で Gateway を呼び出します(プライベートネットワーキング)。Gateway はその後、AWS マネージドインフラストラクチャ上で Lambda 関数を呼び出します。エージェントのネットワーク呼び出し自体はプライベートに保たれ、Lambda の実行のみ VPC の外で行われます。 +- **Code Interpreter** — エージェントは `bedrock-agentcore` VPC エンドポイント経由で Code Interpreter API を呼び出します。サンドボックス実行は Bedrock のマネージド環境で行われます。 +- **Bedrock model invocations** — モデル呼び出しは `bedrock-runtime` VPC エンドポイント経由で Bedrock のマネージドインフラストラクチャに到達します。 +- **Frontend (Amplify/CloudFront)** — 完全に分離されており、パブリック向けで、VPC デプロイの一部ではありません。 + +要するに、エージェントの送信ネットワークトラフィックは VPC エンドポイント経由でプライベートな AWS ネットワーキング上に留まります。エージェントが呼び出すサービス(Bedrock、Gateway、Code Interpreter)は VPC 外のインフラストラクチャ上で実行される場合がありますが、エージェントからそれらのサービス API へのネットワーク経路はプライベートです。 + +#### 構成 + +```yaml +backend: + pattern: strands-single-agent + deployment_type: docker + network_mode: VPC + vpc: + vpc_id: vpc-0abc1234def56789a + subnet_ids: + - subnet-aaaa1111bbbb2222c + - subnet-cccc3333dddd4444e + security_group_ids: # 任意 - 省略時はデフォルトの SG が作成されます + - sg-0abc1234def56789a +``` + +`vpc_id` および `subnet_ids` フィールドは必須です。`security_group_ids` フィールドは任意で、省略した場合は CDK construct が runtime 用のデフォルトセキュリティグループを作成します。 + +#### 必要な VPC エンドポイント + +VPC モードでデプロイすると、runtime はインターネットアクセスのないプライベートサブネット内で動作します。エージェントが依存する AWS サービスに到達できるよう、VPC には以下の VPC エンドポイントを構成しておく必要があります。 + +| Endpoint | Service | Type | +| -------------------------------------------------- | -------------------------------- | --------- | +| `com.amazonaws.{region}.bedrock-runtime` | Bedrock model invocation | Interface | +| `com.amazonaws.{region}.bedrock-agentcore` | AgentCore Identity (Token Vault) | Interface | +| `com.amazonaws.{region}.bedrock-agentcore.gateway` | AgentCore Gateway (MCP tools) | Interface | +| `com.amazonaws.{region}.ssm` | SSM Parameter Store | Interface | +| `com.amazonaws.{region}.secretsmanager` | Secrets Manager | Interface | +| `com.amazonaws.{region}.logs` | CloudWatch Logs | Interface | +| `com.amazonaws.{region}.ecr.api` | ECR API (Docker deployment) | Interface | +| `com.amazonaws.{region}.ecr.dkr` | ECR Docker (Docker deployment) | Interface | +| `com.amazonaws.{region}.s3` | S3 (ZIP deployment, ECR layers) | Gateway | +| `com.amazonaws.{region}.dynamodb` | DynamoDB (feedback table) | Gateway | +| `com.amazonaws.{region}.xray` | X-Ray (OTel trace export) | Interface | +| `com.amazonaws.{region}.bedrock-mantle` | OpenAI GPT-5.x (Responses API) | Interface | + +`{region}` をデプロイリージョン(例: `us-east-1`)に置き換えてください。 + +すべての interface endpoint は private DNS を有効化し、AgentCore Runtime からのトラフィックを許可するサブネットおよびセキュリティグループに関連付ける必要があります。 + +#### サブネット要件 + +- CDK が管理する VPC は **完全に隔離されたプライベートサブネット**(`PRIVATE_ISOLATED`)を使用します。`0.0.0.0/0` ルートは一切ありません +- 高可用性のため、AgentCore がサポートする AZ(少なくとも 2 つ)にピン留めします +- runtime ENI のために十分な利用可能 IP アドレスを持つサブネットを使用してください + +#### NAT Gateway なし(完全閉域) + +デフォルトのデプロイには **NAT Gateway がなく**、アウトバウンドのインターネットアクセスもありません。すべての依存先に VPC エンドポイント経由で到達できるため、これで動作します。 + +- Gateway 用 M2M トークンは AgentCore Identity(Token Vault)経由で取得します。Cognito トークン交換は AWS 内のサーバーサイドで実行され、`bedrock-agentcore` VPC エンドポイント経由で到達可能なため、Runtime はパブリックな Cognito ホストドメインを呼び出しません。 +- ユーザーアイデンティティは `aws_client_metadata` で M2M トークンに伝播されます(追加の egress 不要)。 +- S3 Files(skills)は **VPC 内の** マウントターゲット ENI に NFS(ポート 2049)でマウントします。AgentCore の VPC ドキュメントによれば、マウントに必要なのは runtime ENI とマウントターゲット間の TCP 2049 接続のみ(self-reference のセキュリティグループで許可)で、専用の VPC エンドポイントも NAT も不要です(TLS と IAM 認証は自動で処理されます)。 + +> **補足:** アウトバウンドの _パブリックインターネット_ 呼び出しを行うカスタムツール(または Browser ツール)を追加する場合は、NAT Gateway を再導入する必要があります。AWS サービスであれば、対応する VPC エンドポイントを追加することで到達できます。 + +#### セキュリティグループの構成 + +CDK スタックは AgentCore Runtime 用のセキュリティグループを自動作成します。同じセキュリティグループは通常 VPC エンドポイントにも適用されます。runtime からエンドポイントへ到達できるように、自己参照のインバウンドルールを追加する必要があります。 + +- Protocol: TCP, Port: 443, Source: セキュリティグループ自身 + +### OpenAI モデル(GPT-5.x / bedrock-mantle 経由) + +ピッカーに表示される OpenAI モデル(GPT-5.4 / GPT-5.5)は `bedrock-mantle` エンドポイント(OpenAI Responses API)経由で提供され、2026-06 から us-east-1 でも利用可能になりました。CDK 管理の VPC は同リージョンの `bedrock-mantle` interface endpoint を常設します(上のエンドポイント表を参照)。追加スタック・フラグ・クロスリージョンネットワークは不要で、素の `cdk deploy` だけで GPT モデルも使えます(旧 `OPENAI_MANTLE` の us-east-2 ピアリングスタックは撤去済み)。 + +### 複数環境のデプロイ(同一アカウント・同一リージョン) + +第 2 の環境(本番と並存する開発スタックなど)にコード変更は不要です。config ファイルを別途作成し、`CONFIG_FILE` 環境変数で選択します。 + +```yaml +# infra-cdk/config.dev.yaml(gitignore 対象 — 実在の管理者メールを含むため) +stack_name_base: FAST-dev # 必ず変える: CloudFormation エクスポートや Cognito ドメインがここから導出される +admin_user_email: you@example.com +backend: + pattern: strands-single-agent + deployment_type: docker + network_mode: VPC + vpc_management: CDK + vpc_cidr: 10.30.0.0/16 # 他環境と重複させない(本番は 10.20.0.0/16) + use_long_term_memory: true + skills: + enabled: true + mount_path: /mnt/skills +``` + +```bash +cd infra-cdk +# 全機能(VPC + skills + LTM + OpenAI モデル)を一括デプロイ: +CONFIG_FILE=config.dev.yaml npx cdk deploy --all --require-approval never +cd .. +python scripts/deploy-frontend.py FAST-dev # 指定したスタックの Outputs からフロントを構成 +``` + +並存のルール: + +- `stack_name_base` は環境ごとに一意にすること — すべての名前付きリソースと CloudFormation エクスポートがここから導出されます。 +- `vpc_cidr` は他環境と重複させないこと: ネットワークの区別が運用上明確に保たれます(将来ピアリングが必要になった場合にも張れる状態を保てます)。 +- `CONFIG_FILE` なしの素の `cdk deploy` は常に本番の `config.yaml` を対象とします — 切り替えは環境変数のみなので、「ファイルの戻し忘れ」が構造的に発生しません。 +- **別の AWS アカウント**にデプロイする場合は `backend.availability_zones` も設定してください: AgentCore Runtime の VPC モードはリージョンごとに特定の AZ _ID_(us-east-1 では use1-az1/az2/az4)しかサポートせず、AZ 名 → ID のマッピングはアカウント固有です。`aws ec2 describe-availability-zones` で正しい AZ 名を導出してください。 + +## デプロイ手順 + +### TL;DR 版 + +バックエンドおよびフロントエンドをデプロイするコマンドは以下のとおりです。 + +```bash +cd infra-cdk +npm install +cdk bootstrap # 一度だけ実行 +cdk deploy +cd .. +python scripts/deploy-frontend.py +``` + +### ローカルツールなしでデプロイ(CodeBuild 経由) + +ローカルに Node.js、Docker、または CDK がインストールされていない場合でも、一時的な CodeBuild プロジェクトを利用して完全にクラウド上でデプロイできます。必要なのは Python 3.8+ と AWS CLI のみです。 + +```bash +python scripts/deploy-with-codebuild.py +``` + +詳細および必要な IAM 権限については `scripts/README.md` を参照してください。 + +### 1. 依存関係のインストール + +インフラストラクチャの依存関係をインストールします。 + +```bash +cd infra-cdk +npm install +``` + +**補足**: フロントエンドの依存関係はデプロイ時に Docker bundling 経由で自動インストールされるため、別途フロントエンドの `npm install` を実行する必要はありません。 + +### 2. CDK のブートストラップ(初回のみ) + +この AWS アカウント/リージョンで初めて CDK を使用する場合は以下を実行します。 + +```bash +cdk bootstrap +``` + +### 3. CDK でバックエンドをデプロイ + +スタック全体をビルドおよびデプロイします。 + +```bash +cdk deploy +``` + +デプロイでは以下が実行されます。 + +1. 認証用の Cognito User Pool を作成 +1. agent コンテナをビルドして ECR にプッシュ +1. AgentCore runtime を作成 +1. フロントエンド用の CloudFront ディストリビューションを構成 + +**補足**: コンテナビルドおよび AgentCore のセットアップにより、デプロイには約 5〜10 分かかります。 + +**デプロイ前にスキル関連スクリプトを手動実行する必要はありません。** `skills/agent-toolkit-for-aws/` 配下のベンダリング済み AWS スキルはリポジトリにコミットされており、`fast-project-guide` スキルは synth 時に `cdk` が自動再生成します(skills-storage スタックの bundling 経由で `scripts/build-project-guide.py` を実行)。`scripts/vendor-skills.py` は上流の新しいコミットからベンダリング済みスキルを更新したいときだけ実行するメンテ用スクリプトで、デプロイ手順ではありません。`cdk deploy` だけで完結します。 + +### 4. フロントエンドをデプロイ + +```bash +# ルートディレクトリから実行 +python scripts/deploy-frontend.py +``` + +このスクリプトは自動的に以下を行います。 + +- CDK スタックの出力から最新の `aws-exports.json` を生成(`aws-exports.json` の詳細は後述) +- 必要に応じて npm 依存関係をインストール/更新 +- フロントエンドをビルド +- AWS Amplify Hosting にデプロイ + +スクリプトの出力にアプリケーションの URL が表示されます。次のような形式になります。 + +``` +ℹ App URL: https://main.d123abc456def7.amplifyapp.com +``` + +### 5. Cognito ユーザーの作成(必要な場合) + +**`admin_user_email` を config に指定した場合:** + +- 仮パスワードがメールで届きます +- サインインして初回ログイン時にパスワードを変更します + +**メールを指定しなかった場合:** + +1. [AWS Cognito Console](https://console.aws.amazon.com/cognito/) を開きます +2. 該当する User Pool(名前は `{stack_name_base}-user-pool`)を見つけます +3. User Pool をクリックします +4. "Users" タブに移動します +5. "Create user" をクリックします +6. ユーザー詳細を入力します: + - **Email**: メールアドレス + - **Temporary password**: 仮パスワードを作成 + - **Mark email as verified**: このボックスにチェック +7. "Create user" をクリックします + +### 6. アプリケーションへのアクセス + +1. Amplify Hosting の URL をブラウザで開きます +1. 作成した Cognito ユーザーでサインインします +1. 初回ログイン時に仮パスワードの変更を求められます + +## デプロイ後 + +### アプリケーションの更新 + +フロントエンドコードを更新する場合: + +```bash +# ルートディレクトリから実行 +python scripts/deploy-frontend.py +``` + +バックエンドエージェントを更新する場合: + +**Docker デプロイ:** + +```bash +cd infra-cdk +cdk deploy --all +``` + +### モニタリングとログ + +- **フロントエンドログ**: CloudFront アクセスログを確認 +- **バックエンドログ**: AgentCore runtime の CloudWatch logs を確認 +- **ビルドログ**: コンテナビルドの CodeBuild プロジェクトログを確認 + +## クリーンアップ + +すべてのリソースを削除するには以下を実行します。 + +```bash +cd infra-cdk +cdk destroy --force +``` + +**警告**: これによりデプロイ中に作成された S3 buckets および ECR images を含むすべてのデータが削除されます。 + +## トラブルシューティング + +### よくある問題 + +1. **`cdk deploy` が Docker エラーで失敗する** + - Docker がインストールされ、デーモンが動作していることを確認: `docker ps` + - Mac では Docker Desktop を開くか Finch を起動: `finch vm start` + - Linux では: `sudo systemctl start docker` + +2. **Docker ビルド中に "Architecture incompatible" や "exec format error" が出る** + - クロスプラットフォームビルドの設定なしに non-ARM マシンからデプロイした場合に発生します + - 前提条件セクションの "Docker Cross-Platform Build Setup" の手順に従ってください + - QEMU エミュレーションをインストール済みであることを確認: `docker run --privileged --rm tonistiigi/binfmt --install all` + - ARM64 サポートを確認: `docker buildx ls` でプラットフォームに `linux/arm64` が表示されるはずです + +3. **"Agent Runtime ARN not configured"** + - バックエンドスタックが正常にデプロイされたことを確認してください + - SSM パラメータが正しく作成されているか確認してください + +4. **認証エラー** + - Cognito ユーザーを作成したことを確認 + - ユーザーのメールアドレスが verified になっていることを確認 + +5. **ビルド失敗** + - AWS Console で CodeBuild ログを確認 + - `agent/` 内の agent コードが正しいことを確認 + +6. **権限エラー** + - AWS 認証情報に十分な権限があることを確認 + - スタックが作成した IAM ロールを確認 + +### サポートを得る + +- 詳細なエラーメッセージは CloudWatch logs を確認してください +- CDK デプロイ出力に警告がないか確認してください +- すべての前提条件が満たされていることを確認してください + +## セキュリティ上の考慮事項 + +- Cognito User Pool は強力なパスワードポリシーで構成されています +- すべての通信は CloudFront 経由で HTTPS を使用します +- AgentCore runtime は JWT 認証を使用します +- IAM ロールは最小権限の原則に従います + +本番環境のデプロイでは、以下の検討をお勧めします。 + +- Cognito ユーザーで MFA を有効にする +- 独自証明書を用いたカスタムドメインを設定する +- 追加のモニタリングとアラートを構成する +- 永続データに対するバックアップ戦略を実装する + +## Docker クロスプラットフォームビルド設定(non-ARM マシンで必要) + +**重要**: BedrockAgentCore Runtime は ARM64 アーキテクチャのみをサポートしています。non-ARM マシン(x86_64/amd64)からデプロイする場合は、Docker のクロスプラットフォームビルド機能を有効にする必要があります。 + +マシンのアーキテクチャを確認します。 + +```bash +uname -m +``` + +出力が `x86_64`(`aarch64` や `arm64` ではない)の場合は、以下のコマンドを実行してください。 + +1. **ARM64 エミュレーション用に QEMU をインストール:** + + ```bash + docker run --privileged --rm tonistiigi/binfmt --install all + ``` + +2. **Docker buildx を有効化し、マルチプラットフォームビルダーを作成:** + + ```bash + docker buildx create --use --name multiarch --driver docker-container + docker buildx inspect --bootstrap + ``` + +3. **ARM64 サポートが利用可能であることを確認:** + ```bash + docker buildx ls + ``` + プラットフォーム一覧に `linux/arm64` が表示されるはずです。 + +**補足**: この設定はマシンごとに 1 度だけ必要です。CDK デプロイは ARM64 コンテナをビルドするためにこれらの機能を自動的に使用します。 + +## aws-exports.json の理解 + +`aws-exports.json` ファイルは、React フロントエンドが AWS Cognito と通信して認証を行うための重要な構成ファイルです。このファイルはフロントエンドデプロイ時に自動生成され、Cognito 認証に必要な構成パラメータを含みます。 + +**aws-exports.json とは何か?** + +`aws-exports.json` ファイルは、React アプリケーションが Cognito Authentication を適切に構成するために読み込む認証構成を含みます。デプロイスクリプトによって自動的に作成され、`frontend/public/aws-exports.json` に配置されます。 + +**なぜ必要か?** + +この構成ファイルは以下の理由で不可欠です。 + +- React アプリケーションに正しい Cognito User Pool ID および Client ID を提供する +- 認証エンドポイントとリダイレクト URI を指定する +- 認証フローのパラメータを構成する +- このファイルがないと Cognito 認証は機能しない + +**どのように作成されるか?** + +このファイルは `deploy-frontend.py` によって自動生成され、以下の処理を行います。 + +1. デプロイ済み CDK スタックの出力から構成を抽出 +2. CloudFormation スタック ARN から AWS リージョンを自動検出 +3. 必要な値(`CognitoClientId`、`CognitoUserPoolId`、`AmplifyUrl`)を取得 +4. 以下の構造で構成ファイルを生成: + +```json +{ + "authority": "https://cognito-idp.region.amazonaws.com/user-pool-id", + "client_id": "your-client-id", + "redirect_uri": "https://your-amplify-url", + "post_logout_redirect_uri": "https://your-amplify-url", + "response_type": "code", + "scope": "email openid profile", + "automaticSilentRenew": true +} +``` + +**重要**: このファイルはデプロイのたびに再生成されるため、手動で編集しないでください。認証が機能しない場合は、フロントエンドを再デプロイして最新の構成を取得してください。 + +## Design notes + +本派生プロジェクトに固有の設計判断 (upstream にない補足): + +- **完全閉域 (NAT なし)**: AWS API への外向きトラフィックは全て interface VPC endpoint 経由。NAT gateway は意図的に未配置で、NAT の固定費を削減しつつ egress を明示的に許可した endpoint だけに制限する。 +- **AgentCore Gateway VPC endpoint は必須**: Runtime が VPC 内から Gateway を呼ぶ場合、`bedrock-agentcore` interface endpoint がないとツール呼び出しがネットワーク層で失敗する。 +- **endpoint 集合はキュレーション**: 各 interface endpoint は VPC 内からの実呼び出しコードに紐付けて配置している。使用しなくなった endpoint (例: 直接 Bedrock Agent 呼び出しを廃止した後の `bedrock-agent-runtime`) は削除しコストと攻撃面を抑える。 +- **VPC CIDR と AZ をパラメータ化**: `config.yaml` で `vpc_cidr` と AZ リストを公開し、同一アカウントに複数環境を共存させたり、必要なサービス非対応の AZ を回避したりできる。 +- **Cold-start 緩和**: AgentCore Runtime のコンテナ寿命を長く取り、フロントエンドから pre-warm ping を送る構成にしている。別途 warmer Lambda を運用せずに初トークン遅延を抑える狙い。 diff --git a/samples/aws-specialist-agent/docs-jp/GATEWAY.md b/samples/aws-specialist-agent/docs-jp/GATEWAY.md new file mode 100644 index 0000000..b28bc73 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/GATEWAY.md @@ -0,0 +1,485 @@ +# AgentCore Gateway 実装 + +このドキュメントでは、FAST がスケーラブルで本番運用に耐えるツール実行アーキテクチャを提供するために、AgentCore Gateway と Lambda targets をどのように実装しているかを説明します。 + +## 概要 + +FAST は **AgentCore Gateway with Lambda Targets** を使用して、エージェントが外部のツールやサービスにアクセスできるようにしています。このアーキテクチャは、エージェントロジックとツール実装の明確な分離を提供し、個々のツールを独立してスケーリングおよびデプロイできるようにします。 + +### エージェントが利用できるもの + +デプロイされたエージェントは 3 つのケイパビリティソースを持ちます。 + +1. **Lambda ターゲット(`sample-tool-target`)** — デモ用の `text_analysis_tool`。詳細は後述。 +2. **AWS MCP Server ターゲット(`aws-mcp`)** — マネージドな Agent Toolkit for AWS の MCP Server(`https://aws-mcp..api.aws/mcp`)を MCP gateway ターゲットとして登録したもの。`aws___*` ツール(AWS の知識に加え `aws___call_aws` / `aws___run_script`)を公開します。Gateway はサービス名 `aws-mcp` にスコープした SigV4 でリクエストに署名します。 +3. **S3 Files 経由の Skills** — ベンダリングした AWS skills を S3 に同期し、S3 Files で Runtime の `/mnt/skills` にマウントして、Strands `AgentSkills` プラグインがエージェントに公開します(Gateway ターゲットではありません)。`infra-cdk/lib/skills-storage-stack.ts` を参照。 + +**ユーザーごとのツールアクセス:** Gateway の Cedar policy が、ユーザーの `department` クレーム(`cognito:groups` から解決。[Identity Propagation](IDENTITY_POLICY.md) 参照)に基づいて _誰が_ 各ツールを呼べるかを制御します。読み取り系ツールは finance + engineering に許可、破壊的な `aws___call_aws` / `aws___run_script` は finance のみ、guest は拒否です。拒否されたツールは `tools/list` の段階で非表示になります。 + +## アーキテクチャの比較 + +### スタンドアロン MCP Gateway と Lambda Targets + +AgentCore Gateway を実装するには 2 つの主要なアプローチがあります。 + +#### スタンドアロン MCP Gateway + +- Gateway が直接 MCP (Model Context Protocol) サーバーを実装 +- ツールが gateway インフラストラクチャに組み込まれている +- 基本的なシナリオではセットアップが簡単 +- クライアント → gateway の直接通信 + +#### Lambda Targets (FAST の選択) + +- Gateway が外部 Lambda 関数へのプロキシ/ルーターとして動作 +- 各ツールは別々の Lambda 関数として実装される +- クライアント → Gateway → Lambda → Gateway → クライアントの流れ +- エンタープライズ用途のメリットを備えた本番運用向けアーキテクチャ + +### FAST が Lambda Targets を採用する理由 + +以下の本番運用上の利点から Lambda targets を選択しました。 + +1. **関心の分離**: ビジネスロジックは Lambda 関数内にあり、gateway インフラストラクチャ側には存在しない +2. **独立したスケーリング**: 各ツールを利用パターンに応じて独立してスケールできる +3. **保守性**: gateway インフラストラクチャに触れることなくツールロジックを更新できる +4. **再利用性**: 同じ Lambda を複数の gateway や他のサービスから使用できる +5. **言語の柔軟性**: 各 Lambda が異なるプログラミング言語を使用できる +6. **独立したデプロイ**: gateway のダウンタイムなしでツール更新をデプロイできる +7. **コスト最適化**: 実際のツール実行時間に対してのみ料金が発生する +8. **セキュリティ**: 各 Lambda に必要な要件に応じた IAM 権限を個別に設定できる + +## 実装の詳細 + +### Gateway の構成 + +gateway は以下の構成で AWS CDK の L1 constructs を用いて作成されます。 + +- **Protocol Type**: MCP (Model Context Protocol) +- **Authorization**: Cognito 統合によるカスタム JWT +- **Authentication**: Machine-to-machine の client credentials フロー +- **Target Type**: AWS Lambda 関数 +- **Optional Features**: Semantic search(ツール検索のために有効化可能) + +### Lambda Target の構造 + +FAST における各 Lambda target は次のパターンに従います。 + +```python +def handler(event, context): + # context からツール名を取得(target prefix を除去) + delimiter = "___" + original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + tool_name = original_tool_name[original_tool_name.index(delimiter) + len(delimiter):] + + # event にはツールの引数が直接入っている + arguments = event + + # 期待される形式でレスポンスを返す + return { + 'content': [ + { + 'type': 'text', + 'text': 'Tool response here' + } + ] + } +``` + +#### ツール呼び出しプロトコルの詳細 + +**重要な実装上の注意点:** + +ツール名は event の body には **渡されません**。Gateway は Lambda の context オブジェクト経由でツール名を渡します。 + +```python +# ツール名の取得場所 +original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + +# 引数は event body にある +name = event.get('name', 'World') +``` + +**ツール名のフォーマット:** + +Gateway はターゲット名を 3 つのアンダースコアを区切り文字として prefix に含めます。 + +``` +{target_name}___{tool_name} +``` + +例: `sample_tool_target___sample_tool` + +**完全な動作実装例:** + +```python +import json +import logging + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +def lambda_handler(event, context): + try: + # context からツール名を取得 + original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + logger.info(f"Received tool invocation: {original_tool_name}") + logger.info(f"Event: {json.dumps(event)}") + + # target prefix を除去 + delimiter = "___" + if delimiter in original_tool_name: + tool_name = original_tool_name[original_tool_name.index(delimiter) + len(delimiter):] + else: + tool_name = original_tool_name + + # 適切なツールハンドラーへルーティング + if tool_name == "sample_tool": + name = event.get('name', 'World') + result = f"Hello, {name}! This is a sample tool from FAST." + return {"result": result} + else: + raise ValueError(f"Unknown tool: {tool_name}") + + except Exception as e: + logger.error(f"Error in lambda_handler: {str(e)}", exc_info=True) + raise +``` + +**Lambda ごとに複数のツールを処理:** + +抽出したツール名でルーティングすることで、1 つの Lambda で複数のツールを処理できます。 + +```python +if tool_name == "tool_one": + # tool one を処理 + pass +elif tool_name == "tool_two": + # tool two を処理 + pass +``` + +これは AWS samples で使用されている有効な本番運用パターンです。 + +### ツールスキーマの定義 + +ツールは CDK スタック内で JSON schema を用いて定義されます。 + +```json +{ + "name": "sample_tool", + "description": "A sample tool that returns a greeting", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name to greet" + } + }, + "required": ["name"] + } +} +``` + +**サポートされる JSON Schema 型:** + +Gateway 用のツール仕様を定義する際は、以下の型を使用します。 + +- `"integer"` - 整数用("int" ではない) +- `"number"` - 浮動小数点数用 +- `"string"` - 文字列用 +- `"boolean"` - 真偽値用 +- `"array"` - 配列用 +- `"object"` - オブジェクト用 + +### 認証フロー + +1. **Machine Client**: CDK が client credentials フロー対応の Cognito machine client を作成 +2. **Resource Server**: gateway アクセス用のスコープを定義(read/write) +3. **JWT Authorization**: Gateway が Cognito の OIDC discovery を用いてトークンを検証 +4. **SSM Parameters**: クライアント認証情報は SSM Parameter Store に安全に保管 + +## 主要コンポーネント + +### 1. Gateway L1 Construct + +gateway は `infra-cdk/lib/backend-stack.ts` 内でネイティブの CloudFormation L1 constructs を用いて作成されます。 + +- `CfnGateway`: MCP プロトコルで AgentCore Gateway を作成 +- `CfnGatewayTarget`: ツールスキーマと共に Lambda targets を構成 +- JWT authorization は Cognito 経由で構成 +- ライフサイクルは CloudFormation により自動管理 + +### 2. Sample Tool Lambda + +`gateway/tools/sample_tool/sample_tool_lambda.py` に配置されています。 + +- 適切な Lambda target 実装を示している +- AgentCore Gateway のイベント形式の解析方法を示している +- エラーハンドリングとロギングを含む + +### 3. IAM ロールと権限 + +**Gateway Role**: gateway が Lambda 関数を呼び出し、必要な AWS サービスにアクセスすることを許可します + +**Custom Resource Role**: gateway のライフサイクル操作を管理します + +### 4. SSM Parameter ストレージ + +Gateway 構成は容易にアクセスできるように SSM に格納されます。 + +- `/stack-name/gateway_url`: Gateway エンドポイント URL +- `/stack-name/machine_client_id`: Cognito client ID +- `/stack-name/machine_client_secret`: Cognito client secret +- `/stack-name/cognito_provider`: Cognito ドメイン URL + +## Gateway のテスト + +### 直接的な Gateway テスト + +提供されているテストスクリプトを使用して gateway の機能を検証します。 + +```bash +python3 scripts/test-gateway.py +``` + +このスクリプトは以下を実行します。 + +1. SSM から取得した machine client 認証情報で認証 +2. MCP プロトコル経由で利用可能なツールをリスト +3. テストパラメータで sample tool を呼び出し +4. 検証用のレスポンスを表示 + +### AgentCore Runtime との統合 + +gateway は以下を介して AgentCore Runtime と統合されます。 + +1. **Runtime Configuration**: Runtime に SSM 経由で gateway URL が設定される +2. **Authentication**: Runtime は JWT トークンに同じ Cognito user pool を使用する +3. **Tool Discovery**: Runtime は gateway の `tools/list` エンドポイント経由でツールを発見する +4. **Tool Execution**: Runtime は gateway の `tools/call` エンドポイント経由でツールを呼び出す + +### MCP 経由でのエージェントとの統合 + +エージェントは Model Context Protocol (MCP) を使用して Gateway に接続します。以下に 2 つの代表的な統合アプローチを示します。 + +#### LangGraph と MultiServerMCPClient の組み合わせ + +`langchain-mcp-adapters` の `MultiServerMCPClient` は、自動的なセッション管理を提供します。 + +```python +from langchain_mcp_adapters.client import MultiServerMCPClient +from langgraph.prebuilt import create_react_agent + +# Gateway 構成で MCP クライアントを作成 +mcp_client = MultiServerMCPClient({ + "gateway": { + "transport": "streamable_http", + "url": gateway_url, + "headers": { + "Authorization": f"Bearer {access_token}" + } + } +}) + +# Gateway からツールを読み込む +tools = await mcp_client.get_tools() + +# ツールを伴うエージェントを作成 +graph = create_react_agent( + model=bedrock_model, + tools=tools, + checkpointer=checkpointer +) +``` + +#### Strands と直接的な MCP セッションの組み合わせ + +Strands エージェントはより細かな制御のために MCP セッションを直接管理することができます。 + +```python +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from langchain_mcp_adapters.tools import load_mcp_tools + +async with streamablehttp_client( + gateway_url, + headers={"Authorization": f"Bearer {access_token}"} +) as (read, write, _): + async with ClientSession(read, write) as session: + await session.initialize() + tools = await load_mcp_tools(session) + # エージェントでツールを使用 +``` + +**例:** 完全な実装は `agent/strands-single-agent/basic_agent.py` を参照してください。 + +## 新しいツールの追加 + +gateway に新しいツールを追加するには以下を行います。 + +1. **Lambda 関数を作成**: Lambda target パターンに従ってツールロジックを実装 +2. **ツールスキーマを定義**: CDK スタックに JSON schema 定義を追加 +3. **Gateway 構成を更新**: gateway custom resource に新しい target を追加 +4. **デプロイ**: CDK deploy を実行してインフラストラクチャを更新 + +### 例: Weather ツールの追加 + +```typescript +// backend-stack.ts 内 +const weatherLambda = new lambda.Function(this, "WeatherToolLambda", { + runtime: lambda.Runtime.PYTHON_3_13, + handler: "weather_tool.handler", + code: lambda.Code.fromAsset(path.join(__dirname, "../../gateway/tools/sample_tool")), +}) + +const weatherToolSchema = { + name: "get_weather", + description: "Get current weather for a location", + inputSchema: { + type: "object", + properties: { + location: { + type: "string", + description: "City and state, e.g. 'Seattle, WA'", + }, + }, + required: ["location"], + }, +} +``` + +## セキュリティ上の考慮事項 + +### 認証 + +- Cognito client credentials を用いた machine-to-machine 認証 +- 設定可能な有効期限を持つ JWT トークン +- Cognito resource server を用いたスコープ付きアクセス + +### 認可 + +- Gateway はリクエストごとに JWT トークンを検証する +- Lambda 関数は gateway の IAM ロール権限を継承する +- すべてのコンポーネントで最小権限の原則 + +### ネットワークセキュリティ + +- Gateway エンドポイントは HTTPS のみ +- Lambda 関数は AWS マネージド VPC 内で動作する +- Lambda 関数は直接インターネットアクセスを必要としない + +## モニタリングとロギング + +### CloudWatch Logs + +- Gateway 操作は `/aws/bedrock-agentcore/gateway/*` に記録される +- Lambda 関数のログは `/aws/lambda/function-name` +- カスタムリソース操作は `/aws/lambda/gateway-custom-resource` + +### メトリクス + +- Gateway の呼び出しメトリクスは CloudWatch 経由で利用可能 +- Lambda 関数の duration およびエラーメトリクス +- Lambda 関数にカスタムメトリクスを追加することも可能 + +## トラブルシューティング + +### よくある問題 + +**"Unknown tool: None" エラー** + +- Lambda 関数が context を正しく解析していないことを示します +- Lambda が AgentCore Gateway の入力形式に従っているか確認してください +- 詳細なエラー情報は CloudWatch logs を確認してください + +**認証失敗** + +- SSM 内の Cognito client 認証情報を確認してください +- JWT トークンの有効期限を確認してください +- gateway authorization 構成が正しいことを確認してください + +**ツールが見つからない** + +- ツールスキーマが Lambda の実装と一致するか確認してください +- gateway target 構成を確認してください +- Lambda 関数がデプロイされアクセス可能であることを確認してください + +**Gateway が "An internal error occurred" を返す** + +- CDK construct で `exceptionLevel: 'DEBUG'` を設定するか、AWS CLI 経由で gateway を更新してデバッグを有効にし、詳細なエラーメッセージを確認できます。 + +```bash +# gateway のデバッグを有効化 +aws bedrock-agentcore-control update-gateway \ + --gateway-identifier \ + --name \ + --role-arn \ + --protocol-type MCP \ + --authorizer-type CUSTOM_JWT \ + --authorizer-configuration \ + --exception-level DEBUG +``` + +または CDK 内の gateway construct を更新します。 + +```typescript +const gateway = new bedrockagentcore.CfnGateway(this, "AgentCoreGateway", { + name: `${config.stack_name_base}-gateway`, + roleArn: gatewayRole.roleArn, + protocolType: "MCP", + exceptionLevel: "DEBUG", // 詳細エラーメッセージのためにこの行を追加 + // ... 残りの構成 +}) +``` + +### デバッグ手順 + +1. **SSM パラメータの確認**: すべての gateway 構成パラメータが存在するか検証 +2. **認証のテスト**: テストスクリプトでトークン生成を検証 +3. **CloudWatch Logs のレビュー**: gateway および Lambda 関数のログを確認 +4. **ツールスキーマの検証**: スキーマが期待される形式と一致するか確認 +5. **Lambda の直接テスト**: Lambda 関数を独立して呼び出してロジックを検証 + +## ベストプラクティス + +### Lambda 関数開発 + +- デバッグのために受信イベントを必ずログに記録する +- 適切なエラーハンドリングを実装し、意味のあるエラーメッセージを返す +- 構成には環境変数を使用する +- 関数は単一のツールに責任を集中させる + +### スキーマ設計 + +- ツールおよびパラメータの説明は明確かつ説明的に書く +- 適切な JSON schema 型と制約を使用する +- 役立つ場合は説明文に例を含める +- 入力スキーマはシンプルで焦点を絞ったものに保つ + +### デプロイ + +- gateway と統合する前に各ツールを個別にテストする +- Lambda 関数のデプロイにバージョンタグを使用する +- デプロイ後は CloudWatch メトリクスを監視する +- 本番環境への変更には段階的なロールアウトを実装する + +## 関連ドキュメント + +- [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md) - Gateway tools のためのユーザーレベルアクセス制御 +- [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md) - Cedar policy の構文、機能、リファレンス +- [Replacing Cognito](REPLACING_COGNITO.md) - Identity provider の差し替えと Gateway interceptors のガイド +- [Runtime-Gateway Authentication](RUNTIME_GATEWAY_AUTH.md) - Runtime と Gateway 間の M2M トークンフロー +- [Deployment Guide](DEPLOYMENT.md) - FAST インフラストラクチャのデプロイ方法 +- [AWS AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-gateway.html) - 公式 AWS ドキュメント +- [AWS Gateway Lambda Target Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-add-target-lambda.html) - Lambda target 実装の詳細 + +## Design notes + +本派生プロジェクト固有の設計判断 (Gateway target まわり): + +- **Lambda だけでなく MCP server target**: AWS MCP server・Strands docs MCP server・長期記憶のメタ想起 MCP server を Gateway の MCP server target として公開する。agent 内に直接組み込むのではなく target 化することで、ネイティブツールと MCP ツールでツール面・認証・Cedar ポリシーを統一できる。 +- **AWS MCP target は広めの role を継承**: AWS MCP server の `call_aws` と `run_script` を成立させるには Gateway target role に AWS サービス列挙・呼び出し権限が必要。デモでは read-broad / write-narrow に絞ってある。プロダクション利用時は脅威モデルに応じて再評価する想定。 +- **SigV4 service name の固定**: Runtime 内から Gateway target endpoint を呼ぶ際、SigV4 署名の service identifier が重要なため、推論ではなくコードで固定している。軽微な API 変更に伴う認証無音失敗を避ける狙い。 +- **マネージド Web Search target**: GA された Web Search connector を Gateway target として配線し、別途検索 SDK やブラウザツールを抱え込まずに引用付きの一般質問応答ができるようにしている。 diff --git a/samples/aws-specialist-agent/docs-jp/IDENTITY_POLICY.md b/samples/aws-specialist-agent/docs-jp/IDENTITY_POLICY.md new file mode 100644 index 0000000..0ce75d3 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/IDENTITY_POLICY.md @@ -0,0 +1,344 @@ +# Identity Propagation & Cedar Policy ガイド + +このドキュメントでは、FAST がフロントエンドから AgentCore Gateway の Cedar policy までユーザーアイデンティティをどのように伝搬し、Gateway ツール上できめ細かなユーザー単位のアクセス制御を実現するかを説明します。 + +## 概要 + +AgentCore Gateway は CUSTOM_JWT authorizer によって検証された OAuth2 トークンでリクエストを認証します。デフォルトでは、Runtime は Client Credentials フローで M2M トークンを取得し、すべてのリクエストが同じマシンアイデンティティを持つことになります。つまり Gateway は個々のユーザーを区別できません。 + +この機能は、既存の M2M フローの上に **アイデンティティ伝搬** を追加します。認証済みユーザーのアイデンティティと Cognito グループメンバーシップは、Cognito の `aws_client_metadata` パラメータと V3 Pre-Token Lambda トリガを使って M2M トークンに埋め込まれます。Runtime は M2M トークンを AgentCore Identity(Token Vault)経由で取得するため、パブリックな Cognito ホストドメインを直接呼び出すことはなく、Runtime を閉域(NAT 不要)に保てます。リッチ化されたトークンは Gateway で Cedar policy により評価され、「財務部門のユーザーのみが破壊的ツールを実行できる」といったアクセス制御ルールを実現します。 + +**こんな時に使う:** Gateway tool が、部門・役割・ユーザー ID といった属性に基づくユーザー単位のアクセス制御を必要とする場合。 + +> **このデモのスコープ:** この実装では、ユーザー → ツール のアクセス制御(例: 「ゲストユーザーは AgentCore Gateway から text_analysis_tool を利用できない」)を示します。AgentCore Policy は、入力検証、リクエストパラメータに基づく条件付きアクセス、複数ツールに渡るポリシーなど、追加の機能もサポートしており、これらは [Cedar Policy Capabilities](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) に記載されています。 + +## AgentCore Policy とは + +AgentCore Policy は、AI エージェントが実行可能な操作を制御するサービスです。エージェントとそのツールの間に立つセキュリティガードのようなもので、エージェントがツールを使おうとするたびに、ガードがルールをチェックして許可するか拒否するかを判断します。 + +**シンプルに言うと:** + +- 誰がどのツールをどのような条件下で使えるかを定めるルール(Cedar policy)を書きます +- Policy Engine がツール呼び出しのたびにそのルールを自動的に強制します +- どのルールも明示的に許可しなければ、その操作は拒否されます(deny-by-default) +- 強制は決定的です — プロンプトエンジニアリングと違い、巧妙な言い回しでバイパスできません + +**制御できること:** + +| 機能 | ルール例 | このデモで実演しているか | +| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------ | +| ユーザー → ツール アクセス | 「財務ユーザーのみが billing tool にアクセスできる」 | はい | +| 入力検証 | 「返金額は $1000 を超えてはならない」 | いいえ([Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) 参照) | +| 複数ツールポリシー | 「開発者は読み取りツールは使えるが書き込みツールは使えない」 | いいえ([Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) 参照) | +| 環境分離 | 「本番 runtime のみが本番ツールにアクセスできる」 | いいえ([Runtime-Level Access Control](#runtime-level-access-control) 参照) | +| 条件付きアクセス | 「クエリが特定のアカウントを対象とする場合のみツールを許可」 | いいえ([Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) 参照) | + +**このデモでは** カスタムの `department` クレームに基づくユーザー → ツール アクセス制御を実装します。他の機能も同じインフラ(Policy Engine + Cedar + Gateway)で異なるポリシー条件を使うだけで実現できます。各機能の例を含む完全な構文リファレンスは [Cedar Policy Capabilities](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) を参照してください。 + +**主要コンセプト:** + +- **Policy Engine** — Cedar policy を処理する評価エンジン。1 つのエンジンは 1 つの Gateway にアタッチされます。 +- **Cedar Policy** — AWS のオープンソースポリシー言語 [Cedar](https://www.cedarpolicy.com/) で書かれた宣言的ルール。確率的ではなく決定的です。 +- **CUSTOM_JWT Authorizer** — トークンを検証し、JWT クレームを Cedar の principal タグにマッピングする Gateway コンポーネント。 +- **Deny-by-default** — どの `permit` 文にもマッチしなければ、リクエストは拒否されます。明示的な `forbid` は不要です。 +- **ツールフィルタリング** — 拒否されたツールは実行時にブロックされるだけでなく、ディスカバリー時(`tools/list`)にエージェントから隠されます。[Tool Discovery vs Execution](CEDAR_POLICY_GUIDE.md#tool-discovery-vs-execution) を参照してください。 + +## アーキテクチャ / フロー + +アイデンティティ伝搬フローは 6 ステップで構成されます。 + +``` +1. ユーザーがログイン → フロントエンドが Cognito から JWT を取得(access token は cognito:groups を含む) +2. フロントエンドがリクエストを送信 → Runtime が JWT を検証し user_id(sub)と cognito:groups を抽出 +3. Runtime が AgentCore Identity(Token Vault)経由で M2M トークンを要求し、aws_client_metadata に user_id + groups を載せる +4. Cognito V3 Pre-Token Lambda が発火 → グループを department/role クレームにマッピング → M2M トークンに注入 +5. Runtime がリッチ化された M2M トークンで Gateway tool を呼び出し +6. Gateway の CUSTOM_JWT Authorizer がトークンクレームを Cedar principal タグにマッピング → Policy Engine が Cedar policy を評価 → 許可または拒否 +``` + +主要なセキュリティ特性: `user_id`(`sub`)と `cognito:groups` はいずれも Runtime の Session Context で検証済みの JWT から取得され、LLM やリクエストペイロードからではありません。これにより、エンドツーエンドで暗号学的に安全なアイデンティティチェーンが保証されます。 + +## コンポーネント + +### Cognito ESSENTIALS Tier + +**ファイル:** `infra-cdk/lib/cognito-stack.ts` + +Cognito User Pool は `featurePlan: ESSENTIALS` で構成されます。これは、V3 Pre-Token Generation Lambda トリガが ESSENTIALS tier を有効にした場合にのみ Client Credentials(M2M)グラントで発火するため、必須となります。これがないと、M2M トークン生成時に Pre-Token Lambda が呼び出されません。 + +### V3 Pre-Token Lambda + +**ファイル:** `infra-cdk/lambdas/pretoken-v3/index.py` + +この Lambda はあらゆるトークン生成イベント(ユーザーログインと M2M の両方)で発火します。M2M フロー(`TokenGeneration_ClientCredentials`)のみを処理し、ユーザーログインフローはスキップします。 + +M2M フローでは、`clientMetadata` から `verified_groups`(ユーザーの `cognito:groups`、カンマ区切り)を読み取り、グループ名を department / role クレームにマッピングします。 + +| Cognito グループ | Department | Role | +| ---------------- | ----------- | --------- | +| `finance` | finance | admin | +| `engineering` | engineering | developer | +| (グループなし) | guest | viewer | + +これらのクレームは `claimsToAddOrOverride` 経由で M2M アクセストークンに注入されます。 + +- `user_id` — 認証済みユーザーの ID(`sub`) +- `department` — ユーザーの部門(Cognito グループ名) +- `role` — ユーザーの役割 + +> **補足:** これらのクレーム名(`user_id`、`department`、`role`)はカスタムのアプリケーション定義クレームで、標準的な JWT/OIDC クレームではありません。必要に応じて任意の名前を定義できます。詳細は [Understanding Claims](CEDAR_POLICY_GUIDE.md#understanding-claims-custom-vs-standard) を参照してください。 + +グループは検証済みの access token から直接読み取られるため、Lambda は `AdminListGroupsForUser` を呼び出しません。割り当てを変更するには、ユーザーを Cognito グループに追加・削除するか、Pre-Token Lambda 内の `GROUP_ROLES` マップを編集してください。 + +### Cedar Policy ファイル + +**ディレクトリ:** `gateway/policies/` + +Cedar policy は Gateway tool に対するアクセス制御ルールを定義します。1 ファイル = 1 Cedar ステートメント(AgentCore `CreatePolicy` は 1 ポリシーにつき 1 ステートメント)です。デプロイ時に CDK によって読み込まれ、`//` のコメント行が削除され、`{{GATEWAY_ARN}}` プレースホルダが実際の Gateway ARN に置換されます。 + +| ファイル | ツール | 許可される部門 | +| ------------------------------ | -------------------------------------------------------- | -------------------- | +| `01-sample-tool.cedar` | `sample-tool-target___text_analysis_tool` | finance, engineering | +| `02-aws-mcp-read.cedar` | AWS MCP の読み取り系ツール(`aws-mcp___aws___*`) | finance, engineering | +| `03-aws-mcp-destructive.cedar` | `aws-mcp___aws___call_aws`、`aws-mcp___aws___run_script` | finance のみ | + +guest(グループなし)はどの `permit` にもマッチせず、Cedar の deny-by-default により拒否されます。engineering は読み取り系ツールを使えますが、破壊的な `call_aws` / `run_script` は使えません。finance はすべて使えます。マトリクスを変更するには該当ファイルを編集し `cdk deploy` を実行します。 + +### Policy Engine カスタムリソース + +**ファイル:** + +- `infra-cdk/lambdas/cedar-policy/index.py` — Custom Resource Lambda +- `infra-cdk/lib/backend-stack.ts` — CDK リソース定義 + +AgentCore Policy には L1/L2 CDK construct が存在しないため、CloudFormation Custom Resource が Policy Engine のライフサイクル全体を管理します。Lambda は 3 つの CloudFormation イベントを処理します。 + +- **Create:** Policy Engine を作成 → Cedar Policy を作成 → Policy Engine を Gateway にアタッチ +- **Update:** 既存ポリシーを削除 → 更新されたドキュメントで新規ポリシーを作成 → エンジンが Gateway にアタッチされたままか確認 +- **Delete:** Policy Engine を Gateway からデタッチ → すべてのポリシーを削除 → Policy Engine を削除 + +すべての操作は公式の boto3 waiter(`policy_engine_active`、`policy_engine_deleted`、`policy_active`、`policy_deleted`)を使います。Gateway のステータス変更は公式 waiter が存在しないため、カスタムポーリングループを使います。 + +### Gateway Authorizer + +**ファイル:** `infra-cdk/lib/backend-stack.ts` + +Gateway は Cognito の OIDC discovery URL とマシンクライアント ID で構成された `CUSTOM_JWT` authorizer を使います。authorizer は M2M トークンを検証し、JWT クレームを Cedar の principal タグにマッピングします。 + +| JWT クレーム | Cedar Principal タグ | クレームタイプ | +| ------------ | -------------------------------- | ----------------------------------- | +| `department` | `principal.getTag("department")` | カスタム(Pre-Token Lambda が注入) | +| `role` | `principal.getTag("role")` | カスタム(Pre-Token Lambda が注入) | +| `user_id` | `principal.getTag("user_id")` | カスタム(Pre-Token Lambda が注入) | + +## Cedar Policy ガイド + +クレーム、アクションフォーマット、スキーマ制約、ツールディスカバリー vs 実行、ポリシー機能を含む完全な Cedar policy リファレンスについては [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md) を参照してください。 + +## Gateway 認証: AgentCore Identity(Token Vault) + +Runtime は Gateway 用の M2M トークンを AgentCore Identity の `@requires_access_token` デコレータ経由で取得します(各パターンの `tools/gateway.py` を参照)。AgentCore Identity が Cognito トークン交換を AWS 内のサーバー側で実行し、`bedrock-agentcore` VPC エンドポイント経由で到達可能なため、Runtime はパブリックな Cognito ホストドメインを呼び出さず、NAT Gateway は不要です。 + +ユーザーアイデンティティの伝搬は、デコレータに `custom_parameters={"aws_client_metadata": json.dumps({"verified_user_id": , "verified_groups": })}` を渡すことで行います。`aws_client_metadata` は Cognito が Pre-Token Lambda に転送する唯一の `custom_parameters` キー(`ClientMetadata` として)であり、フラットな文字列マップである必要があるため、グループはカンマ区切り文字列に結合します。Lambda は `verified_groups` を読み取り、Cedar policy が評価する `department`/`role` クレームを注入します。 + +> **Cognito を置き換えるには?** Cognito を別の Identity Provider(Okta、Auth0、Entra ID など)に切り替える方法や、動的なアクセス制御に Gateway Interceptor を使う方法は [Replacing Cognito](REPLACING_COGNITO.md) を参照してください。 + +## カスタマイズ + +### グループ割り当ての変更 + +最も簡単な変更は、ユーザーを `finance` / `engineering` の Cognito グループに追加・削除することです(コード変更も再デプロイも不要)。グループとクレームのマッピングを変えるには、`infra-cdk/lambdas/pretoken-v3/index.py` の `GROUP_ROLES` マップを編集します。まったく別のアイデンティティソース(DynamoDB、LDAP など)を使う場合は、`verified_groups` の参照部分を独自の解決に置き換えてください。 + +### 新しいクレームの追加 + +M2M トークンに新しいクレームを追加するには: + +1. Pre-Token Lambda の `claimsToAddOrOverride` にクレームを追加 +2. Cedar policy で `principal.getTag("claim_name")` を使ってクレームを参照 +3. Gateway の構成変更は不要 — CUSTOM_JWT authorizer がすべての JWT クレームを Cedar タグに自動マッピングします + +### VPC モード + +VPC モードのデフォルトデプロイは **完全閉域(NAT Gateway なし)** です。M2M トークンを AgentCore Identity 経由で取得するため(Cognito トークン交換は AWS 内のサーバー側で実行され、`bedrock-agentcore` VPC エンドポイント経由で到達可能)、Runtime はアウトバウンドのインターネットアクセスを必要としません。プライベートサブネットは隔離(`0.0.0.0/0` ルートなし)され、すべての AWS アクセスは VPC エンドポイント経由で行われます。 + +VPC 構成の詳細は `docs/DEPLOYMENT.md` を参照してください。 + +### Runtime レベルのアクセス制御 + +デフォルトでは、Gateway 経由のすべてのリクエストは同じマシンクライアントアイデンティティを共有します。複数の AgentCore Runtime をデプロイし、どの runtime がどのツールにアクセスできるかを制御したい場合、Cognito の `clientId` を暗号学的に検証された runtime アイデンティティとして使えます。 + +**Cedar で `context.runtime.arn` を使えないのはなぜ?** +Cedar スキーマは `context.input`(ツールパラメータ)のみをサポートしており、`context.runtime.arn` のようなフィールドは存在しません。サポートされていない context フィールドを参照しようとするとポリシー作成が失敗します。 + +**runtime アイデンティティに Cognito Groups を使えないのはなぜ?** +Cognito User Pool Groups はユーザーアイデンティティに適用され、app client には適用されません。M2M トークン自体には `cognito:groups` クレームが付かないため、runtime を識別できません。(ユーザーアイデンティティには引き続きグループを使います — 上述のとおり Runtime はユーザーの access token から `cognito:groups` を読み取り `aws_client_metadata` で伝搬します。これはここで論じる runtime ごとの `clientId` アイデンティティとは別軸です。) + +**解決策: Runtime ごとに Cognito App Client を 1 つ用意する** + +各 CDK スタックは Cognito app client と AgentCore Runtime の両方を作成するため、`clientId` は runtime アイデンティティとして機能します — `client_secret` 経由で暗号学的に検証されます。Pre-Token Lambda は自己申告なしで `clientId` を `runtime_env` クレームにマッピングします。 + +**アーキテクチャ:** + +``` +Runtime A (production) → Client A (client_secret_A) で認証 + → Cognito が clientId = "abc123" を検証 + → Pre-Token Lambda が "abc123" → runtime_env: "production" にマッピング + → Cedar policy が principal.getTag("runtime_env") をチェック + +Runtime B (staging) → Client B (client_secret_B) で認証 + → Cognito が clientId = "def456" を検証 + → Pre-Token Lambda が "def456" → runtime_env: "staging" にマッピング + → Cedar policy が principal.getTag("runtime_env") をチェック +``` + +**ステップ 1: CDK で別々のマシンクライアントを作成** + +```typescript +// runtime 環境ごとに 1 つのマシンクライアントを作成 +const machineClientProd = new cognito.UserPoolClient(this, "MachineClientProd", { + userPool: this.userPool, + generateSecret: true, + oAuth: { + flows: { clientCredentials: true }, + // 既存のマシンクライアントと同じ resource server スコープを使用 + scopes: [ + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "read", scopeDescription: "Read access" }) + ), + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "write", scopeDescription: "Write access" }) + ), + ], + }, +}) + +const machineClientStaging = new cognito.UserPoolClient(this, "MachineClientStaging", { + userPool: this.userPool, + generateSecret: true, + oAuth: { + flows: { clientCredentials: true }, + scopes: [ + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "read", scopeDescription: "Read access" }) + ), + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "write", scopeDescription: "Write access" }) + ), + ], + }, +}) + +// マッピングを環境変数として Pre-Token Lambda に渡す +preTokenLambda.addEnvironment( + "CLIENT_RUNTIME_MAP", + JSON.stringify({ + [machineClientProd.userPoolClientId]: "production", + [machineClientStaging.userPoolClientId]: "staging", + }) +) +``` + +**ステップ 2: Pre-Token Lambda で clientId → runtime_env のマッピングを行う** + +```python +import os, json + +def lambda_handler(event, context): + if event["triggerSource"] != "TokenGeneration_ClientCredentials": + return event + + # clientId は Cognito によって検証済み + client_id = event["callerContext"]["clientId"] + + # CDK がデプロイ時にセットしたマッピング + client_runtime_map = json.loads(os.environ.get("CLIENT_RUNTIME_MAP", "{}")) + runtime_env = client_runtime_map.get(client_id, "unknown") + + # 既存のユーザーアイデンティティロジック(変更なし) + meta = event["request"].get("clientMetadata", {}) + user_id = meta.get("verified_user_id", "") + groups = [g for g in meta.get("verified_groups", "").split(",") if g] + + GROUP_ROLES = {"finance": "admin", "engineering": "developer"} + department = next((g for g in groups if g in GROUP_ROLES), "guest") + role = GROUP_ROLES.get(department, "viewer") + + event["response"]["claimsAndScopeOverrideDetails"] = { + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "user_id": user_id, + "department": department, + "role": role, + "runtime_env": runtime_env, + } + } + } + return event +``` + +**ステップ 3: Cedar policy に runtime_env を追加** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("runtime_env") && + principal.getTag("runtime_env") == "production" && + principal.hasTag("department") && + (principal.getTag("department") == "finance" || + principal.getTag("department") == "engineering") +}; +``` + +**セキュリティモデル — 二層構造のアイデンティティ:** + +| レイヤー | クレーム | ソース | 信頼レベル | +| ------------------------ | ------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| Runtime アイデンティティ | `runtime_env` | `callerContext.clientId`(Cognito 検証済み) | 暗号学的 — `client_secret` が必要 | +| ユーザーアイデンティティ | `user_id`、`department`、`role` | `clientMetadata.verified_user_id`(検証済み JWT の `sub` クレーム由来) | JWT 検証済み — Runtime が Cognito 検証済みトークンからサーバー側で抽出 | + +両レイヤーとも Cognito によって保護されています: `clientId` は client secret 交換で検証され、`user_id` は Runtime の `extract_user_id_from_context()` によって抽出された検証済み JWT の `sub` クレームに由来します。 + +> **補足:** このセクションでは runtime レベルのアクセス制御のアーキテクチャパターンを説明しています。現状の FAST 実装は単一のマシンクライアントを使っています。このパターンを実装するには、`cognito-stack.ts` で追加のマシンクライアントを作成し、上記のマッピングロジックで Pre-Token Lambda を更新してください。 + +## デプロイ済みポリシーの確認 + +Gateway に現在アクティブな Cedar policy を確認するには: + +1. **AWS Console → Bedrock AgentCore → Policy** に移動 +2. Policy engines セクションから対象の Policy Engine(例: `fast_specialist_agent_policy_engine`)をクリック +3. **Policies** セクションで対象のポリシー(例: `fast_specialist_agent_policy_engine_cp_`)をクリック +4. **Definition** セクションにポリシーの構成要素が表示されます: + - **Effect**: `permit` または `forbid` + - **Scope: Principal**: `AgentCore::OAuthUser` + - **Scope: Actions**: ツールアクション名(例: `sample-tool-target___text_analysis_tool`) + - **Scope: Resource**: Gateway 名 + - **Conditions**: `when` 句のロジック +5. **Cedar** セクションにはデプロイ済みの完全な Cedar policy 文が表示されます + +これを使って、`cdk deploy` で期待したポリシーバージョンが適用されたかを確認できます。 + +## トレースによるポリシー判定の検証 + +Cedar policy の許可/拒否判定を CloudWatch ログで確認するには: + +1. **AWS Console → Bedrock AgentCore → Runtimes** に移動 +2. Runtime resources セクションから対象の runtime(例: `fast_specialist_agent_FASTAgent`)をクリック +3. **Tracing** セクションまでスクロールし、**Edit** をクリックして **Enable tracing** を Enable に切り替え +4. **Bedrock AgentCore → Gateways** に移動 +5. 対象の gateway(例: `fast-specialist-agent-gateway`)をクリックし、**Tracing** までスクロールして **Edit** をクリック、**Enable tracing** を Enable に切り替え +6. フロントエンドからツール呼び出しをトリガーするクエリを実行 +7. **CloudWatch Console → Log Management → Log groups** に移動 +8. `aws/spans` ロググループを検索してクリックし、デフォルトのログストリームをクリック +9. **Filter events** 検索ボックスに `policy` と入力 +10. `AgentCore.Policy.PartiallyAuthorizeActions` スパンを探します — 含まれる情報: + - `aws.agentcore.policy.allowed_tools`: ユーザーが利用を許可されたツール + - `aws.agentcore.policy.denied_tools`: ユーザーがアクセスを拒否されたツール + - `aws.agentcore.gateway.policy.mode`: `ENFORCE` と表示されるはず diff --git a/samples/aws-specialist-agent/docs-jp/LOCAL_DEVELOPMENT.md b/samples/aws-specialist-agent/docs-jp/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000..1e5e9de --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/LOCAL_DEVELOPMENT.md @@ -0,0 +1,215 @@ +# Docker Compose を使用したローカル開発 + +このガイドでは、開発目的で Docker Compose を使用して FAST スタック全体をローカルで実行する方法について説明します。 + +## 前提条件 + +**重要**: ローカル開発でも、バックエンドの依存関係 (Memory、Gateway、SSM パラメータ) のために、AWS にデプロイされた FAST スタックが必要です。Docker Compose はフロントエンドとエージェントをコンテナ化するだけで、AWS サービスを置き換えるものではありません。 + +### 必須 + +1. **デプロイ済みの FAST スタック**: 以下を使用してすでに FAST を AWS にデプロイしている必要があります: + + ```bash + cd infra-cdk + cdk deploy + ``` + +2. **AWS 認証情報**: AWS 認証情報は **環境変数としてエクスポートする必要があります** — Docker コンテナは `~/.aws/credentials` や `~/.aws/config` を読み取れません: + + ```bash + # Option 1: 既存の aws configure プロファイルからエクスポート + export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) + export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) + export AWS_SESSION_TOKEN=$(aws configure get aws_session_token) # 一時的な認証情報を使用する場合 + + # Option 2: 直接設定 + export AWS_ACCESS_KEY_ID=your-key + export AWS_SECRET_ACCESS_KEY=your-secret + export AWS_SESSION_TOKEN=your-token # 一時的な認証情報を使用する場合 + ``` + +3. **Docker と Docker Compose**: Compose サポートを備えた Docker Desktop または Docker Engine をインストールします + +4. **環境変数**: 以下の必須変数を設定します: + ```bash + export MEMORY_ID=your-memory-id + export STACK_NAME=your-stack-name + export AWS_DEFAULT_REGION=us-east-1 + ``` + +### 環境変数の検索 + +これらの値はデプロイ済みのスタックから取得します: + +```bash +# スタックの outputs を取得 +aws cloudformation describe-stacks --stack-name your-stack-name --query 'Stacks[0].Outputs' + +# MemoryArn から Memory ID を抽出 (最後の / 以降の部分) +# Stack Name を抽出 (デプロイ時に使用した名前) +# デプロイしたのと同じリージョンを使用 +``` + +## クイックスタート + +1. **環境変数を設定**: + + ```bash + export MEMORY_ID=your-memory-id-from-stack-outputs + export STACK_NAME=your-stack-name + export AWS_DEFAULT_REGION=us-east-1 + ``` + +2. **スタックを起動**: + + ```bash + cd docker && docker compose up --build + ``` + +3. **アプリケーションへのアクセス**: + - Frontend: http://localhost:3000 + - Agent API: http://localhost:8080 + - Agent Health: http://localhost:8080/ping + +## ローカルモードでの認証 + +本番環境では、AgentCore Runtime がユーザーの JWT を検証し、それをエージェントに渡します。エージェントは、リクエストペイロードを信頼するのではなく、JWT の `sub` クレームからユーザー ID を抽出します (プロンプトインジェクションによるなりすましを防止)。 + +Docker Compose 経由でローカル実行する場合、AgentCore Runtime はありません。テストスクリプトは、`sub` クレームとしてテストユーザー ID を持つモックの未署名 JWT を生成し、`Authorization: Bearer` ヘッダーで送信します。これにより、実際の Cognito トークンを必要とせずに、本番環境と同じコードパスが実行されます。 + +## 環境設定 + +利便性のために、リポジトリのルートに `.env` ファイルを作成します: + +```bash +# 必須 - デプロイされた AWS スタックから取得 +MEMORY_ID=your-memory-id +STACK_NAME=your-stack-name +AWS_DEFAULT_REGION=us-east-1 + +# AWS 認証情報 (必須 - Docker コンテナは ~/.aws/credentials を読み取れません) +AWS_ACCESS_KEY_ID=your-key +AWS_SECRET_ACCESS_KEY=your-secret +AWS_SESSION_TOKEN=your-token +``` + +その後、以下を実行: `cd docker && docker compose up --build` + +## 開発ワークフロー + +### 変更を加える + +- **フロントエンドの変更**: ファイルはボリュームとしてマウントされているため、変更はすぐに反映されます +- **エージェントの変更**: エージェントコンテナを再ビルドします: + ```bash + cd docker && docker compose up --build agent + ``` + +### 異なるエージェントパターンの使用 + +異なるエージェントパターンを使用するには: + +1. **docker/docker-compose.yml を編集**: + + ```yaml + agent: + build: + dockerfile: agent//Dockerfile + ``` + +2. **再ビルド**: + ```bash + cd docker && docker compose up --build agent + ``` + +### ログとデバッグ + +```bash +# すべてのログを表示 +docker compose logs -f + +# 特定のサービスのログを表示 +docker compose logs -f agent +docker compose logs -f frontend + +# コンテナのシェルにアクセス +docker compose exec agent bash +docker compose exec frontend sh +``` + +## トラブルシューティング + +### エージェントが起動しない + +**症状**: エージェントコンテナが終了するか、ヘルスチェックが失敗する + +**解決策**: + +1. AWS 認証情報を確認: `aws sts get-caller-identity` +2. 環境変数が正しく設定されていることを確認 +3. デプロイされたスタックが存在し、正常であることを確認 +4. エージェントログを確認: `docker compose logs agent` + +### フロントエンドがエージェントに接続できない + +**症状**: フロントエンドはロードされるが、バックエンドと通信できない + +**解決策**: + +1. エージェントが正常か確認: `curl http://localhost:8080/ping` +2. コンテナ間のネットワーク接続を確認 +3. フロントエンドがローカルエージェントエンドポイントを使用するように設定されていることを確認 + +### AWS 権限エラー + +**症状**: エージェントは起動するが、AWS API 呼び出しで失敗する + +**解決策**: + +1. AWS 認証情報の IAM 権限を確認 +2. デプロイされたスタックリソースにアクセスできることを確認 +3. 正しい AWS リージョンが設定されていることを確認 + +### Memory/Gateway が見つからない + +**症状**: エージェントが Memory または Gateway リソースの不足を報告する + +**解決策**: + +1. `MEMORY_ID` がデプロイされたスタックの Memory リソースと一致することを確認 +2. `STACK_NAME` が CloudFormation スタック名と一致することを確認 +3. スタックのデプロイが正常に完了したことを確認 + +## スタックの停止 + +```bash +# すべてのサービスを停止 +cd docker && docker compose down + +# 停止してボリュームも削除 +cd docker && docker compose down -v + +# 停止してイメージも削除 +cd docker && docker compose down --rmi all +``` + +## 本番環境へのデプロイ + +この Docker Compose のセットアップは開発専用です。本番環境のデプロイには以下を使用します: + +```bash +cd infra-cdk +cdk deploy +cd .. +python scripts/deploy-frontend.py +``` + +## 次のステップ + +- `agent/` のエージェントコードをカスタマイズする +- `frontend/src/` のフロントエンドを変更する +- `tools/` または `gateway/tools/` に新しいツールを追加する +- `infra-cdk/` のインフラストラクチャを更新する + +補足: インフラストラクチャの変更には、Docker Compose の再起動だけでなく、CDK 経由での再デプロイが必要です。 diff --git a/samples/aws-specialist-agent/docs-jp/LOCAL_DOCKER_TESTING.md b/samples/aws-specialist-agent/docs-jp/LOCAL_DOCKER_TESTING.md new file mode 100644 index 0000000..2bee4bc --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/LOCAL_DOCKER_TESTING.md @@ -0,0 +1,234 @@ +# ローカル Docker テストガイド + +AgentCore エージェントの Docker イメージをローカルでビルドおよびテストして、Dockerfile の設定と依存関係を検証します。 + +## 制限事項 + +**スタンドアロンの Docker テストでは Gateway ツールは動作しません**。理由: + +- `@requires_access_token` デコレーターには AgentCore Identity サービスが必要 +- AgentCore Identity は AgentCore Runtime コンテキスト内でのみ動作する +- OAuth2 M2M 認証は Runtime の外部ではモックできない + +**動作するもの:** Dockerfile のビルド、依存関係のインストール、Code Interpreter、Gateway 以外のツール +**動作しないもの:** AgentCore Gateway ツール (MCP ベースの Lambda ツール) + +**Gateway をサポートする完全なローカルテスト** には、`docker-compose` を使用してください ([Local Development Guide](LOCAL_DEVELOPMENT.md) を参照)。 + +## なぜ Docker テストを行うのか? + +| Testing Mode | Gateway Tools | Code Interpreter | Use Case | +| ------------------------ | ------------- | ---------------- | ---------------------------------- | +| `test-agent.py --local` | Yes | Yes | 高速な Python の反復開発 | +| **Manual Docker** | No | Yes | Dockerfile/依存関係の検証 | +| **`docker-compose`** | Yes | Yes | 完全なローカル開発 | +| `test-agent.py` (remote) | Yes | Yes | デプロイされたエージェントのテスト | + +Docker テストでは、以下を検証します: + +- Dockerfile が正しくビルドされること +- 依存関係がコンテナ内に適切にインストールされること +- コンテナが起動し、ヘルスチェックに応答すること +- エージェントコードがコンテナ化された環境で動作すること (Gateway ツールなし) + +## 前提条件 + +1. **Docker** がインストールされ、実行中であること (`docker ps` が動作する必要がある) +2. **デプロイ済みのスタック** - Memory ID と SSM パラメータに必要 +3. **AWS 認証情報** が環境に設定されていること + +## Docker イメージのビルド + +```bash +# エージェントパターン用のイメージをビルド +docker build -f agent/strands-single-agent/Dockerfile \ + -t fast-agent-local \ + --platform linux/arm64 . +``` + +### プラットフォーム要件 + +AgentCore Runtime には ARM64 アーキテクチャが必要です。x86/amd64 マシンでは、エミュレーションを有効にします: + +```bash +# ARM64 エミュレーションのワンタイムセットアップ +docker run --privileged --rm tonistiigi/binfmt --install all +``` + +## コンテナの実行 + +```bash +# CloudFormation の outputs から Memory ID を取得 +MEMORY_ID=$(aws cloudformation describe-stacks \ + --stack-name \ + --query 'Stacks[0].Outputs[?OutputKey==`MemoryArn`].OutputValue' \ + --output text | awk -F'/' '{print $NF}') + +# AWS 認証情報をエクスポート (コンテナに必要) +export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) +export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) +export AWS_SESSION_TOKEN=$(aws configure get aws_session_token) # 一時的な認証情報を使用する場合 + +# コンテナを実行 +docker run --rm -it -p 8080:8080 \ + --platform linux/arm64 \ + -e MEMORY_ID=$MEMORY_ID \ + -e STACK_NAME= \ + -e AWS_DEFAULT_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN \ + fast-agent-local +``` + +**重要:** AWS 認証情報は環境変数としてエクスポートする必要があります。Docker コンテナは `~/.aws/credentials` または `~/.aws/config` から認証情報を読み取ることはできません。 + +## エージェントのテスト + +### ヘルスチェック + +```bash +curl http://localhost:8080/ping +# Returns: {"status":"Healthy","time_of_last_update":...} +``` + +### テスト用のモック JWT + +検証済みの JWT を提供する AgentCore Runtime がないため、モックの未署名 JWT を作成します: + +```bash +# sub=test-user でモック JWT を生成 +MOCK_JWT=$(python3 -c "import base64,json; h=base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=').decode(); p=base64.urlsafe_b64encode(json.dumps({'sub':'test-user'}).encode()).rstrip(b'=').decode(); print(f'{h}.{p}.')") + +# エージェントをテスト (Gateway ツールでは失敗するが、Code Interpreter は動作するはず) +curl -X POST http://localhost:8080/invocations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $MOCK_JWT" \ + -d '{"prompt": "Execute Python: print(2+2)", "runtimeSessionId": "test-123"}' +``` + +**期待される動作:** + +- Code Interpreter のリクエストは動作する +- Gateway ツールのリクエストは認証エラーで失敗する + +## アーキテクチャ + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Local Machine │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Docker Container (ARM64) │ │ +│ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ Agent (basic_agent.py / langgraph_agent.py)│ │ │ +│ │ │ - :8080 をリッスン │ │ │ +│ │ │ - 渡された AWS 認証情報を使用 │ │ │ +│ │ │ - Gateway 認証は失敗する │ │ │ +│ │ └─────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ http://localhost:8080/invocations │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────┐ + │ AWS (デプロイされたリソース) │ + │ - AgentCore Memory (Yes) │ + │ - Code Interpreter (Yes) │ + │ - AgentCore Gateway (No) │ + │ - SSM Parameters (Yes) │ + └─────────────────────────────────┘ +``` + +## トラブルシューティング + +### コンテナは起動するが Gateway 認証が失敗する + +これは **想定通りの動作** です。`@requires_access_token` デコレーターには AgentCore Identity サービスが必要であり、これは AgentCore Runtime 内でのみ動作します。 + +**解決策:** 完全なローカルテストには `docker-compose` を使用してください ([Local Development Guide](LOCAL_DEVELOPMENT.md) を参照)。 + +### コンテナは起動するがエージェントがすぐに失敗する + +コンテナのログを確認します: + +```bash +# コンテナ ID を見つける +docker ps + +# ログを表示 +docker logs +``` + +よくある問題: + +- **AWS 認証情報の不足**: `AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY` が設定されていることを確認 +- **期限切れのセッショントークン**: AWS 認証情報を更新 +- **スタックがデプロイされていない**: スクリプトは Memory ID を取得するためにデプロイされたスタックが必要 + +### "platform mismatch" でビルドが失敗する + +ARM64 エミュレーションを有効にします (上記のプラットフォーム要件を参照)。 + +### localhost:8080 で "Connection refused" になる + +エージェントがまだ起動中の可能性があります。10〜30 秒待ってから再試行してください。問題が続く場合はログを確認してください。 + +### ログ内の ECS/EKS 警告 + +これらの警告はローカル実行時に予想されるものです: + +``` +AwsEcsResourceDetector failed: Missing ECS_CONTAINER_METADATA_URI... +AwsEksResourceDetector failed: No such file or directory... +``` + +OpenTelemetry インストルメンテーションは、ローカルには存在しない ECS/EKS メタデータを探します。これらは安全に無視できます。 + +## 高度な使い方 + +### コンテナログをリアルタイムで表示する + +```bash +# コンテナをフォアグラウンドで起動 (デタッチしない) +docker run --rm -p 8080:8080 \ + --platform linux/arm64 \ + -e MEMORY_ID= \ + -e STACK_NAME= \ + -e AWS_DEFAULT_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN \ + fast-agent-local +``` + +### ビルドのみの検証 + +実行せずに Dockerfile を検証するには: + +```bash +docker build -f agent/strands-single-agent/Dockerfile \ + -t fast-agent-local \ + --platform linux/arm64 . + +# ビルドが成功したか確認 +echo $? # 0 を返すはず +``` + +## 各テストモードを使用するタイミング + +| Scenario | Recommended Mode | +| -------------------------------------- | --------------------------------------- | +| エージェントロジックの高速な反復開発 | `test-agent.py --local` | +| Dockerfile が正しくビルドされるか検証 | Manual Docker build | +| Gateway ツールでローカルテスト | `docker-compose` (LOCAL_DEVELOPMENT.md) | +| デプロイされた本番エージェントのテスト | `test-agent.py` (remote) | +| CI/CD パイプライン検証 | Manual Docker build | + +## 関連ドキュメント + +- [Local Development Guide](LOCAL_DEVELOPMENT.md) - `docker-compose` と Gateway サポートを使用した完全なローカル開発 +- [Deployment Guide](DEPLOYMENT.md) - フルスタックデプロイ手順 +- [Agent Configuration](AGENT_CONFIGURATION.md) - エージェントパターンの設定 +- [Streaming Guide](STREAMING.md) - ストリーミングイベントの理解 diff --git a/samples/aws-specialist-agent/docs-jp/MEMORY_INTEGRATION.md b/samples/aws-specialist-agent/docs-jp/MEMORY_INTEGRATION.md new file mode 100644 index 0000000..c20463e --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/MEMORY_INTEGRATION.md @@ -0,0 +1,408 @@ +# AgentCore Memory 統合ガイド + +AWS Bedrock AgentCore Memory をエージェントに統合するための、簡潔で実践的なガイドです。 + +AgentCore は 2 種類のメモリを提供します。**short-term memory** は生の会話履歴を保存し、エージェントに最近のやり取りからのコンテキストを提供します。**Long-term memory** は AI による戦略を用いて、セッション要約、ユーザーの好み、重要な事実といった意味のあるインサイトを抽出・保存し、エージェントが時間とともに深い理解を構築できるようにします。詳細は [Amazon Bedrock AgentCore Memory blog post](https://aws.amazon.com/blogs/machine-learning/amazon-bedrock-agentcore-memory-building-context-aware-agents/) を参照してください。 + +--- + +## Long-Term Memory の有効化 + +short-term memory(STM — セッション内の生の会話履歴)と long-term memory(LTM — セッションを跨いで思い出せる事実)の両方が利用可能です。LTM は **`strands-single-agent`** パターンでサポートされ、`SemanticMemoryStrategy` を使用して会話から事実を自動的に抽出・保存します。`actorId` はユーザーの検証済み JWT `sub`(Runtime がアイデンティティ伝搬のために抽出するのと同じ識別子)であるため、ユーザーごとに固有の永続メモリが提供されます。 + +### 動作の仕組み + +1. **インフラストラクチャ**: CDK スタックは常にメモリリソース上に `SemanticMemoryStrategy` を作成します(戦略を定義するだけではコストは発生しません)。`USE_LONG_TERM_MEMORY` 環境変数が agent runtime に渡されます。 +2. **エージェントの動作**: `USE_LONG_TERM_MEMORY` が `"true"` の場合、Strands エージェントの `AgentCoreMemorySessionManager` は、ターンごとに `/facts/{actorId}` 名前空間から読み取る `retrieval_config` を伴って構成されます。`"false"`(デフォルト)の場合は、short-term の会話履歴のみが有効になります。 +3. **事実抽出**: AgentCore は会話イベントを非同期に処理し、事実情報(例: 「ユーザーは Seattle に住んでいる」、「ユーザーは Python を好む」)を抽出します。これらの事実は `/facts/{actorId}` の下に保存され、以降のターンで取得されてレスポンスをパーソナライズします。 + +### 構成 + +`infra-cdk/config.yaml` で LTM を切り替えます。 + +```yaml +backend: + use_long_term_memory: true # long-term semantic memory 取得を有効化 +``` + +その後、再デプロイします。 + +```bash +cd infra-cdk && cdk deploy --all +``` + +### コストに関する考慮事項 + +LTM は short-term memory に加えて追加料金が発生します。 + +- **Storage**: $0.75 per 1,000 memory records stored +- **Retrieval**: $0.50 per 1,000 retrieval calls + +`use_long_term_memory` が `false` の場合はどちらの料金も発生せず、short-term memory(会話履歴)のみが有効な機能となります。 + +--- + +## ステップ 1: CDK によるメモリの構成 + +メモリリソースは [CloudFormation L1 constructs](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-bedrockagentcore-memory.html) を使用して作成します。**L2 constructs は将来のリリースで提供される予定です。** + +### 基本的なメモリ(Short-Term のみ) + +```typescript +const memory = new cdk.CfnResource(this, "AgentMemory", { + type: "AWS::BedrockAgentCore::Memory", + properties: { + Name: "MyAgentMemory", + EventExpiryDuration: 7, // 保持日数(7-365) + MemoryStrategies: [], // 空 = short-term のみ + MemoryExecutionRoleArn: executionRole.roleArn, + }, +}) +``` + +### 高度なメモリ(戦略あり) + +```typescript +const memory = new cdk.CfnResource(this, "AgentMemory", { + type: "AWS::BedrockAgentCore::Memory", + properties: { + Name: "MyAgentMemory", + EventExpiryDuration: 30, + Description: "Memory with intelligent extraction", + MemoryStrategies: [ + { + SummaryMemoryStrategy: { + Name: "SessionSummarizer", + Namespaces: ["/summaries/{actorId}/{sessionId}"], + }, + }, + { + UserPreferenceMemoryStrategy: { + Name: "PreferenceLearner", + Namespaces: ["/preferences/{actorId}"], + }, + }, + { + SemanticMemoryStrategy: { + Name: "FactExtractor", + Namespaces: ["/facts/{actorId}"], + }, + }, + ], + MemoryExecutionRoleArn: executionRole.roleArn, + }, +}) +``` + +### 必要な IAM 権限 + +```typescript +new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "bedrock-agentcore:CreateEvent", + "bedrock-agentcore:GetEvent", + "bedrock-agentcore:ListEvents", + "bedrock-agentcore:RetrieveMemoryRecords", + ], + resources: [memoryArn], +}) +``` + +**権限の内訳:** + +- `CreateEvent`、`GetEvent`、`ListEvents`: short-term memory(会話履歴)用 +- `RetrieveMemoryRecords`: long-term memory(戦略から得られる要約、好み、事実)用 + +### メモリ構成の理解 + +詳細な構成内容については、[AgentCore Memory Overview](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html) および [Memory API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/welcome.html) を参照してください。 + +**メモリパラメータ** + +- **EventExpiryDuration**: 7-365 日 +- **MemoryStrategies**: short-term の場合は空、long-term の場合は配列 +- **actor_id**: ユーザー識別子 +- **session_id/thread_id**: 会話識別子 + +**メモリ戦略** + +| 戦略 | 目的 | 名前空間 | +| ------------------------------ | -------------------- | ---------------------------------- | +| `summaryMemoryStrategy` | セッションを自動要約 | `/summaries/{actorId}/{sessionId}` | +| `userPreferenceMemoryStrategy` | 好みを学習 | `/preferences/{actorId}` | +| `semanticMemoryStrategy` | 事実を抽出 | `/facts/{actorId}` | + +--- + +## ステップ 2: フレームワークとの統合 + +### Strands を使用する場合 + +Strands 統合の完全なドキュメントは、[official Strands Memory Integration guide](https://strandsagents.com/latest/documentation/docs/community/session-managers/agentcore-memory/) を参照してください。 + +**インストール:** + +```bash +pip install bedrock-agentcore[strands-agents] +``` + +**コード:** + +```python +import os +from strands import Agent +from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager +from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig + +memory_id = os.environ.get("MEMORY_ID") +if not memory_id: + raise ValueError("MEMORY_ID environment variable is required") + +# 基本構成 +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id +) + +session_manager = AgentCoreMemorySessionManager( + agentcore_memory_config=config, + region_name=os.environ.get("AWS_DEFAULT_REGION", "us-east-1") +) + +agent = Agent( + system_prompt="You are a helpful assistant.", + model=bedrock_model, + session_manager=session_manager +) +``` + +**戦略を伴う場合(CDK で構成済みの場合):** + +```python +from bedrock_agentcore.memory.integrations.strands.config import RetrievalConfig + +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id, + retrieval_config={ + "/preferences/{actorId}": RetrievalConfig(top_k=5, relevance_score=0.7), + "/facts/{actorId}": RetrievalConfig(top_k=10, relevance_score=0.3) + } +) +``` + +**long-term memory を有効化した場合**(上記の [Enabling Long-Term Memory](#enabling-long-term-memory) を参照): + +`strands-single-agent` パターンは、`USE_LONG_TERM_MEMORY` 環境変数に基づいて条件付きで LTM の取得を有効にします。有効化されると、エージェントはターンごとに `/facts/{actorId}` 名前空間から事実を取得します。 + +```python +use_ltm = os.environ.get("USE_LONG_TERM_MEMORY", "false").lower() == "true" + +retrieval_config = ( + { + "/facts/{actorId}": RetrievalConfig( + top_k=10, + relevance_score=0.3, + ) + } + if use_ltm + else None +) + +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id, + retrieval_config=retrieval_config, +) +``` + +**ヒント 例:** このアプローチの実装は `agent/strands-single-agent/basic_agent.py` を参照してください。 + +**公式 AWS ガイド:** [Strands SDK Memory Integration](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/strands-sdk-memory.html) + +#### 代替案: フックベースのアプローチ + +メモリのライフサイクルやカスタムなメモリ操作をより細かく制御したい場合は、Strands hooks を MemorySession と組み合わせて使用できます。 + +```python +from strands import Agent +from strands.hooks import AgentInitializedEvent, HookProvider, HookRegistry, MessageAddedEvent +from bedrock_agentcore.memory.session import MemorySession, MemorySessionManager +from bedrock_agentcore.memory.constants import ConversationalMessage, MessageRole + +# session manager を初期化してセッションを作成 +session_manager = MemorySessionManager(memory_id=memory_id, region_name="us-east-1") +user_session = session_manager.create_memory_session( + actor_id=user_id, + session_id=session_id +) + +# カスタムフックプロバイダを作成 +class MemoryHookProvider(HookProvider): + def __init__(self, memory_session: MemorySession): + self.memory_session = memory_session + + def on_agent_initialized(self, event: AgentInitializedEvent): + """エージェント開始時に最近の会話履歴をロード""" + recent_turns = self.memory_session.get_last_k_turns(k=5) + if recent_turns: + # 整形してエージェントの context に追加 + context_messages = [] + for turn in recent_turns: + for message in turn: + role = message['role'] + content = message['content']['text'] + context_messages.append(f"{role}: {content}") + + context = "\n".join(context_messages) + event.agent.system_prompt += f"\n\nRecent conversation:\n{context}" + + def on_message_added(self, event: MessageAddedEvent): + """新しいメッセージをメモリに保存""" + messages = event.agent.messages + if messages and len(messages) > 0: + message_text = messages[-1]["content"][0]["text"] + message_role = MessageRole.USER if messages[-1]["role"] == "user" else MessageRole.ASSISTANT + + self.memory_session.add_turns( + messages=[ConversationalMessage(message_text, message_role)] + ) + + def register_hooks(self, registry: HookRegistry): + registry.add_callback(MessageAddedEvent, self.on_message_added) + registry.add_callback(AgentInitializedEvent, self.on_agent_initialized) + +# メモリフックを伴うエージェントを作成 +agent = Agent( + system_prompt="You are a helpful assistant.", + model=bedrock_model, + hooks=[MemoryHookProvider(user_session)], + tools=[...] +) +``` + +**使用するケース:** カスタムなメモリロードロジック、複数のフックの組み合わせ、細かな制御が必要な場合に使用します。 + +**ヒント 完全な例:** 動作するコードサンプルについては、[AWS AgentCore Samples repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) を参照してください。例えば、この [Strands with hooks tutorial](https://github.com/awslabs/amazon-bedrock-agentcore-samples/blob/main/01-tutorials/04-AgentCore-memory/01-short-term-memory/01-single-agent/with-strands-agent/) があります。 + +### LangGraph を使用する場合 + +LangGraph 統合の完全なドキュメントは [official LangGraph Memory Integration guide](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-integrate-lang.html) を参照してください。動作するコードサンプルについては、[LangChain AWS Integration samples](https://github.com/langchain-ai/langchain-aws/tree/main/samples/memory) を参照してください。 + +**インストール:** + +```bash +pip install langgraph-checkpoint-aws langchain-mcp-adapters +``` + +**Gateway tools を伴う完全な統合:** + +```python +from langchain_aws import ChatBedrock +from langgraph.prebuilt import create_react_agent +from langgraph_checkpoint_aws import AgentCoreMemorySaver +from langchain_mcp_adapters.client import MultiServerMCPClient + +# memory checkpointer を構成 +checkpointer = AgentCoreMemorySaver( + memory_id=memory_id, + region_name="us-east-1" +) + +# Bedrock model を作成 +bedrock_model = ChatBedrock( + model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + temperature=0.1, + streaming=True +) + +# Gateway tools 用の MCP クライアントを作成 +mcp_client = MultiServerMCPClient({ + "gateway": { + "transport": "streamable_http", + "url": gateway_url, + "headers": { + "Authorization": f"Bearer {access_token}" + } + } +}) + +# Gateway からツールを読み込む +tools = await mcp_client.get_tools() + +# memory とツールを伴うエージェントを作成 +graph = create_react_agent( + model=bedrock_model, + tools=tools, + checkpointer=checkpointer +) + +# actor と session を指定して invoke +config = { + "configurable": { + "thread_id": session_id, + "actor_id": user_id + } +} + +# レスポンスをストリーム +async for event in graph.astream( + {"messages": [("user", "Hello")]}, + config=config, + stream_mode="messages" +): + message_chunk, metadata = event + # ストリーミングチャンクを処理 +``` + +**Long-term memory (Store):** + +```python +from langgraph_checkpoint_aws import AgentCoreMemoryStore +from langchain_core.runnables import RunnableConfig +import uuid + +store = AgentCoreMemoryStore(MEMORY_ID, region_name="us-west-2") + +def pre_model_hook(state, config: RunnableConfig, *, store): + """抽出のためにメッセージを保存""" + actor_id = config["configurable"]["actor_id"] + thread_id = config["configurable"]["thread_id"] + namespace = (actor_id, thread_id) + + messages = state.get("messages", []) + for msg in reversed(messages): + if isinstance(msg, HumanMessage): + store.put(namespace, str(uuid.uuid4()), {"message": msg}) + break + + return {"llm_input_messages": messages} + +graph = create_react_agent( + model=llm, + tools=tools, + checkpointer=checkpointer, + store=store, + pre_model_hook=pre_model_hook +) +``` + +--- + +## 追加リソース + +詳細情報やコミュニティサポートについては以下を参照してください。 + +- **Community Slack**: `#bedrock-agentcore-memory-interest` +- **All Code Examples**: [AgentCore Samples Repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) + +## Design notes + +本派生プロジェクト固有の設計判断: + +- **長期記憶 (LTM) をデモで有効化**: Memory リソースには常に semantic memory strategy をプロビジョンし、Runtime の env var `USE_LONG_TERM_MEMORY` で `AgentCoreMemorySessionManager` を retrieval 付きで構築するかを切り替える。LTM 常時保持の追加コストは小さく、「セッションを跨いで記憶する」というデモ価値の方が大きいと判断した。 +- **意味検索を補うメタ想起ツール**: ベクトル類似度では「過去に何を話したか」のようなメタ質問にヒットしない。クエリ無しで Memory レコードを列挙する `list_long_term_memories` ツールを別途用意し、エージェント自身の履歴に関するメタ質問に答えられるようにしている。 diff --git a/samples/aws-specialist-agent/docs-jp/REPLACING_COGNITO.md b/samples/aws-specialist-agent/docs-jp/REPLACING_COGNITO.md new file mode 100644 index 0000000..b9550bb --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/REPLACING_COGNITO.md @@ -0,0 +1,841 @@ +# Cognito の置き換え: Identity Provider の交換と Gateway Interceptors ガイド + +このドキュメントでは、FAST AgentCore アーキテクチャにおいて Amazon Cognito を別の Identity Provider (IdP) に置き換える方法と、アクセス制御において Cedar Policy の代替もしくは補完として Gateway Interceptors を活用する方法を説明します。 + +--- + +## 1. 概要 + +### 現在のアーキテクチャの概要 + +FAST デモでは、Amazon Cognito を Identity Provider として使用しており、次のフローで動作します。 + +``` +User JWT → Runtime (validates user) → Runtime gets M2M token from Cognito → Pre-Token Lambda injects user claims into M2M token → Gateway authorizer validates M2M token → Cedar Policy Engine evaluates user claims → allows/denies tool access → Target Lambda receives tool input only +``` + +**このドキュメントは、次の 2 つの問いに答えます。** Gateway がアクセス制御の実施のためにユーザーをどのように識別するか、そして異なる IdP でこれをどのように実現するか、です。 + +### 2 つのアプローチ + +| アプローチ | 利用するタイミング | +| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| **アプローチ A: IdP を交換し、Cedar Policy を維持する** | 新しい IdP がトークンの拡張 (発行時に M2M トークンへカスタムクレームを注入する機能) をサポートしている場合 | +| **アプローチ B: Gateway Interceptors** | 新しい IdP がトークンの拡張をサポートしていない場合、または完全に動的なアクセス制御が必要な場合 | + +--- + +## 2. アプローチ A — IdP を交換し、Cedar Policy を維持する + +**対象:** トークンの拡張をサポートする IdP (Okta (Token Inline Hooks)、Auth0 (Actions)、Entra ID (Claims Mapping Policies) など)。 + +### 2a. 仕組み + +アーキテクチャは現在の Cognito フローと同じままです。Cognito 固有のコンポーネントは新しい IdP の同等機能に置き換えられます。 + +``` +User JWT → Runtime (validates user via new IdP's OIDC) → Runtime gets M2M token from new IdP's token endpoint → IdP's token enrichment hook injects user claims (replaces Pre-Token Lambda) → Gateway authorizer validates M2M token (via new IdP's OIDC discovery URL) → Cedar Policy Engine evaluates user claims → allows/denies (UNCHANGED) → Target Lambda receives tool input only (UNCHANGED) +``` + +**重要なポイント:** AgentCore Gateway の CUSTOM_JWT authorizer は **IdP に依存しません**。AWS 公式ドキュメントには次のように記載されています。 + +> "The inbound authorizer is Identity Provider (IdP) agnostic and works with any OAuth 2.0 compatible identity provider." + +有効な OIDC discovery URL のみが必要であり、Gateway はあらゆる発行者からのトークンを検証します。 + +### 2b. 変更が必要な箇所 (コンポーネントマッピング) + +| コンポーネント | 現在 (Cognito) | 新しい構成 (サードパーティ IdP) | +| ------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Gateway authorizer の discovery URL | `https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration` | `https://{your-idp}/.well-known/openid-configuration` | +| Token endpoint | `POST https://{cognito_domain}/oauth2/token` | IdP の token endpoint (例: `https://{okta_domain}/oauth2/v1/token`) | +| トークン拡張へのアイデンティティ伝播 | `aws_client_metadata: {"verified_user_id": "..."}` (Cognito 固有) | IdP 固有の仕組み (下記参照) | +| トークン拡張の仕組み | Pre-Token Lambda (V3、Cognito トリガー) | IdP のネイティブフック (下記参照) | +| Cedar Policy | **変更なし** — 引き続き `principal.getTag("department")` などを参照 | **変更なし** | +| Target Lambda | **変更なし** — ツール入力のみを受け取る | **変更なし** | + +### IdP 固有のトークン拡張の仕組み + +| IdP | トークン拡張機能 | アイデンティティの伝播方法 | +| ------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| **Okta** | Token Inline Hooks | フックがクライアントコンテキストを受け取り、トークンリクエストに渡されたカスタムパラメータに基づいて拡張可能 | +| **Auth0** | Actions (post-client-credentials) | Actions は M2M アプリケーションに紐づくメタデータを参照したり、カスタム `audience` パラメータを使用可能 | +| **Entra ID** | Claims Mapping Policies | アプリケーションロールとクレームマッピング。グループメンバーシップを直接トークンに含めることが可能 | +| **Cognito** | Pre-Token Lambda (V3) | `aws_client_metadata` が Lambda へ user_id を渡す | + +### 2c. よくある懸念点 + +**Q: LLM はトークンを参照することがありますか?** + +ありません。トークンは Python エージェントコードと MCP クライアントライブラリが管理する HTTP トランスポート層に存在します。LLM はツールスキーマとツールの実行結果のみを操作し、HTTP ヘッダーやトークンへのアクセスは持ちません。 + +**Q: IdP が M2M トークンに任意のクレームを注入できない場合はどうしますか?** + +次のセクションで説明するアプローチ B (Gateway Interceptors) を使用してください。 + +--- + +## 3. アプローチ B — Gateway Interceptors (トークン拡張不要) + +**対象:** OIDC 準拠の任意の IdP。トークン拡張フックを持たないものでも対応可能です。完全に動的なアクセス制御が必要な場合 (実行時に変化する権限がデータベースに格納されている場合など) にも有用です。 + +### 3a. 仕組み + +M2M トークンをユーザークレームで拡張する代わりに、Runtime はユーザーアイデンティティを **別のカスタムヘッダー** として渡します。Gateway Interceptor Lambda がこのヘッダーを読み取り、アクセス制御の判断を下します。 + +Runtime が Gateway へユーザーアイデンティティを渡す方法は 2 つあります。 + +#### オプション 1: ユーザー ID のみを渡す + +Runtime は検証済みのユーザー JWT から user_id を取り出し、プレーンな文字列ヘッダーとして渡します。 + +``` +User JWT → Runtime (validates user JWT via any IdP's OIDC) + → Runtime extracts user_id from validated JWT + → Runtime gets a plain M2M token from IdP (no user claims needed) + → Runtime sends to Gateway: + - Authorization: Bearer ← proves machine trust + - X-User-Id: alice@company.com ← carries user identity (plain string) + → Gateway authorizer validates M2M token only (machine trust) + → Request Interceptor Lambda fires: + - Reads X-User-Id from custom header + - Looks up user's permissions (from IdP groups, DB, YAML, etc.) + - Allows or denies the tools/call request + → Response Interceptor Lambda fires (for tools/list): + - Same permission lookup + - Filters the tool list to only show permitted tools + → Target receives tool input only (no tokens, no headers) +``` + +**Runtime 側のコード:** + +```python +# エージェントコード内 (AgentCore Runtime で実行) +user_id = extract_user_id_from_context(context) # 検証済みユーザー JWT から取得 +m2m_token = await get_m2m_token() # ユーザークレームを含まないプレーンな M2M + +mcp_client = MCPClient( + gateway_url=GATEWAY_URL, + headers={ + "Authorization": f"Bearer {m2m_token}", # マシンの信頼性 + "X-User-Id": user_id, # ユーザーアイデンティティ (プレーン文字列) + } +) +``` + +**Interceptor 側の読み取り:** + +```python +def lambda_handler(event, context): + """Request interceptor: プレーン文字列ヘッダーからユーザーアイデンティティを読み取る。""" + gateway_request = event['mcp']['gatewayRequest'] + headers = gateway_request.get('headers', {}) + + # シンプルな文字列抽出 — JWT のデコードは不要 + user_id = headers.get('X-User-Id', '') + + # このユーザーの権限を検索 (DB、YAML、IdP API など) + permissions = get_user_permissions(user_id) + # ... 権限に基づいて許可または拒否 +``` + +| 利点 | 欠点 | +| -------------------------------------------- | --------------------------------------------------------------------------------------- | +| シンプル — interceptor で JWT デコードが不要 | Gateway 境界の信頼に依存する | +| 低レイテンシ — 署名検証なし | interceptor がユーザー属性を取得するために外部ソース (DB、IdP API) を呼び出す必要がある | +| データの露出が最小限 | interceptor 内で user_id が真正であることを検証できない | + +**利用するタイミング:** Gateway 境界が信頼境界であり、(M2M トークンで検証された) 正規の Runtime のみがこのヘッダーを設定できる場合。 + +--- + +#### オプション 2: ユーザー JWT 全体を渡す + +Runtime は元のユーザー JWT を別のヘッダーとして渡します。これにより、interceptor は **JWT 署名を検証** して改ざんされていないことを証明し、すべてのユーザークレームを直接抽出できます。 + +``` +User JWT → Runtime (validates user JWT via any IdP's OIDC) + → Runtime keeps the original user JWT + → Runtime gets a plain M2M token from IdP (no user claims needed) + → Runtime sends to Gateway: + - Authorization: Bearer ← proves machine trust + - X-User-Token: ← carries full user identity (verifiable) + → Gateway authorizer validates M2M token only (machine trust) + → Request Interceptor Lambda fires: + - Reads X-User-Token from custom header + - Verifies the JWT signature (using IdP's public keys from JWKS endpoint) + - Extracts claims: user_id, email, groups, department, etc. + - Decides allow/deny based on claims + → Response Interceptor Lambda fires (for tools/list): + - Same JWT verification and claim extraction + - Filters the tool list based on user's claims + → Target receives tool input only (no tokens, no headers) +``` + +**Runtime 側のコード:** + +```python +# エージェントコード内 (AgentCore Runtime で実行) +user_jwt = get_user_jwt_from_request(context) # 元のユーザー JWT (Runtime の authorizer で検証済み) +m2m_token = await get_m2m_token() # ユーザークレームを含まないプレーンな M2M + +mcp_client = MCPClient( + gateway_url=GATEWAY_URL, + headers={ + "Authorization": f"Bearer {m2m_token}", # マシンの信頼性 + "X-User-Token": user_jwt, # 完全なユーザー JWT (検証可能) + } +) +``` + +**Interceptor 側の読み取りと検証:** + +```python +import jwt +import requests +from functools import lru_cache + +# IdP から取得した JWKS (公開鍵) をキャッシュ +@lru_cache(maxsize=1) +def get_jwks(jwks_url): + """IdP から JWKS 公開鍵を取得してキャッシュする。""" + response = requests.get(jwks_url) + return response.json() + +def lambda_handler(event, context): + """Request interceptor: ユーザー JWT を検証してクレームを抽出する。""" + gateway_request = event['mcp']['gatewayRequest'] + headers = gateway_request.get('headers', {}) + + # 完全なユーザー JWT を抽出 + user_token = headers.get('X-User-Token', '') + + if not user_token: + return deny_request("No user token provided") + + # JWT 署名を検証してクレームを抽出 + try: + # IdP の JWKS エンドポイントから公開鍵を取得 + jwks = get_jwks(IDP_JWKS_URL) # 例: https://the-idp/.well-known/jwks.json + + # JWT をデコードして検証 + claims = jwt.decode( + user_token, + jwks, + algorithms=["RS256"], + audience=EXPECTED_AUDIENCE, + issuer=EXPECTED_ISSUER + ) + except jwt.ExpiredSignatureError: + return deny_request("User token expired") + except jwt.InvalidTokenError as e: + return deny_request(f"Invalid user token: {e}") + + # 検証済みクレームから直接ユーザー属性を抽出 + user_id = claims.get('sub', '') + department = claims.get('department', '') + groups = claims.get('groups', []) # Cognito の場合は 'cognito:groups' + role = claims.get('role', 'viewer') + + # 検証済みクレームに基づいてアクセス制御を判断 + tool_name = gateway_request['body'].get('params', {}).get('name', '') + + if not is_authorized(user_id, department, groups, role, tool_name): + return deny_request(f"User {user_id} ({department}) not authorized for {tool_name}") + + # 認可済み — そのまま通過 + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": gateway_request + } + } +``` + +| 利点 | 欠点 | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | +| 暗号学的に検証可能 — interceptor が JWT の真正性を証明できる | より複雑 — interceptor 内で JWT ライブラリと JWKS の取得が必要 | +| すべてのユーザークレームが直接利用可能 (外部参照不要) | レイテンシがやや高い (署名検証 + JWKS キャッシュ) | +| 多層防御 — M2M トークンが侵害されてもユーザー JWT も有効である必要がある | ヘッダーサイズが大きい (完全な JWT 対 シンプルな文字列) | +| Runtime 境界が完全に信頼されていない場合でも動作する | トークンの有効期限を扱う必要がある (ユーザー JWT が M2M トークンより先に期限切れする可能性) | + +**利用するタイミング:** 多層防御が必要な場合 (2 つの独立した検証)、外部サービスを呼び出さずに interceptor 内で複数のユーザークレームが必要な場合、もしくはセキュリティ要件として Gateway レベルでユーザーアイデンティティの暗号学的証明が求められる場合。 + +--- + +#### 比較: オプション 1 とオプション 2 + +| 観点 | オプション 1: X-User-Id (文字列) | オプション 2: X-User-Token (完全な JWT) | +| ------------------------------ | --------------------------------------------------------------- | ----------------------------------------------------------- | +| **セキュリティモデル** | Gateway 境界を信頼する (M2M トークンで呼び出し元の正当性を証明) | 独立して検証する (JWT 署名でユーザーアイデンティティを証明) | +| **interceptor が受け取るもの** | プレーンな user_id 文字列 | すべてのクレームを含む完全な JWT | +| **interceptor の複雑さ** | シンプル — ヘッダーを読み、権限を検索するだけ | より複雑 — JWT を検証し、クレームを抽出 | +| **必要となる外部参照** | あり — ユーザー属性のために DB/IdP に問い合わせる必要 | なし — クレームは JWT 内に存在 | +| **レイテンシ** | より低い (暗号処理なし) + 検索時間 | より高い (暗号検証あり) だが検索は不要 | +| **トークン有効期限の懸念** | なし (単なる文字列) | 必要 — ユーザー JWT が期限切れする可能性 | +| **なりすましリスク** | 低い (正規の Runtime のみが Gateway に到達可能) | なし (JWT 署名は暗号学的証明) | +| **適している用途** | 信頼された Runtime 境界を持つ内部システム | 高セキュリティ環境、ゼロトラストアーキテクチャ | + +--- + +**重要なポイント (両オプションに共通):** M2M トークンとユーザーアイデンティティは **別々の関心事** です。 + +- **M2M トークン** (`Authorization` ヘッダー内) → Runtime が正規の呼び出し元であることを証明 → Gateway authorizer によって検証 +- **ユーザーアイデンティティ** (`X-User-Id` または `X-User-Token` ヘッダー内) → リクエストが誰のためのものかを interceptor に伝える → アクセス制御判断に使用 + +interceptor は、Pre-Token Lambda と Cedar Policy Engine がユーザーアイデンティティをチェックする役割の **両方** を置き換えます。Gateway authorizer は引き続き M2M トークンを検証 (呼び出しが正規であることを保証) しますが、アクセス制御判断は Cedar Policy から Interceptor Lambda に移ります。 + +> **補足:** どちらのオプションでも、LLM がトークンやヘッダーにアクセスすることはありません。これらはエージェントの Python コードと MCP クライアントライブラリが管理する HTTP トランスポート層にのみ存在します。LLM はツールスキーマとツールの実行結果のみを操作します。 + +### 3b. Request Interceptor (`tools/call` を制御) + +request interceptor は target Lambda が実行される **前** に発火します。ツール呼び出しを許可するか拒否するかを判断します。 + +**フロー:** + +``` +Agent calls tools/call → Gateway → Request Interceptor Lambda → (if allowed) → Target +``` + +**動作内容:** + +1. カスタムヘッダー (例: `X-User-Id`) からユーザーアイデンティティを抽出する +2. 呼び出されているツールを特定する +3. ユーザーが権限を持っているかを確認する (スコープ、DB 検索、YAML など) +4. **認可されている場合** → リクエストをターゲットに通過させる +5. **認可されていない場合** → 構造化された MCP エラーを返し、ターゲットは実行されない + +**サンプルコード:** + +```python +def lambda_handler(event, context): + """Request interceptor: tools/call のアクセスを制御する。""" + gateway_request = event['mcp']['gatewayRequest'] + + # カスタムヘッダーからユーザーアイデンティティを抽出 + headers = gateway_request.get('headers', {}) + user_id = headers.get('X-User-Id', '') + + # 呼び出されているツールを特定 + tool_name = gateway_request['body'].get('params', {}).get('name', '') + target = gateway_request.get('target', '') + + # 権限を検索 (DB、YAML、IdP groups など) + if not check_tool_authorization(user_id, tool_name, target): + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": { + "statusCode": 403, + "body": { + "error": { + "code": "UNAUTHORIZED", + "message": f"User {user_id} is not authorized to call {tool_name}" + } + } + } + } + } + + # 認可済み — ターゲットへ通過 + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": gateway_request + } + } + + +def check_tool_authorization(user_id, tool, target): + """ユーザーがこのツールを呼び出す権限を持つかチェックする。 + + データベースへの問い合わせ、YAML 設定の読み込み、IdP API の呼び出しなどが可能。 + """ + user_scopes = get_user_scopes(user_id) # DB または IdP から取得 + if target in user_scopes: + return True + return f"{target}:{tool}" in user_scopes +``` + +### 3c. Response Interceptor (`tools/list` を制御) + +response interceptor はターゲットが応答した **後** に発火します。エージェントがユーザーに認可されたツールのみを参照するように、ツールリストをフィルタリングします。 + +**フロー:** + +``` +Agent calls tools/list → Gateway → Target returns ALL tools → Response Interceptor → Filtered list +``` + +**動作内容:** + +1. ターゲットからツールリスト全体を受け取る +2. レスポンスペイロードのヘッダーからユーザーアイデンティティを抽出する +3. 各ツールについて、ユーザーが認可されているかを確認する +4. 許可されたツールのみを含むよう変換されたレスポンスを返す + +**サンプルコード:** + +```python +def lambda_handler(event, context): + """Response interceptor: tools/list の結果をフィルタリングする。""" + # gateway response と Authorization ヘッダーを抽出 + gateway_response = event['mcp']['gatewayResponse'] + auth_header = gateway_response['headers'].get('Authorization', '') + + # ユーザーアイデンティティを抽出 (透過したカスタムヘッダーから) + user_id = gateway_response['headers'].get('X-User-Id', '') + + # gateway response からツールを取得 + tools = gateway_response['body']['result'].get('tools', []) + # structuredContent も確認 (セマンティック検索のレスポンス用) + if not tools: + tools = gateway_response['body']['result'].get('structuredContent', {}).get('tools', []) + + # ユーザー権限を検索してツールをフィルタリング + user_scopes = get_user_scopes(user_id) # DB、YAML、IdP などから取得 + filtered_tools = filter_tools_by_scope(tools, user_scopes) + + # フィルタリング済みツールを含む変換済みレスポンスを返す + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayResponse": { + "statusCode": 200, + "headers": {"Authorization": auth_header}, + "body": { + "result": {"tools": filtered_tools} + } + } + } + } + + +def filter_tools_by_scope(tools, allowed_scopes): + """ユーザーの許可スコープに基づきツールをフィルタリングする。""" + filtered_tools = [] + for tool in tools: + target, action = tool['name'].split('___') + # ユーザーがターゲット全体へのアクセス、または特定ツールへのアクセスを持つか確認 + if target in allowed_scopes or f"{target}:{action}" in allowed_scopes: + filtered_tools.append(tool) + return filtered_tools +``` + +### 3d. CDK 設定 + +```typescript +import { LambdaInterceptor } from "aws-cdk-lib/aws-bedrockagentcore" + +// Request interceptor — ターゲットの前に発火 +const requestInterceptor = LambdaInterceptor.forRequest(requestInterceptorLambda, { + passRequestHeaders: true, // 必須: カスタムヘッダー (X-User-Id など) の読み取りを有効化 +}) + +// Response interceptor — ターゲット応答の後に発火 +const responseInterceptor = LambdaInterceptor.forResponse(responseInterceptorLambda, { + passRequestHeaders: true, // 必須: レスポンスペイロード内でヘッダーの読み取りを有効化 +}) + +// Gateway にアタッチ +const gateway = new Gateway(this, "MyGateway", { + // ... その他の設定 + interceptors: [requestInterceptor, responseInterceptor], +}) +``` + +**重要:** `passRequestHeaders: true` は必須です。デフォルトでは、セキュリティ上の理由 (ヘッダーには機密性のあるクレデンシャルが含まれる可能性があるため) からヘッダーは interceptor へ転送されません。明示的にオプトインする必要があります。 + +### 3e. セキュリティとよくある懸念点 + +**Q: LLM がユーザートークンに触れることはありますか?** + +ありません。トークンとユーザーアイデンティティは完全に HTTP トランスポート層に存在します。 + +``` +┌─────────────────────────────────────────────────────────┐ +│ AgentCore Runtime │ +│ │ +│ ┌──────────────────┐ ┌───────────────────────────┐ │ +│ │ Agent Code │ │ LLM (Bedrock) │ │ +│ │ (Python) │◄──►│ │ │ +│ │ │ │ Only sees: │ │ +│ │ Has access to: │ │ - Tool schemas │ │ +│ │ - User JWT │ │ - Tool results │ │ +│ │ - M2M token │ │ - Conversation text │ │ +│ │ - HTTP headers │ │ │ │ +│ └────────┬─────────┘ └───────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────┐ │ +│ │ MCP Client │ ← handles HTTP transport │ +│ │ (tokens, headers │ (invisible to LLM) │ +│ │ live here) │ │ +│ └──────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +LLM は「ツール X を入力 Y で呼び出す」と指示するだけで、トークンやヘッダーを含むすべての HTTP 通信は MCP クライアントが処理します。 + +**Q: 何が何を検証するのですか?** + +| コンポーネント | 検証内容 | 目的 | +| ------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------- | +| Gateway Authorizer (CUSTOM_JWT) | IdP からの M2M トークン | 「私を呼び出しているのは正規の Runtime か?」(マシンの信頼性) | +| Request Interceptor Lambda | X-User-Id ヘッダーからのユーザーアイデンティティ | 「このユーザーはこのツールの利用を許可されているか?」(アクセス制御) | + +これらは順次動作します。 + +1. authorizer が最初に発火 → M2M トークンが無効ならリクエストは拒否 (401) +2. interceptor が次に発火 → ユーザーが権限を持たなければリクエストは拒否 (403) +3. ターゲットが最後に発火 → ツール入力のみを受け取る (トークンもユーザーコンテキストもなし) + +**Q: interceptor と Cedar Policy は共存できますか?** + +はい。両者は補完的な役割を果たします。 + +- **Cedar Policy** は静的・宣言的なルール (例: 「finance 部門は財務ツールへアクセス可能」) を扱う +- **Interceptors** は動的なケース (例: 実行時に変化する DB 内の権限) を扱う + +両方が有効な場合の評価順序は次のとおりです。 + +1. Gateway authorizer がトークンを検証 +2. (設定されていれば) Cedar Policy Engine が評価 +3. interceptor が発火 (request はターゲット前、response はターゲット後) + +リクエストはすべてのチェックを通過する必要があります。 + +--- + +## 4. Cedar Policy 対 Interceptors — どちらをいつ使うか + +| 機能 | Cedar Policy | Gateway Interceptors | +| --------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------ | +| フィルタリングが行われる場所 | Policy Engine (組み込み、マネージド) | Lambda コード | +| チェックする内容 | JWT クレーム (タグ) + context.input | ユーザーアイデンティティ (ヘッダーから) + 任意の外部ソース (DB、YAML、IdP API) | +| 動的か? | 静的ルール (変更にはポリシーの再デプロイが必要) | 完全に動的 (Lambda は実行時に何でも問い合わせ可能) | +| ツールフィルタリング (tools/list) | PartiallyAuthorizeActions により自動 | response interceptor で実装 | +| ツール実行 (tools/call) | AuthorizeAction により自動 | request interceptor で実装 | +| トークン拡張が必要か? | 必要 — クレームが JWT に含まれている必要がある | 不要 — ヘッダーを読み、外部ソースを問い合わせ可能 | +| 入力バリデーション | 可能 — `context.input.amount < 1000` | 可能 — Lambda はリクエストペイロード全体にアクセス可能 | +| スキーマ変換 / PII マスキング | 不可 | 可能 — interceptor がリクエスト/レスポンスを変換可能 | +| マルチテナント分離 | 限定的 (クレームベースのみ) | 完全な柔軟性 (テナント DB へ問い合わせ可能) | +| コード不要 | はい — 宣言的な Cedar ポリシー | いいえ — Lambda コードが必要 | + +### ガイダンス + +- **Cedar Policy を使うとき:** アクセスルールが頻繁に変化しないユーザー属性 (department、role) に基づいており、コードを書かずにシンプルかつ監査可能で宣言的なポリシーを使いたい場合。 +- **Interceptors を使うとき:** 権限が動的 (DB に保存されている)、認可判断のために外部サービスを呼び出す必要がある、スキーマ変換や PII マスキングが必要、または IdP がトークン拡張をサポートしていない場合。 +- **両方を使うとき:** Cedar が基本ルール (例: 「finance のみが財務ツールへアクセス可能」) を扱い、interceptor がエッジケース (例: 「ただしメンテナンス中は除く」「特定テナントのみ」) を扱う場合。 + +--- + +## 5. Cognito User Groups と Cedar Policy の連携 + +ハードコードされたマッピングではなくネイティブの Cognito グループを活用したい、Cognito を継続して利用するチーム向けの内容です。 + +### 5a. Cognito User Groups とは何か + +Cognito User Groups は、ユーザーを論理的なグループ (例: finance、engineering、admin) に整理するための組み込み機能です。次の機能を提供します。 + +- ロール、部門、アクセスレベル別にユーザーを分類する手段 +- ユーザー認証トークンへのグループ名の自動的な含有 (`cognito:groups` クレーム) +- グループごとの IAM ロール関連付け (AWS リソースアクセス用) + +**ユーザーがグループに割り当てられる方法:** + +| 方法 | 発生タイミング | ユースケース | +| -------------------------------- | -------------------------------- | ------------------------------------------ | +| AWS Console | 管理者の手動操作 | アドホックなグループ管理 | +| AdminAddUserToGroup API | プログラム経由 (例: Lambda 内) | 登録時の自動割り当て | +| Post-Confirmation Lambda Trigger | ユーザーがメール確認した後に自動 | 新規ユーザーへのデフォルトグループ割り当て | +| Admin SDK / CLI | バッチ処理 | 一括ユーザー管理 | + +**例: 登録時にグループを自動割り当て (Post-Confirmation Lambda):** + +```python +import boto3 + +cognito = boto3.client('cognito-idp') + +def lambda_handler(event, context): + """Post-Confirmation トリガー: 新規ユーザーをデフォルトグループへ割り当てる。""" + user_pool_id = event['userPoolId'] + username = event['userName'] + + # メールドメインなどのロジックに基づきデフォルトグループを割り当て + email = event['request']['userAttributes'].get('email', '') + + if email.endswith('@finance.company.com'): + group = 'finance' + elif email.endswith('@eng.company.com'): + group = 'engineering' + else: + group = 'general' + + cognito.admin_add_user_to_group( + UserPoolId=user_pool_id, + Username=username, + GroupName=group + ) + + return event +``` + +### 5b. 課題: グループは M2M トークンに含まれない + +`cognito:groups` クレームは、ユーザー認証トークン (Authorization Code フロー) には自動的に含まれます。しかし、M2M トークン (Client Credentials フロー) には含まれません。 + +現在のアーキテクチャでは Gateway が M2M トークンを受け取るため、Cedar Policy はネイティブにユーザーのグループを参照できません。 + +``` +User Auth Token (has groups): M2M Token (NO groups): +{ { + "sub": "alice", "sub": "machine-client-id", + "cognito:groups": [ "scope": "gateway/read gateway/write", + "finance", "token_use": "access" + "admin" // No user context! + ] } +} +``` + +### 5c. 解決策: Pre-Token Lambda がグループを読み取る + +現在の実装では、Runtime が検証済みの access token からユーザーの `cognito:groups` を読み取り、`aws_client_metadata` 経由で Pre-Token Lambda に渡すため、Lambda は API 呼び出しを必要としません。access token にグループメンバーシップが含まれない IdP に切り替える場合は、以下のように Lambda が Cognito の `AdminListGroupsForUser` などの API を呼び出してグループを取得し、カスタムクレームとして M2M トークンに注入することもできます。 + +```python +import boto3 +import os +import logging + +logger = logging.getLogger() +cognito = boto3.client('cognito-idp') + +USER_POOL_ID = os.environ.get('USER_POOL_ID') + +def lambda_handler(event, context): + """V3 Pre-Token Lambda: 実際の Cognito グループ情報を M2M トークンに注入する。""" + trigger_source = event.get("triggerSource", "") + + # M2M (Client Credentials) フローのみ処理 + if trigger_source != "TokenGeneration_ClientCredentials": + return event + + # Runtime から渡された検証済み user_id を取得 + client_metadata = event.get("request", {}).get("clientMetadata", {}) + verified_user_id = client_metadata.get("verified_user_id", "") + + if not verified_user_id: + logger.warning("No verified_user_id in clientMetadata") + return event + + # --- ハードコードされたマッピングを置き換え --- + # ユーザーの実際の Cognito グループを取得 + user_groups = get_user_groups(verified_user_id) + + # グループから department と role を決定 + department = resolve_department(user_groups) + role = resolve_role(user_groups) + + # M2M アクセストークンに注入 + event["response"]["claimsAndScopeOverrideDetails"] = { + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "user_id": verified_user_id, + "department": department, + "role": role, + "user_groups": ",".join(user_groups), # 例: "finance,admin" + } + } + } + + return event + + +def get_user_groups(username): + """Admin API 経由でユーザーの Cognito グループを取得する。""" + try: + response = cognito.admin_list_groups_for_user( + UserPoolId=USER_POOL_ID, + Username=username, + ) + return [group['GroupName'] for group in response.get('Groups', [])] + except Exception as e: + logger.error("Failed to fetch groups for user %s: %s", username, str(e)) + return [] + + +def resolve_department(groups): + """グループメンバーシップから department を解決する。""" + if 'finance' in groups: + return 'finance' + elif 'engineering' in groups: + return 'engineering' + return 'guest' + + +def resolve_role(groups): + """グループメンバーシップから role を解決する。""" + if 'admin' in groups: + return 'admin' + elif 'developer' in groups: + return 'developer' + return 'viewer' +``` + +### 5d. グループを使った Cedar Policy の例 + +グループ情報がクレームとして注入されると、Cedar はそれをアクセス制御に利用できます。 + +**オプション 1: department を確認する (グループから解決)** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"finance-target___generate_report", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" +}; +``` + +**オプション 2: role を確認する (グループから解決)** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"admin-target___delete_records", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("role") && + principal.getTag("role") == "admin" +}; +``` + +**オプション 3: `like` を用いて生のグループメンバーシップを確認する** + +```cedar +// user_groups claim contains: "finance,admin,reporting" +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"finance-target___view_reports", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_groups") && + principal.getTag("user_groups") like "*finance*" +}; +``` + +> **カンマ区切りグループに対する `like` の注意点:** パターン `like "*finance*"` は "refinance" という名前のグループにもマッチしてしまいます。厳密な一致のためには `like "finance,*"`、`like "*,finance,*"`、`like "*,finance"` のようなパターンを利用するか、より望ましい方法として Lambda 内で boolean に事前解決してください (オプション 4 参照)。 + +**オプション 4: 事前解決した boolean (複雑なグループロジックには推奨)** + +Pre-Token Lambda 内でグループメンバーシップをシンプルな boolean クレームに解決します。 + +```python +claims["is_finance_team"] = "true" if "finance" in user_groups else "false" +claims["is_admin"] = "true" if "admin" in user_groups else "false" +claims["can_delete"] = "true" if "admin" in user_groups and "finance" in user_groups else "false" +``` + +そして Cedar 側では次のように記述します。 + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"admin-target___delete_records", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("is_admin") && + principal.getTag("is_admin") == "true" +}; +``` + +このアプローチでは複雑なグループロジックを Lambda に閉じ込め、Cedar ポリシーはシンプルで読みやすい状態を保てます。 + +--- + +## 6. 進化のパス + +現在のデモアーキテクチャから派生する 3 つのパスのまとめです。 + +``` +Current Demo (Cognito + hardcoded user mapping in Pre-Token Lambda) + │ + ├── Path 1: Keep Cognito, use real groups + │ ├── Replace hardcoded mapping with AdminListGroupsForUser + │ ├── Cedar Policy checks group-based claims + │ └── See: Section 5 + │ + ├── Path 2: Swap IdP that supports token enrichment + │ ├── Replace Cognito with Okta/Auth0/Entra + │ ├── Use IdP's native token hook (replaces Pre-Token Lambda) + │ ├── Cedar Policy remains unchanged + │ └── See: Section 2 + │ + └── Path 3: Swap to any IdP + Gateway Interceptors + ├── No token enrichment needed + ├── Interceptors handle all access control dynamically + ├── Works with ANY OIDC-compliant IdP + └── See: Section 3 +``` + +--- + +## 7. FAQ / クイックリファレンス + +**Q1: AgentCore Gateway は任意の IdP で利用できますか?** + +はい。Gateway の CUSTOM_JWT authorizer は OAuth 2.0 / OIDC 準拠の任意のアイデンティティプロバイダーで動作します。有効な OIDC discovery URL (`.well-known/openid-configuration`) のみが必要です。Gateway はこれを利用して動的に公開鍵を取得し、トークンを検証します。 + +**Q2: IdP を交換するための最小限の変更は何ですか?** + +新しい IdP がトークン拡張をサポートしている場合は次の通りです。 + +1. Gateway authorizer の discovery URL を変更 +2. Runtime のトークンエンドポイント呼び出しを変更 +3. Pre-Token Lambda を IdP のネイティブなトークンフックに置き換える +4. その他 (Cedar Policy、ターゲット) はそのまま + +**Q3: interceptor は Cedar Policy を置き換えますか?** + +置き換えることは可能ですが、必須ではありません。interceptor と Cedar Policy は補完的な役割を持ちます。 + +- **Cedar Policy** = JWT クレームに基づく静的・宣言的・コード不要のルール +- **Interceptors** = 外部ソースを問い合わせられる動的・コードベースのロジック + +どちらか単独でも、多層防御のために両方を併用することも可能です。 + +**Q4: アプローチ A と B のどちらを選ぶか?** + +- **アプローチ A を選ぶケース** IdP がトークン拡張をサポートしており、アクセスルールが (department/role などのユーザー属性に基づく) 比較的静的な場合 +- **アプローチ B を選ぶケース** IdP がトークン拡張をサポートしていない、もしくは動的な権限・スキーマ変換・PII マスキング・マルチテナント分離が必要な場合 + +**Q5: Cedar Policy と interceptor を同時に使えますか?** + +はい。両方が有効な場合は次の通りです。 + +1. Gateway authorizer がまずトークンを検証 +2. 次に Cedar Policy Engine が評価 (設定されていれば) +3. interceptor が発火 (request はターゲット前、response はターゲット後) + +リクエストはすべてのチェックを通過する必要があります。これにより多層防御を実現できます。 + +**Q6: Cognito user groups は Cedar から利用できますか?** + +はい。`cognito:groups` クレームは M2M トークン自体には現れませんが、ユーザーの access token には含まれます。Runtime がそこから読み取り、`aws_client_metadata` 経由で Pre-Token Lambda に渡し、Lambda が結果の `department`/`role` をカスタムクレームとして注入します — `AdminListGroupsForUser` の呼び出しは不要です。詳細とコード例はセクション 5 を参照してください。 + +**Q7: カスタムヘッダー経由でのユーザーアイデンティティの受け渡しは安全ですか?** + +はい、その理由は次の通りです。 + +1. Gateway authorizer が最初に M2M トークンを検証する (呼び出し元が正規の Runtime であることを証明) +2. カスタムヘッダー (`X-User-Id`) は (`passRequestHeaders: true` を設定した) interceptor Lambda のみが読み取れる +3. LLM は HTTP ヘッダーへアクセスできない — それらはトランスポート層にのみ存在する + +セキュリティ境界は Gateway レベルで強制されます。誰かが (有効な M2M トークンなしで) 直接 Gateway を呼び出そうとした場合、interceptor が発火する前に authorizer がそれを拒否します。 diff --git a/samples/aws-specialist-agent/docs-jp/RUNTIME_GATEWAY_AUTH.md b/samples/aws-specialist-agent/docs-jp/RUNTIME_GATEWAY_AUTH.md new file mode 100644 index 0000000..beb7d49 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/RUNTIME_GATEWAY_AUTH.md @@ -0,0 +1,591 @@ +# AgentCore M2M 認証ワークフロー + +**AgentCore Runtime <--> OAuth Provider <--> Cognito <--> AgentCore Gateway** + +このドキュメントでは、AgentCore Runtime が OAuth2 Credential Provider(AgentCore Identity が管理)を使用して Cognito M2M トークンを取得し、AgentCore Gateway へのリクエストを認証するための完全なワークフローを説明します。内容は **Deployment**(インフラのセットアップ)と **Runtime**(実行時のトークンとリクエストフロー)の 2 フェーズに分かれています。 + +## 背景: 2 つの Secret + +認証ワークフローでは 2 つの Secret が登場します。 + +**Secret 1:** `//machine_client_secret` + +- 作成元: CDK (`secretsmanager.Secret`) + +**Secret 2:** `bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth` + +- 作成元: デプロイ時の `oauth2ProviderLambda` Custom Resource + +Secret 1 は `secretsmanager.Secret` で作成され、Cognito machine client が生成した secret から値が投入されます。Secret 2 は `oauth2ProviderLambda` がデプロイ時に `secretsmanager:CreateSecret` および `secretsmanager:PutSecretValue` を使って作成します。 + +**補足:** `bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth` という名前空間は、デフォルトの Token Vault における OAuth2 認証情報用の AgentCore Identity の規約であり、本スタックの実装に由来しています。 + +## 背景: 3 つの IAM Role + +1. **AgentCoreRole** + - 作成元: CDK construct `createAgentCoreRuntime()` + - 引き受け元: AgentCore Runtime + +2. **GatewayRole** + - 作成元: `createAgentCoreGateway()` + - 引き受け元: AgentCore Gateway サービス + +3. **oauth2ProviderLambda Role** + - 作成元: CDK(Lambda 関数用に自動生成) + - 引き受け元: `oauth2ProviderLambda` 関数 + +`GatewayRole` は trust principal として `new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com")` を使用します。 + +## 重要: User Auth と M2M Auth は別物 + +このスタックには相互に独立した 2 つの認証フローが存在します。両者は同じ Cognito User Pool を使いますが、用途は異なります。 + +**Flow 1 -- Human User --> AgentCore Runtime(Runtime への inbound):** + +- 人間向けの Cognito app client (`userPoolClientId`) を使用 +- User Pool の discovery URL を指す `RuntimeAuthorizerConfiguration.usingJWT(...)` で Runtime に設定 +- Authorization Code grant を使用(フロントエンド経由の人間によるログイン) +- ユーザーの JWT トークン(`sub` クレームを含む)は Runtime に渡され、許可リスト化された `Authorization` ヘッダー (`requestHeaderConfiguration`) 経由でエージェントコードから利用可能になります + +**Flow 2 -- AgentCore Runtime --> AgentCore Gateway(M2M、Runtime からの outbound):** + +- `machineClient` を使用 — `clientCredentials: true` および `generateSecret: true` を持つ別の Cognito app client +- Client Credentials grant を使用(人間ユーザーは関与しない) +- M2M トークンは AgentCore Identity の Token Vault 経由で Runtime が取得し、Gateway 呼び出しの認証に使用します +- Runtime はユーザーのアイデンティティ(および `cognito:groups`)を `aws_client_metadata` でこの M2M トークンに伝播し、Gateway の Cedar policy がユーザーごとに認可できるようにします — [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md) を参照してください + +2 つのフローはネストではなく並列の関係です。 + +``` +Human User + --> Cognito (Authorization Code, userPoolClientId) + --> User JWT token + --> AgentCore Runtime (validates via userPoolClientId authorizer) + --> Agent code runs... + --> Needs to call Gateway + --> AgentCore Identity Token Vault (Client Credentials, machineClient) + --> Cognito issues M2M JWT (machineClient.userPoolClientId) + --> AgentCore Gateway (validates via machineClient.userPoolClientId authorizer) + --> Tool Lambda +``` + +ユーザーの Cognito Pool と machine client は CDK 上の利便性の都合から同じ Cognito User Pool を共有しており、認証目的は異なります。ユーザーの検証済みアイデンティティは M2M トークンの _取得方法_(常に Token Vault 経由の Client Credentials)は変えませんが、`aws_client_metadata` 経由でトークンのクレームに _載せられ_、Gateway がユーザーごとに認可できるようにします([Identity Propagation](IDENTITY_POLICY.md) を参照)。 + +## PHASE 1: DEPLOYMENT WORKFLOW + +このフェーズは `cdk deploy` 時に一度だけ実行されます。目的は、Runtime が実行時に利用できるように OAuth2 Credential Provider を AgentCore Identity に登録することです。 + +### Step D1 -- Cognito M2M インフラのセットアップ + +CDK が M2M 認証に必要な Cognito リソースをプロビジョニングします。 + +**作成されるリソース:** + +- `UserPoolResourceServer` -- 識別子 `-gateway` のもとで API スコープ(read、write)を定義 +- `UserPoolClient`(Machine Client)-- `clientCredentials: true` および `generateSecret: true` を持つ confidential app client +- `secretsmanager.Secret`(Secret 1)-- machine client の `client_id` と `client_secret` を保存 + +**アクティブな IAM Role:** なし(CDK CloudFormation の execution role がプロビジョニングを担う) + +**データフロー:** + +``` +CDK CloudFormation + --> Creates Cognito User Pool Resource Server + --> Identifier: -gateway + --> Scopes: read, write + --> Creates Cognito Machine Client + --> Grant type: CLIENT_CREDENTIALS + --> generateSecret: true --> Cognito generates client_id + client_secret + --> Stores client_secret in Secrets Manager as Secret 1 + --> Path: //machine_client_secret +``` + +**理由:** Machine Client は Cognito から M2M トークンを要求する際に使用される OAuth2 アイデンティティです。Resource Server はそれらのトークンが有効となるスコープを定義します。Secret 1 は次のステップで使用される認証情報を保管する、CDK 管理下の安全なストアです。 + +### Step D2 -- Cognito JWT Authorizer 付きの AgentCore Gateway デプロイ + +CDK が `CUSTOM_JWT` authorizer を Cognito User Pool に向けて構成した状態で AgentCore Gateway (`CfnGateway`) をデプロイします。 + +**主要な構成:** + +``` +authorizerType: "CUSTOM_JWT" +authorizerConfiguration: + customJwtAuthorizer: + discoveryUrl: https://cognito-idp..amazonaws.com//.well-known/openid-configuration + allowedClients: [ machineClient.userPoolClientId ] +``` + +**アクティブな IAM Role:** GatewayRole + +**GatewayRole の権限:** + +- `lambda:InvokeFunction` on `toolLambda` -- MCP tool Lambda ターゲットを呼び出す +- `bedrock:InvokeModel`, `bedrock:InvokeModelWithResponseStream` +- `ssm:GetParameter`, `ssm:GetParameters` +- `cognito-idp:DescribeUserPoolClient` on User Pool ARN -- JWT 検証のため Cognito app client 設定を内省する +- `cognito-idp:InitiateAuth` on User Pool ARN -- トークン関連の操作のために Cognito 認証フローを開始する +- `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents` + +**データフロー:** + +``` +CDK CloudFormation + --> Creates CfnGateway + --> authorizerType: CUSTOM_JWT + --> discoveryUrl: Cognito OIDC discovery URL (for JWKS fetching at runtime) + --> allowedClients: [ machineClient.userPoolClientId ] + --> Creates CfnGatewayTarget + --> Protocol: MCP + --> Target: toolLambda ARN + --> Credential provider: GATEWAY_IAM_ROLE + --> Stores Gateway URL in SSM: //gateway_url +``` + +**理由:** `discoveryUrl` はトークン検証で信頼する Cognito User Pool を指定し、実行時に JWT 署名検証用の JWKS(公開鍵)を取得するために使用されます。`allowedClients` 制限により、machine client に発行されたトークンのみが受け入れられるようになります。 + +### Step D3 -- OAuth2 Credential Provider の登録(Custom Resource) + +`oauth2ProviderLambda`(CDK Custom Resource により駆動)が実行され、AgentCore Identity の Token Vault に OAuth2 Credential Provider を登録します。 + +**アクティブな IAM Role:** oauth2ProviderLambda execution role(CDK により自動作成) + +**oauth2ProviderLambda Role の権限:** + +- `secretsmanager:GetSecretValue` on Secret 1 (`//machine_client_secret`) -- CDK 管理ストレージから Cognito machine client の `client_id` と `client_secret` を読み取る。これらの認証情報は信頼の起点であり、AgentCore Identity に登録するためにはまず読み出す必要があります。 +- `secretsmanager:CreateSecret` on Secret 2 (`bedrock-agentcore-identity!default/oauth2/*`) -- Secrets Manager 内の AgentCore Identity 名前空間に新しい secret を作成する。この名前空間は Token Vault が実行時に認証情報を見つけることを想定する場所です。 +- `secretsmanager:PutSecretValue` on Secret 2 -- 新しく作成された AgentCore Identity 管理の secret に `client_id` と `client_secret` の値を書き込み、M2M トークン取得時に Token Vault が読み取れるようにする。 +- `secretsmanager:DescribeSecret` on Secret 2 -- 作成を試みる前に Secret 2 が既に存在するかを確認する。これによりべき等性が確保され、スタックを再デプロイしても既存の secret を再作成しようとして失敗することはなくなります。 +- `secretsmanager:DeleteSecret` on Secret 2 -- CDK スタックが破棄されるときに Secret 2 を削除する。これがないと、スタック削除後も AgentCore Identity 管理の secret が Secrets Manager に取り残されてしまいます。 +- `bedrock-agentcore:CreateOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- Token Vault に OAuth2 Credential Provider を登録し、provider 名 (`-runtime-gateway-auth`) を Cognito の discovery URL、`client_id`、Secret 2 に紐付ける。これは Provider を実行時に Runtime から利用可能にするコアな登録ステップです。 +- `bedrock-agentcore:GetOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- 登録後に Provider が正常に作成されたことを確認する。再デプロイ時にも Provider が既に存在するかを作成前に確認する用途で使用されます(べき等性)。 +- `bedrock-agentcore:DeleteOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- CDK スタックが破棄されるときに Token Vault から Provider 登録を削除する。これがないと、スタック削除後も Provider のエントリが Token Vault に残り続けてしまいます。 +- `bedrock-agentcore:CreateTokenVault` on `token-vault/default` and `token-vault/default/*` -- Provider 登録を試みる前に default Token Vault が存在することを保証する。Token Vault がまだ作成されていない場合、この権限により Lambda が前提条件として作成できます。 +- `bedrock-agentcore:GetTokenVault` on `token-vault/default` and `token-vault/default/*` -- 操作の前に default Token Vault のステータスを確認する。Provider 登録の前に Vault が利用可能かつ準備完了であることを確認するために使用されます。 +- `bedrock-agentcore:DeleteTokenVault` on `token-vault/default` and `token-vault/default/*` -- 必要に応じてスタック破棄時に Token Vault をクリーンアップする。完全な解体シナリオに備えた防御的なクリーンアップ権限です。 + +**データフロー:** + +``` +oauth2ProviderLambda + | + |-- 1. Reads Secret 1 + | --> Gets: client_id, client_secret + | + |-- 2. Creates Secret 2 in Secrets Manager + | --> Namespace: bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth + | --> Stores: { client_id, client_secret } + | + |-- 3. Calls bedrock-agentcore:CreateOauth2CredentialProvider + | --> Provider name: -runtime-gateway-auth + | --> discoveryUrl: Cognito OIDC discovery URL + | --> clientId: machineClient.userPoolClientId + | --> secretArn: Secret 2 ARN + | --> grantType: CLIENT_CREDENTIALS (M2M / 2LO) + | + └-- 4. Provider is now registered in Token Vault (default) + --> Provider ARN: arn:aws:bedrock-agentcore:::token-vault/default/oauth2credentialprovider/-runtime-gateway-auth +``` + +**理由:** この登録ステップにより、論理的な provider 名 (`GATEWAY_CREDENTIAL_PROVIDER_NAME` 環境変数) と実際の Cognito OAuth2 構成が結び付けられ、Runtime が AgentCore Identity を介して M2M トークンを取得できるようになります。 + +### Step D4 -- AgentCore Runtime のデプロイ + +CDK が AgentCoreRole と環境変数(`GATEWAY_CREDENTIAL_PROVIDER_NAME` を含む)を伴って AgentCore Runtime をデプロイします。 + +**アクティブな IAM Role:** AgentCoreRole + +**AgentCoreRole の権限(M2M 関連):** + +- `bedrock-agentcore:GetOauth2CredentialProvider` on `oauth2-credential-provider/*` -- 論理名(`GATEWAY_CREDENTIAL_PROVIDER_NAME` の値)から登録済みの OAuth2 Credential Provider のメタデータを参照する。これは実行時に `@requires_access_token` デコレーター内部で呼ばれ、Provider 名を Cognito トークン URL、`client_id`、および Secret 2 への参照に解決します。この権限がないとデコレーターはトークン取得プロセスを開始できません。 +- `bedrock-agentcore:GetResourceOauth2Token` on `token-vault/*` -- Token Vault から M2M アクセストークンを要求する。これは実行時に Cognito JWT を取得するための主要な権限です。Token Vault はキャッシュされた有効なトークンを返すか、Client Credentials grant を使って Cognito から新しいトークンを取得します。 +- `bedrock-agentcore:GetResourceOauth2Token` on `workload-identity-directory/*` -- 呼び出し元の IAM アイデンティティ (AgentCoreRole) を AgentCore Identity ディレクトリに登録された Workload Identity に解決する。Token Vault はこの Workload Identity を使ってキャッシュされたトークンを当該エージェント固有にスコープし、同じ IAM ロールを共有していてもエージェント間でトークンが分離されるようにします。 +- `secretsmanager:GetSecretValue` on Secret 2 (`bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth`) -- IAM 委譲のために必要: Token Vault が Cognito から新しいトークンを取得する必要があるとき(cache miss)、AgentCore Identity は自身のサービスロールではなく Runtime の IAM ロールで Secret 2 を読み取ります。つまり Runtime は Secret 2 への `GetSecretValue` を持つ必要がありますが、これは Runtime が直接読み取るからではなく、AgentCore Identity が Runtime のロールを代行して認証情報にアクセスするためです。この設計は権限昇格を防ぎます: 呼び出し元は AgentCore Identity を踏み台として、本来直接アクセス権を持たない secret に到達することはできません。 +- `secretsmanager:GetSecretValue` on Secret 1 (`//machine_client_secret`) -- 防御的/テスト用途のみ。標準的な Token Vault フローでは Secret 2 へのアクセスのみで十分です(IAM 委譲経由)。Secret 1 へのアクセスは、Runtime またはその内部で動作するテストスクリプトが Token Vault フロー外で直接 Cognito トークンエンドポイントを呼び出す必要があるときのために用意されています。標準の M2M パスでは不要です。 +- `ssm:GetParameter`, `ssm:GetParameters` on `//*` -- 実行時に Gateway URL (`//gateway_url`) やその他の構成値を SSM Parameter Store から読み取る。Gateway URL は `create_gateway_mcp_client()` が呼ばれた際に一度だけ取得され、以降の MCP 接続すべてで使用されます。 + +**データフロー:** + +``` +CDK CloudFormation + --> Creates AgentCore Runtime + --> Assigns AgentCoreRole as execution role + --> Sets environment variables: + GATEWAY_CREDENTIAL_PROVIDER_NAME = -runtime-gateway-auth + STACK_NAME = + MEMORY_ID = + --> Configures inbound JWT authorizer (for human users calling the Runtime): + discoveryUrl: Cognito User Pool discovery URL + allowedAudiences: [ userPoolClientId ] <-- human-facing app client, NOT machine client + --> Configures requestHeaderConfiguration: + allowlistedHeaders: [ "Authorization" ] <-- so agent code can read the user's JWT +``` + +**`GATEWAY_CREDENTIAL_PROVIDER_NAME` 環境変数を使う理由:** エージェントコードを特定の Provider ARN から切り離すためです。エージェントコードは論理名さえ知っていればよく、Provider 構成の解決は AgentCore Identity が行います。これにより、エージェントコードは環境やスタックを跨いでポータブルになります。 + +## PHASE 2: RUNTIME WORKFLOW + +このフェーズは、エージェントコードが AgentCore Gateway を呼び出す必要があるたびに実行されます。目的は、有効な Cognito M2M トークンを取得し、それを使って Gateway リクエストを認証することです。 + +### Step R1 -- MCP Client の作成とトークン取得のセットアップ + +`create_gateway_mcp_client()` が呼び出され、Gateway 通信用の MCP クライアントをセットアップします。Gateway URL はこの時点で SSM から一度だけ読み取られます(安定しており変化しないため)。トークン取得は lambda factory 内に遅延され、MCP の接続および再接続のたびに最新のものが取得されます。 + +**アクティブな IAM Role:** AgentCoreRole + +**使用される権限:** `ssm:GetParameter` on `//*`(Gateway URL の参照のため) + +**データフロー:** + +``` +create_gateway_mcp_client() is called + | + |-- 1. Reads STACK_NAME from env var + | + |-- 2. Reads Gateway URL from SSM: + | ssm:GetParameter --> //gateway_url + | <-- Returns: Gateway URL (stable, fetched once) + | + └-- 3. Creates MCPClient with lambda factory: + MCPClient( + lambda: streamablehttp_client( + url=gateway_url, + headers={"Authorization": f"Bearer {_fetch_gateway_token()}"} + ), + prefix="gateway", + ) + --> Token is NOT fetched here -- deferred into the lambda + --> _fetch_gateway_token() will be called on every MCP connection/reconnection +``` + +**lambda factory パターンの理由:** 「クロージャの罠」を避けるためです。`_fetch_gateway_token()` を lambda の外で呼び出すと、Python のクロージャはクライアント作成時のトークン値をキャプチャしてしまいます。そのトークンは 60 分間有効です。MCP クライアントが期限切れ後に再接続すると、キャプチャされた古いトークンを使用して Gateway から 401 を受け取ることになります。lambda 内部で `_fetch_gateway_token()` を呼ぶことで、すべての MCP 接続時に最新のトークンが取得されます。Token Vault のキャッシュ層(`@requires_access_token` デコレーター内)により、これは効率的です -- トークンがまだ有効ならキャッシュされたものが Cognito を呼ばずに即座に返されます。 + +**SSM 読み取りが lambda の外にある理由:** Gateway URL は接続ごとに変化しない安定したインフラエンドポイントです。再接続のたびではなく、クライアント作成時に一度読み取るのが安全かつ効率的です。 + +### Step R2 -- @requires_access_token デコレーターによるトークン取得 + +MCP の接続または再接続のたびに lambda factory が実行され、`_fetch_gateway_token()` が呼び出されます。`@requires_access_token` デコレーター(AgentCore Identity Python SDK の一部)がこの呼び出しを横取りし、すべての OAuth メカニクスを内部で処理します。 + +**アクティブな IAM Role:** AgentCoreRole + +**データフロー:** + +``` +Lambda factory executes (on each MCP connection/reconnection) + --> _fetch_gateway_token() is called + --> @requires_access_token decorator intercepts: + --> provider_name = GATEWAY_CREDENTIAL_PROVIDER_NAME env var + --> auth_flow = "M2M" (Client Credentials grant) + --> scopes = [] (Cognito embeds scopes based on machine client authorization) + --> custom_parameters = {"aws_client_metadata": json({verified_user_id, verified_groups})} + (Pre-Token Lambda 向けのユーザーアイデンティティ -- Identity Propagation guide 参照) + --> Decorator calls AgentCore Identity API internally (see R3 for details) + --> Decorator injects the obtained JWT as access_token argument + --> _fetch_gateway_token(access_token=) returns the JWT string + --> MCPClient uses JWT in Authorization: Bearer header +``` + +**auth_flow="M2M" と scopes=[] の理由:** `auth_flow="M2M"` はデコレーターに対し、Client Credentials grant(ユーザーの介在なし)を使うよう指示します。`scopes=[]` は M2M では正しい設定です -- スコープ (`-gateway/read`、`-gateway/write`) は、machine client が認可されている内容に基づき Cognito によってトークンに埋め込まれるため、呼び出し側で指定する必要はありません。 + +**デコレーター内部に関する補足:** `@requires_access_token` デコレーターは AgentCore Identity Python SDK の一部です。その内部のトークン取得は、後述の R3 で説明する 2 段階のサブステップで構成されます。これはエージェントコードからは見えません -- エージェント側から見ると、デコレーターはトークンを返す単一の呼び出しに過ぎません。 + +### Step R3 -- デコレーター内部: Token Vault からのトークン取得 + +`@requires_access_token` デコレーターの内部で、AgentCore Identity は 2 段階のサブステップでトークンを取得します。AgentCore Identity は Token Vault にキャッシュされた有効なトークンがあるかを確認し、なければ、または期限切れの場合は Cognito から新しいトークンを取得します。 + +**アクティブな IAM Role:** AgentCoreRole + +**使用される権限:** + +- `bedrock-agentcore:GetOauth2CredentialProvider` on `oauth2-credential-provider/*` -- Provider メタデータの参照(サブステップ 3a) +- `bedrock-agentcore:GetResourceOauth2Token` on `token-vault/*` -- Token Vault からのトークン取得(サブステップ 3b) +- `bedrock-agentcore:GetResourceOauth2Token` on `workload-identity-directory/*` -- トークンスコープのための Workload Identity 解決(サブステップ 3b) +- `secretsmanager:GetSecretValue` on Secret 2 -- client_secret アクセスのための IAM 委譲(cache miss 時のみ、サブステップ 3b) + +**データフロー -- サブステップ 3a: Provider メタデータの参照:** + +``` +@requires_access_token decorator (SDK internals) + --> bedrock-agentcore:GetOauth2CredentialProvider + --> Input: provider_name = -runtime-gateway-auth + --> AgentCore Identity looks up the provider registered in Token Vault (Step D3) + <-- Returns: + --> Provider ARN + --> Cognito token URL (derived from discoveryUrl registered in D3) + --> client_id (machineClient.userPoolClientId) + --> Reference to Secret 2 (where client_secret lives) + --> Grant type: CLIENT_CREDENTIALS +``` + +**データフロー -- サブステップ 3b: Token Vault チェックとトークン発行:** + +**Cache Hit(トークンがまだ有効):** + +``` + --> bedrock-agentcore:GetResourceOauth2Token + --> Resolves Workload Identity from AgentCoreRole ARN via workload-identity-directory + --> Checks Token Vault: token for this Workload Identity + provider = VALID (< 60 min old) + <-- Returns: cached JWT access token (no Cognito call needed) +``` + +**Cache Miss(トークン未取得または期限切れ):** + +``` + --> bedrock-agentcore:GetResourceOauth2Token + --> Resolves Workload Identity from AgentCoreRole ARN via workload-identity-directory + --> Checks Token Vault: no valid token found + --> Reads Secret 2 using AgentCoreRole (IAM delegation) + --> Gets: client_secret + --> Calls Cognito token endpoint: + POST https://.auth..amazoncognito.com/oauth2/token + grant_type=client_credentials + client_id= + client_secret= + scope=-gateway/read -gateway/write + aws_client_metadata= + --> Cognito が V3 Pre-Token Lambda を呼び出し、グループを読み取って + department/role クレームを注入(Identity Propagation guide 参照) + <-- Cognito returns: JWT access token (valid 60 minutes by default) + --> Stores token in Token Vault, scoped to this Workload Identity + <-- Returns: new JWT access token +``` + +**Workload Identity が重要な理由:** Token Vault はトークンを IAM ロール単位ではなく Workload Identity 単位で保存します。つまり、2 つの異なるエージェントランタイムが同じ IAM ロールを共有していたとしても、Vault 内では別々のトークンエントリを持ちます。Workload Identity は、エージェント間でのトークン分離を保証する、エージェントレベルのきめ細かなプリンシパルです。 + +**Secret 2 で IAM 委譲を行う理由:** AgentCore Identity は自身のサービスロールではなく、呼び出し元の IAM ロール (AgentCoreRole) を使って Secret 2 を読み取ります。これは意図的なセキュリティ設計です: secret にアクセスできるのは、呼び出し元が `GetResourceOauth2Token` API 権限と Secret 2 への `secretsmanager:GetSecretValue` 権限の両方を持つ場合に限ります。これにより権限昇格を防ぎます -- 呼び出し元は AgentCore Identity を踏み台にして、自身が直接読む権限を持たない secret にアクセスすることはできません。 + +**Token Vault のキャッシュにより lambda factory が効率的になる理由:** `_fetch_gateway_token()` は MCP 接続のたびに呼ばれますが、Token Vault のキャッシュにより、Cognito が呼ばれるのはトークン期限切れ時(およそ 60 分ごと)だけです。それ以外の呼び出しはすべてキャッシュされたトークンを即座に返すため、このパターンは「常に最新」と「効率」の両方を実現します。 + +### Step R4 -- エージェントコードが Gateway へリクエストを送信 + +MCPClient は `_fetch_gateway_token()` から返された JWT を使い、AgentCore Gateway に対して認証付きの HTTP リクエストを送信します。 + +**アクティブな IAM Role:** AgentCoreRole + +**データフロー:** + +``` +MCPClient lambda factory + --> streamablehttp_client( + url=gateway_url, <-- read from SSM in Step R1, stable + headers={"Authorization": f"Bearer {jwt}"} <-- fresh token from Step R2/R3 + ) + --> Sends HTTP request: + POST /mcp + Authorization: Bearer + Content-Type: application/json + { ... MCP tool invocation payload ... } +``` + +**理由:** Gateway URL は安定しており(R1 で一度だけ読み取り)、トークンは最新(R2/R3 で接続ごとに取得)です。`Authorization: Bearer` ヘッダーは、保護されたリソースへトークンを提示する OAuth2 の標準的なメカニズムです。 + +### Step R5 -- Gateway が JWT トークンを検証 + +AgentCore Gateway がリクエストを受信し、`CUSTOM_JWT` authorizer が Bearer トークンを検証します。 + +**アクティブな IAM Role:** GatewayRole + +**データフロー:** + +``` +AgentCore Gateway (CUSTOM_JWT authorizer) + | + |-- 1. Extracts Bearer token from Authorization header + | + |-- 2. Fetches JWKS from Cognito discovery URL: + | GET https://cognito-idp..amazonaws.com//.well-known/openid-configuration + | --> Gets JWKS URI + | GET + | --> Gets Cognito public keys for signature verification + | + |-- 3. Verifies JWT signature using JWKS public keys + | + |-- 4. Checks token claims: + | --> client_id claim ∈ allowedClients (machineClient.userPoolClientId) + | --> token not expired + | --> issuer matches Cognito User Pool + | + └-- 5. Authorization decision: + --> VALID --> forwards request to Gateway Target (Step R6) + --> INVALID --> returns 401 Unauthorized +``` + +**allowedClients によるスコープ制限の理由:** AgentCore Gateway は、ユーザーが作成した特定の machine client に発行されたトークンのみを受け入れます。同じ User Pool に存在する別の Cognito クライアントがトークンを取得したとしても、それは拒否されます。これは意図的な厳格なスコープ設定であり、この AgentCore Gateway を呼び出せるのは AgentCore Runtime の machine client だけになります。 + +### Step R6 -- Gateway が MCP Tool Lambda へ転送 + +検証済みのリクエストは Gateway Target(MCP tool Lambda)に転送されます。 + +**アクティブな IAM Role:** GatewayRole + +**使用される権限:** `lambda:InvokeFunction` on `toolLambda` + +**データフロー:** + +``` +AgentCore Gateway (GatewayRole) + --> lambda:InvokeFunction on toolLambda + --> MCP tool invocation payload + <-- Lambda returns tool result + <-- AgentCore Gateway returns MCP response to AgentCore Runtime + <-- MCPClient delivers result to agent code + <-- Agent code continues execution with tool result +``` + +--- + +## APPENDIX: M2M 認証の Cognito を別の IdP に置き換える + +このセクションでは、Runtime --> Gateway 間の M2M 認証フローにおいて、Cognito を別の identity provider (IdP) に置き換える方法を説明します。Client --> Runtime 間のユーザー認証フローは独立しており、変更されません(上述の「User Auth と M2M Auth は別物」を参照)。 + +### この変更の範囲 + +M2M フローは OAuth2 Client Credentials grant を使用します: Runtime は IdP に登録された `client_id` と `client_secret` で machine トークンを取得し、Gateway は IdP の JWKS を使ってそのトークンを検証します。ユーザーアイデンティティもユーザートークンもこのフローには関与しません — Runtime はユーザーの代理ではなく、自分自身として認証します。 + +**将来のユーザー委譲フローに関する補足:** このドキュメントは現行の Phase 1 実装(Runtime から Gateway へのフローが純粋な M2M、Client Credentials grant)を扱います。将来の Phase では、Runtime が認証済みユーザーの代理でトークンを取得し、Gateway がユーザーアイデンティティを含むトークンを受け取るユーザー委譲フロー(3-legged OAuth / Authorization Code grant)が導入される可能性があります。AgentCore Identity は両方のフローをサポートします。その Phase が実装される際には、Gateway authorizer の構成と `@requires_access_token` デコレーターの `auth_flow` パラメータを更新する必要があります — このセクションの IdP 置き換えガイドはどちらのフローにも適用されます。 + +このフローで Cognito を置き換えるには、2 つの信頼関係を更新する必要があります。 + +1. **AgentCore Gateway の JWT authorizer** — 新しい IdP の discovery URL を指し、新しいクライアントの `client_id` を受け入れる必要があります +2. **AgentCore Identity の OAuth2 Credential Provider** — 新しい IdP の discovery URL、`client_id`、`client_secret` を構成する必要があります + +Token Vault、`@requires_access_token` デコレーター、Workload Identity、AgentCore Runtime コードは IdP 非依存であり、変更は必要ありません。 + +### サポートされる IdP + +AgentCore Identity は OAuth2 Provider について 2 つのモードをサポートします。 + +1. **組み込みのマネージド Provider**(AWS が事前構成・保守): Amazon Cognito, Auth0 by Okta, Atlassian, CyberArk, Dropbox, Facebook, FusionAuth, GitHub, Google, HubSpot, LinkedIn, Microsoft, Notion, Okta, OneLogin, Ping Identity, Reddit, Salesforce, Slack, Spotify, Twitch, X, Yandex, Zoom + +2. **CustomOauth2**(このスタックで使用): `.well-known/openid-configuration` の discovery エンドポイントを公開している任意の OIDC 準拠 IdP。これは汎用的なパスであり、上記すべての IdP に対して機能します。 + +**Cognito が組み込みベンダーであるにもかかわらず、このスタックが CustomOauth2 を使う理由:** AgentCore Identity の組み込みベンダー `AmazonCognito` は、エージェントが人間ユーザーの代理として動作するユーザー委譲フロー(3-legged OAuth / Authorization Code grant)向けに設計されています。ここで使用する M2M Client Credentials grant ではユーザーは関与しません — Runtime は自分自身として認証します。`discoveryUrl` 付きの `CustomOauth2` は、どの IdP がバックエンドにあっても Client Credentials / M2M フローにとっては正しい選択肢であり、Cognito を標準的な OIDC Provider として扱います。この選択により、スタックは設計上 IdP 移植可能になります: 別の IdP に切り替える際には、`discoveryUrl` とクライアント認証情報の値を変更するだけでよく、Custom Lambda コードを変更する必要はありません。 + +M2M(Client Credentials grant)の場合、置き換える IdP は次をサポートする必要があります。 + +- OAuth 2.0 Client Credentials grant (`grant_type=client_credentials`) +- JWKS ベースの JWT 検証のための OIDC Discovery (`.well-known/openid-configuration`) +- クライアントアプリケーションでの設定可能なスコープ +- client secret を持つ confidential client + +ほとんどのエンタープライズ IdP はこれらをネイティブにサポートしています。例として Microsoft Entra ID、Okta、Auth0 などがあります — 進める前に、IdP のドキュメントを参照して Client Credentials grant と OIDC Discovery のサポートを確認してください。 + +### 必要な CDK スタックの修正 + +#### 1. createMachineAuthentication() — 全面置き換え + +このメソッドは Cognito 固有です。`UserPoolResourceServer`、`UserPoolClient`(machine client)、`MachineClientSecret` を作成します。これらのリソースは OAuth2 machine client のアイデンティティを確立し、要求できるスコープを定義します。別の IdP の場合は、その IdP 自身のコンソールや管理ツールで同等のセットアップを行います。 + +**このステップが後続のステップに対して提供すべき内容:** + +- **`client_id`**: machine client の OAuth2 クライアント識別子 + - 利用先: Gateway authorizer (`allowedClients`)、Custom Resource (`properties.ClientId`) +- **`client_secret`**: machine client の OAuth2 クライアントシークレット + - 利用先: Secret 1 (`//machine_client_secret`) として Secrets Manager に保存 +- **Discovery URL**: IdP の OIDC discovery エンドポイント + - 利用先: Gateway authorizer (`discoveryUrl`)、Custom Resource (`properties.DiscoveryUrl`) + +オリジナルと同じ `secretsmanager.Secret` パターンを使い、`//machine_client_secret` に `client_id` と `client_secret` を保存します。Discovery URL は静的な文字列です — 以降のステップで使えるよう、変数または SSM パラメータとして保管してください。 + +IdP 固有のセットアップ(confidential client の作成、Client Credentials grant の有効化、スコープの定義)については、IdP のドキュメントを参照してください。 + +#### 2. createAgentCoreGateway() — Discovery URL と allowedClients の値 + +**変更 A — Discovery URL 変数:** + +```typescript +// Current (Cognito): +const cognitoIssuer = `https://cognito-idp.${this.region}.amazonaws.com/${this.userPool.userPoolId}` +const cognitoDiscoveryUrl = `${cognitoIssuer}/.well-known/openid-configuration` + +// Any OIDC-compliant IdP: +const discoveryUrl = `/.well-known/openid-configuration` +``` + +**変更 B — CfnGateway authorizer 構成:** + +```typescript +authorizerConfiguration: { + customJwtAuthorizer: { + allowedClients: [], // replace machineClient.userPoolClientId + discoveryUrl: discoveryUrl, // replace cognitoDiscoveryUrl + }, +}, +``` + +**変更 C — GatewayRole から Cognito 固有の IAM 権限を削除:** + +```typescript +// Remove these — they are Cognito-specific and not needed for other IdPs: +// cognito-idp:DescribeUserPoolClient +// cognito-idp:InitiateAuth +``` + +**理由**: Gateway は実行時に `discoveryUrl` を使って IdP の JWKS を取得し、JWT 署名の検証に使用します。`allowedClients` の制限により、特定の machine client に発行されたトークンだけが受け入れられるようになります。Cognito の IAM 権限は Cognito 固有の API 呼び出しにのみ必要なものであり、他の IdP は AWS IAM 権限を必要としない標準的な OIDC/JWKS エンドポイントを使用します。 + +#### 3. Custom Resource プロパティ — 値のみ変更、Lambda コードは不要 + +`oauth2ProviderLambda` のコードは既に IdP 非依存です。汎用的な `discoveryUrl` を伴う `credentialProviderVendor="CustomOauth2"` を使用しているため、コード変更は不要です。Custom Resource に渡されるプロパティのみ変更します。 + +```typescript +const runtimeCredentialProvider = new cdk.CustomResource(this, "RuntimeCredentialProvider", { + serviceToken: oauth2Provider.serviceToken, + properties: { + ProviderName: providerName, + ClientSecretArn: this.machineClientSecret.secretArn, // same pattern, new IdP secret + DiscoveryUrl: discoveryUrl, // new IdP discovery URL + ClientId: , // new IdP client_id + }, +}) +``` + +**理由**: Lambda は渡された `DiscoveryUrl` と `ClientId` をそのまま読み取り、`CustomOauth2` プロバイダーとして AgentCore Identity に登録します。Token Vault は実行時にこれらの値を使って正しい IdP のトークンエンドポイントを呼び出します。Lambda の `handle_create`、`handle_update`、`handle_delete` ハンドラーには変更は一切不要です。 + +**組み込みベンダーに関する補足**: `CustomOauth2` の代わりに組み込みベンダー(上述「サポートされる IdP」セクションを参照)を使いたい場合は、Lambda の `handle_create` と `handle_update` で `credentialProviderVendor` を変更します。組み込みベンダーは AWS が管理するエンドポイント構成を持ち、プロバイダー固有のクセを自動的に処理する場合があります。ただし、`discoveryUrl` 付きの `CustomOauth2` はすべての OIDC 準拠 IdP に対して機能し、環境を跨いで保守しやすいです。 + +#### 4. createAgentCoreRuntime() — 変更不要 + +Runtime は `GATEWAY_CREDENTIAL_PROVIDER_NAME` 環境変数のみを把握しています。どの IdP がプロバイダーをバックアップしているかは関知しません。Token Vault が、Step D3 で登録された構成を使って IdP との対話を透過的に解決します。 + +#### 5. createCognitoSSMParameters() — Cognito 固有のパラメータ名を変更(任意) + +SSM パラメータ `cognito-user-pool-id`、`cognito-user-pool-client-id`、`cognito_provider` は名前的に Cognito 固有です。別の IdP の場合は、これらを IdP 非依存の名称(例: `idp-discovery-url`、`machine-client-id`)にリネームします。`gateway_url` と `machine_client_id` のパラメータは既に IdP 非依存です。 + +--- + +### サマリー: 何が変わり、何が変わらないか + +**変更が必要なコンポーネント:** + +- **createMachineAuthentication()** — 全面置き換えが必要 + - Cognito 固有のリソースを同等の IdP クライアントセットアップに置き換える + - 提供すべきもの: `client_id`、`client_secret`、Discovery URL +- **CfnGateway authorizerConfiguration** — 値の更新 + - `discoveryUrl` を新しい IdP の OIDC discovery エンドポイントに変更 + - `allowedClients` を新しい IdP の `client_id` に変更 +- **GatewayRole IAM 権限** — Cognito 固有の権限を削除 + - `cognito-idp:DescribeUserPoolClient` を削除 + - `cognito-idp:InitiateAuth` を削除 +- **Custom Resource プロパティ** — 値のみ更新 + - `DiscoveryUrl` を新しい IdP の discovery エンドポイントに変更 + - `ClientId` を新しい IdP の `client_id` に変更 + - `ClientSecretArn` を Secrets Manager の新しい IdP secret に向ける +- **SSM パラメータ名** — 任意のリネーム(見た目のみ) + - 必要に応じて Cognito 固有のパラメータ名を IdP 非依存の名称にリネーム + +**変更が不要なコンポーネント:** + +- **oauth2ProviderLambda コード** — 既に IdP 非依存 + - 汎用的な `CustomOauth2` ベンダーを `discoveryUrl` パラメータと共に使用 + - `handle_create`、`handle_update`、`handle_delete` のコード変更は不要 + - **任意**: Lambda コードで `credentialProviderVendor` を変更することで組み込みベンダーに切り替え可能。ただし `CustomOauth2` はすべての OIDC 準拠 IdP に対して機能する +- **createAgentCoreRuntime()** — 既に IdP 非依存 + - `GATEWAY_CREDENTIAL_PROVIDER_NAME` 環境変数のみを把握 + - Token Vault が IdP との対話を透過的に解決 +- **エージェントコード** — 既に IdP 非依存 + - IdP 非依存の `@requires_access_token` デコレーターを使用 + - 採用しているエージェントパターンに関わらずエージェントコードに変更は不要 +- **Client → Runtime ユーザー認証** — 独立したフロー + - Runtime → Gateway の M2M 認証とは別の関心事 + - M2M IdP の変更による影響を受けない diff --git a/samples/aws-specialist-agent/docs-jp/SKILLS.md b/samples/aws-specialist-agent/docs-jp/SKILLS.md new file mode 100644 index 0000000..ef0f146 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/SKILLS.md @@ -0,0 +1,19 @@ +# Skills + +このデプロイでは、キュレーション済みの「スキル」(自己記述型の機能バンドル) を S3 Files 経由で Runtime にマウントしています。モデルの system prompt を肥大化させずにドメイン知識を持たせるためのしくみです。 + +## スキルのマウント方法 + +- スキルは `skills/agent-toolkit-for-aws/` (ピン留めされた第三者コンテンツ。LICENSE/NOTICE 付きでそのまま保持) と `skills/aws-specialist-agent/` (本プロジェクト独自スキル) の配下に置く。 +- ビルドステップ (`scripts/build-project-guide.py`) がプロジェクト固有のガイドを組み立てて全体を S3 バケットにアップロードする。 +- AgentCore Runtime コンテナは S3 Files でこのバケットを既知のパスにマウントする。エージェントは必要な機能の `SKILL.md` を遅延読み込みする。 + +## Design notes + +- **イメージ同梱ではなく S3 Files**: スキルは Runtime イメージより更新頻度が高い。S3 Files でマウントすることで、Runtime を再デプロイせずにスキル更新を反映できる。 +- **submodule ではなく vendored**: 第三者スキルは当初 git submodule で取り込んでいたが、コミットハッシュをピン留めしたコピー (LICENSE 同梱) に切り替えた。サプライチェーンの予期せぬ変化を避け、submodule init 無しでもビルドできる利点がある。 +- **マウントパス vs. VPC のトレードオフ**: S3 Files は Runtime が S3 に到達できる必要がある。閉域構成では S3 (Gateway) endpoint の追加コストを払う必要があるが、スキルを別経路で更新できる柔軟性とのトレードオフで S3 Files を選択した。 +- **S3 Files 用 IAM ロールを厳格化**: Runtime がスキルを取得する execution role はスキル用 bucket/prefix に限定し、AgentCore 側の IAM 要件をデプロイ時に検証する。 +- **自己説明型プロジェクトガイド**: `skills/aws-specialist-agent/fast-project-guide/` はライブのソースツリーから (サニタイズしたうえで) 生成する。デプロイ済みエージェントが「このアプリはどう組み立てられているか」という質問に、ドキュメントではなく実コードに基づいて答えられる。 +- **公式 Code Interpreter**: 独自サンドボックスを再実装せず AgentCore 提供の Code Interpreter ツールを直接使う。フレームワーク固有のラッパ (`tools/code_interpreter/`) で選択したエージェント SDK に橋渡しする。 +- **補助 Lambda も閉域 VPC に接続**: 補助 Lambda (feedback / history / session / oauth2-provider / zip-packager / pre-token) はすべて Runtime と同じ VPC で動かし、トラフィックをプライベート経路に閉じる。各 SG は必要な endpoint だけを許可する。 diff --git a/samples/aws-specialist-agent/docs-jp/STREAMING.md b/samples/aws-specialist-agent/docs-jp/STREAMING.md new file mode 100644 index 0000000..0d3f36d --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/STREAMING.md @@ -0,0 +1,331 @@ +# エージェント向けストリーミングガイド + +## 概要 + +エージェントはストリーミングイベントを SSE 形式で送信します。このガイドでは、ストリーミングをフロントエンドと統合する方法について説明します。 + +## 統合手順 + +1. **エージェントがストリーミングイベントを送信** (SSE 形式) +2. **`agentcore-client` ライブラリ** が SSE ストリームを読み取り、適切なパーサーにルーティング: + - **Strands エージェント (デフォルト)** の場合: `frontend/src/lib/agentcore-client/parsers/strands.ts` — Strands スキーマイベントを解析 + - **LangGraph エージェント** の場合: `frontend/src/lib/agentcore-client/parsers/langgraph.ts` + - **Bedrock Converse (汎用)** の場合: `frontend/src/lib/agentcore-client/parsers/converse.ts` — 生の Bedrock Converse ストリームイベントを解析 + - **その他のエージェントフレームワーク** の場合: 新しいパーサーを作成し、`frontend/src/lib/agentcore-client/client.ts` に登録 +3. **パーサーが型付けされた `StreamEvent` を発行** (text、tool_use_start、tool_use_delta、tool_result、message、result、lifecycle) +4. **`ChatInterface.tsx`** がイベントを処理し、メッセージセグメント (テキスト + ツール呼び出しのインターリーブ) を構築 +5. **`ChatMessage.tsx`** がセグメントを Markdown フォーマットとツール呼び出しコンポーネントとともにインラインでレンダリング + +--- + +## 現在の実装 + +### バックエンド: Strands エージェント + +**ファイル:** `agent/strands-single-agent/basic_agent.py` + +バックエンドは、JSON 安全な dict にシリアライズされたすべての生の Strands ストリーミングイベントを yield します: + +```python +async for event in agent.stream_async(user_query): + yield json.loads(json.dumps(dict(event), default=str)) +``` + +**補足:** Strands イベントには JSON シリアライズできない Python オブジェクト (エージェントインスタンス、UUID、`ModelStopReason` タプルなど) が含まれることがあります。`json.dumps(default=str)` 呼び出しはこれらを文字列に変換し、すべてのイベントが SSE 経由で安全に送信できることを保証します。 + +### フロントエンド: イベントパーサー + +**ファイル:** `frontend/src/lib/agentcore-client/parsers/strands.ts` + +`strands-single-agent` のデフォルトパーサーは、Strands スキーマイベントを処理します: + +```typescript +export const parseStrandsChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return; + const json = JSON.parse(line.substring(6).trim()); + + // テキストトークン: {"data": "Hello"} + if (typeof json.data === "string") { + callback({ type: "text", content: json.data }); + } + + // ツール使用: {"current_tool_use": {...}, "delta": {"toolUse": {"input": "..."}}} + if (json.current_tool_use) { + // 最初のデルタ (空の入力) → tool_use_start + // 後続のデルタ → tool_use_delta + } + + // ツール結果: {"message": {"role": "user", "content": [{"toolResult": {...}}]}} + if (json.message?.role === "user") { + // toolResult ブロックを抽出 → callback({ type: "tool_result", ... }) + } + + // 完了: {"result": {"stop_reason": "end_turn"}} + if (json.result) { + callback({ type: "result", stopReason: "end_turn" }); + } + + // ライフサイクル: {"init_event_loop": true} + if (json.init_event_loop || json.start_event_loop) { ... } +}; +``` + +エッジケースを含む完全な実装はソースファイルを参照してください。 + +### イベント構造 + +Strands は以下のイベントタイプを提供します: + +- `data`: テキストチャンク (到着すると蓄積される) +- `current_tool_use`: ツール名、ID、入力パラメータ (ストリーミング用の `delta` を含む) +- `message`: 完全なコンテンツを持つ最終的な構造化メッセージ (`toolUse` を持つ assistant、`toolResult` を持つ user) +- `result`: 停止理由とメトリクスを持つ AgentResult +- `init_event_loop`、`start_event_loop`、`complete`: ライフサイクルマーカー +- `tool_stream_event`: ツール実行からストリーミングされるイベント +- `event`: 生の Bedrock Converse イベント (以下の代替 converse パーサーで使用) + +```javascript +// テキストストリーミング +data: {"data": "Hello"} +data: {"data": " there"} + +// ツール使用開始 — 最初のデルタは空の入力を持つ +data: {"current_tool_use": {"toolUseId": "tool_abc123", "name": "text_analysis"}, "delta": {"toolUse": {"input": ""}}} + +// ツール入力ストリーミング +data: {"current_tool_use": {"toolUseId": "tool_abc123", "name": "text_analysis"}, "delta": {"toolUse": {"input": "{\"text\": \"hello\"}"}}} + +// 完全な assistant メッセージ +data: {"message": {"role": "assistant", "content": [{"toolUse": {"toolUseId": "tool_abc123", "name": "text_analysis", "input": {"text": "hello"}}}]}} + +// ツール結果 (toolResult ブロックを持つ user メッセージ) +data: {"message": {"role": "user", "content": [{"toolResult": {"toolUseId": "tool_abc123", "content": [{"text": "Analysis complete: 1 word"}]}}]}} + +// 最終結果 +data: {"result": {"stop_reason": "end_turn"}} + +// ライフサイクルイベント +data: {"init_event_loop": true} +data: {"start_event_loop": true} +``` + +**参考:** [Strands Streaming Documentation](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/streaming/overview/) + +--- + +## 代替: 生の Converse イベントを使用する + +Strands スキーマイベントを解析する代わりに、`event` キーの下にネストされた生の Bedrock Converse イベントを解析することもできます。これにより、Converse ストリーム API 構造への低レベルアクセスが得られます。 + +**補足:** ツール結果は Converse ストリームイベントとしては発行されません — それらは次の `converse_stream` 呼び出しへの入力となります。Strands はこれを内部で処理し、ツール結果を `message` イベントとして発行します。converse パーサーはツール結果を処理しません。代わりに、`ChatInterface.tsx` は次のテキストセグメントがストリーミングを開始したときにツールを完了としてマークします。 + +### フロントエンドパーサー + +**ファイル:** `frontend/src/lib/agentcore-client/parsers/converse.ts` + +デフォルトの strands パーサーの代わりにこのパーサーを使用するには、`client.ts` を更新します: + +```typescript +import { parseConverseChunk } from "./parsers/converse"; + +const PARSERS: Record = { + "strands-single-agent": parseConverseChunk, // Converse パーサーに切り替える + ... +}; +``` + +このパーサーは生の Bedrock Converse イベントを処理します: + +```typescript +export const parseConverseChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return; + const json = JSON.parse(line.substring(6).trim()); + + const event = json.event; + if (event) { + // テキストストリーミング + if (event.contentBlockDelta?.delta?.text) { + callback({ type: "text", content: event.contentBlockDelta.delta.text }); + } + + // ツール使用開始 + if (event.contentBlockStart?.start?.toolUse) { + const toolUse = event.contentBlockStart.start.toolUse; + callback({ type: "tool_use_start", toolUseId: toolUse.toolUseId, name: toolUse.name }); + } + + // ツール使用入力ストリーミング + if (event.contentBlockDelta?.delta?.toolUse?.input) { + callback({ type: "tool_use_delta", toolUseId: currentToolUseId, input: ... }); + } + + // メッセージ停止 + if (event.messageStop?.stopReason) { + callback({ type: "result", stopReason: event.messageStop.stopReason }); + } + } +}; +``` + +エッジケースを含む完全な実装はソースファイルを参照してください。 + +### イベント構造 + +Converse イベントは `event` キーの下にネストされます: + +```javascript +// メッセージライフサイクル +data: {"event": {"messageStart": {"role": "assistant"}}} + +// テキストストリーミング +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hello"}}}} +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": " there"}}}} + +// ツール使用開始 +data: {"event": {"contentBlockStart": {"contentBlockIndex": 1, "start": {"toolUse": {"toolUseId": "tool_abc123", "name": "text_analysis"}}}}} + +// ツール使用入力ストリーミング +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 1, "delta": {"toolUse": {"input": "{\"text\": \"hello\"}"}}}}} + +// コンテンツブロックとメッセージの完了 +data: {"event": {"contentBlockStop": {"contentBlockIndex": 0}}} +data: {"event": {"messageStop": {"stopReason": "end_turn"}}} + +// メタデータ +data: {"event": {"metadata": {"usage": {"inputTokens": 88, "outputTokens": 30}}}} +``` + +**参考:** [Bedrock Converse Stream API](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse_stream.html) + +--- + +## LangGraph/LangChain 実装 + +**補足:** LangGraph はタプルベースのストリーミング `(message_chunk, metadata)` を使用し、コンテンツが配列となる LangChain メッセージオブジェクトを返します。 + +### バックエンド + +LangGraph エージェントを `messages` モードでストリーミングすると、生の LangChain メッセージチャンクが yield されます: + +```python +# messages モードでストリーミング - 生の LangChain メッセージチャンクを yield する +async for event in graph.astream( + {"messages": [("user", user_query)]}, + config=config, + stream_mode="messages" +): + message_chunk, metadata = event + yield message_chunk.model_dump() # JSON 安全な dict にシリアライズ +``` + +### イベント構造 + +LangGraph は LangChain メッセージオブジェクトを発行し、これらは **コンテンツブロックの配列** をコンテンツとして JSON にシリアライズされます: + +```javascript +// テキストストリーミング (AIMessageChunk) +data: {"content": [{"type": "text", "text": "Hello", "index": 0}], "type": "AIMessageChunk", ...} +data: {"content": [{"type": "text", "text": " there", "index": 0}], "type": "AIMessageChunk", ...} + +// ツール使用開始 — コンテンツブロックは id と name を持つ +data: {"content": [{"type": "tool_use", "id": "tool_abc123", "name": "text_analysis", "input": {}, "index": 1}], "type": "AIMessageChunk", ...} + +// ツール入力ストリーミング — partial_json が増分入力を運ぶ +data: {"content": [{"type": "tool_use", "partial_json": "{\"text\":", "index": 1}], "type": "AIMessageChunk", ...} +data: {"content": [{"type": "tool_use", "partial_json": " \"hello\"}", "index": 1}], "type": "AIMessageChunk", ...} + +// ツールレスポンス (ToolMessage — 別のメッセージタイプ) +data: {"content": "Tool result text", "type": "tool", "name": "text_analysis", "tool_call_id": "tool_abc123", ...} + +// 停止理由 +data: {"content": [], "type": "AIMessageChunk", "response_metadata": {"stop_reason": "end_turn"}, ...} + +// 使用量メタデータを含む最終チャンク +data: {"content": [], "type": "AIMessageChunk", "chunk_position": "last", "usage_metadata": {"input_tokens": 88, "output_tokens": 30}} +``` + +**現在のパーサーが処理するもの:** + +- `content[].type === "text"` を持つ `AIMessageChunk`: 表示用のテキストトークン +- `content[].type === "tool_use"` + `id` + `name` を持つ `AIMessageChunk`: ツール呼び出し開始 +- `content[].type === "tool_use"` + `partial_json` を持つ `AIMessageChunk`: ストリーミングツール入力 +- `type === "tool"` (ToolMessage): ツール実行結果 +- `response_metadata.stop_reason`: ストリーム完了 + +**Strands との主な違い:** LangGraph の `content` は常に型付けされたブロック (text、tool_use) の配列であり、フラットな文字列ではありません。ツール結果は user メッセージ内にネストされるのではなく、別の `ToolMessage` オブジェクトとして提供されます。 + +### フロントエンドパーサー + +**ファイル:** `frontend/src/lib/agentcore-client/parsers/langgraph.ts` + +同じパターンで、SSE 行を解析し、型付けされたイベントを発行します。LangGraph は LangChain メッセージタイプを使用します: + +```typescript +export const parseLanggraphChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return + const json = JSON.parse(line.substring(6).trim()) + + // ツール結果: {"type": "tool", "tool_call_id": "...", "content": "result"} + if (json.type === "tool") { + callback({ type: "tool_result", toolUseId: json.tool_call_id, result: json.content }) + } + + // AIMessageChunk — コンテンツはブロックの配列 + if (json.type === "AIMessageChunk" && Array.isArray(json.content)) { + for (const block of json.content) { + if (block.type === "text" && block.text) { + callback({ type: "text", content: block.text }) + } + if (block.type === "tool_use" && block.id && block.name) { + callback({ type: "tool_use_start", toolUseId: block.id, name: block.name }) + } + } + + // レスポンスメタデータからの停止理由 + if (json.response_metadata?.stop_reason) { + callback({ type: "result", stopReason: json.response_metadata.stop_reason }) + } + } +} +``` + +ツール入力デルタストリーミングとエッジケースを含む完全な実装を参照してください。 + +**重要なポイント:** + +- アシスタントレスポンスのみを処理するために `type === 'AIMessageChunk'` でフィルタリングする +- `ToolMessage` やその他の内部メッセージタイプは無視する +- `content` は文字列ではなく **コンテンツブロックの配列** である +- 各ブロックには `type`、`text`、`index` フィールドがある +- テキストコンテンツを抽出するために `type === 'text'` でフィルタリングする +- 複数のテキストブロックがある場合は結合する + +**コンテンツが配列である理由:** +LangChain は、Anthropic/OpenAI のメッセージ形式に従って、マルチモーダルメッセージ (テキスト、画像、ツール呼び出し) をサポートするためにコンテンツブロックを使用します。 + +**参考:** + +- [LangGraph Streaming](https://docs.langchain.com/oss/python/langgraph/streaming) +- [LangChain Streaming](https://docs.langchain.com/oss/python/langchain/streaming) + +--- + +## 新しいエージェントパターンの追加 + +1. エージェントコードを含む `agent/my-pattern/` を作成する +2. パーサーを作成する: `frontend/src/lib/agentcore-client/parsers/my-pattern.ts` + - `callback()` を介して SSE 行を `StreamEvent` に変換する `ChunkParser` 関数をエクスポートする +3. `frontend/src/lib/agentcore-client/client.ts` に登録する (コンストラクタのパーサーマップに追加) +4. `infra-cdk/config.yaml` で `pattern: my-pattern` を設定する + +--- + +## デバッグ + +パーサーでコンソールロギングを有効にします: + +```javascript +console.log("[Streaming Event]", data) +``` + +ブラウザコンソール (F12) を開いて、エージェントからのすべてのイベントを確認します。 diff --git a/samples/aws-specialist-agent/docs-jp/TOOL_AC_CODE_INTERPRETER.md b/samples/aws-specialist-agent/docs-jp/TOOL_AC_CODE_INTERPRETER.md new file mode 100644 index 0000000..949d59a --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/TOOL_AC_CODE_INTERPRETER.md @@ -0,0 +1,171 @@ +# AgentCore Code Interpreter 統合 + +このドキュメントでは、Amazon Bedrock AgentCore Code Interpreter を FAST に統合するためのアーキテクチャ上の決定について説明します。 + +## AgentCore Code Interpreter とは? + +Amazon Bedrock AgentCore Code Interpreter は、AI エージェントが分離されたサンドボックス環境でコードを安全に実行できるようにするフルマネージド機能です。主な機能: + +- コンテナ化された環境での安全なコード実行 +- 複数言語のサポート (Python、JavaScript、TypeScript) +- 一般的なライブラリを含む事前構築済みランタイム +- 状態の永続化を伴うセッション管理 +- 長い実行時間 (デフォルト 15 分、最大 8 時間) + +**ドキュメント**: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html + +## なぜ Gateway ではなく直接統合なのか? + +FAST は Code Interpreter を Gateway 経由ではなく **エージェントに直接統合** しています。理由は以下の通りです: + +### アプローチ 1: 直接統合 ✅ (採用) + +**アーキテクチャ**: `Agent → Code Interpreter SDK → Code Interpreter Service` + +**長所**: + +- **シンプルな実装** - 最小限のコード、追加のインフラ不要 +- **低レイテンシ** - Gateway/Lambda のホップなし +- **低コスト** - Lambda 呼び出しなし +- **セッション管理** - Code Interpreter が呼び出し間で状態を維持 +- **AWS のパターンに準拠** - 公式ドキュメントの例と一致 +- **より良いエラー処理** - Code Interpreter のエラーへの直接アクセス + +**短所**: + +- Gateway 経由で発見できない +- 更新にはエージェントの再デプロイが必要 +- ツールロジックがエージェントコード内に存在する + +### アプローチ 2: Gateway 統合 ❌ (不採用) + +**アーキテクチャ**: `Agent → Gateway → Lambda → Code Interpreter SDK → Code Interpreter Service` + +**長所**: + +- Gateway パターンとの一貫性 +- MCP 経由で発見可能 +- 独立したデプロイ + +**短所**: + +- **より複雑** - Lambda ラッパー + Gateway ターゲット + IAM ロール +- **より高いレイテンシ** - リクエストパスでの追加のホップ +- **より高いコスト** - Lambda 呼び出し + Code Interpreter 使用料 +- **セッションの複雑さ** - Lambda がコールドスタートをまたいでセッションを管理する必要がある +- **AWS 参照なし** - このパターンの公式な例がない +- **意図された使用例ではない** - Code Interpreter は組み込みサービスであり、カスタムツールではない + +### 決定の根拠 + +Code Interpreter は **組み込みの AgentCore サービス** であり、Bedrock モデルや AgentCore Memory に類似しています。AWS は Gateway 経由でプロキシするのではなく、直接統合のために設計しました。Gateway は組み込みサービスではなく、**カスタム Lambda ベースのツール** を対象としています。 + +**比較**: + +| Aspect | Direct | Gateway | +| ------------ | --------------------- | -------------- | +| 複雑さ | 低 | 高 | +| レイテンシ | ~100ms | ~300-500ms | +| コスト | CI のみ | Lambda + CI | +| AWS パターン | ✅ ドキュメント化済み | ❌ 例なし | +| 使用例 | 組み込みサービス | カスタムツール | + +## 実装アーキテクチャ + +エージェントは、自前のラッパーではなく公式の Strands Code Interpreter ツール +(`strands_tools.code_interpreter.AgentCoreCodeInterpreter`) を直接使用します。 +統合はエージェントのエントリポイントに完結しています: + +``` +agent/strands-single-agent/ +└── basic_agent.py # AgentCoreCodeInterpreter をインポートしツールを登録 +``` + +### 主要コンポーネント + +**エージェント統合** (`agent/strands-single-agent/basic_agent.py`): + +```python +from strands_tools.code_interpreter import AgentCoreCodeInterpreter + +# サンドボックスを会話セッションに紐づけることで、同一会話内の 2 回目以降の +# 呼び出しは新規にコールドクリエイトせず、同じ AgentCore サンドボックスへ +# 再接続する (warm reconnect)。 +code_interpreter_tool = AgentCoreCodeInterpreter( + region=region, session_name=session_id +) + +# Gateway MCP クライアントと file_read と並べてツールを登録する。 +tools = [gateway_client, code_interpreter_tool.code_interpreter, file_read] +``` + +### 設計原則 + +1. **保守されているツールを使う** - 公式の `strands_tools` Code Interpreter が + AgentCore Code Interpreter API に追従するため、同期を取り続ける自前ラッパーは不要。 +2. **セッションに紐づくサンドボックス** - `session_name` に会話の `session_id` を + 設定するため、ツールのモジュールレベルキャッシュが呼び出しをまたいで同じ + サンドボックスへ再接続する (warm reconnect)。VPC コールドスタート緩和と相乗する。 +3. **直接統合** - 上の意思決定のとおり、Gateway/Lambda を挟まず Code Interpreter + サービスへ直接アクセスする。 + +## このアーキテクチャの利点 + +1. **保守コストの低減**: 自前ラッパーが無く、API 表面は公式ツールが担う。 +2. **パフォーマンス**: 直接統合 = 低レイテンシ。warm reconnect により会話内の + 繰り返しコールドスタートを避ける。 +3. **コスト**: Lambda のオーバーヘッドなし。 +4. **シンプルさ**: AWS のドキュメント化されたパターンに従う。 + +## 使い方 + +エージェントは、ユーザーがコード実行を要求したときに自動的に Code Interpreter を使用します: + +**プロンプトの例**: + +- "Calculate the factorial of 20" +- "Create a list of the first 50 Fibonacci numbers" +- "Generate 100 random numbers and calculate statistics" + +このツールは `execute_python_securely` として登録され、組み込みの Python 実行と比較してセキュリティを強調しています。 + +## セッション管理 + +- **自動**: Code Interpreter が初回使用時にセッションを作成する +- **永続化**: セッションは複数の呼び出しをまたいで状態を維持する (`clearContext=False`) +- **クリーンアップ**: AgentCore はタイムアウト後に非アクティブなセッションを自動的にクリーンアップする +- **手動クリーンアップ**: 即時のリソース解放のために `cleanup()` メソッドでオプションで実行可能 + +## テスト + +**ローカル Docker ビルド**: + +```bash +docker build -f agent/strands-single-agent/Dockerfile -t test-agent . +docker run --rm test-agent python -c "from strands_tools.code_interpreter import AgentCoreCodeInterpreter; print('✓ Import successful')" +``` + +**デプロイ**: + +```bash +cd infra-cdk +cdk deploy +``` + +**フロントエンドテスト**: 機能を検証するために、コード実行を必要とするプロンプトを使用します。 + +## 将来の拡張 + +潜在的な改善: + +- ファイル操作のための `write_files` ツールを追加 +- サンドボックスの内容を確認するための `list_files` ツールを追加 +- JavaScript/TypeScript の実行をサポート +- S3 からのファイルアップロードを追加 +- カスタムタイムアウト設定を実装 + +## 参考資料 + +- [AgentCore Code Interpreter Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html) +- [AWS IDP Reference Implementation](https://github.com/aws-solutions-library-samples/accelerated-intelligent-document-processing-on-aws) +- [FAST Gateway Documentation](./GATEWAY.md) diff --git a/samples/aws-specialist-agent/docs-jp/VERSION_BUMP_PLAYBOOK.md b/samples/aws-specialist-agent/docs-jp/VERSION_BUMP_PLAYBOOK.md new file mode 100644 index 0000000..9a76935 --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/VERSION_BUMP_PLAYBOOK.md @@ -0,0 +1,69 @@ +# バージョンバンプ Playbook + +このドキュメントでは、FAST (Fullstack AgentCore Solution Template) のバージョンをバンプするためのチェックリストを提供します。 + +## 手動更新が必要なファイル (6 ファイル) + +1. **`VERSION`** - ルートのバージョンファイル +2. **`pyproject.toml`** - Python パッケージのバージョン (`version = "X.Y.Z"`) +3. **`frontend/package.json`** - フロントエンドパッケージのバージョン (`"version": "X.Y.Z"`) +4. **`infra-cdk/package.json`** - CDK パッケージのバージョン (`"version": "X.Y.Z"`) +5. **`infra-cdk/lib/fast-main-stack.ts`** - スタックの説明文 (`(vX.Y.Z)`) +6. **`CHANGELOG.md`** - 先頭に新しいバージョンエントリを追加 + +## 自動生成ファイル (手動で更新しない) + +- `frontend/package-lock.json` +- `infra-cdk/package-lock.json` +- `infra-cdk/lib/fast-main-stack.js` + +## 手順 + +### 1. ソースファイルを更新する + +上記の 7 ファイルを新しいバージョン番号で手動で更新します。 + +### 2. 自動生成ファイルを再生成する + +```bash +# Frontend +cd frontend && npm install + +# Infrastructure +cd infra-cdk && npm install && npm run build +``` + +### 3. 検証 + +古いバージョンへの参照が残っていないか検索します: + +```bash +find . -type f \( -name "*.md" -o -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.yaml" -o -name "*.yml" -o -name "VERSION" \) | grep -v node_modules | grep -v cdk.out | grep -v ".next" | grep -v "dist" | grep -v "build" | grep -v "__pycache__" | xargs grep -n "OLD_VERSION" 2>/dev/null +``` + +### 4. テスト + +```bash +make all # lint を実行 +cd infra-cdk && cdk synth # CDK の synth をテスト +cd frontend && npm run build # フロントエンドのビルドをテスト +``` + +### 5. Git 操作 + +```bash +git add . +git commit -m "Bump version to X.Y.Z" +git push origin main + +# タグを作成してプッシュ +git tag vX.Y.Z +git push origin vX.Y.Z +``` + +## 補足 + +- セマンティックバージョニング (MAJOR.MINOR.PATCH) に従う +- git タグには `v` プレフィックスを使用 (例: `v0.1.3`) +- プロジェクトのバージョンのみを更新し、依存関係のバージョンは更新しない +- 過去の changelog エントリはそのまま変更しない diff --git a/samples/aws-specialist-agent/docs-jp/index.md b/samples/aws-specialist-agent/docs-jp/index.md new file mode 100644 index 0000000..6aa810d --- /dev/null +++ b/samples/aws-specialist-agent/docs-jp/index.md @@ -0,0 +1,9 @@ + + +# Fullstack AgentCore Solution Template ドキュメント + +Fullstack AgentCore Solution Template (FAST) のドキュメントへようこそ! + +実際のドキュメントを読むには、ブラウザの左側にあるナビゲーションバーをご覧ください! + +_この index.md ファイルは、mkdocs がドキュメントページの index.html を生成するために必要なため存在しています。_ diff --git a/samples/aws-specialist-agent/docs/.nav.yml b/samples/aws-specialist-agent/docs/.nav.yml new file mode 100644 index 0000000..a7f988c --- /dev/null +++ b/samples/aws-specialist-agent/docs/.nav.yml @@ -0,0 +1,5 @@ +# This file is only used for the mkdocs documentation hosting webpage +nav: + - " ": # This an empty section header, which makes every .md file its own subsection + - index.md + - "*" \ No newline at end of file diff --git a/samples/aws-specialist-agent/docs/AGENTCORE_EVALUATIONS_GUIDE.md b/samples/aws-specialist-agent/docs/AGENTCORE_EVALUATIONS_GUIDE.md new file mode 100644 index 0000000..ded7bf0 --- /dev/null +++ b/samples/aws-specialist-agent/docs/AGENTCORE_EVALUATIONS_GUIDE.md @@ -0,0 +1,1317 @@ +# Amazon Bedrock AgentCore Evaluations Guide + +This guide covers how to use Amazon Bedrock AgentCore's evaluation capabilities to assess and monitor AI agent performance. It includes instructions for launching on-demand evaluations, configuring online (live) evaluations, understanding available metrics, downloading results, using AI-powered analysis to improve your agent, and building dashboards. + +--- + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [SDK Installation](#sdk-installation) +3. [Online (Live) Evaluations](#online-live-evaluations) +4. [On-Demand Evaluations](#on-demand-evaluations) +5. [Built-in Evaluators Reference](#built-in-evaluators-reference) +6. [Custom Evaluators](#custom-evaluators) +7. [Evaluation Results & Format](#evaluation-results--format) +8. [Downloading & Querying Results](#downloading--querying-results) +9. [AI-Powered Analysis](#ai-powered-analysis) +10. [Retrieving Sessions, Traces & Spans (Observability)](#retrieving-sessions-traces--spans-observability) +11. [Building a Dashboard](#building-a-dashboard) +12. [Best Practices](#best-practices) +13. [References](#references) + +--- + +## Prerequisites + +- An Amazon Bedrock AgentCore runtime with a deployed agent +- AWS credentials with permissions for: + - `bedrock-agentcore:*` (control and data plane) + - `iam:CreateRole`, `iam:AttachRolePolicy`, `iam:PutRolePolicy`, `iam:GetRole`, `iam:PassRole` (scoped to `AgentCoreEvalsSDK-*` roles) + - `logs:DescribeLogGroups`, `logs:FilterLogEvents`, `logs:GetLogEvents` (for evaluation result log groups) + - `bedrock:InvokeModel` (for LLM-as-judge evaluations) +- Python 3.10+ with boto3 + +> **Region availability:** Cross-region inference profiles (e.g., `us.anthropic.claude-sonnet-4-5-20250929-v1:0`) are system-defined and use an empty account ID in the ARN. Your IAM policy must include both `arn:aws:bedrock:*:*:inference-profile/*` and `arn:aws:bedrock:*::inference-profile/*` to cover account-level and system-level profiles. Check [Bedrock supported regions](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html) for model availability. + +### IAM Policy Example + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "bedrock-agentcore:CreateEvaluator", + "bedrock-agentcore:ListEvaluators", + "bedrock-agentcore:GetEvaluator", + "bedrock-agentcore:UpdateEvaluator", + "bedrock-agentcore:DeleteEvaluator", + "bedrock-agentcore:CreateOnlineEvaluation", + "bedrock-agentcore:ListOnlineEvaluations", + "bedrock-agentcore:GetOnlineEvaluation", + "bedrock-agentcore:UpdateOnlineEvaluation", + "bedrock-agentcore:DeleteOnlineEvaluation", + "bedrock-agentcore:Evaluate", + "bedrock-agentcore:ListEvaluationResults" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:CreateRole", + "iam:AttachRolePolicy", + "iam:PutRolePolicy", + "iam:GetRole", + "iam:PassRole" + ], + "Resource": "arn:aws:iam::*:role/AgentCoreEvalsSDK-*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:DescribeLogGroups", + "logs:FilterLogEvents", + "logs:GetLogEvents" + ], + "Resource": "arn:aws:logs:*:*:log-group:/aws/bedrock-agentcore/evaluations/*" + }, + { + "Effect": "Allow", + "Action": [ + "bedrock:InvokeModel" + ], + "Resource": [ + "arn:aws:bedrock:*:*:inference-profile/*", + "arn:aws:bedrock:*::inference-profile/*" + ] + } + ] +} +``` + +--- + +## SDK Installation + +Install the AgentCore starter toolkit: + +```bash +pip install bedrock-agentcore-starter-toolkit +``` + +Initialize the evaluation client: + +```python +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") +``` + +--- + +## Online (Live) Evaluations + +Online evaluations continuously monitor your agent by sampling a percentage of live sessions and running evaluators automatically. Results are written to CloudWatch Logs. + +```mermaid +sequenceDiagram + participant You + participant SDK as Evaluation SDK + participant AC as AgentCore + participant CW as CloudWatch Logs + + You->>SDK: create_online_config(agent_id, evaluators, sampling_rate) + SDK->>AC: CreateOnlineEvaluationConfig + AC-->>SDK: config_id, execution_status + SDK-->>You: Config created ✓ + + loop Every sampled session + AC->>CW: Read agent runtime logs + AC->>AC: Run LLM-as-judge evaluators + AC->>CW: Write evaluation results + end + + You->>SDK: list_online_configs() + SDK->>AC: ListOnlineEvaluationConfigs + AC-->>You: configs[] +``` + +### Create an Online Evaluation Config + +Use this when you want to start continuously monitoring your agent's quality in a given environment. + +> **Note:** `auto_create_execution_role=True` is what triggers automatic IAM role creation. The SDK creates a role named `AgentCoreEvalsSDK-{region}-{hash}` with permissions to read your agent's CloudWatch logs and invoke Bedrock models for LLM-as-judge scoring. + +```python +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +# agent_id is the last segment of your runtime ARN +# e.g., "my-agent-abc123" from "arn:aws:bedrock-agentcore:us-east-1:123456789:runtime/my-agent-abc123" +agent_id = "my-agent-abc123" # Replace with your actual agent ID + +response = eval_client.create_online_config( + agent_id=agent_id, + config_name="production_eval", + sampling_rate=10.0, # Evaluate 10% of sessions + evaluator_list=[ + "Builtin.Helpfulness", + "Builtin.Correctness", + "Builtin.GoalSuccessRate", + ], + config_description="Production evaluation with core metrics", + auto_create_execution_role=True, # Creates IAM role automatically + enable_on_create=True, # Start evaluating immediately +) + +config_id = response["onlineEvaluationConfigId"] # Save this — you'll need it to query metrics +print(f"Created config: {config_id}") +print(f"Status: {response['executionStatus']}") +``` + +> **Timing:** Config creation typically completes in 10–30 seconds. Evaluation results start appearing in CloudWatch 2–5 minutes after your agent handles its next session. + +### Enable / Disable an Online Evaluation + +Use this to temporarily pause evaluations without losing the configuration (e.g., during maintenance windows or cost control). + +```python +# Disable evaluation (pause without deleting) +eval_client.update_online_config( + config_id="your-config-id", # Replace with actual config ID + execution_status="DISABLED" +) + +# Re-enable evaluation +eval_client.update_online_config( + config_id="your-config-id", + execution_status="ENABLED" +) +``` + +### Update Sampling Rate + +Use this to adjust cost vs. coverage tradeoff without recreating the config. + +```python +eval_client.update_online_config( + config_id="your-config-id", + sampling_rate=5.0 # Reduce to 5% for cost savings +) +``` + +### List All Configs + +```python +configs = eval_client.list_online_configs() +for config in configs.get("onlineEvaluationConfigs", []): + print(f"{config['onlineEvaluationConfigName']}: {config['executionStatus']} " + f"(sampling: {config.get('samplingRate', 'N/A')}%)") +``` + +### Delete a Config + +```python +eval_client.delete_online_config(config_id="your-config-id") +``` + +--- + +## On-Demand Evaluations + +On-demand evaluations let you evaluate a specific session immediately and get results back synchronously. The SDK retrieves the session's traces from AgentCore Observability automatically. + +```mermaid +sequenceDiagram + participant You + participant SDK as Evaluation SDK + participant Obs as Observability + participant AC as AgentCore + + You->>SDK: eval_client.run(agent_id, session_id, evaluators) + SDK->>Obs: Retrieve session traces & spans + Obs-->>SDK: trace_data + SDK->>AC: Evaluate(spans, evaluator_id) + AC->>AC: Run LLM-as-judge + AC-->>SDK: score, label, explanation + SDK-->>You: Results (synchronous) +``` + +### Evaluate a Single Session + +Use this to spot-check a specific conversation, debug a user-reported issue, or validate agent behavior after a prompt change. + +> **Timing:** Each evaluator takes 5–15 seconds per session. Running 3 evaluators on one session typically completes in 15–45 seconds. + +```python +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +agent_id = "my-agent-abc123" # Replace with your actual agent ID +session_id = "session-456" # Replace with actual session ID + +# Run one or more evaluators against a session +results = eval_client.run( + agent_id=agent_id, + session_id=session_id, + evaluators=["Builtin.Helpfulness", "Builtin.Correctness"] +) + +for result in results.results: + print(f"Evaluator: {result.evaluator_name}") + print(f" Score: {result.value:.2f}") + print(f" Label: {result.label}") + print(f" Explanation: {result.explanation}") + if hasattr(result, 'token_usage') and result.token_usage: + print(f" Tokens: {result.token_usage}") + print() +``` + +### Evaluate Using the Low-Level boto3 API + +Use this when you need fine-grained control — for example, evaluating specific spans rather than an entire session, or when you've already retrieved spans yourself. + +```python +import boto3 + +bedrock_agentcore = boto3.client("bedrock-agentcore") + +response = bedrock_agentcore.evaluate( + evaluatorId="Builtin.Helpfulness", + evaluationInput={ + "sessionSpans": [ + { + "traceId": "abc123", + "spanId": "def456", + "name": "agent_response", + "startTimeUnixNano": 1708128000000000000, + "endTimeUnixNano": 1708128001000000000, + "attributes": { + "session.id": "session-456" + }, + "status": {"code": "OK"}, + "scope": {"name": "bedrock-agentcore"} + } + ] + }, + evaluationTarget={ + "traceIds": ["abc123"], # Optional: scope to specific traces + "spanIds": ["def456"] # Optional: scope to specific spans + } +) + +for result in response.get("evaluationResults", []): + print(f"Score: {result['value']}, Label: {result['label']}") + print(f"Explanation: {result['explanation']}") +``` + +### Batch Evaluation (Multiple Sessions) + +Use this to evaluate a set of sessions after a prompt change or deployment, to compare before/after quality. + +> **Timing:** Batch evaluation is sequential — expect ~10 seconds per session per evaluator. Evaluating 10 sessions with 2 evaluators takes roughly 3–4 minutes. + +```python +agent_id = "my-agent-abc123" +session_ids = ["session-001", "session-002", "session-003"] # Replace with actual IDs +evaluators = ["Builtin.Helpfulness", "Builtin.Correctness"] + +all_results = [] +for session_id in session_ids: + try: + results = eval_client.run( + agent_id=agent_id, + session_id=session_id, + evaluators=evaluators + ) + all_results.append({ + "session_id": session_id, + "results": [ + { + "evaluator": r.evaluator_name, + "score": r.value, + "label": r.label, + "explanation": r.explanation + } + for r in results.results + ] + }) + except Exception as e: + print(f"Failed to evaluate {session_id}: {e}") + +print(f"Evaluated {len(all_results)} sessions successfully") +``` + +--- + +## Built-in Evaluators Reference + +AgentCore provides 15 built-in evaluators across three evaluation levels. + +### Evaluation Level Decision Tree + +| Your Goal | Use This Level | Why | +|---|---|---| +| Measure overall task completion | SESSION | Evaluates the entire conversation end-to-end | +| Assess individual response quality | TRACE | Evaluates each agent turn independently | +| Validate tool usage correctness | TOOL_CALL | Evaluates specific tool invocations | + +### Quality & Relevance (TRACE level) + +| Evaluator ID | What It Measures | +|---|---| +| `Builtin.Helpfulness` | How useful and valuable the response is from the user's perspective | +| `Builtin.Correctness` | Whether the information in the response is factually accurate | +| `Builtin.Faithfulness` | Whether the response stays true to the provided context without hallucination | +| `Builtin.Coherence` | Logical flow and consistency of the response | +| `Builtin.Conciseness` | Whether the response is appropriately brief without unnecessary information | +| `Builtin.InstructionFollowing` | Whether the response adheres to all explicit instructions in the user's input | +| `Builtin.ContextRelevance` | How relevant the retrieved context is to the user's query | +| `Builtin.ResponseRelevance` | How well the response addresses the specific question or request | + +### Safety (TRACE level) + +| Evaluator ID | What It Measures | +|---|---| +| `Builtin.Harmfulness` | Detects potentially harmful or unsafe content in responses | +| `Builtin.Maliciousness` | Identifies malicious intent or attempts to manipulate users | +| `Builtin.Stereotyping` | Detects stereotypical or biased language in responses | +| `Builtin.Refusal` | Tracks when the agent appropriately refuses inappropriate requests | + +### Tool Usage (TOOL_CALL level) + +| Evaluator ID | What It Measures | +|---|---| +| `Builtin.ToolSelectionAccuracy` | Whether the agent selected the correct tools for the task | +| `Builtin.ToolParameterAccuracy` | Whether the agent used tools with correct parameters | + +### Session-Level + +| Evaluator ID | What It Measures | +|---|---| +| `Builtin.GoalSuccessRate` | Whether the agent successfully completed all user goals in the conversation | + +### Evaluator Selection Guide + +Use this matrix to decide which evaluators to enable based on your use case: + +| Use Case | Recommended Evaluators | +|---|---| +| General-purpose chatbot | `Helpfulness`, `Correctness`, `GoalSuccessRate` | +| RAG / knowledge retrieval | `Faithfulness`, `ContextRelevance`, `Correctness` | +| Tool-using agent | `ToolSelectionAccuracy`, `ToolParameterAccuracy`, `GoalSuccessRate` | +| Safety-critical application | `Harmfulness`, `Maliciousness`, `Stereotyping`, `Refusal` | +| Instruction-following tasks | `InstructionFollowing`, `Coherence`, `Conciseness` | + +### Listing Available Evaluators Programmatically + +```python +evaluators = eval_client.list_evaluators() +for evaluator in evaluators.get("evaluatorSummaries", []): + print(f"{evaluator['evaluatorId']}: {evaluator.get('evaluatorName', '')}") +``` + +--- + +## Custom Evaluators + +Use custom evaluators when built-in ones don't cover your domain-specific quality criteria (e.g., financial accuracy, medical safety, brand voice compliance). + +### Placeholder Reference + +Each evaluation level supports a fixed set of placeholders (single braces) that get replaced with actual trace data: + +| Level | Placeholder | Description | +|---|---|---| +| SESSION | `{context}` | User prompts, assistant responses, and tool calls across all turns | +| SESSION | `{available_tools}` | Available tool calls including ID, parameters, and description | +| TRACE | `{context}` | Previous turns + current turn's user prompt and tool calls | +| TRACE | `{assistant_turn}` | The assistant response for the current turn | +| TOOL_CALL | `{context}` | Previous turns + current turn's user prompt and prior tool calls | +| TOOL_CALL | `{tool_turn}` | The tool call under evaluation | +| TOOL_CALL | `{available_tools}` | Available tool calls including ID, parameters, and description | + +> **Important:** Use single braces `{placeholder}`, not double braces `{{placeholder}}`. The instruction must include at least one placeholder. + +### Create a Custom Evaluator (AWS SDK) + +The `create_evaluator` API uses a nested `evaluatorConfig` structure with `llmAsAJudge` containing the instructions, rating scale, and model config: + +```python +import boto3 + +control_client = boto3.client("bedrock-agentcore-control") + +response = control_client.create_evaluator( + evaluatorName="domain_accuracy", + description="Evaluates domain-specific accuracy for financial queries", + level="TRACE", + evaluatorConfig={ + "llmAsAJudge": { + "instructions": ( + "You are evaluating the domain-specific accuracy of the assistant's response " + "in financial contexts. Consider:\n" + "1. Are financial terms used correctly?\n" + "2. Are calculations accurate?\n" + "3. Are regulatory references correct?\n\n" + "Context: {context}\n" + "Candidate Response: {assistant_turn}" + ), + "ratingScale": { + "numerical": [ + { + "value": 1.0, + "label": "Very Good", + "definition": "Completely accurate, all facts and calculations correct" + }, + { + "value": 0.75, + "label": "Good", + "definition": "Mostly accurate with minor issues" + }, + { + "value": 0.5, + "label": "OK", + "definition": "Partially correct with notable errors" + }, + { + "value": 0.25, + "label": "Poor", + "definition": "Significant errors or misconceptions" + }, + { + "value": 0.0, + "label": "Very Poor", + "definition": "Completely incorrect or irrelevant" + } + ] + }, + "modelConfig": { + "bedrockEvaluatorModelConfig": { + "modelId": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "inferenceConfig": { + "maxTokens": 500, + "temperature": 1.0 + } + } + } + } + } +) + +evaluator_arn = response["evaluatorArn"] +evaluator_id = evaluator_arn.split("/")[-1] +print(f"Created custom evaluator: {evaluator_id}") +``` + +### Create a Custom Evaluator (Starter Toolkit SDK) + +```python +import json +from bedrock_agentcore_starter_toolkit import Evaluation + +eval_client = Evaluation(region="us-east-1") + +# Load config from JSON file (see config format below) +with open("custom_evaluator_config.json") as f: + evaluator_config = json.load(f) + +custom_evaluator = eval_client.create_evaluator( + name="domain_accuracy", + level="TRACE", + description="Evaluates domain-specific accuracy for financial queries", + config=evaluator_config +) +``` + +### Custom Evaluator Config JSON Format + +Save this as `custom_evaluator_config.json` for use with the Starter Toolkit SDK or AWS CLI: + +```json +{ + "llmAsAJudge": { + "modelConfig": { + "bedrockEvaluatorModelConfig": { + "modelId": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "inferenceConfig": { + "maxTokens": 500, + "temperature": 1.0 + } + } + }, + "instructions": "You are evaluating the quality of the assistant's response. Context: {context}\nCandidate Response: {assistant_turn}", + "ratingScale": { + "numerical": [ + {"value": 1.0, "label": "Very Good", "definition": "Completely accurate"}, + {"value": 0.75, "label": "Good", "definition": "Mostly accurate with minor issues"}, + {"value": 0.5, "label": "OK", "definition": "Partially correct"}, + {"value": 0.25, "label": "Poor", "definition": "Significant errors"}, + {"value": 0.0, "label": "Very Poor", "definition": "Completely incorrect"} + ] + } + } +} +``` + +### Add a Custom Evaluator to an Online Config + +After creating the evaluator, add it to your online evaluation config: + +```python +import boto3 + +control_client = boto3.client("bedrock-agentcore-control") + +# Get current evaluators +config = control_client.get_online_evaluation_config( + onlineEvaluationConfigId="your-config-id" +) +current_evaluators = [e["evaluatorId"] for e in config.get("evaluators", [])] + +# Add the custom evaluator +current_evaluators.append("domain_accuracy-XXXXXXXXXX") # Use the ID from create_evaluator + +control_client.update_online_evaluation_config( + onlineEvaluationConfigId="your-config-id", + evaluators=[{"evaluatorId": eid} for eid in current_evaluators] +) +``` + +Custom evaluators can then be used in both online and on-demand evaluations just like built-in ones. + +> **Note:** The service automatically appends a standardization prompt to your instructions that enforces `reason` and `score` output fields. Do not include output formatting instructions in your evaluator instructions. + +--- + +## Evaluation Results & Format + +### Online Evaluation Results (CloudWatch Logs) + +Online evaluation results are written to CloudWatch Logs at: + +``` +/aws/bedrock-agentcore/evaluations/results/{config-id} +``` + +> **Note:** The log group name uses the config ID (e.g., `a1b2c3d4-...`), not the config name. You can find the config ID from `create_online_config()` response or `list_online_configs()`. + +Each log event contains a JSON object with OpenTelemetry-style attributes: + +```json +{ + "attributes": { + "gen_ai.evaluation.name": "Builtin.Helpfulness", + "gen_ai.evaluation.score.value": 0.83, + "gen_ai.evaluation.score.label": "Very Helpful", + "gen_ai.evaluation.explanation": "The response directly addresses the user's question with relevant and actionable information..." + }, + "traceId": "abc123def456", // pragma: allowlist secret (example placeholder, not a real secret) + "spanId": "789ghi", + "sessionId": "session-456", + "timestamp": "2026-02-17T00:42:42.086Z" +} +``` + +### Key Fields + +| Field | Description | +|---|---| +| `attributes.gen_ai.evaluation.name` | Evaluator ID (e.g., `Builtin.Helpfulness`) | +| `attributes.gen_ai.evaluation.score.value` | Numeric score from 0.0 to 1.0 | +| `attributes.gen_ai.evaluation.score.label` | Human-readable label (e.g., "Very Helpful") | +| `attributes.gen_ai.evaluation.explanation` | Detailed reasoning for the score | +| `traceId` | The trace that was evaluated | +| `spanId` | The specific span that was evaluated | +| `sessionId` | The agent session ID | + +### On-Demand Evaluation Results + +On-demand results are returned synchronously in the API response: + +```json +{ + "evaluatorId": "Builtin.Helpfulness", + "evaluatorName": "Builtin.Helpfulness", + "value": 0.83, + "label": "Very Helpful", + "explanation": "The response directly addresses...", + "tokenUsage": { + "inputTokens": 958, + "outputTokens": 211, + "totalTokens": 1169 + } +} +``` + +--- + +## Downloading & Querying Results + +### Query Results from CloudWatch (Python) + +Use this to pull evaluation results for analysis, export, or dashboard display. + +> **CloudWatch Logs quota:** `FilterLogEvents` is limited to 5 requests per second per account per region. For large result sets, add pagination delays or use CloudWatch Logs Insights for faster queries. + +```python +import boto3 +import json +from datetime import datetime, timedelta + +cloudwatch_logs = boto3.client("logs") + +config_id = "your-config-id" # Replace with actual config ID from create/list +log_group = f"/aws/bedrock-agentcore/evaluations/results/{config_id}" + +end_time = datetime.utcnow() +start_time = end_time - timedelta(days=7) + +results = [] +next_token = None + +while True: + params = { + "logGroupName": log_group, + "startTime": int(start_time.timestamp() * 1000), + "endTime": int(end_time.timestamp() * 1000), + "limit": 10000, + } + if next_token: + params["nextToken"] = next_token + + response = cloudwatch_logs.filter_log_events(**params) + + for event in response.get("events", []): + try: + log_data = json.loads(event["message"]) + results.append(log_data) + except json.JSONDecodeError: + continue + + next_token = response.get("nextToken") + if not next_token: + break + +print(f"Retrieved {len(results)} evaluation results") +``` + +### Export Results to CSV + +Use this to share results with stakeholders or import into spreadsheet tools. + +```python +import csv + +with open("evaluation_results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["session_id", "trace_id", "evaluator", "score", "label", "explanation"]) + + for result in results: + attrs = result.get("attributes", {}) + writer.writerow([ + result.get("sessionId", ""), + result.get("traceId", ""), + attrs.get("gen_ai.evaluation.name", ""), + attrs.get("gen_ai.evaluation.score.value", ""), + attrs.get("gen_ai.evaluation.score.label", ""), + attrs.get("gen_ai.evaluation.explanation", ""), + ]) +``` + +### Compute Aggregate Metrics from Results + +Use this to build summary statistics for dashboards or reports. + +```python +from collections import defaultdict + +evaluator_metrics = defaultdict(lambda: {"count": 0, "total_score": 0.0}) + +for result in results: + attrs = result.get("attributes", {}) + evaluator = attrs.get("gen_ai.evaluation.name", "unknown") + score = attrs.get("gen_ai.evaluation.score.value", 0.0) + + evaluator_metrics[evaluator]["count"] += 1 + evaluator_metrics[evaluator]["total_score"] += score + +# Print per-evaluator averages +for evaluator, metrics in evaluator_metrics.items(): + avg = metrics["total_score"] / metrics["count"] if metrics["count"] > 0 else 0 + print(f"{evaluator}: avg={avg:.2f} ({metrics['count']} evaluations)") +``` + +### Score Distribution + +Use this to understand the shape of your score data — are most scores clustered high, or is there a long tail of low scores? + +```python +def compute_score_distribution(results): + """Compute score distribution across standard bins.""" + bins = { + "0.0-0.2": 0, + "0.2-0.4": 0, + "0.4-0.6": 0, + "0.6-0.8": 0, + "0.8-1.0": 0, + } + + for result in results: + score = result.get("attributes", {}).get("gen_ai.evaluation.score.value", 0.0) + if score < 0.2: + bins["0.0-0.2"] += 1 + elif score < 0.4: + bins["0.2-0.4"] += 1 + elif score < 0.6: + bins["0.4-0.6"] += 1 + elif score < 0.8: + bins["0.6-0.8"] += 1 + else: + bins["0.8-1.0"] += 1 + + return bins + +distribution = compute_score_distribution(results) +for range_label, count in distribution.items(): + print(f" {range_label}: {count}") +``` + +--- + +## AI-Powered Analysis + +Once you have evaluation results, you can use Amazon Bedrock foundation models to automatically analyze patterns in low-scoring evaluations and generate system prompt improvements. This closes the feedback loop: + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Evaluate │────▶│ Identify │────▶│ Improve │────▶│ Deploy & │ +│ Agent │ │ Patterns │ │ Prompt │ │ Re-evaluate│ +└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ + ▲ │ + └───────────────────────────────────────────────────────────┘ + Continuous improvement loop +``` + +```mermaid +sequenceDiagram + participant You + participant CW as CloudWatch Logs + participant FM as Foundation Model (Bedrock) + + You->>CW: Query low-scoring evaluation results + CW-->>You: Evaluation logs (scores, explanations) + You->>FM: Analyze patterns in low-scoring results + FM-->>You: Patterns, summary, recommendations + You->>FM: Generate improved prompt based on patterns + FM-->>You: Improved prompt + change explanations + You->>You: Review, apply, and re-evaluate +``` + +### Pattern Analysis + +Use this after you've accumulated evaluation results and want to understand why your agent is scoring low on certain evaluators. + +> **Timing:** Pattern analysis with ~50 evaluation results typically takes 15–30 seconds depending on model and input size. + +```python +import boto3 +import json + +bedrock_runtime = boto3.client("bedrock-runtime") + +def analyze_evaluation_patterns(low_scoring_results: list[dict]) -> dict: + """ + Analyze low-scoring evaluation results to identify failure patterns. + + Args: + low_scoring_results: Evaluation result logs from CloudWatch + (filtered to scores below your threshold, e.g., <= 0.5) + + Returns: + Analysis with patterns, summary, and recommendations + """ + # Format results for the model — include evaluator name, score, and explanation + formatted = [] + for result in low_scoring_results[:50]: # Limit to avoid token overflow + attrs = result.get("attributes", {}) + formatted.append({ + "session_id": attrs.get("session.id", "unknown"), + "evaluator": attrs.get("gen_ai.evaluation.name", "unknown"), + "score": attrs.get("gen_ai.evaluation.score.value", 0.0), + "label": attrs.get("gen_ai.evaluation.score.label", ""), + "explanation": attrs.get("gen_ai.evaluation.explanation", ""), + }) + + system_prompt = """You are an expert at analyzing agent evaluation data to identify +patterns and root causes of poor performance. For each pattern you identify: +1. Describe the pattern clearly +2. Count how frequently it occurs +3. List affected session IDs +4. Provide concrete evidence from the evaluation explanations + +Return JSON: {"patterns": [...], "summary": "...", "recommendations": [...]}""" + + response = bedrock_runtime.invoke_model( + modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 4096, + "system": system_prompt, + "messages": [{"role": "user", "content": f""" +Analyze these {len(formatted)} low-scoring evaluations and identify common patterns: + +{json.dumps(formatted, indent=2)} + +Focus on: which evaluators score low consistently, common issues in explanations, +and actionable patterns across sessions."""}], + "temperature": 0.7, + }), + ) + + response_body = json.loads(response["body"].read()) + analysis_text = response_body["content"][0]["text"] + + # Parse JSON from response (strip markdown fences if present) + analysis_text = analysis_text.strip().strip("`").removeprefix("json").strip() + return json.loads(analysis_text) +``` + +### System Prompt Improvement + +Use this after pattern analysis to automatically generate a better system prompt that addresses the identified issues. + +> **Timing:** Prompt improvement generation typically takes 20–60 seconds, as the model produces a complete revised prompt with explanations. + +```python +def generate_prompt_improvement(current_prompt: str, analysis: dict) -> dict: + """ + Generate an improved system prompt based on analysis of failure patterns. + + Args: + current_prompt: The agent's current system prompt + analysis: Output from analyze_evaluation_patterns() + + Returns: + Dict with improvedPrompt and list of changes with reasoning + """ + system_prompt = """You are an expert at improving system prompts for AI agents. +Generate specific improvements based on identified failure patterns. +For each change, explain the reasoning and expected impact. + +Return JSON: {"improvedPrompt": "...", "changes": [{"section": "...", +"reasoning": "...", "impact": "..."}]}""" + + # Truncate pattern evidence to stay within token limits + analysis_summary = { + "summary": analysis.get("summary", ""), + "patterns": [ + { + "pattern": p["pattern"], + "frequency": p["frequency"], + "evidence": p["evidence"][:500], + } + for p in analysis.get("patterns", [])[:10] + ], + "recommendations": analysis.get("recommendations", [])[:10], + } + + response = bedrock_runtime.invoke_model( + modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 8192, + "system": system_prompt, + "messages": [{"role": "user", "content": f""" +Current System Prompt: +{current_prompt} + +Performance Analysis: +{json.dumps(analysis_summary, indent=2)} + +Generate an improved prompt that addresses these issues."""}], + "temperature": 0.7, + }), + ) + + response_body = json.loads(response["body"].read()) + result_text = response_body["content"][0]["text"] + result_text = result_text.strip().strip("`").removeprefix("json").strip() + return json.loads(result_text) +``` + +### End-to-End Workflow + +Putting it all together — from querying results to generating an improved prompt: + +```python +# 1. Query low-scoring evaluation results (see "Downloading & Querying Results") +results = query_evaluation_results("your-config-id", days=30) +low_scoring = [ + r for r in results + if r.get("attributes", {}).get("gen_ai.evaluation.score.value", 1.0) <= 0.5 +] +print(f"Found {len(low_scoring)} low-scoring evaluations") + +# 2. Analyze patterns (~15-30 seconds) +analysis = analyze_evaluation_patterns(low_scoring) +print(f"Identified {len(analysis['patterns'])} patterns") +for pattern in analysis["patterns"]: + print(f" - {pattern['pattern']} (frequency: {pattern['frequency']})") + +# 3. Generate prompt improvement (~20-60 seconds) +current_prompt = "You are a helpful assistant..." # your agent's current prompt +improvement = generate_prompt_improvement(current_prompt, analysis) +print(f"\nSuggested {len(improvement['changes'])} changes:") +for change in improvement["changes"]: + print(f" Section: {change['section']}") + print(f" Reasoning: {change['reasoning']}") + print(f" Impact: {change['impact']}\n") + +# 4. Review and apply the improved prompt +print("Improved prompt:") +print(improvement["improvedPrompt"]) +``` + +--- + +## Retrieving Sessions, Traces & Spans (Observability) + +The `bedrock-agentcore-starter-toolkit` includes an Observability client that retrieves session data (traces and spans) from AgentCore. This is useful for building session explorers, trace viewers, and feeding data into evaluations. + +### Session / Trace / Span Hierarchy + +``` +Session (conversation) +├── Trace 1 (user turn) +│ ├── Span: agent_planning (root) +│ ├── Span: tool_call → search_api +│ ├── Span: tool_result ← search_api +│ └── Span: agent_response +├── Trace 2 (user turn) +│ ├── Span: agent_planning (root) +│ └── Span: agent_response +└── Trace 3 (user turn) + ├── Span: agent_planning (root) + ├── Span: tool_call → database_query + ├── Span: tool_result ← database_query + └── Span: agent_response +``` + +### Initialize the Observability Client + +```python +from bedrock_agentcore_starter_toolkit import Observability + +agent_id = "my-agent-abc123" # Replace with your actual agent ID +obs_client = Observability(agent_id=agent_id, region="us-east-1") +``` + +### List Traces and Spans for a Session + +Use this to inspect what happened during a specific conversation — which tools were called, how long each step took, and whether any errors occurred. + +```python +trace_data = obs_client.list(session_id="session-456") # Replace with actual session ID + +# trace_data.traces → dict: {trace_id: [list of spans]} +# trace_data.spans → flat list of all spans across all traces +# trace_data.start_time → session start time in nanoseconds + +print(f"Traces: {len(trace_data.traces)}") +print(f"Total spans: {len(trace_data.spans)}") + +for trace_id, spans in trace_data.traces.items(): + print(f"\nTrace {trace_id}: {len(spans)} spans") + for span in sorted(spans, key=lambda s: s.start_time_unix_nano or 0): + print(f" {span.span_name} ({span.duration_ms}ms) " + f"parent={span.parent_span_id or 'root'}") +``` + +### Span Properties + +Each span object returned by the SDK has these key properties: + +| Property | Type | Description | +|---|---|---| +| `span_id` | `str` | Unique span identifier | +| `trace_id` | `str` | Parent trace identifier | +| `parent_span_id` | `str \| None` | Parent span ID (`None` for root spans) | +| `span_name` | `str` | Name of the operation (e.g., `"agent_response"`, `"tool_call"`) | +| `start_time_unix_nano` | `int` | Start time in nanoseconds since epoch | +| `end_time_unix_nano` | `int` | End time in nanoseconds since epoch | +| `duration_ms` | `float` | Duration in milliseconds | +| `status_code` | `str` | Status (`"OK"`, `"ERROR"`, `"UNSET"`) | +| `attributes` | `dict` | OpenTelemetry attributes (model ID, token counts, etc.) | + +### Formatting Trace Data for Display + +Use this helper to convert SDK trace data into a JSON-serializable structure suitable for API responses or UI rendering. + +```python +from datetime import datetime + +def format_session_for_display(trace_data) -> dict: + """Format SDK trace data into a JSON-serializable structure.""" + formatted_traces = [] + + for trace_id, spans in trace_data.traces.items(): + if not spans: + continue + + spans.sort(key=lambda s: s.start_time_unix_nano or 0) + + trace_start = min(s.start_time_unix_nano for s in spans if s.start_time_unix_nano) + trace_end = max(s.end_time_unix_nano for s in spans if s.end_time_unix_nano) + + formatted_traces.append({ + "traceId": trace_id, + "startTime": datetime.fromtimestamp(trace_start / 1e9).isoformat(), + "endTime": datetime.fromtimestamp(trace_end / 1e9).isoformat(), + "durationMs": (trace_end - trace_start) / 1e6, + "spans": [ + { + "spanId": s.span_id, + "traceId": s.trace_id, + "parentSpanId": s.parent_span_id, + "name": s.span_name, + "startTime": datetime.fromtimestamp(s.start_time_unix_nano / 1e9).isoformat(), + "endTime": datetime.fromtimestamp(s.end_time_unix_nano / 1e9).isoformat(), + "durationMs": s.duration_ms, + "status": s.status_code or "UNSET", + "attributes": s.attributes or {}, + } + for s in spans + ], + }) + + formatted_traces.sort(key=lambda t: t["startTime"]) + return { + "traceCount": len(formatted_traces), + "spanCount": len(trace_data.spans), + "traces": formatted_traces, + } +``` + +--- + +## Building a Dashboard + +With evaluation results and observability data, you can build a dashboard to monitor agent performance. Here's a high-level overview of the key components and data flow. + +### Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Dashboard UI │ +│ │ +│ ┌───────────────┐ ┌──────────────┐ ┌────────────────────┐ │ +│ │ Summary Tiles │ │ Score Dist. │ │ Per-Evaluator │ │ +│ │ • Total evals │ │ Chart │ │ Metrics │ │ +│ │ • Avg score │ │ (bar chart) │ │ (color-coded) │ │ +│ │ • Low/High │ │ │ │ │ │ +│ └───────────────┘ └──────────────┘ └────────────────────┘ │ +│ │ +│ ┌──────────────────────────┐ ┌───────────────────────────┐ │ +│ │ Session Explorer │ │ On-Demand Eval Panel │ │ +│ │ • Browse sessions │ │ • Select evaluators │ │ +│ │ • Filter by score/date │ │ • Run against session │ │ +│ │ • View trace timeline │ │ • View scores/explanations│ │ +│ └──────────────────────────┘ └───────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ AI Analysis Panel │ │ +│ │ • Trigger pattern analysis on low-scoring results │ │ +│ │ • View identified patterns and recommendations │ │ +│ │ • Generate and review prompt improvements │ │ +│ └──────────────────────────────────────────────────────────┘ │ +└──────────────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ Backend API │ +│ │ +│ Evaluation SDK CloudWatch Logs Observability SDK │ +│ • list_online_configs • filter_log_events • obs.list() │ +│ • run() (on-demand) • (eval results) • (traces/spans) │ +│ • create/update/delete │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### Key Dashboard Components + +| Component | Data Source | What It Shows | +|---|---|---| +| Summary Tiles | Aggregated CloudWatch results | Total evaluations, average score, low-score count (< 0.5), high-score count (≥ 0.8) | +| Score Distribution | Aggregated CloudWatch results | Bar chart of scores across bins (0.0–0.2, 0.2–0.4, etc.) | +| Per-Evaluator Metrics | Aggregated CloudWatch results | Each evaluator's average score and count, color-coded (green ≥ 0.8, yellow ≥ 0.6, red < 0.6) | +| Session Explorer | Observability SDK | Browsable list of sessions with scores, trace counts, timestamps | +| Trace Viewer | Observability SDK | Timeline visualization of traces and spans, showing parent-child hierarchy | +| On-Demand Eval Panel | Evaluation SDK `run()` | Select evaluators, run against a session, display scores and explanations | +| AI Analysis Panel | Bedrock `invoke_model` | Pattern analysis, recommendations, prompt improvement suggestions | + +### Data Flow + +```mermaid +graph TD + A[Dashboard loads] --> B[Fetch evaluation configs] + B --> C{Configs found?} + C -->|Yes| D[Fetch metrics per config from CloudWatch] + C -->|No| E[Show setup banner] + D --> F[Aggregate across configs] + F --> G[Display summary tiles + charts + evaluator breakdown] + + H[User selects session] --> I[Fetch traces via Observability SDK] + I --> J[Display trace timeline viewer] + + K[User clicks Run Evaluation] --> L[Call eval_client.run per evaluator] + L --> M[Display scores + explanations] + + N[User triggers AI Analysis] --> O[Query low-scoring results from CloudWatch] + O --> P[Send to foundation model for pattern analysis] + P --> Q[Display patterns + recommendations] + Q --> R[Generate prompt improvement] +``` + +### Aggregating Metrics Across Configs + +If you have multiple evaluation configs (e.g., separate configs for different evaluator sets), aggregate their metrics for a unified dashboard view. + +```python +def aggregate_metrics(all_config_metrics: list[dict]) -> dict: + """Combine metrics from multiple evaluation configs.""" + total_evals = 0 + weighted_score_sum = 0.0 + combined_distribution = {} + combined_evaluators = {} + + for metrics in all_config_metrics: + count = metrics["totalEvaluations"] + if count == 0: + continue + + total_evals += count + weighted_score_sum += metrics["averageScore"] * count + + for bin_key, bin_count in metrics.get("scoreDistribution", {}).items(): + combined_distribution[bin_key] = combined_distribution.get(bin_key, 0) + bin_count + + for eval_id, eval_metrics in metrics.get("evaluatorMetrics", {}).items(): + if eval_id not in combined_evaluators: + combined_evaluators[eval_id] = {"count": 0, "totalScore": 0.0} + combined_evaluators[eval_id]["count"] += eval_metrics["count"] + combined_evaluators[eval_id]["totalScore"] += eval_metrics["totalScore"] + + # Compute averages + for eval_id in combined_evaluators: + m = combined_evaluators[eval_id] + m["averageScore"] = m["totalScore"] / m["count"] if m["count"] > 0 else 0.0 + + return { + "totalEvaluations": total_evals, + "averageScore": weighted_score_sum / total_evals if total_evals > 0 else 0.0, + "scoreDistribution": combined_distribution, + "evaluatorMetrics": combined_evaluators, + } +``` + +--- + +## Best Practices + +### Cost Optimization + +#### Sampling Rate Strategy + +| Environment | Recommended Rate | Rationale | +|---|---|---| +| Development | 100% | Full visibility during testing | +| Staging | 25–50% | Good coverage for QA | +| Production | 5–10% | Cost-effective monitoring | + +#### Evaluator Selection Strategy + +Start with a small set of core evaluators and expand as needed: + +1. **Start with 3 core evaluators**: `Helpfulness`, `Correctness`, `GoalSuccessRate` +2. **Add safety evaluators for production**: `Harmfulness`, `Maliciousness` +3. **Add quality evaluators as needed**: `Faithfulness`, `Coherence`, `InstructionFollowing` +4. **Add tool evaluators if using tools**: `ToolSelectionAccuracy`, `ToolParameterAccuracy` + +Each online evaluation config supports up to 10 evaluators. + +#### Token Usage + +- Built-in evaluators use efficient prompts (~1,000 tokens per evaluation) +- SESSION-level evaluators cost more (evaluate entire conversation) +- TRACE-level evaluators are more granular (per response) +- TOOL_CALL-level evaluators are the most targeted (per tool invocation) + +#### CloudWatch Logs Costs + +Online evaluation results are stored in CloudWatch Logs, which incurs: +- **Ingestion:** $0.50 per GB ingested +- **Storage:** $0.03 per GB per month (default retention: never expire) + +To control costs, set a retention policy on evaluation log groups: + +```python +cloudwatch_logs.put_retention_policy( + logGroupName=f"/aws/bedrock-agentcore/evaluations/results/{config_id}", + retentionInDays=90 # Keep results for 90 days +) +``` + +### Operational Tips + +#### Time Estimates for Common Operations + +| Operation | Typical Duration | +|---|---| +| Create online evaluation config | 10–30 seconds | +| First results appear in CloudWatch | 2–5 minutes after next agent session | +| On-demand evaluation (1 evaluator, 1 session) | 5–15 seconds | +| On-demand evaluation (3 evaluators, 1 session) | 15–45 seconds | +| Batch evaluation (10 sessions × 2 evaluators) | 3–4 minutes | +| AI pattern analysis (~50 results) | 15–30 seconds | +| AI prompt improvement generation | 20–60 seconds | + +#### Naming Conventions + +Use consistent naming across your codebase: + +| Concept | Python (SDK) | JSON (API responses) | +|---|---|---| +| Agent identifier | `agent_id` | `agentId` | +| Config identifier | `config_id` | `onlineEvaluationConfigId` | +| Session identifier | `session_id` | `sessionId` | +| Evaluator identifier | `evaluator_id` | `evaluatorId` | + +> **Note:** The SDK uses `snake_case` for parameters. API responses from AgentCore use `camelCase`. The examples in this guide use `snake_case` for Python variables and `camelCase` when showing JSON responses. + +#### Troubleshooting + +| Symptom | Likely Cause | Fix | +|---|---|---| +| No metrics appearing | Results take 2–5 min to appear | Wait and refresh; verify `executionStatus` is `ENABLED` | +| 403 on evaluation API calls | Missing IAM permissions | Add `bedrock-agentcore:*` to your role | +| `ResourceNotFoundException` on log group | No evaluations have run yet | Invoke your agent to generate sessions, then wait | +| Low evaluation counts vs. session count | Sampling rate too low | Increase `sampling_rate` or generate more agent traffic | +| `FilterLogEvents` throttled | CloudWatch 5 req/sec limit | Add pagination delays or use CloudWatch Logs Insights | + +--- + +## References + +- [AgentCore Evaluations Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/evaluations.html) +- [CreateOnlineEvaluationConfig API](https://docs.aws.amazon.com/bedrock-agentcore-control/latest/APIReference/API_CreateOnlineEvaluationConfig.html) +- [Evaluate API](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_Evaluate.html) +- [AgentCore Samples Repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) +- [Fullstack Solution Template for AgentCore](https://github.com/awslabs/amazon-bedrock-agentcore-samples/tree/main/fullstack-solution-template-for-agentcore) +- [CloudWatch Logs Pricing](https://aws.amazon.com/cloudwatch/pricing/) +- [Bedrock Supported Regions](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html) diff --git a/samples/aws-specialist-agent/docs/AGENT_CONFIGURATION.md b/samples/aws-specialist-agent/docs/AGENT_CONFIGURATION.md new file mode 100644 index 0000000..0712fec --- /dev/null +++ b/samples/aws-specialist-agent/docs/AGENT_CONFIGURATION.md @@ -0,0 +1,185 @@ +# Agent Configuration Guide + +FAST supports any agent framework that can run in a container. This guide covers how to use existing patterns, create your own, and configure agent behavior. + +--- + +## Existing Patterns + +### Strands Single Agent Pattern + +**Location**: `agent/strands-single-agent/` + +A basic conversational agent using the Strands framework with AgentCore Memory integration. + +**What This Agent Does**: + +- Multi-turn conversational chat +- Maintains conversation history with short-term memory +- **Optional long-term memory**: When `use_long_term_memory: true` is set in `config.yaml`, the agent uses a `SemanticMemoryStrategy` to extract and recall facts across sessions (keyed by Cognito user ID). See [Memory Integration Guide](MEMORY_INTEGRATION.md#enabling-long-term-memory) for details. +- Streams responses for better UX +- Authenticated via Cognito (user identity tracked in memory) + +**Key Configuration Files**: + +- **Agent Logic**: `agent/strands-single-agent/basic_agent.py` - Main agent implementation with memory integration, model configuration, and streaming logic +- **Python Dependencies**: `agent/strands-single-agent/requirements.txt` - Required Python packages (Strands, bedrock-agentcore, etc.) +- **Container Config**: `agent/strands-single-agent/Dockerfile` - Docker container definition (only used for `deployment_type: docker`) +- **Infrastructure**: `infra-cdk/lib/backend-stack.ts` - CDK configuration for memory resource and runtime deployment + +**Model Configuration** (registry-driven): + +The chat model is no longer hardcoded in `basic_agent.py`. Users pick a model in +the UI, and the selectable list is defined once in the registry: + +```typescript +// infra-cdk/lib/utils/model-registry.ts +export const SELECTABLE_MODELS: readonly SelectableModel[] = [ + { + key: "opus-4.8", + label: "Claude Opus 4.8", + id: "global.anthropic.claude-opus-4-8", + provider: "anthropic", + }, + { + key: "sonnet-5", + label: "Claude Sonnet 5", + id: "global.anthropic.claude-sonnet-5", + provider: "anthropic", + }, + { + key: "sonnet-4.6", + label: "Claude Sonnet 4.6", + id: "global.anthropic.claude-sonnet-4-6", + provider: "anthropic", + default: true, + }, + // ...add a model here (one line) and redeploy +] +``` + +The CDK derives the backend allowlist (`MODEL_MAP` / `DEFAULT_MODEL_KEY` env vars) +and the frontend picker options (`aws-exports.json`) from this single source, so +they cannot drift. To change or add a model, edit this array and redeploy; no +change to `basic_agent.py` is needed. `agent/strands-single-agent/models.py` +resolves the selected logical key to the physical model. Both providers are +live: Claude models run on bedrock-runtime (Converse) and OpenAI GPT models on +the bedrock-mantle OpenAI Responses API. The registry has no `available` flag — +every entry is selectable. + +**System Prompt** (`agent/strands-single-agent/basic_agent.py`): + +```python +system_prompt = """You are a helpful assistant. Answer questions clearly and concisely.""" +``` + +**After making changes**: See [Deployment Guide](DEPLOYMENT.md) for redeployment instructions. + +--- + +## Creating Your Own Agent Pattern + +### Step 1: Create Pattern Directory + +```bash +mkdir -p agent/my-custom-agent +cd agent/my-custom-agent +``` + +### Step 2: Implement Your Agent + +Create your agent code that: + +- Accepts HTTP requests from AgentCore Runtime +- Processes user queries +- Returns responses (streaming or non-streaming) +- Integrates with AgentCore Memory (optional) + +**Example Structure**: + +```python +from bedrock_agentcore.runtime import BedrockAgentCoreApp, RequestContext +from utils.auth import extract_user_id_from_context + +app = BedrockAgentCoreApp() + +@app.entrypoint +async def agent_handler(payload, context: RequestContext): + """Main entrypoint for the agent""" + user_query = payload.get("prompt") + session_id = payload.get("runtimeSessionId") + + # Extract user ID securely from the validated JWT token + # instead of trusting the payload body (which could be manipulated) + user_id = extract_user_id_from_context(context) + + # Your agent logic here + # ... + + yield response + +if __name__ == "__main__": + app.run() +``` + +### Step 3: Create Dockerfile (for Docker deployment only) + +If using `deployment_type: docker` in your config, create a Dockerfile: + +```dockerfile +FROM public.ecr.aws/docker/library/python:3.13-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8080 + +CMD ["python", "your_agent.py"] +``` + +**For ZIP deployment**: No Dockerfile is needed. The ZIP packager automatically bundles your `agent//` directory along with `agent/utils/`, `gateway/`, and `tools/` directories, plus dependencies from `requirements.txt`. + +### Step 4: Update CDK Configuration + +In `infra-cdk/config.yaml`: + +```yaml +backend: + pattern: "my-custom-agent" # Your pattern directory name +``` + +**If your agent needs additional AWS services** (Knowledge Bases, DynamoDB, S3, etc.), modify the CDK stacks in `infra-cdk/lib/`: + +**Example**: Adding a Knowledge Base + +```typescript +// Create your knowledge base construct +const knowledgeBase = new bedrock.CfnKnowledgeBase(this, "KB", { + name: "MyKnowledgeBase", + // ... configuration +}); + +// Add to agent environment variables in backend-stack.ts +EnvironmentVariables: { + KNOWLEDGE_BASE_ID: knowledgeBase.attrKnowledgeBaseId, + // ... other vars +} +``` + +### Step 5: Deploy + +See the [Deployment Guide](DEPLOYMENT.md) for complete deployment instructions. + +## Design notes + +Decisions specific to this derivative: + +- **Selectable models, single source of truth**: the model picker is driven by a registry rather than inferring availability from arbitrary IDs. The registry declares display name, provider, inference profile or endpoint, and capability flags; the frontend and backend both read from it so the picker and the runtime stay in sync. +- **OpenAI models served via Bedrock**: GPT models are added as registry entries and called through the OpenAI Responses API endpoint that Bedrock exposes. This keeps a single authentication and observability path; there is no separate OpenAI key wiring. +- **Direct call simplification**: the OpenAI integration calls the in-region endpoint directly. The earlier cross-region peering setup was removed once the in-region endpoint became available, simplifying the network model and lowering latency. +- **No temperature flag at construction**: some reasoning-tier endpoints reject `temperature`, so the model construction path never sets it; per-model registry flags govern any parameters that vary by endpoint. +- **System prompt is sectioned**: the system prompt is split into well-named sections (Language, Skills first, AWS guidance, Tool routing) so additions land in the right place and the prompt stays maintainable across model providers. diff --git a/samples/aws-specialist-agent/docs/CEDAR_POLICY_GUIDE.md b/samples/aws-specialist-agent/docs/CEDAR_POLICY_GUIDE.md new file mode 100644 index 0000000..35a59fa --- /dev/null +++ b/samples/aws-specialist-agent/docs/CEDAR_POLICY_GUIDE.md @@ -0,0 +1,415 @@ +# Cedar Policy Guide + +This document covers how to write, manage, and extend Cedar policies for AgentCore Gateway. For the identity propagation architecture and component setup, see [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md). + +## Understanding Claims (Custom vs. Standard) + +Cedar policies reference JWT claims via `principal.getTag("claim_name")`. These claims come in two categories: + +### Custom Claims (Application-Defined) + +Custom claims are injected by the V3 Pre-Token Lambda via `claimsToAddOrOverride`. They are not part of the standard JWT/OIDC claim set — they are defined based on the application's access control needs. + +**Claims in this demo:** + +| Claim | Purpose | Example Value | +| ------------ | ----------------------------- | ------------------------ | +| `user_id` | Authenticated user's identity | `"yourname@company.com"` | +| `department` | User's organizational unit | `"finance"` | +| `role` | User's permission level | `"admin"` | + +**Additional custom claim examples:** + +| Claim | Use Case | Cedar Usage | +| ----------------- | ----------------------- | ---------------------------------------------------- | +| `tenant_id` | Multi-tenant isolation | `principal.getTag("tenant_id") == "example-corp"` | +| `clearance_level` | Tiered data access | `principal.getTag("clearance_level") == "top-level"` | +| `region` | Geo-restricted access | `principal.getTag("region") == "us-east-1"` | +| `runtime_env` | Runtime-level isolation | `principal.getTag("runtime_env") == "production"` | + +To add a custom claim: inject it in the Pre-Token Lambda's `claimsToAddOrOverride` dict, then reference it in Cedar via `principal.getTag("claim_name")`. No Gateway configuration change is needed — the CUSTOM_JWT authorizer maps all JWT claims to Cedar tags automatically. + +### Standard Claims (Cognito-Managed) + +Standard claims are automatically included in every token by Cognito. They cannot be overridden by the Pre-Token Lambda. + +| Claim | Description | Modifiable? | +| --------------------- | ------------------------------------------ | ----------- | +| `sub` | Subject identifier (app client ID for M2M) | No | +| `iss` | Token issuer (Cognito user pool URL) | No | +| `client_id` | The app client ID | No | +| `token_use` | Always `"access"` | No | +| `scope` | OAuth scopes granted | No | +| `exp` / `iat` / `jti` | Token timing and ID | No | + +Standard claims are also accessible via `principal.getTag()` in Cedar but are typically used for infrastructure-level checks rather than business logic. + +### What's NOT Available as a Claim + +| Data | Why Not Available | Alternative | +| --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Request headers / IP | Not exposed to Cedar | N/A (not supported) | +| Runtime ARN | Not in Cedar schema | Inject `runtime_env` via Pre-Token Lambda (see [Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control)) | +| Tool input parameters | Not a claim | Use `context.input.` in Cedar | + +## Policy File Location + +`gateway/policies/` — one Cedar statement per file (AgentCore `CreatePolicy` accepts one statement per policy). The shipped set is: + +| File | Tools | Permitted departments | +| ------------------------------ | -------------------------------------------------------- | --------------------- | +| `01-sample-tool.cedar` | `sample-tool-target___text_analysis_tool` | finance, engineering | +| `02-aws-mcp-read.cedar` | AWS MCP read tools | finance, engineering | +| `03-aws-mcp-destructive.cedar` | `aws-mcp___aws___call_aws`, `aws-mcp___aws___run_script` | finance only | + +Edit a file and run `cdk deploy` to apply changes. The Custom Resource Lambda updates the policies in-place without recreating the Policy Engine. Guest (no group) matches no `permit` and is denied by deny-by-default. + +## Action Name Format + +Cedar action names follow the format: `___` (triple underscore). + +- **TargetName** comes from the `CfnGatewayTarget` name in `backend-stack.ts` (e.g., `sample-tool-target`) +- **tool_name** comes from `tool_spec.json` (e.g., `text_analysis_tool`) +- Combined: `sample-tool-target___text_analysis_tool` + +These are case-sensitive. A mismatch silently denies all requests even when the policy logic looks correct. + +## Deny-by-Default + +Cedar is deny-by-default: if no `permit` statement matches a request, it is automatically denied. An explicit `forbid` statement is not needed to block access — simply omit the department from the permit's conditions. + +For example, to deny guests, remove `"guest"` from the department list. No `forbid` statement is required. + +## Tool Discovery vs Execution + +The AgentCore Policy Engine enforces authorization at **two points** in the tool lifecycle: + +### 1. Discovery (`tools/list`) — Tool Filtering + +When the Runtime calls `tools/list` on the Gateway, the Policy Engine evaluates **every tool** against the caller's identity using `PartiallyAuthorizeActions`. Tools that the caller is not permitted to use are **removed from the response**. The agent never sees them. + +``` +Agent → Runtime → Gateway tools/list → Policy Engine (PartiallyAuthorizeActions) + ↓ + Evaluates each tool against principal's claims + ↓ + Returns ONLY permitted tools + ↓ + Agent receives filtered tool list +``` + +**Effect:** If a user with `department=guest` calls `tools/list` while Version 2 (guest denied) is active, the `text_analysis_tool` will NOT appear in the response. The agent has no knowledge the tool exists and will not attempt to call it. + +### 2. Execution (`tools/call`) — Full Context Enforcement + +When the agent calls a specific tool, the Policy Engine evaluates the request with **full context** — including the tool's input parameters (`context.input`). This is a stricter evaluation than discovery because it has access to the actual request payload. + +``` +Agent → Runtime → Gateway tools/call → Policy Engine (AuthorizeAction) + ↓ + Evaluates principal claims + context.input + ↓ + Allow → execute tool + Deny → return authorization error +``` + +**Why both?** A tool might pass discovery filtering (the user is generally allowed to use it) but fail at execution time due to input-specific conditions. For example: + +```cedar +// User can discover the refund tool (passes tools/list filtering) +// But execution is denied if amount > 1000 (fails tools/call check) +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"billing-target___process_refund", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" && + context.input.amount < 1000 +}; +``` + +In this example, a finance user would see `process_refund` in `tools/list` (they're in the finance department), but if they try to process a refund of $5000, the `tools/call` would be denied because `context.input.amount < 1000` fails. + +### Verifying Discovery Filtering in CloudWatch + +To confirm that denied tools are being filtered from `tools/list`: + +1. Enable tracing on both the Runtime and Gateway (see [Verifying Policy Decisions via Tracing](IDENTITY_POLICY.md#verifying-policy-decisions-via-tracing)) +2. Trigger a query from the frontend +3. In CloudWatch → `aws/spans` log group, filter for `PartiallyAuthorizeActions` +4. The span contains: + - `aws.agentcore.policy.allowed_tools`: tools returned to the agent + - `aws.agentcore.policy.denied_tools`: tools filtered out + - `aws.agentcore.gateway.policy.mode`: should show `ENFORCE` + +> **Verifying at the Runtime level:** Add a log line in the agent code to confirm which tools the agent received after Cedar policy filtering. Example for the Strands agent pattern: +> +> **Strands pattern** (`agent/strands-single-agent/basic_agent.py`) — add after `Agent()` creation: +> +> ```python +> agent = Agent( +> name="strands_agent", +> tools=[gateway_client, code_tools.execute_python_securely], +> ... +> ) +> specs = agent.tool_registry.get_all_tool_specs() +> logger.info(f"[GATEWAY] Raw tool specs: {specs}") +> return agent +> ``` +> +> **Where to find:** CloudWatch → Log groups → `/aws/bedrock-agentcore/runtimes/{runtime_name}` → log stream `otel-rt-logs`. Search for `[GATEWAY] Raw tool specs`. + +### Summary + +| Stage | API | Evaluation | What Happens on Deny | +| ------------------------ | --------------------------- | ---------------------------------------- | ------------------------------------------------- | +| Discovery (`tools/list`) | `PartiallyAuthorizeActions` | Principal claims only (no input context) | Tool is hidden — agent never sees it | +| Execution (`tools/call`) | `AuthorizeAction` | Principal claims + `context.input` | Request rejected — agent gets authorization error | + +## Adding New Tools + +When adding a new Gateway target and tool: + +1. Create the new Lambda tool and `CfnGatewayTarget` in `backend-stack.ts` +2. Add a new policy file (one statement) under `gateway/policies/` with the correct action name +3. Run `cdk deploy` + +Each `create_policy` call creates one policy containing one Cedar statement. The Custom Resource currently creates a single policy per deploy. To add multiple policies (e.g., separate permit and forbid statements), update the Custom Resource Lambda to call `create_policy()` once per statement. + +## Cedar Schema Constraints + +AgentCore Gateway validates Cedar policies against an auto-generated schema derived from the Gateway's MCP tool manifest. Policies that reference unsupported fields will fail during creation, causing CloudFormation rollback. + +**Supported in Cedar policies:** + +| Element | What Can Be Referenced | Example | +| ------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------- | +| `principal` | Must be `AgentCore::OAuthUser` | `principal is AgentCore::OAuthUser` | +| `principal.hasTag()` / `principal.getTag()` | Any JWT claim mapped by the CUSTOM_JWT authorizer | `principal.getTag("department")` | +| `action` | Tool actions in `___` format | `AgentCore::Action::"sample-tool-target___text_analysis_tool"` | +| `resource` | The Gateway ARN | `AgentCore::Gateway::"arn:aws:..."` | +| `context.input` | Tool input parameters as defined in the MCP manifest | `context.input.query` | + +**NOT supported in Cedar policies:** + +| Element | Why | +| ------------------------------------ | ----------------------------------------------------------- | +| `context.runtime.arn` | Not in the schema — only `context.input` is available | +| Custom entity types | Cannot define entities outside the `AgentCore` namespace | +| Custom attributes on `OAuthUser` | Use `hasTag()`/`getTag()` instead of direct property access | +| Request metadata (headers, IP, etc.) | Not exposed to Cedar | + +If access control decisions depend on information not available in `context.input`, inject it as a JWT claim via the Pre-Token Lambda and access it via `principal.getTag()`. See [Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control) for an example of this pattern. + +## Cedar Policy Capabilities + +Cedar is a purpose-built policy language designed for authorization. This section documents what can be expressed in Cedar policies for AgentCore Gateway, with practical examples for each capability. + +> **Already demonstrated in this project:** +> +> - Identity-based access (`principal.getTag("department") == "finance"`) — see [Cedar Policy File](IDENTITY_POLICY.md#cedar-policy-file) Version 1 & 2 +> - Multi-value OR conditions (`department == "finance" || department == "engineering"`) — see Version 1 policy +> +> The capabilities below show **additional patterns** that can be implemented using the same infrastructure. + +### Capability 1: Input Validation (`context.input`) + +**Scenario:** Finance users can process refunds, but only up to $1000. Refunds above $1000 require a different approval workflow. + +**How it works:** `context.input` gives Cedar access to the tool's input parameters (as defined in the MCP tool manifest). Conditions can be written against these values. The tool still appears in `tools/list` for finance users (discovery only checks principal claims), but the $1000 limit is enforced at `tools/call` time when the actual input is available. + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"billing-target___process_refund", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" && + context.input.amount < 1000 +}; +``` + +**Result:** + +- Finance user, amount=500 → permitted +- Finance user, amount=5000 → denied (exceeds limit) +- Engineering user, amount=100 → denied (wrong department) + +**Important:** This is where [Tool Discovery vs Execution](#tool-discovery-vs-execution) matters most. The tool passes discovery filtering (finance user is generally permitted), but execution is denied when the input violates the condition. + +--- + +### Capability 2: Multi-Tool Policies (`action in [...]`) + +**Scenario:** Developers can use all read-only tools (list, get, search) but cannot use write tools (create, update, delete). + +**How it works:** Use `action in [...]` to apply one policy to multiple tools at once, instead of writing separate `permit` statements for each tool. + +```cedar +permit( + principal is AgentCore::OAuthUser, + action in [ + AgentCore::Action::"data-target___list_records", + AgentCore::Action::"data-target___get_record", + AgentCore::Action::"data-target___search_records" + ], + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("role") && + principal.getTag("role") == "developer" +}; +``` + +**Result:** + +- Developer calls `list_records` → permitted +- Developer calls `search_records` → permitted +- Developer calls `delete_record` → denied (not in the action list) + +> **Note:** Separate `permit` statements can also be written for each tool. The `action in [...]` syntax is a convenience for grouping related tools under the same conditions. + +--- + +### Capability 3: Explicit Deny (`forbid`) + +**Scenario:** All departments are allowed to use a tool, EXCEPT a specific user (e.g., a compromised account) needs to be explicitly blocked regardless of their department. + +**How it works:** `forbid` statements override `permit` statements. Cedar's conflict resolution is "forbid wins" — if both a `permit` and `forbid` match, the request is denied. + +```cedar +// Allow all departments +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") +}; + +// But explicitly block a specific user +forbid( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_id") && + principal.getTag("user_id") == "compromised-user@example.com" +}; +``` + +**Result:** + +- Any user with a department → permitted +- `compromised-user@example.com` → denied (forbid wins over permit) + +> **Note:** Cedar's deny-by-default means simply omitting a user/department from the `permit` +> is often sufficient to deny access. Use `forbid` when overriding a broad `permit` +> for specific cases — such as blocking a compromised user, disabling a tool during an incident, +> or implementing an emergency shutdown. + +--- + +### Capability 4: Wildcard String Matching (`like`) + +**Scenario:** Only users with an `@example.com` email domain can access internal tools. External contractors with other email domains are denied. + +**How it works:** Use `like` with `*` wildcard for pattern matching on string claim values. + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"internal-target___internal_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_id") && + principal.getTag("user_id") like "*@example.com" +}; +``` + +**Result:** + +- `alice@example.com` → permitted +- `bob@example.com` → permitted +- `contractor@external.com` → denied (doesn't match pattern) + +> **Note:** `like` only supports `*` as a wildcard, which matches zero or more characters +> of any kind (letters, numbers, symbols, dots, etc.). It does not support regex, +> single-character wildcards, character classes, or other pattern syntax. + +--- + +### Capability 5: Environment-Based Access Control + +**Scenario:** Production tools should only be accessible from the production runtime. Staging runtimes should not be able to call production tools even if the user has the right department/role. + +**How it works:** The Pre-Token Lambda maps the Cognito `clientId` to a `runtime_env` claim (see [Runtime-Level Access Control](IDENTITY_POLICY.md#runtime-level-access-control)). Cedar checks both user identity AND runtime environment. + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"prod-target___production_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("runtime_env") && + principal.getTag("runtime_env") == "production" && + principal.hasTag("department") && + principal.getTag("department") == "finance" +}; +``` + +**Result:** + +- Finance user from production runtime → permitted +- Finance user from staging runtime → denied (wrong environment) +- Engineering user from production runtime → denied (wrong department) + +--- + +### Quick Reference: Cedar Operators + +| Operator | Meaning | Example | +| -------------------- | ----------------------- | ---------------------------------------------------- | +| `==` | Equals | `principal.getTag("role") == "admin"` | +| `!=` | Not equals | `principal.getTag("department") != "restricted"` | +| `&&` | AND (both must be true) | `condition_a && condition_b` | +| `\|\|` | OR (either can be true) | `value == "a" \|\| value == "b"` | +| `<`, `>`, `<=`, `>=` | Numeric comparison | `context.input.amount < 1000` | +| `in [...]` | Action is one of a set | `action in [Action::"a", Action::"b"]` | +| `like` | Wildcard string match | `principal.getTag("email") like "*@example.com"` | +| `hasTag()` | Claim exists in token | `principal.hasTag("department")` | +| `getTag()` | Get claim value | `principal.getTag("department")` | +| `has` | Field/attribute exists | `context.input has shippingAddress` | +| `.contains()` | Set membership | `["US", "CA", "MX"].contains(context.input.country)` | + +### What Cedar CANNOT Do + +| Limitation | Workaround | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Regular expressions | Use `like` with `*` wildcard for simple patterns | +| Arithmetic operations (e.g., `a + b > c`) | Pre-compute in the Pre-Token Lambda and inject as a claim | +| External data lookups (e.g., query a database) | Resolve in the Pre-Token Lambda and inject as a claim | +| Time-based rules (e.g., "only during business hours") | Inject a `time_window` claim from the Pre-Token Lambda | +| Array/list membership (e.g., "user in allowed_list") | Use `.contains()` for hardcoded lists: `["a", "b"].contains(context.input.x)`. For dynamic lists (loaded from a database), resolve in the Pre-Token Lambda and inject as a boolean claim | +| Request headers, IP address, or network context | Not exposed to Cedar — not available | + +> **Architecture Pattern:** When Cedar cannot evaluate something directly (time, +> external data, complex logic), resolve it in the Pre-Token Lambda and inject the +> result as a custom claim. Cedar then checks the pre-resolved value. This keeps +> policies simple, deterministic, and auditable. + +## Design notes + +Decisions specific to this derivative: + +- **Cedar v2 with guest denied by default**: the policy set rejects unauthenticated callers and requires an authenticated principal to invoke any tool, instead of relying on application-level filtering after the fact. +- **Per-user ABAC via Cognito groups**: department-style group membership is mapped into Cedar attributes through a pre-token Lambda, so tool access can be allowed/denied per user without rewriting policies. Closed-network deployments still work because the resolution happens server-side in Cognito. +- **Cedar policy Lambda permissions**: the Custom Resource that generates the Cedar policy needs `bedrock-agentcore:ListGatewayTargets` and `bedrock-agentcore:InvokeGateway` to enumerate and verify the target catalog. Missing either permission produces a deployment-time error rather than a runtime failure, which makes the regression easy to diagnose. diff --git a/samples/aws-specialist-agent/docs/CONTEXT_MANAGEMENT.md b/samples/aws-specialist-agent/docs/CONTEXT_MANAGEMENT.md new file mode 100644 index 0000000..a634406 --- /dev/null +++ b/samples/aws-specialist-agent/docs/CONTEXT_MANAGEMENT.md @@ -0,0 +1,618 @@ +# Context Management Guide + +Practical guide for managing LLM context windows in long-running or multi-turn agent conversations within FAST. + +As agents handle longer conversations — especially those involving many tool calls, large tool results, or iterative workflows — the conversation history can grow to exceed the model's context window. Even before overflow, large contexts degrade model performance, increase latency, and balloon costs. Context management strategies address this by proactively or reactively compressing, trimming, or summarizing conversation history. + +This guide covers the built-in options available in Strands and LangGraph, when to use each, and how to implement a fully custom solution when the built-in options don't fit your use case. + +--- + +## When Do You Need Context Management? + +You likely need context management if your agent: + +- Runs **multi-turn conversations** that accumulate many messages over time +- Performs **iterative workflows** with many sequential tool calls (e.g., code generation loops, data analysis pipelines) +- Returns **large tool results** (e.g., file contents, API responses, screenshots) +- Runs **autonomously for extended periods** without human intervention + +For simple single-turn or short multi-turn chat agents, the default behavior (no explicit context management) is usually sufficient, especially with the most powerful LLMs with large (200k+ token) context windows. + +--- + +## Strategy Comparison + +| Strategy | Information Loss | Complexity | Best For | +| ------------------------- | ----------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Sliding Window** | High — older messages are dropped entirely | Low | Simple chat bots, short conversations, user experiences which don't require referencing older messages, or applications with long term memory built-in to handle referencing older topics | +| **Summarization** | Low — key information is preserved in compressed form | Medium | Multi-turn assistants, iterative workflows, user experiences where pausing for several seconds when the context window overflows (to generate the summary) are acceptable. | +| **Proactive Compression** | Low–Medium — triggers before overflow | Medium | Long-running autonomous agents, applications using weaker LLMs which struggle to return high quality results when their context window is over 50% full. | +| **Custom Hook-Based** | Configurable — full control over what is preserved | High | Specialized long-running agents with external memory | + +--- + +## Option 1: Sliding Window (Strands) + +The simplest approach. Keeps the N most recent messages and discards older ones. Strands provides `SlidingWindowConversationManager` out of the box. + +### How It Works + +- Maintains a fixed window of messages (default: 40) +- When the window is exceeded, the oldest messages are removed +- Preserves tool use/result pairs to avoid invalid conversation state +- Optionally truncates large tool results (keeping first/last 200 characters) + +### Configuration + +```python +from strands import Agent +from strands.agent.conversation_manager import SlidingWindowConversationManager + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SlidingWindowConversationManager( + window_size=40, # Max messages to keep (default: 40) + should_truncate_results=True, # Truncate large tool results (default: True) + ), +) +``` + +### Proactive Compression + +The sliding window manager also supports proactive compression, which triggers context reduction before the context window overflows rather than waiting for an error: + +```python +conversation_manager=SlidingWindowConversationManager( + window_size=40, + proactive_compression=True, # Compress at 70% context usage (default threshold) +) + +# Or with a custom threshold: +conversation_manager=SlidingWindowConversationManager( + window_size=40, + proactive_compression={"compression_threshold": 0.5}, # Compress at 50% +) +``` + +### Per-Turn Management + +For agents that perform many tool operations in loops (e.g., web browsing with frequent screenshots), enable per-turn management to proactively trim before every model call: + +```python +conversation_manager=SlidingWindowConversationManager( + window_size=40, + per_turn=True, # Apply before every model call + # per_turn=5, # Or apply every 5 model calls +) +``` + +### Applying to FAST Strands Pattern + +To add sliding window management to the `agent/strands-single-agent/basic_agent.py`, pass the `conversation_manager` parameter when constructing the `Agent`: + +```python +from strands.agent.conversation_manager import SlidingWindowConversationManager + +# In create_strands_agent(): +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=[gateway_client, code_tools.execute_python_securely], + conversation_manager=SlidingWindowConversationManager(window_size=40), + session_manager=session_manager, +) +``` + +### Pros and Cons + +| Pros | Cons | +| ------------------------- | ---------------------------------------------------------- | +| Zero additional LLM calls | Complete loss of older context | +| No added latency or cost | Agent "forgets" earlier conversation | +| Simple to configure | Not suitable for long-running tasks requiring full history | + +📚 **Strands Docs**: [SlidingWindowConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.sliding_window_conversation_manager/) + +--- + +## Option 2: Summarizing Conversation Manager (Strands) + +Summarizes older messages using an LLM call instead of discarding them entirely. This preserves key information while reducing token count. + +### How It Works + +- When context overflow occurs (or proactive threshold is reached), the oldest N% of messages are summarized +- The summary replaces the original messages as a single user message +- Recent messages are preserved verbatim +- A separate LLM call generates the summary + +### Configuration + +```python +from strands import Agent +from strands.agent.conversation_manager import SummarizingConversationManager + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, # Summarize oldest 30% of messages (default) + preserve_recent_messages=10, # Always keep last 10 messages (default) + proactive_compression=True, # Compress at 70% context usage + ), +) +``` + +### Using a Separate Summarization Agent + +By default, the summarizing manager uses the parent agent (with its tools) to generate summaries. For more control, you can provide a dedicated summarization agent: + +```python +from strands import Agent +from strands.agent.conversation_manager import SummarizingConversationManager + +# Lightweight agent just for summarization — no tools needed +summarizer = Agent( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + system_prompt="You are a conversation summarizer. Create concise bullet-point summaries.", +) + +agent = Agent( + model=model, + tools=tools, + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, + preserve_recent_messages=10, + summarization_agent=summarizer, + proactive_compression={"compression_threshold": 0.5}, + ), +) +``` + +### Applying to FAST Strands Pattern + +```python +from strands.agent.conversation_manager import SummarizingConversationManager + +# In create_strands_agent(): +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=[gateway_client, code_tools.execute_python_securely], + conversation_manager=SummarizingConversationManager( + summary_ratio=0.3, + preserve_recent_messages=10, + proactive_compression=True, + ), + session_manager=session_manager, +) +``` + +### Pros and Cons + +| Pros | Cons | +| -------------------------------------------- | ----------------------------------------- | +| Preserves key information from older context | Additional LLM call adds latency and cost | +| Configurable compression ratio | Summary quality depends on the model | +| Built-in, no custom code needed | May lose nuance or specific details | + +📚 **Strands Docs**: [SummarizingConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.summarizing_conversation_manager/) + +--- + +## Option 3: LangGraph Middleware (Trim / Summarize) + +LangGraph provides middleware-based approaches for context management using `@before_model` decorators. + +### Trim Messages + +Remove older messages before each model call, keeping only recent ones: + +```python +from langchain.messages import RemoveMessage +from langgraph.graph.message import REMOVE_ALL_MESSAGES +from langchain.agents import create_agent, AgentState +from langchain.agents.middleware import before_model +from langgraph.runtime import Runtime +from typing import Any + + +@before_model +def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None: + """Keep only the last few messages to fit context window.""" + messages = state["messages"] + if len(messages) <= 10: + return None + + first_msg = messages[0] + recent_messages = messages[-10:] + return { + "messages": [ + RemoveMessage(id=REMOVE_ALL_MESSAGES), + first_msg, + *recent_messages, + ] + } +``` + +### Summarize Messages + +Use the built-in `SummarizationMiddleware` for automatic summarization: + +```python +from langchain.agents import create_agent +from langchain.agents.middleware import SummarizationMiddleware +from langgraph.checkpoint.memory import InMemorySaver + +agent = create_agent( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + tools=tools, + middleware=[ + SummarizationMiddleware( + model="us.anthropic.claude-sonnet-4-20250514-v1:0", + trigger=("tokens", 4000), # Trigger when token count exceeds 4000 + keep=("messages", 20), # Keep last 20 messages verbatim + ) + ], + checkpointer=InMemorySaver(), +) +``` + +### Pros and Cons + +| Pros | Cons | +| -------------------------------------------- | ------------------------------------ | +| Native LangGraph integration | Requires LangGraph-specific patterns | +| Composable with other middleware | Different API than Strands | +| `SummarizationMiddleware` handles complexity | Token counting adds minor overhead | + +📚 **LangGraph Docs**: [Short-term Memory & Summarization](https://docs.langchain.com/oss/python/langchain/short-term-memory) + +--- + +## Option 4: Custom Hook-Based Context Management (Strands) + +For advanced use cases — particularly long-running autonomous agents — the built-in managers may not provide enough control. You can implement a fully custom solution using Strands hooks. + +This approach is ideal when you need to: + +- Trigger summarization at a specific **percentage** of context window usage (not just on overflow) +- **Re-inject external memory** (e.g., a log file, knowledge base, or structured state) after compression +- Use a **raw Bedrock Converse call** for summarization (avoiding the agent loop and tool invocations) +- Emit **custom stream events** to notify the frontend about context management activity +- Implement **fallback strategies** when summarization fails + +### Architecture + +The pattern uses two components working together: + +1. **A no-op `ConversationManager`** — Disables Strands' built-in context management entirely +2. **A `HookProvider`** — Registers on `BeforeModelCallEvent` to perform proactive context management before every LLM call + +### Implementation + +#### Step 1: Create a No-Op Conversation Manager inheriting from `strands.agent.conversation_manager.ConversationManager` + +```python +from strands.agent.conversation_manager import ConversationManager + + +class NoOpConversationManager(ConversationManager): + """Disables built-in context management. + + Strands requires a ConversationManager but we handle context reduction + ourselves via a hook. Both methods are intentionally empty. + """ + + def apply_management(self, agent, **kwargs): + """No-op: context management is handled by the hook.""" + pass + + def reduce_context(self, agent, **kwargs): + """No-op: context management is handled by the hook.""" + pass +``` + +#### Step 2: Create the Context Check Hook + +```python +import logging +import os + +import boto3 +from strands.hooks import HookProvider, HookRegistry, BeforeModelCallEvent + +logger = logging.getLogger(__name__) + +# Set this to your model's context window size +CONTEXT_WINDOW_TOKENS = 200_000 # e.g., Claude Sonnet + +SUMMARIZATION_PROMPT = """You are a conversation summarizer. Provide a concise summary. + +Format Requirements: +- Create a structured summary in bullet-point format +- Do NOT respond conversationally +- Include: key decisions, tool executions and results, current state, next steps +""" + + +class ContextCheckHook(HookProvider): + """Proactively summarize conversation when context usage exceeds threshold. + + Fires on BeforeModelCallEvent. When context exceeds threshold_pct, + summarizes older messages via a direct Bedrock Converse call (no agent + loop, no tools), preserving the most recent messages verbatim. + + Args: + threshold_pct: Percentage of context window that triggers summarization. + preserve_recent: Number of most recent messages to keep verbatim. + model_id: Model ID to use for the summarization call. + """ + + def __init__( + self, + threshold_pct: float = 50.0, + preserve_recent: int = 6, + model_id: str = "us.anthropic.claude-sonnet-4-20250514-v1:0", + ): + self.threshold_pct = threshold_pct + self.preserve_recent = preserve_recent + self._model_id = model_id + self._bedrock = None + + def register_hooks(self, registry: HookRegistry, **kwargs) -> None: + """Register the before-model-call check.""" + registry.add_callback(BeforeModelCallEvent, self._check) + + def _check(self, event: BeforeModelCallEvent) -> None: + """Check context usage and trigger summarization if threshold exceeded.""" + agent = event.agent + # Walk backward to find the last assistant message with usage metadata + for msg in reversed(agent.messages): + if msg.get("role") == "assistant": + usage = msg.get("metadata", {}).get("usage", {}) + if usage: + input_tokens = usage.get("inputTokens", 0) + cache_tokens = usage.get("cacheReadInputTokens", 0) + pct = (input_tokens + cache_tokens) / CONTEXT_WINDOW_TOKENS * 100 + if pct >= self.threshold_pct: + logger.info("Context at %.1f%% — summarizing", pct) + self._summarize_and_replace(agent) + return # Only check the most recent assistant message + + def _summarize_and_replace(self, agent) -> None: + """Summarize older messages and replace them with the summary.""" + messages = agent.messages + if len(messages) <= self.preserve_recent: + return + + # Find split point — avoid breaking tool_use/tool_result pairs + split = len(messages) - self.preserve_recent + while split > 0 and self._is_tool_result(messages[split]): + split -= 1 + if split <= 0: + return + + to_summarize = messages[:split] + to_keep = messages[split:] + + # Convert to text-only format for the summarization call + converse_messages = self._to_text_only(to_summarize) + converse_messages.append({ + "role": "user", + "content": [{"text": "Please summarize this conversation."}], + }) + + # Direct Bedrock Converse call — no agent loop, no tools + if not self._bedrock: + self._bedrock = boto3.client( + "bedrock-runtime", + region_name=os.environ.get("AWS_DEFAULT_REGION", "us-east-1"), + ) + + try: + response = self._bedrock.converse( + modelId=self._model_id, + system=[{"text": SUMMARIZATION_PROMPT}], + messages=converse_messages, + inferenceConfig={"maxTokens": 4096}, + ) + summary_text = response["output"]["message"]["content"][0]["text"] + except Exception as e: + logger.error("Summarization failed: %s — falling back to truncation", e) + summary_text = "(conversation history truncated due to context limits)" + + # Replace agent messages in-place + agent.messages[:] = [ + {"role": "user", "content": [{"text": f"## Previous Conversation Summary\n\n{summary_text}"}]}, + {"role": "assistant", "content": [{"text": "Understood. I'll continue from where we left off."}]}, + ] + to_keep + + def _is_tool_result(self, msg: dict) -> bool: + """Check if a message contains a toolResult block.""" + content = msg.get("content", []) + if isinstance(content, list): + return any(isinstance(b, dict) and "toolResult" in b for b in content) + return False + + def _to_text_only(self, messages: list[dict]) -> list[dict]: + """Convert messages to text-only format (strips toolUse/toolResult blocks). + + This is necessary because Bedrock Converse API doesn't allow toolUse + blocks without corresponding tool definitions in the request. + """ + result = [] + for msg in messages: + role = msg.get("role") + if role not in ("user", "assistant"): + continue + content = msg.get("content", []) + if isinstance(content, str): + result.append({"role": role, "content": [{"text": content}]}) + continue + text_blocks = [] + for block in content: + if isinstance(block, dict): + if "text" in block: + text_blocks.append({"text": block["text"]}) + elif "toolUse" in block: + name = block["toolUse"].get("name", "unknown") + text_blocks.append({"text": f"[Called tool: {name}]"}) + elif "toolResult" in block: + tr_content = block["toolResult"].get("content", []) + snippet = next( + (c["text"][:200] for c in tr_content if isinstance(c, dict) and "text" in c), + "result received", + ) + text_blocks.append({"text": f"[Tool result: {snippet}]"}) + if text_blocks: + result.append({"role": role, "content": text_blocks}) + + # Ensure alternating user/assistant (Bedrock requirement) + cleaned = [] + for msg in result: + if cleaned and cleaned[-1]["role"] == msg["role"]: + cleaned[-1]["content"].extend(msg["content"]) + else: + cleaned.append(msg) + if cleaned and cleaned[0]["role"] == "assistant": + cleaned.insert(0, {"role": "user", "content": [{"text": "(start of conversation)"}]}) + + return cleaned +``` + +#### Step 3: Wire It Into the Agent + +```python +from strands import Agent + +agent = Agent( + model=model, + system_prompt=SYSTEM_PROMPT, + tools=tools, + hooks=[ContextCheckHook(threshold_pct=50.0, preserve_recent=6)], + conversation_manager=NoOpConversationManager(), + session_manager=session_manager, +) +``` + +### Advanced: Re-Injecting External Memory After Summarization + +For long-running agents that maintain structured state outside the conversation (e.g., a progress log, a results file, or a knowledge base), you can re-inject that state after summarization to restore critical context that may have been compressed: + +```python +def _summarize_and_replace(self, agent) -> None: + # ... (summarization logic as above) ... + + # After replacing messages, re-inject external state + self._inject_external_state(agent) + +def _inject_external_state(self, agent) -> None: + """Re-inject structured external state after context compression.""" + state_path = os.environ.get("AGENT_STATE_FILE", "") + if not state_path: + return + try: + with open(state_path) as f: + state_content = f.read() + except FileNotFoundError: + return + + agent.messages.append({ + "role": "user", + "content": [{"text": ( + "Context was compressed. Here is the current state log — " + "use it to recall what has been done and the current status:\n\n" + + state_content + )}], + }) +``` + +This pattern is particularly powerful for agents that: + +- Maintain a running log of actions taken and results observed +- Need to preserve structured data (tables, configurations) across compressions +- Operate in iterative optimization loops where history of attempts matters + +### Pros and Cons + +| Pros | Cons | +| -------------------------------------------------------------- | --------------------------------------------------- | +| Full control over trigger timing and compression behavior | More code to write and maintain | +| Can integrate external memory re-injection | Must handle Bedrock API format constraints manually | +| Proactive — fires before overflow, preserving more detail | Requires understanding of message format internals | +| Direct Bedrock call avoids agent loop / tool invocation issues | Must handle tool_use/tool_result pair splitting | + +--- + +## Design Decisions and Best Practices + +### Choosing a Threshold + +- **70%** (default for built-in proactive compression) — Good for most multi-turn chat agents. Leaves headroom for the model's response. +- **50%** — Better for long-running autonomous agents that accumulate context rapidly. Triggers earlier, preserving more detail in the summary. +- **On overflow only** (reactive) — Simplest, but risks losing information if the overflow error discards the last request. + +Overall the threshold is a metric that can be quantitatively determined if you have data to test on. For example try running with a variety of thresholds and see at what threshold hallucinations start to happen and be sure to set a threshold below that. This can also be done with A/B testing and collecting user feedback signals. + +### Preserving Tool Pairs + +When trimming or splitting messages, never break a `toolUse` block from its corresponding `toolResult`. This creates an invalid conversation state that will cause API errors. Always walk backward from your split point to find a clean boundary. + +### Text-Only Conversion for Summarization + +When making a separate Bedrock Converse call for summarization, you cannot include `toolUse` or `toolResult` blocks without also providing the corresponding tool definitions. The simplest solution is to convert these blocks to text descriptions (e.g., `[Called tool: analyze_data]`, `[Tool result: 42 records processed]`). + +### Cost Considerations + +Each summarization call is an additional LLM invocation: + +- A summarization of ~50K tokens of context using Claude Sonnet costs approximately $0.15–$0.25 +- For agents that trigger summarization frequently, consider using a smaller/cheaper model for the summarization call +- The sliding window approach has zero additional cost but loses information + +### Conversation Validity Rules (Bedrock Converse API) + +When manipulating `agent.messages` directly, ensure: + +1. Messages alternate between `user` and `assistant` roles +2. The conversation starts with a `user` message +3. Every `toolUse` block has a corresponding `toolResult` in the next user message +4. No empty content blocks + +--- + +## Quick Reference: Which Option to Choose + +| Use Case | Recommended Approach | +| --------------------------------------------- | --------------------------------------------------------- | +| Simple chatbot, short conversations | Sliding Window (Option 1) | +| Multi-turn assistant, moderate length | Summarizing Manager (Option 2) with proactive compression | +| LangGraph-based agent | LangGraph Middleware (Option 3) | +| Long-running autonomous agent (hours) | Custom Hook (Option 4) with external memory re-injection | +| Agent with large tool results (images, files) | Sliding Window with `per_turn=True` and truncation | + +--- + +## Further Reading + +- [Strands ConversationManager API](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.conversation_manager/) +- [Strands SlidingWindowConversationManager](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.sliding_window_conversation_manager/) +- [Strands SummarizingConversationManager](https://strandsagents.com/docs/api/python/strands.agent.conversation_manager.summarizing_conversation_manager/) +- [Strands Hooks API](https://strandsagents.com/docs/api/python/strands.hooks.events/) +- [LangGraph Short-term Memory](https://docs.langchain.com/oss/python/langchain/short-term-memory) +- [LangMem Summarization Guide](https://langchain-ai.github.io/langmem/guides/summarization/) +- [FAST Memory Integration Guide](./MEMORY_INTEGRATION.md) + +## Design notes + +Chat UX decisions specific to this derivative: + +- **Chat history sidebar with Lambda-backed restore**: previous conversations are listed by hitting a dedicated history API and individual sessions are restored by querying a DynamoDB index. Storing only an index in DDB (not the full messages) keeps writes cheap and avoids duplicating Memory state. +- **Slim streaming events**: streaming payloads are minimised to avoid hitting the AgentCore Runtime per-event size limit. Diagnostic data is omitted from the wire format and reconstructed client-side. +- **Sticky auto-scroll**: the chat view auto-scrolls during streaming only while the user is at the bottom, so reading earlier messages mid-stream is not interrupted. +- **Cmd+Enter to send, IME-safe**: composition events from Japanese/Chinese input methods are detected so Enter during conversion does not submit. Cmd/Ctrl+Enter is the canonical submit binding. +- **Tolerate OpenAI tool-use ID reuse**: OpenAI re-uses `toolUseId` values across rounds in the same turn while Anthropic does not. The chat UI tracks tool calls without assuming IDs are globally unique. +- **Mermaid rendering**: fenced ` ```mermaid ` blocks in agent responses are rendered to SVG client-side so architecture answers can include diagrams without extra tooling. diff --git a/samples/aws-specialist-agent/docs/DEPLOYMENT.md b/samples/aws-specialist-agent/docs/DEPLOYMENT.md new file mode 100644 index 0000000..4608e6d --- /dev/null +++ b/samples/aws-specialist-agent/docs/DEPLOYMENT.md @@ -0,0 +1,520 @@ +# Deployment Guide + +This guide walks you through deploying the Fullstack AgentCore Solution Template (FAST) to AWS. + +## Prerequisites + +Before deploying, ensure you have: + +- **Node.js 20+** installed (see [AWS guide for installing Node.js on EC2](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html)) +- **AWS CLI** configured with credentials (`aws configure`) - see [AWS CLI Configuration guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) +- **AWS CDK CLI** installed: `npm install -g aws-cdk` (see [CDK Getting Started guide](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)) +- **Python 3.11 or above+** (standard library only - no virtual environment needed for deployment) +- **Docker** - Required for all deployments. See [Install Docker Engine](https://docs.docker.com/engine/install/). Verify with `docker ps`. Alternatively, [Finch](https://github.com/runfinch/finch) can be used on Mac. See below if you have a non-ARM machine. +- An AWS account with sufficient permissions to create: + - S3 buckets + - CloudFront distributions + - Cognito User Pools + - Amplify Hosting projects + - Bedrock AgentCore resources + - IAM roles and policies + +### Deploying to a different account or region + +The committed `config.yaml` is validated for **us-east-1**. Within the same account and region you can deploy as-is — just give the stack a non-overlapping `vpc_cidr` and a real `admin_user_email`. Deploying into a **different account or region** additionally requires: + +1. **Region must be us-east-1 (for now).** AgentCore Web Search is only available in us-east-1, and the deploy intentionally fails fast elsewhere. The OpenAI (GPT) models are also reached through the in-region `bedrock-mantle` endpoint. To deploy to another region you would need to remove the Web Search target and the OpenAI models first. +2. **Pin account-correct AZs.** AgentCore Runtime VPC mode only supports specific AZ **ids** (us-east-1: `use1-az1`, `use1-az2`, `use1-az4`), and the AZ name → id mapping is account-specific. The default `availability_zones: [us-east-1b, us-east-1d]` maps to supported ids in the validation account, but may not in yours. Re-derive the right names with `aws ec2 describe-availability-zones --query "AvailabilityZones[].[ZoneName,ZoneId]" --output table` and set `backend.availability_zones` to two names that map to supported ids; otherwise subnet creation fails. +3. **Enable Bedrock model access.** The selectable models (Claude Fable 5 / Opus / Sonnet / Haiku and OpenAI GPT) must be enabled in your account. Claude Fable 5 additionally needs the account's Bedrock data retention mode set to `provider_data_share` in the calling region (see "Enabling Claude Fable 5" below). Models you do not have access to should be set `available: false` in `infra-cdk/lib/utils/model-registry.ts`. + +## Configuration + +### 1. Update Configuration File + +Edit `infra-cdk/config.yaml` to customize your deployment: + +```yaml +stack_name_base: your-project-name # Change this to your preferred stack name (max 35 chars) + +admin_user_email: null # Optional: admin@example.com (auto-creates user & emails credentials) + +backend: + pattern: strands-single-agent # Available patterns: strands-single-agent + deployment_type: docker # Available deployment types: docker (default), zip +``` + +**Important**: + +- Change `stack_name_base` to a unique name for your project to avoid conflicts +- Maximum length is 35 characters (due to AWS AgentCore runtime naming constraints) +- The committed `infra-cdk/config.yaml` ships `admin_user_email` as a placeholder (`your-email+fastprojectadmin@example.com`). **Replace it with your own real, reachable address before deploying** — the admin user's temporary password is emailed there, so a placeholder leaves you unable to sign in. +- Department/role authorization is driven by Cognito **group membership**, not the email address. Group membership is operational data that CDK does not manage, so after deploying, add the admin user to a group (e.g. `aws cognito-idp admin-add-user-to-group --user-pool-id --username --group-name finance`) — otherwise every Gateway tool call is denied as "guest". + +### Deployment Types + +FAST supports two deployment types for AgentCore Runtime. Set `deployment_type` in `infra-cdk/config.yaml`: + +| Type | Description | +| ------------------ | --------------------------------------- | +| `docker` (default) | Builds container image, pushes to ECR | +| `zip` | Packages code via Lambda, uploads to S3 | + +**Note**: Docker is required for both deployment types. The `zip` option only affects how the agent runtime is packaged. Other Lambda functions in the stack still use Docker for dependency bundling. + +**Use Docker (default) when:** + +- You need native C/C++ libraries without ARM64 wheels on PyPI +- Your deployment package exceeds 250 MB +- You need custom OS-level dependencies +- You want maximum compatibility + +**Use ZIP when:** + +- You want faster iteration during development +- Your dependencies are pure Python or have ARM64 wheels available +- You need higher session throughput + +**ZIP packaging includes**: The `agent//`, `agent/utils/`, `gateway/`, and `tools/` directories are bundled together with dependencies from `requirements.txt`. This matches the `COPY` commands in the Docker deployment's Dockerfile. + +### VPC Deployment (Private Network) + +By default, the AgentCore Runtime runs in PUBLIC network mode with internet access. To deploy the runtime into an existing VPC for private network isolation, set `network_mode: VPC` in `infra-cdk/config.yaml` and provide your VPC details. + +#### What runs inside vs outside the VPC + +When VPC mode is enabled, the **AgentCore Runtime** (your agent code) runs inside your VPC's private subnets. All network calls the agent makes are subject to VPC networking rules and reach AWS services through VPC endpoints — the agent never makes direct internet calls. + +The following components run **outside** the VPC in AWS-managed infrastructure: + +- **Gateway tool Lambdas** — The agent calls the Gateway through the `bedrock-agentcore.gateway` VPC endpoint (private networking). The Gateway then invokes Lambda functions on AWS-managed infrastructure. The agent's network call stays private; only the Lambda execution happens outside the VPC. +- **Code Interpreter** — The agent calls the Code Interpreter API through the `bedrock-agentcore` VPC endpoint. The sandbox execution happens in Bedrock's managed environment. +- **Bedrock model invocations** — Model calls go through the `bedrock-runtime` VPC endpoint to Bedrock's managed infrastructure. +- **Frontend (Amplify/CloudFront)** — Entirely separate, public-facing, and not part of the VPC deployment. + +In short: the agent's outbound network traffic stays on private AWS networking via VPC endpoints. The services it calls (Bedrock, Gateway, Code Interpreter) may execute on infrastructure outside the VPC, but the network path from the agent to those service APIs is private. + +#### Configuration + +```yaml +backend: + pattern: strands-single-agent + deployment_type: docker + network_mode: VPC + vpc: + vpc_id: vpc-0abc1234def56789a + subnet_ids: + - subnet-aaaa1111bbbb2222c + - subnet-cccc3333dddd4444e + security_group_ids: # Optional - a default SG is created if omitted + - sg-0abc1234def56789a +``` + +The `vpc_id` and `subnet_ids` fields are required. The `security_group_ids` field is optional — if omitted, the CDK construct will create a default security group for the runtime. + +#### Required VPC Endpoints + +When deploying in VPC mode, the runtime runs in private subnets without internet access. Your VPC must have the following VPC endpoints configured so the agent can reach the AWS services it depends on: + +| Endpoint | Service | Type | +| -------------------------------------------------- | -------------------------------- | --------- | +| `com.amazonaws.{region}.bedrock-runtime` | Bedrock model invocation | Interface | +| `com.amazonaws.{region}.bedrock-agentcore` | AgentCore Identity (Token Vault) | Interface | +| `com.amazonaws.{region}.bedrock-agentcore.gateway` | AgentCore Gateway (MCP tools) | Interface | +| `com.amazonaws.{region}.ssm` | SSM Parameter Store | Interface | +| `com.amazonaws.{region}.secretsmanager` | Secrets Manager | Interface | +| `com.amazonaws.{region}.logs` | CloudWatch Logs | Interface | +| `com.amazonaws.{region}.ecr.api` | ECR API (Docker deployment) | Interface | +| `com.amazonaws.{region}.ecr.dkr` | ECR Docker (Docker deployment) | Interface | +| `com.amazonaws.{region}.s3` | S3 (ZIP deployment, ECR layers) | Gateway | +| `com.amazonaws.{region}.dynamodb` | DynamoDB (feedback table) | Gateway | +| `com.amazonaws.{region}.xray` | X-Ray (OTel trace export) | Interface | +| `com.amazonaws.{region}.bedrock-mantle` | OpenAI GPT-5.x (Responses API) | Interface | + +Replace `{region}` with your deployment region (e.g. `us-east-1`). + +All interface endpoints must have private DNS enabled and must be associated with the same subnets and security groups that allow traffic from the AgentCore Runtime. + +#### Subnet Requirements + +- The CDK-managed VPC uses **fully isolated private subnets** (`PRIVATE_ISOLATED`): no `0.0.0.0/0` route at all +- Subnets are pinned to AgentCore-supported AZs (at least two) for high availability +- Subnets must have sufficient available IP addresses for the runtime ENIs + +#### No NAT Gateway (fully closed network) + +The default deployment has **no NAT Gateway** and no outbound internet access. This works because every dependency is reached through a VPC endpoint: + +- The Gateway M2M token is obtained through AgentCore Identity (the Token Vault), which runs the Cognito token exchange server-side and is reachable via the `bedrock-agentcore` VPC endpoint — the Runtime never calls the public Cognito hosted domain. +- User identity is propagated into the M2M token via `aws_client_metadata` (no extra egress). +- S3 Files (skills) mount over NFS (port 2049) to the mount-target ENI **inside the VPC**. Per the AgentCore VPC docs, the mount only needs TCP 2049 connectivity between the runtime ENIs and the mount targets (allowed by the self-referencing security group) — no dedicated VPC endpoint and no NAT. (TLS and IAM auth are handled automatically.) + +> **Note:** If you add custom tools that make outbound _public-internet_ calls (or use the Browser tool), you would need to reintroduce a NAT Gateway. AWS services can instead be reached by adding the corresponding VPC endpoint. + +#### Security Group Configuration + +The CDK stack auto-creates a security group for the AgentCore Runtime. This same security group is typically applied to your VPC endpoints. You must add a self-referencing inbound rule to allow the runtime to reach the endpoints: + +- Protocol: TCP, Port: 443, Source: the security group itself + +### OpenAI models (GPT-5.x via bedrock-mantle) + +The OpenAI models in the picker (GPT-5.4 / GPT-5.5) are served from the `bedrock-mantle` endpoint (OpenAI Responses API), available in us-east-1 since 2026-06. The CDK-managed VPC always provisions the in-region `bedrock-mantle` interface endpoint (see the endpoint table above), so no extra stacks, flags, or cross-region networking are needed — a plain `cdk deploy` covers GPT models (the former `OPENAI_MANTLE` us-east-2 peering stacks are gone). + +### Enabling Claude Fable 5 (data retention prerequisite) + +Before Claude Fable 5 can be used, the account's Bedrock data retention mode must be set to `provider_data_share` via the Data Retention API. Fable 5 declares `allowed_modes: ["provider_data_share"]` — with any other effective mode (`default` / `none`) the model is reported as unavailable and invocations fail with `ValidationException: data retention mode 'default' is not available for this model` (the agent then looks silently unresponsive in the UI; the error only shows in the Runtime logs). Setting this mode explicitly acknowledges that prompts and outputs sent to Fable 5 are shared with Anthropic and retained for up to 30 days ([data retention docs](https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html)). + +Operational notes: + +- **Set it in the region the runtime calls Bedrock from** (us-east-1 for this stack). The check is evaluated against the account setting of the source region — the bedrock-runtime endpoint that receives the request — not the regions the `global.` inference profile routes to, so configuring other regions is unnecessary. +- There is no console UI for this; use the API. It is a one-time account-level runtime setting outside CloudFormation/CDK, so re-creating the stack keeps it, but moving to a **new account or a different source region requires setting it again** — a forgotten setting resurfaces as the silent failure above. +- The caller needs `bedrock:PutAccountDataRetention` / `bedrock:GetAccountDataRetention` (account-level, `Resource: "*"`). The effective mode resolves as project -> account -> model default; an account-level setting is sufficient here. +- Models whose `allowed_modes` include `default` (e.g. Claude Opus 4.8) keep their data inside AWS even after this change — the account mode only sets what you allow, each model's `allowed_modes` decides what actually happens. + +```bash +# Using a Bedrock API key (bearer token): +curl -X PUT https://bedrock.us-east-1.amazonaws.com/data-retention \ + -H "Authorization: Bearer $AWS_BEARER_TOKEN_BEDROCK" \ + -H "Content-Type: application/json" \ + -d '{ "mode": "provider_data_share" }' + +# Or with SigV4 credentials via boto3 (the AWS CLI does not expose these +# operations yet as of v2.31): +python3 -c " +import boto3 +c = boto3.client('bedrock', region_name='us-east-1') +print(c.put_account_data_retention(mode='provider_data_share'))" + +# Verify: +python3 -c " +import boto3 +print(boto3.client('bedrock', region_name='us-east-1').get_account_data_retention()['mode'])" +``` + +### Deploying Multiple Environments (same account/region) + +A second environment (e.g. a dev stack alongside production) needs no code changes. Create a sibling config file and select it with the `CONFIG_FILE` env var: + +```yaml +# infra-cdk/config.dev.yaml (gitignored — embeds a real admin email) +stack_name_base: FAST-dev # MUST differ: CloudFormation exports, Cognito domain etc. derive from it +admin_user_email: you@example.com +backend: + pattern: strands-single-agent + deployment_type: docker + network_mode: VPC + vpc_management: CDK + vpc_cidr: 10.30.0.0/16 # MUST not overlap other environments (prod uses 10.20.0.0/16) + use_long_term_memory: true + skills: + enabled: true + mount_path: /mnt/skills +``` + +```bash +cd infra-cdk +# Everything (VPC + skills + LTM + OpenAI models) in one shot: +CONFIG_FILE=config.dev.yaml npx cdk deploy --all --require-approval never +cd .. +python scripts/deploy-frontend.py FAST-dev # frontend reads outputs from the named stack +``` + +Rules for coexistence: + +- `stack_name_base` must be unique per environment — every named resource and CloudFormation export derives from it. +- `vpc_cidr` must not overlap any other environment, so the networks stay unambiguous to operate (and remain peerable should that ever be needed). +- A plain `cdk deploy` (no `CONFIG_FILE`) always targets production's `config.yaml` — the env var is the only switch, so there is no file to forget to revert. +- When deploying into a **different AWS account**, also set `backend.availability_zones`: AgentCore Runtime VPC mode only supports specific AZ _ids_ per region (us-east-1: use1-az1/az2/az4), and the AZ name -> id mapping is account-specific. Derive the right names with `aws ec2 describe-availability-zones`. + +## Deployment Steps + +### TL;DR version + +Here are the commands to deploy backend and frontend: + +```bash +cd infra-cdk +npm install +cdk bootstrap # Once ever +cdk deploy +cd .. +python scripts/deploy-frontend.py +``` + +### Deploy Without Local Tooling (via CodeBuild) + +If you don't have Node.js, Docker, or CDK installed locally, you can deploy entirely in the cloud using a temporary CodeBuild project. Requires only Python 3.8+ and AWS CLI: + +```bash +python scripts/deploy-with-codebuild.py +``` + +See `scripts/README.md` for details and required IAM permissions. + +### 1. Install Dependencies + +Install infrastructure dependencies: + +```bash +cd infra-cdk +npm install +``` + +**Note**: Frontend dependencies are automatically installed during deployment via Docker bundling, so no separate frontend `npm install` is required. + +### 2. Bootstrap CDK (First Time Only) + +If this is your first time using CDK in this AWS account/region: + +```bash +cdk bootstrap +``` + +### 3. Deploy backend with CDK + +Build and deploy the complete stack: + +```bash +cdk deploy +``` + +The deployment will: + +1. Create a Cognito User Pool for authentication +1. Build and push the agent container to ECR +1. Create the AgentCore runtime +1. Set up CloudFront distribution for the frontend + +**Note**: The deployment takes approximately 5-10 minutes due to container building and AgentCore setup. + +**You do not need to run the skill scripts by hand before deploying.** The vendored AWS skills under `skills/agent-toolkit-for-aws/` are committed to the repo, and `cdk` regenerates the `fast-project-guide` skill automatically at synth time (it runs `scripts/build-project-guide.py` through the skills-storage stack's bundling). `scripts/vendor-skills.py` is only needed to refresh the vendored skills from a newer upstream commit — a maintenance task, not a deployment step. `cdk deploy` is self-contained. + +### 4. Deploy frontend + +```bash +# From root directory +python scripts/deploy-frontend.py +``` + +This script automatically: + +- Generates fresh `aws-exports.json` from CDK stack outputs (see below for more information about `aws-exports.json`) +- Installs/updates npm dependencies if needed +- Builds the frontend +- Deploys to AWS Amplify Hosting + +You will see the URL for application in the script's output, which will look similar to this: + +``` +ℹ App URL: https://main.d123abc456def7.amplifyapp.com +``` + +### 5. Create a Cognito User (if necessary) + +**If you provided `admin_user_email` in config:** + +- Check your email for temporary password +- Sign in and change password on first login + +**If you didn't provide email:** + +1. Go to the [AWS Cognito Console](https://console.aws.amazon.com/cognito/) +2. Find your User Pool (named `{stack_name_base}-user-pool`) +3. Click on the User Pool +4. Go to "Users" tab +5. Click "Create user" +6. Fill in the user details: + - **Email**: Your email address + - **Temporary password**: Create a temporary password + - **Mark email as verified**: Check this box +7. Click "Create user" + +**For a demo: create role users in bulk with a script** + +`scripts/create-demo-users.py` provisions one user per role (`finance`, `engineer`, `guest`) across several "sets" (one set per demo PC) with a shared password, and assigns each user to the right Cognito group. `engineer` is mapped to the `engineering` group; `guest` is left group-less on purpose so Cedar denies it every Gateway tool. The secrets (email prefix, domain, password) live in `scripts/.env` (git-ignored), and the User Pool / Client IDs are resolved from the target stack's CloudFormation outputs, so nothing sensitive or environment-specific is hard-coded. + +```bash +cp scripts/.env.example scripts/.env # then fill in real values +uv run scripts/create-demo-users.py create # provision all users (idempotent) +uv run scripts/create-demo-users.py verify # log in as each user, check groups +uv run scripts/create-demo-users.py cleanup # delete demo users after the demo +``` + +`create` is idempotent: re-running it never creates duplicates (existing users are reported as `exists` and only their password/group are re-applied). `cleanup` never touches the `admin_user_email` account. + +### 6. Access the Application + +1. Open the Amplify Hosting URL in your browser +1. Sign in with the Cognito user you created +1. You'll be prompted to change your temporary password on first login + +## Post-Deployment + +### Updating the Application + +To update the frontend code: + +```bash +# From root directory +python scripts/deploy-frontend.py +``` + +To update the backend agent: + +**Docker deployment:** + +```bash +cd infra-cdk +cdk deploy --all +``` + +### Monitoring and Logs + +- **Frontend logs**: Check CloudFront access logs +- **Backend logs**: Check CloudWatch logs for the AgentCore runtime +- **Build logs**: Check CodeBuild project logs for container builds + +## Cleanup + +To remove all resources: + +```bash +cd infra-cdk +cdk destroy --force +``` + +**Warning**: This will delete all data including S3 buckets created during deployment and ECR images. + +## Troubleshooting + +### Common Issues + +1. **`cdk deploy` fails with Docker errors** + - Ensure Docker is installed and the daemon is running: `docker ps` + - On Mac, open Docker Desktop or start Finch: `finch vm start` + - On Linux: `sudo systemctl start docker` + +2. **"Architecture incompatible" or "exec format error" during Docker build** + - This occurs when deploying from a non-ARM machine without cross-platform build setup + - Follow the "Docker Cross-Platform Build Setup" instructions in the Prerequisites section + - Ensure you've installed QEMU emulation: `docker run --privileged --rm tonistiigi/binfmt --install all` + - Verify ARM64 support: `docker buildx ls` should show `linux/arm64` in platforms + +3. **"Agent Runtime ARN not configured"** + - Ensure the backend stack deployed successfully + - Check that SSM parameters were created correctly + +4. **Authentication errors** + - Verify you created a Cognito user + - Check that the user's email is verified + +5. **Build failures** + - Check CodeBuild logs in the AWS Console + - Ensure your agent code in `agent/` is valid + +6. **Permission errors** + - Verify your AWS credentials have sufficient permissions + - Check IAM roles created by the stack + +### Getting Help + +- Check CloudWatch logs for detailed error messages +- Review the CDK deployment output for any warnings +- Ensure all prerequisites are met + +## Security Considerations + +- The Cognito User Pool is configured with strong password policies +- All communication uses HTTPS via CloudFront +- AgentCore runtime uses JWT authentication +- IAM roles follow least-privilege principles + +For production deployments, consider: + +- Enabling MFA on Cognito users +- Setting up custom domains with your own certificates +- Configuring additional monitoring and alerting +- Implementing backup strategies for any persistent data + +## Docker Cross-Platform Build Setup (Required for non-ARM machines) + +**Important**: BedrockAgentCore Runtime only supports ARM64 architecture. If you're deploying from a non-ARM machine (x86_64/amd64), you need to enable Docker's cross-platform building capabilities. + +Check your machine architecture: + +```bash +uname -m +``` + +If the output is `x86_64` (not `aarch64` or `arm64`), run these commands: + +1. **Install QEMU for ARM64 emulation:** + + ```bash + docker run --privileged --rm tonistiigi/binfmt --install all + ``` + +2. **Enable Docker buildx and create a multi-platform builder:** + + ```bash + docker buildx create --use --name multiarch --driver docker-container + docker buildx inspect --bootstrap + ``` + +3. **Verify ARM64 support is available:** + ```bash + docker buildx ls + ``` + You should see `linux/arm64` in the platforms list. + +**Note**: This setup is only required once per machine. The CDK deployment will automatically use these capabilities to build ARM64 containers. + +## Understanding aws-exports.json + +The `aws-exports.json` file is a critical configuration file that enables the React frontend to communicate with AWS Cognito for authentication. This file is automatically generated during frontend deployment and contains the necessary configuration parameters for Cognito authentication. + +**What is aws-exports.json?** + +The `aws-exports.json` file contains authentication configuration that the React application reads to properly configure Cognito Authentication. It's created automatically by the deployment script and placed in `frontend/public/aws-exports.json`. + +**Why is it necessary?** + +This configuration file is essential because: + +- It provides the React application with the correct Cognito User Pool and Client IDs +- It specifies the authentication endpoints and redirect URIs +- It configures the authentication flow parameters +- Without this file, Cognito authentication will not work + +**How is it created?** + +The file is automatically generated by `deploy-frontend.py` which: + +1. Extracts configuration from your deployed CDK stack outputs +2. Automatically detects the AWS region from the CloudFormation stack ARN +3. Retrieves the required values: `CognitoClientId`, `CognitoUserPoolId`, and `AmplifyUrl` +4. Generates the configuration file with the following structure: + +```json +{ + "authority": "https://cognito-idp.region.amazonaws.com/user-pool-id", + "client_id": "your-client-id", + "redirect_uri": "https://your-amplify-url", + "post_logout_redirect_uri": "https://your-amplify-url", + "response_type": "code", + "scope": "email openid profile", + "automaticSilentRenew": true +} +``` + +**Important**: You should not manually edit this file as it's regenerated on each deployment. If authentication isn't working, redeploy the frontend to ensure you have the latest configuration. + +## Design notes + +Selected design decisions specific to this derivative (rationale beyond the upstream baseline): + +- **Fully closed network (no NAT)**: outbound traffic to AWS APIs goes through interface VPC endpoints exclusively. A NAT gateway is intentionally not provisioned, eliminating the recurring NAT cost and limiting egress to the explicitly configured endpoint set. +- **AgentCore Gateway VPC endpoint is mandatory**: when the runtime calls Gateway from inside the VPC, the `bedrock-agentcore` interface endpoint must be present, or tool invocations fail at network level. +- **Endpoint set is curated**: each interface endpoint is justified by an actual code path that calls it from the VPC. Unused endpoints (e.g. `bedrock-agent-runtime` once direct Bedrock Agent calls were removed) are pruned to reduce cost and attack surface. +- **VPC CIDR and AZ are parameterized**: `config.yaml` exposes `vpc_cidr` and the AZ list so multiple environments can coexist in one account without overlap, and so AZ selection can avoid AZs that lack required service support. +- **Cold-start mitigation**: the AgentCore Runtime is configured with a long-running container lifecycle and a frontend pre-warm ping. This keeps first-token latency low without a separately scheduled warmer Lambda. diff --git a/samples/aws-specialist-agent/docs/GATEWAY.md b/samples/aws-specialist-agent/docs/GATEWAY.md new file mode 100644 index 0000000..da71844 --- /dev/null +++ b/samples/aws-specialist-agent/docs/GATEWAY.md @@ -0,0 +1,485 @@ +# AgentCore Gateway Implementation + +This document describes how FAST implements AgentCore Gateway with Lambda targets to provide a scalable, production-ready tool execution architecture. + +## Overview + +FAST uses **AgentCore Gateway with Lambda Targets** to enable agents to access external tools and services. This architecture provides a clean separation between agent logic and tool implementation, allowing for independent scaling and deployment of individual tools. + +### What the agent can reach + +The deployed agent has three sources of capability: + +1. **Lambda target (`sample-tool-target`)** — the demo `text_analysis_tool`, described in detail below. +2. **AWS MCP Server target (`aws-mcp`)** — the managed Agent Toolkit for AWS MCP Server at `https://aws-mcp..api.aws/mcp`, registered as an MCP gateway target. It exposes `aws___*` tools (AWS knowledge plus `aws___call_aws` / `aws___run_script`). The Gateway signs requests with SigV4 scoped to the service name `aws-mcp`. +3. **Skills via S3 Files** — vendored AWS skills are synced to S3, mounted into the Runtime at `/mnt/skills` through S3 Files, and surfaced to the agent by the Strands `AgentSkills` plugin (not a Gateway target). See `infra-cdk/lib/skills-storage-stack.ts`. + +**Per-user tool access:** the Gateway's Cedar policy gates _who_ can call each tool by the user's `department` claim (resolved from `cognito:groups`; see [Identity Propagation](IDENTITY_POLICY.md)). Read tools are allowed for finance + engineering; the destructive `aws___call_aws` / `aws___run_script` tools are restricted to finance; guest is denied. Denied tools are hidden at `tools/list`. + +## Architecture Comparison + +### Standalone MCP Gateway vs Lambda Targets + +There are two primary approaches to implementing AgentCore Gateway: + +#### Standalone MCP Gateway + +- Gateway directly implements MCP (Model Context Protocol) server +- Tools are built into the gateway infrastructure +- Simpler setup for basic scenarios +- Direct client → gateway communication + +#### Lambda Targets (FAST's Choice) + +- Gateway acts as a proxy/router to external Lambda functions +- Each tool is implemented as a separate Lambda function +- Client → Gateway → Lambda → Gateway → Client flow +- Production-ready architecture with enterprise benefits + +### Why FAST Uses Lambda Targets + +We chose Lambda targets for the following production advantages: + +1. **Separation of Concerns**: Business logic lives in Lambda functions, not gateway infrastructure +2. **Independent Scaling**: Each tool can scale independently based on usage patterns +3. **Maintainability**: Update tool logic without touching gateway infrastructure +4. **Reusability**: Same Lambda can be used by multiple gateways or other services +5. **Language Flexibility**: Each Lambda can use different programming languages +6. **Independent Deployment**: Deploy tool updates without gateway downtime +7. **Cost Optimization**: Pay only for actual tool execution time +8. **Security**: Each Lambda can have specific IAM permissions for its requirements + +## Implementation Details + +### Gateway Configuration + +The gateway is created using AWS CDK L1 constructs with the following configuration: + +- **Protocol Type**: MCP (Model Context Protocol) +- **Authorization**: Custom JWT with Cognito integration +- **Authentication**: Machine-to-machine client credentials flow +- **Target Type**: AWS Lambda functions +- **Optional Features**: Semantic search (can be enabled for tool discovery) + +### Lambda Target Structure + +Each Lambda target in FAST follows this pattern: + +```python +def handler(event, context): + # Get tool name from context (strip target prefix) + delimiter = "___" + original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + tool_name = original_tool_name[original_tool_name.index(delimiter) + len(delimiter):] + + # Event contains tool arguments directly + arguments = event + + # Return response in expected format + return { + 'content': [ + { + 'type': 'text', + 'text': 'Tool response here' + } + ] + } +``` + +#### Tool Invocation Protocol Details + +**Critical Implementation Notes:** + +The tool name is **NOT** passed in the event body. Gateway passes it via the Lambda context object: + +```python +# Tool name location +original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + +# Arguments are in event body +name = event.get('name', 'World') +``` + +**Tool Name Format:** + +Gateway includes the target name as a prefix with three underscores as delimiter: + +``` +{target_name}___{tool_name} +``` + +Example: `sample_tool_target___sample_tool` + +**Complete Working Implementation:** + +```python +import json +import logging + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +def lambda_handler(event, context): + try: + # Get tool name from context + original_tool_name = context.client_context.custom['bedrockAgentCoreToolName'] + logger.info(f"Received tool invocation: {original_tool_name}") + logger.info(f"Event: {json.dumps(event)}") + + # Strip target prefix + delimiter = "___" + if delimiter in original_tool_name: + tool_name = original_tool_name[original_tool_name.index(delimiter) + len(delimiter):] + else: + tool_name = original_tool_name + + # Route to appropriate tool handler + if tool_name == "sample_tool": + name = event.get('name', 'World') + result = f"Hello, {name}! This is a sample tool from FAST." + return {"result": result} + else: + raise ValueError(f"Unknown tool: {tool_name}") + + except Exception as e: + logger.error(f"Error in lambda_handler: {str(e)}", exc_info=True) + raise +``` + +**Multiple Tools Per Lambda:** + +A single Lambda can handle multiple tools by routing on the extracted tool name: + +```python +if tool_name == "tool_one": + # Handle tool one + pass +elif tool_name == "tool_two": + # Handle tool two + pass +``` + +This is a valid production pattern used in AWS samples. + +### Tool Schema Definition + +Tools are defined using JSON schema in the CDK stack: + +```json +{ + "name": "sample_tool", + "description": "A sample tool that returns a greeting", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name to greet" + } + }, + "required": ["name"] + } +} +``` + +**Supported JSON Schema Types:** + +When defining tool specs for Gateway, use these types: + +- `"integer"` - for integers (not "int") +- `"number"` - for floats +- `"string"` - for strings +- `"boolean"` - for booleans +- `"array"` - for arrays +- `"object"` - for objects + +### Authentication Flow + +1. **Machine Client**: CDK creates a Cognito machine client with client credentials flow +2. **Resource Server**: Defines scopes for gateway access (read/write) +3. **JWT Authorization**: Gateway validates tokens using Cognito's OIDC discovery +4. **SSM Parameters**: Client credentials stored securely in SSM Parameter Store + +## Key Components + +### 1. Gateway L1 Construct + +The gateway is created using native CloudFormation L1 constructs in `infra-cdk/lib/backend-stack.ts`: + +- `CfnGateway`: Creates AgentCore Gateway with MCP protocol +- `CfnGatewayTarget`: Configures Lambda targets with tool schemas +- JWT authorization configured via Cognito +- Automatic lifecycle management by CloudFormation + +### 2. Sample Tool Lambda + +Located in `gateway/tools/sample_tool/sample_tool_lambda.py`: + +- Demonstrates proper Lambda target implementation +- Shows how to parse AgentCore Gateway event format +- Includes error handling and logging + +### 3. IAM Roles and Permissions + +**Gateway Role**: Allows gateway to invoke Lambda functions and access required AWS services + +**Custom Resource Role**: Manages gateway lifecycle operations + +### 4. SSM Parameter Storage + +Gateway configuration is stored in SSM for easy access: + +- `/stack-name/gateway_url`: Gateway endpoint URL +- `/stack-name/machine_client_id`: Cognito client ID +- `/stack-name/machine_client_secret`: Cognito client secret +- `/stack-name/cognito_provider`: Cognito domain URL + +## Testing the Gateway + +### Direct Gateway Testing + +Use the provided test script to verify gateway functionality: + +```bash +python3 scripts/test-gateway.py +``` + +This script: + +1. Authenticates using machine client credentials from SSM +2. Lists available tools via MCP protocol +3. Calls the sample tool with test parameters +4. Displays responses for verification + +### Integration with AgentCore Runtime + +The gateway integrates with AgentCore Runtime through: + +1. **Runtime Configuration**: Runtime is configured with gateway URL via SSM +2. **Authentication**: Runtime uses same Cognito user pool for JWT tokens +3. **Tool Discovery**: Runtime discovers tools via gateway's `tools/list` endpoint +4. **Tool Execution**: Runtime calls tools via gateway's `tools/call` endpoint + +### Integration with Agents via MCP + +Agents connect to the Gateway using the Model Context Protocol (MCP). Two common integration approaches are shown below: + +#### LangGraph with MultiServerMCPClient + +The `MultiServerMCPClient` from `langchain-mcp-adapters` provides automatic session management: + +```python +from langchain_mcp_adapters.client import MultiServerMCPClient +from langgraph.prebuilt import create_react_agent + +# Create MCP client with Gateway configuration +mcp_client = MultiServerMCPClient({ + "gateway": { + "transport": "streamable_http", + "url": gateway_url, + "headers": { + "Authorization": f"Bearer {access_token}" + } + } +}) + +# Load tools from Gateway +tools = await mcp_client.get_tools() + +# Create agent with tools +graph = create_react_agent( + model=bedrock_model, + tools=tools, + checkpointer=checkpointer +) +``` + +#### Strands with Direct MCP Session + +Strands agents can use direct MCP session management for more control: + +```python +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from langchain_mcp_adapters.tools import load_mcp_tools + +async with streamablehttp_client( + gateway_url, + headers={"Authorization": f"Bearer {access_token}"} +) as (read, write, _): + async with ClientSession(read, write) as session: + await session.initialize() + tools = await load_mcp_tools(session) + # Use tools with agent +``` + +**Example:** See `agent/strands-single-agent/basic_agent.py` for complete implementation. + +## Adding New Tools + +To add a new tool to the gateway: + +1. **Create Lambda Function**: Implement tool logic following the Lambda target pattern +2. **Define Tool Schema**: Add JSON schema definition to CDK stack +3. **Update Gateway Configuration**: Add new target to gateway custom resource +4. **Deploy**: Run CDK deploy to update infrastructure + +### Example: Adding a Weather Tool + +```typescript +// In backend-stack.ts +const weatherLambda = new lambda.Function(this, "WeatherToolLambda", { + runtime: lambda.Runtime.PYTHON_3_13, + handler: "weather_tool.handler", + code: lambda.Code.fromAsset(path.join(__dirname, "../../gateway/tools/sample_tool")), +}) + +const weatherToolSchema = { + name: "get_weather", + description: "Get current weather for a location", + inputSchema: { + type: "object", + properties: { + location: { + type: "string", + description: "City and state, e.g. 'Seattle, WA'", + }, + }, + required: ["location"], + }, +} +``` + +## Security Considerations + +### Authentication + +- Machine-to-machine authentication using Cognito client credentials +- JWT tokens with configurable expiration +- Scoped access using Cognito resource server + +### Authorization + +- Gateway validates JWT tokens on every request +- Lambda functions inherit gateway's IAM role permissions +- Principle of least privilege for all components + +### Network Security + +- Gateway endpoints use HTTPS only +- Lambda functions run in AWS managed VPC +- No direct internet access required for Lambda functions + +## Monitoring and Logging + +### CloudWatch Logs + +- Gateway operations logged to `/aws/bedrock-agentcore/gateway/*` +- Lambda function logs in `/aws/lambda/function-name` +- Custom resource operations in `/aws/lambda/gateway-custom-resource` + +### Metrics + +- Gateway invocation metrics via CloudWatch +- Lambda function duration and error metrics +- Custom metrics can be added to Lambda functions + +## Troubleshooting + +### Common Issues + +**"Unknown tool: None" Error** + +- Indicates Lambda function isn't parsing context correctly +- Verify Lambda follows AgentCore Gateway input format +- Check CloudWatch logs for detailed error information + +**Authentication Failures** + +- Verify Cognito client credentials in SSM +- Check JWT token expiration +- Ensure gateway authorization configuration is correct + +**Tool Not Found** + +- Verify tool schema matches Lambda implementation +- Check gateway target configuration +- Ensure Lambda function is deployed and accessible + +**Gateway returns "An internal error occurred"** + +- Enable debugging to see detailed error messages by updating the gateway to set `exceptionLevel: 'DEBUG'` in the CDK construct or via AWS CLI. + +```bash +# Enable debugging on gateway +aws bedrock-agentcore-control update-gateway \ + --gateway-identifier \ + --name \ + --role-arn \ + --protocol-type MCP \ + --authorizer-type CUSTOM_JWT \ + --authorizer-configuration \ + --exception-level DEBUG +``` + +Or update the gateway construct in CDK: + +```typescript +const gateway = new bedrockagentcore.CfnGateway(this, "AgentCoreGateway", { + name: `${config.stack_name_base}-gateway`, + roleArn: gatewayRole.roleArn, + protocolType: "MCP", + exceptionLevel: "DEBUG", // Add this line for detailed error messages + // ... rest of configuration +}) +``` + +### Debug Steps + +1. **Check SSM Parameters**: Verify all gateway configuration parameters exist +2. **Test Authentication**: Use test script to verify token generation +3. **Review CloudWatch Logs**: Check gateway and Lambda function logs +4. **Validate Tool Schema**: Ensure schema matches expected format +5. **Test Lambda Directly**: Invoke Lambda function independently to verify logic + +## Best Practices + +### Lambda Function Development + +- Always log incoming events for debugging +- Implement proper error handling and return meaningful error messages +- Use environment variables for configuration +- Keep functions focused on single tool responsibility + +### Schema Design + +- Provide clear, descriptive tool and parameter descriptions +- Use appropriate JSON schema types and constraints +- Include examples in descriptions where helpful +- Keep input schemas simple and focused + +### Deployment + +- Test tools individually before gateway integration +- Use version tags for Lambda function deployments +- Monitor CloudWatch metrics after deployment +- Implement gradual rollout for production changes + +## Related Documentation + +- [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md) - User-level access control for Gateway tools +- [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md) - Cedar policy syntax, capabilities, and reference +- [Replacing Cognito](REPLACING_COGNITO.md) - Identity provider swap and Gateway interceptors guide +- [Runtime-Gateway Authentication](RUNTIME_GATEWAY_AUTH.md) - M2M token flow between Runtime and Gateway +- [Deployment Guide](DEPLOYMENT.md) - How to deploy FAST infrastructure +- [AWS AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore-gateway.html) - Official AWS documentation +- [AWS Gateway Lambda Target Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-add-target-lambda.html) - Lambda target implementation details + +## Design notes + +Decisions specific to this derivative around Gateway targets: + +- **MCP server targets, not just Lambda**: the AWS MCP server, a Strands docs MCP server, and a long-term-memory meta-recall MCP server are exposed as Gateway MCP server targets. Hosting them as targets (rather than embedding their tools in the agent) keeps tool surface, authentication, and Cedar policy unified across native and MCP tools. +- **AWS MCP target inherits a broader role**: the AWS MCP server's `call_aws` and `run_script` capabilities require the Gateway target role to enumerate and call AWS services. The role is scoped read-broad / write-narrow for the demo; production deployments should re-evaluate against their threat model. +- **SigV4 service name pinned**: when calling Gateway target endpoints from inside the runtime, the SigV4 signing service identifier matters and is pinned in code rather than inferred, to avoid silent auth failures on minor API changes. +- **Managed Web Search target**: the GA Web Search connector is wired in as a Gateway target so the agent can answer general questions with citations without bringing along a separate search SDK or browser tool. diff --git a/samples/aws-specialist-agent/docs/IDENTITY_POLICY.md b/samples/aws-specialist-agent/docs/IDENTITY_POLICY.md new file mode 100644 index 0000000..f682656 --- /dev/null +++ b/samples/aws-specialist-agent/docs/IDENTITY_POLICY.md @@ -0,0 +1,344 @@ +# Identity Propagation & Cedar Policy Guide + +This document describes how FAST propagates user identity from the frontend through to AgentCore Gateway Cedar policies, enabling fine-grained, user-level access control on Gateway tools. + +## Overview + +AgentCore Gateway authenticates requests using OAuth2 tokens validated by a CUSTOM_JWT authorizer. By default, the Runtime obtains M2M tokens via the Client Credentials flow, and all requests carry the same machine identity. This means the Gateway cannot distinguish between individual users. + +This feature adds **identity propagation** on top of the existing M2M flow: the authenticated user's identity and Cognito group membership are embedded into the M2M token using Cognito's `aws_client_metadata` parameter and a V3 Pre-Token Lambda trigger. The Runtime obtains the M2M token through AgentCore Identity (the Token Vault), so it never calls the public Cognito hosted domain directly — which keeps the Runtime closed-network (no NAT). The enriched token is then evaluated by Cedar policies at the Gateway, enabling access control rules like "only users in the finance department can run destructive tools." + +**Use this when:** Gateway tools need user-level access control based on attributes like department, role, or user ID. + +> **Scope of this demo:** This implementation demonstrates user-to-tool access control (e.g., "guest users cannot use the text_analysis_tool from the AgentCore Gateway"). AgentCore Policy supports additional capabilities — including input validation, conditional access based on request parameters, and multi-tool policies — which are documented in [Cedar Policy Capabilities](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities). + +## What is AgentCore Policy? + +AgentCore Policy is a service that controls what your AI agents are allowed to do. Think of it as a security guard sitting between your agent and its tools — every time the agent tries to use a tool, the guard checks the rules and decides: allow or deny. + +**The simple version:** + +- You write rules (Cedar policies) that say who can use which tools, and under what conditions +- The Policy Engine enforces those rules automatically on every single tool call +- If no rule explicitly allows an action, it's denied (deny-by-default) +- Enforcement is deterministic — unlike prompt engineering, policies cannot be bypassed by clever phrasing + +**What it can control:** + +| Capability | Example Rule | Demonstrated in This Demo? | +| --------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ | +| User-to-tool access | "Only finance users can access the billing tool" | Yes | +| Input validation | "Refund amount cannot exceed $1000" | No (see [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities)) | +| Multi-tool policies | "Developers can use read tools but not write tools" | No (see [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities)) | +| Environment isolation | "Only production runtime can access production tools" | No (see [Runtime-Level Access Control](#runtime-level-access-control)) | +| Conditional access | "Allow tool only when query targets a specific account" | No (see [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities)) | + +**This demo implements** user-to-tool access control based on custom `department` claims. The other capabilities use the same infrastructure (Policy Engine + Cedar + Gateway) with different policy conditions. See [Cedar Policy Capabilities](CEDAR_POLICY_GUIDE.md#cedar-policy-capabilities) for the full syntax reference with examples of each capability. + +**Key concepts:** + +- **Policy Engine** — The evaluation engine that processes Cedar policies. One engine attaches to one Gateway. +- **Cedar Policy** — A declarative rule written in [Cedar](https://www.cedarpolicy.com/), AWS's open-source policy language. Deterministic, not probabilistic. +- **CUSTOM_JWT Authorizer** — The Gateway component that validates tokens and maps JWT claims to Cedar principal tags. +- **Deny-by-default** — If no `permit` statement matches, the request is denied. No explicit `forbid` needed. +- **Tool filtering** — Denied tools are hidden from the agent at discovery time (`tools/list`), not just blocked at execution time. See [Tool Discovery vs Execution](CEDAR_POLICY_GUIDE.md#tool-discovery-vs-execution). + +## Architecture / Flow + +The identity propagation flow has six steps: + +``` +1. User logs in → Frontend gets JWT from Cognito (access token carries cognito:groups) +2. Frontend sends request → Runtime validates JWT, extracts user_id (sub) and cognito:groups +3. Runtime requests an M2M token via AgentCore Identity (Token Vault), passing user_id + groups in aws_client_metadata +4. Cognito V3 Pre-Token Lambda fires → maps the group to department/role claims → injects them into the M2M token +5. Runtime calls Gateway tool with the enriched M2M token +6. Gateway's CUSTOM_JWT Authorizer maps token claims to Cedar principal tags → Policy Engine evaluates Cedar policy → allow or deny +``` + +Key security property: both `user_id` (`sub`) and `cognito:groups` come from the validated JWT in the Runtime's Session Context, not from the LLM or request payload. This ensures the identity chain is cryptographically secure end-to-end. + +## Components + +### Cognito ESSENTIALS Tier + +**File:** `infra-cdk/lib/cognito-stack.ts` + +The Cognito User Pool is configured with `featurePlan: ESSENTIALS`. This is required because V3 Pre-Token Generation Lambda triggers only fire on Client Credentials (M2M) grants when the ESSENTIALS tier is enabled. Without it, the Pre-Token Lambda would not be invoked during M2M token generation. + +### V3 Pre-Token Lambda + +**File:** `infra-cdk/lambdas/pretoken-v3/index.py` + +This Lambda fires on every token generation event (both user login and M2M). It only processes M2M flows (`TokenGeneration_ClientCredentials`) and skips user login flows. + +For M2M flows, it reads `verified_groups` (the user's `cognito:groups`, comma-separated) from `clientMetadata` and maps the group name to department/role claims: + +| Cognito Group | Department | Role | +| ------------- | ----------- | --------- | +| `finance` | finance | admin | +| `engineering` | engineering | developer | +| (no group) | guest | viewer | + +These claims are injected into the M2M access token via `claimsToAddOrOverride`: + +- `user_id` — the authenticated user's ID (`sub`) +- `department` — the user's department (the Cognito group name) +- `role` — the user's role + +> **Note:** These claim names (`user_id`, `department`, `role`) are custom, application-defined claims — not standard JWT/OIDC claims. You can define any claim names you need. See [Understanding Claims](CEDAR_POLICY_GUIDE.md#understanding-claims-custom-vs-standard) for details. + +The group is read straight from the validated access token, so the Lambda needs no `AdminListGroupsForUser` call. To change the assignment, add or remove users from Cognito groups, or edit the `GROUP_ROLES` map in the Pre-Token Lambda. + +### Cedar Policy Files + +**Directory:** `gateway/policies/` + +The Cedar policies define access control rules for Gateway tools. Each file holds one Cedar statement (AgentCore `CreatePolicy` accepts one statement per policy), loaded by CDK at deploy time with `//` comment lines stripped and the `{{GATEWAY_ARN}}` placeholder replaced with the actual Gateway ARN. + +| File | Tools | Permitted departments | +| ------------------------------ | -------------------------------------------------------- | --------------------- | +| `01-sample-tool.cedar` | `sample-tool-target___text_analysis_tool` | finance, engineering | +| `02-aws-mcp-read.cedar` | AWS MCP read tools (`aws-mcp___aws___*` read) | finance, engineering | +| `03-aws-mcp-destructive.cedar` | `aws-mcp___aws___call_aws`, `aws-mcp___aws___run_script` | finance only | + +Guest (no group) matches no `permit` and is denied by Cedar's deny-by-default. Engineering can use read tools but not the destructive `call_aws` / `run_script` tools. Finance can use everything. To change the matrix, edit the relevant file and run `cdk deploy`. + +### Policy Engine Custom Resource + +**Files:** + +- `infra-cdk/lambdas/cedar-policy/index.py` — Custom Resource Lambda +- `infra-cdk/lib/backend-stack.ts` — CDK resource definition + +A CloudFormation Custom Resource manages the full Policy Engine lifecycle because no L1/L2 CDK construct exists for AgentCore Policy. The Lambda handles three CloudFormation events: + +- **Create:** Creates Policy Engine → creates Cedar Policy → attaches Policy Engine to Gateway +- **Update:** Deletes existing policies → creates new policy with updated document → verifies engine is still attached to Gateway +- **Delete:** Detaches Policy Engine from Gateway → deletes all policies → deletes Policy Engine + +All operations use official boto3 waiters (`policy_engine_active`, `policy_engine_deleted`, `policy_active`, `policy_deleted`). Gateway status changes use a custom polling loop as no official waiter exists. + +### Gateway Authorizer + +**File:** `infra-cdk/lib/backend-stack.ts` + +The Gateway uses a `CUSTOM_JWT` authorizer configured with the Cognito OIDC discovery URL and the machine client ID. The authorizer validates M2M tokens and maps JWT claims to Cedar principal tags: + +| JWT Claim | Cedar Principal Tag | Claim Type | +| ------------ | -------------------------------- | ------------------------------------- | +| `department` | `principal.getTag("department")` | Custom (injected by Pre-Token Lambda) | +| `role` | `principal.getTag("role")` | Custom (injected by Pre-Token Lambda) | +| `user_id` | `principal.getTag("user_id")` | Custom (injected by Pre-Token Lambda) | + +## Cedar Policy Guide + +For the full Cedar policy reference — including claims, action format, schema constraints, tool discovery vs execution, and policy capabilities — see [Cedar Policy Guide](CEDAR_POLICY_GUIDE.md). + +## Gateway Authentication: AgentCore Identity (Token Vault) + +The Runtime obtains its Gateway M2M token through AgentCore Identity using the `@requires_access_token` decorator (see each pattern's `tools/gateway.py`). AgentCore Identity performs the Cognito token exchange server-side, reachable through the `bedrock-agentcore` VPC endpoint — the Runtime never calls the public Cognito hosted domain, so no NAT Gateway is required. + +User identity is propagated by passing `custom_parameters={"aws_client_metadata": json.dumps({"verified_user_id": , "verified_groups": })}` to the decorator. `aws_client_metadata` is the only `custom_parameters` key Cognito forwards to the Pre-Token Lambda (as `ClientMetadata`); it must be a flat string map, which is why groups are joined into a comma-separated string. The Lambda reads `verified_groups` and injects the `department`/`role` claims the Cedar policy evaluates. + +> **Replacing Cognito?** For swapping Cognito with another Identity Provider (Okta, Auth0, Entra ID, etc.) or using Gateway Interceptors for dynamic access control, see [Replacing Cognito](REPLACING_COGNITO.md). + +## Customization + +### Changing Group Assignment + +The simplest change is to add or remove users from the `finance` / `engineering` Cognito groups — no code change or redeploy needed. To change how groups map to claims, edit the `GROUP_ROLES` map in `infra-cdk/lambdas/pretoken-v3/index.py`. For a fully different identity source (DynamoDB, LDAP, etc.), replace the `verified_groups` lookup with your own resolution. + +### Adding New Claims + +To add new claims to the M2M token: + +1. Add the claim to `claimsToAddOrOverride` in the Pre-Token Lambda +2. Reference the claim in Cedar policy using `principal.getTag("claim_name")` +3. No Gateway configuration change is needed — the CUSTOM_JWT authorizer maps all JWT claims to Cedar tags automatically + +### VPC Mode + +In VPC mode the default deployment is **fully closed (no NAT Gateway)**. Because the M2M token is obtained through AgentCore Identity — which runs the Cognito token exchange server-side, reachable via the `bedrock-agentcore` VPC endpoint — the Runtime needs no outbound internet access. The private subnets are isolated (no `0.0.0.0/0` route) and all AWS access flows through VPC endpoints. + +See `docs/DEPLOYMENT.md` for full VPC configuration details. + +### Runtime-Level Access Control + +By default, all requests through a Gateway share the same machine client identity. If you deploy multiple AgentCore Runtimes and need to control which runtime can access which tools, you can use the Cognito `clientId` as a cryptographically verified runtime identity. + +**Why not use `context.runtime.arn` in Cedar?** +The Cedar schema only supports `context.input` (tool parameters) — there is no `context.runtime.arn` or similar field. Attempting to reference unsupported context fields will cause policy creation to fail. + +**Why not use Cognito Groups for the runtime identity?** +Cognito User Pool Groups apply to user identities, not app clients. The M2M token itself has no `cognito:groups` claim, so it cannot identify the runtime. (User groups are still used for the _user_ identity — the Runtime reads `cognito:groups` from the user's access token and forwards them via `aws_client_metadata`, as described above. That is a separate axis from the per-runtime `clientId` identity discussed here.) + +**Solution: One Cognito App Client Per Runtime** + +Since each CDK stack creates both the Cognito app client and the AgentCore Runtime, the `clientId` serves as the runtime identity — verified cryptographically via the `client_secret`. The Pre-Token Lambda maps the `clientId` to a `runtime_env` claim without any self-reporting. + +**Architecture:** + +``` +Runtime A (production) → authenticates with Client A (client_secret_A) + → Cognito verifies clientId = "abc123" + → Pre-Token Lambda maps "abc123" → runtime_env: "production" + → Cedar policy checks principal.getTag("runtime_env") + +Runtime B (staging) → authenticates with Client B (client_secret_B) + → Cognito verifies clientId = "def456" + → Pre-Token Lambda maps "def456" → runtime_env: "staging" + → Cedar policy checks principal.getTag("runtime_env") +``` + +**Step 1: Create separate machine clients in CDK** + +```typescript +// Create one machine client per runtime environment +const machineClientProd = new cognito.UserPoolClient(this, "MachineClientProd", { + userPool: this.userPool, + generateSecret: true, + oAuth: { + flows: { clientCredentials: true }, + // Use the same resource server scopes as the existing machine client + scopes: [ + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "read", scopeDescription: "Read access" }) + ), + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "write", scopeDescription: "Write access" }) + ), + ], + }, +}) + +const machineClientStaging = new cognito.UserPoolClient(this, "MachineClientStaging", { + userPool: this.userPool, + generateSecret: true, + oAuth: { + flows: { clientCredentials: true }, + scopes: [ + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "read", scopeDescription: "Read access" }) + ), + cognito.OAuthScope.resourceServer( + resourceServer, + new cognito.ResourceServerScope({ scopeName: "write", scopeDescription: "Write access" }) + ), + ], + }, +}) + +// Pass the mapping to the Pre-Token Lambda as an environment variable +preTokenLambda.addEnvironment( + "CLIENT_RUNTIME_MAP", + JSON.stringify({ + [machineClientProd.userPoolClientId]: "production", + [machineClientStaging.userPoolClientId]: "staging", + }) +) +``` + +**Step 2: Map clientId → runtime_env in the Pre-Token Lambda** + +```python +import os, json + +def lambda_handler(event, context): + if event["triggerSource"] != "TokenGeneration_ClientCredentials": + return event + + # clientId is Cognito-verified + client_id = event["callerContext"]["clientId"] + + # Mapping set at deploy time by CDK + client_runtime_map = json.loads(os.environ.get("CLIENT_RUNTIME_MAP", "{}")) + runtime_env = client_runtime_map.get(client_id, "unknown") + + # Existing user identity logic (unchanged) + meta = event["request"].get("clientMetadata", {}) + user_id = meta.get("verified_user_id", "") + groups = [g for g in meta.get("verified_groups", "").split(",") if g] + + GROUP_ROLES = {"finance": "admin", "engineering": "developer"} + department = next((g for g in groups if g in GROUP_ROLES), "guest") + role = GROUP_ROLES.get(department, "viewer") + + event["response"]["claimsAndScopeOverrideDetails"] = { + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "user_id": user_id, + "department": department, + "role": role, + "runtime_env": runtime_env, + } + } + } + return event +``` + +**Step 3: Add runtime_env to Cedar policy** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"sample-tool-target___text_analysis_tool", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("runtime_env") && + principal.getTag("runtime_env") == "production" && + principal.hasTag("department") && + (principal.getTag("department") == "finance" || + principal.getTag("department") == "engineering") +}; +``` + +**Security model — two-layer identity:** + +| Layer | Claim | Source | Trust Level | +| ---------------- | ------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------- | +| Runtime identity | `runtime_env` | `callerContext.clientId` (Cognito-verified) | Cryptographic — requires `client_secret` | +| User identity | `user_id`, `department`, `role` | `clientMetadata.verified_user_id` (from validated JWT `sub` claim) | JWT-verified — extracted server-side by Runtime from Cognito-validated token | + +Both layers are secured by Cognito: the `clientId` is verified through the client secret exchange, and the `user_id` originates from the validated JWT `sub` claim extracted by `extract_user_id_from_context()` in the Runtime. + +> **Note:** This section documents the architecture pattern for runtime-level access control. The current FAST implementation uses a single machine client. To implement this pattern, create additional machine clients in `cognito-stack.ts` and update the Pre-Token Lambda with the mapping logic above. + +## Verifying the Deployed Policy + +To check which Cedar policy is currently active on the Gateway: + +1. Go to **AWS Console → Bedrock AgentCore → Policy** +2. Click on your Policy Engine (e.g., `fast_specialist_agent_policy_engine`) from the Policy engines section +3. In the **Policies** section, click on your policy (e.g., `fast_specialist_agent_policy_engine_cp_`) +4. The **Definition** section shows the policy breakdown: + - **Effect**: `permit` or `forbid` + - **Scope: Principal**: `AgentCore::OAuthUser` + - **Scope: Actions**: the tool action name (e.g., `sample-tool-target___text_analysis_tool`) + - **Scope: Resource**: the Gateway name + - **Conditions**: the `when` clause logic +5. The **Cedar** section shows the full Cedar policy statement as deployed + +Use this to confirm that a `cdk deploy` applied the expected policy version. + +## Verifying Policy Decisions via Tracing + +To verify Cedar policy allow/deny decisions in CloudWatch logs: + +1. Go to **AWS Console → Bedrock AgentCore → Runtimes** +2. Click on your runtime (e.g., `fast_specialist_agent_FASTAgent`) from the Runtime resources section +3. Scroll down to **Tracing**, click **Edit**, and toggle **Enable tracing** to Enable +4. Go to **Bedrock AgentCore → Gateways** +5. Click on your gateway (e.g., `fast-specialist-agent-gateway`), scroll down to **Tracing**, click **Edit**, and toggle **Enable tracing** to Enable +6. Run a query from the frontend that triggers a tool call +7. Go to **CloudWatch Console → Log Management → Log groups** +8. Find and click on the `aws/spans` log group, then click on the default log stream +9. In the **Filter events** search box, type `policy` +10. Look for the `AgentCore.Policy.PartiallyAuthorizeActions` span — it contains: + - `aws.agentcore.policy.allowed_tools`: tools the user is permitted to use + - `aws.agentcore.policy.denied_tools`: tools the user is denied access to + - `aws.agentcore.gateway.policy.mode`: should show `ENFORCE` diff --git a/samples/aws-specialist-agent/docs/LOCAL_DEVELOPMENT.md b/samples/aws-specialist-agent/docs/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000..9064540 --- /dev/null +++ b/samples/aws-specialist-agent/docs/LOCAL_DEVELOPMENT.md @@ -0,0 +1,215 @@ +# Local Development with Docker Compose + +This guide explains how to run the full FAST stack locally using Docker Compose for development purposes. + +## Prerequisites + +**Important**: Local development still requires a deployed FAST stack in AWS for backend dependencies (Memory, Gateway, SSM parameters). Docker Compose only containerizes the frontend and agent - it doesn't replace AWS services. + +### Required + +1. **Deployed FAST Stack**: You must have already deployed FAST to AWS using: + + ```bash + cd infra-cdk + cdk deploy + ``` + +2. **AWS Credentials**: AWS credentials **must be exported as environment variables** — the Docker containers cannot read from `~/.aws/credentials` or `~/.aws/config`: + + ```bash + # Option 1: Export from your existing aws configure profile + export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) + export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) + export AWS_SESSION_TOKEN=$(aws configure get aws_session_token) # if using temporary credentials + + # Option 2: Set directly + export AWS_ACCESS_KEY_ID=your-key + export AWS_SECRET_ACCESS_KEY=your-secret + export AWS_SESSION_TOKEN=your-token # if using temporary credentials + ``` + +3. **Docker & Docker Compose**: Install Docker Desktop or Docker Engine with Compose support + +4. **Environment Variables**: Set the following required variables: + ```bash + export MEMORY_ID=your-memory-id + export STACK_NAME=your-stack-name + export AWS_DEFAULT_REGION=us-east-1 + ``` + +### Finding Your Environment Variables + +Get these values from your deployed stack: + +```bash +# Get stack outputs +aws cloudformation describe-stacks --stack-name your-stack-name --query 'Stacks[0].Outputs' + +# Extract Memory ID from MemoryArn (last part after the final /) +# Extract Stack Name (the name you used when deploying) +# Use the same region where you deployed +``` + +## Quick Start + +1. **Set Environment Variables**: + + ```bash + export MEMORY_ID=your-memory-id-from-stack-outputs + export STACK_NAME=your-stack-name + export AWS_DEFAULT_REGION=us-east-1 + ``` + +2. **Start the Stack**: + + ```bash + cd docker && docker compose up --build + ``` + +3. **Access the Application**: + - Frontend: http://localhost:3000 + - Agent API: http://localhost:8080 + - Agent Health: http://localhost:8080/ping + +## Authentication in Local Mode + +In production, AgentCore Runtime validates the user's JWT and passes it to the agent. The agent extracts the user ID from the JWT's `sub` claim rather than trusting the request payload (preventing impersonation via prompt injection). + +When running locally via Docker Compose, there is no AgentCore Runtime. The test scripts generate a mock unsigned JWT with a test user ID as the `sub` claim and send it in the `Authorization: Bearer` header. This exercises the same code path as production without requiring a real Cognito token. + +## Environment Configuration + +Create a `.env` file in the repository root for convenience: + +```bash +# Required - from your deployed AWS stack +MEMORY_ID=your-memory-id +STACK_NAME=your-stack-name +AWS_DEFAULT_REGION=us-east-1 + +# AWS Credentials (required - Docker containers cannot read ~/.aws/credentials) +AWS_ACCESS_KEY_ID=your-key +AWS_SECRET_ACCESS_KEY=your-secret +AWS_SESSION_TOKEN=your-token +``` + +Then run: `cd docker && docker compose up --build` + +## Development Workflow + +### Making Changes + +- **Frontend Changes**: Files are mounted as volumes, so changes appear immediately +- **Agent Changes**: Rebuild the agent container: + ```bash + cd docker && docker compose up --build agent + ``` + +### Using Different Agent Patterns + +To use a different agent pattern: + +1. **Edit docker/docker-compose.yml**: + + ```yaml + agent: + build: + dockerfile: agent//Dockerfile + ``` + +2. **Rebuild**: + ```bash + cd docker && docker compose up --build agent + ``` + +### Logs and Debugging + +```bash +# View all logs +docker compose logs -f + +# View specific service logs +docker compose logs -f agent +docker compose logs -f frontend + +# Access container shell +docker compose exec agent bash +docker compose exec frontend sh +``` + +## Troubleshooting + +### Agent Won't Start + +**Symptoms**: Agent container exits or health check fails + +**Solutions**: + +1. Verify AWS credentials: `aws sts get-caller-identity` +2. Check environment variables are set correctly +3. Ensure deployed stack exists and is healthy +4. Check agent logs: `docker compose logs agent` + +### Frontend Can't Connect to Agent + +**Symptoms**: Frontend loads but can't communicate with backend + +**Solutions**: + +1. Verify agent is healthy: `curl http://localhost:8080/ping` +2. Check network connectivity between containers +3. Ensure frontend is configured to use local agent endpoint + +### AWS Permission Errors + +**Symptoms**: Agent starts but fails on AWS API calls + +**Solutions**: + +1. Verify IAM permissions for your AWS credentials +2. Check that the deployed stack resources are accessible +3. Ensure correct AWS region is set + +### Memory/Gateway Not Found + +**Symptoms**: Agent reports missing Memory or Gateway resources + +**Solutions**: + +1. Verify `MEMORY_ID` matches the deployed stack's Memory resource +2. Check `STACK_NAME` matches your CloudFormation stack name +3. Ensure stack deployment completed successfully + +## Stopping the Stack + +```bash +# Stop all services +cd docker && docker compose down + +# Stop and remove volumes +cd docker && docker compose down -v + +# Stop and remove images +cd docker && docker compose down --rmi all +``` + +## Production Deployment + +This Docker Compose setup is for development only. For production deployment, use: + +```bash +cd infra-cdk +cdk deploy +cd .. +python scripts/deploy-frontend.py +``` + +## Next Steps + +- Customize the agent code in `agent/` +- Modify the frontend in `frontend/src/` +- Add new tools in `tools/` or `gateway/tools/` +- Update infrastructure in `infra-cdk/` + +Remember: Changes to infrastructure require redeployment via CDK, not just Docker Compose restart. diff --git a/samples/aws-specialist-agent/docs/LOCAL_DOCKER_TESTING.md b/samples/aws-specialist-agent/docs/LOCAL_DOCKER_TESTING.md new file mode 100644 index 0000000..f2bbcea --- /dev/null +++ b/samples/aws-specialist-agent/docs/LOCAL_DOCKER_TESTING.md @@ -0,0 +1,234 @@ +# Local Docker Testing Guide + +Build and test your AgentCore agent Docker image locally to validate Dockerfile configuration and dependencies. + +## LIMITATIONS + +**Gateway tools will NOT work in standalone Docker testing** because: + +- The `@requires_access_token` decorator requires AgentCore Identity service +- AgentCore Identity only operates within AgentCore Runtime context +- OAuth2 M2M authentication cannot be mocked outside Runtime + +**What works:** Dockerfile builds, dependency installation, Code Interpreter, non-Gateway tools +**What doesn't work:** AgentCore Gateway tools (MCP-based Lambda tools) + +**For full local testing with Gateway support**, use `docker-compose` (see [Local Development Guide](LOCAL_DEVELOPMENT.md)). + +## Why Docker Testing? + +| Testing Mode | Gateway Tools | Code Interpreter | Use Case | +| ------------------------ | ------------- | ---------------- | -------------------------------- | +| `test-agent.py --local` | Yes | Yes | Quick Python iteration | +| **Manual Docker** | No | Yes | Validate Dockerfile/dependencies | +| **`docker-compose`** | Yes | Yes | Full local development | +| `test-agent.py` (remote) | Yes | Yes | Test deployed agent | + +Docker testing validates: + +- Dockerfile builds correctly +- Dependencies install properly in container +- Container starts and responds to health checks +- Agent code runs in containerized environment (without Gateway tools) + +## Prerequisites + +1. **Docker** installed and running (`docker ps` should work) +2. **Deployed stack** - Required for Memory ID and SSM parameters +3. **AWS credentials** configured in your environment + +## Building the Docker Image + +```bash +# Build image for your agent pattern +docker build -f agent/strands-single-agent/Dockerfile \ + -t fast-agent-local \ + --platform linux/arm64 . +``` + +### Platform Requirements + +AgentCore Runtime requires ARM64 architecture. On x86/amd64 machines, enable emulation: + +```bash +# One-time setup for ARM64 emulation +docker run --privileged --rm tonistiigi/binfmt --install all +``` + +## Running the Container + +```bash +# Get Memory ID from CloudFormation outputs +MEMORY_ID=$(aws cloudformation describe-stacks \ + --stack-name \ + --query 'Stacks[0].Outputs[?OutputKey==`MemoryArn`].OutputValue' \ + --output text | awk -F'/' '{print $NF}') + +# Export AWS credentials (required for container) +export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id) +export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key) +export AWS_SESSION_TOKEN=$(aws configure get aws_session_token) # if using temporary credentials + +# Run container +docker run --rm -it -p 8080:8080 \ + --platform linux/arm64 \ + -e MEMORY_ID=$MEMORY_ID \ + -e STACK_NAME= \ + -e AWS_DEFAULT_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN \ + fast-agent-local +``` + +**Important:** AWS credentials must be exported as environment variables. The Docker container cannot read credentials from `~/.aws/credentials` or `~/.aws/config`. + +## Testing the Agent + +### Health Check + +```bash +curl http://localhost:8080/ping +# Returns: {"status":"Healthy","time_of_last_update":...} +``` + +### Mock JWT for Testing + +Since there's no AgentCore Runtime to provide a validated JWT, create a mock unsigned JWT: + +```bash +# Generate mock JWT with sub=test-user +MOCK_JWT=$(python3 -c "import base64,json; h=base64.urlsafe_b64encode(json.dumps({'alg':'none','typ':'JWT'}).encode()).rstrip(b'=').decode(); p=base64.urlsafe_b64encode(json.dumps({'sub':'test-user'}).encode()).rstrip(b'=').decode(); print(f'{h}.{p}.')") + +# Test agent (will fail on Gateway tools but Code Interpreter should work) +curl -X POST http://localhost:8080/invocations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $MOCK_JWT" \ + -d '{"prompt": "Execute Python: print(2+2)", "runtimeSessionId": "test-123"}' +``` + +**Expected behavior:** + +- Code Interpreter requests will work +- Gateway tool requests will fail with authentication errors + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Local Machine │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Docker Container (ARM64) │ │ +│ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ Agent (basic_agent.py / langgraph_agent.py)│ │ │ +│ │ │ - Listens on :8080 │ │ │ +│ │ │ - Uses passed AWS credentials │ │ │ +│ │ │ - Gateway auth will FAIL │ │ │ +│ │ └─────────────────────────────────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ http://localhost:8080/invocations │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────┐ + │ AWS (Deployed Resources) │ + │ - AgentCore Memory (Yes) │ + │ - Code Interpreter (Yes) │ + │ - AgentCore Gateway (No) │ + │ - SSM Parameters (Yes) │ + └─────────────────────────────────┘ +``` + +## Troubleshooting + +### Container starts but Gateway authentication fails + +This is **expected behavior**. The `@requires_access_token` decorator requires AgentCore Identity service, which only works within AgentCore Runtime. + +**Solution:** Use `docker-compose` for full local testing (see [Local Development Guide](LOCAL_DEVELOPMENT.md)). + +### Container starts but agent fails immediately + +Check container logs: + +```bash +# Find container ID +docker ps + +# View logs +docker logs +``` + +Common issues: + +- **Missing AWS credentials**: Ensure `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` are set +- **Expired session token**: Refresh your AWS credentials +- **Stack not deployed**: The script needs a deployed stack to fetch Memory ID + +### Build fails with "platform mismatch" + +Enable ARM64 emulation (see Platform Requirements above). + +### "Connection refused" on localhost:8080 + +The agent may still be starting. Wait 10-30 seconds and try again. Check logs if it persists. + +### ECS/EKS warnings in logs + +These warnings are expected when running locally: + +``` +AwsEcsResourceDetector failed: Missing ECS_CONTAINER_METADATA_URI... +AwsEksResourceDetector failed: No such file or directory... +``` + +The OpenTelemetry instrumentation looks for ECS/EKS metadata which doesn't exist locally. These can be safely ignored. + +## Advanced Usage + +### Viewing Container Logs in Real-Time + +```bash +# Start container in foreground (not detached) +docker run --rm -p 8080:8080 \ + --platform linux/arm64 \ + -e MEMORY_ID= \ + -e STACK_NAME= \ + -e AWS_DEFAULT_REGION=us-east-1 \ + -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN \ + fast-agent-local +``` + +### Build-Only Validation + +To validate Dockerfile without running: + +```bash +docker build -f agent/strands-single-agent/Dockerfile \ + -t fast-agent-local \ + --platform linux/arm64 . + +# Check if build succeeded +echo $? # Should return 0 +``` + +## When to Use Each Testing Mode + +| Scenario | Recommended Mode | +| ---------------------------------- | --------------------------------------- | +| Quick iteration on agent logic | `test-agent.py --local` | +| Verify Dockerfile builds correctly | Manual Docker build | +| Test with Gateway tools locally | `docker-compose` (LOCAL_DEVELOPMENT.md) | +| Test deployed production agent | `test-agent.py` (remote) | +| CI/CD pipeline validation | Manual Docker build | + +## Related Documentation + +- [Local Development Guide](LOCAL_DEVELOPMENT.md) - Full local development with `docker-compose` and Gateway support +- [Deployment Guide](DEPLOYMENT.md) - Full stack deployment instructions +- [Agent Configuration](AGENT_CONFIGURATION.md) - Configuring agent patterns +- [Streaming Guide](STREAMING.md) - Understanding streaming events diff --git a/samples/aws-specialist-agent/docs/MEMORY_INTEGRATION.md b/samples/aws-specialist-agent/docs/MEMORY_INTEGRATION.md new file mode 100644 index 0000000..b468ec5 --- /dev/null +++ b/samples/aws-specialist-agent/docs/MEMORY_INTEGRATION.md @@ -0,0 +1,408 @@ +# AgentCore Memory Integration Guide + +Quick, practical guide for integrating AWS Bedrock AgentCore Memory with your agents. + +AgentCore provides two types of memory: **short-term memory** stores raw conversation history, providing agents with context from recent interactions. **Long-term memory** uses AI-powered strategies to extract and store meaningful insights—such as session summaries, user preferences, and important facts—enabling agents to build deeper understanding over time. Learn more in the [Amazon Bedrock AgentCore Memory blog post](https://aws.amazon.com/blogs/machine-learning/amazon-bedrock-agentcore-memory-building-context-aware-agents/). + +--- + +## Enabling Long-Term Memory + +Both short-term memory (STM — raw conversation history within a session) and long-term memory (LTM — facts recalled across sessions) are available. LTM is supported on the **`strands-single-agent`** pattern and uses a `SemanticMemoryStrategy` to automatically extract and store facts from conversations. The `actorId` is the user's validated JWT `sub` (the same identifier the Runtime extracts for identity propagation), so each user gets their own persistent memory. + +### How It Works + +1. **Infrastructure**: The CDK stack always creates the `SemanticMemoryStrategy` on the memory resource (there is no cost to simply define the strategy). A `USE_LONG_TERM_MEMORY` environment variable is passed to the agent runtime. +2. **Agent behavior**: When `USE_LONG_TERM_MEMORY` is `"true"`, the Strands agent's `AgentCoreMemorySessionManager` is configured with a `retrieval_config` that reads from the `/facts/{actorId}` namespace on each turn. When `"false"` (the default), only short-term conversation history is active. +3. **Fact extraction**: AgentCore processes conversation events asynchronously and extracts factual information (e.g., "the user lives in Seattle", "the user prefers Python"). These facts are stored under `/facts/{actorId}` and retrieved on subsequent turns to personalize responses. + +### Configuration + +Toggle LTM in `infra-cdk/config.yaml`: + +```yaml +backend: + use_long_term_memory: true # Enable long-term semantic memory retrieval +``` + +Then redeploy: + +```bash +cd infra-cdk && cdk deploy --all +``` + +### Cost Considerations + +LTM incurs additional charges beyond short-term memory: + +- **Storage**: $0.75 per 1,000 memory records stored +- **Retrieval**: $0.50 per 1,000 retrieval calls + +When `use_long_term_memory` is `false`, neither cost applies — short-term memory (conversation history) is the only active feature. + +--- + +## Step 1: Configure Memory with CDK + +Memory resources are created using [CloudFormation L1 constructs](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-bedrockagentcore-memory.html). **L2 constructs will be available in future releases.** + +### Basic Memory (Short-Term Only) + +```typescript +const memory = new cdk.CfnResource(this, "AgentMemory", { + type: "AWS::BedrockAgentCore::Memory", + properties: { + Name: "MyAgentMemory", + EventExpiryDuration: 7, // Days to retain (7-365) + MemoryStrategies: [], // Empty = short-term only + MemoryExecutionRoleArn: executionRole.roleArn, + }, +}) +``` + +### Advanced Memory (With Strategies) + +```typescript +const memory = new cdk.CfnResource(this, "AgentMemory", { + type: "AWS::BedrockAgentCore::Memory", + properties: { + Name: "MyAgentMemory", + EventExpiryDuration: 30, + Description: "Memory with intelligent extraction", + MemoryStrategies: [ + { + SummaryMemoryStrategy: { + Name: "SessionSummarizer", + Namespaces: ["/summaries/{actorId}/{sessionId}"], + }, + }, + { + UserPreferenceMemoryStrategy: { + Name: "PreferenceLearner", + Namespaces: ["/preferences/{actorId}"], + }, + }, + { + SemanticMemoryStrategy: { + Name: "FactExtractor", + Namespaces: ["/facts/{actorId}"], + }, + }, + ], + MemoryExecutionRoleArn: executionRole.roleArn, + }, +}) +``` + +### Required IAM Permissions + +```typescript +new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "bedrock-agentcore:CreateEvent", + "bedrock-agentcore:GetEvent", + "bedrock-agentcore:ListEvents", + "bedrock-agentcore:RetrieveMemoryRecords", + ], + resources: [memoryArn], +}) +``` + +**Permission Breakdown:** + +- `CreateEvent`, `GetEvent`, `ListEvents`: For short-term memory (conversation history) +- `RetrieveMemoryRecords`: For long-term memory (summaries, preferences, facts from strategies) + +### Understanding Memory Configuration + +For complete configuration details, see the [AgentCore Memory Overview](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html) and [Memory API Reference](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/welcome.html). + +**Memory Parameters** + +- **EventExpiryDuration**: 7-365 days +- **MemoryStrategies**: Empty for short-term, array for long-term +- **actor_id**: User identifier +- **session_id/thread_id**: Conversation identifier + +**Memory Strategies** + +| Strategy | Purpose | Namespace | +| ------------------------------ | ----------------------- | ---------------------------------- | +| `summaryMemoryStrategy` | Auto-summarize sessions | `/summaries/{actorId}/{sessionId}` | +| `userPreferenceMemoryStrategy` | Learn preferences | `/preferences/{actorId}` | +| `semanticMemoryStrategy` | Extract facts | `/facts/{actorId}` | + +--- + +## Step 2: Integrate with Your Framework + +### Using Strands? + +For complete Strands integration documentation, see the [official Strands Memory Integration guide](https://strandsagents.com/latest/documentation/docs/community/session-managers/agentcore-memory/). + +**Install:** + +```bash +pip install bedrock-agentcore[strands-agents] +``` + +**Code:** + +```python +import os +from strands import Agent +from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager +from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig + +memory_id = os.environ.get("MEMORY_ID") +if not memory_id: + raise ValueError("MEMORY_ID environment variable is required") + +# Basic configuration +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id +) + +session_manager = AgentCoreMemorySessionManager( + agentcore_memory_config=config, + region_name=os.environ.get("AWS_DEFAULT_REGION", "us-east-1") +) + +agent = Agent( + system_prompt="You are a helpful assistant.", + model=bedrock_model, + session_manager=session_manager +) +``` + +**With strategies (if configured in CDK):** + +```python +from bedrock_agentcore.memory.integrations.strands.config import RetrievalConfig + +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id, + retrieval_config={ + "/preferences/{actorId}": RetrievalConfig(top_k=5, relevance_score=0.7), + "/facts/{actorId}": RetrievalConfig(top_k=10, relevance_score=0.3) + } +) +``` + +**With long-term memory enabled** (see [Enabling Long-Term Memory](#enabling-long-term-memory) above): + +The `strands-single-agent` pattern conditionally enables LTM retrieval based on the `USE_LONG_TERM_MEMORY` environment variable. When enabled, the agent retrieves facts from the `/facts/{actorId}` namespace on each turn: + +```python +use_ltm = os.environ.get("USE_LONG_TERM_MEMORY", "false").lower() == "true" + +retrieval_config = ( + { + "/facts/{actorId}": RetrievalConfig( + top_k=10, + relevance_score=0.3, + ) + } + if use_ltm + else None +) + +config = AgentCoreMemoryConfig( + memory_id=memory_id, + session_id=session_id, + actor_id=user_id, + retrieval_config=retrieval_config, +) +``` + +**💡 Example:** See this approach implemented in `agent/strands-single-agent/basic_agent.py` + +**📚 Official AWS Guide:** [Strands SDK Memory Integration](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/strands-sdk-memory.html) + +#### Alternative: Hook-Based Approach + +For more control over memory lifecycle and custom memory operations, you can use Strands hooks with MemorySession: + +```python +from strands import Agent +from strands.hooks import AgentInitializedEvent, HookProvider, HookRegistry, MessageAddedEvent +from bedrock_agentcore.memory.session import MemorySession, MemorySessionManager +from bedrock_agentcore.memory.constants import ConversationalMessage, MessageRole + +# Initialize session manager and create session +session_manager = MemorySessionManager(memory_id=memory_id, region_name="us-east-1") +user_session = session_manager.create_memory_session( + actor_id=user_id, + session_id=session_id +) + +# Create custom hook provider +class MemoryHookProvider(HookProvider): + def __init__(self, memory_session: MemorySession): + self.memory_session = memory_session + + def on_agent_initialized(self, event: AgentInitializedEvent): + """Load recent conversation history when agent starts""" + recent_turns = self.memory_session.get_last_k_turns(k=5) + if recent_turns: + # Format and add to agent context + context_messages = [] + for turn in recent_turns: + for message in turn: + role = message['role'] + content = message['content']['text'] + context_messages.append(f"{role}: {content}") + + context = "\n".join(context_messages) + event.agent.system_prompt += f"\n\nRecent conversation:\n{context}" + + def on_message_added(self, event: MessageAddedEvent): + """Store new messages in memory""" + messages = event.agent.messages + if messages and len(messages) > 0: + message_text = messages[-1]["content"][0]["text"] + message_role = MessageRole.USER if messages[-1]["role"] == "user" else MessageRole.ASSISTANT + + self.memory_session.add_turns( + messages=[ConversationalMessage(message_text, message_role)] + ) + + def register_hooks(self, registry: HookRegistry): + registry.add_callback(MessageAddedEvent, self.on_message_added) + registry.add_callback(AgentInitializedEvent, self.on_agent_initialized) + +# Create agent with memory hooks +agent = Agent( + system_prompt="You are a helpful assistant.", + model=bedrock_model, + hooks=[MemoryHookProvider(user_session)], + tools=[...] +) +``` + +**Use when:** Custom memory loading logic, combining multiple hooks, or fine-grained control needed. + +**💡 Complete Example:** See the [AWS AgentCore Samples repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) for working code examples, including this [Strands with hooks tutorial](https://github.com/awslabs/amazon-bedrock-agentcore-samples/blob/main/01-tutorials/04-AgentCore-memory/01-short-term-memory/01-single-agent/with-strands-agent/). + +### Using LangGraph? + +For complete LangGraph integration documentation, see the [official LangGraph Memory Integration guide](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-integrate-lang.html). For working code examples, explore the [LangChain AWS Integration samples](https://github.com/langchain-ai/langchain-aws/tree/main/samples/memory). + +**Install:** + +```bash +pip install langgraph-checkpoint-aws langchain-mcp-adapters +``` + +**Complete Integration with Gateway Tools:** + +```python +from langchain_aws import ChatBedrock +from langgraph.prebuilt import create_react_agent +from langgraph_checkpoint_aws import AgentCoreMemorySaver +from langchain_mcp_adapters.client import MultiServerMCPClient + +# Configure memory checkpointer +checkpointer = AgentCoreMemorySaver( + memory_id=memory_id, + region_name="us-east-1" +) + +# Create Bedrock model +bedrock_model = ChatBedrock( + model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + temperature=0.1, + streaming=True +) + +# Create MCP client for Gateway tools +mcp_client = MultiServerMCPClient({ + "gateway": { + "transport": "streamable_http", + "url": gateway_url, + "headers": { + "Authorization": f"Bearer {access_token}" + } + } +}) + +# Load tools from Gateway +tools = await mcp_client.get_tools() + +# Create agent with memory and tools +graph = create_react_agent( + model=bedrock_model, + tools=tools, + checkpointer=checkpointer +) + +# Invoke with actor and session +config = { + "configurable": { + "thread_id": session_id, + "actor_id": user_id + } +} + +# Stream responses +async for event in graph.astream( + {"messages": [("user", "Hello")]}, + config=config, + stream_mode="messages" +): + message_chunk, metadata = event + # Process streaming chunks +``` + +**Long-term memory (Store):** + +```python +from langgraph_checkpoint_aws import AgentCoreMemoryStore +from langchain_core.runnables import RunnableConfig +import uuid + +store = AgentCoreMemoryStore(MEMORY_ID, region_name="us-west-2") + +def pre_model_hook(state, config: RunnableConfig, *, store): + """Save messages for extraction""" + actor_id = config["configurable"]["actor_id"] + thread_id = config["configurable"]["thread_id"] + namespace = (actor_id, thread_id) + + messages = state.get("messages", []) + for msg in reversed(messages): + if isinstance(msg, HumanMessage): + store.put(namespace, str(uuid.uuid4()), {"message": msg}) + break + + return {"llm_input_messages": messages} + +graph = create_react_agent( + model=llm, + tools=tools, + checkpointer=checkpointer, + store=store, + pre_model_hook=pre_model_hook +) +``` + +--- + +## Additional Resources + +For more information and community support: + +- **Community Slack**: `#bedrock-agentcore-memory-interest` +- **All Code Examples**: [AgentCore Samples Repository](https://github.com/awslabs/amazon-bedrock-agentcore-samples) + +## Design notes + +Decisions specific to this derivative: + +- **Long-term memory (LTM) is enabled in the demo**: semantic memory strategy is always provisioned on the Memory resource; the runtime env var `USE_LONG_TERM_MEMORY` selects whether to attach an `AgentCoreMemorySessionManager` with retrieval. The cost of keeping LTM provisioned is small enough that the demo value of "the agent remembers across sessions" justifies it. +- **A meta-recall tool augments semantic search**: vector similarity does not match questions like "what did we discuss before?". A dedicated `list_long_term_memories` tool enumerates Memory records without a query so the agent can answer meta questions about its own history. diff --git a/samples/aws-specialist-agent/docs/REPLACING_COGNITO.md b/samples/aws-specialist-agent/docs/REPLACING_COGNITO.md new file mode 100644 index 0000000..046352d --- /dev/null +++ b/samples/aws-specialist-agent/docs/REPLACING_COGNITO.md @@ -0,0 +1,841 @@ +# Replacing Cognito: Identity Provider Swap & Gateway Interceptors Guide + +This document explains how to replace Amazon Cognito with another Identity Provider (IdP) in the FAST AgentCore architecture, and how to use Gateway Interceptors as an alternative or complement to Cedar Policy for access control. + +--- + +## 1. Overview + +### Current Architecture Summary + +The FAST demo uses Amazon Cognito as the Identity Provider with the following flow: + +``` +User JWT → Runtime (validates user) → Runtime gets M2M token from Cognito → Pre-Token Lambda injects user claims into M2M token → Gateway authorizer validates M2M token → Cedar Policy Engine evaluates user claims → allows/denies tool access → Target Lambda receives tool input only +``` + +**This document addresses two questions:** how the Gateway identifies the user for access control enforcement, and how to achieve this with a different IdP. + +### Two Approaches + +| Approach | When to Use | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| **Approach A: Swap IdP, Keep Cedar Policy** | The new IdP supports token enrichment (injecting custom claims into M2M tokens at issuance time) | +| **Approach B: Gateway Interceptors** | The new IdP does NOT support token enrichment, OR fully dynamic access control is needed | + +--- + +## 2. Approach A — Swap IdP, Keep Cedar Policy + +**For:** IdPs that support token enrichment — Okta (Token Inline Hooks), Auth0 (Actions), Entra ID (Claims Mapping Policies), or similar. + +### 2a. How It Works + +The architecture remains the same as the current Cognito flow. Cognito-specific components are replaced with equivalents from the new IdP: + +``` +User JWT → Runtime (validates user via new IdP's OIDC) → Runtime gets M2M token from new IdP's token endpoint → IdP's token enrichment hook injects user claims (replaces Pre-Token Lambda) → Gateway authorizer validates M2M token (via new IdP's OIDC discovery URL) → Cedar Policy Engine evaluates user claims → allows/denies (UNCHANGED) → Target Lambda receives tool input only (UNCHANGED) +``` + +**Key insight:** The AgentCore Gateway's CUSTOM_JWT authorizer is **IdP-agnostic**. The official AWS documentation states: + +> "The inbound authorizer is Identity Provider (IdP) agnostic and works with any OAuth 2.0 compatible identity provider." + +Only a valid OIDC discovery URL is needed, and the Gateway will validate tokens from any issuer. + +### 2b. What Needs to Change (Component Mapping) + +| Component | Current (Cognito) | New (Third-Party IdP) | +| ---------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| Gateway authorizer discovery URL | `https://cognito-idp.{region}.amazonaws.com/{pool_id}/.well-known/openid-configuration` | `https://{your-idp}/.well-known/openid-configuration` | +| Token endpoint | `POST https://{cognito_domain}/oauth2/token` | IdP's token endpoint (e.g., `https://{okta_domain}/oauth2/v1/token`) | +| Identity propagation to token enrichment | `aws_client_metadata: {"verified_user_id": "..."}` (Cognito-specific) | IdP-specific mechanism (see below) | +| Token enrichment mechanism | Pre-Token Lambda (V3, Cognito trigger) | IdP's native hook (see below) | +| Cedar Policy | **Unchanged** — still reads `principal.getTag("department")` etc. | **Unchanged** | +| Target Lambda | **Unchanged** — receives tool input only | **Unchanged** | + +### IdP-Specific Token Enrichment Mechanisms + +| IdP | Token Enrichment Feature | How Identity Is Propagated | +| ------------ | --------------------------------- | ----------------------------------------------------------------------------------------------- | +| **Okta** | Token Inline Hooks | Hook receives client context; can enrich based on custom parameters passed in the token request | +| **Auth0** | Actions (post-client-credentials) | Actions can read metadata attached to the M2M application or use a custom `audience` parameter | +| **Entra ID** | Claims Mapping Policies | Application roles + claims mapping; can include group memberships directly in the token | +| **Cognito** | Pre-Token Lambda (V3) | `aws_client_metadata` passes user_id to the Lambda | + +### 2c. Common Concerns + +**Q: Does the LLM ever see the token?** + +No. Tokens live in the HTTP transport layer managed by the Python agent code and the MCP client library. The LLM only interacts with tool schemas and tool results — it has no access to HTTP headers or tokens. + +**Q: What if the IdP doesn't support injecting arbitrary claims into M2M tokens?** + +Use Approach B (Gateway Interceptors) described in the next section. + +--- + +## 3. Approach B — Gateway Interceptors (No Token Enrichment Needed) + +**For:** Any OIDC-compliant IdP, even those without token enrichment hooks. Also useful when fully dynamic access control is needed (e.g., permissions stored in a database that change at runtime). + +### 3a. How It Works + +Instead of enriching the M2M token with user claims, the Runtime passes user identity as a **separate custom header**. The Gateway Interceptor Lambda reads this header and makes access control decisions. + +There are two options for how the Runtime passes user identity to the Gateway: + +#### Option 1: Pass User ID Only + +The Runtime extracts the user_id from the validated user JWT and passes it as a plain string header: + +``` +User JWT → Runtime (validates user JWT via any IdP's OIDC) + → Runtime extracts user_id from validated JWT + → Runtime gets a plain M2M token from IdP (no user claims needed) + → Runtime sends to Gateway: + - Authorization: Bearer ← proves machine trust + - X-User-Id: alice@company.com ← carries user identity (plain string) + → Gateway authorizer validates M2M token only (machine trust) + → Request Interceptor Lambda fires: + - Reads X-User-Id from custom header + - Looks up user's permissions (from IdP groups, DB, YAML, etc.) + - Allows or denies the tools/call request + → Response Interceptor Lambda fires (for tools/list): + - Same permission lookup + - Filters the tool list to only show permitted tools + → Target receives tool input only (no tokens, no headers) +``` + +**Runtime code:** + +```python +# In the agent code (runs in AgentCore Runtime) +user_id = extract_user_id_from_context(context) # from validated user JWT +m2m_token = await get_m2m_token() # plain M2M, no user claims + +mcp_client = MCPClient( + gateway_url=GATEWAY_URL, + headers={ + "Authorization": f"Bearer {m2m_token}", # machine trust + "X-User-Id": user_id, # user identity (plain string) + } +) +``` + +**Interceptor reads it:** + +```python +def lambda_handler(event, context): + """Request interceptor: reads user identity from plain string header.""" + gateway_request = event['mcp']['gatewayRequest'] + headers = gateway_request.get('headers', {}) + + # Simple string extraction — no JWT decoding needed + user_id = headers.get('X-User-Id', '') + + # Look up permissions for this user (from DB, YAML, IdP API, etc.) + permissions = get_user_permissions(user_id) + # ... allow or deny based on permissions +``` + +| Pros | Cons | +| --------------------------------------- | ----------------------------------------------------------------------------- | +| Simple — no JWT decoding in interceptor | Relies on trust in the Gateway boundary | +| Low latency — no signature verification | Interceptor must call an external source (DB, IdP API) to get user attributes | +| Minimal data exposure | Cannot verify the user_id is authentic within the interceptor itself | + +**When to use:** When the Gateway boundary is the trust boundary and only a legitimate Runtime (validated by the M2M token) can set this header. + +--- + +#### Option 2: Pass Full User JWT + +The Runtime passes the original user JWT as a separate header. The interceptor can then **verify the JWT signature** to prove it hasn't been tampered with, and extract all user claims directly from it. + +``` +User JWT → Runtime (validates user JWT via any IdP's OIDC) + → Runtime keeps the original user JWT + → Runtime gets a plain M2M token from IdP (no user claims needed) + → Runtime sends to Gateway: + - Authorization: Bearer ← proves machine trust + - X-User-Token: ← carries full user identity (verifiable) + → Gateway authorizer validates M2M token only (machine trust) + → Request Interceptor Lambda fires: + - Reads X-User-Token from custom header + - Verifies the JWT signature (using IdP's public keys from JWKS endpoint) + - Extracts claims: user_id, email, groups, department, etc. + - Decides allow/deny based on claims + → Response Interceptor Lambda fires (for tools/list): + - Same JWT verification and claim extraction + - Filters the tool list based on user's claims + → Target receives tool input only (no tokens, no headers) +``` + +**Runtime code:** + +```python +# In the agent code (runs in AgentCore Runtime) +user_jwt = get_user_jwt_from_request(context) # the original user JWT (already validated by Runtime's authorizer) +m2m_token = await get_m2m_token() # plain M2M, no user claims + +mcp_client = MCPClient( + gateway_url=GATEWAY_URL, + headers={ + "Authorization": f"Bearer {m2m_token}", # machine trust + "X-User-Token": user_jwt, # full user JWT (verifiable) + } +) +``` + +**Interceptor reads and verifies it:** + +```python +import jwt +import requests +from functools import lru_cache + +# Cache the JWKS (public keys) from the IdP +@lru_cache(maxsize=1) +def get_jwks(jwks_url): + """Fetch and cache JWKS public keys from the IdP.""" + response = requests.get(jwks_url) + return response.json() + +def lambda_handler(event, context): + """Request interceptor: verifies user JWT and extracts claims.""" + gateway_request = event['mcp']['gatewayRequest'] + headers = gateway_request.get('headers', {}) + + # Extract the full user JWT + user_token = headers.get('X-User-Token', '') + + if not user_token: + return deny_request("No user token provided") + + # Verify JWT signature and extract claims + try: + # Get public keys from the IdP's JWKS endpoint + jwks = get_jwks(IDP_JWKS_URL) # e.g., https://the-idp/.well-known/jwks.json + + # Decode and verify the JWT + claims = jwt.decode( + user_token, + jwks, + algorithms=["RS256"], + audience=EXPECTED_AUDIENCE, + issuer=EXPECTED_ISSUER + ) + except jwt.ExpiredSignatureError: + return deny_request("User token expired") + except jwt.InvalidTokenError as e: + return deny_request(f"Invalid user token: {e}") + + # Extract user attributes directly from verified claims + user_id = claims.get('sub', '') + department = claims.get('department', '') + groups = claims.get('groups', []) # or 'cognito:groups' for Cognito + role = claims.get('role', 'viewer') + + # Make access control decision based on verified claims + tool_name = gateway_request['body'].get('params', {}).get('name', '') + + if not is_authorized(user_id, department, groups, role, tool_name): + return deny_request(f"User {user_id} ({department}) not authorized for {tool_name}") + + # Authorized — pass through + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": gateway_request + } + } +``` + +| Pros | Cons | +| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| Cryptographically verifiable — interceptor can prove the JWT is authentic | More complex — requires JWT library and JWKS fetching in the interceptor | +| All user claims available directly (no external lookup needed) | Slightly higher latency (signature verification + JWKS cache) | +| Defense-in-depth — even if M2M token is compromised, user JWT must also be valid | Larger header size (full JWT vs. simple string) | +| Works even when the Runtime boundary is not fully trusted | Must handle token expiry (user JWT may expire before M2M token) | + +**When to use:** When defense-in-depth is needed (two independent verifications), when multiple user claims are needed in the interceptor without calling an external service, or when security requirements demand cryptographic proof of user identity at the Gateway level. + +--- + +#### Comparison: Option 1 vs. Option 2 + +| Aspect | Option 1: X-User-Id (string) | Option 2: X-User-Token (full JWT) | +| ----------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------- | +| **Security model** | Trust the Gateway boundary (M2M token proves caller is legitimate) | Verify independently (JWT signature proves user identity) | +| **What interceptor receives** | Plain user_id string | Full JWT with all claims | +| **Interceptor complexity** | Simple — just read header, look up permissions | More complex — verify JWT, extract claims | +| **External lookups needed** | Yes — must query DB/IdP for user attributes | No — claims are in the JWT | +| **Latency** | Lower (no crypto) + lookup time | Higher (crypto verification) but no lookup needed | +| **Token expiry concern** | None (it's just a string) | Must handle — user JWT may expire | +| **Spoofing risk** | Low (only valid Runtimes can reach Gateway) | None (JWT signature is cryptographic proof) | +| **Best for** | Internal systems with trusted Runtime boundary | High-security environments, zero-trust architectures | + +--- + +**Key insight (applies to both options):** The M2M token and user identity are **separate concerns**: + +- **M2M token** (in `Authorization` header) → proves the Runtime is a legitimate caller → validated by Gateway authorizer +- **User identity** (in `X-User-Id` or `X-User-Token` header) → tells the interceptor WHO the request is for → used for access control decisions + +The interceptor replaces BOTH the Pre-Token Lambda AND the Cedar Policy Engine's role in checking user identity. The Gateway authorizer still validates the M2M token (to ensure the call is legitimate), but the access control decision moves from Cedar Policy to the Interceptor Lambda. + +> **Note:** In both options, the LLM never has access to either token or header. They exist only in the HTTP transport layer managed by the agent's Python code and MCP client library. The LLM only interacts with tool schemas and tool results. + +### 3b. Request Interceptor (Controls `tools/call`) + +The request interceptor fires **BEFORE** the target Lambda executes. It decides whether to allow or deny the tool invocation. + +**Flow:** + +``` +Agent calls tools/call → Gateway → Request Interceptor Lambda → (if allowed) → Target +``` + +**What it does:** + +1. Extracts the user identity from the custom header (e.g., `X-User-Id`) +2. Identifies which tool is being invoked +3. Checks if the user has permission (via scopes, DB lookup, YAML, etc.) +4. **If authorized** → passes request through to the target +5. **If unauthorized** → returns a structured MCP error, target never executes + +**Example code:** + +```python +def lambda_handler(event, context): + """Request interceptor: controls tools/call access.""" + gateway_request = event['mcp']['gatewayRequest'] + + # Extract user identity from custom header + headers = gateway_request.get('headers', {}) + user_id = headers.get('X-User-Id', '') + + # Identify which tool is being called + tool_name = gateway_request['body'].get('params', {}).get('name', '') + target = gateway_request.get('target', '') + + # Look up permissions (from DB, YAML, IdP groups, etc.) + if not check_tool_authorization(user_id, tool_name, target): + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": { + "statusCode": 403, + "body": { + "error": { + "code": "UNAUTHORIZED", + "message": f"User {user_id} is not authorized to call {tool_name}" + } + } + } + } + } + + # Authorized — pass through to target + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayRequest": gateway_request + } + } + + +def check_tool_authorization(user_id, tool, target): + """Check if user has permission to call this tool. + + This can query a database, read a YAML config, call an IdP API, etc. + """ + user_scopes = get_user_scopes(user_id) # from DB or IdP + if target in user_scopes: + return True + return f"{target}:{tool}" in user_scopes +``` + +### 3c. Response Interceptor (Controls `tools/list`) + +The response interceptor fires **AFTER** the target responds. It filters the tool list so the agent only sees tools the user is authorized to use. + +**Flow:** + +``` +Agent calls tools/list → Gateway → Target returns ALL tools → Response Interceptor → Filtered list +``` + +**What it does:** + +1. Receives the full tool list from the target +2. Extracts user identity from the response payload headers +3. For each tool, checks if the user is authorized +4. Returns a transformed response with only permitted tools + +**Example code:** + +```python +def lambda_handler(event, context): + """Response interceptor: filters tools/list results.""" + # Extract gateway response and authorization header + gateway_response = event['mcp']['gatewayResponse'] + auth_header = gateway_response['headers'].get('Authorization', '') + + # Extract user identity (from custom header passed through) + user_id = gateway_response['headers'].get('X-User-Id', '') + + # Get tools from gateway response + tools = gateway_response['body']['result'].get('tools', []) + # Also check structuredContent (for semantic search responses) + if not tools: + tools = gateway_response['body']['result'].get('structuredContent', {}).get('tools', []) + + # Look up user permissions and filter tools + user_scopes = get_user_scopes(user_id) # from DB, YAML, IdP, etc. + filtered_tools = filter_tools_by_scope(tools, user_scopes) + + # Return transformed response with filtered tools + return { + "interceptorOutputVersion": "1.0", + "mcp": { + "transformedGatewayResponse": { + "statusCode": 200, + "headers": {"Authorization": auth_header}, + "body": { + "result": {"tools": filtered_tools} + } + } + } + } + + +def filter_tools_by_scope(tools, allowed_scopes): + """Filter tools based on user's allowed scopes.""" + filtered_tools = [] + for tool in tools: + target, action = tool['name'].split('___') + # Check if user has full target access or specific tool access + if target in allowed_scopes or f"{target}:{action}" in allowed_scopes: + filtered_tools.append(tool) + return filtered_tools +``` + +### 3d. CDK Configuration + +```typescript +import { LambdaInterceptor } from "aws-cdk-lib/aws-bedrockagentcore" + +// Request interceptor — fires BEFORE target +const requestInterceptor = LambdaInterceptor.forRequest(requestInterceptorLambda, { + passRequestHeaders: true, // Required: enables reading custom headers (X-User-Id, etc.) +}) + +// Response interceptor — fires AFTER target responds +const responseInterceptor = LambdaInterceptor.forResponse(responseInterceptorLambda, { + passRequestHeaders: true, // Required: enables reading headers in the response payload +}) + +// Attach to Gateway +const gateway = new Gateway(this, "MyGateway", { + // ... other config + interceptors: [requestInterceptor, responseInterceptor], +}) +``` + +**Important:** `passRequestHeaders: true` is required. By default, headers are NOT forwarded to interceptors for security reasons (headers may contain sensitive credentials). This must be explicitly opted in. + +### 3e. Security & Common Concerns + +**Q: Does the LLM ever touch the user token?** + +No. The token and user identity live entirely in the HTTP transport layer: + +``` +┌─────────────────────────────────────────────────────────┐ +│ AgentCore Runtime │ +│ │ +│ ┌──────────────────┐ ┌───────────────────────────┐ │ +│ │ Agent Code │ │ LLM (Bedrock) │ │ +│ │ (Python) │◄──►│ │ │ +│ │ │ │ Only sees: │ │ +│ │ Has access to: │ │ - Tool schemas │ │ +│ │ - User JWT │ │ - Tool results │ │ +│ │ - M2M token │ │ - Conversation text │ │ +│ │ - HTTP headers │ │ │ │ +│ └────────┬─────────┘ └───────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────┐ │ +│ │ MCP Client │ ← handles HTTP transport │ +│ │ (tokens, headers │ (invisible to LLM) │ +│ │ live here) │ │ +│ └──────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +The LLM only says "call tool X with input Y" — the MCP client handles all HTTP communication including tokens and headers. + +**Q: Who validates what?** + +| Component | What It Validates | Purpose | +| ------------------------------- | ----------------------------------- | ---------------------------------------------------------- | +| Gateway Authorizer (CUSTOM_JWT) | M2M token from IdP | "Is this a legitimate Runtime calling me?" (machine trust) | +| Request Interceptor Lambda | User identity from X-User-Id header | "Is THIS USER allowed to use THIS TOOL?" (access control) | + +They work in sequence: + +1. Authorizer fires first → if M2M token is invalid, request is rejected (401) +2. Interceptor fires second → if user doesn't have permission, request is denied (403) +3. Target fires last → only receives tool input (no tokens, no user context) + +**Q: Can interceptors and Cedar Policy coexist?** + +Yes. They serve complementary purposes: + +- **Cedar Policy** handles static, declarative rules (e.g., "finance department can access financial tools") +- **Interceptors** handle dynamic cases (e.g., permissions from a database that change at runtime) + +When both are active, the evaluation order is: + +1. Gateway authorizer validates the token +2. Cedar Policy Engine evaluates (if configured) +3. Interceptors fire (request before target, response after target) + +A request must pass ALL checks to succeed. + +--- + +## 4. Cedar Policy vs. Interceptors — When to Use Which + +| Feature | Cedar Policy | Gateway Interceptors | +| ---------------------------------- | ---------------------------------------- | --------------------------------------------------------------------- | +| Where filtering happens | Policy Engine (built-in, managed) | Lambda code | +| What it checks | JWT claims (tags) + context.input | User identity (from header) + any external source (DB, YAML, IdP API) | +| Dynamic? | Static rules (redeploy policy to change) | Fully dynamic (Lambda can query anything at runtime) | +| Tool filtering (tools/list) | Automatic via PartiallyAuthorizeActions | Implemented in response interceptor | +| Tool execution (tools/call) | Automatic via AuthorizeAction | Implemented in request interceptor | +| Requires token enrichment? | Yes — claims must be in the JWT | No — can read headers, query external sources | +| Input validation | Yes — `context.input.amount < 1000` | Yes — Lambda has access to the full request payload | +| Schema translation / PII redaction | No | Yes — interceptor can transform request/response | +| Multi-tenant isolation | Limited (claim-based only) | Full flexibility (can query tenant DB) | +| No code needed | Yes — declarative Cedar policies | No — requires Lambda code | + +### Guidance + +- **Use Cedar Policy when:** Access rules are based on user attributes (department, role) that don't change frequently, and simple, auditable, declarative policies are preferred without writing code. +- **Use Interceptors when:** Permissions are dynamic (stored in a DB), external services need to be called for authorization decisions, schema translation or PII redaction is needed, or the IdP doesn't support token enrichment. +- **Use both when:** Cedar handles the base rules (e.g., "only finance can access financial tools"), and interceptors handle edge cases (e.g., "but not during maintenance windows" or "only for specific tenants"). + +--- + +## 5. Using Cognito User Groups with Cedar Policy + +For teams staying with Cognito who want to leverage native Cognito groups instead of hardcoded mappings. + +### 5a. What Are Cognito User Groups? + +Cognito User Groups are a built-in feature for organizing users into logical groups (e.g., finance, engineering, admin). They provide: + +- A way to categorize users by role, department, or access level +- Automatic inclusion of group names in user authentication tokens (`cognito:groups` claim) +- IAM role association per group (for AWS resource access) + +**How users get assigned to groups:** + +| Method | When It Happens | Use Case | +| -------------------------------- | --------------------------------------- | ---------------------------------------- | +| AWS Console | Manual admin action | Ad-hoc group management | +| AdminAddUserToGroup API | Programmatic (e.g., in a Lambda) | Automated assignment during registration | +| Post-Confirmation Lambda Trigger | Automatically after user confirms email | Default group assignment for new users | +| Admin SDK / CLI | Batch operations | Bulk user management | + +**Example: Auto-assign group on registration (Post-Confirmation Lambda):** + +```python +import boto3 + +cognito = boto3.client('cognito-idp') + +def lambda_handler(event, context): + """Post-Confirmation trigger: assign new users to a default group.""" + user_pool_id = event['userPoolId'] + username = event['userName'] + + # Assign to default group based on email domain or other logic + email = event['request']['userAttributes'].get('email', '') + + if email.endswith('@finance.company.com'): + group = 'finance' + elif email.endswith('@eng.company.com'): + group = 'engineering' + else: + group = 'general' + + cognito.admin_add_user_to_group( + UserPoolId=user_pool_id, + Username=username, + GroupName=group + ) + + return event +``` + +### 5b. The Problem: Groups Aren't in M2M Tokens + +The `cognito:groups` claim is automatically included in user authentication tokens (Authorization Code flow). However, it is NOT included in M2M tokens (Client Credentials flow). + +Since the Gateway receives M2M tokens in the current architecture, Cedar Policy never sees the user's groups natively. + +``` +User Auth Token (has groups): M2M Token (NO groups): +{ { + "sub": "alice", "sub": "machine-client-id", + "cognito:groups": [ "scope": "gateway/read gateway/write", + "finance", "token_use": "access" + "admin" // No user context! + ] } +} +``` + +### 5c. The Solution: Pre-Token Lambda Reads Groups + +In the current implementation the Runtime reads the user's `cognito:groups` from the validated access token and forwards them to the Pre-Token Lambda via `aws_client_metadata`, so the Lambda needs no API call. If you switch to an IdP whose access token does not carry group memberships, the Lambda can instead call an API such as Cognito `AdminListGroupsForUser` to fetch them and inject them as custom claims in the M2M token, as shown below. + +```python +import boto3 +import os +import logging + +logger = logging.getLogger() +cognito = boto3.client('cognito-idp') + +USER_POOL_ID = os.environ.get('USER_POOL_ID') + +def lambda_handler(event, context): + """V3 Pre-Token Lambda: injects real Cognito group info into M2M token.""" + trigger_source = event.get("triggerSource", "") + + # Only process M2M (Client Credentials) flows + if trigger_source != "TokenGeneration_ClientCredentials": + return event + + # Get the verified user_id passed from the Runtime + client_metadata = event.get("request", {}).get("clientMetadata", {}) + verified_user_id = client_metadata.get("verified_user_id", "") + + if not verified_user_id: + logger.warning("No verified_user_id in clientMetadata") + return event + + # --- REPLACES HARDCODED MAPPING --- + # Fetch user's ACTUAL Cognito groups + user_groups = get_user_groups(verified_user_id) + + # Determine department and role from groups + department = resolve_department(user_groups) + role = resolve_role(user_groups) + + # Inject into M2M access token + event["response"]["claimsAndScopeOverrideDetails"] = { + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "user_id": verified_user_id, + "department": department, + "role": role, + "user_groups": ",".join(user_groups), # e.g., "finance,admin" + } + } + } + + return event + + +def get_user_groups(username): + """Fetch user's Cognito groups via Admin API.""" + try: + response = cognito.admin_list_groups_for_user( + UserPoolId=USER_POOL_ID, + Username=username, + ) + return [group['GroupName'] for group in response.get('Groups', [])] + except Exception as e: + logger.error("Failed to fetch groups for user %s: %s", username, str(e)) + return [] + + +def resolve_department(groups): + """Resolve department from group membership.""" + if 'finance' in groups: + return 'finance' + elif 'engineering' in groups: + return 'engineering' + return 'guest' + + +def resolve_role(groups): + """Resolve role from group membership.""" + if 'admin' in groups: + return 'admin' + elif 'developer' in groups: + return 'developer' + return 'viewer' +``` + +### 5d. Cedar Policy Examples Using Groups + +Once group information is injected as claims, Cedar can use them for access control: + +**Option 1: Check department (resolved from groups)** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"finance-target___generate_report", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("department") && + principal.getTag("department") == "finance" +}; +``` + +**Option 2: Check role (resolved from groups)** + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"admin-target___delete_records", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("role") && + principal.getTag("role") == "admin" +}; +``` + +**Option 3: Check raw group membership using `like`** + +```cedar +// user_groups claim contains: "finance,admin,reporting" +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"finance-target___view_reports", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("user_groups") && + principal.getTag("user_groups") like "*finance*" +}; +``` + +> **Caution with `like` on comma-separated groups:** The pattern `like "*finance*"` would also match a group named "refinance". For precise matching, use patterns like `like "finance,*"` or `like "*,finance,*"` or `like "*,finance"` — or better yet, pre-resolve to a boolean in the Lambda (see Option 4). + +**Option 4: Pre-resolved boolean (Recommended for complex group logic)** + +In the Pre-Token Lambda, resolve group membership to simple boolean claims: + +```python +claims["is_finance_team"] = "true" if "finance" in user_groups else "false" +claims["is_admin"] = "true" if "admin" in user_groups else "false" +claims["can_delete"] = "true" if "admin" in user_groups and "finance" in user_groups else "false" +``` + +Then in Cedar: + +```cedar +permit( + principal is AgentCore::OAuthUser, + action == AgentCore::Action::"admin-target___delete_records", + resource == AgentCore::Gateway::"{{GATEWAY_ARN}}" +) +when { + principal.hasTag("is_admin") && + principal.getTag("is_admin") == "true" +}; +``` + +This approach keeps complex group logic in the Lambda, while Cedar policies remain simple and readable. + +--- + +## 6. Evolution Paths + +A summary of the three paths from the current demo architecture: + +``` +Current Demo (Cognito + hardcoded user mapping in Pre-Token Lambda) + │ + ├── Path 1: Keep Cognito, use real groups + │ ├── Replace hardcoded mapping with AdminListGroupsForUser + │ ├── Cedar Policy checks group-based claims + │ └── See: Section 5 + │ + ├── Path 2: Swap IdP that supports token enrichment + │ ├── Replace Cognito with Okta/Auth0/Entra + │ ├── Use IdP's native token hook (replaces Pre-Token Lambda) + │ ├── Cedar Policy remains unchanged + │ └── See: Section 2 + │ + └── Path 3: Swap to any IdP + Gateway Interceptors + ├── No token enrichment needed + ├── Interceptors handle all access control dynamically + ├── Works with ANY OIDC-compliant IdP + └── See: Section 3 +``` + +--- + +## 7. FAQ / Quick Reference + +**Q1: Can any IdP be used with AgentCore Gateway?** + +Yes. The Gateway's CUSTOM_JWT authorizer works with any OAuth 2.0 / OIDC-compliant identity provider. Only a valid OIDC discovery URL (`.well-known/openid-configuration`) is needed. The Gateway uses this to dynamically fetch public keys and validate tokens. + +**Q2: What's the minimum change to swap IdPs?** + +If the new IdP supports token enrichment: + +1. Change the Gateway authorizer's discovery URL +2. Change the Runtime's token endpoint call +3. Replace the Pre-Token Lambda with the IdP's native token hook +4. Everything else (Cedar Policy, targets) stays the same + +**Q3: Do interceptors replace Cedar Policy?** + +They can, but they don't have to. Interceptors and Cedar Policy serve complementary purposes: + +- **Cedar Policy** = static, declarative, no-code rules based on JWT claims +- **Interceptors** = dynamic, code-based logic that can query external sources + +Either can be used alone, or both together for defense-in-depth. + +**Q4: How to choose between Approach A and B?** + +- **Choose Approach A** if the IdP supports token enrichment and access rules are relatively static (based on user attributes like department/role) +- **Choose Approach B** if the IdP doesn't support token enrichment, OR if dynamic permissions, schema translation, PII redaction, or multi-tenant isolation is needed + +**Q5: Can both Cedar Policy and interceptors be used together?** + +Yes. When both are active: + +1. Gateway authorizer validates the token first +2. Cedar Policy Engine evaluates next (if configured) +3. Interceptors fire (request before target, response after target) + +A request must pass ALL checks. This provides defense-in-depth. + +**Q6: What about Cognito user groups — can Cedar use them?** + +Yes. The `cognito:groups` claim does not appear in the M2M token itself, but it _is_ present in the user's access token. The Runtime reads it there and forwards it to the Pre-Token Lambda via `aws_client_metadata`, which injects the resulting `department`/`role` as custom claims — no `AdminListGroupsForUser` call needed. See Section 5 for full details and code examples. + +**Q7: Is passing user identity via custom header secure?** + +Yes, because: + +1. The Gateway authorizer validates the M2M token first (proving the caller is a legitimate Runtime) +2. The custom header (`X-User-Id`) is only readable by the interceptor Lambda (with `passRequestHeaders: true`) +3. The LLM do not have access to HTTP headers — they exist only in the transport layer + +The security boundary is enforced at the Gateway level. If someone tries to call the Gateway directly (without a valid M2M token), the authorizer rejects them before the interceptor even fires. diff --git a/samples/aws-specialist-agent/docs/RUNTIME_GATEWAY_AUTH.md b/samples/aws-specialist-agent/docs/RUNTIME_GATEWAY_AUTH.md new file mode 100644 index 0000000..d1da6f9 --- /dev/null +++ b/samples/aws-specialist-agent/docs/RUNTIME_GATEWAY_AUTH.md @@ -0,0 +1,591 @@ +# AgentCore M2M Authentication Workflow + +**AgentCore Runtime <--> OAuth Provider <--> Cognito <--> AgentCore Gateway** + +This document describes the complete workflow for how the AgentCore Runtime uses an OAuth2 Credential Provider (managed by AgentCore Identity) to obtain a Cognito M2M token and authenticate requests to the AgentCore Gateway. It is split into two phases: **Deployment** (infrastructure setup) and **Runtime** (live token and request flow). + +## Background: The Two Secrets + +The authentication workflow involves two secrets: + +**Secret 1:** `//machine_client_secret` + +- Created by: CDK (`secretsmanager.Secret`) + +**Secret 2:** `bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth` + +- Created by: `oauth2ProviderLambda` Custom Resource during deployment + +Secret 1 is created via `secretsmanager.Secret` and populated from the Cognito machine client's generated secret. Secret 2 is created by the `oauth2ProviderLambda` during deployment using `secretsmanager:CreateSecret` and `secretsmanager:PutSecretValue`. + +**Note:** The `bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth` namespace is the AgentCore Identity convention for OAuth2 credentials in the default Token Vault, derived from this stack's implementation. + +## Background: The Three IAM Roles + +1. **AgentCoreRole** + - Created in: CDK construct `createAgentCoreRuntime()` + - Assumed by: AgentCore Runtime + +2. **GatewayRole** + - Created in: `createAgentCoreGateway()` + - Assumed by: AgentCore Gateway service + +3. **oauth2ProviderLambda Role** + - Created by: CDK (auto-generated for Lambda function) + - Assumed by: `oauth2ProviderLambda` function + +The `GatewayRole` uses `new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com")` as its trust principal. + +## Important: User Auth vs. M2M Auth -- They Are Separate + +The stack contains two distinct authentication flows that are independent of each other. Both use the same Cognito User Pool but serve different purposes. + +**Flow 1 -- Human User --> AgentCore Runtime (inbound to Runtime):** + +- Uses the human-facing Cognito app client (`userPoolClientId`) +- Configured on the Runtime via `RuntimeAuthorizerConfiguration.usingJWT(...)` pointing to the User Pool discovery URL +- Uses the Authorization Code grant (human login via frontend) +- The user's JWT token (including their `sub` claim) is passed to the Runtime and made available to agent code via the allowlisted `Authorization` header (`requestHeaderConfiguration`) + +**Flow 2 -- AgentCore Runtime --> AgentCore Gateway (M2M, outbound from Runtime):** + +- Uses the `machineClient` — a separate Cognito app client with `clientCredentials: true` and `generateSecret: true` +- Uses the Client Credentials grant (no human user involved) +- The M2M token is obtained by the Runtime via AgentCore Identity's Token Vault and used to authenticate calls to the Gateway +- The Runtime propagates the user's identity (and `cognito:groups`) into this M2M token via `aws_client_metadata` so the Gateway's Cedar policy can authorize per user — see [Identity Propagation & Cedar Policy Guide](IDENTITY_POLICY.md) + +The two flows are parallel, not nested: + +``` +Human User + --> Cognito (Authorization Code, userPoolClientId) + --> User JWT token + --> AgentCore Runtime (validates via userPoolClientId authorizer) + --> Agent code runs... + --> Needs to call Gateway + --> AgentCore Identity Token Vault (Client Credentials, machineClient) + --> Cognito issues M2M JWT (machineClient.userPoolClientId) + --> AgentCore Gateway (validates via machineClient.userPoolClientId authorizer) + --> Tool Lambda +``` + +The user's Cognito pool and the machine client both use the same Cognito User Pool for CDK convenience, and serve different authentication purposes. The user's validated identity does not change _how_ the M2M token is obtained (always Client Credentials via the Token Vault), but it _is_ carried into the token's claims via `aws_client_metadata` so the Gateway can authorize per user (see [Identity Propagation](IDENTITY_POLICY.md)). + +## PHASE 1: DEPLOYMENT WORKFLOW + +This phase runs once during `cdk deploy`. Its goal is to register the OAuth2 Credential Provider in AgentCore Identity so the Runtime can use it at runtime. + +### Step D1 -- Cognito M2M Infrastructure Setup + +CDK provisions the Cognito resources needed for M2M authentication. + +**Resources created:** + +- `UserPoolResourceServer` -- defines API scopes (read, write) under identifier `-gateway` +- `UserPoolClient` (Machine Client) -- confidential app client with `clientCredentials: true` and `generateSecret: true` +- `secretsmanager.Secret` (Secret 1) -- stores the machine client's `client_id` and `client_secret` + +**IAM Role Active:** None (CDK CloudFormation execution role handles provisioning) + +**Data Flow:** + +``` +CDK CloudFormation + --> Creates Cognito User Pool Resource Server + --> Identifier: -gateway + --> Scopes: read, write + --> Creates Cognito Machine Client + --> Grant type: CLIENT_CREDENTIALS + --> generateSecret: true --> Cognito generates client_id + client_secret + --> Stores client_secret in Secrets Manager as Secret 1 + --> Path: //machine_client_secret +``` + +**Why:** The Machine Client is the OAuth2 identity used to request M2M tokens from Cognito. The Resource Server defines what scopes those tokens are valid for. Secret 1 is the secure, CDK-managed store for the credentials that will be used in the next step. + +### Step D2 -- AgentCore Gateway Deployment with Cognito JWT Authorizer + +CDK deploys the AgentCore Gateway (`CfnGateway`) configured with a `CUSTOM_JWT` authorizer pointing to the Cognito User Pool. + +**Key configuration:** + +``` +authorizerType: "CUSTOM_JWT" +authorizerConfiguration: + customJwtAuthorizer: + discoveryUrl: https://cognito-idp..amazonaws.com//.well-known/openid-configuration + allowedClients: [ machineClient.userPoolClientId ] +``` + +**IAM Role Active:** GatewayRole + +**GatewayRole Permissions:** + +- `lambda:InvokeFunction` on `toolLambda` -- Invoke MCP tool Lambda targets +- `bedrock:InvokeModel`, `bedrock:InvokeModelWithResponseStream` +- `ssm:GetParameter`, `ssm:GetParameters` +- `cognito-idp:DescribeUserPoolClient` on User Pool ARN -- Introspect Cognito app client configuration for JWT validation +- `cognito-idp:InitiateAuth` on User Pool ARN -- Initiate Cognito auth flows for token-related operations +- `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents` + +**Data Flow:** + +``` +CDK CloudFormation + --> Creates CfnGateway + --> authorizerType: CUSTOM_JWT + --> discoveryUrl: Cognito OIDC discovery URL (for JWKS fetching at runtime) + --> allowedClients: [ machineClient.userPoolClientId ] + --> Creates CfnGatewayTarget + --> Protocol: MCP + --> Target: toolLambda ARN + --> Credential provider: GATEWAY_IAM_ROLE + --> Stores Gateway URL in SSM: //gateway_url +``` + +**Why:** The `discoveryUrl` specifies which Cognito User Pool to trust for token validation and is used at runtime to fetch the JWKS (public keys) for JWT signature verification. The `allowedClients` restriction ensures only tokens issued to the machine client are accepted. + +### Step D3 -- OAuth2 Credential Provider Registration (Custom Resource) + +The `oauth2ProviderLambda` (backed by a CDK Custom Resource) runs and registers the OAuth2 Credential Provider in AgentCore Identity's Token Vault. + +**IAM Role Active:** oauth2ProviderLambda execution role (auto-created by CDK) + +**oauth2ProviderLambda Role Permissions:** + +- `secretsmanager:GetSecretValue` on Secret 1 (`//machine_client_secret`) -- Read the Cognito machine client's `client_id` and `client_secret` from CDK-managed storage. These credentials are the source of truth and must be read before they can be registered with AgentCore Identity. +- `secretsmanager:CreateSecret` on Secret 2 (`bedrock-agentcore-identity!default/oauth2/*`) -- Create a new secret in the AgentCore Identity namespace in Secrets Manager. This namespace is where the Token Vault expects to find credentials at runtime. +- `secretsmanager:PutSecretValue` on Secret 2 -- Write the `client_id` and `client_secret` values into the newly created AgentCore Identity-managed secret, so the Token Vault can read them during M2M token retrieval. +- `secretsmanager:DescribeSecret` on Secret 2 -- Check whether Secret 2 already exists before attempting to create it. This enables idempotency -- if the stack is redeployed, the Lambda will not fail trying to create a secret that already exists. +- `secretsmanager:DeleteSecret` on Secret 2 -- Remove Secret 2 when the CDK stack is destroyed. Without this, the AgentCore Identity-managed secret would remain orphaned in Secrets Manager after stack deletion. +- `bedrock-agentcore:CreateOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- Register the OAuth2 Credential Provider in the Token Vault, linking the provider name (`-runtime-gateway-auth`) to the Cognito discovery URL, `client_id`, and Secret 2. This is the core registration step that makes the provider available to the Runtime at runtime. +- `bedrock-agentcore:GetOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- Verify that the provider was successfully created after registration. Also used on re-deploy to check whether the provider already exists before attempting to create it again (idempotency). +- `bedrock-agentcore:DeleteOauth2CredentialProvider` on `token-vault/default` and `token-vault/default/oauth2credentialprovider/*` -- Remove the provider registration from the Token Vault when the CDK stack is destroyed. Without this, the provider entry would remain orphaned in the Token Vault after stack deletion. +- `bedrock-agentcore:CreateTokenVault` on `token-vault/default` and `token-vault/default/*` -- Ensure the default Token Vault exists before attempting to register the provider. If the Token Vault has not been created yet, this permission allows the Lambda to create it as a prerequisite. +- `bedrock-agentcore:GetTokenVault` on `token-vault/default` and `token-vault/default/*` -- Check the status of the default Token Vault before operating on it. Used to confirm the vault is available and ready before registering the provider. +- `bedrock-agentcore:DeleteTokenVault` on `token-vault/default` and `token-vault/default/*` -- Clean up the Token Vault on stack destruction if needed. This is a defensive cleanup permission for full teardown scenarios. + +**Data Flow:** + +``` +oauth2ProviderLambda + | + |-- 1. Reads Secret 1 + | --> Gets: client_id, client_secret + | + |-- 2. Creates Secret 2 in Secrets Manager + | --> Namespace: bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth + | --> Stores: { client_id, client_secret } + | + |-- 3. Calls bedrock-agentcore:CreateOauth2CredentialProvider + | --> Provider name: -runtime-gateway-auth + | --> discoveryUrl: Cognito OIDC discovery URL + | --> clientId: machineClient.userPoolClientId + | --> secretArn: Secret 2 ARN + | --> grantType: CLIENT_CREDENTIALS (M2M / 2LO) + | + └-- 4. Provider is now registered in Token Vault (default) + --> Provider ARN: arn:aws:bedrock-agentcore:::token-vault/default/oauth2credentialprovider/-runtime-gateway-auth +``` + +**Why:** This registration step connects the logical provider name (`GATEWAY_CREDENTIAL_PROVIDER_NAME` env var) to the actual Cognito OAuth2 configuration, enabling the Runtime to obtain M2M tokens through AgentCore Identity. + +### Step D4 -- AgentCore Runtime Deployment + +CDK deploys the AgentCore Runtime with the AgentCoreRole and environment variables, including `GATEWAY_CREDENTIAL_PROVIDER_NAME`. + +**IAM Role Active:** AgentCoreRole + +**AgentCoreRole Permissions (M2M-relevant):** + +- `bedrock-agentcore:GetOauth2CredentialProvider` on `oauth2-credential-provider/*` -- Look up the registered OAuth2 Credential Provider metadata by logical name (the value of `GATEWAY_CREDENTIAL_PROVIDER_NAME`). This is called inside the `@requires_access_token` decorator at runtime to resolve the provider name to its Cognito token URL, `client_id`, and Secret 2 reference. Without this permission, the decorator cannot initiate the token retrieval process. +- `bedrock-agentcore:GetResourceOauth2Token` on `token-vault/*` -- Request the M2M access token from the Token Vault. This is the primary permission for obtaining the Cognito JWT at runtime. The Token Vault either returns a cached valid token or fetches a new one from Cognito using the Client Credentials grant. +- `bedrock-agentcore:GetResourceOauth2Token` on `workload-identity-directory/*` -- Resolve the caller's IAM identity (AgentCoreRole) to a Workload Identity registered in the AgentCore Identity directory. The Token Vault uses this Workload Identity to scope the cached token to this specific agent, ensuring token isolation between agents even if they share the same IAM role. +- `secretsmanager:GetSecretValue` on Secret 2 (`bedrock-agentcore-identity!default/oauth2/-runtime-gateway-auth`) -- Required for IAM delegation: when the Token Vault needs to fetch a new token from Cognito (cache miss), AgentCore Identity reads Secret 2 using the Runtime's IAM role rather than its own service role. This means the Runtime must have `GetSecretValue` on Secret 2 -- not because the Runtime reads it directly, but because AgentCore Identity acts on behalf of the Runtime's role to access the credential. This design prevents privilege escalation: a caller cannot use AgentCore Identity as a proxy to access secrets it does not have direct permission to read. +- `secretsmanager:GetSecretValue` on Secret 1 (`//machine_client_secret`) -- Defensive / testing use only. The standard Token Vault flow only requires Secret 2 access (via IAM delegation). Secret 1 access allows the Runtime or test scripts running inside it to call the Cognito token endpoint directly if needed outside the Token Vault flow. This permission is not required for the standard M2M path. +- `ssm:GetParameter`, `ssm:GetParameters` on `//*` -- Read the Gateway URL (`//gateway_url`) and other configuration values from SSM Parameter Store at runtime. The Gateway URL is fetched once when `create_gateway_mcp_client()` is called and used for all subsequent MCP connections. + +**Data Flow:** + +``` +CDK CloudFormation + --> Creates AgentCore Runtime + --> Assigns AgentCoreRole as execution role + --> Sets environment variables: + GATEWAY_CREDENTIAL_PROVIDER_NAME = -runtime-gateway-auth + STACK_NAME = + MEMORY_ID = + --> Configures inbound JWT authorizer (for human users calling the Runtime): + discoveryUrl: Cognito User Pool discovery URL + allowedAudiences: [ userPoolClientId ] <-- human-facing app client, NOT machine client + --> Configures requestHeaderConfiguration: + allowlistedHeaders: [ "Authorization" ] <-- so agent code can read the user's JWT +``` + +**Why the `GATEWAY_CREDENTIAL_PROVIDER_NAME` env var:** Decouples agent code from the specific provider ARN. Agent code only needs the logical name; AgentCore Identity resolves the provider configuration. This makes agent code portable across environments and stacks. + +## PHASE 2: RUNTIME WORKFLOW + +This phase runs every time the agent code needs to call the AgentCore Gateway. Its goal is to obtain a valid Cognito M2M token and use it to authenticate the Gateway request. + +### Step R1 -- MCP Client Creation and Token Retrieval Setup + +`create_gateway_mcp_client()` is called to set up the MCP client for Gateway communication. The Gateway URL is read from SSM once at this point (it is stable and does not change). The token retrieval is deferred into a lambda factory so it runs fresh on every MCP connection and reconnection. + +**IAM Role Active:** AgentCoreRole + +**Permission Used:** `ssm:GetParameter` on `//*` (for Gateway URL lookup) + +**Data Flow:** + +``` +create_gateway_mcp_client() is called + | + |-- 1. Reads STACK_NAME from env var + | + |-- 2. Reads Gateway URL from SSM: + | ssm:GetParameter --> //gateway_url + | <-- Returns: Gateway URL (stable, fetched once) + | + └-- 3. Creates MCPClient with lambda factory: + MCPClient( + lambda: streamablehttp_client( + url=gateway_url, + headers={"Authorization": f"Bearer {_fetch_gateway_token()}"} + ), + prefix="gateway", + ) + --> Token is NOT fetched here -- deferred into the lambda + --> _fetch_gateway_token() will be called on every MCP connection/reconnection +``` + +**Why the lambda factory pattern:** This avoids the "closure trap". If `_fetch_gateway_token()` were called outside the lambda, Python's closure would capture the token value at client creation time. That token is valid for 60 minutes. If the MCP client reconnects after expiry, it would use the stale captured token and receive a 401 from the Gateway. By calling `_fetch_gateway_token()` inside the lambda, a fresh token is obtained on every MCP connection. The Token Vault's caching layer (inside the `@requires_access_token` decorator) ensures this is efficient -- if the token is still valid, the cached one is returned immediately without calling Cognito. + +**Why the SSM read is outside the lambda:** The Gateway URL is a stable infrastructure endpoint that does not change between connections. It is safe and efficient to read it once at client creation time rather than on every reconnection. + +### Step R2 -- Token Retrieval via @requires_access_token Decorator + +On each MCP connection or reconnection, the lambda factory executes and calls `_fetch_gateway_token()`. The `@requires_access_token` decorator (part of the AgentCore Identity Python SDK) intercepts this call and handles all OAuth mechanics internally. + +**IAM Role Active:** AgentCoreRole + +**Data Flow:** + +``` +Lambda factory executes (on each MCP connection/reconnection) + --> _fetch_gateway_token() is called + --> @requires_access_token decorator intercepts: + --> provider_name = GATEWAY_CREDENTIAL_PROVIDER_NAME env var + --> auth_flow = "M2M" (Client Credentials grant) + --> scopes = [] (Cognito embeds scopes based on machine client authorization) + --> custom_parameters = {"aws_client_metadata": json({verified_user_id, verified_groups})} + (user identity for the Pre-Token Lambda -- see Identity Propagation guide) + --> Decorator calls AgentCore Identity API internally (see R3 for details) + --> Decorator injects the obtained JWT as access_token argument + --> _fetch_gateway_token(access_token=) returns the JWT string + --> MCPClient uses JWT in Authorization: Bearer header +``` + +**Why auth_flow="M2M" and scopes=[]:** `auth_flow="M2M"` tells the decorator to use the Client Credentials grant (no user interaction). `scopes=[]` is correct for M2M -- the scopes (`-gateway/read`, `-gateway/write`) are embedded in the token by Cognito based on what the machine client is authorized for, so they do not need to be specified at the call site. + +**Note on decorator internals:** The `@requires_access_token` decorator is part of the AgentCore Identity Python SDK. Its internal token retrieval is a two-sub-step process described in R3 below. This is not visible in the agent code -- from the agent's perspective, the decorator is a single call that returns a token. + +### Step R3 -- Inside the Decorator: Token Retrieval from Token Vault + +Inside the `@requires_access_token` decorator, AgentCore Identity performs a two-sub-step process to obtain the token. AgentCore Identity checks the Token Vault for a cached, valid token. If none exists or the token is expired, it fetches a new one from Cognito. + +**IAM Role Active:** AgentCoreRole + +**Permissions Used:** + +- `bedrock-agentcore:GetOauth2CredentialProvider` on `oauth2-credential-provider/*` -- Provider metadata lookup (Sub-step 3a) +- `bedrock-agentcore:GetResourceOauth2Token` on `token-vault/*` -- Token retrieval from Token Vault (Sub-step 3b) +- `bedrock-agentcore:GetResourceOauth2Token` on `workload-identity-directory/*` -- Workload Identity resolution for token scoping (Sub-step 3b) +- `secretsmanager:GetSecretValue` on Secret 2 -- IAM delegation for client_secret access (cache miss only, Sub-step 3b) + +**Data Flow -- Sub-step 3a: Provider Metadata Lookup:** + +``` +@requires_access_token decorator (SDK internals) + --> bedrock-agentcore:GetOauth2CredentialProvider + --> Input: provider_name = -runtime-gateway-auth + --> AgentCore Identity looks up the provider registered in Token Vault (Step D3) + <-- Returns: + --> Provider ARN + --> Cognito token URL (derived from discoveryUrl registered in D3) + --> client_id (machineClient.userPoolClientId) + --> Reference to Secret 2 (where client_secret lives) + --> Grant type: CLIENT_CREDENTIALS +``` + +**Data Flow -- Sub-step 3b: Token Vault Check and Token Issuance:** + +**Cache Hit (token still valid):** + +``` + --> bedrock-agentcore:GetResourceOauth2Token + --> Resolves Workload Identity from AgentCoreRole ARN via workload-identity-directory + --> Checks Token Vault: token for this Workload Identity + provider = VALID (< 60 min old) + <-- Returns: cached JWT access token (no Cognito call needed) +``` + +**Cache Miss (no token or expired):** + +``` + --> bedrock-agentcore:GetResourceOauth2Token + --> Resolves Workload Identity from AgentCoreRole ARN via workload-identity-directory + --> Checks Token Vault: no valid token found + --> Reads Secret 2 using AgentCoreRole (IAM delegation) + --> Gets: client_secret + --> Calls Cognito token endpoint: + POST https://.auth..amazoncognito.com/oauth2/token + grant_type=client_credentials + client_id= + client_secret= + scope=-gateway/read -gateway/write + aws_client_metadata= + --> Cognito invokes the V3 Pre-Token Lambda, which reads the groups + and injects department/role claims (see Identity Propagation guide) + <-- Cognito returns: JWT access token (valid 60 minutes by default) + --> Stores token in Token Vault, scoped to this Workload Identity + <-- Returns: new JWT access token +``` + +**Why the Workload Identity matters:** The Token Vault stores tokens per Workload Identity, not per IAM role. This means even if two different agent runtimes shared the same IAM role, they would get separate token entries in the vault. The Workload Identity is the fine-grained, agent-level principal that ensures token isolation between agents. + +**Why IAM delegation for Secret 2:** AgentCore Identity reads Secret 2 using the caller's IAM role (AgentCoreRole), not its own service role. This is a deliberate security design: the secret is only accessible if the caller has both the `GetResourceOauth2Token` API permission AND the `secretsmanager:GetSecretValue` permission on Secret 2. This prevents privilege escalation -- a caller cannot use AgentCore Identity as a proxy to access secrets it doesn't have direct permission to read. + +**Why the Token Vault cache makes the lambda factory efficient:** Even though `_fetch_gateway_token()` is called on every MCP connection, the Token Vault cache means Cognito is only called when the token is expired (every ~60 minutes). All other calls return the cached token immediately, making the pattern both fresh and efficient. + +### Step R4 -- Agent Code Sends Request to Gateway + +The MCPClient uses the JWT returned by `_fetch_gateway_token()` to send an authenticated HTTP request to the AgentCore Gateway. + +**IAM Role Active:** AgentCoreRole + +**Data Flow:** + +``` +MCPClient lambda factory + --> streamablehttp_client( + url=gateway_url, <-- read from SSM in Step R1, stable + headers={"Authorization": f"Bearer {jwt}"} <-- fresh token from Step R2/R3 + ) + --> Sends HTTP request: + POST /mcp + Authorization: Bearer + Content-Type: application/json + { ... MCP tool invocation payload ... } +``` + +**Why:** The Gateway URL is stable (read once in R1) while the token is fresh (fetched on each connection in R2/R3). The `Authorization: Bearer` header is the standard OAuth2 mechanism for presenting a token to a protected resource. + +### Step R5 -- Gateway Validates the JWT Token + +The AgentCore Gateway receives the request and its `CUSTOM_JWT` authorizer validates the Bearer token. + +**IAM Role Active:** GatewayRole + +**Data Flow:** + +``` +AgentCore Gateway (CUSTOM_JWT authorizer) + | + |-- 1. Extracts Bearer token from Authorization header + | + |-- 2. Fetches JWKS from Cognito discovery URL: + | GET https://cognito-idp..amazonaws.com//.well-known/openid-configuration + | --> Gets JWKS URI + | GET + | --> Gets Cognito public keys for signature verification + | + |-- 3. Verifies JWT signature using JWKS public keys + | + |-- 4. Checks token claims: + | --> client_id claim ∈ allowedClients (machineClient.userPoolClientId) + | --> token not expired + | --> issuer matches Cognito User Pool + | + └-- 5. Authorization decision: + --> VALID --> forwards request to Gateway Target (Step R6) + --> INVALID --> returns 401 Unauthorized +``` + +**Why allowedClients scoping:** The AgentCore Gateway only accepts tokens issued to the specific machine client you created. Even if another Cognito client in the same User Pool obtained a token, it would be rejected. This is intentional tight scoping -- only the AgentCore Runtime's machine client can call this AgentCore Gateway. + +### Step R6 -- Gateway Forwards to MCP Tool Lambda + +The validated request is forwarded to the Gateway Target (the MCP tool Lambda). + +**IAM Role Active:** GatewayRole + +**Permission Used:** `lambda:InvokeFunction` on `toolLambda` + +**Data Flow:** + +``` +AgentCore Gateway (GatewayRole) + --> lambda:InvokeFunction on toolLambda + --> MCP tool invocation payload + <-- Lambda returns tool result + <-- AgentCore Gateway returns MCP response to AgentCore Runtime + <-- MCPClient delivers result to agent code + <-- Agent code continues execution with tool result +``` + +--- + +## APPENDIX: Replacing Cognito with an Alternative IdP for M2M Auth + +This section describes how to replace Cognito with an alternative identity provider (IdP) for the Runtime --> Gateway M2M authentication flow. The Client --> Runtime user authentication flow is independent and remains unchanged (see "User Auth vs. M2M Auth -- They Are Separate" above). + +### Scope of This Change + +The M2M flow uses the OAuth2 Client Credentials grant: the Runtime obtains a machine token using a `client_id` and `client_secret` registered with an IdP, and the Gateway validates that token using the IdP's JWKS. Neither the user's identity nor the user's token is involved at any point — the Runtime authenticates as itself, not on behalf of the user. + +**Note on future user-delegated flows:** This document covers the current Phase 1 implementation, where the Runtime-to-Gateway flow is pure M2M (Client Credentials grant). A future phase may introduce user-delegated (3-legged OAuth / Authorization Code grant) flows, where the Runtime obtains a token on behalf of the authenticated user and the Gateway receives a token carrying the user's identity. AgentCore Identity supports both flows. When that phase is implemented, the Gateway authorizer configuration and the `@requires_access_token` decorator's `auth_flow` parameter will need to be updated accordingly — the IdP swap guidance in this section applies to both flows. + +To replace Cognito in this flow, two trust relationships must be updated: + +1. **AgentCore Gateway's JWT authorizer** — must point to the new IdP's discovery URL and accept the new client's `client_id` +2. **AgentCore Identity's OAuth2 Credential Provider** — must be configured with the new IdP's discovery URL, `client_id`, and `client_secret` + +The Token Vault, `@requires_access_token` decorator, Workload Identity, and AgentCore Runtime code are IdP-agnostic and require no changes. + +### Supported IdPs + +AgentCore Identity supports two modes for OAuth2 providers: + +1. **Built-in managed providers** (pre-configured and maintained by AWS): Amazon Cognito, Auth0 by Okta, Atlassian, CyberArk, Dropbox, Facebook, FusionAuth, GitHub, Google, HubSpot, LinkedIn, Microsoft, Notion, Okta, OneLogin, Ping Identity, Reddit, Salesforce, Slack, Spotify, Twitch, X, Yandex, Zoom + +2. **CustomOauth2** (what this stack uses): Any OIDC-compliant IdP that exposes a `.well-known/openid-configuration` discovery endpoint. This is the generic path and works for all IdPs listed above. + +**Why this stack uses CustomOauth2 even though Cognito is a built-in vendor:** The built-in `AmazonCognito` vendor in AgentCore Identity is designed for user-delegated (3-legged OAuth / Authorization Code grant) flows, where an agent acts on behalf of a human user. The M2M Client Credentials grant used here does not involve a user — the Runtime authenticates as itself. `CustomOauth2` with a `discoveryUrl` is the correct path for Client Credentials / M2M flows regardless of which IdP is backing it, and it treats Cognito as a standard OIDC provider. This choice also makes the stack IdP-portable by design: swapping to a different IdP only requires changing the `discoveryUrl` and client credentials values, not the Custom Lambda code. + +For M2M (Client Credentials grant), the replacement IdP must support: + +- OAuth 2.0 Client Credentials grant (`grant_type=client_credentials`) +- OIDC Discovery (`.well-known/openid-configuration`) for JWKS-based JWT validation +- Configurable scopes on the client application +- Confidential client with a client secret + +Most enterprise IdPs support these requirements natively. Examples include Microsoft Entra ID, Okta, and Auth0 — refer to your IdP's documentation to confirm Client Credentials grant and OIDC Discovery support before proceeding. + +### CDK Stack Modifications Required + +#### 1. createMachineAuthentication() — Full replacement + +This method is Cognito-specific. It creates the `UserPoolResourceServer`, `UserPoolClient` (machine client), and `MachineClientSecret`. These resources establish the OAuth2 machine client identity and define the scopes it can request. For a different IdP, the equivalent setup is done in the IdP's own console or management tooling. + +**What this step must produce for the following steps:** + +- **`client_id`**: The OAuth2 client identifier for the machine client + - Used by: Gateway authorizer (`allowedClients`), Custom Resource (`properties.ClientId`) +- **`client_secret`**: The OAuth2 client secret for the machine client + - Used by: Stored in Secrets Manager as Secret 1 (`//machine_client_secret`) +- **Discovery URL**: The IdP's OIDC discovery endpoint + - Used by: Gateway authorizer (`discoveryUrl`), Custom Resource (`properties.DiscoveryUrl`) + +Store the `client_id` and `client_secret` in Secrets Manager at `//machine_client_secret` using the same `secretsmanager.Secret` pattern as the original. The discovery URL is a static string — store it as a variable or SSM parameter for use in the steps below. + +For IdP-specific setup (creating a confidential client, enabling Client Credentials grant, defining scopes), refer to your IdP's documentation. + +#### 2. createAgentCoreGateway() — Discovery URL and allowedClients values + +**Change A — Discovery URL variable:** + +```typescript +// Current (Cognito): +const cognitoIssuer = `https://cognito-idp.${this.region}.amazonaws.com/${this.userPool.userPoolId}` +const cognitoDiscoveryUrl = `${cognitoIssuer}/.well-known/openid-configuration` + +// Any OIDC-compliant IdP: +const discoveryUrl = `/.well-known/openid-configuration` +``` + +**Change B — CfnGateway authorizer configuration:** + +```typescript +authorizerConfiguration: { + customJwtAuthorizer: { + allowedClients: [], // replace machineClient.userPoolClientId + discoveryUrl: discoveryUrl, // replace cognitoDiscoveryUrl + }, +}, +``` + +**Change C — Remove Cognito-specific IAM permissions from GatewayRole:** + +```typescript +// Remove these — they are Cognito-specific and not needed for other IdPs: +// cognito-idp:DescribeUserPoolClient +// cognito-idp:InitiateAuth +``` + +**Why**: The Gateway uses the `discoveryUrl` to fetch the IdP's JWKS at runtime for JWT signature verification. The `allowedClients` restriction ensures only tokens issued to your specific machine client are accepted. The Cognito IAM permissions are only needed for Cognito-specific API calls; other IdPs use standard OIDC/JWKS endpoints that do not require AWS IAM permissions. + +#### 3. Custom Resource properties — Values only, no Lambda code change + +The `oauth2ProviderLambda` code is already IdP-agnostic. It uses `credentialProviderVendor="CustomOauth2"` with a generic `discoveryUrl` — no code changes are required. Only the properties passed to the Custom Resource change: + +```typescript +const runtimeCredentialProvider = new cdk.CustomResource(this, "RuntimeCredentialProvider", { + serviceToken: oauth2Provider.serviceToken, + properties: { + ProviderName: providerName, + ClientSecretArn: this.machineClientSecret.secretArn, // same pattern, new IdP secret + DiscoveryUrl: discoveryUrl, // new IdP discovery URL + ClientId: , // new IdP client_id + }, +}) +``` + +**Why**: The Lambda reads whatever `DiscoveryUrl` and `ClientId` are passed in and registers them with AgentCore Identity as a `CustomOauth2` provider. The Token Vault then uses these values to call the correct IdP token endpoint at runtime. The Lambda's `handle_create`, `handle_update`, and `handle_delete` handlers require zero changes. + +**Note on built-in vendors**: If you want to use a built-in vendor (listed in the Supported IdPs section above) instead of `CustomOauth2`, change `credentialProviderVendor` in `handle_create` and `handle_update` in the Lambda. Built-in vendors have AWS-managed endpoint configurations and may handle provider-specific quirks automatically. However, `CustomOauth2` with a discovery URL works for all OIDC-compliant IdPs and is simpler to maintain across environments. + +#### 4. createAgentCoreRuntime() — No changes required + +The Runtime only knows the `GATEWAY_CREDENTIAL_PROVIDER_NAME` environment variable. It does not know or care which IdP backs the provider. The Token Vault resolves the IdP interaction transparently using the configuration registered in Step D3. + +#### 5. createCognitoSSMParameters() — Rename Cognito-specific parameters (optional) + +The SSM parameters `cognito-user-pool-id`, `cognito-user-pool-client-id`, and `cognito_provider` are Cognito-specific by name. For a different IdP, rename these to IdP-agnostic equivalents (e.g., `idp-discovery-url`, `machine-client-id`). The `gateway_url` and `machine_client_id` parameters are already IdP-agnostic. + +--- + +### Summary: What Changes vs. What Does Not + +**Components that require changes:** + +- **createMachineAuthentication()** — Full replacement required + - Cognito-specific resources must be replaced with equivalent IdP client setup + - Must produce: `client_id`, `client_secret`, Discovery URL +- **CfnGateway authorizerConfiguration** — Update values + - Change `discoveryUrl` to new IdP's OIDC discovery endpoint + - Change `allowedClients` to new IdP's `client_id` +- **GatewayRole IAM permissions** — Remove Cognito-specific permissions + - Remove `cognito-idp:DescribeUserPoolClient` + - Remove `cognito-idp:InitiateAuth` +- **Custom Resource properties** — Update values only + - Change `DiscoveryUrl` to new IdP's discovery endpoint + - Change `ClientId` to new IdP's `client_id` + - Change `ClientSecretArn` to point to new IdP secret in Secrets Manager +- **SSM parameter names** — Optional rename (cosmetic only) + - Rename Cognito-specific parameter names to IdP-agnostic equivalents if desired + +**Components that require NO changes:** + +- **oauth2ProviderLambda code** — Already IdP-agnostic + - Uses generic `CustomOauth2` vendor with `discoveryUrl` parameter + - No code changes needed in `handle_create`, `handle_update`, or `handle_delete` + - **Optional**: Can switch to built-in vendor by changing `credentialProviderVendor` in Lambda code, but `CustomOauth2` works for all OIDC-compliant IdPs +- **createAgentCoreRuntime()** — Already IdP-agnostic + - Only knows `GATEWAY_CREDENTIAL_PROVIDER_NAME` environment variable + - Token Vault resolves IdP interaction transparently +- **Agent code** — Already IdP-agnostic + - Uses `@requires_access_token` decorator which is IdP-agnostic + - No changes needed in agent code regardless of which agent pattern is used +- **Client → Runtime user authentication** — Independent flow + - Separate concern from Runtime → Gateway M2M authentication + - Not affected by M2M IdP changes diff --git a/samples/aws-specialist-agent/docs/SKILLS.md b/samples/aws-specialist-agent/docs/SKILLS.md new file mode 100644 index 0000000..0baf9a2 --- /dev/null +++ b/samples/aws-specialist-agent/docs/SKILLS.md @@ -0,0 +1,19 @@ +# Skills + +This deployment mounts a curated set of "skills" (self-describing capability bundles) into the runtime via S3 Files. Skills are how the agent gains domain knowledge without bloating the model's system prompt. + +## How skills are mounted + +- Skills live under `skills/agent-toolkit-for-aws/` (pinned third-party content, kept verbatim with their LICENSE/NOTICE) and `skills/aws-specialist-agent/` (skills authored for this project). +- A build step (`scripts/build-project-guide.py`) assembles the project-specific guide and uploads everything to an S3 bucket. +- The AgentCore Runtime container mounts the bucket via S3 Files at a known path. The agent loads skills lazily by reading `SKILL.md` for each capability it needs. + +## Design notes + +- **S3 Files over a packed image**: skills change more often than the runtime image. Mounting via S3 Files lets a skill update propagate without redeploying the runtime. +- **Vendored, not submoduled**: third-party skills were originally consumed via a git submodule, then switched to a vendored copy with a pinned commit hash recorded next to the LICENSE. Vendoring avoids supply-chain surprises and lets the build run without submodule init. +- **Mount path vs. VPC trade-off**: S3 Files requires the runtime to reach S3. In closed-network deployments this means provisioning an S3 (Gateway) endpoint. The trade-off (extra endpoint vs. ability to update skills out of band) was resolved in favour of S3 Files. +- **Strict IAM on the S3 Files execution role**: the role that the runtime uses to fetch skills is scoped to the specific bucket/prefix used for skills and validated against AgentCore's IAM requirements at deploy time. +- **Self-describing project guide**: `skills/aws-specialist-agent/fast-project-guide/` is generated from the live source tree (sanitized) so the deployed agent can answer questions like "how is this app put together?" against the actual code, not a stale doc. +- **Official Code Interpreter**: the AgentCore-provided Code Interpreter tool is used directly rather than reimplementing a sandbox; framework-specific wrappers (`tools/code_interpreter/`) bridge it to the chosen agent SDK. +- **Auxiliary Lambdas attached to the closed VPC**: helper Lambdas (feedback, history, session, oauth2-provider, zip-packager, pre-token) all run inside the same VPC as the runtime to keep traffic on the private path; their security groups allow only the explicit endpoints they need. diff --git a/samples/aws-specialist-agent/docs/STREAMING.md b/samples/aws-specialist-agent/docs/STREAMING.md new file mode 100644 index 0000000..2fa8448 --- /dev/null +++ b/samples/aws-specialist-agent/docs/STREAMING.md @@ -0,0 +1,331 @@ +# Streaming Guide for Agents + +## Overview + +Your agent sends streaming events in SSE format. This guide explains how to integrate streaming with frontend. + +## Integration Steps + +1. **Your agent sends streaming events** (SSE format) +2. **The `agentcore-client` library** reads the SSE stream and routes it to the appropriate parser: + - For **Strands agents (default)**: `frontend/src/lib/agentcore-client/parsers/strands.ts` — parses Strands schema events + - For **LangGraph agents**: `frontend/src/lib/agentcore-client/parsers/langgraph.ts` + - For **Bedrock Converse (generic)**: `frontend/src/lib/agentcore-client/parsers/converse.ts` — parses raw Bedrock Converse stream events + - For **other agent frameworks**: Create a new parser and register it in `frontend/src/lib/agentcore-client/client.ts` +3. **Parsers emit typed `StreamEvent`s** (text, tool_use_start, tool_use_delta, tool_result, message, result, lifecycle) +4. **`ChatInterface.tsx`** handles events and builds message segments (interleaved text + tool calls) +5. **`ChatMessage.tsx`** renders segments inline with markdown formatting and tool call components + +--- + +## Current Implementation + +### Backend: Strands Agent + +**File:** `agent/strands-single-agent/basic_agent.py` + +The backend yields all raw Strands streaming events, serialized to JSON-safe dicts: + +```python +async for event in agent.stream_async(user_query): + yield json.loads(json.dumps(dict(event), default=str)) +``` + +**Note:** Strands events can contain non-JSON-serializable Python objects (agent instances, UUIDs, `ModelStopReason` tuples, etc.). The `json.dumps(default=str)` call converts these to strings, ensuring all events are safe to send over SSE. + +### Frontend: Event Parser + +**File:** `frontend/src/lib/agentcore-client/parsers/strands.ts` + +The default parser for `strands-single-agent` handles Strands schema events: + +```typescript +export const parseStrandsChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return; + const json = JSON.parse(line.substring(6).trim()); + + // Text token: {"data": "Hello"} + if (typeof json.data === "string") { + callback({ type: "text", content: json.data }); + } + + // Tool use: {"current_tool_use": {...}, "delta": {"toolUse": {"input": "..."}}} + if (json.current_tool_use) { + // First delta (empty input) → tool_use_start + // Subsequent deltas → tool_use_delta + } + + // Tool result: {"message": {"role": "user", "content": [{"toolResult": {...}}]}} + if (json.message?.role === "user") { + // Extract toolResult blocks → callback({ type: "tool_result", ... }) + } + + // Completion: {"result": {"stop_reason": "end_turn"}} + if (json.result) { + callback({ type: "result", stopReason: "end_turn" }); + } + + // Lifecycle: {"init_event_loop": true} + if (json.init_event_loop || json.start_event_loop) { ... } +}; +``` + +See the full implementation in the source file for edge cases. + +### Event Structure + +Strands provides these event types: + +- `data`: Text chunks (accumulate as they arrive) +- `current_tool_use`: Tool name, ID, and input parameters (with `delta` for streaming) +- `message`: Final structured message with full content (assistant with `toolUse`, user with `toolResult`) +- `result`: AgentResult with stop reason and metrics +- `init_event_loop`, `start_event_loop`, `complete`: Lifecycle markers +- `tool_stream_event`: Events streamed from tool execution +- `event`: Raw Bedrock Converse events (used by the alternative converse parser below) + +```javascript +// Text streaming +data: {"data": "Hello"} +data: {"data": " there"} + +// Tool use start — first delta has empty input +data: {"current_tool_use": {"toolUseId": "tool_abc123", "name": "text_analysis"}, "delta": {"toolUse": {"input": ""}}} + +// Tool input streaming +data: {"current_tool_use": {"toolUseId": "tool_abc123", "name": "text_analysis"}, "delta": {"toolUse": {"input": "{\"text\": \"hello\"}"}}} + +// Complete assistant message +data: {"message": {"role": "assistant", "content": [{"toolUse": {"toolUseId": "tool_abc123", "name": "text_analysis", "input": {"text": "hello"}}}]}} + +// Tool result (user message with toolResult blocks) +data: {"message": {"role": "user", "content": [{"toolResult": {"toolUseId": "tool_abc123", "content": [{"text": "Analysis complete: 1 word"}]}}]}} + +// Final result +data: {"result": {"stop_reason": "end_turn"}} + +// Lifecycle events +data: {"init_event_loop": true} +data: {"start_event_loop": true} +``` + +**Reference:** [Strands Streaming Documentation](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/streaming/overview/) + +--- + +## Alternative: Using Raw Converse Events + +Instead of parsing Strands schema events, you can parse the raw Bedrock Converse events nested under the `event` key. This gives you lower-level access to the Converse stream API structures. + +**Note:** Tool results are not emitted as Converse stream events — they are an input to the next `converse_stream` call. Strands handles this internally and emits tool results as `message` events. The converse parser does not handle tool results; instead, `ChatInterface.tsx` marks tools as complete when the next text segment starts streaming. + +### Frontend Parser + +**File:** `frontend/src/lib/agentcore-client/parsers/converse.ts` + +To use this parser instead of the default strands parser, update `client.ts`: + +```typescript +import { parseConverseChunk } from "./parsers/converse"; + +const PARSERS: Record = { + "strands-single-agent": parseConverseChunk, // Switch to Converse parser + ... +}; +``` + +The parser handles raw Bedrock Converse events: + +```typescript +export const parseConverseChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return; + const json = JSON.parse(line.substring(6).trim()); + + const event = json.event; + if (event) { + // Text streaming + if (event.contentBlockDelta?.delta?.text) { + callback({ type: "text", content: event.contentBlockDelta.delta.text }); + } + + // Tool use start + if (event.contentBlockStart?.start?.toolUse) { + const toolUse = event.contentBlockStart.start.toolUse; + callback({ type: "tool_use_start", toolUseId: toolUse.toolUseId, name: toolUse.name }); + } + + // Tool use input streaming + if (event.contentBlockDelta?.delta?.toolUse?.input) { + callback({ type: "tool_use_delta", toolUseId: currentToolUseId, input: ... }); + } + + // Message stop + if (event.messageStop?.stopReason) { + callback({ type: "result", stopReason: event.messageStop.stopReason }); + } + } +}; +``` + +See the full implementation in the source file for edge cases. + +### Event Structure + +Converse events are nested under the `event` key: + +```javascript +// Message lifecycle +data: {"event": {"messageStart": {"role": "assistant"}}} + +// Text streaming +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hello"}}}} +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": " there"}}}} + +// Tool use start +data: {"event": {"contentBlockStart": {"contentBlockIndex": 1, "start": {"toolUse": {"toolUseId": "tool_abc123", "name": "text_analysis"}}}}} + +// Tool use input streaming +data: {"event": {"contentBlockDelta": {"contentBlockIndex": 1, "delta": {"toolUse": {"input": "{\"text\": \"hello\"}"}}}}} + +// Content block and message completion +data: {"event": {"contentBlockStop": {"contentBlockIndex": 0}}} +data: {"event": {"messageStop": {"stopReason": "end_turn"}}} + +// Metadata +data: {"event": {"metadata": {"usage": {"inputTokens": 88, "outputTokens": 30}}}} +``` + +**Reference:** [Bedrock Converse Stream API](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse_stream.html) + +--- + +## LangGraph/LangChain Implementation + +**Note:** LangGraph uses tuple-based streaming `(message_chunk, metadata)` and returns LangChain message objects with content as an array. + +### Backend + +When a LangGraph agent streams with `messages` mode, it yields raw LangChain message chunks: + +```python +# Stream with messages mode - yields raw LangChain message chunks +async for event in graph.astream( + {"messages": [("user", user_query)]}, + config=config, + stream_mode="messages" +): + message_chunk, metadata = event + yield message_chunk.model_dump() # Serialize to JSON-safe dict +``` + +### Event Structure + +LangGraph emits LangChain message objects that serialize to JSON with content as an **array of content blocks**: + +```javascript +// Text streaming (AIMessageChunk) +data: {"content": [{"type": "text", "text": "Hello", "index": 0}], "type": "AIMessageChunk", ...} +data: {"content": [{"type": "text", "text": " there", "index": 0}], "type": "AIMessageChunk", ...} + +// Tool use start — content block has id and name +data: {"content": [{"type": "tool_use", "id": "tool_abc123", "name": "text_analysis", "input": {}, "index": 1}], "type": "AIMessageChunk", ...} + +// Tool input streaming — partial_json carries incremental input +data: {"content": [{"type": "tool_use", "partial_json": "{\"text\":", "index": 1}], "type": "AIMessageChunk", ...} +data: {"content": [{"type": "tool_use", "partial_json": " \"hello\"}", "index": 1}], "type": "AIMessageChunk", ...} + +// Tool response (ToolMessage — separate message type) +data: {"content": "Tool result text", "type": "tool", "name": "text_analysis", "tool_call_id": "tool_abc123", ...} + +// Stop reason +data: {"content": [], "type": "AIMessageChunk", "response_metadata": {"stop_reason": "end_turn"}, ...} + +// Final chunk with usage metadata +data: {"content": [], "type": "AIMessageChunk", "chunk_position": "last", "usage_metadata": {"input_tokens": 88, "output_tokens": 30}} +``` + +**Current parser handles:** + +- `AIMessageChunk` with `content[].type === "text"`: Text tokens for display +- `AIMessageChunk` with `content[].type === "tool_use"` + `id` + `name`: Tool call start +- `AIMessageChunk` with `content[].type === "tool_use"` + `partial_json`: Streaming tool input +- `type === "tool"` (ToolMessage): Tool execution result +- `response_metadata.stop_reason`: Stream completion + +**Key difference from Strands:** LangGraph's `content` is always an array of typed blocks (text, tool_use), not a flat string. Tool results come as separate `ToolMessage` objects, not nested in user messages. + +### Frontend Parser + +**File:** `frontend/src/lib/agentcore-client/parsers/langgraph.ts` + +Same pattern — parses SSE lines and emits typed events. LangGraph uses LangChain message types: + +```typescript +export const parseLanggraphChunk: ChunkParser = (line, callback) => { + if (!line.startsWith("data: ")) return + const json = JSON.parse(line.substring(6).trim()) + + // Tool result: {"type": "tool", "tool_call_id": "...", "content": "result"} + if (json.type === "tool") { + callback({ type: "tool_result", toolUseId: json.tool_call_id, result: json.content }) + } + + // AIMessageChunk — content is an array of blocks + if (json.type === "AIMessageChunk" && Array.isArray(json.content)) { + for (const block of json.content) { + if (block.type === "text" && block.text) { + callback({ type: "text", content: block.text }) + } + if (block.type === "tool_use" && block.id && block.name) { + callback({ type: "tool_use_start", toolUseId: block.id, name: block.name }) + } + } + + // Stop reason from response metadata + if (json.response_metadata?.stop_reason) { + callback({ type: "result", stopReason: json.response_metadata.stop_reason }) + } + } +} +``` + +See the full implementation for tool input delta streaming and edge cases. + +**Key Points:** + +- Filter by `type === 'AIMessageChunk'` to only process assistant responses +- Ignore `ToolMessage` and other internal message types +- `content` is an **array of content blocks**, not a string +- Each block has `type`, `text`, and `index` fields +- Filter for `type === 'text'` to extract text content +- Join multiple text blocks if present + +**Why Content is an Array:** +LangChain uses content blocks to support multimodal messages (text, images, tool calls) following the Anthropic/OpenAI message format. + +**References:** + +- [LangGraph Streaming](https://docs.langchain.com/oss/python/langgraph/streaming) +- [LangChain Streaming](https://docs.langchain.com/oss/python/langchain/streaming) + +--- + +## Adding a New Agent Pattern + +1. Create `agent/my-pattern/` with your agent code +2. Create a parser: `frontend/src/lib/agentcore-client/parsers/my-pattern.ts` + - Export a `ChunkParser` function that converts SSE lines into `StreamEvent`s via `callback()` +3. Register it in `frontend/src/lib/agentcore-client/client.ts` (add to the parser map in the constructor) +4. Set `pattern: my-pattern` in `infra-cdk/config.yaml` + +--- + +## Debugging + +Enable console logging in the parser: + +```javascript +console.log("[Streaming Event]", data) +``` + +Open browser console (F12) to see all events from your agent. diff --git a/samples/aws-specialist-agent/docs/TOOL_AC_CODE_INTERPRETER.md b/samples/aws-specialist-agent/docs/TOOL_AC_CODE_INTERPRETER.md new file mode 100644 index 0000000..9970ecf --- /dev/null +++ b/samples/aws-specialist-agent/docs/TOOL_AC_CODE_INTERPRETER.md @@ -0,0 +1,175 @@ +# AgentCore Code Interpreter Integration + +This document explains the architectural decisions for integrating Amazon Bedrock AgentCore Code Interpreter into FAST. + +## What is AgentCore Code Interpreter? + +Amazon Bedrock AgentCore Code Interpreter is a fully managed capability that enables AI agents to execute code securely in isolated sandbox environments. Key features: + +- Secure code execution in containerized environments +- Multiple language support (Python, JavaScript, TypeScript) +- Pre-built runtimes with common libraries +- Session management with state persistence +- Long execution duration (default 15 minutes, up to 8 hours) + +**Documentation**: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html + +## Why Direct Integration (Not Gateway)? + +FAST integrates Code Interpreter **directly into agents** rather than through the Gateway. Here's why: + +### Approach 1: Direct Integration ✅ (Chosen) + +**Architecture**: `Agent → Code Interpreter SDK → Code Interpreter Service` + +**Pros**: + +- **Simpler implementation** - Minimal code, no additional infrastructure +- **Lower latency** - No Gateway/Lambda hops +- **Lower cost** - No Lambda invocations +- **Session management** - Code Interpreter maintains state across calls +- **Follows AWS patterns** - Matches official documentation examples +- **Better error handling** - Direct access to Code Interpreter errors + +**Cons**: + +- Not discoverable through Gateway +- Requires agent redeployment for updates +- Tool logic lives in agent code + +### Approach 2: Gateway Integration ❌ (Not Chosen) + +**Architecture**: `Agent → Gateway → Lambda → Code Interpreter SDK → Code Interpreter Service` + +**Pros**: + +- Consistent with Gateway pattern +- Discoverable through MCP +- Independent deployment + +**Cons**: + +- **More complex** - Lambda wrapper + Gateway target + IAM roles +- **Higher latency** - Additional hops in request path +- **Higher cost** - Lambda invocations + Code Interpreter usage +- **Session complexity** - Lambda must manage sessions across cold starts +- **No AWS references** - No official examples of this pattern +- **Not intended use case** - Code Interpreter is a built-in service, not a custom tool + +### Decision Rationale + +Code Interpreter is a **built-in AgentCore service**, similar to Bedrock models or AgentCore Memory. AWS designed it for direct integration, not to be proxied through Gateway. Gateway is meant for **custom Lambda-based tools**, not built-in services. + +**Comparison**: + +| Aspect | Direct | Gateway | +| ----------- | ---------------- | -------------- | +| Complexity | Low | High | +| Latency | ~100ms | ~300-500ms | +| Cost | CI only | Lambda + CI | +| AWS Pattern | ✅ Documented | ❌ No examples | +| Use Case | Built-in service | Custom tools | + +## Implementation Architecture + +The agent uses the official Strands Code Interpreter tool +(`strands_tools.code_interpreter.AgentCoreCodeInterpreter`) directly, rather +than a hand-written wrapper. The integration lives entirely in the agent +entrypoint: + +``` +agent/strands-single-agent/ +└── basic_agent.py # Imports AgentCoreCodeInterpreter and registers its tool +``` + +### Key Components + +**Agent Integration** (`agent/strands-single-agent/basic_agent.py`): + +```python +from strands_tools.code_interpreter import AgentCoreCodeInterpreter + +# Bind the sandbox to the conversation session so repeat calls in the same +# conversation reconnect to the same AgentCore sandbox (warm reconnect) +# instead of cold-creating a new one. +code_interpreter_tool = AgentCoreCodeInterpreter( + region=region, session_name=session_id +) + +# Register the tool alongside the Gateway MCP client and file_read. +tools = [gateway_client, code_interpreter_tool.code_interpreter, file_read] +``` + +### Design Principles + +1. **Use the maintained tool** - the official `strands_tools` Code Interpreter + tracks the AgentCore Code Interpreter API, so there is no in-repo wrapper to + keep in sync. +2. **Session-bound sandbox** - `session_name` is set to the conversation + `session_id`, so the tool's module-level cache reconnects to the same + sandbox across invocations (warm reconnect vs cold create). This compounds + with the VPC cold-start mitigation. +3. **Direct integration** - the tool talks to the Code Interpreter service + directly (no Gateway/Lambda hop), as argued in the decision above. + +## Benefits of This Architecture + +1. **Less code to maintain**: no hand-written wrapper; the official tool owns + the API surface. +2. **Performance**: direct integration = lower latency; warm reconnect avoids + repeated cold starts within a conversation. +3. **Cost**: no Lambda overhead. +4. **Simplicity**: follows AWS documented patterns. + +## Usage + +The agent automatically uses Code Interpreter when users request code execution: + +**Example prompts**: + +- "Calculate the factorial of 20" +- "Create a list of the first 50 Fibonacci numbers" +- "Generate 100 random numbers and calculate statistics" + +The tool is registered as `execute_python_securely` to emphasize security vs built-in Python execution. + +## Session Management + +- **Automatic**: Code Interpreter creates sessions on first use +- **Persistence**: Sessions maintain state across multiple calls (`clearContext=False`) +- **Cleanup**: AgentCore automatically cleans up inactive sessions after timeout +- **Manual cleanup**: Optional via `cleanup()` method for immediate resource release + +## Testing + +**Local Docker Build**: + +```bash +docker build -f agent/strands-single-agent/Dockerfile -t test-agent . +docker run --rm test-agent python -c "from strands_tools.code_interpreter import AgentCoreCodeInterpreter; print('✓ Import successful')" +``` + +**Deployment**: + +```bash +cd infra-cdk +cdk deploy +``` + +**Frontend Testing**: Use prompts that require code execution to verify functionality. + +## Future Enhancements + +Potential improvements: + +- Add `write_files` tool for file operations +- Add `list_files` tool to see sandbox contents +- Support JavaScript/TypeScript execution +- Add file upload from S3 +- Implement custom timeout configuration + +## References + +- [AgentCore Code Interpreter Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html) +- [AWS IDP Reference Implementation](https://github.com/aws-solutions-library-samples/accelerated-intelligent-document-processing-on-aws) +- [FAST Gateway Documentation](./GATEWAY.md) diff --git a/samples/aws-specialist-agent/docs/VERSION_BUMP_PLAYBOOK.md b/samples/aws-specialist-agent/docs/VERSION_BUMP_PLAYBOOK.md new file mode 100644 index 0000000..9dbd88d --- /dev/null +++ b/samples/aws-specialist-agent/docs/VERSION_BUMP_PLAYBOOK.md @@ -0,0 +1,69 @@ +# Version Bump Playbook + +This document provides a checklist for bumping the version of FAST (Fullstack AgentCore Solution Template). + +## Files Requiring Manual Updates (6 files) + +1. **`VERSION`** - Root version file +2. **`pyproject.toml`** - Python package version (`version = "X.Y.Z"`) +3. **`frontend/package.json`** - Frontend package version (`"version": "X.Y.Z"`) +4. **`infra-cdk/package.json`** - CDK package version (`"version": "X.Y.Z"`) +5. **`infra-cdk/lib/fast-main-stack.ts`** - Stack description (`(vX.Y.Z)`) +6. **`CHANGELOG.md`** - Add new version entry at top + +## Auto-Generated Files (DO NOT manually update) + +- `frontend/package-lock.json` +- `infra-cdk/package-lock.json` +- `infra-cdk/lib/fast-main-stack.js` + +## Procedure + +### 1. Update Source Files + +Manually update the 7 files listed above with the new version number. + +### 2. Regenerate Auto-Generated Files + +```bash +# Frontend +cd frontend && npm install + +# Infrastructure +cd infra-cdk && npm install && npm run build +``` + +### 3. Verification + +Search for any remaining old version references: + +```bash +find . -type f \( -name "*.md" -o -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.yaml" -o -name "*.yml" -o -name "VERSION" \) | grep -v node_modules | grep -v cdk.out | grep -v ".next" | grep -v "dist" | grep -v "build" | grep -v "__pycache__" | xargs grep -n "OLD_VERSION" 2>/dev/null +``` + +### 4. Testing + +```bash +make all # Run linting +cd infra-cdk && cdk synth # Test CDK synthesis +cd frontend && npm run build # Test frontend build +``` + +### 5. Git Operations + +```bash +git add . +git commit -m "Bump version to X.Y.Z" +git push origin main + +# Create and push tag +git tag vX.Y.Z +git push origin vX.Y.Z +``` + +## Notes + +- Follow semantic versioning (MAJOR.MINOR.PATCH) +- Use `v` prefix for git tags (e.g., `v0.1.3`) +- Only update project version, not dependency versions +- Keep historical changelog entries unchanged diff --git a/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.drawio b/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.drawio new file mode 100644 index 0000000..9a1e0b4 --- /dev/null +++ b/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.drawio @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.png b/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.png new file mode 100644 index 0000000..e07e73b Binary files /dev/null and b/samples/aws-specialist-agent/docs/architecture-diagram/aws-specialist-agent-architecture.png differ diff --git a/samples/aws-specialist-agent/docs/img/demo.webp b/samples/aws-specialist-agent/docs/img/demo.webp new file mode 100644 index 0000000..5e29a58 Binary files /dev/null and b/samples/aws-specialist-agent/docs/img/demo.webp differ diff --git a/samples/aws-specialist-agent/docs/img/screenshot.png b/samples/aws-specialist-agent/docs/img/screenshot.png new file mode 100644 index 0000000..adad2f4 Binary files /dev/null and b/samples/aws-specialist-agent/docs/img/screenshot.png differ diff --git a/samples/aws-specialist-agent/docs/index.md b/samples/aws-specialist-agent/docs/index.md new file mode 100644 index 0000000..c3b7a83 --- /dev/null +++ b/samples/aws-specialist-agent/docs/index.md @@ -0,0 +1,9 @@ + + +# Fullstack AgentCore Solution Template Documentation + +Welcome to the Fullstack AgentCore Solution Template (FAST) documentation! + +Have a look at the navigation bar on the left hand side of your browser to read through the actual documentation! + +_This index.md file exists only because is required for mkdocs to generate an index.html for documentation pages._ \ No newline at end of file diff --git a/samples/aws-specialist-agent/frontend/README.md b/samples/aws-specialist-agent/frontend/README.md new file mode 100644 index 0000000..16c16a7 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/README.md @@ -0,0 +1,200 @@ +# Frontend - Local Development Guide + +This is the React + Vite frontend for the Fullstack AgentCore Solution Template (FAST). This README focuses on local development setup and workflows. + +For full stack deployment instructions, see the [top-level README](../README.md) and [deployment documentation](../docs/DEPLOYMENT.md). + +![Chat example](readme-imgs/fast-chat-screenshot.png) + +## Local Development Setup + +### Prerequisites + +- Node.js (20+ recommended) +- npm + +### Quick Start + +1. Navigate to the frontend directory: + +```bash +cd frontend +``` + +2. Install dependencies: + +```bash +npm install +``` + +3. Start the development server: + +```bash +npm run dev +``` + +4. Open [http://localhost:3000](http://localhost:3000) in your browser + +## Development Options + +### Option 1: With Authentication (Default) + +By default, the app uses Cognito authentication. To test this locally: + +1. First deploy the full stack (see [deployment docs](../docs/DEPLOYMENT.md)) +2. Set the redirect URI for local development: + +```bash +export VITE_COGNITO_REDIRECT_URI=http://localhost:3000 +npm run dev +``` + +### Option 2: Disable Authentication (ONLY for Local Development!!!) + +For faster local development without needing to deploy Cognito, you can disable authentication: + +**⚠️ IMPORTANT: Remove the AuthProvider wrapper from `src/App.tsx`** + +Change this: + +```tsx +export default function App() { + return ( + + + + + + ) +} +``` + +To this: + +```tsx +export default function App() { + return ( + + + + ) +} +``` + +This bypasses all authentication flows and lets you develop the UI directly. + +## UI Components + +This project uses [shadcn/ui](https://ui.shadcn.com/docs/components) for UI components. + +### Adding New Components + +Install additional shadcn components as needed: + +```bash +npx shadcn@latest add calendar +npx shadcn@latest add dialog +npx shadcn@latest add form +``` + +### Available Components + +Browse the full component library at: https://ui.shadcn.com/docs/components + +Popular components include: + +- Button, Input, Textarea +- Dialog, Sheet, Popover +- Table, Card, Badge +- Form, Calendar, Select +- And many more... + +## Icons + +This project includes [Lucide React](https://lucide.dev/) icons, providing a comprehensive set of beautiful, customizable icons. + +### Using Icons + +Import and use any icon from the Lucide library: + +```tsx +import { Camera } from "lucide-react" + +// Usage +const App = () => { + return +} + +export default App +``` + +### Available Icons + +Browse all available icons at: https://lucide.dev/ + +Popular icons include Camera, Search, Menu, User, Settings, Download, Upload, and hundreds more. + +## Project Structure + +``` +frontend/ +├── src/ +│ ├── main.tsx # Application entry point +│ ├── App.tsx # Root component with routing +│ ├── routes/ # Route components +│ ├── components/ +│ │ ├── ui/ # shadcn components +│ │ ├── chat/ # Chat UI +│ │ └── auth/ # Authentication components +│ ├── hooks/ # Custom hooks (useToolRenderer) +│ ├── lib/ # Utilities and configurations +│ │ └── agentcore-client/ # AgentCore streaming client, parsers, types +│ ├── services/ # API service layers +│ └── styles/ # Global styles +├── public/ # Static assets +├── index.html # HTML entry point +└── package.json +``` + +## Environment Variables + +The application uses Vite environment variables with the `VITE_` prefix: + +- `VITE_COGNITO_USER_POOL_ID` - Cognito User Pool ID +- `VITE_COGNITO_CLIENT_ID` - Cognito Client ID +- `VITE_COGNITO_REGION` - AWS Region +- `VITE_COGNITO_REDIRECT_URI` - Redirect URI after authentication +- `VITE_COGNITO_POST_LOGOUT_REDIRECT_URI` - Redirect URI after logout + +These can be set in a `.env` file or as environment variables. The application will fall back to `aws-exports.json` if environment variables are not set. + +## Available Scripts + +- `npm run dev` - Start the Vite development server +- `npm run build` - Build for production (runs TypeScript check + Vite build) +- `npm run preview` - Preview the production build locally +- `npm run lint` - Run ESLint +- `npm run lint:fix` - Run ESLint with auto-fix +- `npm run clean` - Clean build artifacts and dependencies + +## Development Tips + +- **Hot Reload**: Changes auto-reload in the browser with Vite's fast HMR +- **TypeScript**: Full type safety with strict mode enabled +- **Vibe Coding**: Optimized for AI-assisted development +- **Tailwind CSS**: Utility-first styling with Tailwind CSS 4 + +## Building with AI Assistants + +This stack is designed for AI-assisted development: + +1. **Describe your vision**: "Create a document upload component with drag-and-drop" +2. **Leverage shadcn components**: Rich building blocks that AI understands +3. **Iterate quickly**: Make changes and see results instantly + +### Example AI Prompts + +- "Add a file upload component to the chat interface" +- "Create a sidebar with navigation using shadcn components" +- "Build a settings page with form validation" +- "Add a data table with sorting and filtering" diff --git a/samples/aws-specialist-agent/frontend/components.json b/samples/aws-specialist-agent/frontend/components.json new file mode 100644 index 0000000..3289f23 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/samples/aws-specialist-agent/frontend/eslint.config.mjs b/samples/aws-specialist-agent/frontend/eslint.config.mjs new file mode 100644 index 0000000..b14a666 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/eslint.config.mjs @@ -0,0 +1,27 @@ +import tseslint from "@typescript-eslint/eslint-plugin" +import tsparser from "@typescript-eslint/parser" + +const eslintConfig = [ + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + }, + plugins: { + "@typescript-eslint": tseslint, + }, + rules: { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn", + }, + }, + { + ignores: ["node_modules/**", "build/**", "delete/**", "tmp/**", ".vite/**", "src/app/**"], + }, +] + +export default eslintConfig diff --git a/samples/aws-specialist-agent/frontend/index.html b/samples/aws-specialist-agent/frontend/index.html new file mode 100644 index 0000000..acfc35d --- /dev/null +++ b/samples/aws-specialist-agent/frontend/index.html @@ -0,0 +1,18 @@ + + + + + + + + AgentCore AWS Specialist Agent + + + + + + +
+ + + diff --git a/samples/aws-specialist-agent/frontend/package-lock.json b/samples/aws-specialist-agent/frontend/package-lock.json new file mode 100644 index 0000000..02e6542 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/package-lock.json @@ -0,0 +1,17226 @@ +{ + "name": "fullstack-agentcore-solution-template-frontend", + "version": "0.4.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fullstack-agentcore-solution-template-frontend", + "version": "0.4.1", + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-slot": "^1.2.4", + "@types/react-syntax-highlighter": "^15.5.13", + "aws-amplify": "^6.16.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "mermaid": "^11.15.0", + "radix-ui": "^1.4.3", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "react-dropzone": "^14.3.8", + "react-markdown": "^10.1.0", + "react-oidc-context": "^3.3.0", + "react-router-dom": "^6.21.0", + "react-spinners": "^0.17.0", + "react-syntax-highlighter": "^16.1.0", + "react-zoom-pan-pinch": "^4.0.3", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.2.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@shadcn/ui": "^0.0.4", + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.1.5", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.1", + "@types/node": "^25.0.3", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", + "@vitejs/plugin-react": "^4.2.0", + "eslint": "^9.39.2", + "fast-check": "^4.5.3", + "jsdom": "^23.0.1", + "prettier": "^3.8.1", + "shadcn": "^3.0.0", + "tailwindcss": "^4", + "tw-animate-css": "^1.2.9", + "typescript": "^5", + "vite": "^7.3.2", + "vitest": "^4.0.18" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/ni": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-25.0.0.tgz", + "integrity": "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.0.0", + "fzf": "^0.5.2", + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "na": "bin/na.mjs", + "nci": "bin/nci.mjs", + "ni": "bin/ni.mjs", + "nlx": "bin/nlx.mjs", + "nr": "bin/nr.mjs", + "nun": "bin/nun.mjs", + "nup": "bin/nup.mjs" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.2.tgz", + "integrity": "sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^2.3.1", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "node_modules/@aws-amplify/analytics": { + "version": "7.0.93", + "resolved": "https://registry.npmjs.org/@aws-amplify/analytics/-/analytics-7.0.93.tgz", + "integrity": "sha512-3WoB0VzATJyupTNQ+ZnzE0pLYnpZPtqNN4deZ8gadG5uzGhhvkt9uZtgVnn/QFGb35DnP8qNDTRiM0rL3vjyZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-firehose": "3.982.0", + "@aws-sdk/client-kinesis": "3.982.0", + "@aws-sdk/client-personalize-events": "3.982.0", + "@smithy/util-utf8": "2.0.0", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/api": { + "version": "6.3.24", + "resolved": "https://registry.npmjs.org/@aws-amplify/api/-/api-6.3.24.tgz", + "integrity": "sha512-19CVHj+0J35aHMPNzy12nO1mJS4oP68yFUfiMnulSsiVGV5XhUDc/bkdcX0uI7U1SsUSs+9TOBwZg27bzYIGkg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api-graphql": "4.8.5", + "@aws-amplify/api-rest": "4.6.3", + "@aws-amplify/data-schema": "^1.7.0", + "rxjs": "^7.8.1", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/api-graphql": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/@aws-amplify/api-graphql/-/api-graphql-4.8.5.tgz", + "integrity": "sha512-Xu45+MizoethsRfCFIdN9RORenCu0e41tMkiTFVE5oKC76eoOlYHg2LlhG2Lmmasby/Ggi5bZouVxJIcP4IeIA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api-rest": "4.6.3", + "@aws-amplify/core": "6.16.1", + "@aws-amplify/data-schema": "^1.7.0", + "@aws-sdk/types": "3.973.1", + "graphql": "15.8.0", + "rxjs": "^7.8.1", + "tslib": "^2.5.0", + "uuid": "^11.0.0" + } + }, + "node_modules/@aws-amplify/api-rest": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@aws-amplify/api-rest/-/api-rest-4.6.3.tgz", + "integrity": "sha512-SPhttyB9SR2p5PkUPmUPfkXNqGrgvdqiNHNHhx7FjHnqFBXLDRtGhzqRbE7faDeAwrcWz1HCtcpk7MLHYt94yg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/auth": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@aws-amplify/auth/-/auth-6.19.1.tgz", + "integrity": "sha512-N6bqBUEly/xUiho0X5oGhLEDlQTWsj1i0FquDYsyuav5e9HHQBLNgG1zmpq28lyxtDaUREi/IDx+CD10EpcPcQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "5.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0", + "@aws-amplify/react-native": "^1.1.10" + }, + "peerDependenciesMeta": { + "@aws-amplify/react-native": { + "optional": true + } + } + }, + "node_modules/@aws-amplify/core": { + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-6.16.1.tgz", + "integrity": "sha512-WHO6yYegmnZ+K3vnYzVwy+wnxYqSkdFakBIlgm4922QXHOQYWdIl/rrTcaagrpJEGT6YlTnqx1ANIoPojNxWmw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/types": "3.973.1", + "@smithy/util-hex-encoding": "2.0.0", + "@types/uuid": "^9.0.0", + "js-cookie": "^3.0.5", + "rxjs": "^7.8.1", + "tslib": "^2.5.0", + "uuid": "^11.0.0" + } + }, + "node_modules/@aws-amplify/data-schema": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema/-/data-schema-1.24.0.tgz", + "integrity": "sha512-nly/+w3R2JIq6qxsw7io2nGxliSswBO9FQqzckpTnpUAd+oMe06HoTyDvQG6hxozQc9Woy0tT375WIJp4C84Uw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/data-schema-types": "*", + "@smithy/util-base64": "^3.0.0", + "@types/aws-lambda": "^8.10.134", + "@types/json-schema": "^7.0.15", + "rxjs": "^7.8.1" + } + }, + "node_modules/@aws-amplify/data-schema-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@aws-amplify/data-schema-types/-/data-schema-types-1.2.1.tgz", + "integrity": "sha512-SuYVcy9Hg8Ox9P0QCXEPwqHxX5zVPgVo2YvNBOm5TpkZr4UK6ir3USame7dELZsk5/9f6KoP70QAYhTvp/j1Og==", + "license": "Apache-2.0", + "dependencies": { + "graphql": "15.8.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/@aws-amplify/datastore": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@aws-amplify/datastore/-/datastore-5.1.5.tgz", + "integrity": "sha512-/9o4eYqWOlxVxe/riDd282FmUHHSiGUEAwle464T8wzNSqPTB7yTeQfzt2LFYTWsrYLCSR0OtOM1bY5VPSVmew==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/api": "6.3.24", + "@aws-amplify/api-graphql": "4.8.5", + "buffer": "4.9.2", + "idb": "5.0.6", + "immer": "9.0.6", + "rxjs": "^7.8.1", + "ulid": "^2.3.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/notifications": { + "version": "2.0.93", + "resolved": "https://registry.npmjs.org/@aws-amplify/notifications/-/notifications-2.0.93.tgz", + "integrity": "sha512-NtHKusaiWzkPXuaKsTyvKAWE8JnQcXmQoaidQ5/a9/nWWTzs983l5xgc4OPvfVR+3N63K+3iTmYHtKcEbhgS6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.973.1", + "lodash": "^4.17.21", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-amplify/storage": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@aws-amplify/storage/-/storage-6.13.1.tgz", + "integrity": "sha512-iNDUmdvevcujcW4PBY7IGBMeTm+nohsZgswH6k99tG0myVsZRg0lVC4l5lcwoXoyVLpQjOmfZ0JgwV0oQbZ6zg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.973.1", + "@smithy/md5-js": "2.0.7", + "buffer": "4.9.2", + "crc-32": "1.2.2", + "fast-xml-parser": "^5.3.4", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "@aws-amplify/core": "^6.1.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-firehose": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-firehose/-/client-firehose-3.982.0.tgz", + "integrity": "sha512-Qur2Siqep+gRReTjlKXcdpyX/MUnzm5OgNNudDPxzpmzdnc3ZKlUwGlbEoS1VA5cFS6N4zg6WfZqlwcXg//TSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-firehose/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-kinesis/-/client-kinesis-3.982.0.tgz", + "integrity": "sha512-Gh3xyumdz3IRj91HIBR48TohQyA3VSn/blDcGXzl4dwQKXgM0ISdHgyniNo2GQNhORJF3d01MSMx72s5NNQxUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/eventstream-serde-browser": "^4.2.8", + "@smithy/eventstream-serde-config-resolver": "^4.3.8", + "@smithy/eventstream-serde-node": "^4.2.8", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-kinesis/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-personalize-events/-/client-personalize-events-3.982.0.tgz", + "integrity": "sha512-JllssIZCPxAgYy4gkIM2e/kXxWT0xQzzZd5y9rRStm0bl5MiLAxzX4q9WhGG7glyB++EuhYskiT1N+DzyM5nTw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-personalize-events/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.985.0.tgz", + "integrity": "sha512-81J8iE8MuXhdbMfIz4sWFj64Pe41bFi/uqqmqOC5SlGv+kwoyLsyKS/rH2tW2t5buih4vTUxskRjxlqikTD4oQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.7.tgz", + "integrity": "sha512-wNZZQQNlJ+hzD49cKdo+PY6rsTDElO8yDImnrI69p2PLBa7QomeUKAJWYp9xnaR38nlHqWhMHZuYLCQ3oSX+xg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.22.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.5.tgz", + "integrity": "sha512-LxJ9PEO4gKPXzkufvIESUysykPIdrV7+Ocb9yAhbhJLE4TiAYqbCVUE+VuKP1leGR1bBfjWjYgSV5MxprlX3mQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.7.tgz", + "integrity": "sha512-L2uOGtvp2x3bTcxFTpSM+GkwFIPd8pHfGWO1764icMbo7e5xJh0nfhx1UwkXLnwvocTNEf8A7jISZLYjUSNaTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.5.tgz", + "integrity": "sha512-SdDTYE6jkARzOeL7+kudMIM4DaFnP5dZVeatzw849k4bSXDdErDS188bgeNzc/RA2WGrlEpsqHUKP6G7sVXhZg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-login": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.5.tgz", + "integrity": "sha512-uYq1ILyTSI6ZDCMY5+vUsRM0SOCVI7kaW4wBrehVVkhAxC6y+e9rvGtnoZqCOWL1gKjTMouvsf4Ilhc5NCg1Aw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.6.tgz", + "integrity": "sha512-DZ3CnAAtSVtVz+G+ogqecaErMLgzph4JH5nYbHoBMgBkwTUV+SUcjsjOJwdBJTHu3Dm6l5LBYekZoU2nDqQk2A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-ini": "^3.972.5", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.5.tgz", + "integrity": "sha512-HDKF3mVbLnuqGg6dMnzBf1VUOywE12/N286msI9YaK9mEIzdsGCtLTvrDhe3Up0R9/hGFbB+9l21/TwF5L1C6g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.5.tgz", + "integrity": "sha512-8urj3AoeNeQisjMmMBhFeiY2gxt6/7wQQbEGun0YV/OaOOiXrIudTIEYF8ZfD+NQI6X1FY5AkRsx6O/CaGiybA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.985.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/token-providers": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.5.tgz", + "integrity": "sha512-OK3cULuJl6c+RcDZfPpaK5o3deTOnKZbxm7pzhFNGA3fI2hF9yDih17fGRazJzGGWaDVlR9ejZrpDef4DJCEsw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.7.tgz", + "integrity": "sha512-HUD+geASjXSCyL/DHPQc/Ua7JhldTcIglVAoCV8kiVm99IaFSlAbTvEnyhZwdE6bdFyTL+uIaWLaCFSRsglZBQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@smithy/core": "^3.22.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.985.0.tgz", + "integrity": "sha512-TsWwKzb/2WHafAY0CE7uXgLj0FmnkBTgfioG9HO+7z/zCPcl1+YU+i7dW4o0y+aFxFgxTMG+ExBQpqT/k2ao8g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.985.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.985.0.tgz", + "integrity": "sha512-vth7UfGSUR3ljvaq8V4Rc62FsM7GUTH/myxPWkaEgOrprz1/Pc72EgTXxj+cPPPDAfHFIpjhkB7T7Td0RJx+BA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.985.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.985.0.tgz", + "integrity": "sha512-+hwpHZyEq8k+9JL2PkE60V93v2kNhUIv7STFt+EAez1UJsJOQDhc5LpzEX66pNjclI5OTwBROs/DhJjC/BtMjQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/nested-clients": "3.985.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.5.tgz", + "integrity": "sha512-GsUDF+rXyxDZkkJxUsDxnA67FG+kc5W1dnloCFLl6fWzceevsCYzJpASBzT+BPjwUgREE6FngfJYYYMQUY5fZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "license": "Apache-2.0", + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "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/core/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/@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-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "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-compilation-targets/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/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@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-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "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-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "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-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.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/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.51.4", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.4.tgz", + "integrity": "sha512-AoziS8lRQ3ew/lY5J4JSlzYSN9Fo0oiyMBY37L3Bwq4mOQJT5GSrdZYLFPt6pH1LApDI3ZJceNyx+rHRACZSeQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^17.2.1", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", + "integrity": "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.13", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", + "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "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/@mermaid-js/parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.1" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mswjs/interceptors": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", + "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shadcn/ui": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@shadcn/ui/-/ui-0.0.4.tgz", + "integrity": "sha512-0dtu/5ApsOZ24qgaZwtif8jVwqol7a4m1x5AxPuM1k5wxhqU7t/qEfBGtaSki1R8VlbTQfCj5PAlO45NKCa7Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.2.0", + "commander": "^10.0.0", + "execa": "^7.0.0", + "fs-extra": "^11.1.0", + "node-fetch": "^3.3.0", + "ora": "^6.1.2", + "prompts": "^2.4.2", + "zod": "^3.20.2" + }, + "bin": { + "ui": "dist/index.js" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/abort-controller/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz", + "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.12", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", + "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", + "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", + "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", + "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", + "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.7.tgz", + "integrity": "sha512-2i2BpXF9pI5D1xekqUsgQ/ohv5+H//G9FlawJrkOJskV18PgJ8LiNbLiskMeYt07yAsSTZR7qtlcAaa/GQLWww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.3.1", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@smithy/md5-js/node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz", + "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.31", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz", + "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz", + "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.0", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz", + "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.33", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz", + "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz", + "integrity": "sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.160", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.160.tgz", + "integrity": "sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==", + "license": "MIT" + }, + "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/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.55.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.55.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "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/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/aws-amplify": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/aws-amplify/-/aws-amplify-6.16.2.tgz", + "integrity": "sha512-7CHwfH5QxZ0rzCws/DNy5VLVcIIZWd9iUTtV1Oj6kPzpkFhCJ2I8gTvhFdh61HLhrg2lShcPQ8cecBIQS/ZJ0A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-amplify/analytics": "7.0.93", + "@aws-amplify/api": "6.3.24", + "@aws-amplify/auth": "6.19.1", + "@aws-amplify/core": "6.16.1", + "@aws-amplify/datastore": "5.1.5", + "@aws-amplify/notifications": "2.0.93", + "@aws-amplify/storage": "6.13.1", + "tslib": "^2.5.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eciesjs": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.16.tgz", + "integrity": "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.4", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", + "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.5.3.tgz", + "integrity": "sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^7.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fzf": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fzf/-/fzf-0.5.2.tgz", + "integrity": "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphql": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", + "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", + "license": "MIT", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/hono": { + "version": "4.12.28", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.28.tgz", + "integrity": "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/idb": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/idb/-/idb-5.0.6.tgz", + "integrity": "sha512-/PFvOWPzRcEPmlDt5jEvzVZVs0wyd/EvGvkDIcbBpGuMMLQKrTPG0TxvE2UJtgZtCQCmOtM2QD7yQJBVEjKGOw==", + "license": "ISC" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz", + "integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "license": "MIT" + }, + "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/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", + "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^2.0.1", + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsdom/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "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/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.7.tgz", + "integrity": "sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.40.0", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.7.0", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/msw/node_modules/graphql": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/oidc-client-ts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", + "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/radix-ui/node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/radix-ui/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-dropzone": { + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-oidc-context": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz", + "integrity": "sha512-302T/ma4AOVAxrHdYctDSKXjCq9KNHT564XEO2yOPxRfxEP58xa4nz+GQinNl8x7CnEXECSM5JEjQJk3Cr5BvA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "oidc-client-ts": "^3.1.0", + "react": ">=16.14.0" + } + }, + "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/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-spinners": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.17.0.tgz", + "integrity": "sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-syntax-highlighter": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz", + "integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^5.0.0" + }, + "engines": { + "node": ">= 16.20.2" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-zoom-pan-pinch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-4.0.3.tgz", + "integrity": "sha512-N2Hi6L78fFmhRra+ORpFSW7WST5x6kxpOPplIvtB0b7b+U2anpo1z1wLgaWRPS2kUSqcraRG+JgBCIlDJnqqAg==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/refractor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^9.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rettime": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", + "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-3.6.2.tgz", + "integrity": "sha512-2g48/7UsXTSWMFU9GYww85AN5iVTkErbeycrcleI55R+atqW8HE1M/YDFyQ+0T3Bwsd4e8vycPu9gmwODunDpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/ni": "^25.0.0", + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.17.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "https-proxy-agent": "^7.0.6", + "kleur": "^4.1.5", + "msw": "^2.10.4", + "node-fetch": "^3.3.2", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + } + }, + "node_modules/shadcn/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/shadcn/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/shadcn/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/shadcn/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/shadcn/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/shadcn/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/shadcn/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/shadcn/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shadcn/node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/shadcn/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz", + "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ulid": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz", + "integrity": "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==", + "license": "MIT", + "bin": { + "ulid": "bin/cli.js" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/samples/aws-specialist-agent/frontend/package.json b/samples/aws-specialist-agent/frontend/package.json new file mode 100644 index 0000000..65f46ff --- /dev/null +++ b/samples/aws-specialist-agent/frontend/package.json @@ -0,0 +1,64 @@ +{ + "name": "fullstack-agentcore-solution-template-frontend", + "version": "0.4.1", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest --run", + "test:watch": "vitest", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "clean": "rm -rf build/ node_modules/ .vite/" + }, + "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-slot": "^1.2.4", + "@types/react-syntax-highlighter": "^15.5.13", + "aws-amplify": "^6.16.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "mermaid": "^11.15.0", + "radix-ui": "^1.4.3", + "react": "^19.2.1", + "react-dom": "^19.2.1", + "react-dropzone": "^14.3.8", + "react-markdown": "^10.1.0", + "react-oidc-context": "^3.3.0", + "react-router-dom": "^6.21.0", + "react-spinners": "^0.17.0", + "react-syntax-highlighter": "^16.1.0", + "react-zoom-pan-pinch": "^4.0.3", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.2.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@shadcn/ui": "^0.0.4", + "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.1.5", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.1", + "@types/node": "^25.0.3", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", + "@vitejs/plugin-react": "^4.2.0", + "eslint": "^9.39.2", + "fast-check": "^4.5.3", + "jsdom": "^23.0.1", + "prettier": "^3.8.1", + "shadcn": "^3.0.0", + "tailwindcss": "^4", + "tw-animate-css": "^1.2.9", + "typescript": "^5", + "vite": "^7.3.2", + "vitest": "^4.0.18" + } +} diff --git a/samples/aws-specialist-agent/frontend/postcss.config.mjs b/samples/aws-specialist-agent/frontend/postcss.config.mjs new file mode 100644 index 0000000..30563c7 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +import tailwindcss from "@tailwindcss/postcss" + +const config = { + plugins: [tailwindcss], +} + +export default config diff --git a/samples/aws-specialist-agent/frontend/public/favicon.ico b/samples/aws-specialist-agent/frontend/public/favicon.ico new file mode 100644 index 0000000..129ee13 Binary files /dev/null and b/samples/aws-specialist-agent/frontend/public/favicon.ico differ diff --git a/samples/aws-specialist-agent/frontend/readme-imgs/fast-chat-screenshot.png b/samples/aws-specialist-agent/frontend/readme-imgs/fast-chat-screenshot.png new file mode 100644 index 0000000..e1ae98f Binary files /dev/null and b/samples/aws-specialist-agent/frontend/readme-imgs/fast-chat-screenshot.png differ diff --git a/samples/aws-specialist-agent/frontend/readme-imgs/login-page.png b/samples/aws-specialist-agent/frontend/readme-imgs/login-page.png new file mode 100644 index 0000000..4fe1e5d Binary files /dev/null and b/samples/aws-specialist-agent/frontend/readme-imgs/login-page.png differ diff --git a/samples/aws-specialist-agent/frontend/src/App.tsx b/samples/aws-specialist-agent/frontend/src/App.tsx new file mode 100644 index 0000000..f105bfe --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/App.tsx @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { BrowserRouter } from "react-router-dom" +import { AuthProvider } from "@/components/auth/AuthProvider" +import AppRoutes from "./routes" + +export default function App() { + return ( + + + + + + ) +} diff --git a/samples/aws-specialist-agent/frontend/src/app/context/GlobalContext.tsx b/samples/aws-specialist-agent/frontend/src/app/context/GlobalContext.tsx new file mode 100644 index 0000000..da4ece5 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/app/context/GlobalContext.tsx @@ -0,0 +1,74 @@ +"use client" +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Global context provider for the application + * Provides shared state and functionality across components + */ + +import { createContext, useContext, PropsWithChildren, useState, useCallback } from "react" + +// localStorage key for the user's selected model. We persist the +// stable logical key (e.g. "opus-4.8"), never the physical Bedrock id, so the +// saved choice survives model/version/routing changes on the backend. +const MODEL_KEY_STORAGE = "fast.selectedModelKey" + +interface GlobalContextType { + isLoading: boolean + setIsLoading: (loading: boolean) => void + // The user's selected model logical key, persisted to localStorage. + // null until a model list is loaded or a choice is made; ChatInterface seeds + // it with the default once aws-exports models are available. + selectedModelKey: string | null + setSelectedModelKey: (key: string) => void +} + +const GlobalContext = createContext(undefined) + +/** + * Hook to access the global context + * @returns The global context value + * @throws Error if used outside of GlobalContextProvider + */ +export function useGlobal(): GlobalContextType { + const context = useContext(GlobalContext) + if (context === undefined) { + throw new Error("useGlobal must be used within a GlobalContextProvider") + } + return context +} + +/** + * Global context provider component + * Wraps the application to provide global state + * @param children - Child components to wrap + */ +export function GlobalContextProvider({ children }: PropsWithChildren) { + const [isLoading, setIsLoading] = useState(false) + + // Lazy initializer reads the persisted choice once. Guarded for SSR, where + // window/localStorage do not exist. + const [selectedModelKey, setSelectedModelKeyState] = useState(() => { + if (typeof window === "undefined") return null + return window.localStorage.getItem(MODEL_KEY_STORAGE) + }) + + // Update state and persist. Wrapped in useCallback so it is stable across + // renders (it is read in effect dependency lists downstream). + const setSelectedModelKey = useCallback((key: string) => { + setSelectedModelKeyState(key) + if (typeof window !== "undefined") { + window.localStorage.setItem(MODEL_KEY_STORAGE, key) + } + }, []) + + const value: GlobalContextType = { + isLoading, + setIsLoading, + selectedModelKey, + setSelectedModelKey, + } + + return {children} +} diff --git a/samples/aws-specialist-agent/frontend/src/components/auth/AuthProvider.tsx b/samples/aws-specialist-agent/frontend/src/components/auth/AuthProvider.tsx new file mode 100644 index 0000000..ab319b6 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/components/auth/AuthProvider.tsx @@ -0,0 +1,71 @@ +"use client" + +import { createCognitoAuthConfig, cognitoAuthConfig } from "@/lib/auth" +import { useEffect, useState, PropsWithChildren } from "react" +import { AuthProvider as OidcAuthProvider } from "react-oidc-context" +import { WebStorageStateStore } from "oidc-client-ts" +import { AutoSignin } from "./AutoSignin" + +interface CognitoAuthConfig { + authority?: string + client_id?: string + redirect_uri?: string + post_logout_redirect_uri?: string + response_type?: string + scope?: string + automaticSilentRenew?: boolean + userStore?: WebStorageStateStore +} + +const AuthProvider = ({ children }: PropsWithChildren) => { + const [authConfig, setAuthConfig] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + async function loadConfig() { + try { + const config = await createCognitoAuthConfig() + setAuthConfig(config) + } catch (error) { + console.error("Failed to load auth configuration:", error) + console.error("Falling back to environment variables") + // Fallback to env vars on error + setAuthConfig(cognitoAuthConfig) + } finally { + setLoading(false) + } + } + + loadConfig() + }, []) + + if (loading) { + return ( +
+ Loading authentication configuration... +
+ ) + } + + if (!authConfig) { + return ( +
+ Failed to load authentication configuration +
+ ) + } + + return ( + { + window.history.replaceState({}, document.title, window.location.pathname) + }} + > + {children} + + ) +} + +export { AuthProvider } diff --git a/samples/aws-specialist-agent/frontend/src/components/auth/AutoSignin.tsx b/samples/aws-specialist-agent/frontend/src/components/auth/AutoSignin.tsx new file mode 100644 index 0000000..61dba08 --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/components/auth/AutoSignin.tsx @@ -0,0 +1,38 @@ +"use client" + +import { ReactNode, useEffect, useState, PropsWithChildren } from "react" +import { useAuth } from "react-oidc-context" +import { Button } from "@/components/ui/button" + +function AutoSigninContent({ children }: PropsWithChildren) { + const auth = useAuth() + + if (auth.isLoading) { + return
Loading...
+ } + + if (!auth.isAuthenticated) { + return ( +
+

Please sign in

+ +
+ ) + } + + return <>{children} +} + +export function AutoSignin({ children }: { children: ReactNode }) { + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + if (!mounted) { + return null + } + + return {children} +} diff --git a/samples/aws-specialist-agent/frontend/src/components/chat/ChatHeader.tsx b/samples/aws-specialist-agent/frontend/src/components/chat/ChatHeader.tsx new file mode 100644 index 0000000..6425c8b --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/components/chat/ChatHeader.tsx @@ -0,0 +1,66 @@ +import { Button } from "@/components/ui/button" +import { User } from "lucide-react" +import { useAuth } from "@/hooks/useAuth" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" + +type ChatHeaderProps = { + title?: string | undefined +} + +export function ChatHeader({ title }: ChatHeaderProps) { + const { isAuthenticated, signOut, user } = useAuth() + + // The signed-in user's display name. Prefer the preferred_username claim + // (set at user creation); never show the full email — for users without a + // preferred_username, fall back to the email local part (before the @). + const profile = user?.profile as { email?: string; preferred_username?: string } | undefined + const displayName = profile?.preferred_username || profile?.email?.split("@")[0] + + return ( +
+
+

{title || "AgentCore AWS Specialist Agent"}

+
+
+ {isAuthenticated && displayName && ( + + + + {displayName} + + + )} + {isAuthenticated && ( + + + + + + + Confirm Logout + + Are you sure you want to log out? You will need to sign in again to access your + account. + + + + Cancel + signOut()}>Confirm + + + + )} +
+
+ ) +} diff --git a/samples/aws-specialist-agent/frontend/src/components/chat/ChatInput.tsx b/samples/aws-specialist-agent/frontend/src/components/chat/ChatInput.tsx new file mode 100644 index 0000000..d0f648d --- /dev/null +++ b/samples/aws-specialist-agent/frontend/src/components/chat/ChatInput.tsx @@ -0,0 +1,110 @@ +"use client" + +import { FormEvent, KeyboardEvent, useRef, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Loader2Icon, Send } from "lucide-react" +import { ModelSelector } from "./ModelSelector" +import { SelectableModel } from "./types" + +interface ChatInputProps { + input: string + setInput: (input: string) => void + handleSubmit: (e: FormEvent) => void + isLoading: boolean + className?: string + // Model picker wiring. Optional so the input renders even before + // the model list loads; the selector hides itself when models is empty. + models?: SelectableModel[] + selectedModelKey?: string | null + onModelChange?: (key: string) => void +} + +export function ChatInput({ + input, + setInput, + handleSubmit, + isLoading, + className = "", + models = [], + selectedModelKey = null, + onModelChange, +}: ChatInputProps) { + const textareaRef = useRef(null) + + // Auto-resize the textarea based on content + useEffect(() => { + const textarea = textareaRef.current + if (textarea) { + textarea.style.height = "0px" + const scrollHeight = textarea.scrollHeight + textarea.style.height = scrollHeight + "px" + } + }, [input]) + + // Submit on Cmd+Enter (Mac) / Ctrl+Enter (Windows); plain Enter inserts a + // newline. This avoids accidental sends while composing Japanese (IME), where + // Enter is used to confirm a conversion: an Enter that confirms an IME + // composition is ignored entirely so it neither sends nor adds a stray newline. + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== "Enter") return + + // Ignore the Enter that confirms an IME conversion (isComposing is true + // mid-composition; keyCode 229 is the legacy signal for the same). + if (e.nativeEvent.isComposing || e.keyCode === 229) return + + if (e.metaKey || e.ctrlKey) { + // Send on Cmd/Ctrl+Enter. + e.preventDefault() + if (input.trim()) { + handleSubmit(e as unknown as FormEvent) + } + } + // Plain Enter / Shift+Enter: let the textarea insert a newline (default). + } + + return ( +
+
+