Skip to content

feat: add S3 file extension#2241

Draft
Tomas2D wants to merge 1 commit intomainfrom
feat/2136-s3-file-extension
Draft

feat: add S3 file extension#2241
Tomas2D wants to merge 1 commit intomainfrom
feat/2136-s3-file-extension

Conversation

@Tomas2D
Copy link
Collaborator

@Tomas2D Tomas2D commented Feb 26, 2026

Add S3 credentials extension and file storage examples

Introduces S3CredentialsExtensionSpec to the SDK and adds two end-to-end examples covering both S3 file storage approaches.

SDK — new credentials extension

Added s3_credentials.py (split from s3.py to keep modes separate):

  • S3CredentialsExtensionMetadata — STS-style credential bundle (endpoint, bucket, access key, secret, session token, prefix, TTL). Sensitive fields are redacted in logs/telemetry via @field_serializer + redact_str().
  • S3CredentialsExtensionServer — Agent-side; uses aioboto3 with injected credentials to put_object and generate a presigned GET URL. Returns a plain HTTPS URL (no S3 SDK required by caller).
  • S3CredentialsExtensionClientmetadata() for demo/dev (long-lived keys) and from_sts(role_arn=...) for production (AssumeRole with an inline policy scoped to contexts/{context_id}/*).

Example: s3-translation-presigned

Shows the presigned URL approach (S3ExtensionSpec). The client predeclares output “slots,” generates PUT+GET URLs, and passes them in metadata. The agent writes via HTTPS without S3 credentials. Produces two files (reversed text + stats) to demonstrate multi-slot usage. Notes limitations and when to prefer credentials.

Example: s3-translation-credentials

Shows the credentials approach (S3CredentialsExtensionSpec). The client injects short-lived, IAM-scoped credentials; the agent uploads freely within contexts/{context_id}/—isolation enforced by IAM, not app code.

Both examples include uv run server / uv run client, .env.example for local MinIO, and a README with setup, flow, and tradeoffs.

Ref: #2136
Signed-off-by: Tomas Dvorak <toomas2d@gmail.com>
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 26, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Tomas2D, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the SDK's capabilities for S3 file storage by introducing a new extension that allows agents to perform S3 operations using dynamically injected, IAM-scoped credentials. This provides a robust and secure alternative to the existing presigned URL method, particularly for scenarios requiring on-demand uploads or fine-grained access control. The changes are accompanied by two detailed examples that showcase both approaches, making it easier for developers to integrate S3 file handling into their agent workflows.

Highlights

  • New S3 Credentials Extension: Introduced S3CredentialsExtensionSpec to the SDK, enabling agents to handle S3 file operations using injected, short-lived, IAM-scoped credentials. This provides a more secure and flexible approach compared to presigned URLs for certain use cases.
  • Enhanced S3 File Storage Examples: Added two comprehensive end-to-end examples: s3-translation-presigned and s3-translation-credentials. These examples demonstrate both the presigned URL approach (where the client pre-generates URLs) and the new direct credential injection method, illustrating their respective workflows and tradeoffs.
  • Secure Credential Handling: Implemented S3CredentialsExtensionMetadata which uses @field_serializer and redact_str() to automatically redact sensitive fields (like access keys and session tokens) in logs and telemetry, enhancing security.
  • Flexible Client-Side Credential Management: The S3CredentialsExtensionClient now supports both demo usage with long-lived keys and production usage with from_sts(role_arn=...) for assuming IAM roles and obtaining short-lived credentials, ensuring namespace isolation enforced by IAM policies.
  • Dependency Management Update: Added aioboto3 as an optional dependency for S3 functionality, allowing users to install S3-related features only when needed via pip install 'agentstack-sdk[s3]'.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/agentstack-sdk-py/pyproject.toml
    • Added 's3' as an optional dependency group for 'aioboto3'.
  • apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/init.py
    • Imported the new 'storage' module to expose S3 extensions.
  • apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/storage/init.py
    • Added initialization file for the storage extensions, importing 's3' and 's3_credentials' modules.
  • apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/storage/s3.py
    • Added new classes for S3 presigned URL functionality, including S3UploadSlot, S3ExtensionMetadata, S3ExtensionSpec, S3ExtensionServer, and S3ExtensionClient.
  • apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/storage/s3_credentials.py
    • Added new classes for S3 credentials functionality, including S3CredentialsExtensionMetadata, S3CredentialsExtensionSpec, S3CredentialsExtensionServer, and S3CredentialsExtensionClient.
  • apps/agentstack-sdk-py/uv.lock
    • Updated the lock file to include aioboto3 and its transitive dependencies (aiobotocore, aioitertools, boto3, botocore, jmespath, python-dateutil, s3transfer, six) for the new S3 optional dependency.
  • examples/agent-integration/files/s3-translation-credentials/.env.example
    • Added example environment variables for S3 credentials configuration.
  • examples/agent-integration/files/s3-translation-credentials/README.md
    • Added documentation for the S3 credentials translation example, detailing its workflow, demo vs. production usage, prerequisites, setup, and extension classes.
  • examples/agent-integration/files/s3-translation-credentials/input.txt
    • Added a sample input text file for the S3 credentials translation example.
  • examples/agent-integration/files/s3-translation-credentials/pyproject.toml
    • Added project configuration for the S3 credentials translation example, including dependencies and script entry points.
  • examples/agent-integration/files/s3-translation-credentials/src/s3_translation_credentials/init.py
    • Added an empty initialization file for the S3 credentials translation example.
  • examples/agent-integration/files/s3-translation-credentials/src/s3_translation_credentials/agent.py
    • Added the agent implementation for the S3 credentials translation example, which processes files using injected S3 credentials.
  • examples/agent-integration/files/s3-translation-credentials/src/s3_translation_credentials/client.py
    • Added the client implementation for the S3 credentials translation example, demonstrating how to send files with S3 credentials metadata.
  • examples/agent-integration/files/s3-translation-presigned/.env.example
    • Added example environment variables for S3 presigned URL configuration.
  • examples/agent-integration/files/s3-translation-presigned/README.md
    • Added documentation for the S3 presigned URL translation example, explaining slots, on-demand uploads, prerequisites, setup, and extension classes.
  • examples/agent-integration/files/s3-translation-presigned/input.txt
    • Added a sample input text file for the S3 presigned URL translation example.
  • examples/agent-integration/files/s3-translation-presigned/pyproject.toml
    • Added project configuration for the S3 presigned URL translation example, including dependencies and script entry points.
  • examples/agent-integration/files/s3-translation-presigned/src/s3_translation_presigned/init.py
    • Added an empty initialization file for the S3 presigned URL translation example.
  • examples/agent-integration/files/s3-translation-presigned/src/s3_translation_presigned/agent.py
    • Added the agent implementation for the S3 presigned URL translation example, which processes files and uploads results to pre-allocated slots.
  • examples/agent-integration/files/s3-translation-presigned/src/s3_translation_presigned/client.py
    • Added the client implementation for the S3 presigned URL translation example, demonstrating how to create upload slots and send files.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Tomas2D Tomas2D marked this pull request as draft February 26, 2026 14:54
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces S3 file storage extensions with two distinct approaches: presigned URLs and direct credential injection. The code is well-structured, with a clear separation of concerns between the client and server implementations for each method. The inclusion of comprehensive examples is also a great addition. My review focuses on improving maintainability by reducing code duplication and enhancing efficiency in handling S3 bucket checks.

Comment on lines +134 to +140
try:
await s3.head_bucket(Bucket=self._config.bucket)
except ClientError as e:
if e.response["Error"]["Code"] in ("404", "NoSuchBucket"):
await s3.create_bucket(Bucket=self._config.bucket)
else:
raise
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The check for bucket existence is performed on every call to create_upload_slot. When creating multiple upload slots concurrently (e.g., with asyncio.gather), this can lead to redundant head_bucket calls and a potential race condition where multiple coroutines attempt to create the same bucket if it doesn't exist. While create_bucket is generally idempotent, this approach is inefficient.

Consider performing this check only once per S3ExtensionClient instance. You could use an asyncio.Lock and a flag to ensure the check is performed only by the first coroutine that needs it.

Comment on lines +78 to +143
async def upload(self, filename: str, content: bytes, content_type: str = "application/octet-stream") -> str:
"""PUT content at {prefix}{filename} and return a presigned GET URL."""
if not self.data:
raise ExtensionError(self.spec, "S3 credentials extension metadata was not provided")

try:
import aioboto3
except ImportError as e:
raise ImportError(
"aioboto3 is required for S3CredentialsExtensionServer. "
"Install it with: pip install 'agentstack-sdk[s3]'"
) from e

from botocore.config import Config

creds = self.data
key = f"{creds.prefix}{filename}"
session = aioboto3.Session()
async with session.client(
"s3",
endpoint_url=creds.endpoint,
aws_access_key_id=creds.access_key_id,
aws_secret_access_key=creds.secret_access_key,
aws_session_token=creds.session_token,
region_name=creds.region,
use_ssl=creds.use_ssl,
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
) as s3:
await s3.put_object(Bucket=creds.bucket, Key=key, Body=content, ContentType=content_type)
download_url = await s3.generate_presigned_url(
"get_object",
Params={"Bucket": creds.bucket, "Key": key},
ExpiresIn=creds.ttl,
)
return download_url

async def download(self, key: str) -> bytes:
"""GET object at {prefix}{key} using injected credentials."""
if not self.data:
raise ExtensionError(self.spec, "S3 credentials extension metadata was not provided")

try:
import aioboto3
except ImportError as e:
raise ImportError(
"aioboto3 is required for S3CredentialsExtensionServer. "
"Install it with: pip install 'agentstack-sdk[s3]'"
) from e

from botocore.config import Config

creds = self.data
full_key = f"{creds.prefix}{key}"
session = aioboto3.Session()
async with session.client(
"s3",
endpoint_url=creds.endpoint,
aws_access_key_id=creds.access_key_id,
aws_secret_access_key=creds.secret_access_key,
aws_session_token=creds.session_token,
region_name=creds.region,
use_ssl=creds.use_ssl,
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
) as s3:
resp = await s3.get_object(Bucket=creds.bucket, Key=full_key)
return await resp["Body"].read()
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The upload and download methods contain nearly identical code for initializing the aioboto3 client. This duplication can be avoided by extracting the client setup into a private helper method, for instance, an async context manager. This would improve code maintainability and reduce redundancy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant