diff --git a/plugins/aws/example/.env.example b/plugins/aws/example/.env.example index 0ff6a087e..3d825e4cb 100644 --- a/plugins/aws/example/.env.example +++ b/plugins/aws/example/.env.example @@ -1,10 +1,14 @@ STREAM_API_KEY=your_stream_api_key_here STREAM_API_SECRET=your_stream_api_secret_here -AWS_BEARER_TOKEN_BEDROCK= +# AWS authentication (choose one approach): +# Option 1: AWS Profile (recommended for SSO/local dev) +AWS_PROFILE= + +# Option 2: Explicit credentials AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= FAL_KEY= CARTESIA_API_KEY= -DEEPGRAM_API_KEY= \ No newline at end of file +DEEPGRAM_API_KEY= diff --git a/plugins/aws/vision_agents/plugins/aws/aws_llm.py b/plugins/aws/vision_agents/plugins/aws/aws_llm.py index 54395c1a9..5a146ef2c 100644 --- a/plugins/aws/vision_agents/plugins/aws/aws_llm.py +++ b/plugins/aws/vision_agents/plugins/aws/aws_llm.py @@ -1,6 +1,6 @@ import asyncio -import os import logging +import os import time from typing import Optional, List, TYPE_CHECKING, Any, Dict, cast import json @@ -48,6 +48,7 @@ def __init__( aws_access_key_id: Optional[str] = None, aws_secret_access_key: Optional[str] = None, aws_session_token: Optional[str] = None, + aws_profile: Optional[str] = None, ): """ Initialize the BedrockLLM class. @@ -58,14 +59,17 @@ def __init__( aws_access_key_id: Optional AWS access key ID aws_secret_access_key: Optional AWS secret access key aws_session_token: Optional AWS session token + aws_profile: Optional AWS profile name (from ~/.aws/credentials or ~/.aws/config) """ super().__init__() self.events.register_events_from_module(events) self.model = model self._pending_tool_uses_by_index: Dict[int, Dict[str, Any]] = {} - # Initialize boto3 bedrock-runtime client - session_kwargs = {"region_name": region_name} + # Build boto3 Session kwargs + session_kwargs: Dict[str, Any] = {"region_name": region_name} + if aws_profile: + session_kwargs["profile_name"] = aws_profile if aws_access_key_id: session_kwargs["aws_access_key_id"] = aws_access_key_id if aws_secret_access_key: @@ -86,7 +90,8 @@ async def client(self) -> Any: if self._client is None: def _create_client(): - self._client = boto3.client("bedrock-runtime", **self._session_kwargs) + session = boto3.Session(**self._session_kwargs) + self._client = session.client("bedrock-runtime") await asyncio.to_thread(_create_client) return self._client diff --git a/plugins/aws/vision_agents/plugins/aws/aws_realtime.py b/plugins/aws/vision_agents/plugins/aws/aws_realtime.py index 94d91365e..883bcfd4a 100644 --- a/plugins/aws/vision_agents/plugins/aws/aws_realtime.py +++ b/plugins/aws/vision_agents/plugins/aws/aws_realtime.py @@ -18,7 +18,12 @@ ) from getstream.video.rtc import PcmData from getstream.video.rtc.audio_track import AudioStreamTrack -from smithy_aws_core.identity.environment import EnvironmentCredentialsResolver +import boto3 +from smithy_aws_core.identity.components import ( + AWSCredentialsIdentity, + AWSIdentityProperties, +) +from smithy_core.aio.interfaces.identity import IdentityResolver from vision_agents.core.agents.agent_types import AgentOptions from vision_agents.core.edge.types import Participant from vision_agents.core.llm import realtime @@ -34,6 +39,44 @@ FORCE_RECONNECT_IN_MINUTES = 7.0 +class Boto3CredentialsResolver( + IdentityResolver[AWSCredentialsIdentity, AWSIdentityProperties] +): + """IdentityResolver that delegates to boto3.Session for credential resolution. + + Supports the full boto3 credential chain: env vars, shared credentials files, + AWS profiles, SSO, EC2 instance profiles, etc. + """ + + def __init__(self, profile_name: Optional[str] = None) -> None: + self._session = boto3.Session(profile_name=profile_name) + self._cached: Optional[AWSCredentialsIdentity] = None + + async def get_identity( + self, *, properties: AWSIdentityProperties, **kwargs: Any + ) -> AWSCredentialsIdentity: + if self._cached is not None: + return self._cached + + credentials = self._session.get_credentials() + if not credentials: + raise ValueError("Unable to load AWS credentials via boto3") + + creds = credentials.get_frozen_credentials() + if not creds.access_key or not creds.secret_key: + raise ValueError("AWS credentials are incomplete") + + expiry = getattr(credentials, "_expiry_time", None) + + self._cached = AWSCredentialsIdentity( + access_key_id=creds.access_key, + secret_access_key=creds.secret_key, + session_token=creds.token or None, + expiration=expiry, + ) + return self._cached + + class RealtimeConnection: """Encapsulates a single AWS Bedrock bidirectional stream connection. @@ -154,6 +197,7 @@ def __init__( region_name: str = "us-east-1", voice_id: str = "matthew", reconnect_after_minutes=5.0, # Attempt to reconnect during silence after 5 minutes. Reconnect is forced after 7 minutes + aws_profile: Optional[str] = None, **kwargs, ) -> None: """ """ @@ -173,7 +217,9 @@ def __init__( config = Config( endpoint_uri=f"https://bedrock-runtime.{region_name}.amazonaws.com", region=region_name, - aws_credentials_identity_resolver=EnvironmentCredentialsResolver(), + aws_credentials_identity_resolver=Boto3CredentialsResolver( + profile_name=aws_profile + ), ) self.client = BedrockRuntimeClient(config=config) self.logger = logging.getLogger(__name__)