Skip to content
Merged
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
47 changes: 39 additions & 8 deletions sampleapps/AnnotationsSample/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// SPDX-License-Identifier: Apache-2.0

using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using Amazon.S3;
using AWS.AgentCore;
using AnnotationsSample.Models;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace AnnotationsSample;

public class Agent(IChatClient chatClient, ILogger<Agent> logger)
public class Agent(IChatClient chatClient, IAmazonS3 s3Client, ILogger<Agent> logger)
{
[AgentCoreHandler]
public async Task<string> HandleInvocation(
Expand All @@ -20,7 +23,12 @@ public async Task<string> HandleInvocation(
logger.LogInformation("Invocation — SessionId={SessionId}, RequestId={RequestId}",
context.SessionId, context.RequestId);

var agent = chatClient.AsAIAgent(tools: [AIFunctionFactory.Create(GetWeather), AIFunctionFactory.Create(GetAppInfo)]);
var agent = chatClient.AsAIAgent(tools:
[
AIFunctionFactory.Create(GetWeather),
AIFunctionFactory.Create(GetAppInfo),
AIFunctionFactory.Create(GetS3BucketCount)
]);
var session = await agent.CreateSessionAsync(cancellationToken: cancellationToken);
var response = await agent.RunAsync(request.Prompt ?? "Hello!", session, cancellationToken: cancellationToken);

Expand All @@ -38,13 +46,36 @@ static string GetWeather([Description("The city or location to get weather for."
static string GetAppInfo()
{
var isAot = typeof(object).Assembly.Location == string.Empty;
return System.Text.Json.JsonSerializer.Serialize(new
return JsonSerializer.Serialize(new
{
appName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? "Unknown",
appName = Assembly.GetEntryAssembly()?.GetName().Name ?? "Unknown",
isNativeAot = isAot,
framework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription,
architecture = System.Runtime.InteropServices.RuntimeInformation.OSArchitecture.ToString(),
os = System.Runtime.InteropServices.RuntimeInformation.OSDescription
framework = RuntimeInformation.FrameworkDescription,
architecture = RuntimeInformation.OSArchitecture.ToString(),
os = RuntimeInformation.OSDescription
});
}

[Description("Returns the number of S3 buckets in the AWS account. Use this when asked about S3 buckets or AWS resources. This uses the AWS SDK credential chain to authenticate.")]
async Task<string> GetS3BucketCount()
{
try
{
var response = await s3Client.ListBucketsAsync();
return JsonSerializer.Serialize(new
{
bucketCount = response.Buckets.Count,
bucketNames = response.Buckets.Select(b => b.BucketName).ToList()
});
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new
{
error = ex.GetType().Name,
message = ex.Message,
innerError = ex.InnerException?.Message
});
}
}
}
1 change: 1 addition & 0 deletions sampleapps/AnnotationsSample/AnnotationsSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ProjectReference Include="..\..\src\AWS.AgentCore.SourceGenerator\AWS.AgentCore.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<PackageReference Include="AWSSDK.S3" Version="4.0.14" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions sampleapps/AnnotationsSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.S3;
using AWS.AgentCore;
using AWS.AgentCore.Extensions;
using Microsoft.AspNetCore.Builder;
Expand All @@ -16,5 +17,7 @@ public void ConfigureServices(WebApplicationBuilder builder)
{
options.ModelId = "global.anthropic.claude-opus-4-7";
});

builder.Services.AddAWSService<IAmazonS3>();
}
}
20 changes: 17 additions & 3 deletions test/AWS.AgentCore.IntegrationTests/Infrastructure/DockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,23 @@ public static async Task<string> PushToEcrAsync(
var fullUri = $"{ecrRepositoryUri}:{remoteTag}";
Console.Error.WriteLine($"[Docker] Tagging {localImageTag}:latest as {fullUri}");
await RunProcessAsync("docker", $"tag {localImageTag}:latest {fullUri}", ct: ct);
Console.Error.WriteLine($"[Docker] Pushing {fullUri}");
await RunProcessAsync("docker", $"{configFlag} push {fullUri}", ct: ct);
Console.Error.WriteLine($"[Docker] Push complete: {fullUri}");

// Push with retry — ECR pushes can fail with transient TCP resets
for (var attempt = 1; attempt <= 3; attempt++)
{
try
{
Console.Error.WriteLine($"[Docker] Pushing {fullUri} (attempt {attempt})");
await RunProcessAsync("docker", $"{configFlag} push {fullUri}", ct: ct);
Console.Error.WriteLine($"[Docker] Push complete: {fullUri}");
break;
}
catch (Exception ex) when (attempt < 3)
{
Console.Error.WriteLine($"[Docker] Push attempt {attempt} failed: {ex.Message}. Retrying in 5s...");
await Task.Delay(TimeSpan.FromSeconds(5), ct);
}
}

return fullUri;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@
"Resource": {
"Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
}
},
{
"Sid": "S3ListBuckets",
"Effect": "Allow",
"Action": ["s3:ListAllMyBuckets"],
"Resource": "*"
}
]
}
Expand Down
16 changes: 16 additions & 0 deletions test/AWS.AgentCore.IntegrationTests/NonStreamingAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ public async Task Invoke_AppInfoReportsNotNativeAot()
Assert.Contains("\"isNativeAot\":false", result.Message.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
}

[Fact]
public async Task Invoke_S3BucketCountReturnsNumber()
{
var ct = TestContext.Current.CancellationToken;
var result = await _invoker.InvokeAsync(
_fixture.RuntimeArn,
"Call the GetS3BucketCount tool and respond with ONLY the exact JSON it returns. Do not add any other text.",
ct);

Assert.Equal(200, result.HttpStatusCode);
// The tool must successfully call S3 — meaning credentials resolved correctly.
// If credentials fail, the tool returns {"error":"...", "message":"..."} instead.
Assert.Contains("bucketCount", result.Message, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("error", result.Message, StringComparison.OrdinalIgnoreCase);
}

public void Dispose() => _invoker.Dispose();
}

Expand Down
Loading