.NET: Add Foundry Memory Context Provider (#3522)

* Add Azure AI Foundry Memory Context Provider with unit tests

* Add FoundryMemory integration tests and sample application

* Fix ClearStoredMemoriesAsync to handle 404 gracefully and rename to EnsureStoredMemoriesDeletedAsync

* Refactor FoundryMemory: simplify architecture and add memory store creation

- Remove IFoundryMemoryOperations interface (was only for test mocking)
- Remove AIProjectClientMemoryOperations wrapper class
- Provider now directly uses AIProjectClient with internal extension methods
- Extension methods return actual response models instead of extracted values
- Remove WaitForUpdateCompletionAsync from provider (sample uses delay)
- Simplify EnsureMemoryStoreCreatedAsync to return Task instead of Task<bool>
- Add memory store creation with chat_model and embedding_model
- Add UpdateMemoriesResponse with SupersededBy and Error fields
- Simplify unit tests to focus on constructor validation and serialization
- Update sample to use simple delay for memory processing wait

* Add waiting operation for memory store updates

* Fix UTF-8 BOM encoding for FoundryMemory csproj files

* Update copilot instructions for UTF-8 BOM and fix sample API rename

* Fix UTF-8 BOM encoding for TestableAIProjectClient.cs

* Add missing response headers for TS

* Changing default embedding

* Using the SDK Models

* Program update

* Remove debugging code from sample

* Adapt FoundryMemoryProvider to new AIContextProvider API and add UTF-8 BOM instruction

- Override ProvideAIContextAsync/StoreAIContextAsync instead of removed virtual InvokingAsync/InvokedAsync
- Use ProviderSessionState<State> for session-scoped state management (matching Mem0Provider pattern)
- Replace constructor-based scope with stateInitializer delegate
- Remove Serialize method (no longer on base class)
- Add SearchInputMessageFilter, StorageInputMessageFilter, StateKey to options
- Update sample to use AIContextProviders list instead of AIContextProviderFactory
- Update unit and integration tests for new API
- Add UTF-8 BOM encoding and --tl:off instructions to dotnet/AGENTS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use DefaultAzureCredential in Foundry Memory sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review comments for FoundryMemoryProvider

- Move memoryStoreName from options to required constructor parameter
- Make FoundryMemoryProviderScope require non-null/whitespace scope in constructor
- Make Scope property read-only (getter only)
- Replace ConcurrentQueue with single last update ID to fix memory leak
- Only clear pending update ID after successful completion
- Add delete success logging
- Mark FoundryMemoryProvider with [Experimental] attribute
- Update unit tests for new API signatures

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use Throw.IfNullOrWhitespace for scope and memoryStoreName validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Roger Barreto
2026-02-20 11:25:06 +00:00
committed by GitHub
Unverified
parent 0086d38f58
commit 0e2fcb1c7f
18 changed files with 1331 additions and 0 deletions
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
namespace Microsoft.Agents.AI.FoundryMemory;
/// <summary>
/// Internal extension methods for <see cref="AIProjectClient"/> to provide MemoryStores helper operations.
/// </summary>
internal static class AIProjectClientExtensions
{
/// <summary>
/// Creates a memory store if it doesn't already exist.
/// </summary>
internal static async Task<bool> CreateMemoryStoreIfNotExistsAsync(
this AIProjectClient client,
string memoryStoreName,
string? description,
string chatModel,
string embeddingModel,
CancellationToken cancellationToken)
{
try
{
await client.MemoryStores.GetMemoryStoreAsync(memoryStoreName, cancellationToken).ConfigureAwait(false);
return false; // Store already exists
}
catch (ClientResultException ex) when (ex.Status == 404)
{
// Store doesn't exist, create it
}
MemoryStoreDefaultDefinition definition = new(chatModel, embeddingModel);
await client.MemoryStores.CreateMemoryStoreAsync(memoryStoreName, definition, description, cancellationToken: cancellationToken).ConfigureAwait(false);
return true;
}
}
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Agents.AI.FoundryMemory;
/// <summary>
/// Provides JSON serialization utilities for the Foundry Memory provider.
/// </summary>
internal static class FoundryMemoryJsonUtilities
{
/// <summary>
/// Gets the default JSON serializer options for Foundry Memory operations.
/// </summary>
public static JsonSerializerOptions DefaultOptions { get; } = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false,
TypeInfoResolver = FoundryMemoryJsonContext.Default
};
}
/// <summary>
/// Source-generated JSON serialization context for Foundry Memory types.
/// </summary>
[JsonSourceGenerationOptions(
JsonSerializerDefaults.General,
UseStringEnumConverter = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
WriteIndented = false)]
[JsonSerializable(typeof(FoundryMemoryProviderScope))]
[JsonSerializable(typeof(FoundryMemoryProvider.State))]
internal partial class FoundryMemoryJsonContext : JsonSerializerContext;
@@ -0,0 +1,440 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
using OpenAI.Responses;
namespace Microsoft.Agents.AI.FoundryMemory;
/// <summary>
/// Provides an Azure AI Foundry Memory backed <see cref="AIContextProvider"/> that persists conversation messages as memories
/// and retrieves related memories to augment the agent invocation context.
/// </summary>
/// <remarks>
/// The provider stores user, assistant and system messages as Foundry memories and retrieves relevant memories
/// for new invocations using the memory search endpoint. Retrieved memories are injected as user messages
/// to the model, prefixed by a configurable context prompt.
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
public sealed class FoundryMemoryProvider : AIContextProvider
{
private const string DefaultContextPrompt = "## Memories\nConsider the following memories when answering user questions:";
private readonly ProviderSessionState<State> _sessionState;
private readonly string _contextPrompt;
private readonly string _memoryStoreName;
private readonly int _maxMemories;
private readonly int _updateDelay;
private readonly bool _enableSensitiveTelemetryData;
private readonly AIProjectClient _client;
private readonly ILogger<FoundryMemoryProvider>? _logger;
private string? _lastPendingUpdateId;
/// <summary>
/// Initializes a new instance of the <see cref="FoundryMemoryProvider"/> class.
/// </summary>
/// <param name="client">The Azure AI Project client configured for your Foundry project.</param>
/// <param name="memoryStoreName">The name of the memory store in Azure AI Foundry.</param>
/// <param name="stateInitializer">A delegate that initializes the provider state on the first invocation, providing the scope for memory storage and retrieval.</param>
/// <param name="options">Provider options.</param>
/// <param name="loggerFactory">Optional logger factory.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="client"/> or <paramref name="stateInitializer"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="memoryStoreName"/> is null or whitespace.</exception>
public FoundryMemoryProvider(
AIProjectClient client,
string memoryStoreName,
Func<AgentSession?, State> stateInitializer,
FoundryMemoryProviderOptions? options = null,
ILoggerFactory? loggerFactory = null)
: base(options?.SearchInputMessageFilter, options?.StorageInputMessageFilter)
{
Throw.IfNull(client);
Throw.IfNullOrWhitespace(memoryStoreName);
this._sessionState = new ProviderSessionState<State>(
ValidateStateInitializer(Throw.IfNull(stateInitializer)),
options?.StateKey ?? this.GetType().Name,
FoundryMemoryJsonUtilities.DefaultOptions);
FoundryMemoryProviderOptions effectiveOptions = options ?? new FoundryMemoryProviderOptions();
this._logger = loggerFactory?.CreateLogger<FoundryMemoryProvider>();
this._client = client;
this._contextPrompt = effectiveOptions.ContextPrompt ?? DefaultContextPrompt;
this._memoryStoreName = memoryStoreName;
this._maxMemories = effectiveOptions.MaxMemories;
this._updateDelay = effectiveOptions.UpdateDelay;
this._enableSensitiveTelemetryData = effectiveOptions.EnableSensitiveTelemetryData;
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
private static Func<AgentSession?, State> ValidateStateInitializer(Func<AgentSession?, State> stateInitializer) =>
session =>
{
State state = stateInitializer(session);
if (state is null)
{
throw new InvalidOperationException("State initializer must return a non-null state.");
}
return state;
};
/// <inheritdoc />
protected override async ValueTask<AIContext> ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
Throw.IfNull(context);
State state = this._sessionState.GetOrInitializeState(context.Session);
FoundryMemoryProviderScope scope = state.Scope;
List<ResponseItem> messageItems = (context.AIContext.Messages ?? [])
.Where(m => !string.IsNullOrWhiteSpace(m.Text))
.Select(m => (ResponseItem)ToResponseItem(m.Role, m.Text!))
.ToList();
if (messageItems.Count == 0)
{
return new AIContext();
}
try
{
MemorySearchOptions searchOptions = new(scope.Scope)
{
ResultOptions = new MemorySearchResultOptions { MaxMemories = this._maxMemories }
};
foreach (ResponseItem item in messageItems)
{
searchOptions.Items.Add(item);
}
ClientResult<MemoryStoreSearchResponse> result = await this._client.MemoryStores.SearchMemoriesAsync(
this._memoryStoreName,
searchOptions,
cancellationToken).ConfigureAwait(false);
MemoryStoreSearchResponse response = result.Value;
List<string> memories = response.Memories
.Select(m => m.MemoryItem?.Content ?? string.Empty)
.Where(c => !string.IsNullOrWhiteSpace(c))
.ToList();
string? outputMessageText = memories.Count == 0
? null
: $"{this._contextPrompt}\n{string.Join(Environment.NewLine, memories)}";
if (this._logger?.IsEnabled(LogLevel.Information) is true)
{
this._logger.LogInformation(
"FoundryMemoryProvider: Retrieved {Count} memories. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
memories.Count,
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
if (outputMessageText is not null && this._logger.IsEnabled(LogLevel.Trace))
{
this._logger.LogTrace(
"FoundryMemoryProvider: Search Results\nOutput:{MessageText}\nMemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
this.SanitizeLogData(outputMessageText),
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
}
}
return new AIContext
{
Messages = [new ChatMessage(ChatRole.User, outputMessageText)]
};
}
catch (ArgumentException)
{
throw;
}
catch (Exception ex)
{
if (this._logger?.IsEnabled(LogLevel.Error) is true)
{
this._logger.LogError(
ex,
"FoundryMemoryProvider: Failed to search for memories due to error. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
}
return new AIContext();
}
}
/// <inheritdoc />
protected override async ValueTask StoreAIContextAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
State state = this._sessionState.GetOrInitializeState(context.Session);
FoundryMemoryProviderScope scope = state.Scope;
try
{
List<ResponseItem> messageItems = context.RequestMessages
.Concat(context.ResponseMessages ?? [])
.Where(m => IsAllowedRole(m.Role) && !string.IsNullOrWhiteSpace(m.Text))
.Select(m => (ResponseItem)ToResponseItem(m.Role, m.Text!))
.ToList();
if (messageItems.Count == 0)
{
return;
}
MemoryUpdateOptions updateOptions = new(scope.Scope)
{
UpdateDelay = this._updateDelay
};
foreach (ResponseItem item in messageItems)
{
updateOptions.Items.Add(item);
}
ClientResult<MemoryUpdateResult> result = await this._client.MemoryStores.UpdateMemoriesAsync(
this._memoryStoreName,
updateOptions,
cancellationToken).ConfigureAwait(false);
MemoryUpdateResult response = result.Value;
if (response.UpdateId is not null)
{
Interlocked.Exchange(ref this._lastPendingUpdateId, response.UpdateId);
}
if (this._logger?.IsEnabled(LogLevel.Information) is true)
{
this._logger.LogInformation(
"FoundryMemoryProvider: Sent {Count} messages to update memories. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}', UpdateId: '{UpdateId}'.",
messageItems.Count,
this._memoryStoreName,
this.SanitizeLogData(scope.Scope),
response.UpdateId);
}
}
catch (Exception ex)
{
if (this._logger?.IsEnabled(LogLevel.Error) is true)
{
this._logger.LogError(
ex,
"FoundryMemoryProvider: Failed to send messages to update memories due to error. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
}
}
}
/// <summary>
/// Ensures all stored memories for the configured scope are deleted.
/// This method handles cases where the scope doesn't exist (no memories stored yet).
/// </summary>
/// <param name="session">The session containing the scope state to clear memories for.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public async Task EnsureStoredMemoriesDeletedAsync(AgentSession session, CancellationToken cancellationToken = default)
{
Throw.IfNull(session);
State state = this._sessionState.GetOrInitializeState(session);
FoundryMemoryProviderScope scope = state.Scope;
try
{
await this._client.MemoryStores.DeleteScopeAsync(this._memoryStoreName, scope.Scope, cancellationToken).ConfigureAwait(false);
if (this._logger?.IsEnabled(LogLevel.Information) is true)
{
this._logger.LogInformation(
"FoundryMemoryProvider: Deleted stored memories for scope. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
}
}
catch (ClientResultException ex) when (ex.Status == 404)
{
// Scope doesn't exist (no memories stored yet), nothing to delete
if (this._logger?.IsEnabled(LogLevel.Debug) is true)
{
this._logger.LogDebug(
"FoundryMemoryProvider: No memories to delete for scope. MemoryStore: '{MemoryStoreName}', Scope: '{Scope}'.",
this._memoryStoreName,
this.SanitizeLogData(scope.Scope));
}
}
}
/// <summary>
/// Ensures the memory store exists, creating it if necessary.
/// </summary>
/// <param name="chatModel">The deployment name of the chat model for memory processing.</param>
/// <param name="embeddingModel">The deployment name of the embedding model for memory search.</param>
/// <param name="description">Optional description for the memory store.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public async Task EnsureMemoryStoreCreatedAsync(
string chatModel,
string embeddingModel,
string? description = null,
CancellationToken cancellationToken = default)
{
bool created = await this._client.CreateMemoryStoreIfNotExistsAsync(
this._memoryStoreName,
description,
chatModel,
embeddingModel,
cancellationToken).ConfigureAwait(false);
if (created)
{
if (this._logger?.IsEnabled(LogLevel.Information) is true)
{
this._logger.LogInformation(
"FoundryMemoryProvider: Created memory store '{MemoryStoreName}'.",
this._memoryStoreName);
}
}
else
{
if (this._logger?.IsEnabled(LogLevel.Debug) is true)
{
this._logger.LogDebug(
"FoundryMemoryProvider: Memory store '{MemoryStoreName}' already exists.",
this._memoryStoreName);
}
}
}
/// <summary>
/// Waits for all pending memory update operations to complete.
/// </summary>
/// <remarks>
/// Memory extraction in Azure AI Foundry is asynchronous. This method polls the latest pending update
/// and returns when it has completed, failed, or been superseded. Since updates are processed in order,
/// completion of the latest update implies all prior updates have also been processed.
/// </remarks>
/// <param name="pollingInterval">The interval between status checks. Defaults to 5 seconds.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <exception cref="InvalidOperationException">Thrown if the update operation failed.</exception>
public async Task WhenUpdatesCompletedAsync(
TimeSpan? pollingInterval = null,
CancellationToken cancellationToken = default)
{
string? updateId = Volatile.Read(ref this._lastPendingUpdateId);
if (updateId is null)
{
return;
}
TimeSpan interval = pollingInterval ?? TimeSpan.FromSeconds(5);
await this.WaitForUpdateAsync(updateId, interval, cancellationToken).ConfigureAwait(false);
// Only clear the pending update ID after successful completion
Interlocked.CompareExchange(ref this._lastPendingUpdateId, null, updateId);
}
private async Task WaitForUpdateAsync(string updateId, TimeSpan interval, CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ClientResult<MemoryUpdateResult> result = await this._client.MemoryStores.GetUpdateResultAsync(
this._memoryStoreName,
updateId,
cancellationToken).ConfigureAwait(false);
MemoryUpdateResult response = result.Value;
MemoryStoreUpdateStatus status = response.Status;
if (this._logger?.IsEnabled(LogLevel.Debug) is true)
{
this._logger.LogDebug(
"FoundryMemoryProvider: Update status for '{UpdateId}': {Status}",
updateId,
status);
}
if (status == MemoryStoreUpdateStatus.Completed || status == MemoryStoreUpdateStatus.Superseded)
{
return;
}
if (status == MemoryStoreUpdateStatus.Failed)
{
throw new InvalidOperationException($"Memory update operation '{updateId}' failed: {response.ErrorDetails}");
}
if (status == MemoryStoreUpdateStatus.Queued || status == MemoryStoreUpdateStatus.InProgress)
{
await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
}
else
{
throw new InvalidOperationException($"Unknown update status '{status}' for update '{updateId}'.");
}
}
}
private static MessageResponseItem ToResponseItem(ChatRole role, string text)
{
if (role == ChatRole.Assistant)
{
return ResponseItem.CreateAssistantMessageItem(text);
}
if (role == ChatRole.System)
{
return ResponseItem.CreateSystemMessageItem(text);
}
return ResponseItem.CreateUserMessageItem(text);
}
private static bool IsAllowedRole(ChatRole role) =>
role == ChatRole.User || role == ChatRole.Assistant || role == ChatRole.System;
private string? SanitizeLogData(string? data) => this._enableSensitiveTelemetryData ? data : "<redacted>";
/// <summary>
/// Represents the state of a <see cref="FoundryMemoryProvider"/> stored in the <see cref="AgentSession.StateBag"/>.
/// </summary>
public sealed class State
{
/// <summary>
/// Initializes a new instance of the <see cref="State"/> class with the specified scope.
/// </summary>
/// <param name="scope">The scope to use for memory storage and retrieval.</param>
[JsonConstructor]
public State(FoundryMemoryProviderScope scope)
{
this.Scope = Throw.IfNull(scope);
}
/// <summary>
/// Gets the scope used for memory storage and retrieval.
/// </summary>
public FoundryMemoryProviderScope Scope { get; }
}
}
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.FoundryMemory;
/// <summary>
/// Options for configuring the <see cref="FoundryMemoryProvider"/>.
/// </summary>
public sealed class FoundryMemoryProviderOptions
{
/// <summary>
/// When providing memories to the model, this string is prefixed to the retrieved memories to supply context.
/// </summary>
/// <value>Defaults to "## Memories\nConsider the following memories when answering user questions:".</value>
public string? ContextPrompt { get; set; }
/// <summary>
/// Gets or sets the maximum number of memories to retrieve during search.
/// </summary>
/// <value>Defaults to 5.</value>
public int MaxMemories { get; set; } = 5;
/// <summary>
/// Gets or sets the delay in seconds before memory updates are processed.
/// </summary>
/// <remarks>
/// Setting to 0 triggers updates immediately without waiting for inactivity.
/// Higher values allow the service to batch multiple updates together.
/// </remarks>
/// <value>Defaults to 0 (immediate).</value>
public int UpdateDelay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
/// </summary>
/// <value>Defaults to <see langword="false"/>.</value>
public bool EnableSensitiveTelemetryData { get; set; }
/// <summary>
/// Gets or sets the key used to store the provider state in the session's <see cref="AgentSessionStateBag"/>.
/// </summary>
/// <value>Defaults to the provider's type name.</value>
public string? StateKey { get; set; }
/// <summary>
/// Gets or sets an optional filter function applied to request messages when building the search text to use when
/// searching for relevant memories during <see cref="AIContextProvider.InvokingAsync"/>.
/// </summary>
/// <value>
/// When <see langword="null"/>, the provider defaults to including only
/// <see cref="AgentRequestMessageSourceType.External"/> messages.
/// </value>
public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? SearchInputMessageFilter { get; set; }
/// <summary>
/// Gets or sets an optional filter function applied to request messages when determining which messages to
/// extract memories from during <see cref="AIContextProvider.InvokedAsync"/>.
/// </summary>
/// <value>
/// When <see langword="null"/>, the provider defaults to including only
/// <see cref="AgentRequestMessageSourceType.External"/> messages.
/// </value>
public Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? StorageInputMessageFilter { get; set; }
}
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.FoundryMemory;
/// <summary>
/// Allows scoping of memories for the <see cref="FoundryMemoryProvider"/>.
/// </summary>
/// <remarks>
/// Azure AI Foundry memories are scoped by a single string identifier that you control.
/// Common patterns include using a user ID, team ID, or other unique identifier
/// to partition memories across different contexts.
/// </remarks>
public sealed class FoundryMemoryProviderScope
{
/// <summary>
/// Initializes a new instance of the <see cref="FoundryMemoryProviderScope"/> class with the specified scope identifier.
/// </summary>
/// <param name="scope">The scope identifier used to partition memories. Must not be null or whitespace.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="scope"/> is null or whitespace.</exception>
public FoundryMemoryProviderScope(string scope)
{
Throw.IfNullOrWhitespace(scope);
this.Scope = scope;
}
/// <summary>
/// Gets the scope identifier used to partition memories.
/// </summary>
/// <remarks>
/// This value controls how memory is partitioned in the memory store.
/// Each unique scope maintains its own isolated collection of memory items.
/// For example, use a user ID to ensure each user has their own individual memory.
/// </remarks>
public string Scope { get; }
}
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionSuffix>preview</VersionSuffix>
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectSharedThrow>true</InjectSharedThrow>
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<PropertyGroup>
<!-- Disable packing until we are ready to release this as a nuget -->
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="OpenAI" />
</ItemGroup>
<PropertyGroup>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework - Azure AI Foundry Memory integration</Title>
<Description>Provides Azure AI Foundry Memory integration for Microsoft Agent Framework.</Description>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.FoundryMemory.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Shared.IntegrationTests;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
internal sealed class FoundryMemoryConfiguration
{
public string Endpoint { get; set; }
public string MemoryStoreName { get; set; }
public string? DeploymentName { get; set; }
}