Skip to content

Commit 4659bae

Browse files
committed
feat: add settings and user-level credential support
1 parent 68ff2b1 commit 4659bae

File tree

24 files changed

+1128
-75
lines changed

24 files changed

+1128
-75
lines changed

agent-blueprint/agentcore-gateway-stack/lambda-functions/google-maps/lambda_function.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,22 @@ def lambda_handler(event, context):
6262
return error_response(str(e))
6363

6464

65-
def get_google_maps_client() -> Optional[googlemaps.Client]:
65+
def get_google_maps_client(user_api_key: Optional[str] = None) -> Optional[googlemaps.Client]:
6666
"""
6767
Get Google Maps client with API key from Secrets Manager (with caching)
6868
69+
Args:
70+
user_api_key: Optional user-provided API key (takes priority)
71+
6972
Returns googlemaps.Client instance
7073
"""
7174
global _credentials_cache, _gmaps_client
7275

76+
# If user provides their own key, create a new client (don't use cache)
77+
if user_api_key:
78+
logger.info("Creating Google Maps client with user-provided API key")
79+
return googlemaps.Client(key=user_api_key)
80+
7381
# Return cached client if available
7482
if _gmaps_client:
7583
return _gmaps_client
@@ -115,13 +123,23 @@ def get_google_maps_client() -> Optional[googlemaps.Client]:
115123
return None
116124

117125

126+
def _extract_user_maps_api_key(params: Dict[str, Any]) -> Optional[str]:
127+
"""Extract user-provided Google Maps API key from params"""
128+
user_api_keys = params.pop('__user_api_keys', None)
129+
if user_api_keys and user_api_keys.get('google_maps_api_key'):
130+
logger.info("Using user-provided Google Maps API key")
131+
return user_api_keys['google_maps_api_key']
132+
return None
133+
134+
118135
def search_places(params: Dict[str, Any]) -> Dict[str, Any]:
119136
"""
120137
Text-based place search (e.g., "restaurants in Seoul")
121138
122139
Uses Places API Text Search
123140
"""
124-
gmaps = get_google_maps_client()
141+
user_api_key = _extract_user_maps_api_key(params)
142+
gmaps = get_google_maps_client(user_api_key)
125143
if not gmaps:
126144
return error_response("Failed to initialize Google Maps client")
127145

@@ -194,7 +212,8 @@ def search_nearby_places(params: Dict[str, Any]) -> Dict[str, Any]:
194212
195213
Uses Places API Nearby Search
196214
"""
197-
gmaps = get_google_maps_client()
215+
user_api_key = _extract_user_maps_api_key(params)
216+
gmaps = get_google_maps_client(user_api_key)
198217
if not gmaps:
199218
return error_response("Failed to initialize Google Maps client")
200219

@@ -271,7 +290,8 @@ def get_place_details(params: Dict[str, Any]) -> Dict[str, Any]:
271290
272291
Uses Places API Place Details
273292
"""
274-
gmaps = get_google_maps_client()
293+
user_api_key = _extract_user_maps_api_key(params)
294+
gmaps = get_google_maps_client(user_api_key)
275295
if not gmaps:
276296
return error_response("Failed to initialize Google Maps client")
277297

@@ -354,7 +374,8 @@ def get_directions(params: Dict[str, Any]) -> Dict[str, Any]:
354374
355375
Uses Directions API
356376
"""
357-
gmaps = get_google_maps_client()
377+
user_api_key = _extract_user_maps_api_key(params)
378+
gmaps = get_google_maps_client(user_api_key)
358379
if not gmaps:
359380
return error_response("Failed to initialize Google Maps client")
360381

@@ -435,7 +456,8 @@ def geocode_address(params: Dict[str, Any]) -> Dict[str, Any]:
435456
436457
Uses Geocoding API
437458
"""
438-
gmaps = get_google_maps_client()
459+
user_api_key = _extract_user_maps_api_key(params)
460+
gmaps = get_google_maps_client(user_api_key)
439461
if not gmaps:
440462
return error_response("Failed to initialize Google Maps client")
441463

@@ -494,7 +516,8 @@ def reverse_geocode(params: Dict[str, Any]) -> Dict[str, Any]:
494516
495517
Uses Reverse Geocoding API
496518
"""
497-
gmaps = get_google_maps_client()
519+
user_api_key = _extract_user_maps_api_key(params)
520+
gmaps = get_google_maps_client(user_api_key)
498521
if not gmaps:
499522
return error_response("Failed to initialize Google Maps client")
500523

agent-blueprint/agentcore-gateway-stack/lambda-functions/google-search/lambda_function.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,24 @@ def get_google_credentials() -> Optional[Dict[str, str]]:
101101
def google_web_search(params: Dict[str, Any]) -> Dict[str, Any]:
102102
"""Execute Google web search with optional image results"""
103103

104-
# Get credentials
105-
credentials = get_google_credentials()
104+
# Check for user-provided API keys first (from __user_api_keys)
105+
user_api_keys = params.pop('__user_api_keys', None)
106+
credentials = None
107+
108+
if user_api_keys:
109+
user_api_key = user_api_keys.get('google_api_key')
110+
user_search_engine_id = user_api_keys.get('google_search_engine_id')
111+
if user_api_key and user_search_engine_id:
112+
credentials = {
113+
'api_key': user_api_key,
114+
'search_engine_id': user_search_engine_id
115+
}
116+
logger.info("Using user-provided Google API credentials")
117+
118+
# Fall back to default credentials from Secrets Manager
119+
if not credentials:
120+
credentials = get_google_credentials()
121+
106122
if not credentials:
107123
return error_response("Failed to get Google API credentials")
108124

agent-blueprint/agentcore-gateway-stack/lambda-functions/tavily/lambda_function.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,18 @@ def get_tavily_api_key() -> Optional[str]:
9696
def tavily_search(params: Dict[str, Any]) -> Dict[str, Any]:
9797
"""Execute Tavily web search"""
9898

99-
# Get API key from Secrets Manager (with caching)
100-
api_key = get_tavily_api_key()
99+
# Check for user-provided API key first (from __user_api_keys)
100+
user_api_keys = params.pop('__user_api_keys', None)
101+
api_key = None
102+
103+
if user_api_keys and user_api_keys.get('tavily_api_key'):
104+
api_key = user_api_keys['tavily_api_key']
105+
logger.info("Using user-provided Tavily API key")
106+
107+
# Fall back to default API key from Secrets Manager
108+
if not api_key:
109+
api_key = get_tavily_api_key()
110+
101111
if not api_key:
102112
return error_response("Failed to get Tavily API key")
103113

@@ -174,8 +184,18 @@ def tavily_search(params: Dict[str, Any]) -> Dict[str, Any]:
174184
def tavily_extract(params: Dict[str, Any]) -> Dict[str, Any]:
175185
"""Extract content from URLs using Tavily"""
176186

177-
# Get API key from Secrets Manager (with caching)
178-
api_key = get_tavily_api_key()
187+
# Check for user-provided API key first (from __user_api_keys)
188+
user_api_keys = params.pop('__user_api_keys', None)
189+
api_key = None
190+
191+
if user_api_keys and user_api_keys.get('tavily_api_key'):
192+
api_key = user_api_keys['tavily_api_key']
193+
logger.info("Using user-provided Tavily API key")
194+
195+
# Fall back to default API key from Secrets Manager
196+
if not api_key:
197+
api_key = get_tavily_api_key()
198+
179199
if not api_key:
180200
return error_response("Failed to get Tavily API key")
181201

agent-blueprint/agentcore-gateway-stack/scripts/deploy.sh

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,45 @@ if [ "$FORCE_REBUILD" = true ]; then
256256
fi
257257

258258
# ============================================================================
259-
# Step 5: Deploy to AWS (CodeBuild will build Lambda packages automatically)
259+
# Step 5: Upload Lambda Sources to S3
260260
# ============================================================================
261261

262-
echo "☁️ Step 5: Deploying to AWS..."
262+
echo "📤 Step 5: Uploading Lambda sources to S3..."
263+
echo ""
264+
265+
# Get AWS account ID
266+
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text 2>/dev/null || echo "")
267+
LAMBDA_BUCKET="${PROJECT_NAME}-gateway-lambdas-${AWS_ACCOUNT_ID}-${AWS_REGION}"
268+
269+
# Check if bucket exists, create if not
270+
if ! aws s3 ls "s3://${LAMBDA_BUCKET}" > /dev/null 2>&1; then
271+
echo " Creating S3 bucket: ${LAMBDA_BUCKET}"
272+
aws s3 mb "s3://${LAMBDA_BUCKET}" --region "$AWS_REGION" 2>/dev/null || true
273+
fi
274+
275+
# Upload all Lambda sources
276+
LAMBDA_FUNCTIONS_DIR="$PROJECT_ROOT/lambda-functions"
277+
for func in tavily wikipedia arxiv google-search google-maps finance weather; do
278+
if [ -d "$LAMBDA_FUNCTIONS_DIR/$func" ]; then
279+
echo " Uploading $func..."
280+
aws s3 sync "$LAMBDA_FUNCTIONS_DIR/$func/" "s3://${LAMBDA_BUCKET}/source/$func/" \
281+
--exclude "__pycache__/*" \
282+
--exclude "*.pyc" \
283+
--exclude ".DS_Store" \
284+
--exclude "build/*" \
285+
--exclude "*.zip" \
286+
--delete \
287+
--quiet
288+
fi
289+
done
290+
echo " ✅ All Lambda sources uploaded"
291+
echo ""
292+
293+
# ============================================================================
294+
# Step 6: Deploy to AWS (CodeBuild will build Lambda packages automatically)
295+
# ============================================================================
296+
297+
echo "☁️ Step 6: Deploying to AWS..."
263298
echo ""
264299
echo "ℹ️ Lambda functions will be built automatically by CodeBuild during deployment"
265300
echo " This may take 5-10 minutes for the first deployment."
@@ -268,10 +303,10 @@ npm run deploy
268303
echo ""
269304

270305
# ============================================================================
271-
# Step 6: Retrieve Gateway Information
306+
# Step 7: Retrieve Gateway Information
272307
# ============================================================================
273308

274-
echo "📡 Step 6: Retrieving Gateway information..."
309+
echo "📡 Step 7: Retrieving Gateway information..."
275310
echo ""
276311

277312
# Get Gateway URL from Parameter Store
@@ -299,7 +334,7 @@ echo "Region: $AWS_REGION"
299334
echo ""
300335

301336
# ============================================================================
302-
# Step 7: Verify API Key Configuration
337+
# Step 8: Verify API Key Configuration
303338
# ============================================================================
304339

305340
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

agent-blueprint/chatbot-deployment/infrastructure/lib/chatbot-stack.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,16 @@ export class ChatbotStack extends cdk.Stack {
246246
})
247247
);
248248

249-
// Secrets Manager Access for Google Maps API Key
249+
// Secrets Manager Access for API Keys detection
250250
codeBuildRole.addToPolicy(
251251
new iam.PolicyStatement({
252252
effect: iam.Effect.ALLOW,
253253
actions: ['secretsmanager:GetSecretValue'],
254254
resources: [
255255
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:${projectName}/mcp/google-maps-credentials-*`,
256+
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:${projectName}/mcp/tavily-api-key-*`,
257+
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:${projectName}/mcp/google-credentials-*`,
258+
`arn:aws:secretsmanager:${this.region}:${this.account}:secret:${projectName}/nova-act-api-key-*`,
256259
],
257260
})
258261
);
@@ -288,6 +291,14 @@ export class ChatbotStack extends cdk.Stack {
288291
`GOOGLE_MAPS_SECRET=$(aws secretsmanager get-secret-value --secret-id "${projectName}/mcp/google-maps-credentials" --region ${this.region} --query SecretString --output text || echo "{}")`,
289292
`GOOGLE_MAPS_API_KEY=$(echo $GOOGLE_MAPS_SECRET | jq -r '.api_key // empty')`,
290293
'echo "Google Maps API Key: ${GOOGLE_MAPS_API_KEY:0:10}..." # Show first 10 chars only for security',
294+
'',
295+
'echo Detecting configured API keys from Secrets Manager...',
296+
'DEFAULT_KEYS=""',
297+
`if aws secretsmanager get-secret-value --secret-id "${projectName}/mcp/tavily-api-key" --region ${this.region} &>/dev/null; then DEFAULT_KEYS="tavily_api_key"; echo " ✓ Tavily"; fi`,
298+
`if aws secretsmanager get-secret-value --secret-id "${projectName}/mcp/google-credentials" --region ${this.region} &>/dev/null; then [ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"; DEFAULT_KEYS="\${DEFAULT_KEYS}google_api_key,google_search_engine_id"; echo " ✓ Google Search"; fi`,
299+
`if aws secretsmanager get-secret-value --secret-id "${projectName}/mcp/google-maps-credentials" --region ${this.region} &>/dev/null; then [ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"; DEFAULT_KEYS="\${DEFAULT_KEYS}google_maps_api_key"; echo " ✓ Google Maps"; fi`,
300+
`if aws secretsmanager get-secret-value --secret-id "${projectName}/nova-act-api-key" --region ${this.region} &>/dev/null; then [ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"; DEFAULT_KEYS="\${DEFAULT_KEYS}nova_act_api_key"; echo " ✓ Nova Act"; fi`,
301+
'echo "Default API keys: $DEFAULT_KEYS"',
291302
],
292303
},
293304
build: {
@@ -299,6 +310,7 @@ export class ChatbotStack extends cdk.Stack {
299310
'--build-arg NEXT_PUBLIC_COGNITO_USER_POOL_CLIENT_ID=$COGNITO_CLIENT_ID ' +
300311
'--build-arg NEXT_PUBLIC_STREAMING_API_URL=$ALB_DNS ' +
301312
'--build-arg NEXT_PUBLIC_GOOGLE_MAPS_EMBED_API_KEY=$GOOGLE_MAPS_API_KEY ' +
313+
'--build-arg NEXT_PUBLIC_DEFAULT_KEYS=$DEFAULT_KEYS ' +
302314
'-t frontend:latest .',
303315
`docker tag frontend:latest ${frontendRepository.repositoryUri}:latest`,
304316
],

agent-blueprint/chatbot-deployment/infrastructure/scripts/deploy.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,52 @@ else
210210
echo "🌐 CORS Origins: $CORS_ORIGINS"
211211
fi
212212

213+
# Detect configured API keys from Secrets Manager
214+
echo ""
215+
echo "🔑 Detecting configured API keys from Secrets Manager..."
216+
DEFAULT_KEYS=""
217+
218+
# Check Tavily
219+
if aws secretsmanager get-secret-value --secret-id "strands-agent-chatbot/mcp/tavily-api-key" --region "$AWS_REGION" &>/dev/null; then
220+
DEFAULT_KEYS="tavily_api_key"
221+
echo " ✓ Tavily API key found"
222+
fi
223+
224+
# Check Google Search
225+
if aws secretsmanager get-secret-value --secret-id "strands-agent-chatbot/mcp/google-credentials" --region "$AWS_REGION" &>/dev/null; then
226+
[ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"
227+
DEFAULT_KEYS="${DEFAULT_KEYS}google_api_key,google_search_engine_id"
228+
echo " ✓ Google Search credentials found"
229+
fi
230+
231+
# Check Google Maps
232+
if aws secretsmanager get-secret-value --secret-id "strands-agent-chatbot/mcp/google-maps-credentials" --region "$AWS_REGION" &>/dev/null; then
233+
[ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"
234+
DEFAULT_KEYS="${DEFAULT_KEYS}google_maps_api_key"
235+
echo " ✓ Google Maps credentials found"
236+
fi
237+
238+
# Check Nova Act
239+
if aws secretsmanager get-secret-value --secret-id "strands-agent-chatbot/nova-act-api-key" --region "$AWS_REGION" &>/dev/null; then
240+
[ -n "$DEFAULT_KEYS" ] && DEFAULT_KEYS="$DEFAULT_KEYS,"
241+
DEFAULT_KEYS="${DEFAULT_KEYS}nova_act_api_key"
242+
echo " ✓ Nova Act API key found"
243+
fi
244+
245+
if [ -n "$DEFAULT_KEYS" ]; then
246+
export NEXT_PUBLIC_DEFAULT_KEYS="$DEFAULT_KEYS"
247+
echo "✅ Default API keys: $DEFAULT_KEYS"
248+
249+
# Save to master .env file
250+
MAIN_ENV_FILE="../../.env"
251+
grep -v "^NEXT_PUBLIC_DEFAULT_KEYS=" "$MAIN_ENV_FILE" > "$MAIN_ENV_FILE.tmp" 2>/dev/null || touch "$MAIN_ENV_FILE.tmp"
252+
mv "$MAIN_ENV_FILE.tmp" "$MAIN_ENV_FILE"
253+
echo "NEXT_PUBLIC_DEFAULT_KEYS=$DEFAULT_KEYS" >> "$MAIN_ENV_FILE"
254+
else
255+
echo "⚠️ No default API keys found in Secrets Manager"
256+
fi
257+
echo ""
258+
213259
# Collect IP ranges for CIDR-based access control (if not using Cognito)
214260
if [ "$ENABLE_COGNITO" != "true" ]; then
215261
# Set default IP ranges if not configured

0 commit comments

Comments
 (0)