Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions plugins/aws/example/.env.example
Original file line number Diff line number Diff line change
@@ -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=
DEEPGRAM_API_KEY=
13 changes: 9 additions & 4 deletions plugins/aws/vision_agents/plugins/aws/aws_llm.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Comment on lines +69 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does the AWS boto3 bedrock-runtimeclient support authenticating with an API key mapped toaws_session_token, or should profile / standard AWS credentials remain the only supported auth inputs here?

💡 Result:

No—aws_session_token in boto3/botocore is only the third value in the standard AWS credential triple (access key ID + secret access key + session token) used for SigV4-signed requests; it’s not a generic “API key” slot, and you can’t authenticate to bedrock-runtime by stuffing a Bedrock API key into aws_session_token. [1][2]

If you want API-key-style auth for Amazon Bedrock Runtime, AWS provides Bedrock API keys that are sent as a Bearer token (for example via the environment variable AWS_BEARER_TOKEN_BEDROCK, or an Authorization: Bearer ... header). With recent SDKs, boto3 will work when that env var is set and you create a normal boto3.client("bedrock-runtime", ...). [1][3]

So the supported auth inputs are:

  • Standard AWS credentials / profiles / default credential chain (SigV4), optionally with aws_session_token as part of temporary creds. [2]
  • Bedrock API key bearer-token flow via AWS_BEARER_TOKEN_BEDROCK (not via aws_session_token). [1][3]

Sources
[1] Amazon Bedrock User Guide – “Use an Amazon Bedrock API key” (AWS_BEARER_TOKEN_BEDROCK, Authorization: Bearer ..., boto3 example)
[2] Boto3 credentials guide (defines aws_access_key_id/aws_secret_access_key/aws_session_token as AWS credentials)
[3] AWS ML Blog (Jul 8, 2025) – Bedrock API keys; SDKs detect AWS_BEARER_TOKEN_BEDROCK and support API-key authentication


Remove AWS_BEDROCK_API_KEY override to aws_session_token—it's not a valid Bedrock authentication method.

The code at lines 80–81 maps AWS_BEDROCK_API_KEY to aws_session_token, but aws_session_token in boto3 is only the session-token component of standard AWS credentials (SigV4), not a slot for Bedrock API keys. According to AWS Bedrock documentation, Bedrock API keys should be passed via the AWS_BEARER_TOKEN_BEDROCK environment variable, which boto3 detects and uses automatically. The current code both misuses the boto3 API and clobbers any profile-derived credentials set by the new aws_profile path.

Either remove this block entirely if only standard AWS credentials should be supported, or replace it with proper Bedrock API key handling via AWS_BEARER_TOKEN_BEDROCK.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/aws/vision_agents/plugins/aws/aws_llm.py` around lines 69 - 72, The
code currently injects AWS_BEDROCK_API_KEY into boto3 session_kwargs as
aws_session_token which is invalid and can override profile credentials; remove
the block that maps os.environ.get("AWS_BEDROCK_API_KEY") to
session_kwargs["aws_session_token"] in aws_llm.py so session creation uses only
region_name and optional aws_profile, or if Bedrock bearer tokens are required,
instead rely on AWS_BEARER_TOKEN_BEDROCK being set in the environment (do not
place it into session_kwargs) and ensure session creation logic around
session_kwargs, region_name, and aws_profile remains unchanged.

if aws_access_key_id:
session_kwargs["aws_access_key_id"] = aws_access_key_id
if aws_secret_access_key:
Expand All @@ -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
Expand Down
50 changes: 48 additions & 2 deletions plugins/aws/vision_agents/plugins/aws/aws_realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Comment on lines +51 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For boto3/botocore sessions that use SSO, assume-role, or instance-profile credentials, do credentials auto-refresh, and is _expiry_time on the credentials object considered a private implementation detail?

💡 Result:

  • Do SSO / assume-role / instance-profile creds auto-refresh? Yes—when they come from botocore’s refreshable credential providers, botocore will keep using cached temporary credentials until near/at expiration and then refresh them automatically (i.e., you don’t normally recreate the Session). This is the documented behavior for temporary credentials in boto3. (docs.aws.amazon.com)

    • AssumeRole (from ~/.aws/config, e.g., role_arn) is handled by botocore’s assume-role provider in the default credential chain, which uses refreshable credentials under the hood. (github.com)
    • Instance profile (EC2 IMDS) / container task roles are also providers in the default chain and are refreshable. (github.com)
    • SSO / IAM Identity Center profiles are provided by SSOProvider in the profile provider chain and are designed to refresh as long as the underlying SSO token/session is still valid; if that token is expired/invalid, refresh can fail and you’ll need to re-auth (e.g., aws sso login). (github.com)
  • Is _expiry_time private? Yes. In botocore it’s an underscore-prefixed attribute (self._expiry_time) on RefreshableCredentials, not a documented/stable public API, and botocore issue discussions indicate callers want (but don’t have) a public and stable expiration interface—so depending on _expiry_time is relying on an internal implementation detail. (github.com)

Citations:


Remove credential caching to enable boto3's automatic refresh.

Caching AWSCredentialsIdentity in self._cached prevents boto3/botocore from refreshing temporary credentials (SSO, assume-role, instance profiles). Even though botocore auto-refreshes when the session is reused, this code short-circuits that mechanism by caching the frozen credentials from the first call and never calling session.get_credentials() again. Line 69 also violates the guideline against getattr by accessing botocore's private _expiry_time attribute.

Remove the cache and call session.get_credentials() on every get_identity() call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/aws/vision_agents/plugins/aws/aws_realtime.py` around lines 51 - 77,
Remove the credential caching and private-attribute access: delete self._cached
and any short-circuit return using it in __init__ and get_identity, so
get_identity always calls self._session.get_credentials() each time; remove use
of getattr(credentials, "_expiry_time", None) (do not read botocore's private
_expiry_time) and instead pass a safe value (e.g., None) for
AWSCredentialsIdentity.expiration when constructing the AWSCredentialsIdentity
in get_identity; keep validation of access_key/secret_key and session_token
handling as-is.



class RealtimeConnection:
"""Encapsulates a single AWS Bedrock bidirectional stream connection.

Expand Down Expand Up @@ -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:
""" """
Expand All @@ -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__)
Expand Down